xref: /PHP-7.4/ext/json/json_encoder.c (revision 2d2c001c)
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