diff options
Diffstat (limited to 'ext/json/json_encoder.c')
-rw-r--r-- | ext/json/json_encoder.c | 300 |
1 files changed, 135 insertions, 165 deletions
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index a7453bf926..8e3eecc0d8 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -17,8 +17,6 @@ +----------------------------------------------------------------------+ */ -/* $Id$ */ - #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -117,14 +115,14 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options) #define PHP_JSON_HASH_APPLY_PROTECTION_INC(_tmp_ht) \ do { \ - if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \ + 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)) { \ + if (_tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \ ZEND_HASH_DEC_APPLY_COUNT(_tmp_ht); \ } \ } while (0) @@ -142,12 +140,14 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso r = PHP_JSON_OUTPUT_OBJECT; } - if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) { + if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) { encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); return FAILURE; } + PHP_JSON_HASH_APPLY_PROTECTION_INC(myht); + if (r == PHP_JSON_OUTPUT_ARRAY) { smart_str_appendc(buf, '['); } else { @@ -162,13 +162,8 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso zend_string *key; zval *data; zend_ulong index; - HashTable *tmp_ht; ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) { - ZVAL_DEREF(data); - tmp_ht = HASH_OF(data); - PHP_JSON_HASH_APPLY_PROTECTION_INC(tmp_ht); - if (r == PHP_JSON_OUTPUT_ARRAY) { if (need_comma) { smart_str_appendc(buf, ','); @@ -182,7 +177,6 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso if (key) { if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) { /* Skip protected and private members. */ - PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht); continue; } @@ -195,8 +189,13 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso php_json_pretty_print_char(buf, options, '\n'); php_json_pretty_print_indent(buf, options, encoder); - php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), - options & ~PHP_JSON_NUMERIC_CHECK, encoder); + if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), + options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE && + (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) && + buf->s) { + ZSTR_LEN(buf->s) -= 4; + smart_str_appendl(buf, "\"\"", 2); + } } else { if (need_comma) { smart_str_appendc(buf, ','); @@ -218,14 +217,14 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso 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); + PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht); return FAILURE; } - - PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht); } ZEND_HASH_FOREACH_END(); } + PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht); + if (encoder->depth > encoder->max_depth) { encoder->error_code = PHP_JSON_ERROR_DEPTH; if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { @@ -250,43 +249,6 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso } /* }}} */ -static int php_json_utf8_to_utf16(unsigned short *utf16, char utf8[], size_t len) /* {{{ */ -{ - size_t pos = 0, us; - int j, status; - - if (utf16) { - /* really convert the utf8 string */ - for (j=0 ; pos < len ; j++) { - us = php_next_utf8_char((const unsigned char *)utf8, len, &pos, &status); - if (status != SUCCESS) { - return -1; - } - /* From http://en.wikipedia.org/wiki/UTF16 */ - if (us >= 0x10000) { - us -= 0x10000; - utf16[j++] = (unsigned short)((us >> 10) | 0xd800); - utf16[j] = (unsigned short)((us & 0x3ff) | 0xdc00); - } else { - utf16[j] = (unsigned short)us; - } - } - } else { - /* Only check if utf8 string is valid, and compute utf16 length */ - for (j=0 ; pos < len ; j++) { - us = php_next_utf8_char((const unsigned char *)utf8, len, &pos, &status); - if (status != SUCCESS) { - return -1; - } - if (us >= 0x10000) { - j++; - } - } - } - return j; -} -/* }}} */ - static int php_json_escape_string( smart_str *buf, char *s, size_t len, int options, php_json_encoder *encoder) /* {{{ */ @@ -316,18 +278,6 @@ static int php_json_escape_string( } } - - if (options & PHP_JSON_UNESCAPED_UNICODE) { - /* validate UTF-8 string first */ - if (php_json_utf8_to_utf16(NULL, s, len) < 0) { - encoder->error_code = PHP_JSON_ERROR_UTF8; - if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { - smart_str_appendl(buf, "null", 4); - } - return FAILURE; - } - } - pos = 0; checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0; @@ -337,26 +287,44 @@ static int php_json_escape_string( do { us = (unsigned char)s[pos]; - 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 (us >= 0x80) { + int utf8_sub = 0; + size_t prev_pos = pos; + + us = php_next_utf8_char((unsigned char *)s, len, &pos, &status); + + /* check whether UTF8 character is correct */ if (status != SUCCESS) { - if (buf->s) { - ZSTR_LEN(buf->s) = checkpoint; - } - encoder->error_code = PHP_JSON_ERROR_UTF8; - if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { - smart_str_appendl(buf, "null", 4); + if (options & PHP_JSON_INVALID_UTF8_IGNORE) { + /* ignore invalid UTF8 character */ + continue; + } else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) { + /* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */ + us = 0xfffd; + utf8_sub = 1; + } else { + if (buf->s) { + ZSTR_LEN(buf->s) = checkpoint; + } + encoder->error_code = PHP_JSON_ERROR_UTF8; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; } - 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) + && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS) || us < 0x2028 || us > 0x2029)) { - smart_str_appendl(buf, &s[pos - 3], 3); + if (utf8_sub) { + smart_str_appendl(buf, "\xef\xbf\xbd", 3); + } else { + smart_str_appendl(buf, s + prev_pos, pos - prev_pos); + } continue; } /* From http://en.wikipedia.org/wiki/UTF16 */ @@ -378,90 +346,93 @@ static int php_json_escape_string( smart_str_appendc(buf, digits[(us & 0xf0) >> 4]); smart_str_appendc(buf, digits[(us & 0xf)]); } else { - pos++; - - switch (us) { - case '"': - if (options & PHP_JSON_HEX_QUOT) { - smart_str_appendl(buf, "\\u0022", 6); - } else { - smart_str_appendl(buf, "\\\"", 2); - } - break; - - case '\\': - smart_str_appendl(buf, "\\\\", 2); - break; - - case '/': - if (options & PHP_JSON_UNESCAPED_SLASHES) { - smart_str_appendc(buf, '/'); - } else { - smart_str_appendl(buf, "\\/", 2); - } - break; - - case '\b': - smart_str_appendl(buf, "\\b", 2); - break; - - case '\f': - smart_str_appendl(buf, "\\f", 2); - break; - - case '\n': - smart_str_appendl(buf, "\\n", 2); - break; - - case '\r': - smart_str_appendl(buf, "\\r", 2); - break; - - case '\t': - smart_str_appendl(buf, "\\t", 2); - break; - - case '<': - if (options & PHP_JSON_HEX_TAG) { - smart_str_appendl(buf, "\\u003C", 6); - } else { - smart_str_appendc(buf, '<'); - } - break; + static const uint32_t charmap[4] = { + 0xffffffff, 0x500080c4, 0x10000000, 0x00000000}; - case '>': - if (options & PHP_JSON_HEX_TAG) { - smart_str_appendl(buf, "\\u003E", 6); - } else { - smart_str_appendc(buf, '>'); - } - break; - - case '&': - if (options & PHP_JSON_HEX_AMP) { - smart_str_appendl(buf, "\\u0026", 6); - } else { - smart_str_appendc(buf, '&'); - } - break; - - case '\'': - if (options & PHP_JSON_HEX_APOS) { - smart_str_appendl(buf, "\\u0027", 6); - } else { - smart_str_appendc(buf, '\''); - } - break; - - default: - if (us >= ' ') { - smart_str_appendc(buf, (unsigned char) us); - } else { + pos++; + if (EXPECTED(!(charmap[us >> 5] & (1 << (us & 0x1f))))) { + smart_str_appendc(buf, (unsigned char) us); + } else { + switch (us) { + case '"': + if (options & PHP_JSON_HEX_QUOT) { + smart_str_appendl(buf, "\\u0022", 6); + } else { + smart_str_appendl(buf, "\\\"", 2); + } + break; + + case '\\': + smart_str_appendl(buf, "\\\\", 2); + break; + + case '/': + if (options & PHP_JSON_UNESCAPED_SLASHES) { + smart_str_appendc(buf, '/'); + } else { + smart_str_appendl(buf, "\\/", 2); + } + break; + + case '\b': + smart_str_appendl(buf, "\\b", 2); + break; + + case '\f': + smart_str_appendl(buf, "\\f", 2); + break; + + case '\n': + smart_str_appendl(buf, "\\n", 2); + break; + + case '\r': + smart_str_appendl(buf, "\\r", 2); + break; + + case '\t': + smart_str_appendl(buf, "\\t", 2); + break; + + case '<': + if (options & PHP_JSON_HEX_TAG) { + smart_str_appendl(buf, "\\u003C", 6); + } else { + smart_str_appendc(buf, '<'); + } + break; + + case '>': + if (options & PHP_JSON_HEX_TAG) { + smart_str_appendl(buf, "\\u003E", 6); + } else { + smart_str_appendc(buf, '>'); + } + break; + + case '&': + if (options & PHP_JSON_HEX_AMP) { + smart_str_appendl(buf, "\\u0026", 6); + } else { + smart_str_appendc(buf, '&'); + } + break; + + case '\'': + if (options & PHP_JSON_HEX_APOS) { + smart_str_appendl(buf, "\\u0027", 6); + } else { + smart_str_appendc(buf, '\''); + } + break; + + default: + ZEND_ASSERT(us < ' '); smart_str_appendl(buf, "\\u00", sizeof("\\u00")-1); smart_str_appendc(buf, digits[(us & 0xf0) >> 4]); smart_str_appendc(buf, digits[(us & 0xf)]); - } - break; + break; + } } } } while (pos < len); @@ -475,17 +446,11 @@ static int php_json_escape_string( 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); + HashTable* myht = Z_OBJPROP_P(val); zval retval, fname; - HashTable* myht; int return_code; - if (Z_TYPE_P(val) == IS_ARRAY) { - myht = Z_ARRVAL_P(val); - } else { - myht = Z_OBJPROP_P(val); - } - - if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) { + if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) { encoder->error_code = PHP_JSON_ERROR_RECURSION; if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); @@ -493,6 +458,7 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op return FAILURE; } + PHP_JSON_HASH_APPLY_PROTECTION_INC(myht); ZVAL_STRING(&fname, "jsonSerialize"); @@ -505,6 +471,7 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } + PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht); return FAILURE; } @@ -516,16 +483,19 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } + PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht); 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_HASH_APPLY_PROTECTION_DEC(myht); return_code = php_json_encode_array(buf, &retval, options, encoder); } else { /* All other types, encode as normal */ return_code = php_json_encode_zval(buf, &retval, options, encoder); + PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht); } zval_ptr_dtor(&retval); |