Project

General

Profile

JSON.php

fixed - Magh S, 01/03/2021 06:55 PM

 
1
<?php
2
// ensure this file is being included by a parent file
3
if( !defined( '_JEXEC' ) && !defined( '_VALID_MOS' ) ) die( 'Restricted access' );
4
/**
5
 * @version $Id: JSON.php 242 2015-08-19 06:29:26Z soeren $
6
 * @package eXtplorer
7
 *
8
 *
9
 * Converts to and from JSON format.
10
 *
11
 * JSON (JavaScript Object Notation) is a lightweight data-interchange
12
 * format. It is easy for humans to read and write. It is easy for machines
13
 * to parse and generate. It is based on a subset of the JavaScript
14
 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
15
 * This feature can also be found in  Python. JSON is a text format that is
16
 * completely language independent but uses conventions that are familiar
17
 * to programmers of the C-family of languages, including C, C++, C#, Java,
18
 * JavaScript, Perl, TCL, and many others. These properties make JSON an
19
 * ideal data-interchange language.
20
 *
21
 * This package provides a simple encoder and decoder for JSON notation. It
22
 * is intended for use with client-side Javascript applications that make
23
 * use of HTTPRequest to perform server communication functions - data can
24
 * be encoded into JSON notation for use in a client-side javascript, or
25
 * decoded from incoming Javascript requests. JSON format is native to
26
 * Javascript, and can be directly eval()'ed with no further parsing
27
 * overhead
28
 *
29
 * All strings should be in ASCII or UTF-8 format!
30
 *
31
 * LICENSE: Redistribution and use in source and binary forms, with or
32
 * without modification, are permitted provided that the following
33
 * conditions are met: Redistributions of source code must retain the
34
 * above copyright notice, this list of conditions and the following
35
 * disclaimer. Redistributions in binary form must reproduce the above
36
 * copyright notice, this list of conditions and the following disclaimer
37
 * in the documentation and/or other materials provided with the
38
 * distribution.
39
 *
40
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
41
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
42
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
43
 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
44
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
45
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
46
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
48
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
49
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
50
 * DAMAGE.
51
 *
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,v 1.2 2009/03/13 03:43:58 alan_k Exp $
57
 * @license     http://www.opensource.org/licenses/bsd-license.php
58
 * @link        http://pear.php.net/package/Services_JSON
59
 */
60

    
61
/**
62
 * Marker constant for ext_Json::decode(), used to flag stack state
63
 */
64
defined('SERVICES_JSON_SLICE') or define('SERVICES_JSON_SLICE',   1);
65

    
66
/**
67
 * Marker constant for ext_Json::decode(), used to flag stack state
68
 */
69
defined('SERVICES_JSON_IN_STR') or define('SERVICES_JSON_IN_STR',  2);
70

    
71
/**
72
 * Marker constant for ext_Json::decode(), used to flag stack state
73
 */
74
defined('SERVICES_JSON_IN_ARR') or define('SERVICES_JSON_IN_ARR',  3);
75

    
76
/**
77
 * Marker constant for ext_Json::decode(), used to flag stack state
78
 */
79
defined('SERVICES_JSON_IN_OBJ') or define('SERVICES_JSON_IN_OBJ',  4);
80

    
81
/**
82
 * Marker constant for ext_Json::decode(), used to flag stack state
83
 */
84
defined('SERVICES_JSON_IN_CMT') or define('SERVICES_JSON_IN_CMT', 5);
85

    
86
/**
87
 * Behavior switch for ext_Json::decode()
88
 */
89
defined('SERVICES_JSON_LOOSE_TYPE') or define('SERVICES_JSON_LOOSE_TYPE', 16);
90

    
91
/**
92
 * Behavior switch for ext_Json::decode()
93
 */
94
defined('SERVICES_JSON_SUPPRESS_ERRORS') or define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
95

    
96
/**
97
 * Converts to and from JSON format.
98
 *
99
 * Brief example of use:
100
 *
101
 * <code>
102
 * // create a new instance of Services_JSON
103
 * $json = new Services_JSON();
104
 *
105
 * // convert a complexe value to JSON notation, and send it to the browser
106
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
107
 * $output = $json->encode($value);
108
 *
109
 * print($output);
110
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
111
 *
112
 * // accept incoming POST data, assumed to be in JSON notation
113
 * $input = file_get_contents('php://input', 1000000);
114
 * $value = $json->decode($input);
115
 * </code>
116
 */
