Services_JSON::isError()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 7
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 11
rs 9.6111
1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
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 <[email protected]>
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,v 1.31 2006/06/28 05:54:17 migurski Exp $
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
 * Converts to and from JSON format.
96
 *
97
 * Brief example of use:
98
 *
99
 * <code>
100
 * // create a new instance of Services_JSON
101
 * $json = new Services_JSON();
102
 *
103
 * // convert a complexe value to JSON notation, and send it to the browser
104
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105
 * $output = $json->encode($value);
106
 *
107
 * print($output);
108
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109
 *
110
 * // accept incoming POST data, assumed to be in JSON notation
111
 * $input = file_get_contents('php://input', 1000000);
112
 * $value = $json->decode($input);
113
 * </code>
114
 */
115
class Services_JSON
116
{
117
    /**
118
     * constructs a new JSON instance
119
     *
120
     * @param int $use object behavior flags; combine with boolean-OR
121
     *
122
     *                           possible values:
123
     *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
124
     *                                   "{...}" syntax creates associative arrays
125
     *                                   instead of objects in decode().
126
     *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
127
     *                                   Values which can't be encoded (e.g. resources)
128
     *                                   appear as NULL instead of throwing errors.
129
     *                                   By default, a deeply-nested resource will
130
     *                                   bubble up with an error, so all return values
131
     *                                   from encode() should be checked with isError()
132
     */
133
    public function __construct($use = 0)
134
    {
135
        $this->use = $use;
0 ignored issues
show
Bug Best Practice introduced by
The property use does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
136
    }
137
138
    /**
139
     * convert a string from one UTF-16 char to one UTF-8 char
140
     *
141
     * Normally should be handled by mb_convert_encoding, but
142
     * provides a slower PHP-only method for installations
143
     * that lack the multibye string extension.
144
     *
145
     * @param string $utf16 UTF-16 character
146
     * @return   string  UTF-8 character
147
     * @access   private
148
     */
149
    public function utf162utf8($utf16)
150
    {
151
        // oh please oh please oh please oh please oh please
152
        if (function_exists('mb_convert_encoding')) {
153
            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...f16, 'UTF-8', 'UTF-16') also could return the type array which is incompatible with the documented return type string.
Loading history...
154
        }
155
156
        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
157
158
        switch (true) {
159
            case ((0x7F & $bytes) == $bytes):
160
                // this case should never be reached, because we are in ASCII range
161
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162
                return chr(0x7F & $bytes);
163
164
            case (0x07FF & $bytes) == $bytes:
165
                // return a 2-byte UTF-8 character
166
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167
                return chr(0xC0 | (($bytes >> 6) & 0x1F)) . chr(0x80 | ($bytes & 0x3F));
168
169
            case (0xFFFF & $bytes) == $bytes:
170
                // return a 3-byte UTF-8 character
171
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
172
                return chr(0xE0 | (($bytes >> 12) & 0x0F)) . chr(0x80 | (($bytes >> 6) & 0x3F)) . chr(0x80 | ($bytes & 0x3F));
173
        }
174
175
        // ignoring UTF-32 for now, sorry
176
        return '';
177
    }
178
179
    /**
180
     * convert a string from one UTF-8 char to one UTF-16 char
181
     *
182
     * Normally should be handled by mb_convert_encoding, but
183
     * provides a slower PHP-only method for installations
184
     * that lack the multibye string extension.
185
     *
186
     * @param string $utf8 UTF-8 character
187
     * @return   string  UTF-16 character
188
     * @access   private
189
     */
190
    public function utf82utf16($utf8)
191
    {
192
        // oh please oh please oh please oh please oh please
193
        if (function_exists('mb_convert_encoding')) {
194
            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...tf8, 'UTF-16', 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
195
        }
196
197
        switch (strlen($utf8)) {
198
            case 1:
199
                // this case should never be reached, because we are in ASCII range
200
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
201
                return $utf8;
202
203
            case 2:
204
                // return a UTF-16 character from a 2-byte UTF-8 char
205
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
206
                return chr(0x07 & (ord($utf8{0}) >> 2)) . chr(
207
                        (0xC0 & (ord($utf8{0}) << 6)) | (0x3F & ord($utf8{1}))
208
                    );
209
210
            case 3:
211
                // return a UTF-16 character from a 3-byte UTF-8 char
212
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
213
                return chr(
214
                           (0xF0 & (ord($utf8{0}) << 4)) | (0x0F & (ord($utf8{1}) >> 2))
215
                       ) . chr(
216
                           (0xC0 & (ord($utf8{1}) << 6)) | (0x7F & ord($utf8{2}))
217
                       );
218
        }
219
220
        // ignoring UTF-32 for now, sorry
221
        return '';
222
    }
