Passed
Pull Request — master (#2)
by Michael
07:08 queued 02:34
created

Services_JSON::encodeUnsafe()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php
2
3
/*
4
Module: Document
5
6
Version: 2.01
7
8
Description: Multilingual Content Module with tags and lists with search functions
9
10
Author: Written by Simon Roberts aka. Wishcraft ([email protected])
11
12
Owner: Chronolabs
13
14
License: See /docs - GPL 2.0
15
*/
16
17
 /*
18
 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
19
 */
20
21
/**
22
 * Marker constant for Services_JSON::decode(), used to flag stack state
23
 */
24
define('SERVICES_JSON_SLICE',   1);
25
26
/**
27
 * Marker constant for Services_JSON::decode(), used to flag stack state
28
 */
29
define('SERVICES_JSON_IN_STR',  2);
30
31
/**
32
 * Marker constant for Services_JSON::decode(), used to flag stack state
33
 */
34
define('SERVICES_JSON_IN_ARR',  3);
35
36
/**
37
 * Marker constant for Services_JSON::decode(), used to flag stack state
38
 */
39
define('SERVICES_JSON_IN_OBJ',  4);
40
41
/**
42
 * Marker constant for Services_JSON::decode(), used to flag stack state
43
 */
44
define('SERVICES_JSON_IN_CMT', 5);
45
46
/**
47
 * Behavior switch for Services_JSON::decode()
48
 */
49
define('SERVICES_JSON_LOOSE_TYPE', 16);
50
51
/**
52
 * Behavior switch for Services_JSON::decode()
53
 */
54
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
55
56
/**
57
 * Converts to and from JSON format.
58
 *
59
 * Brief example of use:
60
 *
61
 * <code>
62
 * // create a new instance of Services_JSON
63
 * $json = new Services_JSON();
64
 *
65
 * // convert a complexe value to JSON notation, and send it to the browser
66
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
67
 * $output = $json->encode($value);
68
 *
69
 * print($output);
70
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
71
 *
72
 * // accept incoming POST data, assumed to be in JSON notation
73
 * $input = file_get_contents('php://input', 1000000);
74
 * $value = $json->decode($input);
75
 * </code>
76
 */
77
class Services_JSON
78
{
79
   /**
80
    * constructs a new JSON instance
81
    *
82
    * @param    int     $use    object behavior flags; combine with boolean-OR
83
    *
84
    *                           possible values:
85
    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
86
    *                                   "{...}" syntax creates associative arrays
87
    *                                   instead of objects in decode().
88
    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
89
    *                                   Values which can't be encoded (e.g. resources)
90
    *                                   appear as NULL instead of throwing errors.
91
    *                                   By default, a deeply-nested resource will
92
    *                                   bubble up with an error, so all return values
93
    *                                   from encode() should be checked with isError()
94
    */
95
    function Services_JSON($use = 0)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
96
    {
97
        $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...
98
    }
99
100
   /**
101
    * convert a string from one UTF-16 char to one UTF-8 char
102
    *
103
    * Normally should be handled by mb_convert_encoding, but
104
    * provides a slower PHP-only method for installations
105
    * that lack the multibye string extension.
106
    *
107
    * @param    string  $utf16  UTF-16 character
108
    * @return   string  UTF-8 character
109
    * @access   private
110
    */
111
    function utf162utf8($utf16)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
112
    {
113
        // oh please oh please oh please oh please oh please
114
        if(function_exists('mb_convert_encoding')) {
115
            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...
116
        }
117
118
        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
119
120
        switch(true) {
121
            case ((0x7F & $bytes) == $bytes):
122
                // this case should never be reached, because we are in ASCII range
123
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
124
                return chr(0x7F & $bytes);
125
126
            case (0x07FF & $bytes) == $bytes:
127
                // return a 2-byte UTF-8 character
128
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
129
                return chr(0xC0 | (($bytes >> 6) & 0x1F))
130
                    .chr(0x80 | ($bytes & 0x3F));
131
132
            case (0xFFFF & $bytes) == $bytes:
133
                // return a 3-byte UTF-8 character
134
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
135
                return chr(0xE0 | (($bytes >> 12) & 0x0F))
136
                    .chr(0x80 | (($bytes >> 6) & 0x3F))
137
                    .chr(0x80 | ($bytes & 0x3F));
138
        }
139
140
        // ignoring UTF-32 for now, sorry
141
        return '';
142
    }
143
144
   /**
145
    * convert a string from one UTF-8 char to one UTF-16 char
146
    *
147
    * Normally should be handled by mb_convert_encoding, but
148
    * provides a slower PHP-only method for installations
149
    * that lack the multibye string extension.
150
    *
151
    * @param    string  $utf8   UTF-8 character
152
    * @return   string  UTF-16 character
153
    * @access   private
154
    */
155
    function utf82utf16($utf8)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
156
    {
157
        // oh please oh please oh please oh please oh please
158
        if(function_exists('mb_convert_encoding')) {
159
            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...
160
        }
161
162
        switch(strlen($utf8)) {
163
            case 1:
164
                // this case should never be reached, because we are in ASCII range
165
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
166
                return $utf8;
167
168
            case 2:
169
                // return a UTF-16 character from a 2-byte UTF-8 char
170
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
171
                return chr(0x07 & (ord($utf8{0}) >> 2))
172
                    .chr((0xC0 & (ord($utf8{0}) << 6))
173
                         | (0x3F & ord($utf8{1})));
174
175
            case 3:
176
                // return a UTF-16 character from a 3-byte UTF-8 char
177
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
178
                return chr((0xF0 & (ord($utf8{0}) << 4))
179
                         | (0x0F & (ord($utf8{1}) >> 2)))
180
                    .chr((0xC0 & (ord($utf8{1}) << 6))
181
                         | (0x7F & ord($utf8{2})));
182
        }
183
184
        // ignoring UTF-32 for now, sorry
185
        return '';
186
    }
187
188
   /**
189
    * encodes an arbitrary variable into JSON format (and sends JSON Header)
190
    *
191
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
192
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
193
    *                           if var is a strng, note that encode() always expects it
194
    *                           to be in ASCII or UTF-8 format!
195
    *
196
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
197
    * @access   public
198
    */
199
    function encode($var)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
200
    {
201
        header('Document-type: application/json');
202
        return $this->encodeUnsafe($var);
203
    }
204
    /**
205
    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
206
    *
207
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
208
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
209
    *                           if var is a strng, note that encode() always expects it
210
    *                           to be in ASCII or UTF-8 format!
211
    *
212
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
213
    * @access   public
214
    */
215
    function encodeUnsafe($var)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
216
    {
217
        // see bug #16908 - regarding numeric locale printing
218
        $lc = setlocale(LC_NUMERIC, 0);
219
        setlocale(LC_NUMERIC, 'C');
220
        $ret = $this->_encode($var);
221
        setlocale(LC_NUMERIC, $lc);
222
        return $ret;
223
        
224
    }
225
    /**
226
    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
227
    *
228
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
229
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
230
    *                           if var is a strng, note that encode() always expects it
231
    *                           to be in ASCII or UTF-8 format!
232
    *
233
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
234
    * @access   public
235
    */
236
    function _encode($var) 
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
237
    {
238
         
239
        switch (gettype($var)) {
240
            case 'boolean':
241
                return $var ? 'true' : 'false';
242
243
            case 'NULL':
244
                return 'null';
245
246
            case 'integer':
247
                return (int) $var;
248
249
            case 'double':
250
            case 'float':
251
                return  (float) $var;
252
253
            case 'string':
254
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255
                $ascii = '';
256
                $strlen_var = strlen($var);
257
258
               /*
259
                * Iterate over every character in the string,
260
                * escaping with a slash or encoding to UTF-8 where necessary
261
                */
262
                for ($c = 0; $c < $strlen_var; ++$c) {
263
264
                    $ord_var_c = ord($var{$c});
265
266
                    switch (true) {
267
                        case $ord_var_c == 0x08:
268
                            $ascii .= '\b';
269
                            break;
270
                        case $ord_var_c == 0x09:
271
                            $ascii .= '\t';
272
                            break;
273
                        case $ord_var_c == 0x0A:
274
                            $ascii .= '\n';
275
                            break;
276
                        case $ord_var_c == 0x0C:
277
                            $ascii .= '\f';
278
                            break;
279
                        case $ord_var_c == 0x0D:
280
                            $ascii .= '\r';
281
                            break;
282
283
                        case $ord_var_c == 0x22:
284
                        case $ord_var_c == 0x2F:
285
                        case $ord_var_c == 0x5C:
286
                            // double quote, slash, slosh
287
                            $ascii .= '\\'.$var{$c};
288
                            break;
289
290
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291
                            // characters U-00000000 - U-0000007F (same as ASCII)
292
                            $ascii .= $var{$c};
293
                            break;
294
295
                        case (($ord_var_c & 0xE0) == 0xC0):
296
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
297
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298
                            if ($c+1 >= $strlen_var) {
299
                                $c += 1;
300
                                $ascii .= '?';
301
                                break;
302
                            }
303
                            
304
                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
305
                            $c += 1;
306
                            $utf16 = $this->utf82utf16($char);
307
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
308
                            break;
309
310
                        case (($ord_var_c & 0xF0) == 0xE0):
311
                            if ($c+2 >= $strlen_var) {
312
                                $c += 2;
313
                                $ascii .= '?';
314
                                break;
315
                            }
316
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
317
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
                            $char = pack('C*', $ord_var_c,
319
                                         @ord($var{$c + 1}),
320
                                         @ord($var{$c + 2}));
321
                            $c += 2;
322
                            $utf16 = $this->utf82utf16($char);
323
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
324
                            break;
325
326
                        case (($ord_var_c & 0xF8) == 0xF0):
327
                            if ($c+3 >= $strlen_var) {
328
                                $c += 3;
329
                                $ascii .= '?';
330
                                break;
331
                            }
332
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
333
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
334
                            $char = pack('C*', $ord_var_c,
335
                                         ord($var{$c + 1}),
336
                                         ord($var{$c + 2}),
337
                                         ord($var{$c + 3}));
338
                            $c += 3;
339
                            $utf16 = $this->utf82utf16($char);
340
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
341
                            break;
342
343
                        case (($ord_var_c & 0xFC) == 0xF8):
344
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
345
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
346
                            if ($c+4 >= $strlen_var) {
347
                                $c += 4;
348
                                $ascii .= '?';
349
                                break;
350
                            }
351
                            $char = pack('C*', $ord_var_c,
352
                                         ord($var{$c + 1}),
353
                                         ord($var{$c + 2}),
354
                                         ord($var{$c + 3}),
355
                                         ord($var{$c + 4}));
356
                            $c += 4;
357
                            $utf16 = $this->utf82utf16($char);
358
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
359
                            break;
360
361
                        case (($ord_var_c & 0xFE) == 0xFC):
362
                        if ($c+5 >= $strlen_var) {
363
                                $c += 5;
364
                                $ascii .= '?';
365
                                break;
366
                            }
367
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
368
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
369
                            $char = pack('C*', $ord_var_c,
370
                                         ord($var{$c + 1}),
371
                                         ord($var{$c + 2}),
372
                                         ord($var{$c + 3}),
373
                                         ord($var{$c + 4}),
374
                                         ord($var{$c + 5}));
375
                            $c += 5;
376
                            $utf16 = $this->utf82utf16($char);
377
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
378
                            break;
379
                    }
380
                }
381
                return  '"'.$ascii.'"';
382
383
            case 'array':
384
               /*
385
                * As per JSON spec if any array key is not an integer
386
                * we must treat the the whole array as an object. We
387
                * also try to catch a sparsely populated associative
388
                * array with numeric keys here because some JS engines
389
                * will create an array with empty indexes up to
390
                * max_index which can cause memory issues and because
391
                * the keys, which may be relevant, will be remapped
392
                * otherwise.
393
                *
394
                * As per the ECMA and JSON specification an object may
395
                * have any string as a property. Unfortunately due to
396
                * a hole in the ECMA specification if the key is a
397
                * ECMA reserved word or starts with a digit the
398
                * parameter is only accessible using ECMAScript's
399
                * bracket notation.
400
                */
401
402
                // treat as a JSON object
403
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
404
                    $properties = array_map(array($this, 'name_value'),
405
                                            array_keys($var),
406
                                            array_values($var));
407
408
                    foreach($properties as $property) {
409
                        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

409
                        if(Services_JSON::/** @scrutinizer ignore-call */ isError($property)) {
Loading history...
410
                            return $property;
411
                        }
412
                    }
413
414
                    return '{'.join(',', $properties).'}';
415
                }
416
417
                // treat it like a regular array
418
                $elements = array_map(array($this, '_encode'), $var);
419
420
                foreach($elements as $element) {
421
                    if(Services_JSON::isError($element)) {
422
                        return $element;
423
                    }
424
                }
425
426
                return '['.join(',', $elements).']';
427
428
            case 'object':
429
                $vars = get_object_vars($var);
430
431
                $properties = array_map(array($this, 'name_value'),
432
                                        array_keys($vars),
433
                                        array_values($vars));
434
435
                foreach($properties as $property) {
436
                    if(Services_JSON::isError($property)) {
437
                        return $property;
438
                    }
439
                }
440
441
                return '{'.join(',', $properties).'}';
442
443
            default:
444
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
445
                    ? 'null'
446
                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
447
        }
448
    }
449
450
   /**
451
    * array-walking function for use in generating JSON-formatted name-value pairs
452
    *
453
    * @param    string  $name   name of key to use
454
    * @param    mixed   $value  reference to an array element to be encoded
455
    *
456
    * @return   string  JSON-formatted name-value pair, like '"name":value'
457
    * @access   private
458
    */
459
    function name_value($name, $value)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
460
    {
461
        $encoded_value = $this->_encode($value);
462
463
        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

463
        if(Services_JSON::/** @scrutinizer ignore-call */ isError($encoded_value)) {
Loading history...
464
            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...
465
        }
466
467
        return $this->_encode(strval($name)).':'.$encoded_value;
468
    }
469
470
   /**
471
    * reduce a string by removing leading and trailing comments and whitespace
472
    *
473
    * @param    $str    string      string value to strip of comments and whitespace
474
    *
475
    * @return   string  string value stripped of comments and whitespace
476
    * @access   private
477
    */
478
    function reduce_string($str)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
479
    {
480
        $str = preg_replace(array(
481
482
                // eliminate single line comments in '// ...' form
483
                '#^\s*//(.+)$#m',
484
485
                // eliminate multi-line comments in '/* ... */' form, at start of string
486
                '#^\s*/\*(.+)\*/#Us',
487
488
                // eliminate multi-line comments in '/* ... */' form, at end of string
489
                '#/\*(.+)\*/\s*$#Us'
490
491
            ), '', $str);
492
493
        // eliminate extraneous space
494
        return trim($str);
495
    }
496
497
   /**
498
    * decodes a JSON string into appropriate variable
499
    *
500
    * @param    string  $str    JSON-formatted string
501
    *
502
    * @return   mixed   number, boolean, string, array, or object
503
    *                   corresponding to given JSON input string.
504
    *                   See argument 1 to Services_JSON() above for object-output behavior.
505
    *                   Note that decode() always returns strings
506
    *                   in ASCII or UTF-8 format!
507
    * @access   public
508
    */
509
    function decode($str)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
510
    {
511
        $str = $this->reduce_string($str);
512
513
        switch (strtolower($str)) {
514
            case 'true':
515
                return true;
516
517
            case 'false':
518
                return false;
519
520
            case 'null':
521
                return null;
522
523
            default:
524
                $m = array();
525
526
                if (is_numeric($str)) {
527
                    // Lookie-loo, it's a number
528
529
                    // This would work on its own, but I'm trying to be
530
                    // good about returning integers where appropriate:
531
                    // return (float)$str;
532
533
                    // Return float or int, as appropriate
534
                    return ((float)$str == (integer)$str)
535
                        ? (integer)$str
536
                        : (float)$str;
537
538
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
539
                    // STRINGS RETURNED IN UTF-8 FORMAT
540
                    $delim = substr($str, 0, 1);
541
                    $chrs = substr($str, 1, -1);
542
                    $utf8 = '';
543
                    $strlen_chrs = strlen($chrs);
544
545
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
546
547
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
548
                        $ord_chrs_c = ord($chrs{$c});
549
550
                        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...
551
                            case $substr_chrs_c_2 == '\b':
552
                                $utf8 .= chr(0x08);
553
                                ++$c;
554
                                break;
555
                            case $substr_chrs_c_2 == '\t':
556
                                $utf8 .= chr(0x09);
557
                                ++$c;
558
                                break;
559
                            case $substr_chrs_c_2 == '\n':
560
                                $utf8 .= chr(0x0A);
561
                                ++$c;
562
                                break;
563
                            case $substr_chrs_c_2 == '\f':
564
                                $utf8 .= chr(0x0C);
565
                                ++$c;
566
                                break;
567
                            case $substr_chrs_c_2 == '\r':
568
                                $utf8 .= chr(0x0D);
569
                                ++$c;
570
                                break;
571
572
                            case $substr_chrs_c_2 == '\\"':
573
                            case $substr_chrs_c_2 == '\\\'':
574
                            case $substr_chrs_c_2 == '\\\\':
575
                            case $substr_chrs_c_2 == '\\/':
576
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
577
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
578
                                    $utf8 .= $chrs{++$c};
579
                                }
580
                                break;
581
582
                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
583
                                // single, escaped unicode character
584
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 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

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

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
792
    {
793
        if (class_exists('pear')) {
794
            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...
795
        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
796
                                 is_subclass_of($data, 'services_json_error'))) {
797
            return true;
798
        }
799
800
        return false;
801
    }
802
}
803
804
if (class_exists('PEAR_Error')) {
805
806
    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...
807
    {
808
        function Services_JSON_Error($message = 'unknown error', $code = null,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
809
                                     $mode = null, $options = null, $userinfo = null)
810
        {
811
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
812
        }
813
    }
814
815
} else {
816
817
    /**
818
     * @todo Ultimately, this class shall be descended from PEAR_Error
819
     */
820
    class Services_JSON_Error
821
    {
822
        function Services_JSON_Error($message = 'unknown error', $code = null,
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

822
        function Services_JSON_Error(/** @scrutinizer ignore-unused */ $message = 'unknown error', $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...
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

822
        function Services_JSON_Error($message = 'unknown error', /** @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...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
823
                                     $mode = null, $options = null, $userinfo = 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

823
                                     /** @scrutinizer ignore-unused */ $mode = null, $options = null, $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...
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

823
                                     $mode = null, /** @scrutinizer ignore-unused */ $options = null, $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...
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

823
                                     $mode = null, $options = null, /** @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...
824
        {
825
826
        }
827
    }
828
829
}
830
   
831