Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
400 views
in Technique[技术] by (71.8m points)

post - Why can php://input be read more than once despite the documentation saying otherwise?

The PHP documentation states that php://input can only be read once.

In my application I need to read it twice, once for authentication purposes and once for actually processing the content, and both functions are handled by different, independent modules. The crazy thing is: it works.

Can I count on this working everywhere, or is this a fluke in my version of PHP (5.2.10)? The only documentation I can find about this is the one that states that it shouldn't work, with no version limitation mentioned.


Following Dennis' hunch, I did this test:

$in = fopen('php://input', 'r');
echo fread($in, 1024) . "
";
fseek($in, 0);
echo fread($in, 1024) . "
";
fclose($in);
echo file_get_contents('php://input') . "
";

Curling:

$ curl http://localhost:8888/tests/test.php -d "This is a test"
This is a test

This is a test

Apparently it's limited to one read per open handle.


A little more digging revealed that indeed php://input can only be read once, ever, for PUT requests. The above example used a POST request.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

A little inspection of the source code yields the answers.

First, yes, you're limited to one read per handle because the underlying stream does not implement the seek handler:

php_stream_ops php_stream_input_ops = {
    php_stream_input_write,
    /* ... */
    "Input",
    NULL, /* seek */
    /* ... */
};

Second, the read handler has two different behaviors depending on whether the "POST data" has been read and stored in SG(request_info).raw_post_data.

if (SG(request_info).raw_post_data) {
    read_bytes = SG(request_info).raw_post_data_length - *position;
    /* ...*/
    if (read_bytes) {
        memcpy(buf, SG(request_info).raw_post_data + *position, read_bytes);
    }
} else if (sapi_module.read_post) {
    read_bytes = sapi_module.read_post(buf, count TSRMLS_CC);
    /* ... */
} else {
    stream->eof = 1;
}

So we have three possibilities here:

  1. The request body data has already been read and stored in SG(request_info).raw_post_data. In this case, since the data is stored, we can open and read multiple handles for php://input.
  2. The request body data has been read, but its contents were not stored anywhere. php://input cannot give us anything.
  3. The request data hasn't been read yet. This means we can open php://input and read it only once.

NOTE: What follows is the default behavior. Different SAPIs or additional extensions may change this behavior.

In case of POST requests, PHP defines a different POST reader and a POST handler depending on the content-type.

Case 1. This happens when we have a POST request:

  • With content-type application/x-www-form-encoded. sapi_activate detects a POST request with a content-type and calls sapi_read_post_data. This detects the content-type and defines the POST reader/handler pair. The POST reader is sapi_read_standard_form_data, which is immediately called and just copies the request body to SG(request_info).post_data. The default post reader php_default_post_reader is then called, which fills $HTTP_RAW_POST_DATA if the ini setting always_populate_post_data is set and then copies SG(request_info).post_data to SG(request_info).raw_post_data and clears the first. The call to the handler doesn't matter here and is deferred until the superglobals are built (which may not happen, in case JIT is activated and the superglobals are not used).
  • With an unrecognized or inexistent content-type. In this case, there's no defined POST reader and handler. Both cases end up in php_default_post_reader without any data read. Since this is a POST request and there's no reader/handler pair, sapi_read_standard_form_data will be called. This is the same function as the read handler the content type application/x-www-form-encoded, so all the data gets swallowed to SG(request_info).post_data. The only differences from now on is that $HTTP_RAW_POST_DATA is always populated (no matter the value of always_populate_post_data) and there's no handler for building the superglobals.

Case 2. This happens when we have a form request with content-type "multipart/form-data". The POST reader is NULL, so the handler, which is rfc1867_post_handler acts as a mixed reader/handler. No data whatsoever is read in the sapi_activate phase. The function sapi_handle_post is eventually called in a later phase, which, in its turn calls the POST handler. rfc1867_post_handler reads the request data, populates POST and FILES, but leaves nothing in SG(request_info).raw_post_data.

Case 3. This last case takes place with requests different from POST (e.g. PUT). php_default_post_reader is directly called. Because the request is not a POST request, the data is swallowed by sapi_read_standard_form_data. Since no data is read, there's not anything left to be done.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...