Repository: inertiajs/inertia-laravel (php)

Issue:
Error in view data / url prop
Currently if the project has the base url `localhost/test` and we navigate to `some/sub/page` the result is a url change to `localhost/test/test/some/sub/page` in the brower by inertiajs. This url no longer matches the route we are currently at.

The issue seems to be that a path as part of the base url is not handled correctly.

This line in \Inertia\Response causes the issue.

`'url' => $request->getBaseUrl().$request->getRequestUri(),`
Subdirectory urls broken
#347 breaks Laravel projects that run in a subdirectory.

src/Response.php was changed at line 128 but nothing in the commit message details why. Symfony don't seem to have changed what these methods do and this now returns the incorrect URL which causes the Inertia client adapter to push an incorrect path to the history. So an Inertia app running at https://example.com/app will have https://example.com/app/app pushed to the history on the initial page render automatically.

The issue is also described here: https://laracasts.com/discuss/channels/laravel/root-directory-is-displayed-twice-in-url-on-load-and-reload

PR on the way

Pull request description:
Fix URL generation
There are several reports of issues with the URL returned by Inertia as a result of #333.

#333 was created to support the `X_FORWARDED_PREFIX` header value which can be added by reverse proxies that serve a web request under a sub-path that isn't normally visible to the underlying webserver.

Unfortunately, it seems to have created a scenario where a sub-path can be doubled up. I haven't been able to figure out exactly what scenario triggers this. There are several proposed PRs to fix it, but nothing I can reproduce. In any case, I'm assuming both `$request->baseUrl()` _and_ `$request->getRequestUri()` can contain the same sub path, which we concatenate.

Some solutions propose to use `$request->fullUrl()`, which constructs a full URL from the various pieces (without concatenating those specific methods). The problem is that this method includes the HTTP scheme, host, and port, and there may be users that depend on Inertia's URL not having those elements (e.g. styling active links). Other solutions combine various other methods to construct the URL, but they need to get into the weeds of adding query strings, etc.

This PR proposes to let Laravel/Symfony handle the URL construction intricacies. Unfortunately, there doesn't appear to be a method that gives us exactly what we need without the scheme and host, so I've just stripped those from the resulting URL.

Alternatives:

* #360 (This one manually constructs the URL but misses some logic from `fullUrl()`. It has a test that replicates the new issue, but not the potential regression.)
* #372 (This just restored the old behaviour, which had the proxy issue)
* #566 (this is very similar to mine, and given the existing tests pass, I'm happy to accept it)
* #576

Closes #359
Closes #421

Patch:
diff --git a/src/Response.php b/src/Response.php
index 953a8e5..e58da0f 100644
--- a/src/Response.php
+++ b/src/Response.php
@@ -7,6 +7,7 @@ use Illuminate\Support\Arr;
 use Illuminate\Http\Request;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\App;
+use Illuminate\Support\Str;
 use GuzzleHttp\Promise\PromiseInterface;
 use Illuminate\Support\Traits\Macroable;
 use Illuminate\Contracts\Support\Arrayable;
@@ -99,7 +100,7 @@ class Response implements Responsable
         $page = [
             'component' => $this->component,
             'props' => $props,
-            'url' => $request->getBaseUrl().$request->getRequestUri(),
+            'url' => Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'),
             'version' => $this->version,
         ];
 


Test patch:
diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
index f8035c5..b9e29cb 100644
--- a/tests/ResponseTest.php
+++ b/tests/ResponseTest.php
@@ -361,4 +361,43 @@ class ResponseTest extends TestCase
             $page['props']['resource']
         );
     }
+
+    public function test_the_page_url_is_prefixed_with_the_proxy_prefix(): void
+    {
+        if (version_compare(app()->version(), '7', '<')) {
+            $this->markTestSkipped('This test requires Laravel 7 or higher.');
+        }
+
+        Request::setTrustedProxies(['1.2.3.4'], Request::HEADER_X_FORWARDED_PREFIX);
+
+        $request = Request::create('/user/123', 'GET');
+        $request->server->set('REMOTE_ADDR', '1.2.3.4');
+        $request->headers->set('X_FORWARDED_PREFIX', '/sub/directory');
+
+        $user = ['name' => 'Jonathan'];
+        $response = new Response('User/Edit', ['user' => $user], 'app', '123');
+        $response = $response->toResponse($request);
+        $view = $response->getOriginalContent();
+        $page = $view->getData()['page'];
+
+        $this->assertInstanceOf(BaseResponse::class, $response);
+        $this->assertInstanceOf(View::class, $view);
+
+        $this->assertSame('/sub/directory/user/123', $page['url']);
+    }
+
+    public function test_the_page_url_doesnt_double_up(): void
+    {
+        $request = Request::create('/subpath/product/123', 'GET', [], [], [], [
+            'SCRIPT_FILENAME' => '/project/public/index.php',
+            'SCRIPT_NAME' => '/subpath/index.php',
+        ]);
+        $request->headers->add(['X-Inertia' => 'true']);
+
+        $response = new Response('Product/Show', []);
+        $response = $response->toResponse($request);
+        $page = $response->getData();
+
+        $this->assertSame('/subpath/product/123', $page->url);
+    }
 }