117
class ext_Json
118
{
119
   /**
120
    * constructs a new JSON instance
121
    *
122
    * @param    int     $use    object behavior flags; combine with boolean-OR
123
    *
124
    *                           possible values:
125
    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
126
    *                                   "{...}" syntax creates associative arrays
127
    *                                   instead of objects in decode().
128
    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
129
    *                                   Values which can't be encoded (e.g. resources)
130
    *                                   appear as NULL instead of throwing errors.
131
    *                                   By default, a deeply-nested resource will
132
    *                                   bubble up with an error, so all return values
133
    *                                   from encode() should be checked with isError()
134
    */
135
    function __construct($use = 0)
136
    {
137
        $this->use = $use;
138
    }
139

    
140
   /**
141
    * convert a string from one UTF-16 char to one UTF-8 char
142
    *
143
    * Normally should be handled by mb_convert_encoding, but
144
    * provides a slower PHP-only method for installations
145
    * that lack the multibye string extension.
146
    *
147
    * @param    string  $utf16  UTF-16 character
148
    * @return   string  UTF-8 character
149
    * @access   private
150
    */
151
    function utf162utf8($utf16)
152
    {
153
        // oh please oh please oh please oh please oh please
154
        if(function_exists('mb_convert_encoding')) {
155
            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
156
        }
157

    
158
        $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
159

    
160
        switch(true) {
161
            case ((0x7F & $bytes) == $bytes):
162
                // this case should never be reached, because we are in ASCII range
163
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
164
                return chr(0x7F & $bytes);
165

    
166
            case (0x07FF & $bytes) == $bytes:
167
                // return a 2-byte UTF-8 character
168
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
169
                return chr(0xC0 | (($bytes >> 6) & 0x1F))
170
                     . chr(0x80 | ($bytes & 0x3F));
171

    
172
            case (0xFFFF & $bytes) == $bytes:
173
                // return a 3-byte UTF-8 character
174
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
175
                return chr(0xE0 | (($bytes >> 12) & 0x0F))
176
                     . chr(0x80 | (($bytes >> 6) & 0x3F))
177
                     . chr(0x80 | ($bytes & 0x3F));
178
        }
179

    
180
        // ignoring UTF-32 for now, sorry
181
        return '';
182
    }
183

    
184
   /**
185
    * convert a string from one UTF-8 char to one UTF-16 char
186
    *
187
    * Normally should be handled by mb_convert_encoding, but
188
    * provides a slower PHP-only method for installations
189
    * that lack the multibye string extension.
190
    *
191
    * @param    string  $utf8   UTF-8 character
192
    * @return   string  UTF-16 character
193
    * @access   private
194
    */
195
    function utf82utf16($utf8)
196
    {
197
        // oh please oh please oh please oh please oh please
198
        if(function_exists('mb_convert_encoding')) {
199
            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
200
        }
201

    
202
        switch(strlen($utf8)) {
203
            case 1:
204
                // this case should never be reached, because we are in ASCII range
205
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
206
                return $utf8;
207

    
208
            case 2:
209
                // return a UTF-16 character from a 2-byte UTF-8 char
210
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
211
                return chr(0x07 & (ord($utf8[0]) >> 2))
212
                     . chr((0xC0 & (ord($utf8[0]) << 6))
213
                         | (0x3F & ord($utf8[1])));
214

    
215
            case 3:
216
                // return a UTF-16 character from a 3-byte UTF-8 char
217
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
218
                return chr((0xF0 & (ord($utf8[0]) << 4))
219
                         | (0x0F & (ord($utf8[1]) >> 2)))
220
                     . chr((0xC0 & (ord($utf8[1]) << 6))
221
                         | (0x7F & ord($utf8[2])));
222
        }
223

    
224
        // ignoring UTF-32 for now, sorry
225
        return '';
226
    }
227

    
228
   /**
229
    * encodes an arbitrary variable into JSON format (and sends JSON Header)
230
    *
231
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
232
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
233
    *                           if var is a strng, note that encode() always expects it
234
    *                           to be in ASCII or UTF-8 format!
235
    *
236
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
237
    * @access   public
238
    */
239
    function encode($var)
240
    {
241
        header('Content-type: application/x-javascript');
242
        return $this->_encode($var);
243
    }
244
    /**
245
    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
246
    *
247
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
248
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
249
    *                           if var is a strng, note that encode() always expects it
250
    *                           to be in ASCII or UTF-8 format!
251
    *
252
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
253
    * @access   public
254
    */
255
    function encodeUnsafe($var)
256
    {
257
        return $this->_encode($var);
258
    }
259
    /**
260
    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
261
    *
262
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
263
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
264
    *                           if var is a strng, note that encode() always expects it
265
    *                           to be in ASCII or UTF-8 format!
266
    *
267
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
268
    * @access   public
269
    */
270
    function _encode($var) 
271
    {
272
         
273
        switch (gettype($var)) {
274
            case 'boolean':
275
                return $var ? 'true' : 'false';
276

    
277
            case 'NULL':
278
                return 'null';
279

    
280
            case 'integer':
281
                return (int) $var;
282

    
283
            case 'double':
284
            case 'float':
285
                return (float) $var;
286

    
287
            case 'string':
288
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
289
                $ascii = '';
290
                $strlen_var = strlen($var);
291

    
292
               /*
293
                * Iterate over every character in the string,
294
                * escaping with a slash or encoding to UTF-8 where necessary
295
                */
296
                for ($c = 0; $c < $strlen_var; ++$c) {
297

    
298
                    $ord_var_c = ord($var[$c]);
299

    
300
                    switch (true) {
301
                        case $ord_var_c == 0x08:
302
                            $ascii .= '\b';
303
                            break;
304
                        case $ord_var_c == 0x09:
305
                            $ascii .= '\t';
306
                            break;
307
                        case $ord_var_c == 0x0A:
308
                            $ascii .= '\n';
309
                            break;
310
                        case $ord_var_c == 0x0C:
311
                            $ascii .= '\f';
312
                            break;
313
                        case $ord_var_c == 0x0D:
314
                            $ascii .= '\r';
315
                            break;
316

    
317
                        case $ord_var_c == 0x22:
318
                        case $ord_var_c == 0x2F:
319
                        case $ord_var_c == 0x5C:
320
                            // double quote, slash, slosh
321
                            $ascii .= '\\'.$var[$c];
322
                            break;
323

    
324
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
325
                            // characters U-00000000 - U-0000007F (same as ASCII)
326
                            $ascii .= $var[$c];
327
                            break;
328

    
329
                        case (($ord_var_c & 0xE0) == 0xC0):
330
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
331
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
332
                            if ($c+1 >= $strlen_var) {
333
                                $c += 1;
334
                                $ascii .= '?';
335
                                break;
336
                            }
337
                            
338
                            $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
339
                            $c += 1;
340
                            $utf16 = $this->utf82utf16($char);
341
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
342
                            break;
343

    
344
                        case (($ord_var_c & 0xF0) == 0xE0):
345
                            if ($c+2 >= $strlen_var) {
346
                                $c += 2;
347
                                $ascii .= '?';
348
                                break;
349
                            }
350
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
351
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
352
                            $char = pack('C*', $ord_var_c,
353
                                         @ord($var[$c + 1]),
354
                                         @ord($var[$c + 2]));
355
                            $c += 2;
356
                            $utf16 = $this->utf82utf16($char);
357
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
358
                            break;
359

    
360
                        case (($ord_var_c & 0xF8) == 0xF0):
361
                            if ($c+3 >= $strlen_var) {
362
                                $c += 3;
363
                                $ascii .= '?';
364
                                break;
365
                            }
366
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
367
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
368
                            $char = pack('C*', $ord_var_c,
369
                                         ord($var[$c + 1]),
370
                                         ord($var[$c + 2]),
371
                                         ord($var[$c + 3]));
372
                            $c += 3;
373
                            $utf16 = $this->utf82utf16($char);
374
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
375
                            break;
376

    
377
                        case (($ord_var_c & 0xFC) == 0xF8):
378
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
379
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
380
                            if ($c+4 >= $strlen_var) {
381
                                $c += 4;
382
                                $ascii .= '?';
383
                                break;
384
                            }
385
                            $char = pack('C*', $ord_var_c,
386
                                         ord($var[$c + 1]),
387
                                         ord($var[$c + 2]),
388
                                         ord($var[$c + 3]),
389
                                         ord($var[$c + 4]));
390
                            $c += 4;
391
                            $utf16 = $this->utf82utf16($char);
392
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
393
                            break;
394

    
395
                        case (($ord_var_c & 0xFE) == 0xFC):
396
                        if ($c+5 >= $strlen_var) {
397
                                $c += 5;
398
                                $ascii .= '?';
399
                                break;
400
                            }
401
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
402
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
403
                            $char = pack('C*', $ord_var_c,
404
                                         ord($var[$c + 1]),
405
                                         ord($var[$c + 2]),
406
                                         ord($var[$c + 3]),
407
                                         ord($var[$c + 4]),
408
                                         ord($var[$c + 5]));
409
                            $c += 5;
410
                            $utf16 = $this->utf82utf16($char);
411
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
412
                            break;
413
                    }
414
                }
415
                return (strpos($ascii, "'") > -1)  ? '"'.$ascii.'"' : "'".$ascii."'";
416

    
417
            case 'array':
418
               /*
419
                * As per JSON spec if any array key is not an integer
420
                * we must treat the the whole array as an object. We
421
                * also try to catch a sparsely populated associative
422
                * array with numeric keys here because some JS engines
423
                * will create an array with empty indexes up to
424
                * max_index which can cause memory issues and because
425
                * the keys, which may be relevant, will be remapped
426
                * otherwise.
427
                *
428
                * As per the ECMA and JSON specification an object may
429
                * have any string as a property. Unfortunately due to
430
                * a hole in the ECMA specification if the key is a
431
                * ECMA reserved word or starts with a digit the
432
                * parameter is only accessible using ECMAScript's
433
                * bracket notation.
434
                */
435

    
436
                // treat as a JSON object
437
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
438
                    $properties = array_map(array($this, 'name_value'),
439
                                            array_keys($var),
440
                                            array_values($var));
441

    
442
                    foreach($properties as $property) {
443
                        if(ext_Json::isError($property)) {
444
                            return $property;
445
                        }
446
                    }
447

    
448
                    return '{' . join(',', $properties) . '}';
449
                }
450

    
451
                // treat it like a regular array
452
                $elements = array_map(array($this, '_encode'), $var);
453

    
454
                foreach($elements as $element) {
455
                    if(ext_Json::isError($element)) {
456
                        return $element;
457
                    }
458
                }
459

    
460
                return '[' . join(',', $elements) . ']';
461

    
462
            case 'object':
463
                $vars = get_object_vars($var);
464

    
465
                $properties = array_map(array($this, 'name_value'),
466
                                        array_keys($vars),
467
                                        array_values($vars));
468

    
469
                foreach($properties as $property) {
470
                    if(ext_Json::isError($property)) {
471
                        return $property;
472
                    }
473
                }
474

    
475
                return '{' . join(',', $properties) . '}';
476

    
477
            default:
478
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
479
                    ? 'null'
480
                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
481
        }
482
    }
