diff --git a/ext/json/json.c b/ext/json/json.c index 4fac95fab39fe..2de78e3dabc2e 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -178,6 +178,24 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, } /* }}} */ +/* {{{ */ +PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_long options, zend_long depth) +{ + php_json_parser parser; + zval tmp; + const php_json_parser_methods* parser_validate_methods = php_json_get_validate_methods(); + php_json_parser_init_ex(&parser, &tmp, str, str_len, (int)options, (int)depth, parser_validate_methods); + + if (php_json_yyparse(&parser)) { + php_json_error_code error_code = php_json_parser_error_code(&parser); + JSON_G(error_code) = error_code; + return false; + } + + return true; +} +/* }}} */ + /* {{{ Returns the JSON representation of a value */ PHP_FUNCTION(json_encode) { @@ -270,6 +288,48 @@ PHP_FUNCTION(json_decode) } /* }}} */ +/* {{{ Validates if a string contains a valid json */ +PHP_FUNCTION(json_validate) +{ + char *str; + size_t str_len; + zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; + zend_long options = 0; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(depth) + Z_PARAM_LONG(options) + ZEND_PARSE_PARAMETERS_END(); + + + if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) { + zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)"); + RETURN_THROWS(); + } + + if (!str_len) { + JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; + RETURN_FALSE; + } + + JSON_G(error_code) = PHP_JSON_ERROR_NONE; + + if (depth <= 0) { + zend_argument_value_error(2, "must be greater than 0"); + RETURN_THROWS(); + } + + if (depth > INT_MAX) { + zend_argument_value_error(2, "must be less than %d", INT_MAX); + RETURN_THROWS(); + } + + RETURN_BOOL(php_json_validate_ex(str, str_len, options, depth)); +} +/* }}} */ + /* {{{ Returns the error code of the last json_encode() or json_decode() call. */ PHP_FUNCTION(json_last_error) { diff --git a/ext/json/json.stub.php b/ext/json/json.stub.php index 7a8b479ab7657..a805c3893dd10 100644 --- a/ext/json/json.stub.php +++ b/ext/json/json.stub.php @@ -156,6 +156,8 @@ function json_encode(mixed $value, int $flags = 0, int $depth = 512): string|fal function json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed {} +function json_validate(string $json, int $depth = 512, int $flags = 0): bool {} + function json_last_error(): int {} /** @refcount 1 */ diff --git a/ext/json/json_arginfo.h b/ext/json/json_arginfo.h index 8c88f24c403cd..6cf3e7dc8b38c 100644 --- a/ext/json/json_arginfo.h +++ b/ext/json/json_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 91b1a992a7020081c42e1db876e5cdce94b681dd */ + * Stub hash: 0ceb50047401c4b9e878c09cc518eacc274f7fff */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_json_encode, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -14,6 +14,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_json_decode, 0, 1, IS_MIXED, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_json_validate, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, depth, IS_LONG, 0, "512") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_json_last_error, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -26,6 +32,7 @@ ZEND_END_ARG_INFO() ZEND_FUNCTION(json_encode); ZEND_FUNCTION(json_decode); +ZEND_FUNCTION(json_validate); ZEND_FUNCTION(json_last_error); ZEND_FUNCTION(json_last_error_msg); @@ -33,6 +40,7 @@ ZEND_FUNCTION(json_last_error_msg); static const zend_function_entry ext_functions[] = { ZEND_FE(json_encode, arginfo_json_encode) ZEND_FE(json_decode, arginfo_json_decode) + ZEND_FE(json_validate, arginfo_json_validate) ZEND_FE(json_last_error, arginfo_json_last_error) ZEND_FE(json_last_error_msg, arginfo_json_last_error_msg) ZEND_FE_END diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index 90ff0a7c758fe..d88b6299983b4 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -255,10 +255,40 @@ static int php_json_parser_object_update(php_json_parser *parser, zval *object, return SUCCESS; } +static int php_json_parser_array_create_validate(php_json_parser *parser, zval *array) +{ + ZVAL_NULL(array); + return SUCCESS; +} + +static int php_json_parser_array_append_validate(php_json_parser *parser, zval *array, zval *zvalue) +{ + return SUCCESS; +} + +static int php_json_parser_object_create_validate(php_json_parser *parser, zval *object) +{ + ZVAL_NULL(object); + return SUCCESS; +} + +static int php_json_parser_object_update_validate(php_json_parser *parser, zval *object, zend_string *key, zval *zvalue) +{ + return SUCCESS; +} + static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser) { int token = php_json_scan(&parser->scanner); value->value = parser->scanner.value; + + if (parser->methods.array_create == php_json_parser_array_create_validate + && parser->methods.array_append == php_json_parser_array_append_validate + && parser->methods.object_create == php_json_parser_object_create_validate + && parser->methods.object_update == php_json_parser_object_update_validate) { + zval_ptr_dtor_str(&(parser->scanner.value)); + } + return token; } @@ -286,6 +316,18 @@ static const php_json_parser_methods default_parser_methods = NULL, }; +static const php_json_parser_methods validate_parser_methods = +{ + php_json_parser_array_create_validate, + php_json_parser_array_append_validate, + NULL, + NULL, + php_json_parser_object_create_validate, + php_json_parser_object_update_validate, + NULL, + NULL, +}; + PHP_JSON_API void php_json_parser_init_ex(php_json_parser *parser, zval *return_value, const char *str, @@ -323,3 +365,8 @@ PHP_JSON_API int php_json_parse(php_json_parser *parser) { return php_json_yyparse(parser); } + +const php_json_parser_methods* php_json_get_validate_methods() +{ + return &validate_parser_methods; +} diff --git a/ext/json/php_json.h b/ext/json/php_json.h index d4d8ac421f886..3c47e44875cc7 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -72,8 +72,10 @@ typedef enum { #define PHP_JSON_PRESERVE_ZERO_FRACTION (1<<10) #define PHP_JSON_UNESCAPED_LINE_TERMINATORS (1<<11) -/* json_decode() and json_encode() common options */ +/* json_validate(), json_decode() and json_encode() common options */ #define PHP_JSON_INVALID_UTF8_IGNORE (1<<20) + +/* json_decode() and json_encode() common options */ #define PHP_JSON_INVALID_UTF8_SUBSTITUTE (1<<21) #define PHP_JSON_THROW_ON_ERROR (1<<22) @@ -100,6 +102,7 @@ ZEND_TSRMLS_CACHE_EXTERN() PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth); PHP_JSON_API zend_result php_json_encode(smart_str *buf, zval *val, int options); PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth); +PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_long options, zend_long depth); static inline zend_result php_json_decode(zval *return_value, const char *str, size_t str_len, bool assoc, zend_long depth) { diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index c17cf708d35a5..8aedce9ac55d6 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -81,4 +81,6 @@ PHP_JSON_API int php_json_parse(php_json_parser *parser); int php_json_yyparse(php_json_parser *parser); +const php_json_parser_methods* php_json_get_validate_methods(void); + #endif /* PHP_JSON_PARSER_H */ diff --git a/ext/json/tests/json_validate_001.phpt b/ext/json/tests/json_validate_001.phpt new file mode 100644 index 0000000000000..2330166aa7c36 --- /dev/null +++ b/ext/json/tests/json_validate_001.phpt @@ -0,0 +1,41 @@ +--TEST-- +json_validate() - General usage +--FILE-- +"), + json_validate(";"), + json_validate("руссиш"), + json_validate("blah"), + json_validate('{ "": "": "" } }'), + json_validate('{ "": { "": "" }'), + json_validate('{ "test": {} "foo": "bar" }, "test2": {"foo" : "bar" }, "test2": {"foo" : "bar" } }'), + + json_validate('{ "test": { "foo": "bar" } }'), + json_validate('{ "test": { "foo": "" } }'), + json_validate('{ "": { "foo": "" } }'), + json_validate('{ "": { "": "" } }'), + json_validate('{ "test": {"foo": "bar"}, "test2": {"foo" : "bar" }, "test2": {"foo" : "bar" } }'), + json_validate('{ "test": {"foo": "bar"}, "test2": {"foo" : "bar" }, "test3": {"foo" : "bar" } }'), +); + +?> +--EXPECT-- +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/json/tests/json_validate_002.phpt b/ext/json/tests/json_validate_002.phpt new file mode 100644 index 0000000000000..53f4e4f2c2ea1 --- /dev/null +++ b/ext/json/tests/json_validate_002.phpt @@ -0,0 +1,50 @@ +--TEST-- +json_validate() - Error handling +--FILE-- + +--EXPECTF-- +bool(false) +int(4) +string(12) "Syntax error" +bool(false) +int(4) +string(12) "Syntax error" +bool(false) +int(4) +string(12) "Syntax error" +bool(false) +int(1) +string(28) "Maximum stack depth exceeded" +bool(true) +int(0) +string(8) "No error" +Error: 0 json_validate(): Argument #2 ($depth) must be greater than 0 +int(0) +string(8) "No error" +Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE) +int(0) +string(8) "No error" +Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE) +int(0) +string(8) "No error" +bool(false) +int(4) +string(12) "Syntax error" +bool(true) +int(0) +string(8) "No error" diff --git a/ext/json/tests/json_validate_003.phpt b/ext/json/tests/json_validate_003.phpt new file mode 100644 index 0000000000000..cd0545b9a7f91 --- /dev/null +++ b/ext/json/tests/json_validate_003.phpt @@ -0,0 +1,19 @@ +--TEST-- +json_validate() - Error handling for max depth +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; + var_dump(json_last_error(), json_last_error_msg()); +} + +?> +--EXPECTF-- +json_validate(): Argument #2 ($depth) must be less than %d +int(0) +string(8) "No error" diff --git a/ext/json/tests/json_validate_004.phpt b/ext/json/tests/json_validate_004.phpt new file mode 100644 index 0000000000000..d8a798d943278 --- /dev/null +++ b/ext/json/tests/json_validate_004.phpt @@ -0,0 +1,47 @@ +--TEST-- +json_validate() - Invalid UTF-8's +--FILE-- + +--EXPECT-- +Testing Invalid UTF-8 +bool(false) +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +bool(false) +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +bool(false) +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +bool(false) +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +bool(true) +int(0) +string(8) "No error" +bool(true) +int(0) +string(8) "No error" +bool(true) +int(0) +string(8) "No error" +bool(true) +int(0) +string(8) "No error" diff --git a/ext/json/tests/json_validate_005.phpt b/ext/json/tests/json_validate_005.phpt new file mode 100644 index 0000000000000..f5cb9d6f732b5 --- /dev/null +++ b/ext/json/tests/json_validate_005.phpt @@ -0,0 +1,64 @@ +--TEST-- +json_validate() - compare against json_decode() for different types of inputs +--FILE-- + +--EXPECTF-- +string(2) """" +string(0) "" +bool(true) +string(8) ""string"" +string(6) "string" +bool(true) +string(4) "1234" +int(1234) +bool(true) +string(6) "123.45" +float(123.45) +bool(true) +string(4) "-123" +int(-123) +bool(true) +string(4) "null" +NULL +bool(true) +string(4) "true" +bool(true) +bool(true) +string(5) "false" +bool(false) +bool(true) +string(2) "{}" +object(stdClass)#1 (0) { +} +bool(true) +string(2) "[]" +array(0) { +} +bool(true) +string(1) "-" +NULL +bool(false) +string(0) "" +NULL +bool(false) diff --git a/ext/json/tests/json_validate_requires.inc b/ext/json/tests/json_validate_requires.inc new file mode 100644 index 0000000000000..848b0c7860936 --- /dev/null +++ b/ext/json/tests/json_validate_requires.inc @@ -0,0 +1,17 @@ +getCode()} {$e->getMessage()}". PHP_EOL; + } catch (Exception $e) { + echo "Exception: {$e->getCode()} {$e->getMessage()}". PHP_EOL; + } catch (Error $e) { + echo "Error: {$e->getCode()} {$e->getMessage()}". PHP_EOL; + } + + var_dump(json_last_error(), json_last_error_msg()); +} + +?>