diff --git a/ext/json/json.c b/ext/json/json.c index c8f0ed5e93a3d..13efeb901e322 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -142,6 +142,21 @@ static PHP_MINFO_FUNCTION(json) } /* }}} */ +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options) +{ + smart_str buf = {0}; + php_json_encoder encoder; + + php_json_encode_init(&encoder); + + if (php_json_escape_string(&buf, s, len, options, &encoder) == FAILURE) { + smart_str_free(&buf); + return NULL; + } + + return smart_str_extract(&buf); +} + PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */ { php_json_encoder encoder; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index b0f703041b068..f3523ed3258b4 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -31,10 +31,6 @@ static const char digits[] = "0123456789abcdef"; -static int php_json_escape_string( - smart_str *buf, const char *s, size_t len, - int options, php_json_encoder *encoder); - static int php_json_determine_array_type(zval *val) /* {{{ */ { zend_array *myht = Z_ARRVAL_P(val); @@ -312,7 +308,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso } /* }}} */ -static int php_json_escape_string( +int php_json_escape_string( smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder) /* {{{ */ { diff --git a/ext/json/php_json.h b/ext/json/php_json.h index 89d04ed7f4d57..f8905b10cbb80 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -97,6 +97,8 @@ PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) ZEND_TSRMLS_CACHE_EXTERN() #endif +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options); + PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth); PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options); PHP_JSON_API int php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth); diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index 51d2d6b59ab49..a1ddd3c349e63 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -35,4 +35,6 @@ static inline void php_json_encode_init(php_json_encoder *encoder) int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder); +int php_json_escape_string(smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder); + #endif /* PHP_JSON_ENCODER_H */ diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 514d60d176e39..f0d869444afce 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -13,7 +13,8 @@ #include "fpm_atomic.h" #include "fpm_conf.h" #include "fpm_php.h" -#include +#include "ext/standard/html.h" +#include "ext/json/php_json.h" static char *fpm_status_uri = NULL; static char *fpm_status_ping_uri = NULL; @@ -140,7 +141,8 @@ int fpm_status_handle_request(void) /* {{{ */ struct fpm_scoreboard_proc_s *proc; char *buffer, *time_format, time_buffer[64]; time_t now_epoch; - int full, encode, has_start_time; + int full, has_start_time; + bool encode_html, encode_json; char *short_syntax, *short_post; char *full_pre, *full_syntax, *full_post, *full_separator; zend_string *_GET_str; @@ -175,7 +177,8 @@ int fpm_status_handle_request(void) /* {{{ */ full = (fpm_php_get_string_from_table(_GET_str, "full") != NULL); short_syntax = short_post = NULL; full_separator = full_pre = full_syntax = full_post = NULL; - encode = 0; + encode_html = false; + encode_json = false; has_start_time = 1; scoreboard_p = fpm_scoreboard_get(); @@ -218,7 +221,7 @@ int fpm_status_handle_request(void) /* {{{ */ if (fpm_php_get_string_from_table(_GET_str, "html")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); time_format = "%d/%b/%Y:%H:%M:%S %z"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -287,7 +290,7 @@ int fpm_status_handle_request(void) /* {{{ */ } else if (fpm_php_get_string_from_table(_GET_str, "xml")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1); time_format = "%s"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -336,6 +339,8 @@ int fpm_status_handle_request(void) /* {{{ */ sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1); time_format = "%s"; + encode_json = true; + short_syntax = "{" "\"pool\":\"%s\"," @@ -549,11 +554,24 @@ int fpm_status_handle_request(void) /* {{{ */ query_string = NULL; tmp_query_string = NULL; if (proc->query_string[0] != '\0') { - if (!encode) { - query_string = proc->query_string; + if (encode_html) { + tmp_query_string = php_escape_html_entities_ex( + (const unsigned char *) proc->query_string, + strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + } else if (encode_json) { + tmp_query_string = php_json_encode_string(proc->query_string, + strlen(proc->query_string), PHP_JSON_INVALID_UTF8_IGNORE); } else { - tmp_query_string = php_escape_html_entities_ex((const unsigned char *) proc->query_string, strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); + query_string = proc->query_string; + } + if (tmp_query_string) { query_string = ZSTR_VAL(tmp_query_string); + /* remove quotes around the string */ + if (encode_json && ZSTR_LEN(tmp_query_string) >= 2) { + query_string[ZSTR_LEN(tmp_query_string) - 1] = '\0'; + ++query_string; + } } } diff --git a/sapi/fpm/tests/bug64539-status-json-encoding.phpt b/sapi/fpm/tests/bug64539-status-json-encoding.phpt new file mode 100644 index 0000000000000..0d735925593a4 --- /dev/null +++ b/sapi/fpm/tests/bug64539-status-json-encoding.phpt @@ -0,0 +1,50 @@ +--TEST-- +FPM: bug64539 - status json format escaping +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$responses = $tester + ->multiRequest([ + ['query' => 'a=b"c'], + ['uri' => '/status', 'query' => 'full&json', 'delay' => 100000], + ]); +$data = json_decode($responses[1]->getBody('application/json'), true); +var_dump(explode('?', $data['processes'][0]['request uri'])[1]); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +string(5) "a=b"c" +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc index 99290e72f41d4..c1c4566d4b12d 100644 --- a/sapi/fpm/tests/response.inc +++ b/sapi/fpm/tests/response.inc @@ -192,18 +192,22 @@ class Response /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function dumpBody() + public function dumpBody(string $contentType = 'text/html') { - var_dump($this->getBody()); + var_dump($this->getBody($contentType)); } /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function printBody() + public function printBody(string $contentType = 'text/html') { - echo $this->getBody() . "\n"; + echo $this->getBody($contentType) . "\n"; } /** diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 0b6ad9d0f831b..ddab17162a227 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -795,6 +795,10 @@ class Tester $requestData['uri'] ?? null ); + if (isset($requestData['delay'])) { + usleep($requestData['delay']); + } + return [ 'client' => $client, 'requestId' => $client->async_request($params, false),