483

    
484
   /**
485
    * array-walking function for use in generating JSON-formatted name-value pairs
486
    *
487
    * @param    string  $name   name of key to use
488
    * @param    mixed   $value  reference to an array element to be encoded
489
    *
490
    * @return   string  JSON-formatted name-value pair, like '"name":value'
491
    * @access   private
492
    */
493
    function name_value($name, $value)
494
    {
495
        $encoded_value = $this->_encode($value);
496

    
497
        if(ext_Json::isError($encoded_value)) {
498
            return $encoded_value;
499
        }
500

    
501
        return $this->_encode(strval($name)) . ':' . $encoded_value;
502
    }
503

    
504
   /**
505
    * reduce a string by removing leading and trailing comments and whitespace
506
    *
507
    * @param    $str    string      string value to strip of comments and whitespace
508
    *
509
    * @return   string  string value stripped of comments and whitespace
510
    * @access   private
511
    */
512
    function reduce_string($str)
513
    {
514
        $str = preg_replace(array(
515

    
516
                // eliminate single line comments in '// ...' form
517
                '#^\s*//(.+)$#m',
518

    
519
                // eliminate multi-line comments in '/* ... */' form, at start of string
520
                '#^\s*/\*(.+)\*/#Us',
521

    
522
                // eliminate multi-line comments in '/* ... */' form, at end of string
523
                '#/\*(.+)\*/\s*$#Us'
524

    
525
            ), '', $str);
526

    
527
        // eliminate extraneous space
528
        return trim($str);
529
    }
