home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-json.php < prev    next >
Encoding:
PHP Script  |  2015-12-06  |  39.5 KB  |  961 lines

  1. <?php
  2. if ( ! class_exists( 'Services_JSON' ) ) :
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4. /**
  5.  * Converts to and from JSON format.
  6.  *
  7.  * JSON (JavaScript Object Notation) is a lightweight data-interchange
  8.  * format. It is easy for humans to read and write. It is easy for machines
  9.  * to parse and generate. It is based on a subset of the JavaScript
  10.  * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
  11.  * This feature can also be found in  Python. JSON is a text format that is
  12.  * completely language independent but uses conventions that are familiar
  13.  * to programmers of the C-family of languages, including C, C++, C#, Java,
  14.  * JavaScript, Perl, TCL, and many others. These properties make JSON an
  15.  * ideal data-interchange language.
  16.  *
  17.  * This package provides a simple encoder and decoder for JSON notation. It
  18.  * is intended for use with client-side Javascript applications that make
  19.  * use of HTTPRequest to perform server communication functions - data can
  20.  * be encoded into JSON notation for use in a client-side javascript, or
  21.  * decoded from incoming Javascript requests. JSON format is native to
  22.  * Javascript, and can be directly eval()'ed with no further parsing
  23.  * overhead
  24.  *
  25.  * All strings should be in ASCII or UTF-8 format!
  26.  *
  27.  * LICENSE: Redistribution and use in source and binary forms, with or
  28.  * without modification, are permitted provided that the following
  29.  * conditions are met: Redistributions of source code must retain the
  30.  * above copyright notice, this list of conditions and the following
  31.  * disclaimer. Redistributions in binary form must reproduce the above
  32.  * copyright notice, this list of conditions and the following disclaimer
  33.  * in the documentation and/or other materials provided with the
  34.  * distribution.
  35.  *
  36.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  37.  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  38.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  39.  * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  40.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  41.  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  42.  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  44.  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  45.  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  46.  * DAMAGE.
  47.  *
  48.  * @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 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
  57.  */
  58.  
  59. /**
  60.  * Marker constant for Services_JSON::decode(), used to flag stack state
  61.  */
  62. define('SERVICES_JSON_SLICE',   1);
  63.  
  64. /**
  65.  * Marker constant for Services_JSON::decode(), used to flag stack state
  66.  */
  67. define('SERVICES_JSON_IN_STR',  2);
  68.  
  69. /**
  70.  * Marker constant for Services_JSON::decode(), used to flag stack state
  71.  */
  72. define('SERVICES_JSON_IN_ARR',  3);
  73.  
  74. /**
  75.  * Marker constant for Services_JSON::decode(), used to flag stack state
  76.  */
  77. define('SERVICES_JSON_IN_OBJ',  4);
  78.  
  79. /**
  80.  * Marker constant for Services_JSON::decode(), used to flag stack state
  81.  */
  82. define('SERVICES_JSON_IN_CMT', 5);
  83.  
  84. /**
  85.  * Behavior switch for Services_JSON::decode()
  86.  */
  87. define('SERVICES_JSON_LOOSE_TYPE', 16);
  88.  
  89. /**
  90.  * Behavior switch for Services_JSON::decode()
  91.  */
  92. define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
  93.  
  94. /**
  95.  * Behavior switch for Services_JSON::decode()
  96.  */
  97. define('SERVICES_JSON_USE_TO_JSON', 64);
  98.  
  99. /**
  100.  * Converts to and from JSON format.
  101.  *
  102.  * Brief example of use:
  103.  *
  104.  * <code>
  105.  * // create a new instance of Services_JSON
  106.  * $json = new Services_JSON();
  107.  *
  108.  * // convert a complexe value to JSON notation, and send it to the browser
  109.  * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
  110.  * $output = $json->encode($value);
  111.  *
  112.  * print($output);
  113.  * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
  114.  *
  115.  * // accept incoming POST data, assumed to be in JSON notation
  116.  * $input = file_get_contents('php://input', 1000000);
  117.  * $value = $json->decode($input);
  118.  * </code>
  119.  */
  120. class Services_JSON
  121. {
  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 itself, 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 __construct( $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.  
  151.     /**
  152.      * PHP4 constructor.
  153.      */
  154.     public function Services_JSON( $use = 0 ) {
  155.         self::__construct( $use );
  156.     }
  157.     // private - cache the mbstring lookup results..
  158.     var $_mb_strlen = false;
  159.     var $_mb_substr = false;
  160.     var $_mb_convert_encoding = false;
  161.     
  162.    /**
  163.     * convert a string from one UTF-16 char to one UTF-8 char
  164.     *
  165.     * Normally should be handled by mb_convert_encoding, but
  166.     * provides a slower PHP-only method for installations
  167.     * that lack the multibye string extension.
  168.     *
  169.     * @param    string  $utf16  UTF-16 character
  170.     * @return   string  UTF-8 character
  171.     * @access   private
  172.     */
  173.     function utf162utf8($utf16)
  174.     {
  175.         // oh please oh please oh please oh please oh please
  176.         if($this->_mb_convert_encoding) {
  177.             return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
  178.         }
  179.  
  180.         $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
  181.  
  182.         switch(true) {
  183.             case ((0x7F & $bytes) == $bytes):
  184.                 // this case should never be reached, because we are in ASCII range
  185.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  186.                 return chr(0x7F & $bytes);
  187.  
  188.             case (0x07FF & $bytes) == $bytes:
  189.                 // return a 2-byte UTF-8 character
  190.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  191.                 return chr(0xC0 | (($bytes >> 6) & 0x1F))
  192.                      . chr(0x80 | ($bytes & 0x3F));
  193.  
  194.             case (0xFFFF & $bytes) == $bytes:
  195.                 // return a 3-byte UTF-8 character
  196.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  197.                 return chr(0xE0 | (($bytes >> 12) & 0x0F))
  198.                      . chr(0x80 | (($bytes >> 6) & 0x3F))
  199.                      . chr(0x80 | ($bytes & 0x3F));
  200.         }
  201.  
  202.         // ignoring UTF-32 for now, sorry
  203.         return '';
  204.     }
  205.  
  206.    /**
  207.     * convert a string from one UTF-8 char to one UTF-16 char
  208.     *
  209.     * Normally should be handled by mb_convert_encoding, but
  210.     * provides a slower PHP-only method for installations
  211.     * that lack the multibye string extension.
  212.     *
  213.     * @param    string  $utf8   UTF-8 character
  214.     * @return   string  UTF-16 character
  215.     * @access   private
  216.     */
  217.     function utf82utf16($utf8)
  218.     {
  219.         // oh please oh please oh please oh please oh please
  220.         if($this->_mb_convert_encoding) {
  221.             return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  222.         }
  223.  
  224.         switch($this->strlen8($utf8)) {
  225.             case 1:
  226.                 // this case should never be reached, because we are in ASCII range
  227.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  228.                 return $utf8;
  229.  
  230.             case 2:
  231.                 // return a UTF-16 character from a 2-byte UTF-8 char
  232.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  233.                 return chr(0x07 & (ord($utf8{0}) >> 2))
  234.                      . chr((0xC0 & (ord($utf8{0}) << 6))
  235.                          | (0x3F & ord($utf8{1})));
  236.  
  237.             case 3:
  238.                 // return a UTF-16 character from a 3-byte UTF-8 char
  239.                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  240.                 return chr((0xF0 & (ord($utf8{0}) << 4))
  241.                          | (0x0F & (ord($utf8{1}) >> 2)))
  242.                      . chr((0xC0 & (ord($utf8{1}) << 6))
  243.                          | (0x7F & ord($utf8{2})));
  244.         }
  245.  
  246.         // ignoring UTF-32 for now, sorry
  247.         return '';
  248.     }
  249.  
  250.    /**
  251.     * encodes an arbitrary variable into JSON format (and sends JSON Header)
  252.     *
  253.     * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
  254.     *                           see argument 1 to Services_JSON() above for array-parsing behavior.
  255.     *                           if var is a strng, note that encode() always expects it
  256.     *                           to be in ASCII or UTF-8 format!
  257.     *
  258.     * @return   mixed   JSON string representation of input var or an error if a problem occurs
  259.     * @access   public
  260.     */
  261.     function encode($var)
  262.     {
  263.         header('Content-type: application/json');
  264.         return $this->encodeUnsafe($var);
  265.     }
  266.     /**
  267.     * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
  268.     *
  269.     * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
  270.     *                           see argument 1 to Services_JSON() above for array-parsing behavior.
  271.     *                           if var is a strng, note that encode() always expects it
  272.     *                           to be in ASCII or UTF-8 format!
  273.     *
  274.     * @return   mixed   JSON string representation of input var or an error if a problem occurs
  275.     * @access   public
  276.     */
  277.     function encodeUnsafe($var)
  278.     {
  279.         // see bug #16908 - regarding numeric locale printing
  280.         $lc = setlocale(LC_NUMERIC, 0);
  281.         setlocale(LC_NUMERIC, 'C');
  282.         $ret = $this->_encode($var);
  283.         setlocale(LC_NUMERIC, $lc);
  284.         return $ret;
  285.         
  286.     }
  287.     /**
  288.     * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
  289.     *
  290.     * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
  291.     *                           see argument 1 to Services_JSON() above for array-parsing behavior.
  292.     *                           if var is a strng, note that encode() always expects it
  293.     *                           to be in ASCII or UTF-8 format!
  294.     *
  295.     * @return   mixed   JSON string representation of input var or an error if a problem occurs
  296.     * @access   public
  297.     */
  298.     function _encode($var) 
  299.     {
  300.          
  301.         switch (gettype($var)) {
  302.             case 'boolean':
  303.                 return $var ? 'true' : 'false';
  304.  
  305.             case 'NULL':
  306.                 return 'null';
  307.  
  308.             case 'integer':
  309.                 return (int) $var;
  310.  
  311.             case 'double':
  312.             case 'float':
  313.                 return  (float) $var;
  314.  
  315.             case 'string':
  316.                 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
  317.                 $ascii = '';
  318.                 $strlen_var = $this->strlen8($var);
  319.  
  320.                /*
  321.                 * Iterate over every character in the string,
  322.                 * escaping with a slash or encoding to UTF-8 where necessary
  323.                 */
  324.                 for ($c = 0; $c < $strlen_var; ++$c) {
  325.  
  326.                     $ord_var_c = ord($var{$c});
  327.  
  328.                     switch (true) {
  329.                         case $ord_var_c == 0x08:
  330.                             $ascii .= '\b';
  331.                             break;
  332.                         case $ord_var_c == 0x09:
  333.                             $ascii .= '\t';
  334.                             break;
  335.                         case $ord_var_c == 0x0A:
  336.                             $ascii .= '\n';
  337.                             break;
  338.                         case $ord_var_c == 0x0C:
  339.                             $ascii .= '\f';
  340.                             break;
  341.                         case $ord_var_c == 0x0D:
  342.                             $ascii .= '\r';
  343.                             break;
  344.  
  345.                         case $ord_var_c == 0x22:
  346.                         case $ord_var_c == 0x2F:
  347.                         case $ord_var_c == 0x5C:
  348.                             // double quote, slash, slosh
  349.                             $ascii .= '\\'.$var{$c};
  350.                             break;
  351.  
  352.                         case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
  353.                             // characters U-00000000 - U-0000007F (same as ASCII)
  354.                             $ascii .= $var{$c};
  355.                             break;
  356.  
  357.                         case (($ord_var_c & 0xE0) == 0xC0):
  358.                             // characters U-00000080 - U-000007FF, mask 110XXXXX
  359.                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  360.                             if ($c+1 >= $strlen_var) {
  361.                                 $c += 1;
  362.                                 $ascii .= '?';
  363.                                 break;
  364.                             }
  365.                             
  366.                             $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
  367.                             $c += 1;
  368.                             $utf16 = $this->utf82utf16($char);
  369.                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
  370.                             break;
  371.  
  372.                         case (($ord_var_c & 0xF0) == 0xE0):
  373.                             if ($c+2 >= $strlen_var) {
  374.                                 $c += 2;
  375.                                 $ascii .= '?';
  376.                                 break;
  377.                             }
  378.                             // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  379.                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  380.                             $char = pack('C*', $ord_var_c,
  381.                                          @ord($var{$c + 1}),
  382.                                          @ord($var{$c + 2}));
  383.                             $c += 2;
  384.                             $utf16 = $this->utf82utf16($char);
  385.                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
  386.                             break;
  387.  
  388.                         case (($ord_var_c & 0xF8) == 0xF0):
  389.                             if ($c+3 >= $strlen_var) {
  390.                                 $c += 3;
  391.                                 $ascii .= '?';
  392.                                 break;
  393.                             }
  394.                             // characters U-00010000 - U-001FFFFF, mask 11110XXX
  395.                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  396.                             $char = pack('C*', $ord_var_c,
  397.                                          ord($var{$c + 1}),
  398.                                          ord($var{$c + 2}),
  399.                                          ord($var{$c + 3}));
  400.                             $c += 3;
  401.                             $utf16 = $this->utf82utf16($char);
  402.                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
  403.                             break;
  404.  
  405.                         case (($ord_var_c & 0xFC) == 0xF8):
  406.                             // characters U-00200000 - U-03FFFFFF, mask 111110XX
  407.                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  408.                             if ($c+4 >= $strlen_var) {
  409.                                 $c += 4;
  410.                                 $ascii .= '?';
  411.                                 break;
  412.                             }
  413.                             $char = pack('C*', $ord_var_c,
  414.                                          ord($var{$c + 1}),
  415.                                          ord($var{$c + 2}),
  416.                                          ord($var{$c + 3}),
  417.                                          ord($var{$c + 4}));
  418.                             $c += 4;
  419.                             $utf16 = $this->utf82utf16($char);
  420.                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
  421.                             break;
  422.  
  423.                         case (($ord_var_c & 0xFE) == 0xFC):
  424.                         if ($c+5 >= $strlen_var) {
  425.                                 $c += 5;
  426.                                 $ascii .= '?';
  427.                                 break;
  428.                             }
  429.                             // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  430.                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  431.                             $char = pack('C*', $ord_var_c,
  432.                                          ord($var{$c + 1}),
  433.                                          ord($var{$c + 2}),
  434.                                          ord($var{$c + 3}),
  435.                                          ord($var{$c + 4}),
  436.                                          ord($var{$c + 5}));
  437.                             $c += 5;
  438.                             $utf16 = $this->utf82utf16($char);
  439.                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
  440.                             break;
  441.                     }
  442.                 }
  443.                 return  '"'.$ascii.'"';
  444.  
  445.             case 'array':
  446.                /*
  447.                 * As per JSON spec if any array key is not an integer
  448.                 * we must treat the whole array as an object. We
  449.                 * also try to catch a sparsely populated associative
  450.                 * array with numeric keys here because some JS engines
  451.                 * will create an array with empty indexes up to
  452.                 * max_index which can cause memory issues and because
  453.                 * the keys, which may be relevant, will be remapped
  454.                 * otherwise.
  455.                 *
  456.                 * As per the ECMA and JSON specification an object may
  457.                 * have any string as a property. Unfortunately due to
  458.                 * a hole in the ECMA specification if the key is a
  459.                 * ECMA reserved word or starts with a digit the
  460.                 * parameter is only accessible using ECMAScript's
  461.                 * bracket notation.
  462.                 */
  463.  
  464.                 // treat as a JSON object
  465.                 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
  466.                     $properties = array_map(array($this, 'name_value'),
  467.                                             array_keys($var),
  468.                                             array_values($var));
  469.  
  470.                     foreach($properties as $property) {
  471.                         if(Services_JSON::isError($property)) {
  472.                             return $property;
  473.                         }
  474.                     }
  475.  
  476.                     return '{' . join(',', $properties) . '}';
  477.                 }
  478.  
  479.                 // treat it like a regular array
  480.                 $elements = array_map(array($this, '_encode'), $var);
  481.  
  482.                 foreach($elements as $element) {
  483.                     if(Services_JSON::isError($element)) {
  484.                         return $element;
  485.                     }
  486.                 }
  487.  
  488.                 return '[' . join(',', $elements) . ']';
  489.  
  490.             case 'object':
  491.             
  492.                 // support toJSON methods.
  493.                 if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
  494.                     // this may end up allowing unlimited recursion
  495.                     // so we check the return value to make sure it's not got the same method.
  496.                     $recode = $var->toJSON();
  497.                     
  498.                     if (method_exists($recode, 'toJSON')) {
  499.                         
  500.                         return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
  501.                         ? 'null'
  502.                         : new Services_JSON_Error(get_class($var).
  503.                             " toJSON returned an object with a toJSON method.");
  504.                             
  505.                     }
  506.                     
  507.                     return $this->_encode( $recode );
  508.                 } 
  509.                 
  510.                 $vars = get_object_vars($var);
  511.                 
  512.                 $properties = array_map(array($this, 'name_value'),
  513.                                         array_keys($vars),
  514.                                         array_values($vars));
  515.  
  516.                 foreach($properties as $property) {
  517.                     if(Services_JSON::isError($property)) {
  518.                         return $property;
  519.                     }
  520.                 }
  521.  
  522.                 return '{' . join(',', $properties) . '}';
  523.  
  524.             default:
  525.                 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
  526.                     ? 'null'
  527.                     : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
  528.         }
  529.     }
  530.  
  531.    /**
  532.     * array-walking function for use in generating JSON-formatted name-value pairs
  533.     *
  534.     * @param    string  $name   name of key to use
  535.     * @param    mixed   $value  reference to an array element to be encoded
  536.     *
  537.     * @return   string  JSON-formatted name-value pair, like '"name":value'
  538.     * @access   private
  539.     */
  540.     function name_value($name, $value)
  541.     {
  542.         $encoded_value = $this->_encode($value);
  543.  
  544.         if(Services_JSON::isError($encoded_value)) {
  545.             return $encoded_value;
  546.         }
  547.  
  548.         return $this->_encode(strval($name)) . ':' . $encoded_value;
  549.     }
  550.  
  551.    /**
  552.     * reduce a string by removing leading and trailing comments and whitespace
  553.     *
  554.     * @param    $str    string      string value to strip of comments and whitespace
  555.     *
  556.     * @return   string  string value stripped of comments and whitespace
  557.     * @access   private
  558.     */
  559.     function reduce_string($str)
  560.     {
  561.         $str = preg_replace(array(
  562.  
  563.                 // eliminate single line comments in '// ...' form
  564.                 '#^\s*//(.+)$#m',
  565.  
  566.                 // eliminate multi-line comments in '/* ... */' form, at start of string
  567.                 '#^\s*/\*(.+)\*/#Us',
  568.  
  569.                 // eliminate multi-line comments in '/* ... */' form, at end of string
  570.                 '#/\*(.+)\*/\s*$#Us'
  571.  
  572.             ), '', $str);
  573.  
  574.         // eliminate extraneous space
  575.         return trim($str);
  576.     }
  577.  
  578.    /**
  579.     * decodes a JSON string into appropriate variable
  580.     *
  581.     * @param    string  $str    JSON-formatted string
  582.     *
  583.     * @return   mixed   number, boolean, string, array, or object
  584.     *                   corresponding to given JSON input string.
  585.     *                   See argument 1 to Services_JSON() above for object-output behavior.
  586.     *                   Note that decode() always returns strings
  587.     *                   in ASCII or UTF-8 format!
  588.     * @access   public
  589.     */
  590.     function decode($str)
  591.     {
  592.         $str = $this->reduce_string($str);
  593.  
  594.         switch (strtolower($str)) {
  595.             case 'true':
  596.                 return true;
  597.  
  598.             case 'false':
  599.                 return false;
  600.  
  601.             case 'null':
  602.                 return null;
  603.  
  604.             default:
  605.                 $m = array();
  606.  
  607.                 if (is_numeric($str)) {
  608.                     // Lookie-loo, it's a number
  609.  
  610.                     // This would work on its own, but I'm trying to be
  611.                     // good about returning integers where appropriate:
  612.                     // return (float)$str;
  613.  
  614.                     // Return float or int, as appropriate
  615.                     return ((float)$str == (integer)$str)
  616.                         ? (integer)$str
  617.                         : (float)$str;
  618.  
  619.                 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
  620.                     // STRINGS RETURNED IN UTF-8 FORMAT
  621.                     $delim = $this->substr8($str, 0, 1);
  622.                     $chrs = $this->substr8($str, 1, -1);
  623.                     $utf8 = '';
  624.                     $strlen_chrs = $this->strlen8($chrs);
  625.  
  626.                     for ($c = 0; $c < $strlen_chrs; ++$c) {
  627.  
  628.                         $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
  629.                         $ord_chrs_c = ord($chrs{$c});
  630.  
  631.                         switch (true) {
  632.                             case $substr_chrs_c_2 == '\b':
  633.                                 $utf8 .= chr(0x08);
  634.                                 ++$c;
  635.                                 break;
  636.                             case $substr_chrs_c_2 == '\t':
  637.                                 $utf8 .= chr(0x09);
  638.                                 ++$c;
  639.                                 break;
  640.                             case $substr_chrs_c_2 == '\n':
  641.                                 $utf8 .= chr(0x0A);
  642.                                 ++$c;
  643.                                 break;
  644.                             case $substr_chrs_c_2 == '\f':
  645.                                 $utf8 .= chr(0x0C);
  646.                                 ++$c;
  647.                                 break;
  648.                             case $substr_chrs_c_2 == '\r':
  649.                                 $utf8 .= chr(0x0D);
  650.                                 ++$c;
  651.                                 break;
  652.  
  653.                             case $substr_chrs_c_2 == '\\"':
  654.                             case $substr_chrs_c_2 == '\\\'':
  655.                             case $substr_chrs_c_2 == '\\\\':
  656.                             case $substr_chrs_c_2 == '\\/':
  657.                                 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
  658.                                    ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
  659.                                     $utf8 .= $chrs{++$c};
  660.                                 }
  661.                                 break;
  662.  
  663.                             case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
  664.                                 // single, escaped unicode character
  665.                                 $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
  666.                                        . chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
  667.                                 $utf8 .= $this->utf162utf8($utf16);
  668.                                 $c += 5;
  669.                                 break;
  670.  
  671.                             case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
  672.                                 $utf8 .= $chrs{$c};
  673.                                 break;
  674.  
  675.                             case ($ord_chrs_c & 0xE0) == 0xC0:
  676.                                 // characters U-00000080 - U-000007FF, mask 110XXXXX
  677.                                 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  678.                                 $utf8 .= $this->substr8($chrs, $c, 2);
  679.                                 ++$c;
  680.                                 break;
  681.  
  682.                             case ($ord_chrs_c & 0xF0) == 0xE0:
  683.                                 // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  684.                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  685.                                 $utf8 .= $this->substr8($chrs, $c, 3);
  686.                                 $c += 2;
  687.                                 break;
  688.  
  689.                             case ($ord_chrs_c & 0xF8) == 0xF0:
  690.                                 // characters U-00010000 - U-001FFFFF, mask 11110XXX
  691.                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  692.                                 $utf8 .= $this->substr8($chrs, $c, 4);
  693.                                 $c += 3;
  694.                                 break;
  695.  
  696.                             case ($ord_chrs_c & 0xFC) == 0xF8:
  697.                                 // characters U-00200000 - U-03FFFFFF, mask 111110XX
  698.                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  699.                                 $utf8 .= $this->substr8($chrs, $c, 5);
  700.                                 $c += 4;
  701.                                 break;
  702.  
  703.                             case ($ord_chrs_c & 0xFE) == 0xFC:
  704.                                 // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  705.                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  706.                                 $utf8 .= $this->substr8($chrs, $c, 6);
  707.                                 $c += 5;
  708.                                 break;
  709.  
  710.                         }
  711.  
  712.                     }
  713.  
  714.                     return $utf8;
  715.  
  716.                 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
  717.                     // array, or object notation
  718.  
  719.                     if ($str{0} == '[') {
  720.                         $stk = array(SERVICES_JSON_IN_ARR);
  721.                         $arr = array();
  722.                     } else {
  723.                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  724.                             $stk = array(SERVICES_JSON_IN_OBJ);
  725.                             $obj = array();
  726.                         } else {
  727.                             $stk = array(SERVICES_JSON_IN_OBJ);
  728.                             $obj = new stdClass();
  729.                         }
  730.                     }
  731.  
  732.                     array_push($stk, array('what'  => SERVICES_JSON_SLICE,
  733.                                            'where' => 0,
  734.                                            'delim' => false));
  735.  
  736.                     $chrs = $this->substr8($str, 1, -1);
  737.                     $chrs = $this->reduce_string($chrs);
  738.  
  739.                     if ($chrs == '') {
  740.                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
  741.                             return $arr;
  742.  
  743.                         } else {
  744.                             return $obj;
  745.  
  746.                         }
  747.                     }
  748.  
  749.                     //print("\nparsing {$chrs}\n");
  750.  
  751.                     $strlen_chrs = $this->strlen8($chrs);
  752.  
  753.                     for ($c = 0; $c <= $strlen_chrs; ++$c) {
  754.  
  755.                         $top = end($stk);
  756.                         $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
  757.  
  758.                         if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
  759.                             // found a comma that is not inside a string, array, etc.,
  760.                             // OR we've reached the end of the character list
  761.                             $slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
  762.                             array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
  763.                             //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  764.  
  765.                             if (reset($stk) == SERVICES_JSON_IN_ARR) {
  766.                                 // we are in an array, so just push an element onto the stack
  767.                                 array_push($arr, $this->decode($slice));
  768.  
  769.                             } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  770.                                 // we are in an object, so figure
  771.                                 // out the property name and set an
  772.                                 // element in an associative array,
  773.                                 // for now
  774.                                 $parts = array();
  775.                                 
  776.                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
  777.                                    // "name":value pair
  778.                                     $key = $this->decode($parts[1]);
  779.                                     $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
  780.                                     if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  781.                                         $obj[$key] = $val;
  782.                                     } else {
  783.                                         $obj->$key = $val;
  784.                                     }
  785.                                 } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
  786.                                     // name:value pair, where name is unquoted
  787.                                     $key = $parts[1];
  788.                                     $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
  789.  
  790.                                     if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  791.                                         $obj[$key] = $val;
  792.                                     } else {
  793.                                         $obj->$key = $val;
  794.                                     }
  795.                                 }
  796.  
  797.                             }
  798.  
  799.                         } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
  800.                             // found a quote, and we are not inside a string
  801.                             array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
  802.                             //print("Found start of string at {$c}\n");
  803.  
  804.                         } elseif (($chrs{$c} == $top['delim']) &&
  805.                                  ($top['what'] == SERVICES_JSON_IN_STR) &&
  806.                                  (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
  807.                             // found a quote, we're in a string, and it's not escaped
  808.                             // we know that it's not escaped becase there is _not_ an
  809.                             // odd number of backslashes at the end of the string so far
  810.                             array_pop($stk);
  811.                             //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
  812.  
  813.                         } elseif (($chrs{$c} == '[') &&
  814.                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  815.                             // found a left-bracket, and we are in an array, object, or slice
  816.                             array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
  817.                             //print("Found start of array at {$c}\n");
  818.  
  819.                         } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
  820.                             // found a right-bracket, and we're in an array
  821.                             array_pop($stk);
  822.                             //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  823.  
  824.                         } elseif (($chrs{$c} == '{') &&
  825.                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  826.                             // found a left-brace, and we are in an array, object, or slice
  827.                             array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
  828.                             //print("Found start of object at {$c}\n");
  829.  
  830.                         } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
  831.                             // found a right-brace, and we're in an object
  832.                             array_pop($stk);
  833.                             //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  834.  
  835.                         } elseif (($substr_chrs_c_2 == '/*') &&
  836.                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  837.                             // found a comment start, and we are in an array, object, or slice
  838.                             array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
  839.                             $c++;
  840.                             //print("Found start of comment at {$c}\n");
  841.  
  842.                         } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
  843.                             // found a comment end, and we're in one now
  844.                             array_pop($stk);
  845.                             $c++;
  846.  
  847.                             for ($i = $top['where']; $i <= $c; ++$i)
  848.                                 $chrs = substr_replace($chrs, ' ', $i, 1);
  849.  
  850.                             //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  851.  
  852.                         }
  853.  
  854.                     }
  855.  
  856.                     if (reset($stk) == SERVICES_JSON_IN_ARR) {
  857.                         return $arr;
  858.  
  859.                     } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  860.                         return $obj;
  861.  
  862.                     }
  863.  
  864.                 }
  865.         }
  866.     }
  867.  
  868.     /**
  869.      * @todo Ultimately, this should just call PEAR::isError()
  870.      */
  871.     function isError($data, $code = null)
  872.     {
  873.         if (class_exists('pear')) {
  874.             return PEAR::isError($data, $code);
  875.         } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
  876.                                  is_subclass_of($data, 'services_json_error'))) {
  877.             return true;
  878.         }
  879.  
  880.         return false;
  881.     }
  882.     
  883.     /**
  884.     * Calculates length of string in bytes
  885.     * @param string 
  886.     * @return integer length
  887.     */
  888.     function strlen8( $str ) 
  889.     {
  890.         if ( $this->_mb_strlen ) {
  891.             return mb_strlen( $str, "8bit" );
  892.         }
  893.         return strlen( $str );
  894.     }
  895.     
  896.     /**
  897.     * Returns part of a string, interpreting $start and $length as number of bytes.
  898.     * @param string 
  899.     * @param integer start 
  900.     * @param integer length 
  901.     * @return integer length
  902.     */
  903.     function substr8( $string, $start, $length=false ) 
  904.     {
  905.         if ( $length === false ) {
  906.             $length = $this->strlen8( $string ) - $start;
  907.         }
  908.         if ( $this->_mb_substr ) {
  909.             return mb_substr( $string, $start, $length, "8bit" );
  910.         }
  911.         return substr( $string, $start, $length );
  912.     }
  913.  
  914. }
  915.  
  916. if (class_exists('PEAR_Error')) {
  917.  
  918.     class Services_JSON_Error extends PEAR_Error
  919.     {
  920.         function __construct($message = 'unknown error', $code = null,
  921.                                      $mode = null, $options = null, $userinfo = null)
  922.         {
  923.             parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
  924.         }
  925.  
  926.     public function Services_JSON_Error($message = 'unknown error', $code = null,
  927.                                      $mode = null, $options = null, $userinfo = null) {
  928.         self::__construct($message = 'unknown error', $code = null,
  929.                                      $mode = null, $options = null, $userinfo = null);
  930.     }
  931.     }
  932.  
  933. } else {
  934.  
  935.     /**
  936.      * @todo Ultimately, this class shall be descended from PEAR_Error
  937.      */
  938.     class Services_JSON_Error
  939.     {
  940.         /**
  941.          * PHP5 constructor.
  942.          */
  943.         function __construct( $message = 'unknown error', $code = null,
  944.                                      $mode = null, $options = null, $userinfo = null )
  945.         {
  946.  
  947.         }
  948.  
  949.         /**
  950.          * PHP4 constructor.
  951.          */
  952.         public function Services_JSON_Error( $message = 'unknown error', $code = null,
  953.                                          $mode = null, $options = null, $userinfo = null ) {
  954.             self::__construct( $message, $code, $mode, $options, $userinfo );
  955.         }
  956.     }
  957.     
  958. }
  959.  
  960. endif;
  961.