1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: Omar Kilani <omar@php.net> |
16 | Jakub Zelenka <bukka@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "php.h"
25 #include "php_ini.h"
26 #include "ext/standard/info.h"
27 #include "ext/standard/html.h"
28 #include "zend_smart_str.h"
29 #include "php_json.h"
30 #include "php_json_encoder.h"
31 #include <zend_exceptions.h>
32
33 static const char digits[] = "0123456789abcdef";
34
35 static int php_json_escape_string(
36 smart_str *buf, const char *s, size_t len,
37 int options, php_json_encoder *encoder);
38
php_json_determine_array_type(zval * val)39 static int php_json_determine_array_type(zval *val) /* {{{ */
40 {
41 int i;
42 HashTable *myht = Z_ARRVAL_P(val);
43
44 i = myht ? zend_hash_num_elements(myht) : 0;
45 if (i > 0) {
46 zend_string *key;
47 zend_ulong index, idx;
48
49 if (HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
50 return PHP_JSON_OUTPUT_ARRAY;
51 }
52
53 idx = 0;
54 ZEND_HASH_FOREACH_KEY(myht, index, key) {
55 if (key) {
56 return PHP_JSON_OUTPUT_OBJECT;
57 } else {
58 if (index != idx) {
59 return PHP_JSON_OUTPUT_OBJECT;
60 }
61 }
62 idx++;
63 } ZEND_HASH_FOREACH_END();
64 }
65
66 return PHP_JSON_OUTPUT_ARRAY;
67 }
68 /* }}} */
69
70 /* {{{ Pretty printing support functions */
71
php_json_pretty_print_char(smart_str * buf,int options,char c)72 static inline void php_json_pretty_print_char(smart_str *buf, int options, char c) /* {{{ */
73 {
74 if (options & PHP_JSON_PRETTY_PRINT) {
75 smart_str_appendc(buf, c);
76 }
77 }
78 /* }}} */
79
php_json_pretty_print_indent(smart_str * buf,int options,php_json_encoder * encoder)80 static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
81 {
82 int i;
83
84 if (options & PHP_JSON_PRETTY_PRINT) {
85 for (i = 0; i < encoder->depth; ++i) {
86 smart_str_appendl(buf, " ", 4);
87 }
88 }
89 }
90 /* }}} */
91
92 /* }}} */
93
php_json_is_valid_double(double d)94 static inline int php_json_is_valid_double(double d) /* {{{ */
95 {
96 return !zend_isinf(d) && !zend_isnan(d);
97 }
98 /* }}} */
99
php_json_encode_double(smart_str * buf,double d,int options)100 static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */
101 {
102 size_t len;
103 char num[PHP_DOUBLE_MAX_LENGTH];
104
105 php_gcvt(d, (int)PG(serialize_precision), '.', 'e', num);
106 len = strlen(num);
107 if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) {
108 num[len++] = '.';
109 num[len++] = '0';
110 num[len] = '\0';
111 }
112 smart_str_appendl(buf, num, len);
113 }
114 /* }}} */
115
116 #define PHP_JSON_HASH_PROTECT_RECURSION(_tmp_ht) \
117 do { \
118 if (_tmp_ht && !(GC_FLAGS(_tmp_ht) & GC_IMMUTABLE)) { \
119 GC_PROTECT_RECURSION(_tmp_ht); \
120 } \
121 } while (0)
122
123 #define PHP_JSON_HASH_UNPROTECT_RECURSION(_tmp_ht) \
124 do { \
125 if (_tmp_ht && !(GC_FLAGS(_tmp_ht) & GC_IMMUTABLE)) { \
126 GC_UNPROTECT_RECURSION(_tmp_ht); \
127 } \
128 } while (0)
129
php_json_encode_array(smart_str * buf,zval * val,int options,php_json_encoder * encoder)130 static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
131 {
132 int i, r, need_comma = 0;
133 HashTable *myht, *prop_ht;
134
135 if (Z_TYPE_P(val) == IS_ARRAY) {
136 myht = Z_ARRVAL_P(val);
137 prop_ht = NULL;
138 r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
139 } else {
140 prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON);
141 r = PHP_JSON_OUTPUT_OBJECT;
142 }
143
144 if (myht && GC_IS_RECURSIVE(myht)) {
145 encoder->error_code = PHP_JSON_ERROR_RECURSION;
146 smart_str_appendl(buf, "null", 4);
147 zend_release_properties(prop_ht);
148 return FAILURE;
149 }
150
151 PHP_JSON_HASH_PROTECT_RECURSION(myht);
152
153 if (r == PHP_JSON_OUTPUT_ARRAY) {
154 smart_str_appendc(buf, '[');
155 } else {
156 smart_str_appendc(buf, '{');
157 }
158
159 ++encoder->depth;
160
161 i = myht ? zend_hash_num_elements(myht) : 0;
162
163 if (i > 0) {
164 zend_string *key;
165 zval *data;
166 zend_ulong index;
167
168 ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
169 if (r == PHP_JSON_OUTPUT_ARRAY) {
170 if (need_comma) {
171 smart_str_appendc(buf, ',');
172 } else {
173 need_comma = 1;
174 }
175
176 php_json_pretty_print_char(buf, options, '\n');
177 php_json_pretty_print_indent(buf, options, encoder);
178 } else if (r == PHP_JSON_OUTPUT_OBJECT) {
179 if (key) {
180 if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
181 /* Skip protected and private members. */
182 continue;
183 }
184
185 if (need_comma) {
186 smart_str_appendc(buf, ',');
187 } else {
188 need_comma = 1;
189 }
190
191 php_json_pretty_print_char(buf, options, '\n');
192 php_json_pretty_print_indent(buf, options, encoder);
193
194 if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
195 options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
196 (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
197 buf->s) {
198 ZSTR_LEN(buf->s) -= 4;
199 smart_str_appendl(buf, "\"\"", 2);
200 }
201 } else {
202 if (need_comma) {
203 smart_str_appendc(buf, ',');
204 } else {
205 need_comma = 1;
206 }
207
208 php_json_pretty_print_char(buf, options, '\n');
209 php_json_pretty_print_indent(buf, options, encoder);
210
211 smart_str_appendc(buf, '"');
212 smart_str_append_long(buf, (zend_long) index);
213 smart_str_appendc(buf, '"');
214 }
215
216 smart_str_appendc(buf, ':');
217 php_json_pretty_print_char(buf, options, ' ');
218 }
219
220 if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
221 !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
222 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
223 zend_release_properties(prop_ht);
224 return FAILURE;
225 }
226 } ZEND_HASH_FOREACH_END();
227 }
228
229 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
230
231 if (encoder->depth > encoder->max_depth) {
232 encoder->error_code = PHP_JSON_ERROR_DEPTH;
233 if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
234 zend_release_properties(prop_ht);
235 return FAILURE;
236 }
237 }
238 --encoder->depth;
239
240 /* Only keep closing bracket on same line for empty arrays/objects */
241 if (need_comma) {
242 php_json_pretty_print_char(buf, options, '\n');
243 php_json_pretty_print_indent(buf, options, encoder);
244 }
245
246 if (r == PHP_JSON_OUTPUT_ARRAY) {
247 smart_str_appendc(buf, ']');
248 } else {
249 smart_str_appendc(buf, '}');
250 }
251
252 zend_release_properties(prop_ht);
253 return SUCCESS;
254 }
255 /* }}} */
256
php_json_escape_string(smart_str * buf,const char * s,size_t len,int options,php_json_encoder * encoder)257 static int php_json_escape_string(
258 smart_str *buf, const char *s, size_t len,
259 int options, php_json_encoder *encoder) /* {{{ */
260 {
261 int status;
262 unsigned int us;
263 size_t pos, checkpoint;
264 char *dst;
265
266 if (len == 0) {
267 smart_str_appendl(buf, "\"\"", 2);
268 return SUCCESS;
269 }
270
271 if (options & PHP_JSON_NUMERIC_CHECK) {
272 double d;
273 int type;
274 zend_long p;
275
276 if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
277 if (type == IS_LONG) {
278 smart_str_append_long(buf, p);
279 return SUCCESS;
280 } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
281 php_json_encode_double(buf, d, options);
282 return SUCCESS;
283 }
284 }
285
286 }
287 checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
288
289 /* pre-allocate for string length plus 2 quotes */
290 smart_str_alloc(buf, len+2, 0);
291 smart_str_appendc(buf, '"');
292
293 pos = 0;
294
295 do {
296 static const uint32_t charmap[8] = {
297 0xffffffff, 0x500080c4, 0x10000000, 0x00000000,
298 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
299
300 us = (unsigned char)s[pos];
301 if (EXPECTED(!ZEND_BIT_TEST(charmap, us))) {
302 pos++;
303 len--;
304 if (len == 0) {
305 smart_str_appendl(buf, s, pos);
306 break;
307 }
308 } else {
309 if (pos) {
310 smart_str_appendl(buf, s, pos);
311 s += pos;
312 pos = 0;
313 }
314 us = (unsigned char)s[0];
315 if (UNEXPECTED(us >= 0x80)) {
316
317 us = php_next_utf8_char((unsigned char *)s, len, &pos, &status);
318
319 /* check whether UTF8 character is correct */
320 if (UNEXPECTED(status != SUCCESS)) {
321 if (options & PHP_JSON_INVALID_UTF8_IGNORE) {
322 /* ignore invalid UTF8 character */
323 } else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
324 /* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */
325 if (options & PHP_JSON_UNESCAPED_UNICODE) {
326 smart_str_appendl(buf, "\xef\xbf\xbd", 3);
327 } else {
328 smart_str_appendl(buf, "\\ufffd", 6);
329 }
330 } else {
331 ZSTR_LEN(buf->s) = checkpoint;
332 encoder->error_code = PHP_JSON_ERROR_UTF8;
333 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
334 smart_str_appendl(buf, "null", 4);
335 }
336 return FAILURE;
337 }
338
339 /* Escape U+2028/U+2029 line terminators, UNLESS both
340 JSON_UNESCAPED_UNICODE and
341 JSON_UNESCAPED_LINE_TERMINATORS were provided */
342 } else if ((options & PHP_JSON_UNESCAPED_UNICODE)
343 && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS)
344 || us < 0x2028 || us > 0x2029)) {
345 smart_str_appendl(buf, s, pos);
346 } else {
347 /* From http://en.wikipedia.org/wiki/UTF16 */
348 if (us >= 0x10000) {
349 unsigned int next_us;
350
351 us -= 0x10000;
352 next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
353 us = (unsigned short)((us >> 10) | 0xd800);
354 dst = smart_str_extend(buf, 6);
355 dst[0] = '\\';
356 dst[1] = 'u';
357 dst[2] = digits[(us >> 12) & 0xf];
358 dst[3] = digits[(us >> 8) & 0xf];
359 dst[4] = digits[(us >> 4) & 0xf];
360 dst[5] = digits[us & 0xf];
361 us = next_us;
362 }
363 dst = smart_str_extend(buf, 6);
364 dst[0] = '\\';
365 dst[1] = 'u';
366 dst[2] = digits[(us >> 12) & 0xf];
367 dst[3] = digits[(us >> 8) & 0xf];
368 dst[4] = digits[(us >> 4) & 0xf];
369 dst[5] = digits[us & 0xf];
370 }
371 s += pos;
372 len -= pos;
373 pos = 0;
374 } else {
375 s++;
376 switch (us) {
377 case '"':
378 if (options & PHP_JSON_HEX_QUOT) {
379 smart_str_appendl(buf, "\\u0022", 6);
380 } else {
381 smart_str_appendl(buf, "\\\"", 2);
382 }
383 break;
384
385 case '\\':
386 smart_str_appendl(buf, "\\\\", 2);
387 break;
388
389 case '/':
390 if (options & PHP_JSON_UNESCAPED_SLASHES) {
391 smart_str_appendc(buf, '/');
392 } else {
393 smart_str_appendl(buf, "\\/", 2);
394 }
395 break;
396
397 case '\b':
398 smart_str_appendl(buf, "\\b", 2);
399 break;
400
401 case '\f':
402 smart_str_appendl(buf, "\\f", 2);
403 break;
404
405 case '\n':
406 smart_str_appendl(buf, "\\n", 2);
407 break;
408
409 case '\r':
410 smart_str_appendl(buf, "\\r", 2);
411 break;
412
413 case '\t':
414 smart_str_appendl(buf, "\\t", 2);
415 break;
416
417 case '<':
418 if (options & PHP_JSON_HEX_TAG) {
419 smart_str_appendl(buf, "\\u003C", 6);
420 } else {
421 smart_str_appendc(buf, '<');
422 }
423 break;
424
425 case '>':
426 if (options & PHP_JSON_HEX_TAG) {
427 smart_str_appendl(buf, "\\u003E", 6);
428 } else {
429 smart_str_appendc(buf, '>');
430 }
431 break;
432
433 case '&':
434 if (options & PHP_JSON_HEX_AMP) {
435 smart_str_appendl(buf, "\\u0026", 6);
436 } else {
437 smart_str_appendc(buf, '&');
438 }
439 break;
440
441 case '\'':
442 if (options & PHP_JSON_HEX_APOS) {
443 smart_str_appendl(buf, "\\u0027", 6);
444 } else {
445 smart_str_appendc(buf, '\'');
446 }
447 break;
448
449 default:
450 ZEND_ASSERT(us < ' ');
451 dst = smart_str_extend(buf, 6);
452 dst[0] = '\\';
453 dst[1] = 'u';
454 dst[2] = '0';
455 dst[3] = '0';
456 dst[4] = digits[(us >> 4) & 0xf];
457 dst[5] = digits[us & 0xf];
458 break;
459 }
460 len--;
461 }
462 }
463 } while (len);
464
465 smart_str_appendc(buf, '"');
466
467 return SUCCESS;
468 }
469 /* }}} */
470
php_json_encode_serializable_object(smart_str * buf,zval * val,int options,php_json_encoder * encoder)471 static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
472 {
473 zend_class_entry *ce = Z_OBJCE_P(val);
474 HashTable* myht = Z_OBJPROP_P(val);
475 zval retval, fname;
476 int return_code;
477
478 if (myht && GC_IS_RECURSIVE(myht)) {
479 encoder->error_code = PHP_JSON_ERROR_RECURSION;
480 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
481 smart_str_appendl(buf, "null", 4);
482 }
483 return FAILURE;
484 }
485
486 PHP_JSON_HASH_PROTECT_RECURSION(myht);
487
488 ZVAL_STRING(&fname, "jsonSerialize");
489
490 if (FAILURE == call_user_function(NULL, val, &fname, &retval, 0, NULL) || Z_TYPE(retval) == IS_UNDEF) {
491 if (!EG(exception)) {
492 zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
493 }
494 zval_ptr_dtor(&fname);
495
496 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
497 smart_str_appendl(buf, "null", 4);
498 }
499 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
500 return FAILURE;
501 }
502
503 if (EG(exception)) {
504 /* Error already raised */
505 zval_ptr_dtor(&retval);
506 zval_ptr_dtor(&fname);
507
508 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
509 smart_str_appendl(buf, "null", 4);
510 }
511 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
512 return FAILURE;
513 }
514
515 if ((Z_TYPE(retval) == IS_OBJECT) &&
516 (Z_OBJ(retval) == Z_OBJ_P(val))) {
517 /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
518 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
519 return_code = php_json_encode_array(buf, &retval, options, encoder);
520 } else {
521 /* All other types, encode as normal */
522 return_code = php_json_encode_zval(buf, &retval, options, encoder);
523 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
524 }
525
526 zval_ptr_dtor(&retval);
527 zval_ptr_dtor(&fname);
528
529 return return_code;
530 }
531 /* }}} */
532
php_json_encode_zval(smart_str * buf,zval * val,int options,php_json_encoder * encoder)533 int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
534 {
535 again:
536 switch (Z_TYPE_P(val))
537 {
538 case IS_NULL:
539 smart_str_appendl(buf, "null", 4);
540 break;
541
542 case IS_TRUE:
543 smart_str_appendl(buf, "true", 4);
544 break;
545 case IS_FALSE:
546 smart_str_appendl(buf, "false", 5);
547 break;
548
549 case IS_LONG:
550 smart_str_append_long(buf, Z_LVAL_P(val));
551 break;
552
553 case IS_DOUBLE:
554 if (php_json_is_valid_double(Z_DVAL_P(val))) {
555 php_json_encode_double(buf, Z_DVAL_P(val), options);
556 } else {
557 encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
558 smart_str_appendc(buf, '0');
559 }
560 break;
561
562 case IS_STRING:
563 return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
564
565 case IS_OBJECT:
566 if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
567 return php_json_encode_serializable_object(buf, val, options, encoder);
568 }
569 /* fallthrough -- Non-serializable object */
570 case IS_ARRAY: {
571 /* Avoid modifications (and potential freeing) of the array through a reference when a
572 * jsonSerialize() method is invoked. */
573 zval zv;
574 int res;
575 ZVAL_COPY(&zv, val);
576 res = php_json_encode_array(buf, &zv, options, encoder);
577 zval_ptr_dtor_nogc(&zv);
578 return res;
579 }
580
581 case IS_REFERENCE:
582 val = Z_REFVAL_P(val);
583 goto again;
584
585 default:
586 encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
587 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
588 smart_str_appendl(buf, "null", 4);
589 }
590 return FAILURE;
591 }
592
593 return SUCCESS;
594 }
595 /* }}} */
596