Post data lost on 301 Moved Permanently

Posted on Tue 04 April 2017 in blog

What the hell with 301 Moved Permanently HTTP header!? I'll take an exemple to explain my ugly problem, take, a (very ugly cause it's PHP) index.php, it could be retrieved by using:

  • http://example.com/directory/index.php Got the page, 200 OK or
  • http://example.com/directory/ Got the page, 200 OK or
  • http://example.com/directory Got a 301 Moved Permanently Location:...
  • http://example.com/directory/ Just like expected ... but...

As you can see, on the captured HTTP headers below, when you POST data on a 301 target, you'll be redirected, but unfortunately you'll lost your POST data, even worse, your request can be reforged as a GET request! Let me show you an example: Request:

POST /directory HTTP/1.1
Host: example.com
[user agent, referer, cache control, origin, content type, accept, ...]
Content-Length: 7 foo=bar

Response:

HTTP/1.0 301 Moved Permanently
Location: http://example.com/directory/
...

Reforged request:

GET /directory/ HTTP/1.1
Host: example.com

Why my data isn't kept in the reforged one!? So let's read The RFC 2616 (about HTTP...)

10.3.2 301 Moved Permanently [...] The new permanent URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued. Note: When automatically redirecting a POST request after receiving a 301 status code, some existing HTTP/1.0 user agents will erroneously change it into a GET request.

Ahh, ok, so, for request other than GET or HEAD, the user agent MUST NOT automatically redirect to the request? Ugh, it's not the case in IE 6, 7, 8, Firefox, nor Chrome. And, about the security, while the server recieves the data, it can do everything, for example send it to another server directly. Then I'm wondering, why asking to the user if he allows his data to be transfered? Then, the RFC says an interesting thing...

Note: When automatically redirecting a POST request after receiving a 301 status code, some existing HTTP/1.0 user agents will erroneously change it into a GET request.

But, not only HTTP/1.0 user agents, but HTTP/1.1 user agents like browsers and frameworks (as, I encountred the problem firstly in the .NET Framework 3.5, in HttpWebRequest) )o:

Reforging as a GET is clearly not a good idea, but on the other hand, as POST requests are not idempotent, we're still happy they're not executed twice: Imagine the POST is "Pay for that shiny new laptop", the payment is executed, but later in the request execution, a 301 to another server is generated, if your client re-POST the same request on the other server, your payment may be executed twice... outch. Respecting idempotency looks clearly more important than generating dumb GET, harmless, requests.

Hope it helps...