Make WordPress Core


Ignore:
Timestamp:
02/02/2013 02:09:01 AM (11 years ago)
Author:
nacin
Message:

Update to Services_JSON 1.0.3. props bpetty. fixes #21568.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/class-json.php

    r22798 r23376  
    11<?php
    2 if ( !class_exists( 'Services_JSON' ) ) :
     2if ( ! class_exists( 'Services_JSON' ) ) :
    33/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
    44/**
     
    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
     
    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/**
     
    9191 */
    9292define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
     93
     94/**
     95 * Behavior switch for Services_JSON::decode()
     96 */
     97define('SERVICES_JSON_USE_TO_JSON', 64);
    9398
    9499/**
     
    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}
     935
    862936endif;
Note: See TracChangeset for help on using the changeset viewer.