[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-json.php (source)

   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 complex 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 multibyte 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 string, 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 string, 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 string, 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 because 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;


Generated: Wed Jul 24 01:00:02 2019 Cross-referenced by PHPXref 0.7.1