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) . "\n";
fseek($in, 0);
echo fread($in, 1024) . "\n";
fclose($in);
echo file_get_contents('php://input') . "\n";
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.
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
seekhandler: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.So we have three possibilities here:
SG(request_info).raw_post_data. In this case, since the data is stored, we can open and read multiple handles forphp://input.php://inputcannot give us anything.php://inputand 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:
application/x-www-form-encoded.sapi_activatedetects a POST request with a content-type and callssapi_read_post_data. This detects the content-type and defines the POST reader/handler pair. The POST reader issapi_read_standard_form_data, which is immediately called and just copies the request body toSG(request_info).post_data. The default post readerphp_default_post_readeris then called, which fills$HTTP_RAW_POST_DATAif the ini settingalways_populate_post_datais set and then copiesSG(request_info).post_datatoSG(request_info).raw_post_dataand 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).php_default_post_readerwithout any data read. Since this is a POST request and there’s no reader/handler pair,sapi_read_standard_form_datawill be called. This is the same function as the read handler the content typeapplication/x-www-form-encoded, so all the data gets swallowed toSG(request_info).post_data. The only differences from now on is that$HTTP_RAW_POST_DATAis always populated (no matter the value ofalways_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 isrfc1867_post_handleracts as a mixedreader/handler. No data whatsoever is read in thesapi_activatephase. The functionsapi_handle_postis eventually called in a later phase, which, in its turn calls the POST handler.rfc1867_post_handlerreads the request data, populatesPOSTandFILES, but leaves nothing inSG(request_info).raw_post_data.Case 3. This last case takes place with requests different from POST (e.g. PUT).
php_default_post_readeris directly called. Because the request is not a POST request, the data is swallowed bysapi_read_standard_form_data. Since no data is read, there’s not anything left to be done.