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

RFC - json_validate() (#9399)

* Add `json_validate(string $json, int $depth = 512, int $flags = 0): bool` from https://wiki.php.net/rfc/json_validate
* In json_validate, use a different set of C no-op functions for creating/updating
   arrays/objects when validating while reusing the unmodified parser/scanner code
* Forbid unsupported flags in json_validate()
* Remove test of passing NULL as parameter (normal behavior of https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg for internal functions)

Co-authored-by: jcm <juan.carlos.morales@tradebyte.com>
This commit is contained in:
Juan Morales
2022-10-08 10:21:59 -03:00
committed by GitHub
parent d498908ec4
commit 2e8699f6f2
12 changed files with 362 additions and 2 deletions

View File

@@ -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)
{

View File

@@ -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 */

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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 */

View File

@@ -0,0 +1,41 @@
--TEST--
json_validate() - General usage
--FILE--
<?php
var_dump(
json_validate(""),
json_validate("."),
json_validate("<?>"),
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)

View File

@@ -0,0 +1,50 @@
--TEST--
json_validate() - Error handling
--FILE--
<?php
require_once("json_validate_requires.inc");
json_validate_trycatchdump("");
json_validate_trycatchdump("-");
json_validate_trycatchdump("", -1);
json_validate_trycatchdump('{"key1":"value1", "key2":"value2"}', 1);
json_validate_trycatchdump('{"key1":"value1", "key2":"value2"}', 2);
json_validate_trycatchdump("-", 0);
json_validate_trycatchdump("-", 512, JSON_BIGINT_AS_STRING);
json_validate_trycatchdump("-", 512, JSON_BIGINT_AS_STRING | JSON_INVALID_UTF8_IGNORE);
json_validate_trycatchdump("-", 512, JSON_INVALID_UTF8_IGNORE);
json_validate_trycatchdump("{}", 512, JSON_INVALID_UTF8_IGNORE);
?>
--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"

View File

@@ -0,0 +1,19 @@
--TEST--
json_validate() - Error handling for max depth
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platform only"); ?>
--FILE--
<?php
try {
var_dump(json_validate("-", PHP_INT_MAX));
} catch (ValueError $error) {
echo $error->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"

View File

@@ -0,0 +1,47 @@
--TEST--
json_validate() - Invalid UTF-8's
--FILE--
<?php
require_once("json_validate_requires.inc");
echo "Testing Invalid UTF-8" . PHP_EOL;
json_validate_trycatchdump("\"a\xb0b\"");
json_validate_trycatchdump("\"a\xd0\xf2b\"");
json_validate_trycatchdump("\"\x61\xf0\x80\x80\x41\"");
json_validate_trycatchdump("[\"\xc1\xc1\",\"a\"]");
json_validate_trycatchdump("\"a\xb0b\"", 512, JSON_INVALID_UTF8_IGNORE);
json_validate_trycatchdump("\"a\xd0\xf2b\"", 512, JSON_INVALID_UTF8_IGNORE);
json_validate_trycatchdump("\"\x61\xf0\x80\x80\x41\"", 512, JSON_INVALID_UTF8_IGNORE);
json_validate_trycatchdump("[\"\xc1\xc1\",\"a\"]", 512, JSON_INVALID_UTF8_IGNORE);
?>
--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"

View File

@@ -0,0 +1,64 @@
--TEST--
json_validate() - compare against json_decode() for different types of inputs
--FILE--
<?php
$inputs = [
'""',
'"string"',
'1234',
'123.45',
'-123',
'null',
'true',
'false',
'{}',
'[]',
'-',
'',
];
foreach ($inputs as $input) {
var_dump($input, json_decode($input), json_validate($input));
}
?>
--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)

View File

@@ -0,0 +1,17 @@
<?php
function json_validate_trycatchdump($json, $depth = 512, $flags = 0) {
try {
var_dump(json_validate($json, $depth, $flags));
} catch (JsonException $e) {
echo "JsonException: {$e->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());
}
?>