Line data Source code
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 :
39 1261 : static int php_json_determine_array_type(zval *val) /* {{{ */
40 : {
41 : int i;
42 1261 : HashTable *myht = Z_ARRVAL_P(val);
43 :
44 1261 : i = myht ? zend_hash_num_elements(myht) : 0;
45 1261 : if (i > 0) {
46 : zend_string *key;
47 : zend_ulong index, idx;
48 :
49 1241 : if (HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
50 1216 : return PHP_JSON_OUTPUT_ARRAY;
51 : }
52 :
53 25 : idx = 0;
54 54 : ZEND_HASH_FOREACH_KEY(myht, index, key) {
55 26 : if (key) {
56 22 : return PHP_JSON_OUTPUT_OBJECT;
57 : } else {
58 4 : if (index != idx) {
59 3 : return PHP_JSON_OUTPUT_OBJECT;
60 : }
61 : }
62 1 : idx++;
63 : } ZEND_HASH_FOREACH_END();
64 : }
65 :
66 20 : return PHP_JSON_OUTPUT_ARRAY;
67 : }
68 : /* }}} */
69 :
70 : /* {{{ Pretty printing support functions */
71 :
72 2707 : static inline void php_json_pretty_print_char(smart_str *buf, int options, char c) /* {{{ */
73 : {
74 2707 : if (options & PHP_JSON_PRETTY_PRINT) {
75 32 : smart_str_appendc(buf, c);
76 : }
77 2707 : }
78 : /* }}} */
79 :
80 2431 : static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
81 : {
82 : int i;
83 :
84 2431 : if (options & PHP_JSON_PRETTY_PRINT) {
85 56 : for (i = 0; i < encoder->depth; ++i) {
86 : smart_str_appendl(buf, " ", 4);
87 : }
88 : }
89 2431 : }
90 : /* }}} */
91 :
92 : /* }}} */
93 :
94 69 : static inline int php_json_is_valid_double(double d) /* {{{ */
95 : {
96 69 : return !zend_isinf(d) && !zend_isnan(d);
97 : }
98 : /* }}} */
99 :
100 60 : 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 60 : php_gcvt(d, (int)PG(serialize_precision), '.', 'e', num);
106 60 : len = strlen(num);
107 60 : if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) {
108 9 : num[len++] = '.';
109 9 : num[len++] = '0';
110 9 : num[len] = '\0';
111 : }
112 : smart_str_appendl(buf, num, len);
113 60 : }
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 :
130 1341 : static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
131 : {
132 1341 : int i, r, need_comma = 0;
133 : HashTable *myht;
134 :
135 1341 : if (Z_TYPE_P(val) == IS_ARRAY) {
136 1265 : myht = Z_ARRVAL_P(val);
137 1265 : r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
138 : } else {
139 76 : myht = Z_OBJPROP_P(val);
140 76 : r = PHP_JSON_OUTPUT_OBJECT;
141 : }
142 :
143 1341 : if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) {
144 5 : encoder->error_code = PHP_JSON_ERROR_RECURSION;
145 : smart_str_appendl(buf, "null", 4);
146 5 : return FAILURE;
147 : }
148 :
149 1336 : PHP_JSON_HASH_APPLY_PROTECTION_INC(myht);
150 :
151 1336 : if (r == PHP_JSON_OUTPUT_ARRAY) {
152 : smart_str_appendc(buf, '[');
153 : } else {
154 : smart_str_appendc(buf, '{');
155 : }
156 :
157 1336 : ++encoder->depth;
158 :
159 1336 : i = myht ? zend_hash_num_elements(myht) : 0;
160 :
161 1336 : if (i > 0) {
162 : zend_string *key;
163 : zval *data;
164 : zend_ulong index;
165 :
166 5854 : ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
167 1707 : if (r == PHP_JSON_OUTPUT_ARRAY) {
168 1427 : if (need_comma) {
169 : smart_str_appendc(buf, ',');
170 : } else {
171 1214 : need_comma = 1;
172 : }
173 :
174 1427 : php_json_pretty_print_char(buf, options, '\n');
175 1427 : php_json_pretty_print_indent(buf, options, encoder);
176 280 : } else if (r == PHP_JSON_OUTPUT_OBJECT) {
177 280 : if (key) {
178 271 : if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
179 : /* Skip protected and private members. */
180 4 : continue;
181 : }
182 :
183 262 : if (need_comma) {
184 : smart_str_appendc(buf, ',');
185 : } else {
186 65 : need_comma = 1;
187 : }
188 :
189 262 : php_json_pretty_print_char(buf, options, '\n');
190 262 : php_json_pretty_print_indent(buf, options, encoder);
191 :
192 262 : if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
193 1 : options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
194 2 : (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
195 1 : buf->s) {
196 1 : ZSTR_LEN(buf->s) -= 4;
197 : smart_str_appendl(buf, "\"\"", 2);
198 : }
199 : } else {
200 14 : if (need_comma) {
201 : smart_str_appendc(buf, ',');
202 : } else {
203 6 : need_comma = 1;
204 : }
205 :
206 14 : php_json_pretty_print_char(buf, options, '\n');
207 14 : php_json_pretty_print_indent(buf, options, encoder);
208 :
209 : smart_str_appendc(buf, '"');
210 14 : smart_str_append_long(buf, (zend_long) index);
211 : smart_str_appendc(buf, '"');
212 : }
213 :
214 : smart_str_appendc(buf, ':');
215 276 : php_json_pretty_print_char(buf, options, ' ');
216 : }
217 :
218 2266 : if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
219 563 : !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
220 557 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
221 557 : return FAILURE;
222 : }
223 : } ZEND_HASH_FOREACH_END();
224 : }
225 :
226 779 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
227 :
228 779 : if (encoder->depth > encoder->max_depth) {
229 1 : encoder->error_code = PHP_JSON_ERROR_DEPTH;
230 1 : if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
231 1 : return FAILURE;
232 : }
233 : }
234 778 : --encoder->depth;
235 :
236 : /* Only keep closing bracket on same line for empty arrays/objects */
237 778 : if (need_comma) {
238 728 : php_json_pretty_print_char(buf, options, '\n');
239 728 : php_json_pretty_print_indent(buf, options, encoder);
240 : }
241 :
242 778 : if (r == PHP_JSON_OUTPUT_ARRAY) {
243 : smart_str_appendc(buf, ']');
244 : } else {
245 : smart_str_appendc(buf, '}');
246 : }
247 :
248 778 : return SUCCESS;
249 : }
250 : /* }}} */
251 :
252 707 : 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 707 : if (len == 0) {
261 : smart_str_appendl(buf, "\"\"", 2);
262 33 : return SUCCESS;
263 : }
264 :
265 674 : if (options & PHP_JSON_NUMERIC_CHECK) {
266 : double d;
267 : int type;
268 : zend_long p;
269 :
270 6 : if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
271 6 : if (type == IS_LONG) {
272 3 : smart_str_append_long(buf, p);
273 8 : return SUCCESS;
274 3 : } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
275 2 : php_json_encode_double(buf, d, options);
276 2 : return SUCCESS;
277 : }
278 : }
279 :
280 : }
281 669 : pos = 0;
282 669 : checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
283 :
284 : /* pre-allocate for string length plus 2 quotes */
285 669 : smart_str_alloc(buf, len+2, 0);
286 : smart_str_appendc(buf, '"');
287 :
288 : do {
289 4244 : us = (unsigned char)s[pos];
290 4244 : if (us >= 0x80) {
291 188 : int utf8_sub = 0;
292 188 : size_t prev_pos = pos;
293 :
294 188 : us = php_next_utf8_char((unsigned char *)s, len, &pos, &status);
295 :
296 : /* check whether UTF8 character is correct */
297 188 : if (status != SUCCESS) {
298 25 : if (options & PHP_JSON_INVALID_UTF8_IGNORE) {
299 : /* ignore invalid UTF8 character */
300 2 : continue;
301 23 : } else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
302 : /* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */
303 4 : us = 0xfffd;
304 4 : utf8_sub = 1;
305 : } else {
306 19 : if (buf->s) {
307 19 : ZSTR_LEN(buf->s) = checkpoint;
308 : }
309 19 : encoder->error_code = PHP_JSON_ERROR_UTF8;
310 19 : if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
311 : smart_str_appendl(buf, "null", 4);
312 : }
313 19 : 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 167 : if ((options & PHP_JSON_UNESCAPED_UNICODE)
321 22 : && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS)
322 20 : || us < 0x2028 || us > 0x2029)) {
323 20 : if (utf8_sub) {
324 : smart_str_appendl(buf, "\xef\xbf\xbd", 3);
325 : } else {
326 18 : smart_str_appendl(buf, s + prev_pos, pos - prev_pos);
327 : }
328 20 : continue;
329 : }
330 : /* From http://en.wikipedia.org/wiki/UTF16 */
331 147 : if (us >= 0x10000) {
332 : unsigned int next_us;
333 18 : us -= 0x10000;
334 18 : next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
335 18 : us = (unsigned short)((us >> 10) | 0xd800);
336 : smart_str_appendl(buf, "\\u", 2);
337 18 : smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
338 18 : smart_str_appendc(buf, digits[(us & 0xf00) >> 8]);
339 18 : smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
340 18 : smart_str_appendc(buf, digits[(us & 0xf)]);
341 18 : us = next_us;
342 : }
343 : smart_str_appendl(buf, "\\u", 2);
344 147 : smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
345 147 : smart_str_appendc(buf, digits[(us & 0xf00) >> 8]);
346 147 : smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
347 147 : smart_str_appendc(buf, digits[(us & 0xf)]);
348 : } else {
349 : static const uint32_t charmap[4] = {
350 : 0xffffffff, 0x500080c4, 0x10000000, 0x00000000};
351 :
352 4056 : pos++;
353 4056 : if (EXPECTED(!(charmap[us >> 5] & (1 << (us & 0x1f))))) {
354 3772 : smart_str_appendc(buf, (unsigned char) us);
355 : } else {
356 284 : switch (us) {
357 44 : case '"':
358 44 : if (options & PHP_JSON_HEX_QUOT) {
359 : smart_str_appendl(buf, "\\u0022", 6);
360 : } else {
361 : smart_str_appendl(buf, "\\\"", 2);
362 : }
363 44 : break;
364 :
365 16 : case '\\':
366 : smart_str_appendl(buf, "\\\\", 2);
367 16 : break;
368 :
369 52 : case '/':
370 52 : if (options & PHP_JSON_UNESCAPED_SLASHES) {
371 : smart_str_appendc(buf, '/');
372 : } else {
373 : smart_str_appendl(buf, "\\/", 2);
374 : }
375 52 : break;
376 :
377 10 : case '\b':
378 : smart_str_appendl(buf, "\\b", 2);
379 10 : break;
380 :
381 8 : case '\f':
382 : smart_str_appendl(buf, "\\f", 2);
383 8 : break;
384 :
385 25 : case '\n':
386 : smart_str_appendl(buf, "\\n", 2);
387 25 : break;
388 :
389 8 : case '\r':
390 : smart_str_appendl(buf, "\\r", 2);
391 8 : break;
392 :
393 10 : case '\t':
394 : smart_str_appendl(buf, "\\t", 2);
395 10 : break;
396 :
397 20 : case '<':
398 20 : if (options & PHP_JSON_HEX_TAG) {
399 : smart_str_appendl(buf, "\\u003C", 6);
400 : } else {
401 : smart_str_appendc(buf, '<');
402 : }
403 20 : break;
404 :
405 20 : case '>':
406 20 : if (options & PHP_JSON_HEX_TAG) {
407 : smart_str_appendl(buf, "\\u003E", 6);
408 : } else {
409 : smart_str_appendc(buf, '>');
410 : }
411 20 : break;
412 :
413 32 : case '&':
414 32 : if (options & PHP_JSON_HEX_AMP) {
415 : smart_str_appendl(buf, "\\u0026", 6);
416 : } else {
417 : smart_str_appendc(buf, '&');
418 : }
419 32 : break;
420 :
421 20 : case '\'':
422 20 : if (options & PHP_JSON_HEX_APOS) {
423 : smart_str_appendl(buf, "\\u0027", 6);
424 : } else {
425 : smart_str_appendc(buf, '\'');
426 : }
427 20 : break;
428 :
429 19 : default:
430 19 : ZEND_ASSERT(us < ' ');
431 : smart_str_appendl(buf, "\\u00", sizeof("\\u00")-1);
432 19 : smart_str_appendc(buf, digits[(us & 0xf0) >> 4]);
433 19 : smart_str_appendc(buf, digits[(us & 0xf)]);
434 19 : break;
435 : }
436 : }
437 : }
438 4225 : } while (pos < len);
439 :
440 : smart_str_appendc(buf, '"');
441 :
442 650 : return SUCCESS;
443 : }
444 : /* }}} */
445 :
446 19 : static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
447 : {
448 19 : zend_class_entry *ce = Z_OBJCE_P(val);
449 19 : HashTable* myht = Z_OBJPROP_P(val);
450 : zval retval, fname;
451 : int return_code;
452 :
453 19 : if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) {
454 1 : encoder->error_code = PHP_JSON_ERROR_RECURSION;
455 1 : if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
456 : smart_str_appendl(buf, "null", 4);
457 : }
458 1 : return FAILURE;
459 : }
460 :
461 18 : PHP_JSON_HASH_APPLY_PROTECTION_INC(myht);
462 :
463 36 : ZVAL_STRING(&fname, "jsonSerialize");
464 :
465 36 : if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
466 2 : if (!EG(exception)) {
467 0 : zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
468 : }
469 2 : zval_ptr_dtor(&fname);
470 :
471 2 : if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
472 : smart_str_appendl(buf, "null", 4);
473 : }
474 2 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
475 2 : return FAILURE;
476 : }
477 :
478 16 : if (EG(exception)) {
479 : /* Error already raised */
480 0 : zval_ptr_dtor(&retval);
481 0 : zval_ptr_dtor(&fname);
482 :
483 0 : if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
484 : smart_str_appendl(buf, "null", 4);
485 : }
486 0 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
487 0 : return FAILURE;
488 : }
489 :
490 21 : if ((Z_TYPE(retval) == IS_OBJECT) &&
491 5 : (Z_OBJ(retval) == Z_OBJ_P(val))) {
492 : /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
493 4 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
494 4 : return_code = php_json_encode_array(buf, &retval, options, encoder);
495 : } else {
496 : /* All other types, encode as normal */
497 12 : return_code = php_json_encode_zval(buf, &retval, options, encoder);
498 12 : PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
499 : }
500 :
501 16 : zval_ptr_dtor(&retval);
502 16 : zval_ptr_dtor(&fname);
503 :
504 16 : return return_code;
505 : }
506 : /* }}} */
507 :
508 2182 : int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
509 : {
510 2182 : again:
511 2182 : switch (Z_TYPE_P(val))
512 : {
513 30 : case IS_NULL:
514 : smart_str_appendl(buf, "null", 4);
515 30 : break;
516 :
517 12 : case IS_TRUE:
518 : smart_str_appendl(buf, "true", 4);
519 12 : break;
520 84 : case IS_FALSE:
521 : smart_str_appendl(buf, "false", 5);
522 84 : break;
523 :
524 182 : case IS_LONG:
525 182 : smart_str_append_long(buf, Z_LVAL_P(val));
526 182 : break;
527 :
528 66 : case IS_DOUBLE:
529 66 : if (php_json_is_valid_double(Z_DVAL_P(val))) {
530 58 : php_json_encode_double(buf, Z_DVAL_P(val), options);
531 : } else {
532 8 : encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
533 : smart_str_appendc(buf, '0');
534 : }
535 66 : break;
536 :
537 445 : case IS_STRING:
538 445 : return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
539 :
540 91 : case IS_OBJECT:
541 91 : if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
542 19 : 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 1337 : ZVAL_COPY(&zv, val);
551 1337 : res = php_json_encode_array(buf, &zv, options, encoder);
552 : zval_ptr_dtor_nogc(&zv);
553 1337 : return res;
554 : }
555 :
556 3 : case IS_REFERENCE:
557 3 : val = Z_REFVAL_P(val);
558 3 : goto again;
559 :
560 4 : default:
561 4 : encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
562 4 : if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
563 : smart_str_appendl(buf, "null", 4);
564 : }
565 4 : return FAILURE;
566 : }
567 :
568 374 : 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 : */
|