1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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, 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_APPLY_PROTECTION_INC(_tmp_ht) \
117 do { \
118 if (_tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \
119 ZEND_HASH_INC_APPLY_COUNT(_tmp_ht); \
120 } \
121 } while (0)
122
123 #define PHP_JSON_HASH_APPLY_PROTECTION_DEC(_tmp_ht) \
124 do { \
125 if (_tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \
126 ZEND_HASH_DEC_APPLY_COUNT(_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;
134
135 if (Z_TYPE_P(val) == IS_ARRAY) {
136 myht = Z_ARRVAL_P(val);
137 r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
138 } else {
139 myht = Z_OBJPROP_P(val);
140 r = PHP_JSON_OUTPUT_OBJECT;
141 }
142
143 if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) {
144 encoder->error_code = PHP_JSON_ERROR_RECURSION;
145 smart_str_appendl(buf, "null", 4);
146 return FAILURE;
147 }
148
149 PHP_JSON_HASH_APPLY_PROTECTION_INC(myht);
150
151 if (r == PHP_JSON_OUTPUT_ARRAY) {
152 smart_str_appendc(buf, '[');
153 } else {
154 smart_str_appendc(buf, '{');
155 }
156
157 ++encoder->depth;
158
159 i = myht ? zend_hash_num_elements(myht) : 0;
160
161 if (i > 0) {
162 zend_string *key;
163 zval *data;
164 zend_ulong index;
165
166 ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
167 if (r == PHP_JSON_OUTPUT_ARRAY) {
168 if (need_comma) {
169 smart_str_appendc(buf, ',');
170 } else {
171 need_comma = 1;
172 }
173
174 php_json_pretty_print_char(buf, options, '\n');
175 php_json_pretty_print_indent(buf, options, encoder);
176 } else if (r == PHP_JSON_OUTPUT_OBJECT) {
177 if (key) {
178 if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
179 /* Skip protected and private members. */
180 continue;
181 }
182
183 if (need_comma) {
184 smart_str_appendc(buf, ',');
185 } else {
186 need_comma = 1;
187 }
188
189 php_json_pretty_print_char(buf, options, '\n');
190 php_json_pretty_print_indent(buf, options, encoder);
191
192 if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
193 options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
194 (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
195 buf->s) {
196 ZSTR_LEN(buf->s) -= 4;
197 smart_str_appendl(buf, "\"\"", 2);
198 }
199 } else {
200 if (need_comma) {
201 smart_str_appendc(buf, ',');
202 } else {
203 need_comma = 1;
204 }
205
206 php_json_pretty_print_char(buf, options, '\n');
207 php_json_pretty_print_indent(buf, options, encoder);
208
209 smart_str_appendc(buf, '"');
210 smart_str_append_long(buf, (zend_long) index);
211 smart_str_appendc(buf, '"');
212 }
213
214 smart_str_appendc(buf, ':');
215 php_json_pretty_print_char(buf, options, ' ');
216 }
217
218 if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
219 !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
220 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
221 return FAILURE;
222 }
223 } ZEND_HASH_FOREACH_END();
224 }
225
226 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
227
228 if (encoder->depth > encoder->max_depth) {
229 encoder->error_code = PHP_JSON_ERROR_DEPTH;
230 if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
231 return FAILURE;
232 }
233 }
234 --encoder->depth;
235
236 /* Only keep closing bracket on same line for empty arrays/objects */
237 if (need_comma) {
238 php_json_pretty_print_char(buf, options, '\n');
239 php_json_pretty_print_indent(buf, options, encoder);
240 }
241
242 if (r == PHP_JSON_OUTPUT_ARRAY) {
243 smart_str_appendc(buf, ']');
244 } else {
245 smart_str_appendc(buf, '}');
246 }
247
248 return SUCCESS;
249 }
250 /* }}} */
251
php_json_escape_string(smart_str * buf,char * s,size_t len,int options,php_json_encoder * encoder)252 static int php_json_escape_string(
253 smart_str *buf, char *s, size_t len,
254 int options, php_json_encoder *encoder) /* {{{ */
255 {
256 int status;
257 unsigned int us;
258 size_t pos, checkpoint;
259
260 if (len == 0) {
261 smart_str_appendl(buf, "\"\"", 2);
262 return SUCCESS;
263 }
264
265 if (options & PHP_JSON_NUMERIC_CHECK) {
266 double d;
267 int type;
268 zend_long p;
269
270 if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
271 if (type == IS_LONG) {
272 smart_str_append_long(buf, p);
273 return SUCCESS;
274 } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
275 php_json_encode_double(buf, d, options);
276 return SUCCESS;
277 }
278 }
279
280 }
281 pos = 0;
282 checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
283
284 /* pre-allocate for string length plus 2 quotes */
285 smart_str_alloc(buf, len+2, 0);
286 smart_str_appendc(buf, '"');
287
288 do {
289 us = (unsigned char)s[pos];
290 if (us >= 0x80) {
291 int utf8_sub = 0;
292 size_t prev_pos = pos;
293
294 us = php_next_utf8_char((unsigned char *)s, len, &pos, &status);
295
296 /* check whether UTF8 character is correct */
297 if (status != SUCCESS) {
298 if (options & PHP_JSON_INVALID_UTF8_IGNORE) {
299 /* ignore invalid UTF8 character */
300 continue;
301 } else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
302 /* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */
303 us = 0xfffd;
304 utf8_sub = 1;
305 } else {
306 if (buf->s) {
307 ZSTR_LEN(buf->s) = checkpoint;
308 }
309 encoder->error_code = PHP_JSON_ERROR_UTF8;
310 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
311 smart_str_appendl(buf, "null", 4);
312 }
313 return FAILURE;
314 }
315 }
316
317 /* Escape U+2028/U+2029 line terminators, UNLESS both
318 JSON_UNESCAPED_UNICODE and
319 JSON_UNESCAPED_LINE_TERMINATORS were provided */
320 if ((options & PHP_JSON_UNESCAPED_UNICODE)
321 && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS)
322 || us < 0x2028 || us > 0x2029)) {
323 if (utf8_sub) {
324 smart_str_appendl(buf, "\xef\xbf\xbd", 3);
325 } else {
326 smart_str_appendl(buf, s + prev_pos, pos - prev_pos);
327 }
328 continue;
329 }
330 /* From http://en.wikipedia.org/wiki/UTF16 */
331 if (us >= 0x10000) {
332 unsigned int next_us;
333 us -= 0x10000;
334 next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
335 us = (unsigned short)((us >> 10) | 0xd800);
336 smart_str_appendl(buf, "\\u", 2);
337 smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
338 smart_str_appendc(buf, digits[(us & 0xf00) >> 8]);
339 smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
340 smart_str_appendc(buf, digits[(us & 0xf)]);
341 us = next_us;
342 }
343 smart_str_appendl(buf, "\\u", 2);
344 smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
345 smart_str_appendc(buf, digits[(us & 0xf00) >> 8]);
346 smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
347 smart_str_appendc(buf, digits[(us & 0xf)]);
348 } else {
349 static const uint32_t charmap[4] = {
350 0xffffffff, 0x500080c4, 0x10000000, 0x00000000};
351
352 pos++;
353 if (EXPECTED(!(charmap[us >> 5] & (1 << (us & 0x1f))))) {
354 smart_str_appendc(buf, (unsigned char) us);
355 } else {
356 switch (us) {
357 case '"':
358 if (options & PHP_JSON_HEX_QUOT) {
359 smart_str_appendl(buf, "\\u0022", 6);
360 } else {
361 smart_str_appendl(buf, "\\\"", 2);
362 }
363 break;
364
365 case '\\':
366 smart_str_appendl(buf, "\\\\", 2);
367 break;
368
369 case '/':
370 if (options & PHP_JSON_UNESCAPED_SLASHES) {
371 smart_str_appendc(buf, '/');
372 } else {
373 smart_str_appendl(buf, "\\/", 2);
374 }
375 break;
376
377 case '\b':
378 smart_str_appendl(buf, "\\b", 2);
379 break;
380
381 case '\f':
382 smart_str_appendl(buf, "\\f", 2);
383 break;
384
385 case '\n':
386 smart_str_appendl(buf, "\\n", 2);
387 break;
388
389 case '\r':
390 smart_str_appendl(buf, "\\r", 2);
391 break;
392
393 case '\t':
394 smart_str_appendl(buf, "\\t", 2);
395 break;
396
397 case '<':
398 if (options & PHP_JSON_HEX_TAG) {
399 smart_str_appendl(buf, "\\u003C", 6);
400 } else {
401 smart_str_appendc(buf, '<');
402 }
403 break;
404
405 case '>':
406 if (options & PHP_JSON_HEX_TAG) {
407 smart_str_appendl(buf, "\\u003E", 6);
408 } else {
409 smart_str_appendc(buf, '>');
410 }
411 break;
412
413 case '&':
414 if (options & PHP_JSON_HEX_AMP) {
415 smart_str_appendl(buf, "\\u0026", 6);
416 } else {
417 smart_str_appendc(buf, '&');
418 }
419 break;
420
421 case '\'':
422 if (options & PHP_JSON_HEX_APOS) {
423 smart_str_appendl(buf, "\\u0027", 6);
424 } else {
425 smart_str_appendc(buf, '\'');
426 }
427 break;
428
429 default:
430 ZEND_ASSERT(us < ' ');
431 smart_str_appendl(buf, "\\u00", sizeof("\\u00")-1);
432 smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
433 smart_str_appendc(buf, digits[(us & 0xf)]);
434 break;
435 }
436 }
437 }
438 } while (pos < len);
439
440 smart_str_appendc(buf, '"');
441
442 return SUCCESS;
443 }
444 /* }}} */
445
php_json_encode_serializable_object(smart_str * buf,zval * val,int options,php_json_encoder * encoder)446 static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
447 {
448 zend_class_entry *ce = Z_OBJCE_P(val);
449 HashTable* myht = Z_OBJPROP_P(val);
450 zval retval, fname;
451 int return_code;
452
453 if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) {
454 encoder->error_code = PHP_JSON_ERROR_RECURSION;
455 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
456 smart_str_appendl(buf, "null", 4);
457 }
458 return FAILURE;
459 }
460
461 PHP_JSON_HASH_APPLY_PROTECTION_INC(myht);
462
463 ZVAL_STRING(&fname, "jsonSerialize");
464
465 if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
466 if (!EG(exception)) {
467 zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
468 }
469 zval_ptr_dtor(&fname);
470
471 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
472 smart_str_appendl(buf, "null", 4);
473 }
474 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
475 return FAILURE;
476 }
477
478 if (EG(exception)) {
479 /* Error already raised */
480 zval_ptr_dtor(&retval);
481 zval_ptr_dtor(&fname);
482
483 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
484 smart_str_appendl(buf, "null", 4);
485 }
486 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
487 return FAILURE;
488 }
489
490 if ((Z_TYPE(retval) == IS_OBJECT) &&
491 (Z_OBJ(retval) == Z_OBJ_P(val))) {
492 /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
493 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
494 return_code = php_json_encode_array(buf, &retval, options, encoder);
495 } else {
496 /* All other types, encode as normal */
497 return_code = php_json_encode_zval(buf, &retval, options, encoder);
498 PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
499 }
500
501 zval_ptr_dtor(&retval);
502 zval_ptr_dtor(&fname);
503
504 return return_code;
505 }
506 /* }}} */
507
php_json_encode_zval(smart_str * buf,zval * val,int options,php_json_encoder * encoder)508 int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
509 {
510 again:
511 switch (Z_TYPE_P(val))
512 {
513 case IS_NULL:
514 smart_str_appendl(buf, "null", 4);
515 break;
516
517 case IS_TRUE:
518 smart_str_appendl(buf, "true", 4);
519 break;
520 case IS_FALSE:
521 smart_str_appendl(buf, "false", 5);
522 break;
523
524 case IS_LONG:
525 smart_str_append_long(buf, Z_LVAL_P(val));
526 break;
527
528 case IS_DOUBLE:
529 if (php_json_is_valid_double(Z_DVAL_P(val))) {
530 php_json_encode_double(buf, Z_DVAL_P(val), options);
531 } else {
532 encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
533 smart_str_appendc(buf, '0');
534 }
535 break;
536
537 case IS_STRING:
538 return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
539
540 case IS_OBJECT:
541 if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
542 return php_json_encode_serializable_object(buf, val, options, encoder);
543 }
544 /* fallthrough -- Non-serializable object */
545 case IS_ARRAY: {
546 /* Avoid modifications (and potential freeing) of the array through a reference when a
547 * jsonSerialize() method is invoked. */
548 zval zv;
549 int res;
550 ZVAL_COPY(&zv, val);
551 res = php_json_encode_array(buf, &zv, options, encoder);
552 zval_ptr_dtor_nogc(&zv);
553 return res;
554 }
555
556 case IS_REFERENCE:
557 val = Z_REFVAL_P(val);
558 goto again;
559
560 default:
561 encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
562 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
563 smart_str_appendl(buf, "null", 4);
564 }
565 return FAILURE;
566 }
567
568 return SUCCESS;
569 }
570 /* }}} */
571
572 /*
573 * Local variables:
574 * tab-width: 4
575 * c-basic-offset: 4
576 * End:
577 * vim600: noet sw=4 ts=4 fdm=marker
578 * vim<600: noet sw=4 ts=4
579 */
580