xref: /PHP-7.2/ext/json/json_encoder.c (revision 4831e150)
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