Repository navigation
Conversation
The apache2handler read_post callback ignored errors returned by ap_get_brigade() so a request body that could not be read completely (the client disconnected or the body read timed out, e.g. when mod_reqtimeout is used) was passed to the script as a complete body and the request was answered with 200 and a partial or empty $_POST. The read error is now remembered in the request context and if it happens during the request startup (POST data reading), the script is not executed and the error is reported to httpd using the status mapped by ap_map_http_request_error() (400 Bad Request, 408 Request Timeout or AP_FILTER_ERROR when the input filter already responded). The request shutdown is still performed so that all request resources like the temporary files of a partial upload are released, which was the problem of the previously reverted fix (bug #74318). The body read during the script execution (php://input) is not affected apart from no longer dropping the data read before the error.
|
Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.
|
Fixes https://bugs.php.net/bug.php?id=61471
php_apache_sapi_read_post()ignores the status ofap_get_brigade()and returns 0 on error which PHP takes as the end of the body. So when the client sends less than the declared Content-Length (disconnects or stalls until mod_reqtimeout or the socket timeout aborts the read), the script runs with a partial or empty$_POSTand httpd logs 200. Other handlers report the error and httpd responds with 400 or 408.This was already fixed twice and both fixes got reverted. #2180 compared
content_lengthwithread_post_bytesbefore running the script but PHP reads the body at startup only for POST, so every PATCH or PUT with a body was rejected. #2323 tracked the error properly but returned from the handler beforephp_request_shutdown()so the temporary files of partial uploads leaked (bug #74318).The error status is now kept in
php_structso it is per request and works with threaded MPMs. It is set inread_postwhich also keeps the data that HTTP_IN leaves in the brigade onAPR_INCOMPLETEand does not try to read again after a failure. The handler checks it right afterphp_apache_request_ctor()(so after the POST data reading), skips the script execution and returns the status fromap_map_http_request_error()to httpd which generates the 408 or 400 response, orAP_FILTER_ERRORif the input filter already responded. The request shutdown is always done so the uploaded files are cleaned up. Reads during the script execution (php://input,enable_post_data_reading=0) are not changed because the script might have already sent a response.It means that a truncated POST now results in 400/408 instead of running the script, so for example a cancelled upload can no longer be seen through
UPLOAD_ERR_PARTIAL. FPM behind nginx or mod_proxy_fcgi never got that reliably either.I tested it with wst using instances labelled
bug61471in https://github.com/wstool/wst-php-apache2handler . Therequest-body-incomplete-*instances cover the actual bug (stall and disconnect, chunked and Content-Length),request-body-multipart-incompletethe temp files from bug #74318,request-body-unread-methodsthe PATCH regression of #2180 andrequest-body-unread-no-post-readingandupload-chunked-over-limitcheck that an unread body is not reported as incomplete. All pass with prefork and event MPM.