diff options
Diffstat (limited to 'ext/json/json_encoder.c')
-rw-r--r-- | ext/json/json_encoder.c | 200 |
1 files changed, 118 insertions, 82 deletions
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 43f78cb17c..c5f92e1919 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -29,19 +29,14 @@ #include "ext/standard/html.h" #include "zend_smart_str.h" #include "php_json.h" +#include "php_json_encoder.h" #include <zend_exceptions.h> -/* double limits */ -#include <float.h> -#if defined(DBL_MANT_DIG) && defined(DBL_MIN_EXP) -#define PHP_JSON_DOUBLE_MAX_LENGTH (3 + DBL_MANT_DIG - DBL_MIN_EXP) -#else -#define PHP_JSON_DOUBLE_MAX_LENGTH 1080 -#endif - static const char digits[] = "0123456789abcdef"; -static void php_json_escape_string(smart_str *buf, char *s, size_t len, int options); +static int php_json_escape_string( + smart_str *buf, char *s, size_t len, + int options, php_json_encoder *encoder); static int php_json_determine_array_type(zval *val) /* {{{ */ { @@ -53,6 +48,10 @@ static int php_json_determine_array_type(zval *val) /* {{{ */ zend_string *key; zend_ulong index, idx; + if (HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) { + return PHP_JSON_OUTPUT_ARRAY; + } + idx = 0; ZEND_HASH_FOREACH_KEY(myht, index, key) { if (key) { @@ -80,12 +79,12 @@ static inline void php_json_pretty_print_char(smart_str *buf, int options, char } /* }}} */ -static inline void php_json_pretty_print_indent(smart_str *buf, int options) /* {{{ */ +static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */ { int i; if (options & PHP_JSON_PRETTY_PRINT) { - for (i = 0; i < JSON_G(encoder_depth); ++i) { + for (i = 0; i < encoder->depth; ++i) { smart_str_appendl(buf, " ", 4); } } @@ -103,10 +102,11 @@ static inline int php_json_is_valid_double(double d) /* {{{ */ static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */ { size_t len; - char num[PHP_JSON_DOUBLE_MAX_LENGTH]; - php_gcvt(d, (int)EG(precision), '.', 'e', &num[0]); + char num[PHP_DOUBLE_MAX_LENGTH]; + + php_gcvt(d, (int)PG(serialize_precision), '.', 'e', num); len = strlen(num); - if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_JSON_DOUBLE_MAX_LENGTH - 2) { + if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) { num[len++] = '.'; num[len++] = '0'; num[len] = '\0'; @@ -115,7 +115,21 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options) } /* }}} */ -static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */ +#define PHP_JSON_HASH_APPLY_PROTECTION_INC(_tmp_ht) \ + do { \ + if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \ + ZEND_HASH_INC_APPLY_COUNT(_tmp_ht); \ + } \ + } while (0) + +#define PHP_JSON_HASH_APPLY_PROTECTION_DEC(_tmp_ht) \ + do { \ + if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \ + ZEND_HASH_DEC_APPLY_COUNT(_tmp_ht); \ + } \ + } while (0) + +static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { int i, r, need_comma = 0; HashTable *myht; @@ -129,9 +143,9 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ } if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) { - JSON_G(error_code) = PHP_JSON_ERROR_RECURSION; + encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); - return; + return FAILURE; } if (r == PHP_JSON_OUTPUT_ARRAY) { @@ -140,7 +154,7 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ smart_str_appendc(buf, '{'); } - ++JSON_G(encoder_depth); + ++encoder->depth; i = myht ? zend_hash_num_elements(myht) : 0; @@ -153,9 +167,7 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) { ZVAL_DEREF(data); tmp_ht = HASH_OF(data); - if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) { - ZEND_HASH_INC_APPLY_COUNT(tmp_ht); - } + PHP_JSON_HASH_APPLY_PROTECTION_INC(tmp_ht); if (r == PHP_JSON_OUTPUT_ARRAY) { if (need_comma) { @@ -165,15 +177,12 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ } php_json_pretty_print_char(buf, options, '\n'); - php_json_pretty_print_indent(buf, options); - php_json_encode(buf, data, options); + php_json_pretty_print_indent(buf, options, encoder); } else if (r == PHP_JSON_OUTPUT_OBJECT) { if (key) { - if (ZSTR_VAL(key)[0] == '\0' && Z_TYPE_P(val) == IS_OBJECT) { + if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) { /* Skip protected and private members. */ - if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) { - ZEND_HASH_DEC_APPLY_COUNT(tmp_ht); - } + PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht); continue; } @@ -184,14 +193,10 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ } php_json_pretty_print_char(buf, options, '\n'); - php_json_pretty_print_indent(buf, options); - - php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK); - smart_str_appendc(buf, ':'); - - php_json_pretty_print_char(buf, options, ' '); + php_json_pretty_print_indent(buf, options, encoder); - php_json_encode(buf, data, options); + php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), + options & ~PHP_JSON_NUMERIC_CHECK, encoder); } else { if (need_comma) { smart_str_appendc(buf, ','); @@ -200,34 +205,39 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ } php_json_pretty_print_char(buf, options, '\n'); - php_json_pretty_print_indent(buf, options); + php_json_pretty_print_indent(buf, options, encoder); smart_str_appendc(buf, '"'); smart_str_append_long(buf, (zend_long) index); smart_str_appendc(buf, '"'); - smart_str_appendc(buf, ':'); - - php_json_pretty_print_char(buf, options, ' '); - - php_json_encode(buf, data, options); } + + smart_str_appendc(buf, ':'); + php_json_pretty_print_char(buf, options, ' '); } - if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) { - ZEND_HASH_DEC_APPLY_COUNT(tmp_ht); + if (php_json_encode_zval(buf, data, options, encoder) == FAILURE && + !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { + PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht); + return FAILURE; } + + PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht); } ZEND_HASH_FOREACH_END(); } - if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) { - JSON_G(error_code) = PHP_JSON_ERROR_DEPTH; + if (encoder->depth > encoder->max_depth) { + encoder->error_code = PHP_JSON_ERROR_DEPTH; + if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { + return FAILURE; + } } - --JSON_G(encoder_depth); + --encoder->depth; /* Only keep closing bracket on same line for empty arrays/objects */ if (need_comma) { php_json_pretty_print_char(buf, options, '\n'); - php_json_pretty_print_indent(buf, options); + php_json_pretty_print_indent(buf, options, encoder); } if (r == PHP_JSON_OUTPUT_ARRAY) { @@ -235,6 +245,8 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ } else { smart_str_appendc(buf, '}'); } + + return SUCCESS; } /* }}} */ @@ -275,7 +287,9 @@ static int php_json_utf8_to_utf16(unsigned short *utf16, char utf8[], size_t len } /* }}} */ -static void php_json_escape_string(smart_str *buf, char *s, size_t len, int options) /* {{{ */ +static int php_json_escape_string( + smart_str *buf, char *s, size_t len, + int options, php_json_encoder *encoder) /* {{{ */ { int status; unsigned int us; @@ -283,7 +297,7 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti if (len == 0) { smart_str_appendl(buf, "\"\"", 2); - return; + return SUCCESS; } if (options & PHP_JSON_NUMERIC_CHECK) { @@ -294,10 +308,10 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) { if (type == IS_LONG) { smart_str_append_long(buf, p); - return; + return SUCCESS; } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) { php_json_encode_double(buf, d, options); - return; + return SUCCESS; } } @@ -306,9 +320,11 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti if (options & PHP_JSON_UNESCAPED_UNICODE) { /* validate UTF-8 string first */ if (php_json_utf8_to_utf16(NULL, s, len) < 0) { - JSON_G(error_code) = PHP_JSON_ERROR_UTF8; - smart_str_appendl(buf, "null", 4); - return; + encoder->error_code = PHP_JSON_ERROR_UTF8; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } } @@ -321,16 +337,27 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti do { us = (unsigned char)s[pos]; - if (us >= 0x80 && !(options & PHP_JSON_UNESCAPED_UNICODE)) { + if (us >= 0x80 && (!(options & PHP_JSON_UNESCAPED_UNICODE) || us == 0xE2)) { /* UTF-8 character */ us = php_next_utf8_char((const unsigned char *)s, len, &pos, &status); if (status != SUCCESS) { if (buf->s) { ZSTR_LEN(buf->s) = checkpoint; } - JSON_G(error_code) = PHP_JSON_ERROR_UTF8; - smart_str_appendl(buf, "null", 4); - return; + encoder->error_code = PHP_JSON_ERROR_UTF8; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; + } + /* Escape U+2028/U+2029 line terminators, UNLESS both + JSON_UNESCAPED_UNICODE and + JSON_UNESCAPED_LINE_TERMINATORS were provided */ + if ((options & PHP_JSON_UNESCAPED_UNICODE) + && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS) + || us < 0x2028 || us > 0x2029)) { + smart_str_appendl(buf, &s[pos - 3], 3); + continue; } /* From http://en.wikipedia.org/wiki/UTF16 */ if (us >= 0x10000) { @@ -440,15 +467,17 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti } while (pos < len); smart_str_appendc(buf, '"'); + + return SUCCESS; } /* }}} */ -static void php_json_encode_serializable_object(smart_str *buf, zval *val, int options) /* {{{ */ +static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(val); zval retval, fname; HashTable* myht; - int origin_error_code; + int return_code; if (Z_TYPE_P(val) == IS_ARRAY) { myht = Z_ARRVAL_P(val); @@ -457,48 +486,56 @@ static void php_json_encode_serializable_object(smart_str *buf, zval *val, int o } if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) { - JSON_G(error_code) = PHP_JSON_ERROR_RECURSION; - smart_str_appendl(buf, "null", 4); - return; + encoder->error_code = PHP_JSON_ERROR_RECURSION; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } ZVAL_STRING(&fname, "jsonSerialize"); - origin_error_code = JSON_G(error_code); if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) { if (!EG(exception)) { zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name)); } - smart_str_appendl(buf, "null", sizeof("null") - 1); zval_ptr_dtor(&fname); - return; + + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } - JSON_G(error_code) = origin_error_code; if (EG(exception)) { /* Error already raised */ zval_ptr_dtor(&retval); zval_ptr_dtor(&fname); - smart_str_appendl(buf, "null", sizeof("null") - 1); - return; + + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } if ((Z_TYPE(retval) == IS_OBJECT) && (Z_OBJ(retval) == Z_OBJ_P(val))) { /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */ - php_json_encode_array(buf, &retval, options); + return_code = php_json_encode_array(buf, &retval, options, encoder); } else { /* All other types, encode as normal */ - php_json_encode(buf, &retval, options); + return_code = php_json_encode_zval(buf, &retval, options, encoder); } zval_ptr_dtor(&retval); zval_ptr_dtor(&fname); + + return return_code; } /* }}} */ -void php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */ +int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { again: switch (Z_TYPE_P(val)) @@ -522,36 +559,35 @@ again: if (php_json_is_valid_double(Z_DVAL_P(val))) { php_json_encode_double(buf, Z_DVAL_P(val), options); } else { - JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN; + encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN; smart_str_appendc(buf, '0'); } break; case IS_STRING: - php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options); - break; + return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder); case IS_OBJECT: if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) { - php_json_encode_serializable_object(buf, val, options); - break; + return php_json_encode_serializable_object(buf, val, options, encoder); } /* fallthrough -- Non-serializable object */ case IS_ARRAY: - php_json_encode_array(buf, val, options); - break; + return php_json_encode_array(buf, val, options, encoder); case IS_REFERENCE: val = Z_REFVAL_P(val); goto again; default: - JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE; - smart_str_appendl(buf, "null", 4); - break; + encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } - return; + return SUCCESS; } /* }}} */ |