summaryrefslogtreecommitdiff
path: root/ext/json/json_encoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/json/json_encoder.c')
-rw-r--r--ext/json/json_encoder.c200
1 files changed, 118 insertions, 82 deletions
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c
index 8da2761e99..ca41f383be 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;
}
/* }}} */