Make WordPress Core

Ticket #21568: 21568.patch

File 21568.patch, 59.7 KB (added by bpetty, 13 years ago)
  • wp-includes/class-json.php

    diff --git wp-includes/class-json.php wp-includes/class-json.php
    index 58f6f7d..1e8588d 100644
     
    11<?php
    2 if ( !class_exists( 'Services_JSON' ) ) :
     2if ( ! class_exists( 'Services_JSON' ) ) :
    33/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
    44/**
    55 * Converts to and from JSON format.
    if ( !class_exists( 'Services_JSON' ) ) : 
    4646 * DAMAGE.
    4747 *
    4848 * @category
    49  * @package             Services_JSON
    50  * @author              Michal Migurski <mike-json@teczno.com>
    51  * @author              Matt Knapp <mdknapp[at]gmail[dot]com>
    52  * @author              Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
    53  * @copyright   2005 Michal Migurski
    54  * @version     CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $
    55  * @license             http://www.opensource.org/licenses/bsd-license.php
    56  * @link                http://pear.php.net/pepr/pepr-proposal-show.php?id=198
     49 * @package     Services_JSON
     50 * @author      Michal Migurski <mike-json@teczno.com>
     51 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
     52 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
     53 * @copyright   2005 Michal Migurski
     54 * @version     CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $
     55 * @license     http://www.opensource.org/licenses/bsd-license.php
     56 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
    5757 */
    5858
    5959/**
    6060 * Marker constant for Services_JSON::decode(), used to flag stack state
    6161 */
    62 define('SERVICES_JSON_SLICE', 1);
     62define('SERVICES_JSON_SLICE',   1);
    6363
    6464/**
    6565 * Marker constant for Services_JSON::decode(), used to flag stack state
    define('SERVICES_JSON_LOOSE_TYPE', 16); 
    9292define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
    9393
    9494/**
     95 * Behavior switch for Services_JSON::decode()
     96 */
     97define('SERVICES_JSON_USE_TO_JSON', 64);
     98
     99/**
    95100 * Converts to and from JSON format.
    96101 *
    97102 * Brief example of use:
    define('SERVICES_JSON_SUPPRESS_ERRORS', 32); 
    114119 */
    115120class Services_JSON
    116121{
    117  /**
    118         * constructs a new JSON instance
    119         *
    120         * @param int $use object behavior flags; combine with boolean-OR
    121         *
    122         *                                               possible values:
    123         *                                               - SERVICES_JSON_LOOSE_TYPE:  loose typing.
    124         *                                                               "{...}" syntax creates associative arrays
    125         *                                                               instead of objects in decode().
    126         *                                               - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
    127         *                                                               Values which can't be encoded (e.g. resources)
    128         *                                                               appear as NULL instead of throwing errors.
    129         *                                                               By default, a deeply-nested resource will
    130         *                                                               bubble up with an error, so all return values
    131         *                                                               from encode() should be checked with isError()
    132         */
    133         function Services_JSON($use = 0)
    134         {
    135                 $this->use = $use;
    136         }
    137 
    138  /**
    139         * convert a string from one UTF-16 char to one UTF-8 char
    140         *
    141         * Normally should be handled by mb_convert_encoding, but
    142         * provides a slower PHP-only method for installations
    143         * that lack the multibye string extension.
    144         *
    145         * @param        string  $utf16  UTF-16 character
    146         * @return string  UTF-8 character
    147         * @access private
    148         */
    149         function utf162utf8($utf16)
    150         {
    151                 // oh please oh please oh please oh please oh please
    152                 if(function_exists('mb_convert_encoding')) {
    153                         return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
    154                 }
    155 
    156                 $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
    157 
    158                 switch(true) {
    159                         case ((0x7F & $bytes) == $bytes):
    160                                 // this case should never be reached, because we are in ASCII range
    161                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    162                                 return chr(0x7F & $bytes);
    163 
    164                         case (0x07FF & $bytes) == $bytes:
    165                                 // return a 2-byte UTF-8 character
    166                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    167                                 return chr(0xC0 | (($bytes >> 6) & 0x1F))
    168                                         . chr(0x80 | ($bytes & 0x3F));
    169 
    170                         case (0xFFFF & $bytes) == $bytes:
    171                                 // return a 3-byte UTF-8 character
    172                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    173                                 return chr(0xE0 | (($bytes >> 12) & 0x0F))
    174                                         . chr(0x80 | (($bytes >> 6) & 0x3F))
    175                                         . chr(0x80 | ($bytes & 0x3F));
    176                 }
    177 
    178                 // ignoring UTF-32 for now, sorry
    179                 return '';
    180         }
    181 
    182  /**
    183         * convert a string from one UTF-8 char to one UTF-16 char
    184         *
    185         * Normally should be handled by mb_convert_encoding, but
    186         * provides a slower PHP-only method for installations
    187         * that lack the multibye string extension.
    188         *
    189         * @param        string  $utf8 UTF-8 character
    190         * @return string  UTF-16 character
    191         * @access private
    192         */
    193         function utf82utf16($utf8)
    194         {
    195                 // oh please oh please oh please oh please oh please
    196                 if(function_exists('mb_convert_encoding')) {
    197                         return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
    198                 }
    199 
    200                 switch(strlen($utf8)) {
    201                         case 1:
    202                                 // this case should never be reached, because we are in ASCII range
    203                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    204                                 return $utf8;
    205 
    206                         case 2:
    207                                 // return a UTF-16 character from a 2-byte UTF-8 char
    208                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    209                                 return chr(0x07 & (ord($utf8[0]) >> 2))
    210                                         . chr((0xC0 & (ord($utf8[0]) << 6))
    211                                                 | (0x3F & ord($utf8[1])));
    212 
    213                         case 3:
    214                                 // return a UTF-16 character from a 3-byte UTF-8 char
    215                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    216                                 return chr((0xF0 & (ord($utf8[0]) << 4))
    217                                                 | (0x0F & (ord($utf8[1]) >> 2)))
    218                                         . chr((0xC0 & (ord($utf8[1]) << 6))
    219                                                 | (0x7F & ord($utf8[2])));
    220                 }
    221 
    222                 // ignoring UTF-32 for now, sorry
    223                 return '';
    224         }
    225 
    226  /**
    227         * encodes an arbitrary variable into JSON format (and sends JSON Header)
    228         *
    229         * @param        mixed $var      any number, boolean, string, array, or object to be encoded.
    230         *                                               see argument 1 to Services_JSON() above for array-parsing behavior.
    231         *                                               if var is a strng, note that encode() always expects it
    232         *                                               to be in ASCII or UTF-8 format!
    233         *
    234         * @return mixed JSON string representation of input var or an error if a problem occurs
    235         * @access public
    236         */
    237         function encode($var)
    238         {
    239                 header('Content-type: application/json');
    240                 return $this->_encode($var);
    241         }
    242         /**
    243         * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
    244         *
    245         * @param        mixed $var      any number, boolean, string, array, or object to be encoded.
    246         *                                               see argument 1 to Services_JSON() above for array-parsing behavior.
    247         *                                               if var is a strng, note that encode() always expects it
    248         *                                               to be in ASCII or UTF-8 format!
    249         *
    250         * @return mixed JSON string representation of input var or an error if a problem occurs
    251         * @access public
    252         */
    253         function encodeUnsafe($var)
    254         {
    255                 return $this->_encode($var);
    256         }
    257         /**
    258         * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
    259         *
    260         * @param        mixed $var      any number, boolean, string, array, or object to be encoded.
    261         *                                               see argument 1 to Services_JSON() above for array-parsing behavior.
    262         *                                               if var is a strng, note that encode() always expects it
    263         *                                               to be in ASCII or UTF-8 format!
    264         *
    265         * @return mixed JSON string representation of input var or an error if a problem occurs
    266         * @access public
    267         */
    268         function _encode($var)
    269         {
    270 
    271                 switch (gettype($var)) {
    272                         case 'boolean':
    273                                 return $var ? 'true' : 'false';
    274 
    275                         case 'NULL':
    276                                 return 'null';
    277 
    278                         case 'integer':
    279                                 return (int) $var;
    280 
    281                         case 'double':
    282                         case 'float':
    283                                 return (float) $var;
    284 
    285                         case 'string':
    286                                 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
    287                                 $ascii = '';
    288                                 $strlen_var = strlen($var);
    289 
    290                         /*
    291                                 * Iterate over every character in the string,
    292                                 * escaping with a slash or encoding to UTF-8 where necessary
    293                                 */
    294                                 for ($c = 0; $c < $strlen_var; ++$c) {
    295 
    296                                         $ord_var_c = ord($var[$c]);
    297 
    298                                         switch (true) {
    299                                                 case $ord_var_c == 0x08:
    300                                                         $ascii .= '\b';
    301                                                         break;
    302                                                 case $ord_var_c == 0x09:
    303                                                         $ascii .= '\t';
    304                                                         break;
    305                                                 case $ord_var_c == 0x0A:
    306                                                         $ascii .= '\n';
    307                                                         break;
    308                                                 case $ord_var_c == 0x0C:
    309                                                         $ascii .= '\f';
    310                                                         break;
    311                                                 case $ord_var_c == 0x0D:
    312                                                         $ascii .= '\r';
    313                                                         break;
    314 
    315                                                 case $ord_var_c == 0x22:
    316                                                 case $ord_var_c == 0x2F:
    317                                                 case $ord_var_c == 0x5C:
    318                                                         // double quote, slash, slosh
    319                                                         $ascii .= '\\'.$var[$c];
    320                                                         break;
    321 
    322                                                 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
    323                                                         // characters U-00000000 - U-0000007F (same as ASCII)
    324                                                         $ascii .= $var[$c];
    325                                                         break;
    326 
    327                                                 case (($ord_var_c & 0xE0) == 0xC0):
    328                                                         // characters U-00000080 - U-000007FF, mask 110XXXXX
    329                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    330                                                         if ($c+1 >= $strlen_var) {
    331                                                                 $c += 1;
    332                                                                 $ascii .= '?';
    333                                                                 break;
    334                                                         }
    335 
    336                                                         $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
    337                                                         $c += 1;
    338                                                         $utf16 = $this->utf82utf16($char);
    339                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
    340                                                         break;
    341 
    342                                                 case (($ord_var_c & 0xF0) == 0xE0):
    343                                                         if ($c+2 >= $strlen_var) {
    344                                                                 $c += 2;
    345                                                                 $ascii .= '?';
    346                                                                 break;
    347                                                         }
    348                                                         // characters U-00000800 - U-0000FFFF, mask 1110XXXX
    349                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    350                                                         $char = pack('C*', $ord_var_c,
    351                                                                                 @ord($var[$c + 1]),
    352                                                                                 @ord($var[$c + 2]));
    353                                                         $c += 2;
    354                                                         $utf16 = $this->utf82utf16($char);
    355                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
    356                                                         break;
    357 
    358                                                 case (($ord_var_c & 0xF8) == 0xF0):
    359                                                         if ($c+3 >= $strlen_var) {
    360                                                                 $c += 3;
    361                                                                 $ascii .= '?';
    362                                                                 break;
    363                                                         }
    364                                                         // characters U-00010000 - U-001FFFFF, mask 11110XXX
    365                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    366                                                         $char = pack('C*', $ord_var_c,
    367                                                                                 ord($var[$c + 1]),
    368                                                                                 ord($var[$c + 2]),
    369                                                                                 ord($var[$c + 3]));
    370                                                         $c += 3;
    371                                                         $utf16 = $this->utf82utf16($char);
    372                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
    373                                                         break;
    374 
    375                                                 case (($ord_var_c & 0xFC) == 0xF8):
    376                                                         // characters U-00200000 - U-03FFFFFF, mask 111110XX
    377                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    378                                                         if ($c+4 >= $strlen_var) {
    379                                                                 $c += 4;
    380                                                                 $ascii .= '?';
    381                                                                 break;
    382                                                         }
    383                                                         $char = pack('C*', $ord_var_c,
    384                                                                                 ord($var[$c + 1]),
    385                                                                                 ord($var[$c + 2]),
    386                                                                                 ord($var[$c + 3]),
    387                                                                                 ord($var[$c + 4]));
    388                                                         $c += 4;
    389                                                         $utf16 = $this->utf82utf16($char);
    390                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
    391                                                         break;
    392 
    393                                                 case (($ord_var_c & 0xFE) == 0xFC):
    394                                                 if ($c+5 >= $strlen_var) {
    395                                                                 $c += 5;
    396                                                                 $ascii .= '?';
    397                                                                 break;
    398                                                         }
    399                                                         // characters U-04000000 - U-7FFFFFFF, mask 1111110X
    400                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    401                                                         $char = pack('C*', $ord_var_c,
    402                                                                                 ord($var[$c + 1]),
    403                                                                                 ord($var[$c + 2]),
    404                                                                                 ord($var[$c + 3]),
    405                                                                                 ord($var[$c + 4]),
    406                                                                                 ord($var[$c + 5]));
    407                                                         $c += 5;
    408                                                         $utf16 = $this->utf82utf16($char);
    409                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
    410                                                         break;
    411                                         }
    412                                 }
    413                                 return  '"'.$ascii.'"';
    414 
    415                         case 'array':
    416                         /*
    417                                 * As per JSON spec if any array key is not an integer
    418                                 * we must treat the the whole array as an object. We
    419                                 * also try to catch a sparsely populated associative
    420                                 * array with numeric keys here because some JS engines
    421                                 * will create an array with empty indexes up to
    422                                 * max_index which can cause memory issues and because
    423                                 * the keys, which may be relevant, will be remapped
    424                                 * otherwise.
    425                                 *
    426                                 * As per the ECMA and JSON specification an object may
    427                                 * have any string as a property. Unfortunately due to
    428                                 * a hole in the ECMA specification if the key is a
    429                                 * ECMA reserved word or starts with a digit the
    430                                 * parameter is only accessible using ECMAScript's
    431                                 * bracket notation.
    432                                 */
    433 
    434                                 // treat as a JSON object
    435                                 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
    436                                         $properties = array_map(array($this, 'name_value'),
    437                                                                                         array_keys($var),
    438                                                                                         array_values($var));
    439 
    440                                         foreach($properties as $property) {
    441                                                 if(Services_JSON::isError($property)) {
    442                                                         return $property;
    443                                                 }
    444                                         }
    445 
    446                                         return '{' . join(',', $properties) . '}';
    447                                 }
    448 
    449                                 // treat it like a regular array
    450                                 $elements = array_map(array($this, '_encode'), $var);
    451 
    452                                 foreach($elements as $element) {
    453                                         if(Services_JSON::isError($element)) {
    454                                                 return $element;
    455                                         }
    456                                 }
    457 
    458                                 return '[' . join(',', $elements) . ']';
    459 
    460                         case 'object':
    461                                 $vars = get_object_vars($var);
    462 
    463                                 $properties = array_map(array($this, 'name_value'),
    464                                                                                 array_keys($vars),
    465                                                                                 array_values($vars));
    466 
    467                                 foreach($properties as $property) {
    468                                         if(Services_JSON::isError($property)) {
    469                                                 return $property;
    470                                         }
    471                                 }
    472 
    473                                 return '{' . join(',', $properties) . '}';
    474 
    475                         default:
    476                                 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
    477                                         ? 'null'
    478                                         : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
    479                 }
    480         }
    481 
    482  /**
    483         * array-walking function for use in generating JSON-formatted name-value pairs
    484         *
    485         * @param        string  $name name of key to use
    486         * @param        mixed $value  reference to an array element to be encoded
    487         *
    488         * @return string  JSON-formatted name-value pair, like '"name":value'
    489         * @access private
    490         */
    491         function name_value($name, $value)
    492         {
    493                 $encoded_value = $this->_encode($value);
    494 
    495                 if(Services_JSON::isError($encoded_value)) {
    496                         return $encoded_value;
    497                 }
    498 
    499                 return $this->_encode(strval($name)) . ':' . $encoded_value;
    500         }
    501 
    502  /**
    503         * reduce a string by removing leading and trailing comments and whitespace
    504         *
    505         * @param        $str    string  string value to strip of comments and whitespace
    506         *
    507         * @return string  string value stripped of comments and whitespace
    508         * @access private
    509         */
    510         function reduce_string($str)
    511         {
    512                 $str = preg_replace(array(
    513 
    514                                 // eliminate single line comments in '// ...' form
    515                                 '#^\s*//(.+)$#m',
    516 
    517                                 // eliminate multi-line comments in '/* ... */' form, at start of string
    518                                 '#^\s*/\*(.+)\*/#Us',
    519 
    520                                 // eliminate multi-line comments in '/* ... */' form, at end of string
    521                                 '#/\*(.+)\*/\s*$#Us'
    522 
    523                         ), '', $str);
    524 
    525                 // eliminate extraneous space
    526                 return trim($str);
    527         }
    528 
    529  /**
    530         * decodes a JSON string into appropriate variable
    531         *
    532         * @param        string  $str    JSON-formatted string
    533         *
    534         * @return mixed number, boolean, string, array, or object
    535         *                               corresponding to given JSON input string.
    536         *                               See argument 1 to Services_JSON() above for object-output behavior.
    537         *                               Note that decode() always returns strings
    538         *                               in ASCII or UTF-8 format!
    539         * @access public
    540         */
    541         function decode($str)
    542         {
    543                 $str = $this->reduce_string($str);
    544 
    545                 switch (strtolower($str)) {
    546                         case 'true':
    547                                 return true;
    548 
    549                         case 'false':
    550                                 return false;
    551 
    552                         case 'null':
    553                                 return null;
    554 
    555                         default:
    556                                 $m = array();
    557 
    558                                 if (is_numeric($str)) {
    559                                         // Lookie-loo, it's a number
    560 
    561                                         // This would work on its own, but I'm trying to be
    562                                         // good about returning integers where appropriate:
    563                                         // return (float)$str;
    564 
    565                                         // Return float or int, as appropriate
    566                                         return ((float)$str == (integer)$str)
    567                                                 ? (integer)$str
    568                                                 : (float)$str;
    569 
    570                                 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
    571                                         // STRINGS RETURNED IN UTF-8 FORMAT
    572                                         $delim = substr($str, 0, 1);
    573                                         $chrs = substr($str, 1, -1);
    574                                         $utf8 = '';
    575                                         $strlen_chrs = strlen($chrs);
    576 
    577                                         for ($c = 0; $c < $strlen_chrs; ++$c) {
    578 
    579                                                 $substr_chrs_c_2 = substr($chrs, $c, 2);
    580                                                 $ord_chrs_c = ord($chrs[$c]);
    581 
    582                                                 switch (true) {
    583                                                         case $substr_chrs_c_2 == '\b':
    584                                                                 $utf8 .= chr(0x08);
    585                                                                 ++$c;
    586                                                                 break;
    587                                                         case $substr_chrs_c_2 == '\t':
    588                                                                 $utf8 .= chr(0x09);
    589                                                                 ++$c;
    590                                                                 break;
    591                                                         case $substr_chrs_c_2 == '\n':
    592                                                                 $utf8 .= chr(0x0A);
    593                                                                 ++$c;
    594                                                                 break;
    595                                                         case $substr_chrs_c_2 == '\f':
    596                                                                 $utf8 .= chr(0x0C);
    597                                                                 ++$c;
    598                                                                 break;
    599                                                         case $substr_chrs_c_2 == '\r':
    600                                                                 $utf8 .= chr(0x0D);
    601                                                                 ++$c;
    602                                                                 break;
    603 
    604                                                         case $substr_chrs_c_2 == '\\"':
    605                                                         case $substr_chrs_c_2 == '\\\'':
    606                                                         case $substr_chrs_c_2 == '\\\\':
    607                                                         case $substr_chrs_c_2 == '\\/':
    608                                                                 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
    609                                                                 ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
    610                                                                         $utf8 .= $chrs[++$c];
    611                                                                 }
    612                                                                 break;
    613 
    614                                                         case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
    615                                                                 // single, escaped unicode character
    616                                                                 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
    617                                                                         . chr(hexdec(substr($chrs, ($c + 4), 2)));
    618                                                                 $utf8 .= $this->utf162utf8($utf16);
    619                                                                 $c += 5;
    620                                                                 break;
    621 
    622                                                         case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
    623                                                                 $utf8 .= $chrs[$c];
    624                                                                 break;
    625 
    626                                                         case ($ord_chrs_c & 0xE0) == 0xC0:
    627                                                                 // characters U-00000080 - U-000007FF, mask 110XXXXX
    628                                                                 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    629                                                                 $utf8 .= substr($chrs, $c, 2);
    630                                                                 ++$c;
    631                                                                 break;
    632 
    633                                                         case ($ord_chrs_c & 0xF0) == 0xE0:
    634                                                                 // characters U-00000800 - U-0000FFFF, mask 1110XXXX
    635                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    636                                                                 $utf8 .= substr($chrs, $c, 3);
    637                                                                 $c += 2;
    638                                                                 break;
    639 
    640                                                         case ($ord_chrs_c & 0xF8) == 0xF0:
    641                                                                 // characters U-00010000 - U-001FFFFF, mask 11110XXX
    642                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    643                                                                 $utf8 .= substr($chrs, $c, 4);
    644                                                                 $c += 3;
    645                                                                 break;
    646 
    647                                                         case ($ord_chrs_c & 0xFC) == 0xF8:
    648                                                                 // characters U-00200000 - U-03FFFFFF, mask 111110XX
    649                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    650                                                                 $utf8 .= substr($chrs, $c, 5);
    651                                                                 $c += 4;
    652                                                                 break;
    653 
    654                                                         case ($ord_chrs_c & 0xFE) == 0xFC:
    655                                                                 // characters U-04000000 - U-7FFFFFFF, mask 1111110X
    656                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
    657                                                                 $utf8 .= substr($chrs, $c, 6);
    658                                                                 $c += 5;
    659                                                                 break;
    660 
    661                                                 }
    662 
    663                                         }
    664 
    665                                         return $utf8;
    666 
    667                                 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
    668                                         // array, or object notation
    669 
    670                                         if ($str[0] == '[') {
    671                                                 $stk = array(SERVICES_JSON_IN_ARR);
    672                                                 $arr = array();
    673                                         } else {
    674                                                 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
    675                                                         $stk = array(SERVICES_JSON_IN_OBJ);
    676                                                         $obj = array();
    677                                                 } else {
    678                                                         $stk = array(SERVICES_JSON_IN_OBJ);
    679                                                         $obj = new stdClass();
    680                                                 }
    681                                         }
    682 
    683                                         array_push($stk, array('what'  => SERVICES_JSON_SLICE,
    684                                                                                 'where' => 0,
    685                                                                                 'delim' => false));
    686 
    687                                         $chrs = substr($str, 1, -1);
    688                                         $chrs = $this->reduce_string($chrs);
    689 
    690                                         if ($chrs == '') {
    691                                                 if (reset($stk) == SERVICES_JSON_IN_ARR) {
    692                                                         return $arr;
    693 
    694                                                 } else {
    695                                                         return $obj;
    696 
    697                                                 }
    698                                         }
    699 
    700                                         //print("\nparsing {$chrs}\n");
    701 
    702                                         $strlen_chrs = strlen($chrs);
    703 
    704                                         for ($c = 0; $c <= $strlen_chrs; ++$c) {
    705 
    706                                                 $top = end($stk);
    707                                                 $substr_chrs_c_2 = substr($chrs, $c, 2);
    708 
    709                                                 if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
    710                                                         // found a comma that is not inside a string, array, etc.,
    711                                                         // OR we've reached the end of the character list
    712                                                         $slice = substr($chrs, $top['where'], ($c - $top['where']));
    713                                                         array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
    714                                                         //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
    715 
    716                                                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
    717                                                                 // we are in an array, so just push an element onto the stack
    718                                                                 array_push($arr, $this->decode($slice));
    719 
    720                                                         } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
    721                                                                 // we are in an object, so figure
    722                                                                 // out the property name and set an
    723                                                                 // element in an associative array,
    724                                                                 // for now
    725                                                                 $parts = array();
    726 
    727                                                                 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
    728                                                                         // "name":value pair
    729                                                                         $key = $this->decode($parts[1]);
    730                                                                         $val = $this->decode($parts[2]);
    731 
    732                                                                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
    733                                                                                 $obj[$key] = $val;
    734                                                                         } else {
    735                                                                                 $obj->$key = $val;
    736                                                                         }
    737                                                                 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
    738                                                                         // name:value pair, where name is unquoted
    739                                                                         $key = $parts[1];
    740                                                                         $val = $this->decode($parts[2]);
    741 
    742                                                                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
    743                                                                                 $obj[$key] = $val;
    744                                                                         } else {
    745                                                                                 $obj->$key = $val;
    746                                                                         }
    747                                                                 }
    748 
    749                                                         }
    750 
    751                                                 } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
    752                                                         // found a quote, and we are not inside a string
    753                                                         array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
    754                                                         //print("Found start of string at {$c}\n");
    755 
    756                                                 } elseif (($chrs[$c] == $top['delim']) &&
    757                                                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
    758                                                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
    759                                                         // found a quote, we're in a string, and it's not escaped
    760                                                         // we know that it's not escaped becase there is _not_ an
    761                                                         // odd number of backslashes at the end of the string so far
    762                                                         array_pop($stk);
    763                                                         //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
    764 
    765                                                 } elseif (($chrs[$c] == '[') &&
    766                                                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
    767                                                         // found a left-bracket, and we are in an array, object, or slice
    768                                                         array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
    769                                                         //print("Found start of array at {$c}\n");
    770 
    771                                                 } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
    772                                                         // found a right-bracket, and we're in an array
    773                                                         array_pop($stk);
    774                                                         //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
    775 
    776                                                 } elseif (($chrs[$c] == '{') &&
    777                                                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
    778                                                         // found a left-brace, and we are in an array, object, or slice
    779                                                         array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
    780                                                         //print("Found start of object at {$c}\n");
    781 
    782                                                 } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
    783                                                         // found a right-brace, and we're in an object
    784                                                         array_pop($stk);
    785                                                         //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
    786 
    787                                                 } elseif (($substr_chrs_c_2 == '/*') &&
    788                                                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
    789                                                         // found a comment start, and we are in an array, object, or slice
    790                                                         array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
    791                                                         $c++;
    792                                                         //print("Found start of comment at {$c}\n");
    793 
    794                                                 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
    795                                                         // found a comment end, and we're in one now
    796                                                         array_pop($stk);
    797                                                         $c++;
    798 
    799                                                         for ($i = $top['where']; $i <= $c; ++$i)
    800                                                                 $chrs = substr_replace($chrs, ' ', $i, 1);
    801 
    802                                                         //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
    803 
    804                                                 }
    805 
    806                                         }
    807 
    808                                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
    809                                                 return $arr;
    810 
    811                                         } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
    812                                                 return $obj;
    813 
    814                                         }
    815 
    816                                 }
    817                 }
    818         }
    819 
    820         /**
    821         * @todo Ultimately, this should just call PEAR::isError()
    822         */
    823         function isError($data, $code = null)
    824         {
    825                 if (class_exists('pear')) {
    826                         return PEAR::isError($data, $code);
    827                 } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
    828                                                                 is_subclass_of($data, 'services_json_error'))) {
    829                         return true;
    830                 }
    831 
    832                 return false;
    833         }
     122   /**
     123    * constructs a new JSON instance
     124    *
     125    * @param    int     $use    object behavior flags; combine with boolean-OR
     126    *
     127    *                           possible values:
     128    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
     129    *                                   "{...}" syntax creates associative arrays
     130    *                                   instead of objects in decode().
     131    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
     132    *                                   Values which can't be encoded (e.g. resources)
     133    *                                   appear as NULL instead of throwing errors.
     134    *                                   By default, a deeply-nested resource will
     135    *                                   bubble up with an error, so all return values
     136    *                                   from encode() should be checked with isError()
     137    *                           - SERVICES_JSON_USE_TO_JSON:  call toJSON when serializing objects
     138    *                                   It serializes the return value from the toJSON call rather
     139    *                                   than the object it'self,  toJSON can return associative arrays,
     140    *                                   strings or numbers, if you return an object, make sure it does
     141    *                                   not have a toJSON method, otherwise an error will occur.
     142    */
     143    function Services_JSON($use = 0)
     144    {
     145        $this->use = $use;
     146        $this->_mb_strlen            = function_exists('mb_strlen');
     147        $this->_mb_convert_encoding  = function_exists('mb_convert_encoding');
     148        $this->_mb_substr            = function_exists('mb_substr');
     149    }
     150    // private - cache the mbstring lookup results..
     151    var $_mb_strlen = false;
     152    var $_mb_substr = false;
     153    var $_mb_convert_encoding = false;
     154   
     155   /**
     156    * convert a string from one UTF-16 char to one UTF-8 char
     157    *
     158    * Normally should be handled by mb_convert_encoding, but
     159    * provides a slower PHP-only method for installations
     160    * that lack the multibye string extension.
     161    *
     162    * @param    string  $utf16  UTF-16 character
     163    * @return   string  UTF-8 character
     164    * @access   private
     165    */
     166    function utf162utf8($utf16)
     167    {
     168        // oh please oh please oh please oh please oh please
     169        if($this->_mb_convert_encoding) {
     170            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
     171        }
     172
     173        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
     174
     175        switch(true) {
     176            case ((0x7F & $bytes) == $bytes):
     177                // this case should never be reached, because we are in ASCII range
     178                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     179                return chr(0x7F & $bytes);
     180
     181            case (0x07FF & $bytes) == $bytes:
     182                // return a 2-byte UTF-8 character
     183                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     184                return chr(0xC0 | (($bytes >> 6) & 0x1F))
     185                     . chr(0x80 | ($bytes & 0x3F));
     186
     187            case (0xFFFF & $bytes) == $bytes:
     188                // return a 3-byte UTF-8 character
     189                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     190                return chr(0xE0 | (($bytes >> 12) & 0x0F))
     191                     . chr(0x80 | (($bytes >> 6) & 0x3F))
     192                     . chr(0x80 | ($bytes & 0x3F));
     193        }
     194
     195        // ignoring UTF-32 for now, sorry
     196        return '';
     197    }
     198
     199   /**
     200    * convert a string from one UTF-8 char to one UTF-16 char
     201    *
     202    * Normally should be handled by mb_convert_encoding, but
     203    * provides a slower PHP-only method for installations
     204    * that lack the multibye string extension.
     205    *
     206    * @param    string  $utf8   UTF-8 character
     207    * @return   string  UTF-16 character
     208    * @access   private
     209    */
     210    function utf82utf16($utf8)
     211    {
     212        // oh please oh please oh please oh please oh please
     213        if($this->_mb_convert_encoding) {
     214            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
     215        }
     216
     217        switch($this->strlen8($utf8)) {
     218            case 1:
     219                // this case should never be reached, because we are in ASCII range
     220                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     221                return $utf8;
     222
     223            case 2:
     224                // return a UTF-16 character from a 2-byte UTF-8 char
     225                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     226                return chr(0x07 & (ord($utf8{0}) >> 2))
     227                     . chr((0xC0 & (ord($utf8{0}) << 6))
     228                         | (0x3F & ord($utf8{1})));
     229
     230            case 3:
     231                // return a UTF-16 character from a 3-byte UTF-8 char
     232                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     233                return chr((0xF0 & (ord($utf8{0}) << 4))
     234                         | (0x0F & (ord($utf8{1}) >> 2)))
     235                     . chr((0xC0 & (ord($utf8{1}) << 6))
     236                         | (0x7F & ord($utf8{2})));
     237        }
     238
     239        // ignoring UTF-32 for now, sorry
     240        return '';
     241    }
     242
     243   /**
     244    * encodes an arbitrary variable into JSON format (and sends JSON Header)
     245    *
     246    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
     247    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
     248    *                           if var is a strng, note that encode() always expects it
     249    *                           to be in ASCII or UTF-8 format!
     250    *
     251    * @return   mixed   JSON string representation of input var or an error if a problem occurs
     252    * @access   public
     253    */
     254    function encode($var)
     255    {
     256        header('Content-type: application/json');
     257        return $this->encodeUnsafe($var);
     258    }
     259    /**
     260    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
     261    *
     262    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
     263    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
     264    *                           if var is a strng, note that encode() always expects it
     265    *                           to be in ASCII or UTF-8 format!
     266    *
     267    * @return   mixed   JSON string representation of input var or an error if a problem occurs
     268    * @access   public
     269    */
     270    function encodeUnsafe($var)
     271    {
     272        // see bug #16908 - regarding numeric locale printing
     273        $lc = setlocale(LC_NUMERIC, 0);
     274        setlocale(LC_NUMERIC, 'C');
     275        $ret = $this->_encode($var);
     276        setlocale(LC_NUMERIC, $lc);
     277        return $ret;
     278       
     279    }
     280    /**
     281    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
     282    *
     283    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
     284    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
     285    *                           if var is a strng, note that encode() always expects it
     286    *                           to be in ASCII or UTF-8 format!
     287    *
     288    * @return   mixed   JSON string representation of input var or an error if a problem occurs
     289    * @access   public
     290    */
     291    function _encode($var)
     292    {
     293         
     294        switch (gettype($var)) {
     295            case 'boolean':
     296                return $var ? 'true' : 'false';
     297
     298            case 'NULL':
     299                return 'null';
     300
     301            case 'integer':
     302                return (int) $var;
     303
     304            case 'double':
     305            case 'float':
     306                return  (float) $var;
     307
     308            case 'string':
     309                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
     310                $ascii = '';
     311                $strlen_var = $this->strlen8($var);
     312
     313               /*
     314                * Iterate over every character in the string,
     315                * escaping with a slash or encoding to UTF-8 where necessary
     316                */
     317                for ($c = 0; $c < $strlen_var; ++$c) {
     318
     319                    $ord_var_c = ord($var{$c});
     320
     321                    switch (true) {
     322                        case $ord_var_c == 0x08:
     323                            $ascii .= '\b';
     324                            break;
     325                        case $ord_var_c == 0x09:
     326                            $ascii .= '\t';
     327                            break;
     328                        case $ord_var_c == 0x0A:
     329                            $ascii .= '\n';
     330                            break;
     331                        case $ord_var_c == 0x0C:
     332                            $ascii .= '\f';
     333                            break;
     334                        case $ord_var_c == 0x0D:
     335                            $ascii .= '\r';
     336                            break;
     337
     338                        case $ord_var_c == 0x22:
     339                        case $ord_var_c == 0x2F:
     340                        case $ord_var_c == 0x5C:
     341                            // double quote, slash, slosh
     342                            $ascii .= '\\'.$var{$c};
     343                            break;
     344
     345                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
     346                            // characters U-00000000 - U-0000007F (same as ASCII)
     347                            $ascii .= $var{$c};
     348                            break;
     349
     350                        case (($ord_var_c & 0xE0) == 0xC0):
     351                            // characters U-00000080 - U-000007FF, mask 110XXXXX
     352                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     353                            if ($c+1 >= $strlen_var) {
     354                                $c += 1;
     355                                $ascii .= '?';
     356                                break;
     357                            }
     358                           
     359                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
     360                            $c += 1;
     361                            $utf16 = $this->utf82utf16($char);
     362                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
     363                            break;
     364
     365                        case (($ord_var_c & 0xF0) == 0xE0):
     366                            if ($c+2 >= $strlen_var) {
     367                                $c += 2;
     368                                $ascii .= '?';
     369                                break;
     370                            }
     371                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
     372                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     373                            $char = pack('C*', $ord_var_c,
     374                                         @ord($var{$c + 1}),
     375                                         @ord($var{$c + 2}));
     376                            $c += 2;
     377                            $utf16 = $this->utf82utf16($char);
     378                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
     379                            break;
     380
     381                        case (($ord_var_c & 0xF8) == 0xF0):
     382                            if ($c+3 >= $strlen_var) {
     383                                $c += 3;
     384                                $ascii .= '?';
     385                                break;
     386                            }
     387                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
     388                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     389                            $char = pack('C*', $ord_var_c,
     390                                         ord($var{$c + 1}),
     391                                         ord($var{$c + 2}),
     392                                         ord($var{$c + 3}));
     393                            $c += 3;
     394                            $utf16 = $this->utf82utf16($char);
     395                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
     396                            break;
     397
     398                        case (($ord_var_c & 0xFC) == 0xF8):
     399                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
     400                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     401                            if ($c+4 >= $strlen_var) {
     402                                $c += 4;
     403                                $ascii .= '?';
     404                                break;
     405                            }
     406                            $char = pack('C*', $ord_var_c,
     407                                         ord($var{$c + 1}),
     408                                         ord($var{$c + 2}),
     409                                         ord($var{$c + 3}),
     410                                         ord($var{$c + 4}));
     411                            $c += 4;
     412                            $utf16 = $this->utf82utf16($char);
     413                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
     414                            break;
     415
     416                        case (($ord_var_c & 0xFE) == 0xFC):
     417                        if ($c+5 >= $strlen_var) {
     418                                $c += 5;
     419                                $ascii .= '?';
     420                                break;
     421                            }
     422                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
     423                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     424                            $char = pack('C*', $ord_var_c,
     425                                         ord($var{$c + 1}),
     426                                         ord($var{$c + 2}),
     427                                         ord($var{$c + 3}),
     428                                         ord($var{$c + 4}),
     429                                         ord($var{$c + 5}));
     430                            $c += 5;
     431                            $utf16 = $this->utf82utf16($char);
     432                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
     433                            break;
     434                    }
     435                }
     436                return  '"'.$ascii.'"';
     437
     438            case 'array':
     439               /*
     440                * As per JSON spec if any array key is not an integer
     441                * we must treat the the whole array as an object. We
     442                * also try to catch a sparsely populated associative
     443                * array with numeric keys here because some JS engines
     444                * will create an array with empty indexes up to
     445                * max_index which can cause memory issues and because
     446                * the keys, which may be relevant, will be remapped
     447                * otherwise.
     448                *
     449                * As per the ECMA and JSON specification an object may
     450                * have any string as a property. Unfortunately due to
     451                * a hole in the ECMA specification if the key is a
     452                * ECMA reserved word or starts with a digit the
     453                * parameter is only accessible using ECMAScript's
     454                * bracket notation.
     455                */
     456
     457                // treat as a JSON object
     458                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
     459                    $properties = array_map(array($this, 'name_value'),
     460                                            array_keys($var),
     461                                            array_values($var));
     462
     463                    foreach($properties as $property) {
     464                        if(Services_JSON::isError($property)) {
     465                            return $property;
     466                        }
     467                    }
     468
     469                    return '{' . join(',', $properties) . '}';
     470                }
     471
     472                // treat it like a regular array
     473                $elements = array_map(array($this, '_encode'), $var);
     474
     475                foreach($elements as $element) {
     476                    if(Services_JSON::isError($element)) {
     477                        return $element;
     478                    }
     479                }
     480
     481                return '[' . join(',', $elements) . ']';
     482
     483            case 'object':
     484           
     485                // support toJSON methods.
     486                if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
     487                    // this may end up allowing unlimited recursion
     488                    // so we check the return value to make sure it's not got the same method.
     489                    $recode = $var->toJSON();
     490                   
     491                    if (method_exists($recode, 'toJSON')) {
     492                       
     493                        return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
     494                        ? 'null'
     495                        : new Services_JSON_Error(class_name($var).
     496                            " toJSON returned an object with a toJSON method.");
     497                           
     498                    }
     499                   
     500                    return $this->_encode( $recode );
     501                }
     502               
     503                $vars = get_object_vars($var);
     504               
     505                $properties = array_map(array($this, 'name_value'),
     506                                        array_keys($vars),
     507                                        array_values($vars));
     508
     509                foreach($properties as $property) {
     510                    if(Services_JSON::isError($property)) {
     511                        return $property;
     512                    }
     513                }
     514
     515                return '{' . join(',', $properties) . '}';
     516
     517            default:
     518                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
     519                    ? 'null'
     520                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
     521        }
     522    }
     523
     524   /**
     525    * array-walking function for use in generating JSON-formatted name-value pairs
     526    *
     527    * @param    string  $name   name of key to use
     528    * @param    mixed   $value  reference to an array element to be encoded
     529    *
     530    * @return   string  JSON-formatted name-value pair, like '"name":value'
     531    * @access   private
     532    */
     533    function name_value($name, $value)
     534    {
     535        $encoded_value = $this->_encode($value);
     536
     537        if(Services_JSON::isError($encoded_value)) {
     538            return $encoded_value;
     539        }
     540
     541        return $this->_encode(strval($name)) . ':' . $encoded_value;
     542    }
     543
     544   /**
     545    * reduce a string by removing leading and trailing comments and whitespace
     546    *
     547    * @param    $str    string      string value to strip of comments and whitespace
     548    *
     549    * @return   string  string value stripped of comments and whitespace
     550    * @access   private
     551    */
     552    function reduce_string($str)
     553    {
     554        $str = preg_replace(array(
     555
     556                // eliminate single line comments in '// ...' form
     557                '#^\s*//(.+)$#m',
     558
     559                // eliminate multi-line comments in '/* ... */' form, at start of string
     560                '#^\s*/\*(.+)\*/#Us',
     561
     562                // eliminate multi-line comments in '/* ... */' form, at end of string
     563                '#/\*(.+)\*/\s*$#Us'
     564
     565            ), '', $str);
     566
     567        // eliminate extraneous space
     568        return trim($str);
     569    }
     570
     571   /**
     572    * decodes a JSON string into appropriate variable
     573    *
     574    * @param    string  $str    JSON-formatted string
     575    *
     576    * @return   mixed   number, boolean, string, array, or object
     577    *                   corresponding to given JSON input string.
     578    *                   See argument 1 to Services_JSON() above for object-output behavior.
     579    *                   Note that decode() always returns strings
     580    *                   in ASCII or UTF-8 format!
     581    * @access   public
     582    */
     583    function decode($str)
     584    {
     585        $str = $this->reduce_string($str);
     586
     587        switch (strtolower($str)) {
     588            case 'true':
     589                return true;
     590
     591            case 'false':
     592                return false;
     593
     594            case 'null':
     595                return null;
     596
     597            default:
     598                $m = array();
     599
     600                if (is_numeric($str)) {
     601                    // Lookie-loo, it's a number
     602
     603                    // This would work on its own, but I'm trying to be
     604                    // good about returning integers where appropriate:
     605                    // return (float)$str;
     606
     607                    // Return float or int, as appropriate
     608                    return ((float)$str == (integer)$str)
     609                        ? (integer)$str
     610                        : (float)$str;
     611
     612                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
     613                    // STRINGS RETURNED IN UTF-8 FORMAT
     614                    $delim = $this->substr8($str, 0, 1);
     615                    $chrs = $this->substr8($str, 1, -1);
     616                    $utf8 = '';
     617                    $strlen_chrs = $this->strlen8($chrs);
     618
     619                    for ($c = 0; $c < $strlen_chrs; ++$c) {
     620
     621                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
     622                        $ord_chrs_c = ord($chrs{$c});
     623
     624                        switch (true) {
     625                            case $substr_chrs_c_2 == '\b':
     626                                $utf8 .= chr(0x08);
     627                                ++$c;
     628                                break;
     629                            case $substr_chrs_c_2 == '\t':
     630                                $utf8 .= chr(0x09);
     631                                ++$c;
     632                                break;
     633                            case $substr_chrs_c_2 == '\n':
     634                                $utf8 .= chr(0x0A);
     635                                ++$c;
     636                                break;
     637                            case $substr_chrs_c_2 == '\f':
     638                                $utf8 .= chr(0x0C);
     639                                ++$c;
     640                                break;
     641                            case $substr_chrs_c_2 == '\r':
     642                                $utf8 .= chr(0x0D);
     643                                ++$c;
     644                                break;
     645
     646                            case $substr_chrs_c_2 == '\\"':
     647                            case $substr_chrs_c_2 == '\\\'':
     648                            case $substr_chrs_c_2 == '\\\\':
     649                            case $substr_chrs_c_2 == '\\/':
     650                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
     651                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
     652                                    $utf8 .= $chrs{++$c};
     653                                }
     654                                break;
     655
     656                            case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
     657                                // single, escaped unicode character
     658                                $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
     659                                       . chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
     660                                $utf8 .= $this->utf162utf8($utf16);
     661                                $c += 5;
     662                                break;
     663
     664                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
     665                                $utf8 .= $chrs{$c};
     666                                break;
     667
     668                            case ($ord_chrs_c & 0xE0) == 0xC0:
     669                                // characters U-00000080 - U-000007FF, mask 110XXXXX
     670                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     671                                $utf8 .= $this->substr8($chrs, $c, 2);
     672                                ++$c;
     673                                break;
     674
     675                            case ($ord_chrs_c & 0xF0) == 0xE0:
     676                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
     677                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     678                                $utf8 .= $this->substr8($chrs, $c, 3);
     679                                $c += 2;
     680                                break;
     681
     682                            case ($ord_chrs_c & 0xF8) == 0xF0:
     683                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
     684                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     685                                $utf8 .= $this->substr8($chrs, $c, 4);
     686                                $c += 3;
     687                                break;
     688
     689                            case ($ord_chrs_c & 0xFC) == 0xF8:
     690                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
     691                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     692                                $utf8 .= $this->substr8($chrs, $c, 5);
     693                                $c += 4;
     694                                break;
     695
     696                            case ($ord_chrs_c & 0xFE) == 0xFC:
     697                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
     698                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
     699                                $utf8 .= $this->substr8($chrs, $c, 6);
     700                                $c += 5;
     701                                break;
     702
     703                        }
     704
     705                    }
     706
     707                    return $utf8;
     708
     709                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
     710                    // array, or object notation
     711
     712                    if ($str{0} == '[') {
     713                        $stk = array(SERVICES_JSON_IN_ARR);
     714                        $arr = array();
     715                    } else {
     716                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
     717                            $stk = array(SERVICES_JSON_IN_OBJ);
     718                            $obj = array();
     719                        } else {
     720                            $stk = array(SERVICES_JSON_IN_OBJ);
     721                            $obj = new stdClass();
     722                        }
     723                    }
     724
     725                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
     726                                           'where' => 0,
     727                                           'delim' => false));
     728
     729                    $chrs = $this->substr8($str, 1, -1);
     730                    $chrs = $this->reduce_string($chrs);
     731
     732                    if ($chrs == '') {
     733                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
     734                            return $arr;
     735
     736                        } else {
     737                            return $obj;
     738
     739                        }
     740                    }
     741
     742                    //print("\nparsing {$chrs}\n");
     743
     744                    $strlen_chrs = $this->strlen8($chrs);
     745
     746                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
     747
     748                        $top = end($stk);
     749                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
     750
     751                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
     752                            // found a comma that is not inside a string, array, etc.,
     753                            // OR we've reached the end of the character list
     754                            $slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
     755                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
     756                            //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
     757
     758                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
     759                                // we are in an array, so just push an element onto the stack
     760                                array_push($arr, $this->decode($slice));
     761
     762                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
     763                                // we are in an object, so figure
     764                                // out the property name and set an
     765                                // element in an associative array,
     766                                // for now
     767                                $parts = array();
     768                               
     769                               if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
     770                                      // "name":value pair
     771                                    $key = $this->decode($parts[1]);
     772                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
     773                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
     774                                        $obj[$key] = $val;
     775                                    } else {
     776                                        $obj->$key = $val;
     777                                    }
     778                                } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
     779                                    // name:value pair, where name is unquoted
     780                                    $key = $parts[1];
     781                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
     782
     783                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
     784                                        $obj[$key] = $val;
     785                                    } else {
     786                                        $obj->$key = $val;
     787                                    }
     788                                }
     789
     790                            }
     791
     792                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
     793                            // found a quote, and we are not inside a string
     794                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
     795                            //print("Found start of string at {$c}\n");
     796
     797                        } elseif (($chrs{$c} == $top['delim']) &&
     798                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
     799                                 (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
     800                            // found a quote, we're in a string, and it's not escaped
     801                            // we know that it's not escaped becase there is _not_ an
     802                            // odd number of backslashes at the end of the string so far
     803                            array_pop($stk);
     804                            //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
     805
     806                        } elseif (($chrs{$c} == '[') &&
     807                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
     808                            // found a left-bracket, and we are in an array, object, or slice
     809                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
     810                            //print("Found start of array at {$c}\n");
     811
     812                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
     813                            // found a right-bracket, and we're in an array
     814                            array_pop($stk);
     815                            //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
     816
     817                        } elseif (($chrs{$c} == '{') &&
     818                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
     819                            // found a left-brace, and we are in an array, object, or slice
     820                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
     821                            //print("Found start of object at {$c}\n");
     822
     823                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
     824                            // found a right-brace, and we're in an object
     825                            array_pop($stk);
     826                            //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
     827
     828                        } elseif (($substr_chrs_c_2 == '/*') &&
     829                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
     830                            // found a comment start, and we are in an array, object, or slice
     831                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
     832                            $c++;
     833                            //print("Found start of comment at {$c}\n");
     834
     835                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
     836                            // found a comment end, and we're in one now
     837                            array_pop($stk);
     838                            $c++;
     839
     840                            for ($i = $top['where']; $i <= $c; ++$i)
     841                                $chrs = substr_replace($chrs, ' ', $i, 1);
     842
     843                            //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
     844
     845                        }
     846
     847                    }
     848
     849                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
     850                        return $arr;
     851
     852                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
     853                        return $obj;
     854
     855                    }
     856
     857                }
     858        }
     859    }
     860
     861    /**
     862     * @todo Ultimately, this should just call PEAR::isError()
     863     */
     864    function isError($data, $code = null)
     865    {
     866        if (class_exists('pear')) {
     867            return PEAR::isError($data, $code);
     868        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
     869                                 is_subclass_of($data, 'services_json_error'))) {
     870            return true;
     871        }
     872
     873        return false;
     874    }
     875   
     876    /**
     877    * Calculates length of string in bytes
     878    * @param string
     879    * @return integer length
     880    */
     881    function strlen8( $str )
     882    {
     883        if ( $this->_mb_strlen ) {
     884            return mb_strlen( $str, "8bit" );
     885        }
     886        return strlen( $str );
     887    }
     888   
     889    /**
     890    * Returns part of a string, interpreting $start and $length as number of bytes.
     891    * @param string
     892    * @param integer start
     893    * @param integer length
     894    * @return integer length
     895    */
     896    function substr8( $string, $start, $length=false )
     897    {
     898        if ( $length === false ) {
     899            $length = $this->strlen8( $string ) - $start;
     900        }
     901        if ( $this->_mb_substr ) {
     902            return mb_substr( $string, $start, $length, "8bit" );
     903        }
     904        return substr( $string, $start, $length );
     905    }
     906
    834907}
    835908
    836909if (class_exists('PEAR_Error')) {
    837910
    838         class Services_JSON_Error extends PEAR_Error
    839         {
    840                 function Services_JSON_Error($message = 'unknown error', $code = null,
    841                                                                         $mode = null, $options = null, $userinfo = null)
    842                 {
    843                         parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
    844                 }
    845         }
     911    class Services_JSON_Error extends PEAR_Error
     912    {
     913        function Services_JSON_Error($message = 'unknown error', $code = null,
     914                                     $mode = null, $options = null, $userinfo = null)
     915        {
     916            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
     917        }
     918    }
    846919
    847920} else {
    848921
    849         /**
    850         * @todo Ultimately, this class shall be descended from PEAR_Error
    851         */
    852         class Services_JSON_Error
    853         {
    854                 function Services_JSON_Error($message = 'unknown error', $code = null,
    855                                                                         $mode = null, $options = null, $userinfo = null)
    856                 {
    857 
    858                 }
    859         }
    860 
     922    /**
     923     * @todo Ultimately, this class shall be descended from PEAR_Error
     924     */
     925    class Services_JSON_Error
     926    {
     927        function Services_JSON_Error($message = 'unknown error', $code = null,
     928                                     $mode = null, $options = null, $userinfo = null)
     929        {
     930
     931        }
     932    }
     933   
    861934}
    862 endif;
     935
     936endif;
     937 No newline at end of file