530

    
531
   /**
532
    * decodes a JSON string into appropriate variable
533
    *
534
    * @param    string  $str    JSON-formatted string
535
    *
536
    * @return   mixed   number, boolean, string, array, or object
537
    *                   corresponding to given JSON input string.
538
    *                   See argument 1 to Services_JSON() above for object-output behavior.
539
    *                   Note that decode() always returns strings
540
    *                   in ASCII or UTF-8 format!
541
    * @access   public
542
    */
543
    function decode($str)
544
    {
545
        $str = $this->reduce_string($str);
546

    
547
        switch (strtolower($str)) {
548
            case 'true':
549
                return true;
550

    
551
            case 'false':
552
                return false;
553

    
554
            case 'null':
555
                return null;
556

    
557
            default:
558
                $m = array();
559

    
560
                if (is_numeric($str)) {
561
                    // Lookie-loo, it's a number
562

    
563
                    // This would work on its own, but I'm trying to be
564
                    // good about returning integers where appropriate:
565
                    // return (float)$str;
566

    
567
                    // Return float or int, as appropriate
568
                    return ((float)$str == (integer)$str)
569
                        ? (integer)$str
570
                        : (float)$str;
571

    
572
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
573
                    // STRINGS RETURNED IN UTF-8 FORMAT
574
                    $delim = substr($str, 0, 1);
575
                    $chrs = substr($str, 1, -1);
576
                    $utf8 = '';
577
                    $strlen_chrs = strlen($chrs);
578

    
579
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
580

    
581
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
582
                        $ord_chrs_c = ord($chrs[$c]);
583

    
584
                        switch (true) {
585
                            case $substr_chrs_c_2 == '\b':
586
                                $utf8 .= chr(0x08);
587
                                ++$c;
588
                                break;
589
                            case $substr_chrs_c_2 == '\t':
590
                                $utf8 .= chr(0x09);
591
                                ++$c;
592
                                break;
593
                            case $substr_chrs_c_2 == '\n':
594
                                $utf8 .= chr(0x0A);
595
                                ++$c;
596
                                break;
597
                            case $substr_chrs_c_2 == '\f':
598
                                $utf8 .= chr(0x0C);
599
                                ++$c;
600
                                break;
601
                            case $substr_chrs_c_2 == '\r':
602
                                $utf8 .= chr(0x0D);
603
                                ++$c;
604
                                break;
605

    
606
                            case $substr_chrs_c_2 == '\\"':
607
                            case $substr_chrs_c_2 == '\\\'':
608
                            case $substr_chrs_c_2 == '\\\\':
609
                            case $substr_chrs_c_2 == '\\/':
610
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
611
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
612
                                    $utf8 .= $chrs[++$c];
613
                                }
614
                                break;
615

    
616
                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
617
                                // single, escaped unicode character
618
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
619
                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
620
                                $utf8 .= $this->utf162utf8($utf16);
621
                                $c += 5;
622
                                break;
623

    
624
                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
625
                                $utf8 .= $chrs[$c];
626
                                break;
627

    
628
                            case ($ord_chrs_c & 0xE0) == 0xC0:
629
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
630
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
631
                                $utf8 .= substr($chrs, $c, 2);
632
                                ++$c;
633
                                break;
634

    
635
                            case ($ord_chrs_c & 0xF0) == 0xE0:
636
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
637
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
638
                                $utf8 .= substr($chrs, $c, 3);
639
                                $c += 2;
640
                                break;
641

    
642
                            case ($ord_chrs_c & 0xF8) == 0xF0:
643
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
644
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
645
                                $utf8 .= substr($chrs, $c, 4);
646
                                $c += 3;
647
                                break;
648

    
649
                            case ($ord_chrs_c & 0xFC) == 0xF8:
650
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
651
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
652
                                $utf8 .= substr($chrs, $c, 5);
653
                                $c += 4;
654
                                break;
655

    
656
                            case ($ord_chrs_c & 0xFE) == 0xFC:
657
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
658
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
659
                                $utf8 .= substr($chrs, $c, 6);
660
                                $c += 5;
661
                                break;
662

    
663
                        }
664

    
665
                    }
666

    
667
                    return $utf8;
668

    
669
                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
670
                    // array, or object notation
671

    
672
                    if ($str[0] == '[') {
673
                        $stk = array(SERVICES_JSON_IN_ARR);
674
                        $arr = array();
675
                    } else {
676
                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
677
                            $stk = array(SERVICES_JSON_IN_OBJ);
678
                            $obj = array();
679
                        } else {
680
                            $stk = array(SERVICES_JSON_IN_OBJ);
681
                            $obj = new stdClass();
682
                        }
683
                    }
