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