223
224
    /**
225
     * encodes an arbitrary variable into JSON format
226
     *
227
     * @param mixed $var         any number, boolean, string, array, or object to be encoded.
228
     *                           see argument 1 to Services_JSON() above for array-parsing behavior.
229
     *                           if var is a strng, note that encode() always expects it
230
     *                           to be in ASCII or UTF-8 format!
231
     *
232
     * @return   mixed   JSON string representation of input var or an error if a problem occurs
233
     * @access   public
234
     */
235
    public function encode($var)
236
    {
237
        switch (gettype($var)) {
238
            case 'boolean':
239
                return $var ? 'true' : 'false';
240
241
            case 'NULL':
242
                return 'null';
243
244
            case 'integer':
245
                return (int)$var;
246
247
            case 'double':
248
            case 'float':
249
                return (float)$var;
250
251
            case 'string':
252
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
253
                $ascii      = '';
254
                $strlen_var = strlen($var);
255
256
                /*
257
                 * Iterate over every character in the string,
258
                 * escaping with a slash or encoding to UTF-8 where necessary
259
                 */
260
                for ($c = 0; $c < $strlen_var; ++$c) {
261
                    $ord_var_c = ord($var{$c});
262
263
                    switch (true) {
264
                        case 0x08 == $ord_var_c:
265
                            $ascii .= '\b';
266
                            break;
267
                        case 0x09 == $ord_var_c:
268
                            $ascii .= '\t';
269
                            break;
270
                        case 0x0A == $ord_var_c:
271
                            $ascii .= '\n';
272
                            break;
273
                        case 0x0C == $ord_var_c:
274
                            $ascii .= '\f';
275
                            break;
276
                        case 0x0D == $ord_var_c:
277
                            $ascii .= '\r';
278
                            break;
279
280
                        case 0x22 == $ord_var_c:
281
                        case 0x2F == $ord_var_c:
282
                        case 0x5C == $ord_var_c:
283
                            // double quote, slash, slosh
284
                            $ascii .= '\\' . $var{$c};
285
                            break;
286
287
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
288
                            // characters U-00000000 - U-0000007F (same as ASCII)
289
                            $ascii .= $var{$c};
290
                            break;
291
292
                        case (0xC0 == ($ord_var_c & 0xE0)):
293
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
294
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
295
                            $char  = pack('C*', $ord_var_c, ord($var{$c + 1}));
296
                            $c     += 1;
297
                            $utf16 = $this->utf82utf16($char);
298
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
299
                            break;
300
301
                        case (0xE0 == ($ord_var_c & 0xF0)):
302
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
303
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
304
                            $char  = pack(
305
                                'C*',
306
                                $ord_var_c,
307
                                ord($var{$c + 1}),
308
                                ord($var{$c + 2})
309
                            );
310
                            $c     += 2;
311
                            $utf16 = $this->utf82utf16($char);
312
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
313
                            break;
314
315
                        case (0xF0 == ($ord_var_c & 0xF8)):
316
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
317
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
                            $char  = pack(
319
                                'C*',
320
                                $ord_var_c,
321
                                ord($var{$c + 1}),
322
                                ord($var{$c + 2}),
323
                                ord($var{$c + 3})
324
                            );
325
                            $c     += 3;
326
                            $utf16 = $this->utf82utf16($char);
327
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
328
                            break;
329
330
                        case (0xF8 == ($ord_var_c & 0xFC)):
331
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
332
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
333
                            $char  = pack(
334
                                'C*',
335
                                $ord_var_c,
336
                                ord($var{$c + 1}),
337
                                ord($var{$c + 2}),
338
                                ord($var{$c + 3}),
339
                                ord($var{$c + 4})
340
                            );
341
                            $c     += 4;
342
                            $utf16 = $this->utf82utf16($char);
343
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
344
                            break;
345
346
                        case (0xFC == ($ord_var_c & 0xFE)):
347
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
348
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
349
                            $char  = pack(
350
                                'C*',
351
                                $ord_var_c,
352
                                ord($var{$c + 1}),
353
                                ord($var{$c + 2}),
354
                                ord($var{$c + 3}),
355
                                ord($var{$c + 4}),
356
                                ord($var{$c + 5})
357
                            );
358
                            $c     += 5;
359
                            $utf16 = $this->utf82utf16($char);
360
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
361
                            break;
362
                    }
363
                }
364
365
                return '"' . $ascii . '"';
366
367
            case 'array':
368
                /*
369
                 * As per JSON spec if any array key is not an integer
370
                 * we must treat the the whole array as an object. We
371
                 * also try to catch a sparsely populated associative
372
                 * array with numeric keys here because some JS engines
373
                 * will create an array with empty indexes up to
374
                 * max_index which can cause memory issues and because
375
                 * the keys, which may be relevant, will be remapped
376
                 * otherwise.
377
                 *
378
                 * As per the ECMA and JSON specification an object may
379
                 * have any string as a property. Unfortunately due to
380
                 * a hole in the ECMA specification if the key is a
381
                 * ECMA reserved word or starts with a digit the
382
                 * parameter is only accessible using ECMAScript's
383
                 * bracket notation.
384
                 */
385
386
                // treat as a JSON object
387
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
388
                    $properties = array_map(
389
                        [$this, 'name_value'],
390
                        array_keys($var),
391
                        array_values($var)
392
                    );
393
394
                    foreach ($properties as $property) {
395
                        if (Services_JSON::isError($property)) {
0 ignored issues
show
Bug Best Practice introduced by
The method Services_JSON::isError() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

395
                        if (Services_JSON::/** @scrutinizer ignore-call */ isError($property)) {
Loading history...
396
                            return $property;
397
                        }
398
                    }
399
400
                    return '{' . join(',', $properties) . '}';
401
                }
402
403
                // treat it like a regular array
404
                $elements = array_map([$this, 'encode'], $var);
405
406
                foreach ($elements as $element) {
407
                    if (Services_JSON::isError($element)) {
408
                        return $element;
409
                    }
410
                }
411
412
                return '[' . join(',', $elements) . ']';
413
414
            case 'object':
415
                $vars = get_object_vars($var);
416
417
                $properties = array_map(
418
                    [$this, 'name_value'],
419
                    array_keys($vars),
420
                    array_values($vars)
421
                );
422
423
                foreach ($properties as $property) {
424
                    if (Services_JSON::isError($property)) {
425
                        return $property;
426
                    }
427
                }
428
429
                return '{' . join(',', $properties) . '}';
430
431
            default:
432
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) ? 'null' : new Services_JSON_Error(gettype($var) . ' can not be encoded as JSON string');
433
        }
