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