summaryrefslogtreecommitdiff
path: root/ext/json
diff options
context:
space:
mode:
Diffstat (limited to 'ext/json')
-rw-r--r--ext/json/JSON_parser.c29
-rw-r--r--ext/json/JSON_parser.h9
-rw-r--r--ext/json/config.m42
-rw-r--r--ext/json/config.w323
-rw-r--r--ext/json/json.c233
-rw-r--r--ext/json/php_json.h24
-rw-r--r--ext/json/tests/008.phpt17
-rw-r--r--ext/json/tests/bug53946.phpt16
-rw-r--r--ext/json/tests/bug61978.phpt47
-rw-r--r--ext/json/tests/json_decode_error.phpt4
-rw-r--r--ext/json/tests/json_encode_basic.phpt2
-rw-r--r--ext/json/tests/json_encode_numeric.phpt26
-rw-r--r--ext/json/tests/json_encode_pretty_print.phpt40
-rw-r--r--ext/json/tests/json_encode_unescaped_slashes.phpt12
-rw-r--r--ext/json/tests/serialize.phpt80
-rw-r--r--ext/json/utf8_to_utf16.c56
-rw-r--r--ext/json/utf8_to_utf16.h3
17 files changed, 499 insertions, 104 deletions
diff --git a/ext/json/JSON_parser.c b/ext/json/JSON_parser.c
index ec07a4803d..dd832a7cbd 100644
--- a/ext/json/JSON_parser.c
+++ b/ext/json/JSON_parser.c
@@ -291,12 +291,14 @@ static int dehexchar(char c)
}
-static void json_create_zval(zval **z, smart_str *buf, int type)
+static void json_create_zval(zval **z, smart_str *buf, int type, int options)
{
ALLOC_INIT_ZVAL(*z);
if (type == IS_LONG)
{
+ zend_bool bigint = 0;
+
if (buf->c[0] == '-') {
buf->len--;
}
@@ -306,8 +308,21 @@ static void json_create_zval(zval **z, smart_str *buf, int type)
int cmp = strcmp(buf->c + (buf->c[0] == '-'), long_min_digits);
if (!(cmp < 0 || (cmp == 0 && buf->c[0] == '-'))) {
- goto use_double;
+ bigint = 1;
+ }
+ } else {
+ bigint = 1;
+ }
+ }
+
+ if (bigint) {
+ /* value too large to represent as a long */
+ if (options & PHP_JSON_BIGINT_AS_STRING) {
+ if (buf->c[0] == '-') {
+ /* Restore last char consumed above */
+ buf->len++;
}
+ goto use_string;
} else {
goto use_double;
}
@@ -322,6 +337,7 @@ use_double:
}
else if (type == IS_STRING)
{
+use_string:
ZVAL_STRINGL(*z, buf->c, buf->len, 1);
}
else if (type == IS_BOOL)
@@ -420,12 +436,13 @@ static void attach_zval(JSON_parser jp, int up, int cur, smart_str *key, int ass
machine with a stack.
*/
int
-parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int assoc TSRMLS_DC)
+parse_JSON_ex(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int options TSRMLS_DC)
{
int next_char; /* the next character */
int next_class; /* the next character class */
int next_state; /* the next state */
int the_index;
+ int assoc = options & PHP_JSON_OBJECT_AS_ARRAY;
smart_str buf = {0};
smart_str key = {0};
@@ -530,7 +547,7 @@ parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int
zval *mval;
smart_str_0(&buf);
- json_create_zval(&mval, &buf, type);
+ json_create_zval(&mval, &buf, type, options);
if (!assoc) {
add_property_zval_ex(jp->the_zstack[jp->top], (key.len ? key.c : "_empty_"), (key.len ? (key.len + 1) : sizeof("_empty_")), mval TSRMLS_CC);
@@ -558,7 +575,7 @@ parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int
zval *mval;
smart_str_0(&buf);
- json_create_zval(&mval, &buf, type);
+ json_create_zval(&mval, &buf, type, options);
add_next_index_zval(jp->the_zstack[jp->top], mval);
buf.len = 0;
JSON_RESET_TYPE();
@@ -670,7 +687,7 @@ parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int
jp->stack[jp->top] == MODE_ARRAY))
{
smart_str_0(&buf);
- json_create_zval(&mval, &buf, type);
+ json_create_zval(&mval, &buf, type, options);
}
switch (jp->stack[jp->top]) {
diff --git a/ext/json/JSON_parser.h b/ext/json/JSON_parser.h
index 746190bb35..541664b8c6 100644
--- a/ext/json/JSON_parser.h
+++ b/ext/json/JSON_parser.h
@@ -5,6 +5,7 @@
#include "php.h"
#include "ext/standard/php_smart_str.h"
+#include "php_json.h"
#define JSON_PARSER_DEFAULT_DEPTH 512
@@ -28,6 +29,12 @@ enum error_codes {
};
extern JSON_parser new_JSON_parser(int depth);
-extern int parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int assoc TSRMLS_DC);
+extern int parse_JSON_ex(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int options TSRMLS_DC);
extern int free_JSON_parser(JSON_parser jp);
+
+static inline int parse_JSON(JSON_parser jp, zval *z, unsigned short utf16_json[], int length, int assoc TSRMLS_DC)
+{
+ return parse_JSON_ex(jp, z, utf16_json, length, assoc ? PHP_JSON_OBJECT_AS_ARRAY : 0 TSRMLS_CC);
+}
+
#endif
diff --git a/ext/json/config.m4 b/ext/json/config.m4
index b62e938421..26c43a0e3f 100644
--- a/ext/json/config.m4
+++ b/ext/json/config.m4
@@ -9,7 +9,7 @@ if test "$PHP_JSON" != "no"; then
AC_DEFINE([HAVE_JSON],1 ,[whether to enable JavaScript Object Serialization support])
AC_HEADER_STDC
- PHP_NEW_EXTENSION(json, json.c utf8_to_utf16.c utf8_decode.c JSON_parser.c, $ext_shared)
+ PHP_NEW_EXTENSION(json, json.c utf8_decode.c JSON_parser.c, $ext_shared)
PHP_INSTALL_HEADERS([ext/json], [php_json.h])
PHP_SUBST(JSON_SHARED_LIBADD)
fi
diff --git a/ext/json/config.w32 b/ext/json/config.w32
index a3559e08b7..cedbf42829 100644
--- a/ext/json/config.w32
+++ b/ext/json/config.w32
@@ -5,6 +5,7 @@ ARG_ENABLE("json", "JavaScript Object Serialization support", "yes");
if (PHP_JSON != "no") {
EXTENSION('json', 'json.c', PHP_JSON_SHARED, "");
- ADD_SOURCES(configure_module_dirname, "JSON_parser.c utf8_decode.c utf8_to_utf16.c", "json");
+ ADD_SOURCES(configure_module_dirname, "JSON_parser.c utf8_decode.c", "json");
+ PHP_INSTALL_HEADERS("ext/json/", "php_json.h");
}
diff --git a/ext/json/json.c b/ext/json/json.c
index 5b62c2feea..fb01529711 100644
--- a/ext/json/json.c
+++ b/ext/json/json.c
@@ -25,10 +25,11 @@
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
+#include "ext/standard/html.h"
#include "ext/standard/php_smart_str.h"
-#include "utf8_to_utf16.h"
#include "JSON_parser.h"
#include "php_json.h"
+#include <zend_exceptions.h>
static PHP_MINFO_FUNCTION(json);
static PHP_FUNCTION(json_encode);
@@ -37,6 +38,8 @@ static PHP_FUNCTION(json_last_error);
static const char digits[] = "0123456789abcdef";
+zend_class_entry *php_json_serializable_ce;
+
ZEND_DECLARE_MODULE_GLOBALS(json)
/* {{{ arginfo */
@@ -49,6 +52,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_json_decode, 0, 0, 1)
ZEND_ARG_INFO(0, json)
ZEND_ARG_INFO(0, assoc)
ZEND_ARG_INFO(0, depth)
+ ZEND_ARG_INFO(0, options)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_json_last_error, 0)
@@ -56,7 +60,7 @@ ZEND_END_ARG_INFO()
/* }}} */
/* {{{ json_functions[] */
-static const function_entry json_functions[] = {
+static const zend_function_entry json_functions[] = {
PHP_FE(json_encode, arginfo_json_encode)
PHP_FE(json_decode, arginfo_json_decode)
PHP_FE(json_last_error, arginfo_json_last_error)
@@ -64,15 +68,34 @@ static const function_entry json_functions[] = {
};
/* }}} */
+/* {{{ JsonSerializable methods */
+ZEND_BEGIN_ARG_INFO(json_serialize_arginfo, 0)
+ /* No arguments */
+ZEND_END_ARG_INFO();
+
+static const zend_function_entry json_serializable_interface[] = {
+ PHP_ABSTRACT_ME(JsonSerializable, jsonSerialize, json_serialize_arginfo)
+ PHP_FE_END
+};
+/* }}} */
+
/* {{{ MINIT */
static PHP_MINIT_FUNCTION(json)
{
+ zend_class_entry ce;
+
+ INIT_CLASS_ENTRY(ce, "JsonSerializable", json_serializable_interface);
+ php_json_serializable_ce = zend_register_internal_interface(&ce TSRMLS_CC);
+
REGISTER_LONG_CONSTANT("JSON_HEX_TAG", PHP_JSON_HEX_TAG, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_HEX_AMP", PHP_JSON_HEX_AMP, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_HEX_APOS", PHP_JSON_HEX_APOS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_HEX_QUOT", PHP_JSON_HEX_QUOT, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_FORCE_OBJECT", PHP_JSON_FORCE_OBJECT, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_NUMERIC_CHECK", PHP_JSON_NUMERIC_CHECK, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("JSON_UNESCAPED_SLASHES", PHP_JSON_UNESCAPED_SLASHES, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("JSON_PRETTY_PRINT", PHP_JSON_PRETTY_PRINT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("JSON_UNESCAPED_UNICODE", PHP_JSON_UNESCAPED_UNICODE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_ERROR_NONE", PHP_JSON_ERROR_NONE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_ERROR_DEPTH", PHP_JSON_ERROR_DEPTH, CONST_CS | CONST_PERSISTENT);
@@ -81,6 +104,9 @@ static PHP_MINIT_FUNCTION(json)
REGISTER_LONG_CONSTANT("JSON_ERROR_SYNTAX", PHP_JSON_ERROR_SYNTAX, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("JSON_ERROR_UTF8", PHP_JSON_ERROR_UTF8, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("JSON_OBJECT_AS_ARRAY", PHP_JSON_OBJECT_AS_ARRAY, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("JSON_BIGINT_AS_STRING", PHP_JSON_BIGINT_AS_STRING, CONST_CS | CONST_PERSISTENT);
+
return SUCCESS;
}
/* }}} */
@@ -89,6 +115,7 @@ static PHP_MINIT_FUNCTION(json)
*/
static PHP_GINIT_FUNCTION(json)
{
+ json_globals->encoder_depth = 0;
json_globals->error_code = 0;
}
/* }}} */
@@ -166,6 +193,30 @@ static int json_determine_array_type(zval **val TSRMLS_DC) /* {{{ */
}
/* }}} */
+/* {{{ Pretty printing support functions */
+
+static inline void json_pretty_print_char(smart_str *buf, int options, char c TSRMLS_DC) /* {{{ */
+{
+ if (options & PHP_JSON_PRETTY_PRINT) {
+ smart_str_appendc(buf, c);
+ }
+}
+/* }}} */
+
+static inline void json_pretty_print_indent(smart_str *buf, int options TSRMLS_DC) /* {{{ */
+{
+ int i;
+
+ if (options & PHP_JSON_PRETTY_PRINT) {
+ for (i = 0; i < JSON_G(encoder_depth); ++i) {
+ smart_str_appendl(buf, " ", 4);
+ }
+ }
+}
+/* }}} */
+
+/* }}} */
+
static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC) /* {{{ */
{
int i, r;
@@ -191,6 +242,9 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
smart_str_appendc(buf, '{');
}
+ json_pretty_print_char(buf, options, '\n' TSRMLS_CC);
+ ++JSON_G(encoder_depth);
+
i = myht ? zend_hash_num_elements(myht) : 0;
if (i > 0)
@@ -218,10 +272,12 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
if (r == PHP_JSON_OUTPUT_ARRAY) {
if (need_comma) {
smart_str_appendc(buf, ',');
+ json_pretty_print_char(buf, options, '\n' TSRMLS_CC);
} else {
need_comma = 1;
}
-
+
+ json_pretty_print_indent(buf, options TSRMLS_CC);
php_json_encode(buf, *data, options TSRMLS_CC);
} else if (r == PHP_JSON_OUTPUT_OBJECT) {
if (i == HASH_KEY_IS_STRING) {
@@ -235,26 +291,36 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
if (need_comma) {
smart_str_appendc(buf, ',');
+ json_pretty_print_char(buf, options, '\n' TSRMLS_CC);
} else {
need_comma = 1;
}
+ json_pretty_print_indent(buf, options TSRMLS_CC);
+
json_escape_string(buf, key, key_len - 1, options & ~PHP_JSON_NUMERIC_CHECK TSRMLS_CC);
smart_str_appendc(buf, ':');
+ json_pretty_print_char(buf, options, ' ' TSRMLS_CC);
+
php_json_encode(buf, *data, options TSRMLS_CC);
} else {
if (need_comma) {
smart_str_appendc(buf, ',');
+ json_pretty_print_char(buf, options, '\n' TSRMLS_CC);
} else {
need_comma = 1;
}
+ json_pretty_print_indent(buf, options TSRMLS_CC);
+
smart_str_appendc(buf, '"');
smart_str_append_long(buf, (long) index);
smart_str_appendc(buf, '"');
smart_str_appendc(buf, ':');
+ json_pretty_print_char(buf, options, ' ' TSRMLS_CC);
+
php_json_encode(buf, *data, options TSRMLS_CC);
}
}
@@ -266,6 +332,10 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
}
}
+ --JSON_G(encoder_depth);
+ json_pretty_print_char(buf, options, '\n' TSRMLS_CC);
+ json_pretty_print_indent(buf, options TSRMLS_CC);
+
if (r == PHP_JSON_OUTPUT_ARRAY) {
smart_str_appendc(buf, ']');
} else {
@@ -274,13 +344,50 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
}
/* }}} */
-#define REVERSE16(us) (((us & 0xf) << 12) | (((us >> 4) & 0xf) << 8) | (((us >> 8) & 0xf) << 4) | ((us >> 12) & 0xf))
+static int json_utf8_to_utf16(unsigned short *utf16, char utf8[], int 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 lenght */
+ 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 void json_escape_string(smart_str *buf, char *s, int len, int options TSRMLS_DC) /* {{{ */
{
- int pos = 0;
+ int pos = 0, ulen = 0;
unsigned short us;
unsigned short *utf16;
+ size_t newlen;
if (len == 0) {
smart_str_appendl(buf, "\"\"", 2);
@@ -308,17 +415,16 @@ static void json_escape_string(smart_str *buf, char *s, int len, int options TSR
}
return;
}
-
- }
- utf16 = (unsigned short *) safe_emalloc(len, sizeof(unsigned short), 0);
+ }
- len = utf8_to_utf16(utf16, s, len);
- if (len <= 0) {
+ utf16 = (options & PHP_JSON_UNESCAPED_UNICODE) ? NULL : (unsigned short *) safe_emalloc(len, sizeof(unsigned short), 0);
+ ulen = json_utf8_to_utf16(utf16, s, len);
+ if (ulen <= 0) {
if (utf16) {
efree(utf16);
}
- if (len < 0) {
+ if (ulen < 0) {
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
if (!PG(display_errors)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid UTF-8 sequence in argument");
@@ -329,12 +435,17 @@ static void json_escape_string(smart_str *buf, char *s, int len, int options TSR
}
return;
}
+ if (!(options & PHP_JSON_UNESCAPED_UNICODE)) {
+ len = ulen;
+ }
+ /* pre-allocate for string length plus 2 quotes */
+ smart_str_alloc(buf, len+2, 0);
smart_str_appendc(buf, '"');
while (pos < len)
{
- us = utf16[pos++];
+ us = (options & PHP_JSON_UNESCAPED_UNICODE) ? s[pos++] : utf16[pos++];
switch (us)
{
@@ -351,7 +462,11 @@ static void json_escape_string(smart_str *buf, char *s, int len, int options TSR
break;
case '/':
- smart_str_appendl(buf, "\\/", 2);
+ if (options & PHP_JSON_UNESCAPED_SLASHES) {
+ smart_str_appendc(buf, '/');
+ } else {
+ smart_str_appendl(buf, "\\/", 2);
+ }
break;
case '\b':
@@ -407,26 +522,70 @@ static void json_escape_string(smart_str *buf, char *s, int len, int options TSR
break;
default:
- if (us >= ' ' && (us & 127) == us) {
+ if (us >= ' ' && ((options & PHP_JSON_UNESCAPED_UNICODE) || (us & 127) == us)) {
smart_str_appendc(buf, (unsigned char) us);
} else {
smart_str_appendl(buf, "\\u", 2);
- us = REVERSE16(us);
-
- smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
- us >>= 4;
- smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
- us >>= 4;
- smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
- us >>= 4;
- smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
+ smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
+ smart_str_appendc(buf, digits[(us & 0xf00) >> 8]);
+ smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
+ smart_str_appendc(buf, digits[(us & 0xf)]);
}
break;
}
}
smart_str_appendc(buf, '"');
- efree(utf16);
+ if (utf16) {
+ efree(utf16);
+ }
+}
+/* }}} */
+
+
+static void json_encode_serializable_object(smart_str *buf, zval *val, int options TSRMLS_DC) /* {{{ */
+{
+ zend_class_entry *ce = Z_OBJCE_P(val);
+ zval *retval = NULL, fname;
+ HashTable* myht;
+
+ if (Z_TYPE_P(val) == IS_ARRAY) {
+ myht = HASH_OF(val);
+ } else {
+ myht = Z_OBJPROP_P(val);
+ }
+
+ if (myht && myht->nApplyCount > 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
+ smart_str_appendl(buf, "null", 4);
+ return;
+ }
+
+ ZVAL_STRING(&fname, "jsonSerialize", 0);
+
+ if (FAILURE == call_user_function_ex(EG(function_table), &val, &fname, &retval, 0, NULL, 1, NULL TSRMLS_CC) || !retval) {
+ zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Failed calling %s::jsonSerialize()", ce->name);
+ smart_str_appendl(buf, "null", sizeof("null") - 1);
+ return;
+ }
+
+ if (EG(exception)) {
+ /* Error already raised */
+ zval_ptr_dtor(&retval);
+ smart_str_appendl(buf, "null", sizeof("null") - 1);
+ return;
+ }
+
+ if ((Z_TYPE_P(retval) == IS_OBJECT) &&
+ (Z_OBJ_HANDLE_P(retval) == Z_OBJ_HANDLE_P(val))) {
+ /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
+ json_encode_array(buf, &retval, options TSRMLS_CC);
+ } else {
+ /* All other types, encode as normal */
+ php_json_encode(buf, retval, options TSRMLS_CC);
+ }
+
+ zval_ptr_dtor(&retval);
}
/* }}} */
@@ -471,8 +630,13 @@ PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_
json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options TSRMLS_CC);
break;
- case IS_ARRAY:
case IS_OBJECT:
+ if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce TSRMLS_CC)) {
+ json_encode_serializable_object(buf, val, options TSRMLS_CC);
+ break;
+ }
+ /* fallthrough -- Non-serializable object */
+ case IS_ARRAY:
json_encode_array(buf, &val, options TSRMLS_CC);
break;
@@ -486,7 +650,7 @@ PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_
}
/* }}} */
-PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, zend_bool assoc, long depth TSRMLS_DC) /* {{{ */
+PHP_JSON_API void php_json_decode_ex(zval *return_value, char *str, int str_len, int options, long depth TSRMLS_DC) /* {{{ */
{
int utf16_len;
zval *z;
@@ -495,7 +659,7 @@ PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, ze
utf16 = (unsigned short *) safe_emalloc((str_len+1), sizeof(unsigned short), 1);
- utf16_len = utf8_to_utf16(utf16, str, str_len);
+ utf16_len = json_utf8_to_utf16(utf16, str, str_len);
if (utf16_len <= 0) {
if (utf16) {
efree(utf16);
@@ -512,7 +676,7 @@ PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, ze
ALLOC_INIT_ZVAL(z);
jp = new_JSON_parser(depth);
- if (parse_JSON(jp, z, utf16, utf16_len, assoc TSRMLS_CC)) {
+ if (parse_JSON_ex(jp, z, utf16, utf16_len, options TSRMLS_CC)) {
*return_value = *z;
}
else
@@ -555,6 +719,7 @@ PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, ze
}
/* }}} */
+
/* {{{ proto string json_encode(mixed data [, int options])
Returns the JSON representation of a value */
static PHP_FUNCTION(json_encode)
@@ -585,8 +750,9 @@ static PHP_FUNCTION(json_decode)
int str_len;
zend_bool assoc = 0; /* return JS objects as PHP objects by default */
long depth = JSON_PARSER_DEFAULT_DEPTH;
+ long options = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bl", &str, &str_len, &assoc, &depth) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bll", &str, &str_len, &assoc, &depth, &options) == FAILURE) {
return;
}
@@ -596,7 +762,14 @@ static PHP_FUNCTION(json_decode)
RETURN_NULL();
}
- php_json_decode(return_value, str, str_len, assoc, depth TSRMLS_CC);
+ /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */
+ if (assoc) {
+ options |= PHP_JSON_OBJECT_AS_ARRAY;
+ } else {
+ options &= ~PHP_JSON_OBJECT_AS_ARRAY;
+ }
+
+ php_json_decode_ex(return_value, str, str_len, options, depth TSRMLS_CC);
}
/* }}} */
diff --git a/ext/json/php_json.h b/ext/json/php_json.h
index b104d4ca6a..ef3e4b5a79 100644
--- a/ext/json/php_json.h
+++ b/ext/json/php_json.h
@@ -38,6 +38,7 @@ extern zend_module_entry json_module_entry;
#endif
ZEND_BEGIN_MODULE_GLOBALS(json)
+ int encoder_depth;
int error_code;
ZEND_END_MODULE_GLOBALS(json)
@@ -48,17 +49,34 @@ ZEND_END_MODULE_GLOBALS(json)
#endif
PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_DC);
-PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, zend_bool assoc, long depth TSRMLS_DC);
+PHP_JSON_API void php_json_decode_ex(zval *return_value, char *str, int str_len, int options, long depth TSRMLS_DC);
+extern zend_class_entry *php_json_serializable_ce;
+
+/* json_encode() options */
#define PHP_JSON_HEX_TAG (1<<0)
#define PHP_JSON_HEX_AMP (1<<1)
#define PHP_JSON_HEX_APOS (1<<2)
#define PHP_JSON_HEX_QUOT (1<<3)
#define PHP_JSON_FORCE_OBJECT (1<<4)
#define PHP_JSON_NUMERIC_CHECK (1<<5)
+#define PHP_JSON_UNESCAPED_SLASHES (1<<6)
+#define PHP_JSON_PRETTY_PRINT (1<<7)
+#define PHP_JSON_UNESCAPED_UNICODE (1<<8)
+
+/* Internal flags */
+#define PHP_JSON_OUTPUT_ARRAY 0
+#define PHP_JSON_OUTPUT_OBJECT 1
+
+/* json_decode() options */
+#define PHP_JSON_OBJECT_AS_ARRAY (1<<0)
+#define PHP_JSON_BIGINT_AS_STRING (1<<1)
+
+static inline void php_json_decode(zval *return_value, char *str, int str_len, zend_bool assoc, long depth TSRMLS_DC)
+{
+ php_json_decode_ex(return_value, str, str_len, assoc ? PHP_JSON_OBJECT_AS_ARRAY : 0, depth TSRMLS_CC);
+}
-#define PHP_JSON_OUTPUT_ARRAY 0
-#define PHP_JSON_OUTPUT_OBJECT 1
#endif /* PHP_JSON_H */
diff --git a/ext/json/tests/008.phpt b/ext/json/tests/008.phpt
new file mode 100644
index 0000000000..f2354d381f
--- /dev/null
+++ b/ext/json/tests/008.phpt
@@ -0,0 +1,17 @@
+--TEST--
+json_decode() with large integers
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+$json = '{"largenum":123456789012345678901234567890}';
+$x = json_decode($json);
+var_dump($x->largenum);
+$x = json_decode($json, false, 512, JSON_BIGINT_AS_STRING);
+var_dump($x->largenum);
+echo "Done\n";
+?>
+--EXPECT--
+float(1.2345678901235E+29)
+string(30) "123456789012345678901234567890"
+Done
diff --git a/ext/json/tests/bug53946.phpt b/ext/json/tests/bug53946.phpt
new file mode 100644
index 0000000000..abbb81238b
--- /dev/null
+++ b/ext/json/tests/bug53946.phpt
@@ -0,0 +1,16 @@
+--TEST--
+bug #53946 (json_encode() with JSON_UNESCAPED_UNICODE)
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+var_dump(json_encode("latin 1234 -/ russian мама мыла раму specialchars \x02 \x08 \n U+1D11E >𝄞<"));
+var_dump(json_encode("latin 1234 -/ russian мама мыла раму specialchars \x02 \x08 \n U+1D11E >𝄞<", JSON_UNESCAPED_UNICODE));
+var_dump(json_encode("ab\xE0"));
+var_dump(json_encode("ab\xE0", JSON_UNESCAPED_UNICODE));
+?>
+--EXPECT--
+string(156) ""latin 1234 -\/ russian \u043c\u0430\u043c\u0430 \u043c\u044b\u043b\u0430 \u0440\u0430\u043c\u0443 specialchars \u0002 \b \n U+1D11E >\ud834\udd1e<""
+string(100) ""latin 1234 -\/ russian мама мыла раму specialchars \u0002 \b \n U+1D11E >𝄞<""
+string(4) "null"
+string(4) "null"
diff --git a/ext/json/tests/bug61978.phpt b/ext/json/tests/bug61978.phpt
new file mode 100644
index 0000000000..2c732979ef
--- /dev/null
+++ b/ext/json/tests/bug61978.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Bug #61978 (Object recursion not detected for classes that implement JsonSerializable)
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+
+class JsonTest1 {
+ public $test;
+ public $me;
+ public function __construct() {
+ $this->test = '123';
+ $this->me = $this;
+ }
+}
+
+class JsonTest2 implements JsonSerializable {
+ public $test;
+ public function __construct() {
+ $this->test = '123';
+ }
+ public function jsonSerialize() {
+ return array(
+ 'test' => $this->test,
+ 'me' => $this
+ );
+ }
+}
+
+
+$obj1 = new JsonTest1();
+var_dump(json_encode($obj1));
+
+echo "\n==\n";
+
+$obj2 = new JsonTest2();
+var_dump(json_encode($obj2));
+
+?>
+--EXPECTF--
+Warning: json_encode(): recursion detected in %s on line %d
+string(44) "{"test":"123","me":{"test":"123","me":null}}"
+
+==
+
+Warning: json_encode(): recursion detected in %s on line %d
+string(44) "{"test":"123","me":{"test":"123","me":null}}"
diff --git a/ext/json/tests/json_decode_error.phpt b/ext/json/tests/json_decode_error.phpt
index f3387c21be..4d5d4e4bee 100644
--- a/ext/json/tests/json_decode_error.phpt
+++ b/ext/json/tests/json_decode_error.phpt
@@ -20,7 +20,7 @@ var_dump( json_decode() );
echo "\n-- Testing json_decode() function with more than expected no. of arguments --\n";
$extra_arg = 10;
-var_dump( json_decode('"abc"', TRUE, 512, $extra_arg) );
+var_dump( json_decode('"abc"', TRUE, 512, 0, $extra_arg) );
?>
===Done===
@@ -34,6 +34,6 @@ NULL
-- Testing json_decode() function with more than expected no. of arguments --
-Warning: json_decode() expects at most 3 parameters, 4 given in %s on line %d
+Warning: json_decode() expects at most 4 parameters, 5 given in %s on line %d
NULL
===Done===
diff --git a/ext/json/tests/json_encode_basic.phpt b/ext/json/tests/json_encode_basic.phpt
index 003fcd44c6..152e24444c 100644
--- a/ext/json/tests/json_encode_basic.phpt
+++ b/ext/json/tests/json_encode_basic.phpt
@@ -155,4 +155,4 @@ Warning: json_encode(): type is unsupported, encoded as null in %s on line %d
string(4) "null"
-- Iteration 27 --
string(82) "{"MyInt":99,"MyFloat":123.45,"MyBool":true,"MyNull":null,"MyString":"Hello World"}"
-===Done===
+===Done=== \ No newline at end of file
diff --git a/ext/json/tests/json_encode_numeric.phpt b/ext/json/tests/json_encode_numeric.phpt
new file mode 100644
index 0000000000..5392350194
--- /dev/null
+++ b/ext/json/tests/json_encode_numeric.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Test json_encode() function with numeric flag
+--SKIPIF--
+<?php
+if (!extension_loaded("json")) {
+ die('skip JSON extension not available in this build');
+}
+?>
+--FILE--
+<?php
+var_dump(
+ json_encode("1", JSON_NUMERIC_CHECK),
+ json_encode("9.4324", JSON_NUMERIC_CHECK),
+ json_encode(array("122321", "3232595.33423"), JSON_NUMERIC_CHECK),
+ json_encode("1"),
+ json_encode("9.4324"),
+ json_encode(array("122321", "3232595.33423"))
+);
+?>
+--EXPECT--
+string(1) "1"
+string(6) "9.4324"
+string(22) "[122321,3232595.33423]"
+string(3) ""1""
+string(8) ""9.4324""
+string(26) "["122321","3232595.33423"]"
diff --git a/ext/json/tests/json_encode_pretty_print.phpt b/ext/json/tests/json_encode_pretty_print.phpt
new file mode 100644
index 0000000000..43b93aafe6
--- /dev/null
+++ b/ext/json/tests/json_encode_pretty_print.phpt
@@ -0,0 +1,40 @@
+--TEST--
+json_encode() with JSON_PRETTY_PRINT
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+function encode_decode($json) {
+ $struct = json_decode($json);
+ $pretty = json_encode($struct, JSON_PRETTY_PRINT);
+ echo "$pretty\n";
+ $pretty = json_decode($pretty);
+ printf("Match: %d\n", $pretty == $struct);
+}
+
+encode_decode('[1,2,3,[1,2,3]]');
+encode_decode('{"a":1,"b":[1,2],"c":{"d":42}}');
+?>
+--EXPECT--
+[
+ 1,
+ 2,
+ 3,
+ [
+ 1,
+ 2,
+ 3
+ ]
+]
+Match: 1
+{
+ "a": 1,
+ "b": [
+ 1,
+ 2
+ ],
+ "c": {
+ "d": 42
+ }
+}
+Match: 1
diff --git a/ext/json/tests/json_encode_unescaped_slashes.phpt b/ext/json/tests/json_encode_unescaped_slashes.phpt
new file mode 100644
index 0000000000..72ebae927a
--- /dev/null
+++ b/ext/json/tests/json_encode_unescaped_slashes.phpt
@@ -0,0 +1,12 @@
+--TEST--
+json_decode() tests
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+var_dump(json_encode('a/b'));
+var_dump(json_encode('a/b', JSON_UNESCAPED_SLASHES));
+?>
+--EXPECT--
+string(6) ""a\/b""
+string(5) ""a/b""
diff --git a/ext/json/tests/serialize.phpt b/ext/json/tests/serialize.phpt
new file mode 100644
index 0000000000..5c513d58ad
--- /dev/null
+++ b/ext/json/tests/serialize.phpt
@@ -0,0 +1,80 @@
+--TEST--
+json_encode() Serialization tests
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+
+class NonSerializingTest
+{
+ public $data;
+
+ public function __construct($data)
+ {
+ $this->data = $data;
+ }
+}
+
+class SerializingTest extends NonSerializingTest implements JsonSerializable
+{
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+}
+
+class ValueSerializingTest extends SerializingTest
+{
+ public function jsonSerialize()
+ {
+ return array_values(is_array($this->data) ? $this->data : get_object_vars($this->data));
+ }
+}
+
+class SelfSerializingTest extends SerializingTest
+{
+ public function jsonSerialize()
+ {
+ return $this;
+ }
+}
+
+$adata = array(
+ 'str' => 'foo',
+ 'int' => 1,
+ 'float' => 2.3,
+ 'bool' => false,
+ 'nil' => null,
+ 'arr' => array(1,2,3),
+ 'obj' => new StdClass,
+);
+
+$ndata = array_values($adata);
+
+$odata = (object)$adata;
+
+foreach(array('NonSerializingTest','SerializingTest','ValueSerializingTest','SelfSerializingTest') as $class) {
+ echo "==$class==\n";
+ echo json_encode(new $class($adata)), "\n";
+ echo json_encode(new $class($ndata)), "\n";
+ echo json_encode(new $class($odata)), "\n";
+}
+--EXPECT--
+==NonSerializingTest==
+{"data":{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}}
+{"data":["foo",1,2.3,false,null,[1,2,3],{}]}
+{"data":{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}}
+==SerializingTest==
+{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}
+["foo",1,2.3,false,null,[1,2,3],{}]
+{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}
+==ValueSerializingTest==
+["foo",1,2.3,false,null,[1,2,3],{}]
+["foo",1,2.3,false,null,[1,2,3],{}]
+["foo",1,2.3,false,null,[1,2,3],{}]
+==SelfSerializingTest==
+{"data":{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}}
+{"data":["foo",1,2.3,false,null,[1,2,3],{}]}
+{"data":{"str":"foo","int":1,"float":2.3,"bool":false,"nil":null,"arr":[1,2,3],"obj":{}}}
+
+
diff --git a/ext/json/utf8_to_utf16.c b/ext/json/utf8_to_utf16.c
deleted file mode 100644
index 599f0e13b4..0000000000
--- a/ext/json/utf8_to_utf16.c
+++ /dev/null
@@ -1,56 +0,0 @@
-/* utf8_to_utf16.c */
-
-/* 2005-12-25 */
-
-/*
-Copyright (c) 2005 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-#include "utf8_to_utf16.h"
-#include "utf8_decode.h"
-
-int
-utf8_to_utf16(unsigned short w[], char p[], int length)
-{
- int c;
- int the_index = 0;
- json_utf8_decode utf8;
-
- utf8_decode_init(&utf8, p, length);
- for (;;) {
- c = utf8_decode_next(&utf8);
- if (c < 0) {
- return (c == UTF8_END) ? the_index : UTF8_ERROR;
- }
- if (c < 0x10000) {
- w[the_index] = (unsigned short)c;
- the_index += 1;
- } else {
- c -= 0x10000;
- w[the_index] = (unsigned short)(0xD800 | (c >> 10));
- the_index += 1;
- w[the_index] = (unsigned short)(0xDC00 | (c & 0x3FF));
- the_index += 1;
- }
- }
-}
diff --git a/ext/json/utf8_to_utf16.h b/ext/json/utf8_to_utf16.h
deleted file mode 100644
index 5aff0268bf..0000000000
--- a/ext/json/utf8_to_utf16.h
+++ /dev/null
@@ -1,3 +0,0 @@
-/* utf8_to_utf16.h */
-
-extern int utf8_to_utf16(unsigned short w[], char p[], int length);