434
    }
435
436
    /**
437
     * array-walking function for use in generating JSON-formatted name-value pairs
438
     *
439
     * @param string $name  name of key to use
440
     * @param mixed  $value reference to an array element to be encoded
441
     *
442
     * @return   string  JSON-formatted name-value pair, like '"name":value'
443
     * @access   private
444
     */
445
    public function name_value($name, $value)
446
    {
447
        $encoded_value = $this->encode($value);
448
449
        if (Services_JSON::isError($encoded_value)) {
0 ignored issues
show
Bug Best Practice introduced by
The method Services_JSON::isError() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

449
        if (Services_JSON::/** @scrutinizer ignore-call */ isError($encoded_value)) {
Loading history...
450
            return $encoded_value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $encoded_value also could return the type Services_JSON_Error which is incompatible with the documented return type string.
Loading history...
451
        }
452
453
        return $this->encode(strval($name)) . ':' . $encoded_value;
454
    }
455
456
    /**
457
     * reduce a string by removing leading and trailing comments and whitespace
458
     *
459
     * @param    $str    string      string value to strip of comments and whitespace
460
     *
461
     * @return   string  string value stripped of comments and whitespace
462
     * @access   private
463
     */
464
    public function reduce_string($str)
465
    {
466
        $str = preg_replace(
467
            [
468
469
                // eliminate single line comments in '// ...' form
470
                '#^\s*//(.+)$#m',
471
472
                // eliminate multi-line comments in '/* ... */' form, at start of string
473
                '#^\s*/\*(.+)\*/#Us',
474
475
                // eliminate multi-line comments in '/* ... */' form, at end of string
476
                '#/\*(.+)\*/\s*$#Us'
477
478
            ],
479
            '',
480
            $str
481
        );
482
483
        // eliminate extraneous space
484
        return trim($str);
485
    }
486
487
    /**
488
     * decodes a JSON string into appropriate variable
489
     *
490
     * @param string $str JSON-formatted string
491
     *
492
     * @return   mixed   number, boolean, string, array, or object
493
     *                   corresponding to given JSON input string.
494
     *                   See argument 1 to Services_JSON() above for object-output behavior.
495
     *                   Note that decode() always returns strings
496
     *                   in ASCII or UTF-8 format!
497
     * @access   public
498
     */
499
    public function decode($str)
500
    {
501
        $str = $this->reduce_string($str);
502
503
        switch (strtolower($str)) {
504
            case 'true':
505
                return true;
506
507
            case 'false':
508
                return false;
509
510
            case 'null':
511
                return null;
512
513
            default:
514
                $m = [];
515
516
                if (is_numeric($str)) {
517
                    // Lookie-loo, it's a number
518
519
                    // This would work on its own, but I'm trying to be
520
                    // good about returning integers where appropriate:
521
                    // return (float)$str;
522
523
                    // Return float or int, as appropriate
524
                    return ((float)$str == (integer)$str) ? (integer)$str : (float)$str;
525
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
526
                    // STRINGS RETURNED IN UTF-8 FORMAT
527
                    $delim       = substr($str, 0, 1);
528
                    $chrs        = substr($str, 1, -1);
529
                    $utf8        = '';
530
                    $strlen_chrs = strlen($chrs);
531
532
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
533
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
534
                        $ord_chrs_c      = ord($chrs{$c});
535
536
                        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\\u[0-9A-F]..., substr($chrs, $c, 6)) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
537
                            case '\b' == $substr_chrs_c_2:
538
                                $utf8 .= chr(0x08);
539
                                ++$c;
540
                                break;
541
                            case '\t' == $substr_chrs_c_2:
542
                                $utf8 .= chr(0x09);
543
                                ++$c;
544
                                break;
545
                            case '\n' == $substr_chrs_c_2:
546
                                $utf8 .= chr(0x0A);
547
                                ++$c;
548
                                break;
549
                            case '\f' == $substr_chrs_c_2:
550
                                $utf8 .= chr(0x0C);
551
                                ++$c;
552
                                break;
553
                            case '\r' == $substr_chrs_c_2:
554
                                $utf8 .= chr(0x0D);
555
                                ++$c;
556
                                break;
557
558
                            case '\\"' == $substr_chrs_c_2:
559
                            case '\\\'' == $substr_chrs_c_2:
560
                            case '\\\\' == $substr_chrs_c_2:
561
                            case '\\/' == $substr_chrs_c_2:
562
                                if (('"' == $delim && '\\\'' != $substr_chrs_c_2)
563
                                    || ("'" == $delim && '\\"' != $substr_chrs_c_2)) {
564
                                    $utf8 .= $chrs{++$c};
565
                                }
566
                                break;
567
568
                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
569
                                // single, escaped unicode character
570
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) . chr(hexdec(substr($chrs, ($c + 4), 2)));
0 ignored issues
show
Bug introduced by
It seems like hexdec(substr($chrs, $c + 2, 2)) can also be of type double; however, parameter $codepoint of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

570
                                $utf16 = chr(/** @scrutinizer ignore-type */ hexdec(substr($chrs, ($c + 2), 2))) . chr(hexdec(substr($chrs, ($c + 4), 2)));
Loading history...
571
                                $utf8  .= $this->utf162utf8($utf16);
572
                                $c     += 5;
573
                                break;
574
575
                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
576
                                $utf8 .= $chrs{$c};
577
                                break;
578
579
                            case 0xC0 == ($ord_chrs_c & 0xE0):
580
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
581
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
582
                                $utf8 .= substr($chrs, $c, 2);
583
                                ++$c;
584
                                break;
585
586
                            case 0xE0 == ($ord_chrs_c & 0xF0):
587
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
588
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
589
                                $utf8 .= substr($chrs, $c, 3);
590
                                $c    += 2;
591
                                break;
592
593
                            case 0xF0 == ($ord_chrs_c & 0xF8):
594
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
595
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
596
                                $utf8 .= substr($chrs, $c, 4);
597
                                $c    += 3;
598
                                break;
599
600
                            case 0xF8 == ($ord_chrs_c & 0xFC):
601
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
602
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
603
                                $utf8 .= substr($chrs, $c, 5);
604
                                $c    += 4;
605
                                break;
606
607
                            case 0xFC == ($ord_chrs_c & 0xFE):
608
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
609
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
610
                                $utf8 .= substr($chrs, $c, 6);
611
                                $c    += 5;
612
                                break;
613
                        }
614
                    }
