diff --git a/ext/json/json.c b/ext/json/json.c index 9f91d39594ec7..9da7a3918a043 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -70,6 +70,7 @@ static PHP_GINIT_FUNCTION(json) static PHP_RINIT_FUNCTION(json) { JSON_G(error_code) = 0; + JSON_G(error_pos) = 0; return SUCCESS; } @@ -133,6 +134,7 @@ PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int optio return_code = php_json_encode_zval(buf, val, options, &encoder); JSON_G(error_code) = encoder.error_code; + JSON_G(error_pos) = encoder.error_pos; return return_code; } @@ -177,6 +179,29 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ } /* }}} */ + +char *php_json_get_error_msg_wrapper(php_json_error_code error_code) /* {{{ */ +{ + char *final_message; + const char *error_message = php_json_get_error_msg(error_code); + + switch(error_code) { + case PHP_JSON_ERROR_SYNTAX: + case PHP_JSON_ERROR_UTF8: + case PHP_JSON_ERROR_CTRL_CHAR: + case PHP_JSON_ERROR_UNSUPPORTED_TYPE: + case PHP_JSON_ERROR_INVALID_PROPERTY_NAME: + case PHP_JSON_ERROR_UTF16: + spprintf(&final_message, 0, "%s near character %zu", error_message, JSON_G(error_pos)); + break; + default: + spprintf(&final_message, 0, "%s", error_message); + } + + return final_message; +} +/* }}} */ + 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_parser parser; @@ -185,11 +210,15 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, if (php_json_yyparse(&parser)) { php_json_error_code error_code = php_json_parser_error_code(&parser); + size_t error_pos = php_json_parser_error_pos(&parser); + if (!(options & PHP_JSON_THROW_ON_ERROR)) { JSON_G(error_code) = error_code; + JSON_G(error_pos) = error_pos; } else { zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code); } + RETVAL_NULL(); return FAILURE; } @@ -208,7 +237,9 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon if (php_json_yyparse(&parser)) { php_json_error_code error_code = php_json_parser_error_code(&parser); + size_t error_pos = php_json_parser_error_pos(&parser); JSON_G(error_code) = error_code; + JSON_G(error_pos) = error_pos; return false; } @@ -238,6 +269,7 @@ PHP_FUNCTION(json_encode) if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { JSON_G(error_code) = encoder.error_code; + JSON_G(error_pos) = encoder.error_pos; if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { smart_str_free(&buf); RETURN_FALSE; @@ -364,6 +396,8 @@ PHP_FUNCTION(json_last_error_msg) { ZEND_PARSE_PARAMETERS_NONE(); - RETURN_STRING(php_json_get_error_msg(JSON_G(error_code))); + char *msg = php_json_get_error_msg_wrapper(JSON_G(error_code)); + RETVAL_STRING(msg); + efree(msg); } /* }}} */ diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index d570cddc91e4b..87a0f6d5455d8 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -35,6 +35,7 @@ int json_yydebug = 1; #define PHP_JSON_DEPTH_INC \ if (parser->max_depth && parser->depth >= parser->max_depth) { \ parser->scanner.errcode = PHP_JSON_ERROR_DEPTH; \ + parser->scanner.errpos = (size_t)((parser->scanner.str_start - parser->scanner.input_start) - parser->scanner.str_esc - parser->scanner.utf8_invalid_count); \ YYERROR; \ } \ ++parser->depth @@ -108,6 +109,7 @@ object_end: | ']' { parser->scanner.errcode = PHP_JSON_ERROR_STATE_MISMATCH; + parser->scanner.errpos = (size_t)((parser->scanner.str_start - parser->scanner.input_start) - parser->scanner.str_esc - parser->scanner.utf8_invalid_count); \ YYERROR; } ; @@ -164,6 +166,7 @@ array_end: | '}' { parser->scanner.errcode = PHP_JSON_ERROR_STATE_MISMATCH; + parser->scanner.errpos = (size_t)((parser->scanner.str_start - parser->scanner.input_start) - parser->scanner.str_esc - parser->scanner.utf8_invalid_count); \ YYERROR; } ; @@ -242,6 +245,7 @@ static int php_json_parser_object_update(php_json_parser *parser, zval *object, } else { if (ZSTR_LEN(key) > 0 && ZSTR_VAL(key)[0] == '\0') { parser->scanner.errcode = PHP_JSON_ERROR_INVALID_PROPERTY_NAME; + parser->scanner.errpos = (size_t)((parser->scanner.str_start - parser->scanner.input_start) - parser->scanner.str_esc - parser->scanner.utf8_invalid_count); \ zend_string_release_ex(key, 0); zval_ptr_dtor_nogc(zvalue); zval_ptr_dtor_nogc(object); @@ -300,6 +304,7 @@ static void php_json_yyerror(php_json_parser *parser, char const *msg) { if (!parser->scanner.errcode) { parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + parser->scanner.errpos = (size_t)((parser->scanner.str_start - parser->scanner.input_start) - parser->scanner.str_esc - parser->scanner.utf8_invalid_count); } } @@ -308,6 +313,11 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse return parser->scanner.errcode; } +PHP_JSON_API size_t php_json_parser_error_pos(const php_json_parser *parser) +{ + return parser->scanner.errpos; +} + static const php_json_parser_methods default_parser_methods = { php_json_parser_array_create, @@ -339,7 +349,7 @@ PHP_JSON_API void php_json_parser_init_ex(php_json_parser *parser, int options, int max_depth, const php_json_parser_methods *parser_methods) -{ +{ memset(parser, 0, sizeof(php_json_parser)); php_json_scanner_init(&parser->scanner, str, str_len, options); parser->depth = 1; diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 0debb3b03cb22..6625506826d43 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -93,9 +93,24 @@ static int php_json_ucs2_to_int(php_json_scanner *s, int size) void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len, int options) { + s->token = NULL; + s->marker = NULL; + s->ctxmarker = NULL; + s->pstr = NULL; + s->str_esc = 0; + s->state = 0; + s->errcode = PHP_JSON_ERROR_NONE; + s->errpos = 0; /* Initialize errpos */ + s->utf8_invalid = 0; + s->utf8_invalid_count = 0; + s->str_start = (php_json_ctype *)str; /* Initialize str_start */ + s->input_start = (php_json_ctype *)str; /* Initialize input_start */ + s->cursor = (php_json_ctype *) str; s->limit = (php_json_ctype *) str + str_len; s->options = options; + s->errpos = 0; + s->errcode = 0; PHP_JSON_CONDITION_SET(JS); } @@ -201,6 +216,7 @@ std: return PHP_JSON_T_EOI; } else { s->errcode = PHP_JSON_ERROR_CTRL_CHAR; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } } @@ -213,19 +229,23 @@ std: } CTRL { s->errcode = PHP_JSON_ERROR_CTRL_CHAR; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } UTF8 { s->errcode = PHP_JSON_ERROR_SYNTAX; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } ANY { s->errcode = PHP_JSON_ERROR_UTF8; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } CTRL { s->errcode = PHP_JSON_ERROR_CTRL_CHAR; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } UTF16_1 { @@ -246,6 +266,7 @@ std: } UCS2 { s->errcode = PHP_JSON_ERROR_UTF16; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } ESC { @@ -254,6 +275,8 @@ std: } ESCPREF { s->errcode = PHP_JSON_ERROR_SYNTAX; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); + return PHP_JSON_T_ERROR; } ["] { @@ -283,6 +306,7 @@ std: if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) { if (s->utf8_invalid_count > INT_MAX - 2) { s->errcode = PHP_JSON_ERROR_UTF8; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } s->utf8_invalid_count += 2; @@ -293,6 +317,7 @@ std: PHP_JSON_CONDITION_GOTO(STR_P1); } s->errcode = PHP_JSON_ERROR_UTF8; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); return PHP_JSON_T_ERROR; } @@ -358,6 +383,8 @@ std: break; default: s->errcode = PHP_JSON_ERROR_SYNTAX; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); + return PHP_JSON_T_ERROR; } *(s->pstr++) = esc; @@ -386,6 +413,8 @@ std: <*>ANY { s->errcode = PHP_JSON_ERROR_SYNTAX; + s->errpos = (size_t)(s->str_start - s->input_start - s->str_esc - s->utf8_invalid_count); + return PHP_JSON_T_ERROR; } */ diff --git a/ext/json/php_json.h b/ext/json/php_json.h index 00c87eca53c9e..11847b90c53ec 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -90,6 +90,7 @@ ZEND_BEGIN_MODULE_GLOBALS(json) int encoder_depth; int encode_max_depth; php_json_error_code error_code; + size_t error_pos; ZEND_END_MODULE_GLOBALS(json) PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index d66d71cb5538c..9f4f0696d825d 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -26,6 +26,7 @@ struct _php_json_encoder { int depth; int max_depth; php_json_error_code error_code; + size_t error_pos; }; static inline void php_json_encode_init(php_json_encoder *encoder) diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index 8aedce9ac55d6..df375a29f1735 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -77,6 +77,8 @@ PHP_JSON_API void php_json_parser_init( PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parser *parser); +PHP_JSON_API size_t php_json_parser_error_pos(const php_json_parser *parser); + PHP_JSON_API int php_json_parse(php_json_parser *parser); int php_json_yyparse(php_json_parser *parser); diff --git a/ext/json/php_json_scanner.h b/ext/json/php_json_scanner.h index a49be68cd6328..2ef2864359233 100644 --- a/ext/json/php_json_scanner.h +++ b/ext/json/php_json_scanner.h @@ -28,13 +28,15 @@ typedef struct _php_json_scanner { php_json_ctype *limit; /* the last read character + 1 position */ php_json_ctype *marker; /* marker position for backtracking */ php_json_ctype *ctxmarker; /* marker position for context backtracking */ - php_json_ctype *str_start; /* start position of the string */ + php_json_ctype *str_start; /* dynamic position of the string under analysis */ + php_json_ctype *input_start; /* fixed start position of the original string provided to the scanner */ php_json_ctype *pstr; /* string pointer for escapes conversion */ zval value; /* value */ int str_esc; /* number of extra characters for escaping */ int state; /* condition state */ int options; /* options */ php_json_error_code errcode; /* error type if there is an error */ + size_t errpos; /* holds the aprox character number when an error occurs */ int utf8_invalid; /* whether utf8 is invalid */ int utf8_invalid_count; /* number of extra character for invalid utf8 */ } php_json_scanner; diff --git a/ext/json/tests/007.phpt b/ext/json/tests/007.phpt index dea641317e97f..44eb10a864485 100644 --- a/ext/json/tests/007.phpt +++ b/ext/json/tests/007.phpt @@ -30,8 +30,8 @@ int(2) string(42) "State mismatch (invalid or malformed JSON)" NULL int(3) -string(53) "Control character error, possibly incorrectly encoded" +string(70) "Control character error, possibly incorrectly encoded near character 2" NULL int(4) -string(12) "Syntax error" +string(29) "Syntax error near character 0" Done diff --git a/ext/json/tests/bug54058.phpt b/ext/json/tests/bug54058.phpt index 25c5b6f5ceba7..48e21792a5ca1 100644 --- a/ext/json/tests/bug54058.phpt +++ b/ext/json/tests/bug54058.phpt @@ -29,10 +29,10 @@ var_dump(json_last_error(), json_last_error_msg()); ?> --EXPECT-- int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" diff --git a/ext/json/tests/bug61537.phpt b/ext/json/tests/bug61537.phpt index 32cae8c93dc26..cb8e65955926a 100644 --- a/ext/json/tests/bug61537.phpt +++ b/ext/json/tests/bug61537.phpt @@ -24,14 +24,14 @@ var_dump(json_last_error(), json_last_error_msg()); --EXPECT-- bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" string(4) "null" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" string(4) "null" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" diff --git a/ext/json/tests/bug62010.phpt b/ext/json/tests/bug62010.phpt index 2591231dcdda1..10b0dd4daec18 100644 --- a/ext/json/tests/bug62010.phpt +++ b/ext/json/tests/bug62010.phpt @@ -10,4 +10,4 @@ var_dump(json_last_error_msg()); --EXPECT-- NULL bool(true) -string(50) "Single unpaired UTF-16 surrogate in unicode escape" +string(67) "Single unpaired UTF-16 surrogate in unicode escape near character 1" diff --git a/ext/json/tests/bug68546.phpt b/ext/json/tests/bug68546.phpt index 8835a72c5eac7..4c4aff7e5dda5 100644 --- a/ext/json/tests/bug68546.phpt +++ b/ext/json/tests/bug68546.phpt @@ -16,5 +16,5 @@ NULL bool(true) NULL bool(true) -string(36) "The decoded property name is invalid" +string(54) "The decoded property name is invalid near character 15" Done diff --git a/ext/json/tests/json_encode_exceptions.phpt b/ext/json/tests/json_encode_exceptions.phpt index 2ca7ad4452d72..5d6515cddc49b 100644 --- a/ext/json/tests/json_encode_exceptions.phpt +++ b/ext/json/tests/json_encode_exceptions.phpt @@ -53,4 +53,4 @@ object(JsonException)#1 (7) { } string(4) "null" int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 0" diff --git a/ext/json/tests/json_validate_002.phpt b/ext/json/tests/json_validate_002.phpt index 53f4e4f2c2ea1..e7e4b78693e39 100644 --- a/ext/json/tests/json_validate_002.phpt +++ b/ext/json/tests/json_validate_002.phpt @@ -8,6 +8,9 @@ require_once("json_validate_requires.inc"); json_validate_trycatchdump(""); json_validate_trycatchdump("-"); json_validate_trycatchdump("", -1); +json_validate_trycatchdump('{"key1":"value1", "value2"}', 2); +json_validate_trycatchdump('{"text":"Hello \u20AC World", "key1":"Hello \xE2\x28 World"}', 2); +json_validate_trycatchdump('{"text":"Hello \u20AC World"}', 2); json_validate_trycatchdump('{"key1":"value1", "key2":"value2"}', 1); json_validate_trycatchdump('{"key1":"value1", "key2":"value2"}', 2); json_validate_trycatchdump("-", 0); @@ -20,13 +23,22 @@ json_validate_trycatchdump("{}", 512, JSON_INVALID_UTF8_IGNORE); --EXPECTF-- bool(false) int(4) -string(12) "Syntax error" +string(29) "Syntax error near character 0" bool(false) int(4) -string(12) "Syntax error" +string(29) "Syntax error near character 0" bool(false) int(4) -string(12) "Syntax error" +string(29) "Syntax error near character 0" +bool(false) +int(4) +string(30) "Syntax error near character 19" +bool(false) +int(4) +string(30) "Syntax error near character 38" +bool(true) +int(0) +string(8) "No error" bool(false) int(1) string(28) "Maximum stack depth exceeded" @@ -44,7 +56,7 @@ int(0) string(8) "No error" bool(false) int(4) -string(12) "Syntax error" +string(29) "Syntax error near character 0" bool(true) int(0) string(8) "No error" diff --git a/ext/json/tests/json_validate_004.phpt b/ext/json/tests/json_validate_004.phpt index d8a798d943278..6bf5720583f1c 100644 --- a/ext/json/tests/json_validate_004.phpt +++ b/ext/json/tests/json_validate_004.phpt @@ -23,16 +23,16 @@ json_validate_trycatchdump("[\"\xc1\xc1\",\"a\"]", 512, JSON_INVALID_UTF8_IGNORE Testing Invalid UTF-8 bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(73) "Malformed UTF-8 characters, possibly incorrectly encoded near character 2" bool(true) int(0) string(8) "No error" diff --git a/ext/json/tests/unsupported_type_error.phpt b/ext/json/tests/unsupported_type_error.phpt index 449d423fd9d0c..507a842fd4497 100644 --- a/ext/json/tests/unsupported_type_error.phpt +++ b/ext/json/tests/unsupported_type_error.phpt @@ -15,10 +15,10 @@ var_dump(json_last_error(), json_last_error_msg()); ?> --EXPECTF-- -resource(%d) of type (stream) +resource(5) of type (stream) bool(false) int(8) -string(21) "Type is not supported" +string(38) "Type is not supported near character 0" string(4) "null" int(8) -string(21) "Type is not supported" +string(38) "Type is not supported near character 0"