From 741b1d3b7a7b4a2711e8b6f3e1a9ef3567ae6aae Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 22 Dec 2023 02:07:59 +0000 Subject: [PATCH] Add http_(get|clear)_last_reponse_headers() functions This is to provide an alternative to the $http_response_header magic variable --- NEWS | 3 ++ UPGRADING | 5 +++ ext/standard/basic_functions.c | 6 +++ ext/standard/basic_functions.h | 3 ++ ext/standard/basic_functions.stub.php | 4 ++ ext/standard/basic_functions_arginfo.h | 10 ++++- ext/standard/http.c | 24 ++++++++++++ ext/standard/http_fopen_wrapper.c | 6 +++ ext/standard/tests/http/bug75535.phpt | 10 +++++ ext/standard/tests/http/bug80838.phpt | 12 ++++++ ext/standard/tests/http/gh9316.phpt | 31 +++++++++++++++ .../http_clear_last_response_headers.phpt | 38 +++++++++++++++++++ .../tests/http/http_response_header_01.phpt | 12 ++++++ .../tests/http/http_response_header_02.phpt | 16 ++++++++ .../tests/http/http_response_header_03.phpt | 17 +++++++++ .../tests/http/http_response_header_04.phpt | 10 +++++ .../tests/http/http_response_header_05.phpt | 10 +++++ 17 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/http/http_clear_last_response_headers.phpt diff --git a/NEWS b/NEWS index 0e4a16cf6e8a..e23921cba6fd 100644 --- a/NEWS +++ b/NEWS @@ -198,6 +198,9 @@ PHP NEWS . Changed return type of long2ip to string from string|false. (Jorg Sowa) . Fix GH-12143 (Extend the maximum precision round can handle by one digit). (SakiTakamachi) + . Added the http_get_last_response_headers() and + http_clear_last_response_headers() that allows retrieving the same content + as the magic $http_response_header variable. - XML: . Added XML_OPTION_PARSE_HUGE parser option. (nielsdos) diff --git a/UPGRADING b/UPGRADING index 3b080ac78444..e5c41413e609 100644 --- a/UPGRADING +++ b/UPGRADING @@ -428,6 +428,11 @@ PHP 8.4 UPGRADE NOTES . sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs with the ARM cryptographic extensions. +- Standard: + . Added the http_get_last_response_headers() and + http_clear_last_response_headers() that allows retrieving the same content + as the magic $http_response_header variable. + - XSL: . Added XSLTProcessor::registerPhpFunctionNS(). RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 9dddfdc782e2..6e923d20ecbd 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -418,6 +418,9 @@ PHP_RINIT_FUNCTION(basic) /* {{{ */ BASIC_RINIT_SUBMODULE(dir) BASIC_RINIT_SUBMODULE(url_scanner_ex) + /* Initialize memory for last http headers */ + ZVAL_UNDEF(&BG(last_http_headers)); + /* Setup default context */ FG(default_context) = NULL; @@ -482,6 +485,9 @@ PHP_RSHUTDOWN_FUNCTION(basic) /* {{{ */ BASIC_RSHUTDOWN_SUBMODULE(user_filters) BASIC_RSHUTDOWN_SUBMODULE(browscap) + /* Free last http headers */ + zval_ptr_dtor(&BG(last_http_headers)); + BG(page_uid) = -1; BG(page_gid) = -1; return SUCCESS; diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index c6daba8a1d39..45ea1f0a887b 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -70,6 +70,9 @@ typedef struct _php_basic_globals { zval active_ini_file_section; + /* http_fopen_wrapper.c */ + zval last_http_headers; + /* pageinfo.c */ zend_long page_uid; zend_long page_gid; diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 891edd9b092b..cc7e1fcf15d3 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -3025,6 +3025,10 @@ function pfsockopen(string $hostname, int $port = -1, &$error_code = null, &$err /** @refcount 1 */ function http_build_query(array|object $data, string $numeric_prefix = "", ?string $arg_separator = null, int $encoding_type = PHP_QUERY_RFC1738): string {} +function http_get_last_response_headers(): ?array {} + +function http_clear_last_response_headers(): void {} + /** * @param array|null $options * @return array diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index e1004480ece6..0b9acac1e0f8 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b8ea4527467c70a6f665129cd5d5f34ea2386a70 */ + * Stub hash: 2094d8e51f7bfd0fd7b61060786a1e8ae182b0d3 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1502,6 +1502,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_http_build_query, 0, 1, IS_STRIN ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, encoding_type, IS_LONG, 0, "PHP_QUERY_RFC1738") ZEND_END_ARG_INFO() +#define arginfo_http_get_last_response_headers arginfo_error_get_last + +#define arginfo_http_clear_last_response_headers arginfo_flush + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_request_parse_body, 0, 0, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") ZEND_END_ARG_INFO() @@ -2713,6 +2717,8 @@ ZEND_FUNCTION(vfprintf); ZEND_FUNCTION(fsockopen); ZEND_FUNCTION(pfsockopen); ZEND_FUNCTION(http_build_query); +ZEND_FUNCTION(http_get_last_response_headers); +ZEND_FUNCTION(http_clear_last_response_headers); ZEND_FUNCTION(request_parse_body); ZEND_FUNCTION(image_type_to_mime_type); ZEND_FUNCTION(image_type_to_extension); @@ -3350,6 +3356,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(fsockopen, arginfo_fsockopen) ZEND_FE(pfsockopen, arginfo_pfsockopen) ZEND_FE(http_build_query, arginfo_http_build_query) + ZEND_FE(http_get_last_response_headers, arginfo_http_get_last_response_headers) + ZEND_FE(http_clear_last_response_headers, arginfo_http_clear_last_response_headers) ZEND_FE(request_parse_body, arginfo_request_parse_body) ZEND_RAW_FENTRY("image_type_to_mime_type", zif_image_type_to_mime_type, arginfo_image_type_to_mime_type, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("image_type_to_extension", zif_image_type_to_extension, arginfo_image_type_to_extension, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) diff --git a/ext/standard/http.c b/ext/standard/http.c index da1fdeb39286..a9e56d451619 100644 --- a/ext/standard/http.c +++ b/ext/standard/http.c @@ -20,6 +20,7 @@ #include "SAPI.h" #include "zend_exceptions.h" #include "ext/spl/spl_exceptions.h" +#include "basic_functions.h" static void php_url_encode_scalar(zval *scalar, smart_str *form_str, int encoding_type, zend_ulong index_int, @@ -360,3 +361,26 @@ PHP_FUNCTION(request_parse_body) SG(request_parse_body_context).throw_exceptions = false; memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); } + +PHP_FUNCTION(http_get_last_response_headers) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + if (!Z_ISUNDEF(BG(last_http_headers))) { + RETURN_COPY(&BG(last_http_headers)); + } else { + RETURN_NULL(); + } +} + +PHP_FUNCTION(http_clear_last_response_headers) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + zval_ptr_dtor(&BG(last_http_headers)); + ZVAL_UNDEF(&BG(last_http_headers)); +} diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index daaaa41b00f9..6727311689f9 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -982,13 +982,19 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *pa { php_stream *stream; zval headers; + ZVAL_UNDEF(&headers); + zval_ptr_dtor(&BG(last_http_headers)); + ZVAL_UNDEF(&BG(last_http_headers)); + stream = php_stream_url_wrap_http_ex( wrapper, path, mode, options, opened_path, context, PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC); if (!Z_ISUNDEF(headers)) { + ZVAL_COPY(&BG(last_http_headers), &headers); + if (FAILURE == zend_set_local_var_str( "http_response_header", sizeof("http_response_header")-1, &headers, 0)) { zval_ptr_dtor(&headers); diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt index ff42db2dd555..27249c3fa18e 100644 --- a/ext/standard/tests/http/bug75535.phpt +++ b/ext/standard/tests/http/bug75535.phpt @@ -14,13 +14,17 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + var_dump(file_get_contents($uri)); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL string(0) "" array(2) { [0]=> @@ -28,3 +32,9 @@ array(2) { [1]=> string(14) "Content-Length" } +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(14) "Content-Length" +} diff --git a/ext/standard/tests/http/bug80838.phpt b/ext/standard/tests/http/bug80838.phpt index 66f3113e1a46..7171ad173fb6 100644 --- a/ext/standard/tests/http/bug80838.phpt +++ b/ext/standard/tests/http/bug80838.phpt @@ -23,14 +23,26 @@ $options = [ $ctx = stream_context_create($options); +var_dump(http_get_last_response_headers()); + $fd = fopen($uri, 'rb', false, $ctx); fclose($fd); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL +array(3) { + [0]=> + string(32) "HTTP/1.1 101 Switching Protocols" + [1]=> + string(15) "Header1: Value1" + [2]=> + string(15) "Header2: Value2" +} array(3) { [0]=> string(32) "HTTP/1.1 101 Switching Protocols" diff --git a/ext/standard/tests/http/gh9316.phpt b/ext/standard/tests/http/gh9316.phpt index 3ccbf4d88a5d..2f4a637f83e3 100644 --- a/ext/standard/tests/http/gh9316.phpt +++ b/ext/standard/tests/http/gh9316.phpt @@ -16,8 +16,14 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); for ($i = 0; $i < count($responses); ++$i) { + echo 'http_get_last_response_headers() before stream layer call:', PHP_EOL; + var_dump(http_get_last_response_headers()); + $f = @fopen($uri, "r"); + echo '$http_response_header', PHP_EOL; var_dump($http_response_header); + echo 'http_get_last_response_headers() after stream layer call:', PHP_EOL; + var_dump(http_get_last_response_headers()); fclose($f); } @@ -25,12 +31,37 @@ http_server_kill($pid); ?> --EXPECT-- +http_get_last_response_headers() before stream layer call: +NULL +$http_response_header +array(2) { + [0]=> + string(126) "HTTP/1.1 200 Some very long reason-phrase to test that this is properly handled by our code without adding a new header like " + [1]=> + string(12) "Good: Header" +} +http_get_last_response_headers() after stream layer call: array(2) { [0]=> string(126) "HTTP/1.1 200 Some very long reason-phrase to test that this is properly handled by our code without adding a new header like " [1]=> string(12) "Good: Header" } +http_get_last_response_headers() before stream layer call: +array(2) { + [0]=> + string(126) "HTTP/1.1 200 Some very long reason-phrase to test that this is properly handled by our code without adding a new header like " + [1]=> + string(12) "Good: Header" +} +$http_response_header +array(2) { + [0]=> + string(13) "HTTP/1.1 200 " + [1]=> + string(12) "Good: Header" +} +http_get_last_response_headers() after stream layer call: array(2) { [0]=> string(13) "HTTP/1.1 200 " diff --git a/ext/standard/tests/http/http_clear_last_response_headers.phpt b/ext/standard/tests/http/http_clear_last_response_headers.phpt new file mode 100644 index 000000000000..125706929599 --- /dev/null +++ b/ext/standard/tests/http/http_clear_last_response_headers.phpt @@ -0,0 +1,38 @@ +--TEST-- +Verify that http_clear_last_response_headers() clears the headers. +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$f = file_get_contents($uri); +var_dump($f); +var_dump(http_get_last_response_headers()); + +// Clear headers +http_clear_last_response_headers(); +var_dump(http_get_last_response_headers()); + +http_server_kill($pid); + +?> +--EXPECT-- +string(4) "Body" +array(3) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(12) "Some: Header" + [2]=> + string(12) "Some: Header" +} +NULL diff --git a/ext/standard/tests/http/http_response_header_01.phpt b/ext/standard/tests/http/http_response_header_01.phpt index 22c6d48c8fa7..4c494f0960ff 100644 --- a/ext/standard/tests/http/http_response_header_01.phpt +++ b/ext/standard/tests/http/http_response_header_01.phpt @@ -14,14 +14,18 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + $f = file_get_contents($uri); var_dump($f); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL string(4) "Body" array(3) { [0]=> @@ -31,3 +35,11 @@ array(3) { [2]=> string(12) "Some: Header" } +array(3) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(12) "Some: Header" + [2]=> + string(12) "Some: Header" +} diff --git a/ext/standard/tests/http/http_response_header_02.phpt b/ext/standard/tests/http/http_response_header_02.phpt index 9db78ed1ced8..56eb2868b6d1 100644 --- a/ext/standard/tests/http/http_response_header_02.phpt +++ b/ext/standard/tests/http/http_response_header_02.phpt @@ -16,14 +16,18 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + $f = file_get_contents($uri); var_dump($f); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL string(4) "Body" array(5) { [0]=> @@ -37,3 +41,15 @@ array(5) { [4]=> string(12) "Some: Header" } +array(5) { + [0]=> + string(18) "HTTP/1.0 302 Found" + [1]=> + string(12) "Some: Header" + [2]=> + string(20) "Location: /try-again" + [3]=> + string(15) "HTTP/1.0 200 Ok" + [4]=> + string(12) "Some: Header" +} diff --git a/ext/standard/tests/http/http_response_header_03.phpt b/ext/standard/tests/http/http_response_header_03.phpt index 93afd02ff264..f7fa7e00c60a 100644 --- a/ext/standard/tests/http/http_response_header_03.phpt +++ b/ext/standard/tests/http/http_response_header_03.phpt @@ -16,14 +16,19 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + $f = file_get_contents($uri); var_dump($f); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECTF-- +NULL + Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP request failed! HTTP/1.0 404 Not Found%a bool(false) array(5) { @@ -38,3 +43,15 @@ array(5) { [4]=> string(12) "Some: Header" } +array(5) { + [0]=> + string(18) "HTTP/1.0 302 Found" + [1]=> + string(12) "Some: Header" + [2]=> + string(20) "Location: /try-again" + [3]=> + string(22) "HTTP/1.0 404 Not Found" + [4]=> + string(12) "Some: Header" +} diff --git a/ext/standard/tests/http/http_response_header_04.phpt b/ext/standard/tests/http/http_response_header_04.phpt index 895fa3f0b843..a8a285fb7ad9 100644 --- a/ext/standard/tests/http/http_response_header_04.phpt +++ b/ext/standard/tests/http/http_response_header_04.phpt @@ -14,14 +14,18 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + $f = file_get_contents($uri); var_dump($f); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL string(4) "Body" array(2) { [0]=> @@ -29,3 +33,9 @@ array(2) { [1]=> string(14) "Some: Header" } +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(14) "Some: Header" +} diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt index 5285eee1c03b..d2a7800f4285 100644 --- a/ext/standard/tests/http/http_response_header_05.phpt +++ b/ext/standard/tests/http/http_response_header_05.phpt @@ -14,14 +14,18 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); +var_dump(http_get_last_response_headers()); + $f = file_get_contents($uri); var_dump($f); var_dump($http_response_header); +var_dump(http_get_last_response_headers()); http_server_kill($pid); ?> --EXPECT-- +NULL string(4) "Body" array(2) { [0]=> @@ -29,3 +33,9 @@ array(2) { [1]=> string(0) "" } +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(0) "" +}