615
616
                    return $utf8;
617
                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
618
                    // array, or object notation
619
620
                    if ('[' == $str{0}) {
621
                        $stk = [SERVICES_JSON_IN_ARR];
622
                        $arr = [];
623
                    } else {
624
                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
625
                            $stk = [SERVICES_JSON_IN_OBJ];
626
                            $obj = [];
627
                        } else {
628
                            $stk = [SERVICES_JSON_IN_OBJ];
629
                            $obj = new stdClass();
630
                        }
631
                    }
632
633
                    array_push(
634
                        $stk,
635
                        [
636
                            'what'  => SERVICES_JSON_SLICE,
637
                            'where' => 0,
638
                            'delim' => false
639
                        ]
640
                    );
641
642
                    $chrs = substr($str, 1, -1);
643
                    $chrs = $this->reduce_string($chrs);
644
645
                    if ('' == $chrs) {
646
                        if (SERVICES_JSON_IN_ARR == reset($stk)) {
647
                            return $arr;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $arr does not seem to be defined for all execution paths leading up to this point.
Loading history...
648
                        } else {
649
                            return $obj;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $obj does not seem to be defined for all execution paths leading up to this point.
Loading history...
650
                        }
651
                    }
652
653
                    //print("\nparsing {$chrs}\n");