684

    
685
                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
686
                                           'where' => 0,
687
                                           'delim' => false));
688

    
689
                    $chrs = substr($str, 1, -1);
690
                    $chrs = $this->reduce_string($chrs);
691

    
692
                    if ($chrs == '') {
693
                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
694
                            return $arr;
695

    
696
                        } else {
697
                            return $obj;
698

    
699
                        }
700
                    }
701

    
702
                    //print("\nparsing {$chrs}\n");
703

    
704
                    $strlen_chrs = strlen($chrs);
705

    
706
                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
707

    
708
                        $top = end($stk);
709
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
710

    
711
                        if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
712
                            // found a comma that is not inside a string, array, etc.,
713
                            // OR we've reached the end of the character list
714
                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
715
                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
716
                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
717

    
718
                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
719
                                // we are in an array, so just push an element onto the stack
720
                                array_push($arr, $this->decode($slice));
721

    
722
                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
723
                                // we are in an object, so figure
724
                                // out the property name and set an
725
                                // element in an associative array,
726
                                // for now
727
                                $parts = array();
728
                                
729
                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
730
                                    // "name":value pair
731
                                    $key = $this->decode($parts[1]);
732
                                    $val = $this->decode($parts[2]);
733

    
734
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
735
                                        $obj[$key] = $val;
