Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions ext/json/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) {
if (options & ~PHP_JSON_INVALID_UTF8_IGNORE) {

this works, but checking for any bits found in bitwise not in allowed flags is shorter and conventional - it also works and is used for combinations of multiple flags

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)
{
Expand Down
2 changes: 2 additions & 0 deletions ext/json/json.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
10 changes: 9 additions & 1 deletion ext/json/json_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions ext/json/json_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
5 changes: 4 additions & 1 deletion ext/json/php_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions ext/json/php_json_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
41 changes: 41 additions & 0 deletions ext/json/tests/json_validate_001.phpt
Original file line number Diff line number Diff line change
@@ -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)
50 changes: 50 additions & 0 deletions ext/json/tests/json_validate_002.phpt
Original file line number Diff line number Diff line change
@@ -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"
19 changes: 19 additions & 0 deletions ext/json/tests/json_validate_003.phpt
Original file line number Diff line number Diff line change
@@ -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"
47 changes: 47 additions & 0 deletions ext/json/tests/json_validate_004.phpt
Original file line number Diff line number Diff line change
@@ -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"
Loading