1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge branch 'PHP-8.2'

* PHP-8.2:
  Fix GH-11274: POST/PATCH request via file_get_contents + stream_context_create switches to GET after a HTTP 308 redirect
This commit is contained in:
Niels Dossche
2023-05-19 23:46:47 +02:00
3 changed files with 78 additions and 7 deletions

View File

@@ -79,6 +79,7 @@
#define HTTP_WRAPPER_HEADER_INIT 1
#define HTTP_WRAPPER_REDIRECTED 2
#define HTTP_WRAPPER_KEEP_METHOD 4
static inline void strip_header(char *header_bag, char *lc_header_bag,
const char *lc_header_name)
@@ -140,6 +141,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
char *user_headers = NULL;
int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0);
bool follow_location = 1;
php_stream_filter *transfer_encoding = NULL;
int response_code;
@@ -363,8 +365,8 @@ finish:
if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) {
if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) {
/* As per the RFC, automatically redirected requests MUST NOT use other methods than
* GET and HEAD unless it can be confirmed by the user */
if (!redirected
* GET and HEAD unless it can be confirmed by the user. */
if (!redirected || redirect_keep_method
|| zend_string_equals_literal(Z_STR_P(tmpzval), "GET")
|| zend_string_equals_literal(Z_STR_P(tmpzval), "HEAD")
) {
@@ -458,7 +460,7 @@ finish:
zend_str_tolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
t = ZSTR_VAL(tmp);
if (!header_init) {
if (!header_init && !redirect_keep_method) {
/* strip POST headers on redirect */
strip_header(user_headers, t, "content-length:");
strip_header(user_headers, t, "content-type:");
@@ -606,7 +608,7 @@ finish:
* see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first.
*/
if (
header_init &&
(header_init || redirect_keep_method) &&
context &&
!(have_header & HTTP_HEADER_CONTENT_LENGTH) &&
(tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL &&
@@ -624,7 +626,7 @@ finish:
}
/* Request content, such as for POST requests */
if (header_init && context &&
if ((header_init || redirect_keep_method) && context &&
(tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL &&
Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) {
if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) {
@@ -913,9 +915,16 @@ finish:
CHECK_FOR_CNTRL_CHARS(resource->pass);
CHECK_FOR_CNTRL_CHARS(resource->path);
}
int new_flags = HTTP_WRAPPER_REDIRECTED;
if (response_code == 307 || response_code == 308) {
/* RFC 7538 specifies that status code 308 does not allow changing the request method from POST to GET.
* RFC 7231 does the same for status code 307.
* To keep consistency between POST and PATCH requests, we'll also not change the request method from PATCH to GET, even though it's allowed it's not mandated by the RFC. */
new_flags |= HTTP_WRAPPER_KEEP_METHOD;
}
stream = php_stream_url_wrap_http_ex(
wrapper, new_path, mode, options, opened_path, context,
--redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC);
--redirect_max, new_flags, response_header STREAMS_CC);
} else {
php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
}

View File

@@ -41,7 +41,7 @@ POST / HTTP/1.1
Host: %s:%d
Connection: close
GET /foo HTTP/1.1
POST /foo HTTP/1.1
Host: %s:%d
Connection: close

View File

@@ -0,0 +1,62 @@
--TEST--
GH-11274 (POST/PATCH request via file_get_contents + stream_context_create switches to GET after a HTTP 308 redirect)
--INI--
allow_url_fopen=1
--CONFLICTS--
server
--FILE--
<?php
$serverCode = <<<'CODE'
$uri = $_SERVER['REQUEST_URI'];
if (isset($_GET["desired_status"]) && $uri[strlen($uri) - 1] !== '/') {
$desired_status = (int) $_GET["desired_status"];
http_response_code($desired_status);
header("Location: $uri/");
exit;
}
echo "method: ", $_SERVER['REQUEST_METHOD'], "; body: ", file_get_contents('php://input'), "\n";
CODE;
include __DIR__."/../../../../sapi/cli/tests/php_cli_server.inc";
php_cli_server_start($serverCode, null, []);
foreach ([null, 301, 302, 307, 308] as $status) {
if (is_null($status)) {
echo "-- Testing unredirected request --\n";
} else {
echo "-- Testing redirect status code $status --\n";
}
$suffix = $status ? "?desired_status=$status" : "";
echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test$suffix", false, stream_context_create(['http' => ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]]));
echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]]));
echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]]));
echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]]));
}
?>
--EXPECT--
-- Testing unredirected request --
method: POST; body: hello=world
method: PATCH; body: hello=world
method: POST; body: hello=world
method: PATCH; body: hello=world
-- Testing redirect status code 301 --
method: GET; body:
method: GET; body:
method: GET; body:
method: GET; body:
-- Testing redirect status code 302 --
method: GET; body:
method: GET; body:
method: GET; body:
method: GET; body:
-- Testing redirect status code 307 --
method: POST; body: hello=world
method: PATCH; body: hello=world
method: POST; body: hello=world
method: PATCH; body: hello=world
-- Testing redirect status code 308 --
method: POST; body: hello=world
method: PATCH; body: hello=world
method: POST; body: hello=world
method: PATCH; body: hello=world