1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 5 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 : +----------------------------------------------------------------------+
17 : */
18 :
19 : /* $Id: json.c 286385 2009-07-27 03:43:38Z scottmac $ */
20 :
21 : #ifdef HAVE_CONFIG_H
22 : #include "config.h"
23 : #endif
24 :
25 : #include "php.h"
26 : #include "php_ini.h"
27 : #include "ext/standard/info.h"
28 : #include "ext/standard/php_smart_str.h"
29 : #include "utf8_to_utf16.h"
30 : #include "JSON_parser.h"
31 : #include "php_json.h"
32 :
33 : static PHP_MINFO_FUNCTION(json);
34 : static PHP_FUNCTION(json_encode);
35 : static PHP_FUNCTION(json_decode);
36 : static PHP_FUNCTION(json_last_error);
37 :
38 : static const char digits[] = "0123456789abcdef";
39 :
40 : #define PHP_JSON_HEX_TAG (1<<0)
41 : #define PHP_JSON_HEX_AMP (1<<1)
42 : #define PHP_JSON_HEX_APOS (1<<2)
43 : #define PHP_JSON_HEX_QUOT (1<<3)
44 : #define PHP_JSON_FORCE_OBJECT (1<<4)
45 :
46 : #define PHP_JSON_OUTPUT_ARRAY 0
47 : #define PHP_JSON_OUTPUT_OBJECT 1
48 :
49 : ZEND_DECLARE_MODULE_GLOBALS(json)
50 :
51 : /* {{{ arginfo */
52 : ZEND_BEGIN_ARG_INFO_EX(arginfo_json_encode, 0, 0, 1)
53 : ZEND_ARG_INFO(0, value)
54 : ZEND_ARG_INFO(0, options)
55 : ZEND_END_ARG_INFO()
56 :
57 : ZEND_BEGIN_ARG_INFO_EX(arginfo_json_decode, 0, 0, 1)
58 : ZEND_ARG_INFO(0, json)
59 : ZEND_ARG_INFO(0, assoc)
60 : ZEND_ARG_INFO(0, depth)
61 : ZEND_END_ARG_INFO()
62 :
63 : ZEND_BEGIN_ARG_INFO(arginfo_json_last_error, 0)
64 : ZEND_END_ARG_INFO()
65 : /* }}} */
66 :
67 : /* {{{ json_functions[] */
68 : static const function_entry json_functions[] = {
69 : PHP_FE(json_encode, arginfo_json_encode)
70 : PHP_FE(json_decode, arginfo_json_decode)
71 : PHP_FE(json_last_error, arginfo_json_last_error)
72 : {NULL, NULL, NULL}
73 : };
74 : /* }}} */
75 :
76 : /* {{{ MINIT */
77 : static PHP_MINIT_FUNCTION(json)
78 17633 : {
79 17633 : REGISTER_LONG_CONSTANT("JSON_HEX_TAG", PHP_JSON_HEX_TAG, CONST_CS | CONST_PERSISTENT);
80 17633 : REGISTER_LONG_CONSTANT("JSON_HEX_AMP", PHP_JSON_HEX_AMP, CONST_CS | CONST_PERSISTENT);
81 17633 : REGISTER_LONG_CONSTANT("JSON_HEX_APOS", PHP_JSON_HEX_APOS, CONST_CS | CONST_PERSISTENT);
82 17633 : REGISTER_LONG_CONSTANT("JSON_HEX_QUOT", PHP_JSON_HEX_QUOT, CONST_CS | CONST_PERSISTENT);
83 17633 : REGISTER_LONG_CONSTANT("JSON_FORCE_OBJECT", PHP_JSON_FORCE_OBJECT, CONST_CS | CONST_PERSISTENT);
84 :
85 17633 : REGISTER_LONG_CONSTANT("JSON_ERROR_NONE", PHP_JSON_ERROR_NONE, CONST_CS | CONST_PERSISTENT);
86 17633 : REGISTER_LONG_CONSTANT("JSON_ERROR_DEPTH", PHP_JSON_ERROR_DEPTH, CONST_CS | CONST_PERSISTENT);
87 17633 : REGISTER_LONG_CONSTANT("JSON_ERROR_STATE_MISMATCH", PHP_JSON_ERROR_STATE_MISMATCH, CONST_CS | CONST_PERSISTENT);
88 17633 : REGISTER_LONG_CONSTANT("JSON_ERROR_CTRL_CHAR", PHP_JSON_ERROR_CTRL_CHAR, CONST_CS | CONST_PERSISTENT);
89 17633 : REGISTER_LONG_CONSTANT("JSON_ERROR_SYNTAX", PHP_JSON_ERROR_SYNTAX, CONST_CS | CONST_PERSISTENT);
90 :
91 17633 : return SUCCESS;
92 : }
93 : /* }}} */
94 :
95 : /* {{{ PHP_GINIT_FUNCTION
96 : */
97 : static PHP_GINIT_FUNCTION(json)
98 17633 : {
99 17633 : json_globals->error_code = 0;
100 17633 : }
101 : /* }}} */
102 :
103 :
104 : /* {{{ json_module_entry
105 : */
106 : zend_module_entry json_module_entry = {
107 : STANDARD_MODULE_HEADER,
108 : "json",
109 : json_functions,
110 : PHP_MINIT(json),
111 : NULL,
112 : NULL,
113 : NULL,
114 : PHP_MINFO(json),
115 : PHP_JSON_VERSION,
116 : PHP_MODULE_GLOBALS(json),
117 : PHP_GINIT(json),
118 : NULL,
119 : NULL,
120 : STANDARD_MODULE_PROPERTIES_EX
121 : };
122 : /* }}} */
123 :
124 : #ifdef COMPILE_DL_JSON
125 : ZEND_GET_MODULE(json)
126 : #endif
127 :
128 : /* {{{ PHP_MINFO_FUNCTION
129 : */
130 : static PHP_MINFO_FUNCTION(json)
131 42 : {
132 42 : php_info_print_table_start();
133 42 : php_info_print_table_row(2, "json support", "enabled");
134 42 : php_info_print_table_row(2, "json version", PHP_JSON_VERSION);
135 42 : php_info_print_table_end();
136 42 : }
137 : /* }}} */
138 :
139 : static void json_escape_string(smart_str *buf, char *s, int len, int options TSRMLS_DC);
140 :
141 : static int json_determine_array_type(zval **val TSRMLS_DC) /* {{{ */
142 102 : {
143 : int i;
144 102 : HashTable *myht = HASH_OF(*val);
145 :
146 102 : i = myht ? zend_hash_num_elements(myht) : 0;
147 102 : if (i > 0) {
148 : char *key;
149 : ulong index, idx;
150 : uint key_len;
151 : HashPosition pos;
152 :
153 87 : zend_hash_internal_pointer_reset_ex(myht, &pos);
154 87 : idx = 0;
155 214 : for (;; zend_hash_move_forward_ex(myht, &pos)) {
156 301 : i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
157 301 : if (i == HASH_KEY_NON_EXISTANT)
158 74 : break;
159 :
160 227 : if (i == HASH_KEY_IS_STRING) {
161 10 : return 1;
162 : } else {
163 217 : if (index != idx) {
164 3 : return 1;
165 : }
166 : }
167 214 : idx++;
168 214 : }
169 : }
170 :
171 89 : return PHP_JSON_OUTPUT_ARRAY;
172 : }
173 : /* }}} */
174 :
175 : static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC) /* {{{ */
176 125 : {
177 : int i, r;
178 : HashTable *myht;
179 :
180 125 : if (Z_TYPE_PP(val) == IS_ARRAY) {
181 106 : myht = HASH_OF(*val);
182 106 : r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : json_determine_array_type(val TSRMLS_CC);
183 : } else {
184 19 : myht = Z_OBJPROP_PP(val);
185 19 : r = PHP_JSON_OUTPUT_OBJECT;
186 : }
187 :
188 125 : if (myht && myht->nApplyCount > 1) {
189 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
190 2 : smart_str_appendl(buf, "null", 4);
191 2 : return;
192 : }
193 :
194 123 : if (r == PHP_JSON_OUTPUT_ARRAY) {
195 88 : smart_str_appendc(buf, '[');
196 : } else {
197 35 : smart_str_appendc(buf, '{');
198 : }
199 :
200 123 : i = myht ? zend_hash_num_elements(myht) : 0;
201 :
202 123 : if (i > 0)
203 : {
204 : char *key;
205 : zval **data;
206 : ulong index;
207 : uint key_len;
208 : HashPosition pos;
209 : HashTable *tmp_ht;
210 103 : int need_comma = 0;
211 :
212 103 : zend_hash_internal_pointer_reset_ex(myht, &pos);
213 396 : for (;; zend_hash_move_forward_ex(myht, &pos)) {
214 499 : i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
215 499 : if (i == HASH_KEY_NON_EXISTANT)
216 103 : break;
217 :
218 396 : if (zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS) {
219 396 : tmp_ht = HASH_OF(*data);
220 396 : if (tmp_ht) {
221 92 : tmp_ht->nApplyCount++;
222 : }
223 :
224 396 : if (r == PHP_JSON_OUTPUT_ARRAY) {
225 212 : if (need_comma) {
226 139 : smart_str_appendc(buf, ',');
227 : } else {
228 73 : need_comma = 1;
229 : }
230 :
231 212 : php_json_encode(buf, *data, options TSRMLS_CC);
232 184 : } else if (r == PHP_JSON_OUTPUT_OBJECT) {
233 184 : if (i == HASH_KEY_IS_STRING) {
234 170 : if (key[0] == '\0' && Z_TYPE_PP(val) == IS_OBJECT) {
235 : /* Skip protected and private members. */
236 1 : if (tmp_ht) {
237 1 : tmp_ht->nApplyCount--;
238 : }
239 1 : continue;
240 : }
241 :
242 169 : if (need_comma) {
243 146 : smart_str_appendc(buf, ',');
244 : } else {
245 23 : need_comma = 1;
246 : }
247 :
248 169 : json_escape_string(buf, key, key_len - 1, options TSRMLS_CC);
249 169 : smart_str_appendc(buf, ':');
250 :
251 169 : php_json_encode(buf, *data, options TSRMLS_CC);
252 : } else {
253 14 : if (need_comma) {
254 8 : smart_str_appendc(buf, ',');
255 : } else {
256 6 : need_comma = 1;
257 : }
258 :
259 14 : smart_str_appendc(buf, '"');
260 14 : smart_str_append_long(buf, (long) index);
261 14 : smart_str_appendc(buf, '"');
262 14 : smart_str_appendc(buf, ':');
263 :
264 14 : php_json_encode(buf, *data, options TSRMLS_CC);
265 : }
266 : }
267 :
268 395 : if (tmp_ht) {
269 91 : tmp_ht->nApplyCount--;
270 : }
271 : }
272 396 : }
273 : }
274 :
275 123 : if (r == PHP_JSON_OUTPUT_ARRAY) {
276 88 : smart_str_appendc(buf, ']');
277 : } else {
278 35 : smart_str_appendc(buf, '}');
279 : }
280 : }
281 : /* }}} */
282 :
283 : #define REVERSE16(us) (((us & 0xf) << 12) | (((us >> 4) & 0xf) << 8) | (((us >> 8) & 0xf) << 4) | ((us >> 12) & 0xf))
284 :
285 : static void json_escape_string(smart_str *buf, char *s, int len, int options TSRMLS_DC) /* {{{ */
286 340 : {
287 340 : int pos = 0;
288 : unsigned short us;
289 : unsigned short *utf16;
290 :
291 340 : if (len == 0) {
292 11 : smart_str_appendl(buf, "\"\"", 2);
293 11 : return;
294 : }
295 :
296 329 : utf16 = (unsigned short *) safe_emalloc(len, sizeof(unsigned short), 0);
297 :
298 329 : len = utf8_to_utf16(utf16, s, len);
299 329 : if (len <= 0) {
300 4 : if (utf16) {
301 4 : efree(utf16);
302 : }
303 4 : if (len < 0) {
304 4 : JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
305 4 : if (!PG(display_errors)) {
306 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid UTF-8 sequence in argument");
307 : }
308 4 : smart_str_appendl(buf, "null", 4);
309 : } else {
310 0 : smart_str_appendl(buf, "\"\"", 2);
311 : }
312 4 : return;
313 : }
314 :
315 325 : smart_str_appendc(buf, '"');
316 :
317 3592 : while (pos < len)
318 : {
319 2942 : us = utf16[pos++];
320 :
321 2942 : switch (us)
322 : {
323 : case '"':
324 44 : if (options & PHP_JSON_HEX_QUOT) {
325 4 : smart_str_appendl(buf, "\\u0022", 6);
326 : } else {
327 40 : smart_str_appendl(buf, "\\\"", 2);
328 : }
329 44 : break;
330 :
331 : case '\\':
332 8 : smart_str_appendl(buf, "\\\\", 2);
333 8 : break;
334 :
335 : case '/':
336 48 : smart_str_appendl(buf, "\\/", 2);
337 48 : break;
338 :
339 : case '\b':
340 8 : smart_str_appendl(buf, "\\b", 2);
341 8 : break;
342 :
343 : case '\f':
344 8 : smart_str_appendl(buf, "\\f", 2);
345 8 : break;
346 :
347 : case '\n':
348 9 : smart_str_appendl(buf, "\\n", 2);
349 9 : break;
350 :
351 : case '\r':
352 8 : smart_str_appendl(buf, "\\r", 2);
353 8 : break;
354 :
355 : case '\t':
356 10 : smart_str_appendl(buf, "\\t", 2);
357 10 : break;
358 :
359 : case '<':
360 18 : if (options & PHP_JSON_HEX_TAG) {
361 2 : smart_str_appendl(buf, "\\u003C", 6);
362 : } else {
363 16 : smart_str_appendc(buf, '<');
364 : }
365 18 : break;
366 :
367 : case '>':
368 18 : if (options & PHP_JSON_HEX_TAG) {
369 2 : smart_str_appendl(buf, "\\u003E", 6);
370 : } else {
371 16 : smart_str_appendc(buf, '>');
372 : }
373 18 : break;
374 :
375 : case '&':
376 32 : if (options & PHP_JSON_HEX_AMP) {
377 4 : smart_str_appendl(buf, "\\u0026", 6);
378 : } else {
379 28 : smart_str_appendc(buf, '&');
380 : }
381 32 : break;
382 :
383 : case '\'':
384 20 : if (options & PHP_JSON_HEX_APOS) {
385 4 : smart_str_appendl(buf, "\\u0027", 6);
386 : } else {
387 16 : smart_str_appendc(buf, '\'');
388 : }
389 20 : break;
390 :
391 : default:
392 5280 : if (us >= ' ' && (us & 127) == us) {
393 2569 : smart_str_appendc(buf, (unsigned char) us);
394 : } else {
395 142 : smart_str_appendl(buf, "\\u", 2);
396 142 : us = REVERSE16(us);
397 :
398 142 : smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
399 142 : us >>= 4;
400 142 : smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
401 142 : us >>= 4;
402 142 : smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
403 142 : us >>= 4;
404 142 : smart_str_appendc(buf, digits[us & ((1 << 4) - 1)]);
405 : }
406 : break;
407 : }
408 : }
409 :
410 325 : smart_str_appendc(buf, '"');
411 325 : efree(utf16);
412 : }
413 : /* }}} */
414 :
415 : PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_DC) /* {{{ */
416 482 : {
417 482 : JSON_G(error_code) = PHP_JSON_ERROR_NONE;
418 482 : switch (Z_TYPE_P(val))
419 : {
420 : case IS_NULL:
421 14 : smart_str_appendl(buf, "null", 4);
422 14 : break;
423 :
424 : case IS_BOOL:
425 22 : if (Z_BVAL_P(val)) {
426 12 : smart_str_appendl(buf, "true", 4);
427 : } else {
428 10 : smart_str_appendl(buf, "false", 5);
429 : }
430 22 : break;
431 :
432 : case IS_LONG:
433 110 : smart_str_append_long(buf, Z_LVAL_P(val));
434 110 : break;
435 :
436 : case IS_DOUBLE:
437 : {
438 39 : char *d = NULL;
439 : int len;
440 39 : double dbl = Z_DVAL_P(val);
441 :
442 74 : if (!zend_isinf(dbl) && !zend_isnan(dbl)) {
443 35 : len = spprintf(&d, 0, "%.*k", (int) EG(precision), dbl);
444 35 : smart_str_appendl(buf, d, len);
445 35 : efree(d);
446 : } else {
447 4 : zend_error(E_WARNING, "[json] (php_json_encode) double %.9g does not conform to the JSON spec, encoded as 0", dbl);
448 4 : smart_str_appendc(buf, '0');
449 : }
450 : }
451 39 : break;
452 :
453 : case IS_STRING:
454 171 : json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options TSRMLS_CC);
455 171 : break;
456 :
457 : case IS_ARRAY:
458 : case IS_OBJECT:
459 125 : json_encode_array(buf, &val, options TSRMLS_CC);
460 125 : break;
461 :
462 : default:
463 1 : zend_error(E_WARNING, "[json] (php_json_encode) type is unsupported, encoded as null");
464 1 : smart_str_appendl(buf, "null", 4);
465 : break;
466 : }
467 :
468 : return;
469 : }
470 : /* }}} */
471 :
472 : PHP_JSON_API void php_json_decode(zval *return_value, char *str, int str_len, zend_bool assoc, long depth TSRMLS_DC) /* {{{ */
473 135 : {
474 : int utf16_len;
475 : zval *z;
476 : unsigned short *utf16;
477 : JSON_parser jp;
478 :
479 135 : utf16 = (unsigned short *) safe_emalloc((str_len+1), sizeof(unsigned short), 1);
480 :
481 135 : utf16_len = utf8_to_utf16(utf16, str, str_len);
482 135 : if (utf16_len <= 0) {
483 0 : if (utf16) {
484 0 : efree(utf16);
485 : }
486 0 : RETURN_NULL();
487 : }
488 :
489 135 : if (depth <= 0) {
490 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Depth must greater than zero");
491 0 : efree(utf16);
492 0 : RETURN_NULL();
493 : }
494 :
495 135 : ALLOC_INIT_ZVAL(z);
496 135 : jp = new_JSON_parser(depth);
497 135 : if (parse_JSON(jp, z, utf16, utf16_len, assoc TSRMLS_CC)) {
498 51 : *return_value = *z;
499 : }
500 : else
501 : {
502 : double d;
503 : int type;
504 : long p;
505 :
506 84 : RETVAL_NULL();
507 84 : if (str_len == 4) {
508 10 : if (!strcasecmp(str, "null")) {
509 : /* We need to explicitly clear the error because its an actual NULL and not an error */
510 2 : jp->error_code = PHP_JSON_ERROR_NONE;
511 2 : RETVAL_NULL();
512 8 : } else if (!strcasecmp(str, "true")) {
513 2 : RETVAL_BOOL(1);
514 : }
515 74 : } else if (str_len == 5 && !strcasecmp(str, "false")) {
516 2 : RETVAL_BOOL(0);
517 : }
518 :
519 84 : if ((type = is_numeric_string(str, str_len, &p, &d, 0)) != 0) {
520 17 : if (type == IS_LONG) {
521 14 : RETVAL_LONG(p);
522 3 : } else if (type == IS_DOUBLE) {
523 3 : RETVAL_DOUBLE(d);
524 : }
525 : }
526 :
527 84 : if (Z_TYPE_P(return_value) != IS_NULL) {
528 21 : jp->error_code = PHP_JSON_ERROR_NONE;
529 : }
530 :
531 84 : zval_dtor(z);
532 : }
533 135 : FREE_ZVAL(z);
534 135 : efree(utf16);
535 135 : JSON_G(error_code) = jp->error_code;
536 135 : free_JSON_parser(jp);
537 : }
538 : /* }}} */
539 :
540 : /* {{{ proto string json_encode(mixed data [, int options])
541 : Returns the JSON representation of a value */
542 : static PHP_FUNCTION(json_encode)
543 89 : {
544 : zval *parameter;
545 89 : smart_str buf = {0};
546 89 : long options = 0;
547 :
548 89 : if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", ¶meter, &options) == FAILURE) {
549 2 : return;
550 : }
551 :
552 87 : php_json_encode(&buf, parameter, options TSRMLS_CC);
553 :
554 87 : ZVAL_STRINGL(return_value, buf.c, buf.len, 1);
555 :
556 87 : smart_str_free(&buf);
557 : }
558 : /* }}} */
559 :
560 : /* {{{ proto mixed json_decode(string json [, bool assoc [, long depth]])
561 : Decodes the JSON representation into a PHP value */
562 : static PHP_FUNCTION(json_decode)
563 142 : {
564 : char *str;
565 : int str_len;
566 142 : zend_bool assoc = 0; /* return JS objects as PHP objects by default */
567 142 : long depth = JSON_PARSER_DEFAULT_DEPTH;
568 :
569 142 : if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bl", &str, &str_len, &assoc, &depth) == FAILURE) {
570 3 : return;
571 : }
572 :
573 139 : if (!str_len) {
574 4 : RETURN_NULL();
575 : }
576 :
577 135 : php_json_decode(return_value, str, str_len, assoc, depth TSRMLS_CC);
578 : }
579 : /* }}} */
580 :
581 : /* {{{ proto int json_last_error()
582 : Returns the error code of the last json_decode(). */
583 : static PHP_FUNCTION(json_last_error)
584 5 : {
585 5 : if (zend_parse_parameters_none() == FAILURE) {
586 0 : return;
587 : }
588 :
589 5 : RETURN_LONG(JSON_G(error_code));
590 : }
591 : /* }}} */
592 :
593 : /*
594 : * Local variables:
595 : * tab-width: 4
596 : * c-basic-offset: 4
597 : * End:
598 : * vim600: noet sw=4 ts=4 fdm=marker
599 : * vim<600: noet sw=4 ts=4
600 : */
|