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