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