654
655
                    $strlen_chrs = strlen($chrs);
656
657
                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
658
                        $top             = end($stk);
659
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
660
661
                        if (($c == $strlen_chrs) || ((',' == $chrs{$c}) && (SERVICES_JSON_SLICE == $top['what']))) {
662
                            // found a comma that is not inside a string, array, etc.,
663
                            // OR we've reached the end of the character list
664
                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
665
                            array_push($stk, ['what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false]);
666
                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
667
668
                            if (SERVICES_JSON_IN_ARR == reset($stk)) {
669
                                // we are in an array, so just push an element onto the stack
670
                                array_push($arr, $this->decode($slice));
671
                            } elseif (SERVICES_JSON_IN_OBJ == reset($stk)) {
672
                                // we are in an object, so figure
673
                                // out the property name and set an
674
                                // element in an associative array,
675
                                // for now
676
                                $parts = [];
677
678
                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
679
                                    // "name":value pair
680
                                    $key = $this->decode($parts[1]);
681
                                    $val = $this->decode($parts[2]);
682
683
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
684
                                        $obj[$key] = $val;
685
                                    } else {
686
                                        $obj->$key = $val;
687
                                    }
688
                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
689
                                    // name:value pair, where name is unquoted
690
                                    $key = $parts[1];
691
                                    $val = $this->decode($parts[2]);
692
693
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
694
                                        $obj[$key] = $val;
695
                                    } else {
696
                                        $obj->$key = $val;
697
                                    }
698
                                }
699
                            }
