root/ext/json/json_encoder.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. php_json_determine_array_type
  2. php_json_pretty_print_char
  3. php_json_pretty_print_indent
  4. php_json_is_valid_double
  5. php_json_encode_double
  6. php_json_encode_array
  7. php_json_utf8_to_utf16
  8. php_json_escape_string
  9. php_json_encode_serializable_object
  10. php_json_encode_zval

   1 /*
   2   +----------------------------------------------------------------------+
   3   | PHP Version 7                                                        |
   4   +----------------------------------------------------------------------+
   5   | Copyright (c) 1997-2016 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 <zend_exceptions.h>
  33 
  34 /* double limits */
  35 #include <float.h>
  36 #if defined(DBL_MANT_DIG) && defined(DBL_MIN_EXP)
  37 #define PHP_JSON_DOUBLE_MAX_LENGTH (3 + DBL_MANT_DIG - DBL_MIN_EXP)
  38 #else
  39 #define PHP_JSON_DOUBLE_MAX_LENGTH 1080
  40 #endif
  41 
  42 static const char digits[] = "0123456789abcdef";
  43 
  44 static void php_json_escape_string(smart_str *buf, char *s, size_t len, int options);
  45 
  46 static int php_json_determine_array_type(zval *val) /* {{{ */
  47 {
  48         int i;
  49         HashTable *myht = Z_ARRVAL_P(val);
  50 
  51         i = myht ? zend_hash_num_elements(myht) : 0;
  52         if (i > 0) {
  53                 zend_string *key;
  54                 zend_ulong index, idx;
  55 
  56                 idx = 0;
  57                 ZEND_HASH_FOREACH_KEY(myht, index, key) {
  58                         if (key) {
  59                                 return PHP_JSON_OUTPUT_OBJECT;
  60                         } else {
  61                                 if (index != idx) {
  62                                         return PHP_JSON_OUTPUT_OBJECT;
  63                                 }
  64                         }
  65                         idx++;
  66                 } ZEND_HASH_FOREACH_END();
  67         }
  68 
  69         return PHP_JSON_OUTPUT_ARRAY;
  70 }
  71 /* }}} */
  72 
  73 /* {{{ Pretty printing support functions */
  74 
  75 static inline void php_json_pretty_print_char(smart_str *buf, int options, char c) /* {{{ */
  76 {
  77         if (options & PHP_JSON_PRETTY_PRINT) {
  78                 smart_str_appendc(buf, c);
  79         }
  80 }
  81 /* }}} */
  82 
  83 static inline void php_json_pretty_print_indent(smart_str *buf, int options) /* {{{ */
  84 {
  85         int i;
  86 
  87         if (options & PHP_JSON_PRETTY_PRINT) {
  88                 for (i = 0; i < JSON_G(encoder_depth); ++i) {
  89                         smart_str_appendl(buf, "    ", 4);
  90                 }
  91         }
  92 }
  93 /* }}} */
  94 
  95 /* }}} */
  96 
  97 static inline int php_json_is_valid_double(double d) /* {{{ */
  98 {
  99         return !zend_isinf(d) && !zend_isnan(d);
 100 }
 101 /* }}} */
 102 
 103 static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */
 104 {
 105         size_t len;
 106         char num[PHP_JSON_DOUBLE_MAX_LENGTH];
 107         php_gcvt(d, (int)EG(precision), '.', 'e', &num[0]);
 108         len = strlen(num);
 109         if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_JSON_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 static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */
 119 {
 120         int i, r, need_comma = 0;
 121         HashTable *myht;
 122 
 123         if (Z_TYPE_P(val) == IS_ARRAY) {
 124                 myht = Z_ARRVAL_P(val);
 125                 r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
 126         } else {
 127                 myht = Z_OBJPROP_P(val);
 128                 r = PHP_JSON_OUTPUT_OBJECT;
 129         }
 130 
 131         if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
 132                 JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
 133                 smart_str_appendl(buf, "null", 4);
 134                 return;
 135         }
 136 
 137         if (r == PHP_JSON_OUTPUT_ARRAY) {
 138                 smart_str_appendc(buf, '[');
 139         } else {
 140                 smart_str_appendc(buf, '{');
 141         }
 142 
 143         ++JSON_G(encoder_depth);
 144 
 145         i = myht ? zend_hash_num_elements(myht) : 0;
 146 
 147         if (i > 0) {
 148                 zend_string *key;
 149                 zval *data;
 150                 zend_ulong index;
 151                 HashTable *tmp_ht;
 152 
 153                 ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
 154                         ZVAL_DEREF(data);
 155                         tmp_ht = HASH_OF(data);
 156                         if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
 157                                 ZEND_HASH_INC_APPLY_COUNT(tmp_ht);
 158                         }
 159 
 160                         if (r == PHP_JSON_OUTPUT_ARRAY) {
 161                                 if (need_comma) {
 162                                         smart_str_appendc(buf, ',');
 163                                 } else {
 164                                         need_comma = 1;
 165                                 }
 166 
 167                                 php_json_pretty_print_char(buf, options, '\n');
 168                                 php_json_pretty_print_indent(buf, options);
 169                                 php_json_encode(buf, data, options);
 170                         } else if (r == PHP_JSON_OUTPUT_OBJECT) {
 171                                 if (key) {
 172                                         if (ZSTR_VAL(key)[0] == '\0' && Z_TYPE_P(val) == IS_OBJECT) {
 173                                                 /* Skip protected and private members. */
 174                                                 if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
 175                                                         ZEND_HASH_DEC_APPLY_COUNT(tmp_ht);
 176                                                 }
 177                                                 continue;
 178                                         }
 179 
 180                                         if (need_comma) {
 181                                                 smart_str_appendc(buf, ',');
 182                                         } else {
 183                                                 need_comma = 1;
 184                                         }
 185 
 186                                         php_json_pretty_print_char(buf, options, '\n');
 187                                         php_json_pretty_print_indent(buf, options);
 188 
 189                                         php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK);
 190                                         smart_str_appendc(buf, ':');
 191 
 192                                         php_json_pretty_print_char(buf, options, ' ');
 193 
 194                                         php_json_encode(buf, data, options);
 195                                 } else {
 196                                         if (need_comma) {
 197                                                 smart_str_appendc(buf, ',');
 198                                         } else {
 199                                                 need_comma = 1;
 200                                         }
 201 
 202                                         php_json_pretty_print_char(buf, options, '\n');
 203                                         php_json_pretty_print_indent(buf, options);
 204 
 205                                         smart_str_appendc(buf, '"');
 206                                         smart_str_append_long(buf, (zend_long) index);
 207                                         smart_str_appendc(buf, '"');
 208                                         smart_str_appendc(buf, ':');
 209 
 210                                         php_json_pretty_print_char(buf, options, ' ');
 211 
 212                                         php_json_encode(buf, data, options);
 213                                 }
 214                         }
 215 
 216                         if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
 217                                 ZEND_HASH_DEC_APPLY_COUNT(tmp_ht);
 218                         }
 219                 } ZEND_HASH_FOREACH_END();
 220         }
 221 
 222         if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) {
 223                 JSON_G(error_code) = PHP_JSON_ERROR_DEPTH;
 224         }
 225         --JSON_G(encoder_depth);
 226 
 227         /* Only keep closing bracket on same line for empty arrays/objects */
 228         if (need_comma) {
 229                 php_json_pretty_print_char(buf, options, '\n');
 230                 php_json_pretty_print_indent(buf, options);
 231         }
 232 
 233         if (r == PHP_JSON_OUTPUT_ARRAY) {
 234                 smart_str_appendc(buf, ']');
 235         } else {
 236                 smart_str_appendc(buf, '}');
 237         }
 238 }
 239 /* }}} */
 240 
 241 static int php_json_utf8_to_utf16(unsigned short *utf16, char utf8[], size_t len) /* {{{ */
 242 {
 243         size_t pos = 0, us;
 244         int j, status;
 245 
 246         if (utf16) {
 247                 /* really convert the utf8 string */
 248                 for (j=0 ; pos < len ; j++) {
 249                         us = php_next_utf8_char((const unsigned char *)utf8, len, &pos, &status);
 250                         if (status != SUCCESS) {
 251                                 return -1;
 252                         }
 253                         /* From http://en.wikipedia.org/wiki/UTF16 */
 254                         if (us >= 0x10000) {
 255                                 us -= 0x10000;
 256                                 utf16[j++] = (unsigned short)((us >> 10) | 0xd800);
 257                                 utf16[j] = (unsigned short)((us & 0x3ff) | 0xdc00);
 258                         } else {
 259                                 utf16[j] = (unsigned short)us;
 260                         }
 261                 }
 262         } else {
 263                 /* Only check if utf8 string is valid, and compute utf16 length */
 264                 for (j=0 ; pos < len ; j++) {
 265                         us = php_next_utf8_char((const unsigned char *)utf8, len, &pos, &status);
 266                         if (status != SUCCESS) {
 267                                 return -1;
 268                         }
 269                         if (us >= 0x10000) {
 270                                 j++;
 271                         }
 272                 }
 273         }
 274         return j;
 275 }
 276 /* }}} */
 277 
 278 static void php_json_escape_string(smart_str *buf, char *s, size_t len, int options) /* {{{ */
 279 {
 280         int status;
 281         unsigned int us;
 282         size_t pos, checkpoint;
 283 
 284         if (len == 0) {
 285                 smart_str_appendl(buf, "\"\"", 2);
 286                 return;
 287         }
 288 
 289         if (options & PHP_JSON_NUMERIC_CHECK) {
 290                 double d;
 291                 int type;
 292                 zend_long p;
 293 
 294                 if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
 295                         if (type == IS_LONG) {
 296                                 smart_str_append_long(buf, p);
 297                                 return;
 298                         } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
 299                                 php_json_encode_double(buf, d, options);
 300                                 return;
 301                         }
 302                 }
 303 
 304         }
 305 
 306         if (options & PHP_JSON_UNESCAPED_UNICODE) {
 307                 /* validate UTF-8 string first */
 308                 if (php_json_utf8_to_utf16(NULL, s, len) < 0) {
 309                         JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
 310                         smart_str_appendl(buf, "null", 4);
 311                         return;
 312                 }
 313         }
 314 
 315         pos = 0;
 316         checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
 317 
 318         /* pre-allocate for string length plus 2 quotes */
 319         smart_str_alloc(buf, len+2, 0);
 320         smart_str_appendc(buf, '"');
 321 
 322         do {
 323                 us = (unsigned char)s[pos];
 324                 if (us >= 0x80 && !(options & PHP_JSON_UNESCAPED_UNICODE)) {
 325                         /* UTF-8 character */
 326                         us = php_next_utf8_char((const unsigned char *)s, len, &pos, &status);
 327                         if (status != SUCCESS) {
 328                                 if (buf->s) {
 329                                         ZSTR_LEN(buf->s) = checkpoint;
 330                                 }
 331                                 JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
 332                                 smart_str_appendl(buf, "null", 4);
 333                                 return;
 334                         }
 335                         /* From http://en.wikipedia.org/wiki/UTF16 */
 336                         if (us >= 0x10000) {
 337                                 unsigned int next_us;
 338                                 us -= 0x10000;
 339                                 next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
 340                                 us = (unsigned short)((us >> 10) | 0xd800);
 341                                 smart_str_appendl(buf, "\\u", 2);
 342                                 smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
 343                                 smart_str_appendc(buf, digits[(us & 0xf00)  >> 8]);
 344                                 smart_str_appendc(buf, digits[(us & 0xf0)   >> 4]);
 345                                 smart_str_appendc(buf, digits[(us & 0xf)]);
 346                                 us = next_us;
 347                         }
 348                         smart_str_appendl(buf, "\\u", 2);
 349                         smart_str_appendc(buf, digits[(us & 0xf000) >> 12]);
 350                         smart_str_appendc(buf, digits[(us & 0xf00)  >> 8]);
 351                         smart_str_appendc(buf, digits[(us & 0xf0)   >> 4]);
 352                         smart_str_appendc(buf, digits[(us & 0xf)]);
 353                 } else {
 354                         pos++;
 355 
 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                                         if (us >= ' ') {
 431                                                 smart_str_appendc(buf, (unsigned char) us);
 432                                         } else {
 433                                                 smart_str_appendl(buf, "\\u00", sizeof("\\u00")-1);
 434                                                 smart_str_appendc(buf, digits[(us & 0xf0)   >> 4]);
 435                                                 smart_str_appendc(buf, digits[(us & 0xf)]);
 436                                         }
 437                                         break;
 438                         }
 439                 }
 440         } while (pos < len);
 441 
 442         smart_str_appendc(buf, '"');
 443 }
 444 /* }}} */
 445 
 446 static void php_json_encode_serializable_object(smart_str *buf, zval *val, int options) /* {{{ */
 447 {
 448         zend_class_entry *ce = Z_OBJCE_P(val);
 449         zval retval, fname;
 450         HashTable* myht;
 451 
 452         if (Z_TYPE_P(val) == IS_ARRAY) {
 453                 myht = Z_ARRVAL_P(val);
 454         } else {
 455                 myht = Z_OBJPROP_P(val);
 456         }
 457 
 458         if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
 459                 JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
 460                 smart_str_appendl(buf, "null", 4);
 461                 return;
 462         }
 463 
 464         ZVAL_STRING(&fname, "jsonSerialize");
 465 
 466         if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
 467                 zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
 468                 smart_str_appendl(buf, "null", sizeof("null") - 1);
 469                 zval_ptr_dtor(&fname);
 470                 return;
 471         }
 472 
 473         if (EG(exception)) {
 474                 /* Error already raised */
 475                 zval_ptr_dtor(&retval);
 476                 zval_ptr_dtor(&fname);
 477                 smart_str_appendl(buf, "null", sizeof("null") - 1);
 478                 return;
 479         }
 480 
 481         if ((Z_TYPE(retval) == IS_OBJECT) &&
 482                 (Z_OBJ(retval) == Z_OBJ_P(val))) {
 483                 /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
 484                 php_json_encode_array(buf, &retval, options);
 485         } else {
 486                 /* All other types, encode as normal */
 487                 php_json_encode(buf, &retval, options);
 488         }
 489 
 490         zval_ptr_dtor(&retval);
 491         zval_ptr_dtor(&fname);
 492 }
 493 /* }}} */
 494 
 495 void php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
 496 {
 497 again:
 498         switch (Z_TYPE_P(val))
 499         {
 500                 case IS_NULL:
 501                         smart_str_appendl(buf, "null", 4);
 502                         break;
 503 
 504                 case IS_TRUE:
 505                         smart_str_appendl(buf, "true", 4);
 506                         break;
 507                 case IS_FALSE:
 508                         smart_str_appendl(buf, "false", 5);
 509                         break;
 510 
 511                 case IS_LONG:
 512                         smart_str_append_long(buf, Z_LVAL_P(val));
 513                         break;
 514 
 515                 case IS_DOUBLE:
 516                         if (php_json_is_valid_double(Z_DVAL_P(val))) {
 517                                 php_json_encode_double(buf, Z_DVAL_P(val), options);
 518                         } else {
 519                                 JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
 520                                 smart_str_appendc(buf, '0');
 521                         }
 522                         break;
 523 
 524                 case IS_STRING:
 525                         php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options);
 526                         break;
 527 
 528                 case IS_OBJECT:
 529                         if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
 530                                 php_json_encode_serializable_object(buf, val, options);
 531                                 break;
 532                         }
 533                         /* fallthrough -- Non-serializable object */
 534                 case IS_ARRAY:
 535                         php_json_encode_array(buf, val, options);
 536                         break;
 537 
 538                 case IS_REFERENCE:
 539                         val = Z_REFVAL_P(val);
 540                         goto again;
 541 
 542                 default:
 543                         JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
 544                         smart_str_appendl(buf, "null", 4);
 545                         break;
 546         }
 547 
 548         return;
 549 }
 550 /* }}} */
 551 
 552 /*
 553  * Local variables:
 554  * tab-width: 4
 555  * c-basic-offset: 4
 556  * End:
 557  * vim600: noet sw=4 ts=4 fdm=marker
 558  * vim<600: noet sw=4 ts=4
 559  */

/* [<][>][^][v][top][bottom][index][help] */