736
                                    } else {
737
                                        $obj->$key = $val;
738
                                    }
739
                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
740
                                    // name:value pair, where name is unquoted
741
                                    $key = $parts[1];
742
                                    $val = $this->decode($parts[2]);
743

    
744
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
745
                                        $obj[$key] = $val;
746
                                    } else {
747
                                        $obj->$key = $val;
748
                                    }
749
                                }
750

    
751
                            }
752

    
753
                        } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
754
                            // found a quote, and we are not inside a string
755
                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
756
                            //print("Found start of string at {$c}\n");
757

    
758
                        } elseif (($chrs[$c] == $top['delim']) &&
759
                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
760
                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
761
                            // found a quote, we're in a string, and it's not escaped
762
                            // we know that it's not escaped becase there is _not_ an
763
                            // odd number of backslashes at the end of the string so far
764
                            array_pop($stk);
765
                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
766

    
767
                        } elseif (($chrs[$c] == '[') &&
768
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
769
                            // found a left-bracket, and we are in an array, object, or slice
770
                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
771
                            //print("Found start of array at {$c}\n");
772

    
773
                        } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
774
                            // found a right-bracket, and we're in an array
775
                            array_pop($stk);
776
                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
777

    
778
                        } elseif (($chrs[$c] == '{') &&
779
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
780
                            // found a left-brace, and we are in an array, object, or slice
781
                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
782
                            //print("Found start of object at {$c}\n");
783

    
784
                        } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
785
                            // found a right-brace, and we're in an object
786
                            array_pop($stk);
787
                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
788

    
789
                        } elseif (($substr_chrs_c_2 == '/*') &&
790
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
791
                            // found a comment start, and we are in an array, object, or slice
792
                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
793
                            $c++;
794
                            //print("Found start of comment at {$c}\n");
795

    
796
                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
797
                            // found a comment end, and we're in one now
798
                            array_pop($stk);
799
                            $c++;
800

    
801
                            for ($i = $top['where']; $i <= $c; ++$i)
802
                                $chrs = substr_replace($chrs, ' ', $i, 1);
803

    
804
                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
805

    
806
                        }
807

    
808
                    }
809

    
810
                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
811
                        return $arr;
812

    
813
                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
814
                        return $obj;
815

    
816
                    }
817

    
818
                }
819
        }
820
    }
821

    
822
    /**
823
     * @todo Ultimately, this should just call PEAR::isError()
824
     */
825
    function isError($data, $code = null)
826
    {
827
        if (class_exists('pear')) {
828
            return @PEAR::isError($data, $code);
829
        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
830
                                 is_subclass_of($data, 'services_json_error'))) {
831
            return true;
832
        }
833

    
834
        return false;
835
    }
836
}
837
if( !class_exists('Services_JSON_Error' )) {
838
if (class_exists('PEAR_Error')) {
839

    
840
    class Services_JSON_Error extends PEAR_Error
841
    {
842
        function __construct($message = 'unknown error', $code = null,
843
                                     $mode = null, $options = null, $userinfo = null)
844
        {
845
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
846
        }
847
    }
848

    
849
} else {
850

    
851
    /**
852
     * @todo Ultimately, this class shall be descended from PEAR_Error
853
     */
854
    class Services_JSON_Error
855
    {
856
        function __construct($message = 'unknown error', $code = null,
857
                                     $mode = null, $options = null, $userinfo = null)
858
        {
859

    
860
        }
861
    }
862

    
863
}
864
}