2

In my PHP software I have an self-update feature which sends a HTTP request with the POST method to a certain URL (to a PHP script). Now this URL has changed (I moved the script to another directory), but to stay backward compatible I want to use the script at the old URL to redirect the POST request to the new location. I tried to use the HTTP 307 status code but the second time PHP makes the request, it changes the method from POST to GET, although it must not do this (at least I though this is what the 307 code is for). I use PHP 5.4.29 on Windows 7 as Apache (2.2.27) module and I sniffed the traffic to make sure that HTTP 1.1 is used in the request and the response.

This is how I make a POST request:

<?php

$requestData = http_build_query(
    array(
        "param1" => "value1",
        // and so on...
    )
);

$requestOptions = array("http"=>
    array
    (
        "protocol_version"=>"1.1",
        "method"=>"POST",
        "header"=>array(
            "Content-type: application/x-www-form-urlencoded",
            "Connection: close",
        ),
        "content"=>$requestData,
    )
);

$requestContext = stream_context_create($requestOptions);

$serverResponse = @file_get_contents("http://localhost/old/long/path/update.php", false, $requestContext);

?>

I tried to redirect manually and automatically by PHP:

<?php

    // Redirect manually
    header("HTTP/1.1 307 Temporary Redirect");
    header("Location: http://localhost/update.php");

    // or redirect automatically
    header("Location: http://localhost/update.php", true, 307);

?>

According to the sniffed data, everything looks normal. HTTP 1.1 is used in request and response and code 307 is used. But the second time PHP sends the request (to the new location, still with HTTP 1.1, ..) it simply changes the method to GET and my POST payload is lost.

Again: This is not a user / browser redirection - I redirect PHP. I make my request myself and manually though my software and I want to redirect it to a new location. This has nothing to do with a security related topic.

wizard
  • 213
  • 1
  • 4
  • 8
  • 2
    Possible duplicate of - http://stackoverflow.com/questions/2865289/php-redirection-with-post-parameters ? – nevada_scout Nov 19 '15 at 11:28
  • No, it is not. The other question want's to redirect a user or his browser. I do not want to do this. – wizard Nov 19 '15 at 11:51

2 Answers2

2

It looks like file_get_contents does not repost the data, possibly for the reason highlighted by @daiscog.

Curl however will repost to the redirected url:

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'http://localhost/old/long/path/update.php');   
curl_setopt($ch, CURLOPT_POST, true);    
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);  
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);  
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);    
curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData);

$serverResponse = curl_exec($ch);

However it would make more sense to either handle this at the server level (eg an Apache url rewrite) or to simply include the new file in the old one:

//old.php
include('path/to/new.php');
Steve
  • 20,703
  • 5
  • 41
  • 67
  • Thanks for your recommendations. Too bad that file_get_contents() doesn't do that. I don't understand how I should tell PHP that there was a user confirmation... it's a little bit pointless. But I will use your recommendation and handle it at the server level myself: I made a proxy script that basically does exactly the same as the requesting script (like in my code example above), but instead I pass $_POST to http_build_query(). Now it seems to work as expected. cUrl is afaik an extension, which I can't expect to be always available. include() works only locally. – wizard Nov 19 '15 at 12:22
  • 1
    No problem. Re `"include only works locally"` why is that a problem? Is the old url on a different server than the new one? I was under the impression you have simply changed the update url on the same site, so both `old.php` and `new.php` would reside on the same server – Steve Nov 19 '15 at 12:27
  • Yep, you are absolutely right. This is what I asked in my question. I just also want to be prepared for this case too (I'm planing to create a new domain for all my updateable applications in the future). I will try both options and maybe even use the include option, since it might be a bit faster. – wizard Nov 19 '15 at 12:38
1

The W3C HTTP/1.1 specification for 307 states:

If the 307 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.

The point behind this is that it is unsafe for a server to tell a client to POST to a different URL as that may be a malicious attempt to get the client to send data somewhere the user did not intend, so POST redirections are not possible.

daiscog
  • 11,441
  • 6
  • 50
  • 62
  • Sorry, but this goes completely past my question. I do not redirect the user or his browser but just my own software. There is no malicious attempt possible in this situation and I can't (and I don't want to) ask the user in the middle of the process. I'm redirecting my own software and the destination is on the same server under my control. Also, you said that "POST redirections are not possible" - but this is not what the specification says, didn't it? It just says that the redirection with POST method must not happen without user confirmation. – wizard Nov 19 '15 at 11:46
  • You're right, it's not what the specification says. The specification says "unless it can be confirmed by the user" - so browser vendors have two options: 1) display a confirmation dialog asking the user to confirm the redirection, or 2) do not perform the redirection. It looks like they've all chosen 2 (or, in fact, they've chosen to perform the redirection as a GET, which isn't what the spec says!). I guess they would feel a confirmation dialog would be too technical for most day-to-day users, who wouldn't understand what a POST request is or what the connotations of the redirection are. – daiscog Nov 19 '15 at 12:31
  • I understand this. My problem however is that I do not want to redirect the user or his browser - I want to redirect PHP. There is no user interaction at all. This is an automatic process, where no user is involved, so I can't ask for any confirmation. – wizard Nov 19 '15 at 12:34
  • 1
    The principles are the same - it's still a client/server interaction. In your specific case, the PHP script making the request is the client, and it can't get "confirmation" from anywhere that the POST redirection should proceed. (Sorry this isn't very helpful, by the way!) FWIW, I agree with @Steve's solution to the problem: Place a script at the old URL which internally forwards the request to the new URL, either through an include or by making a new request like a proxy. Or maybe use .htaccess URL rewriting. – daiscog Nov 19 '15 at 12:43
  • Yep, that's what I said: a user confirmation makes no sense. And since it's my own software where I have the full control, such a confirmation is not needed anyway. @url rewrite: Wouldn't a redirection through apache's htaccess / url rewrite feature be subject to the same problem, since the problem seems to be the file_get_contents() function? – wizard Nov 19 '15 at 13:23
  • An internal URL rewrite will just change where Apache sends the request (in just the same way as friendly-URL rewriting does). It involves no actual redirection, as far a the client can see, just Apache deciding internally to serve different URL. It will only work if both new and old scripts are on the same server, though. – daiscog Nov 19 '15 at 16:04