700
                        } elseif ((('"' == $chrs{$c}) || ("'" == $chrs{$c})) && (SERVICES_JSON_IN_STR != $top['what'])) {
701
                            // found a quote, and we are not inside a string
702
                            array_push($stk, ['what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}]);
703
                            //print("Found start of string at {$c}\n");
704
705
                        } elseif (($chrs{$c} == $top['delim'])
706
                                  && (SERVICES_JSON_IN_STR == $top['what'])
707
                                  && (1 != (strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2)) {
708
                            // found a quote, we're in a string, and it's not escaped
709
                            // we know that it's not escaped becase there is _not_ an
710
                            // odd number of backslashes at the end of the string so far
711
                            array_pop($stk);
712
                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
713
714
                        } elseif (('[' == $chrs{$c})
715
                                  && in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
716
                            // found a left-bracket, and we are in an array, object, or slice
717
                            array_push($stk, ['what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false]);
718
                            //print("Found start of array at {$c}\n");
719
720
                        } elseif ((']' == $chrs{$c}) && (SERVICES_JSON_IN_ARR == $top['what'])) {
721
                            // found a right-bracket, and we're in an array
722
                            array_pop($stk);
723
                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
724
725
                        } elseif (('{' == $chrs{$c})
726
                                  && in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
727
                            // found a left-brace, and we are in an array, object, or slice
728
                            array_push($stk, ['what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false]);
729
                            //print("Found start of object at {$c}\n");
730
731
                        } elseif (('}' == $chrs{$c}) && (SERVICES_JSON_IN_OBJ == $top['what'])) {
732
                            // found a right-brace, and we're in an object
733
                            array_pop($stk);
734
                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
735
736
                        } elseif (('/*' == $substr_chrs_c_2)
737
                                  && in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
738
                            // found a comment start, and we are in an array, object, or slice
739
                            array_push($stk, ['what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false]);
740
                            $c++;
741
                            //print("Found start of comment at {$c}\n");
742
743
                        } elseif (('*/' == $substr_chrs_c_2) && (SERVICES_JSON_IN_CMT == $top['what'])) {
744
                            // found a comment end, and we're in one now
745
                            array_pop($stk);
746
                            $c++;
747
748
                            for ($i = $top['where']; $i <= $c; ++$i) {
749
                                $chrs = substr_replace($chrs, ' ', $i, 1);
750
                            }
751
                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
752
753
                        }
754
                    }
755
756
                    if (SERVICES_JSON_IN_ARR == reset($stk)) {
757
                        return $arr;
758
                    } elseif (SERVICES_JSON_IN_OBJ == reset($stk)) {
759
                        return $obj;
760
                    }
761
                }
762
        }
763
    }
764
765
    /**
766
     * @todo Ultimately, this should just call PEAR::isError()
767
     */
768
    public function isError($data, $code = null)
769
    {
770
        if (class_exists('pear')) {
771
            return PEAR::isError($data, $code);
0 ignored issues
show
Bug introduced by
The type PEAR was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
772
        } elseif (is_object($data)
773
                  && ('services_json_error' == get_class($data)
774
                      || is_subclass_of($data, 'services_json_error'))) {
775
            return true;
776
        }
777
778
        return false;
779
    }
780
}
781
782
if (class_exists('PEAR_Error')) {
783
    class Services_JSON_Error extends PEAR_Error
0 ignored issues
show
Bug introduced by
The type PEAR_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
784
    {
785
        public function __construct(
786
            $message = 'unknown error',
787
            $code = null,
788
            $mode = null,
789
            $options = null,
790
            $userinfo = null
791
        ) {
792
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
793
        }
794
    }
795
} else {
796
    /**
797
     * @todo Ultimately, this class shall be descended from PEAR_Error
798
     */
799
    class Services_JSON_Error
800
    {
801
        public function __construct(
802
            $message = 'unknown error',
0 ignored issues
show
Unused Code introduced by
The parameter $message is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

802
            /** @scrutinizer ignore-unused */ $message = 'unknown error',

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
803
            $code = null,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

803
            /** @scrutinizer ignore-unused */ $code = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
804
            $mode = null,
0 ignored issues
show
Unused Code introduced by
The parameter $mode is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

804
            /** @scrutinizer ignore-unused */ $mode = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
805
            $options = null,
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

805
            /** @scrutinizer ignore-unused */ $options = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
806
            $userinfo = null
0 ignored issues
show
Unused Code introduced by
The parameter $userinfo is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

806
            /** @scrutinizer ignore-unused */ $userinfo = null

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
807
        ) {
808
        }
809
    }
810
}
811
812
813