Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel4 POST unexplained redirect to GET

Another I-did-not-sleep-enough question, I'm sure. I'm posting it as a sacrifice to the Elder God Murphy: as soon as I expose my moronity for all to see, I'm guaranteed to find by myself that answer that otherwise will elude me for hours (by way of further penance, I will then post the answer as well).

I have a HTML form that gets rendered as

<form method="post" id="mysearch" action="/search/?uid=1701">
    <input id="searchterm" type="text" name="query" />
</form>

The form can be submitted via jQuery $.POST with url of '/search' and data of { uid: '1701', query: $('#searchterm').val() } and it works.

If I press ENTER after entering something, and thus override the jQuery submission, the following happens:

  • a POST is issued to the server, as expected.
  • the Route::post('/search', function() {... does not get invoked.
  • a 301 Moved Permanently is returned
  • a GET with search parameters lost is issued to the URL specified by the Redirect
  • quite obviously, the search fails.

The 301 response looks like something by Laravel4, added explicitly:

HTTP/1.0 301 Moved Permanently
Date: Thu, 28 Nov 2013 14:05:29 GMT
Server: Apache
X-Powered-By: PHP/5.4.20
Cache-Control: no-cache
Location: http://development/search?uid=1701
Connection: close
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="refresh" content="1;url=http://development/search?uid=1701" />
    <title>Redirecting to http://development/search?uid=1701</title>
</head>
<body>
Redirecting to <a href="Redirecting to http://development/search?uid=1701">Redirecting to http://development/search?uid=1701</a>
</body>
</html>

This is not the same as this question, because there the redirect is expected and it's the answer to that which is undesired. Here it's the redirect itself that is generated for no reason that I can (for now) see.

I suspect that for some reason I'm triggering the "security redirect" described in this other answer, that is not triggered by jQuery (either because it puts everything in the POST while here I've one parameter in the URL and another in the POST, or because jQuery uses XHR).

I had thought it might be a CSRF defense, but that particular route isn't shielded. As a last resource, I'll CSRF-protect the route and add the token to the form, even if it looks a bit like voodoo to me. Something vaguely similar appears to be happening in Rails.

Workarounds

I've got not one, not two, but three workarounds that neatly sidestep the question of why is the above happening:

  • (most brutal) block the keyUp event in the form.
  • redirect the submit event of the form to jQuery
  • (most transparent) route the above event to $('#search-button').click()

...but I'd like to make without the button altogether (which I could do with jQuery) and without jQuery altogether. As well as understand what is happening here. I'm 99% certain I'm missing something obvious.

Debugging

I'm now going to grep -r "Redirecting to" * the whole framework source code (I expect to find something in Symfony/Components/HttpFoundation/ResponseRedirect) and doing step-by-step from there.

like image 461
LSerni Avatar asked Nov 28 '13 14:11

LSerni


1 Answers

TL;DR

ensure POST URLs do not end with a slash. -- but Laravel 4.1 users check the update below, first.

=======

When it works, it's not superstition - it's science :-(

as soon as I expose my moronity for all to see, I'm guaranteed to find by myself that answer that would otherwise elude me for hours

I will argue that Laravel4's HTML message could have been just a bit more informative.

The grep found as expected the origin of the redirect:

grep -r "Redirecting to" *
vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php:        <title>Redirecting to %1$s</title>
vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php:        Redirecting to <a href="%1$s">%1$s</a>.

at that point, a simple backtrace found the origin, very early in Laravel4 spin-up:

bootstrap/start.php:

...
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/

$app = new Illuminate\Foundation\Application;

$app->redirectIfTrailingSlash();

As soon as I saw the redirectIfTrailingSlash, I realized that the two URLs sent by the form and by jQuery were not the same:

... action="/search/?uid=1701">   <--- TRAILING SLASH AFTER 'search'

... url:   '/search',             <--- NO TRAILING SLASH
    data:  {
               uid  : 1701,
               query: $('#searchterm').val()
           },
...

Why this should happen I don't quite grok, but the solution is fiendishly simple:

remove the slash from the POST action field.

(and ensure .htaccess has no rule to consider the URL a "directory" and add back the slash - but if it had, jQuery would have failed too).

UPDATE

Apparently, the matter got reviewed in Laravel 4.1. The upgrade mentions

Removing Redirect Trailing Slash

In your bootstrap/start.php file, remove the call to $app->redirectIfTrailingSlash(). This method is no longer needed as this functionality is now handled by the .htaccess file included with the framework.

Next, replace your Apache .htaccess file with this new one that handles trailing slashes.

like image 200
LSerni Avatar answered Oct 01 '22 14:10

LSerni