Wiseguy
Wiseguy

Reputation: 20873

$HTTP_RAW_POST_DATA not being populated after upgrade to PHP 5.3

My script receives gzipped data from a desktop application via POST, which it retrieves and processes from $HTTP_RAW_POST_DATA. In PHP 5.2.16 (ISAPI), $HTTP_RAW_POST_DATA is correctly being populated with the expected binary data. After upgrading to PHP 5.3.9 (FastCGI), $HTTP_RAW_POST_DATA is not defined. How can I get the data?

I'm running IIS 5.1 under 32-bit Windows XP SP3.


The docs regarding $HTTP_RAW_POST_DATA state that it 'is not available with enctype="multipart/form-data"', but I'm not using that content type. (Recall that the same code works fine under PHP 5.2, so content type would have been a problem then, too.)

When I enabled the always_populate_raw_post_data ini directive, nothing changes. phpinfo() reports that setting as "On", but the variable is still not being set.

The docs also suggest that the preferred method to obtain this data is to read it from the php://input stream instead. I tried that as follows:

$HTTP_RAW_POST_DATA = file_get_contents('php://input');

but the script just hangs on that line, presumably because it's waiting for data (i.e., no End-Of-File sent). Even if I limit the maxlength to a value as small as one byte as follows, it still hangs.

$HTTP_RAW_POST_DATA = file_get_contents('php://input', false, null, -1, 1);

The only time it doesn't hang on that line is when I set the maxlen to 0, which means it doesn't try to read anything but also isn't helpful. :-)

If $HTTP_RAW_POST_DATA won't populate when I force it to, and I can't get anything from php://input, how can I get the data?

Is the fact that php://input is apparently empty indicative of a difference between ISAPI and FastCGI? I haven't read anything suggesting that raw POSTs behave differently or are lost under FastCGI.


Here are some relevant portions of $_SERVER. All of these values are identical between running the script with PHP 5.2 and PHP 5.3.

[CONTENT_LENGTH] => 4294967295
[CONTENT_TYPE] => application/client-gzip
[HTTP_HOST] => 127.0.0.1
[HTTP_CONTENT_TYPE] => application/client-gzip
[HTTP_TRANSFER_ENCODING] => chunked
[HTTP_ACCEPT_ENCODING] => gzip
[HTTP_EXPECT] => 100-continue
[REQUEST_METHOD] => POST
[SERVER_NAME] => 127.0.0.1
[SERVER_PORT] => 80
[SERVER_PROTOCOL] => HTTP/1.1

(Note: I know a 4GB content length looks odd, but because it's using chunked encoding, CONTENT_LENGTH is ignored.)

Since the CONTENT_LENGTH header is not used, is there any way for me to verify that the data is indeed being received by PHP somewhere and not lost by IIS?

Since the data is being sent from a binary library in a desktop client, changing the format of the POST is not an option. Regardless of the pros/cons of using $HTTP_RAW_POST_DATA like this, this is how I must do it. It worked like this before, so I fully expect it to work now. Any insight is sincerely appreciated!


Update

Here's a simple script to exemplify my problem. There are two files:

postsend.php

<?php
$headers = array(
    "Content-Type: application/client-gzip",
    // "Transfer-Encoding: chunked",
);

$text = 'This text is superior to your text even though it is useless.';
$data = gzdeflate($text);

echo "text: $text<br />gzip: $data<br />";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,            "http://localhost/server/postreceive.php");
curl_setopt($ch, CURLOPT_USERPWD,        "user:pass");
curl_setopt($ch, CURLOPT_HTTPAUTH,       CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST,           1);
curl_setopt($ch, CURLOPT_POSTFIELDS,     $data); 
curl_setopt($ch, CURLOPT_HTTPHEADER,     $headers); 

$result = curl_exec($ch);
echo $result;

postreceive.php

<?php
if (!empty($HTTP_RAW_POST_DATA)) {
    printf(
        'HTTP_RAW_POST_DATA set!<br />gzip:%s<br />text: %s',
        $HTTP_RAW_POST_DATA,
        gzinflate($HTTP_RAW_POST_DATA)
    );
} else {
    echo 'No raw data. :-(';
}

Under PHP 5.3 (FastCGI), this works when I run postsend.php, and the output is:

text: This text is superior to your text even though it is useless.
gzip: ÉÈ,V(I­(QÒÅ¥©E™ùE %ù •ù¥E‰Ô²Ô<…’ŒüÒô …L°ºÒâÔœÔâb=
HTTP_RAW_POST_DATA set!
gzip: ÉÈ,V(I­(QÒÅ¥©E™ùE %ù •ù¥E‰Ô²Ô<…’ŒüÒô …L°ºÒâÔœÔâb=
text: This text is superior to your text even though it is useless.

However, if I uncomment the line with the Transfer-Encoding header, everything just hangs when I run postsend.php. So, receiving chunked encoding seems to be breaking it.

Is there some IIS or FastCGI configuration that I'm missing which would allow this to work when receiving chunked encoding? (If so, is this now more appropriate as a ServerFault question?) I've seen settings that enable sending chunked encoding but not receiving chunked encoding.

Upvotes: 3

Views: 7086

Answers (4)

Wiseguy
Wiseguy

Reputation: 20873

As eventually identified in bug report #60826, this problem is caused beyond PHP in the web stack.
It is a shortcoming of how FastCGI is implemented in various web servers.

Upvotes: 1

Gaylord Aulke
Gaylord Aulke

Reputation: 11

I had the very same problem.

A normal fopen without any modifiers: fopen("php://input") gave me a "false". fopen("php://input","rb") worked.

Upvotes: 1

Francis Avila
Francis Avila

Reputation: 31641

The presence of $_SERVER['HTTP_EXPECT'] suggests to me that maybe this request has no data and you need to reply with HTTP/1.1 100 Continue before the client will reply with data. See the http spec on the use of Expect and 100 Continue. However, I don't know why file_get_contents('php://input') doesn't immediately return an empty string.

Also, I'm not sure IIS+FastCGI supports chunked transfer encodings without buffering. There's a transferMode IIS setting that may be useful to investigate.

Finally, if you do get this working, beware that you may need to "dechunk" (decode the transfer-encoding: chunked body) the request input even though you didn't have to do this before. You can use it like so:

$fp = fopen('php://input', 'rb');
stream_filter_append($fp, 'dechunk', STREAM_FILTER_READ);
$HTTP_RAW_POST_DATA = stream_get_contents($fp);

If you need to do this on PHP < 5.3, I wrote an http_unchunk_filter stream filter that does the same thing.

Upvotes: 1

eis
eis

Reputation: 53512

this bug entry suggests it has broken on 5.1 release of php as well as 5.2 release. I wouldn't be that surprised if they broke it also for 5.3 release, so I suggest to open a bug there. 5.3.9 was just released a week ago, so that too suggests that there might be bugs there that haven't been yet found.

What you can also try is to test if downgrading helps.

Upvotes: 0

Related Questions