Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-json.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if ( ! class_exists( 'Services_JSON' ) ) :
3
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
/**
5
 * Converts to and from JSON format.
6
 *
7
 * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
 * format. It is easy for humans to read and write. It is easy for machines
9
 * to parse and generate. It is based on a subset of the JavaScript
10
 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
 * This feature can also be found in  Python. JSON is a text format that is
12
 * completely language independent but uses conventions that are familiar
13
 * to programmers of the C-family of languages, including C, C++, C#, Java,
14
 * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
 * ideal data-interchange language.
16
 *
17
 * This package provides a simple encoder and decoder for JSON notation. It
18
 * is intended for use with client-side Javascript applications that make
19
 * use of HTTPRequest to perform server communication functions - data can
20
 * be encoded into JSON notation for use in a client-side javascript, or
21
 * decoded from incoming Javascript requests. JSON format is native to
22
 * Javascript, and can be directly eval()'ed with no further parsing
23
 * overhead
24
 *
25
 * All strings should be in ASCII or UTF-8 format!
26
 *
27
 * LICENSE: Redistribution and use in source and binary forms, with or
28
 * without modification, are permitted provided that the following
29
 * conditions are met: Redistributions of source code must retain the
30
 * above copyright notice, this list of conditions and the following
31
 * disclaimer. Redistributions in binary form must reproduce the above
32
 * copyright notice, this list of conditions and the following disclaimer
33
 * in the documentation and/or other materials provided with the
34
 * distribution.
35
 *
36
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
 * DAMAGE.
47
 *
48
 * @category
49
 * @package     Services_JSON
50
 * @author      Michal Migurski <[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 305040 2010-11-02 23:19:03Z alan_k $
55
 * @license     http://www.opensource.org/licenses/bsd-license.php
56
 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
 */
58
59
/**
60
 * Marker constant for Services_JSON::decode(), used to flag stack state
61
 */
62
define('SERVICES_JSON_SLICE',   1);
63
64
/**
65
 * Marker constant for Services_JSON::decode(), used to flag stack state
66
 */
67
define('SERVICES_JSON_IN_STR',  2);
68
69
/**
70
 * Marker constant for Services_JSON::decode(), used to flag stack state
71
 */
72
define('SERVICES_JSON_IN_ARR',  3);
73
74
/**
75
 * Marker constant for Services_JSON::decode(), used to flag stack state
76
 */
77
define('SERVICES_JSON_IN_OBJ',  4);
78
79
/**
80
 * Marker constant for Services_JSON::decode(), used to flag stack state
81
 */
82
define('SERVICES_JSON_IN_CMT', 5);
83
84
/**
85
 * Behavior switch for Services_JSON::decode()
86
 */
87
define('SERVICES_JSON_LOOSE_TYPE', 16);
88
89
/**
90
 * Behavior switch for Services_JSON::decode()
91
 */
92
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
94
/**
95
 * Behavior switch for Services_JSON::decode()
96
 */
97
define('SERVICES_JSON_USE_TO_JSON', 64);
98
99
/**
100
 * Converts to and from JSON format.
101
 *
102
 * Brief example of use:
103
 *
104
 * <code>
105
 * // create a new instance of Services_JSON
106
 * $json = new Services_JSON();
107
 *
108
 * // convert a complexe value to JSON notation, and send it to the browser
109
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
110
 * $output = $json->encode($value);
111
 *
112
 * print($output);
113
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
114
 *
115
 * // accept incoming POST data, assumed to be in JSON notation
116
 * $input = file_get_contents('php://input', 1000000);
117
 * $value = $json->decode($input);
118
 * </code>
119
 */
120
class Services_JSON
121
{
122
   /**
123
    * constructs a new JSON instance
124
    *
125
    * @param    int     $use    object behavior flags; combine with boolean-OR
126
    *
127
    *                           possible values:
128
    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
129
    *                                   "{...}" syntax creates associative arrays
130
    *                                   instead of objects in decode().
131
    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
132
    *                                   Values which can't be encoded (e.g. resources)
133
    *                                   appear as NULL instead of throwing errors.
134
    *                                   By default, a deeply-nested resource will
135
    *                                   bubble up with an error, so all return values
136
    *                                   from encode() should be checked with isError()
137
    *                           - SERVICES_JSON_USE_TO_JSON:  call toJSON when serializing objects
138
    *                                   It serializes the return value from the toJSON call rather 
139
    *                                   than the object itself, toJSON can return associative arrays, 
140
    *                                   strings or numbers, if you return an object, make sure it does
141
    *                                   not have a toJSON method, otherwise an error will occur.
142
    */
143
    function __construct( $use = 0 )
144
    {
145
        $this->use = $use;
0 ignored issues
show
The property use does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
146
        $this->_mb_strlen            = function_exists('mb_strlen');
147
        $this->_mb_convert_encoding  = function_exists('mb_convert_encoding');
148
        $this->_mb_substr            = function_exists('mb_substr');
149
    }
150
151
	/**
152
	 * PHP4 constructor.
153
	 */
154
	public function Services_JSON( $use = 0 ) {
155
		self::__construct( $use );
156
	}
157
    // private - cache the mbstring lookup results..
158
    var $_mb_strlen = false;
159
    var $_mb_substr = false;
160
    var $_mb_convert_encoding = false;
161
    
162
   /**
163
    * convert a string from one UTF-16 char to one UTF-8 char
164
    *
165
    * Normally should be handled by mb_convert_encoding, but
166
    * provides a slower PHP-only method for installations
167
    * that lack the multibye string extension.
168
    *
169
    * @param    string  $utf16  UTF-16 character
170
    * @return   string  UTF-8 character
171
    * @access   private
172
    */
173
    function utf162utf8($utf16)
174
    {
175
        // oh please oh please oh please oh please oh please
176
        if($this->_mb_convert_encoding) {
177
            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
178
        }
179
180
        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
181
182
        switch(true) {
183
            case ((0x7F & $bytes) == $bytes):
184
                // this case should never be reached, because we are in ASCII range
185
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
186
                return chr(0x7F & $bytes);
187
188
            case (0x07FF & $bytes) == $bytes:
189
                // return a 2-byte UTF-8 character
190
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
191
                return chr(0xC0 | (($bytes >> 6) & 0x1F))
192
                     . chr(0x80 | ($bytes & 0x3F));
193
194
            case (0xFFFF & $bytes) == $bytes:
195
                // return a 3-byte UTF-8 character
196
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
197
                return chr(0xE0 | (($bytes >> 12) & 0x0F))
198
                     . chr(0x80 | (($bytes >> 6) & 0x3F))
199
                     . chr(0x80 | ($bytes & 0x3F));
200
        }
201
202
        // ignoring UTF-32 for now, sorry
203
        return '';
204
    }
205
206
   /**
207
    * convert a string from one UTF-8 char to one UTF-16 char
208
    *
209
    * Normally should be handled by mb_convert_encoding, but
210
    * provides a slower PHP-only method for installations
211
    * that lack the multibye string extension.
212
    *
213
    * @param    string  $utf8   UTF-8 character
214
    * @return   string  UTF-16 character
215
    * @access   private
216
    */
217
    function utf82utf16($utf8)
218
    {
219
        // oh please oh please oh please oh please oh please
220
        if($this->_mb_convert_encoding) {
221
            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
222
        }
223
224
        switch($this->strlen8($utf8)) {
225
            case 1:
226
                // this case should never be reached, because we are in ASCII range
227
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
228
                return $utf8;
229
230
            case 2:
231
                // return a UTF-16 character from a 2-byte UTF-8 char
232
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
233
                return chr(0x07 & (ord($utf8{0}) >> 2))
234
                     . chr((0xC0 & (ord($utf8{0}) << 6))
235
                         | (0x3F & ord($utf8{1})));
236
237
            case 3:
238
                // return a UTF-16 character from a 3-byte UTF-8 char
239
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
240
                return chr((0xF0 & (ord($utf8{0}) << 4))
241
                         | (0x0F & (ord($utf8{1}) >> 2)))
242
                     . chr((0xC0 & (ord($utf8{1}) << 6))
243
                         | (0x7F & ord($utf8{2})));
244
        }
245
246
        // ignoring UTF-32 for now, sorry
247
        return '';
248
    }
249
250
   /**
251
    * encodes an arbitrary variable into JSON format (and sends JSON Header)
252
    *
253
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
254
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
255
    *                           if var is a strng, note that encode() always expects it
256
    *                           to be in ASCII or UTF-8 format!
257
    *
258
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
259
    * @access   public
260
    */
261
    function encode($var)
262
    {
263
        header('Content-type: application/json');
264
        return $this->encodeUnsafe($var);
265
    }
266
    /**
267
    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
268
    *
269
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
270
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
271
    *                           if var is a strng, note that encode() always expects it
272
    *                           to be in ASCII or UTF-8 format!
273
    *
274
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
275
    * @access   public
276
    */
277
    function encodeUnsafe($var)
278
    {
279
        // see bug #16908 - regarding numeric locale printing
280
        $lc = setlocale(LC_NUMERIC, 0);
281
        setlocale(LC_NUMERIC, 'C');
282
        $ret = $this->_encode($var);
283
        setlocale(LC_NUMERIC, $lc);
284
        return $ret;
285
        
286
    }
287
    /**
288
    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
289
    *
290
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
291
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
292
    *                           if var is a strng, note that encode() always expects it
293
    *                           to be in ASCII or UTF-8 format!
294
    *
295
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
296
    * @access   public
297
    */
298
    function _encode($var) 
299
    {
300
         
301
        switch (gettype($var)) {
302
            case 'boolean':
303
                return $var ? 'true' : 'false';
304
305
            case 'NULL':
306
                return 'null';
307
308
            case 'integer':
309
                return (int) $var;
310
311
            case 'double':
312
            case 'float':
313
                return  (float) $var;
314
315
            case 'string':
316
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
317
                $ascii = '';
318
                $strlen_var = $this->strlen8($var);
319
320
               /*
321
                * Iterate over every character in the string,
322
                * escaping with a slash or encoding to UTF-8 where necessary
323
                */
324
                for ($c = 0; $c < $strlen_var; ++$c) {
325
326
                    $ord_var_c = ord($var{$c});
327
328
                    switch (true) {
329
                        case $ord_var_c == 0x08:
330
                            $ascii .= '\b';
331
                            break;
332
                        case $ord_var_c == 0x09:
333
                            $ascii .= '\t';
334
                            break;
335
                        case $ord_var_c == 0x0A:
336
                            $ascii .= '\n';
337
                            break;
338
                        case $ord_var_c == 0x0C:
339
                            $ascii .= '\f';
340
                            break;
341
                        case $ord_var_c == 0x0D:
342
                            $ascii .= '\r';
343
                            break;
344
345
                        case $ord_var_c == 0x22:
346
                        case $ord_var_c == 0x2F:
347
                        case $ord_var_c == 0x5C:
348
                            // double quote, slash, slosh
349
                            $ascii .= '\\'.$var{$c};
350
                            break;
351
352
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
353
                            // characters U-00000000 - U-0000007F (same as ASCII)
354
                            $ascii .= $var{$c};
355
                            break;
356
357
                        case (($ord_var_c & 0xE0) == 0xC0):
358
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
359
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
360
                            if ($c+1 >= $strlen_var) {
361
                                $c += 1;
362
                                $ascii .= '?';
363
                                break;
364
                            }
365
                            
366
                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
367
                            $c += 1;
368
                            $utf16 = $this->utf82utf16($char);
369
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
370
                            break;
371
372
                        case (($ord_var_c & 0xF0) == 0xE0):
373
                            if ($c+2 >= $strlen_var) {
374
                                $c += 2;
375
                                $ascii .= '?';
376
                                break;
377
                            }
378
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
379
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
380
                            $char = pack('C*', $ord_var_c,
381
                                         @ord($var{$c + 1}),
382
                                         @ord($var{$c + 2}));
383
                            $c += 2;
384
                            $utf16 = $this->utf82utf16($char);
385
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
386
                            break;
387
388
                        case (($ord_var_c & 0xF8) == 0xF0):
389
                            if ($c+3 >= $strlen_var) {
390
                                $c += 3;
391
                                $ascii .= '?';
392
                                break;
393
                            }
394
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
395
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
396
                            $char = pack('C*', $ord_var_c,
397
                                         ord($var{$c + 1}),
398
                                         ord($var{$c + 2}),
399
                                         ord($var{$c + 3}));
400
                            $c += 3;
401
                            $utf16 = $this->utf82utf16($char);
402
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
403
                            break;
404
405 View Code Duplication
                        case (($ord_var_c & 0xFC) == 0xF8):
406
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
407
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
408
                            if ($c+4 >= $strlen_var) {
409
                                $c += 4;
410
                                $ascii .= '?';
411
                                break;
412
                            }
413
                            $char = pack('C*', $ord_var_c,
414
                                         ord($var{$c + 1}),
415
                                         ord($var{$c + 2}),
416
                                         ord($var{$c + 3}),
417
                                         ord($var{$c + 4}));
418
                            $c += 4;
419
                            $utf16 = $this->utf82utf16($char);
420
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
421
                            break;
422
423 View Code Duplication
                        case (($ord_var_c & 0xFE) == 0xFC):
424
                        if ($c+5 >= $strlen_var) {
425
                                $c += 5;
426
                                $ascii .= '?';
427
                                break;
428
                            }
429
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
430
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
431
                            $char = pack('C*', $ord_var_c,
432
                                         ord($var{$c + 1}),
433
                                         ord($var{$c + 2}),
434
                                         ord($var{$c + 3}),
435
                                         ord($var{$c + 4}),
436
                                         ord($var{$c + 5}));
437
                            $c += 5;
438
                            $utf16 = $this->utf82utf16($char);
439
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
440
                            break;
441
                    }
442
                }
443
                return  '"'.$ascii.'"';
444
445
            case 'array':
446
               /*
447
                * As per JSON spec if any array key is not an integer
448
                * we must treat the whole array as an object. We
449
                * also try to catch a sparsely populated associative
450
                * array with numeric keys here because some JS engines
451
                * will create an array with empty indexes up to
452
                * max_index which can cause memory issues and because
453
                * the keys, which may be relevant, will be remapped
454
                * otherwise.
455
                *
456
                * As per the ECMA and JSON specification an object may
457
                * have any string as a property. Unfortunately due to
458
                * a hole in the ECMA specification if the key is a
459
                * ECMA reserved word or starts with a digit the
460
                * parameter is only accessible using ECMAScript's
461
                * bracket notation.
462
                */
463
464
                // treat as a JSON object
465
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
466
                    $properties = array_map(array($this, 'name_value'),
467
                                            array_keys($var),
468
                                            array_values($var));
469
470
                    foreach($properties as $property) {
471
                        if(Services_JSON::isError($property)) {
472
                            return $property;
473
                        }
474
                    }
475
476
                    return '{' . join(',', $properties) . '}';
477
                }
478
479
                // treat it like a regular array
480
                $elements = array_map(array($this, '_encode'), $var);
481
482
                foreach($elements as $element) {
483
                    if(Services_JSON::isError($element)) {
484
                        return $element;
485
                    }
486
                }
487
488
                return '[' . join(',', $elements) . ']';
489
490
            case 'object':
491
            
492
                // support toJSON methods.
493
                if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
494
                    // this may end up allowing unlimited recursion
495
                    // so we check the return value to make sure it's not got the same method.
496
                    $recode = $var->toJSON();
497
                    
498
                    if (method_exists($recode, 'toJSON')) {
499
                        
500
                        return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
501
                        ? 'null'
502
                        : new Services_JSON_Error(get_class($var).
503
                            " toJSON returned an object with a toJSON method.");
504
                            
505
                    }
506
                    
507
                    return $this->_encode( $recode );
508
                } 
509
                
510
                $vars = get_object_vars($var);
511
                
512
                $properties = array_map(array($this, 'name_value'),
513
                                        array_keys($vars),
514
                                        array_values($vars));
515
516
                foreach($properties as $property) {
517
                    if(Services_JSON::isError($property)) {
518
                        return $property;
519
                    }
520
                }
521
522
                return '{' . join(',', $properties) . '}';
523
524
            default:
525
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
526
                    ? 'null'
527
                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
528
        }
529
    }
530
531
   /**
532
    * array-walking function for use in generating JSON-formatted name-value pairs
533
    *
534
    * @param    string  $name   name of key to use
535
    * @param    mixed   $value  reference to an array element to be encoded
536
    *
537
    * @return   string  JSON-formatted name-value pair, like '"name":value'
538
    * @access   private
539
    */
540
    function name_value($name, $value)
541
    {
542
        $encoded_value = $this->_encode($value);
543
544
        if(Services_JSON::isError($encoded_value)) {
545
            return $encoded_value;
546
        }
547
548
        return $this->_encode(strval($name)) . ':' . $encoded_value;
549
    }
550
551
   /**
552
    * reduce a string by removing leading and trailing comments and whitespace
553
    *
554
    * @param    $str    string      string value to strip of comments and whitespace
555
    *
556
    * @return   string  string value stripped of comments and whitespace
557
    * @access   private
558
    */
559
    function reduce_string($str)
560
    {
561
        $str = preg_replace(array(
562
563
                // eliminate single line comments in '// ...' form
564
                '#^\s*//(.+)$#m',
565
566
                // eliminate multi-line comments in '/* ... */' form, at start of string
567
                '#^\s*/\*(.+)\*/#Us',
568
569
                // eliminate multi-line comments in '/* ... */' form, at end of string
570
                '#/\*(.+)\*/\s*$#Us'
571
572
            ), '', $str);
573
574
        // eliminate extraneous space
575
        return trim($str);
576
    }
577
578
   /**
579
    * decodes a JSON string into appropriate variable
580
    *
581
    * @param    string  $str    JSON-formatted string
582
    *
583
    * @return   mixed   number, boolean, string, array, or object
0 ignored issues
show
Consider making the return type a bit more specific; maybe use boolean|null|integer|double|string|array|stdClass.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
584
    *                   corresponding to given JSON input string.
585
    *                   See argument 1 to Services_JSON() above for object-output behavior.
586
    *                   Note that decode() always returns strings
587
    *                   in ASCII or UTF-8 format!
588
    * @access   public
589
    */
590
    function decode($str)
591
    {
592
        $str = $this->reduce_string($str);
593
594
        switch (strtolower($str)) {
595
            case 'true':
596
                return true;
597
598
            case 'false':
599
                return false;
600
601
            case 'null':
602
                return null;
603
604
            default:
605
                $m = array();
606
607
                if (is_numeric($str)) {
608
                    // Lookie-loo, it's a number
609
610
                    // This would work on its own, but I'm trying to be
611
                    // good about returning integers where appropriate:
612
                    // return (float)$str;
613
614
                    // Return float or int, as appropriate
615
                    return ((float)$str == (integer)$str)
616
                        ? (integer)$str
617
                        : (float)$str;
618
619
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
620
                    // STRINGS RETURNED IN UTF-8 FORMAT
621
                    $delim = $this->substr8($str, 0, 1);
622
                    $chrs = $this->substr8($str, 1, -1);
623
                    $utf8 = '';
624
                    $strlen_chrs = $this->strlen8($chrs);
625
626
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
627
628
                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
629
                        $ord_chrs_c = ord($chrs{$c});
630
631
                        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\\\\u[0-9A-...>substr8($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...
632
                            case $substr_chrs_c_2 == '\b':
633
                                $utf8 .= chr(0x08);
634
                                ++$c;
635
                                break;
636
                            case $substr_chrs_c_2 == '\t':
637
                                $utf8 .= chr(0x09);
638
                                ++$c;
639
                                break;
640
                            case $substr_chrs_c_2 == '\n':
641
                                $utf8 .= chr(0x0A);
642
                                ++$c;
643
                                break;
644
                            case $substr_chrs_c_2 == '\f':
645
                                $utf8 .= chr(0x0C);
646
                                ++$c;
647
                                break;
648
                            case $substr_chrs_c_2 == '\r':
649
                                $utf8 .= chr(0x0D);
650
                                ++$c;
651
                                break;
652
653
                            case $substr_chrs_c_2 == '\\"':
654
                            case $substr_chrs_c_2 == '\\\'':
655
                            case $substr_chrs_c_2 == '\\\\':
656
                            case $substr_chrs_c_2 == '\\/':
657
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
658
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
659
                                    $utf8 .= $chrs{++$c};
660
                                }
661
                                break;
662
663
                            case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
664
                                // single, escaped unicode character
665
                                $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
666
                                       . chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
667
                                $utf8 .= $this->utf162utf8($utf16);
668
                                $c += 5;
669
                                break;
670
671
                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
672
                                $utf8 .= $chrs{$c};
673
                                break;
674
675 View Code Duplication
                            case ($ord_chrs_c & 0xE0) == 0xC0:
676
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
677
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
678
                                $utf8 .= $this->substr8($chrs, $c, 2);
679
                                ++$c;
680
                                break;
681
682 View Code Duplication
                            case ($ord_chrs_c & 0xF0) == 0xE0:
683
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
684
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
685
                                $utf8 .= $this->substr8($chrs, $c, 3);
686
                                $c += 2;
687
                                break;
688
689 View Code Duplication
                            case ($ord_chrs_c & 0xF8) == 0xF0:
690
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
691
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
692
                                $utf8 .= $this->substr8($chrs, $c, 4);
693
                                $c += 3;
694
                                break;
695
696 View Code Duplication
                            case ($ord_chrs_c & 0xFC) == 0xF8:
697
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
698
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
699
                                $utf8 .= $this->substr8($chrs, $c, 5);
700
                                $c += 4;
701
                                break;
702
703 View Code Duplication
                            case ($ord_chrs_c & 0xFE) == 0xFC:
704
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
705
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
706
                                $utf8 .= $this->substr8($chrs, $c, 6);
707
                                $c += 5;
708
                                break;
709
710
                        }
711
712
                    }
713
714
                    return $utf8;
715
716
                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
717
                    // array, or object notation
718
719
                    if ($str{0} == '[') {
720
                        $stk = array(SERVICES_JSON_IN_ARR);
721
                        $arr = array();
722
                    } else {
723
                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
724
                            $stk = array(SERVICES_JSON_IN_OBJ);
725
                            $obj = array();
726
                        } else {
727
                            $stk = array(SERVICES_JSON_IN_OBJ);
728
                            $obj = new stdClass();
729
                        }
730
                    }
731
732
                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
733
                                           'where' => 0,
734
                                           'delim' => false));
735
736
                    $chrs = $this->substr8($str, 1, -1);
737
                    $chrs = $this->reduce_string($chrs);
738
739
                    if ($chrs == '') {
740
                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
741
                            return $arr;
0 ignored issues
show
The variable $arr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
742
743
                        } else {
744
                            return $obj;
0 ignored issues
show
The variable $obj does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
745
746
                        }
747
                    }
748
749
                    //print("\nparsing {$chrs}\n");
750
751
                    $strlen_chrs = $this->strlen8($chrs);
752
753
                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
754
755
                        $top = end($stk);
756
                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
757
758
                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
759
                            // found a comma that is not inside a string, array, etc.,
760
                            // OR we've reached the end of the character list
761
                            $slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
762
                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
763
                            //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
764
765
                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
766
                                // we are in an array, so just push an element onto the stack
767
                                array_push($arr, $this->decode($slice));
768
769
                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
770
                                // we are in an object, so figure
771
                                // out the property name and set an
772
                                // element in an associative array,
773
                                // for now
774
                                $parts = array();
775
                                
776
                               if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
777
 	                              // "name":value pair
778
                                    $key = $this->decode($parts[1]);
779
                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
780 View Code Duplication
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
781
                                        $obj[$key] = $val;
782
                                    } else {
783
                                        $obj->$key = $val;
784
                                    }
785
                                } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
786
                                    // name:value pair, where name is unquoted
787
                                    $key = $parts[1];
788
                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
789
790 View Code Duplication
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
791
                                        $obj[$key] = $val;
792
                                    } else {
793
                                        $obj->$key = $val;
794
                                    }
795
                                }
796
797
                            }
798
799
                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
800
                            // found a quote, and we are not inside a string
801
                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
802
                            //print("Found start of string at {$c}\n");
803
804
                        } elseif (($chrs{$c} == $top['delim']) &&
805
                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
806
                                 (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
807
                            // found a quote, we're in a string, and it's not escaped
808
                            // we know that it's not escaped becase there is _not_ an
809
                            // odd number of backslashes at the end of the string so far
810
                            array_pop($stk);
811
                            //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
812
813 View Code Duplication
                        } elseif (($chrs{$c} == '[') &&
814
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
815
                            // found a left-bracket, and we are in an array, object, or slice
816
                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
817
                            //print("Found start of array at {$c}\n");
818
819
                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
820
                            // found a right-bracket, and we're in an array
821
                            array_pop($stk);
822
                            //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
823
824 View Code Duplication
                        } elseif (($chrs{$c} == '{') &&
825
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
826
                            // found a left-brace, and we are in an array, object, or slice
827
                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
828
                            //print("Found start of object at {$c}\n");
829
830
                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
831
                            // found a right-brace, and we're in an object
832
                            array_pop($stk);
833
                            //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
834
835 View Code Duplication
                        } elseif (($substr_chrs_c_2 == '/*') &&
836
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
837
                            // found a comment start, and we are in an array, object, or slice
838
                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
839
                            $c++;
840
                            //print("Found start of comment at {$c}\n");
841
842
                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
843
                            // found a comment end, and we're in one now
844
                            array_pop($stk);
845
                            $c++;
846
847
                            for ($i = $top['where']; $i <= $c; ++$i)
848
                                $chrs = substr_replace($chrs, ' ', $i, 1);
849
850
                            //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
851
852
                        }
853
854
                    }
855
856
                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
857
                        return $arr;
858
859
                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
860
                        return $obj;
861
862
                    }
863
864
                }
865
        }
866
    }
867
868
    /**
869
     * @todo Ultimately, this should just call PEAR::isError()
870
     */
871
    function isError($data, $code = null)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
872
    {
873
        if (class_exists('pear')) {
874
            return PEAR::isError($data, $code);
875
        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
876
                                 is_subclass_of($data, 'services_json_error'))) {
877
            return true;
878
        }
879
880
        return false;
881
    }
882
    
883
    /**
884
    * Calculates length of string in bytes
885
    * @param string 
886
    * @return integer length
887
    */
888
    function strlen8( $str ) 
889
    {
890
        if ( $this->_mb_strlen ) {
891
            return mb_strlen( $str, "8bit" );
892
        }
893
        return strlen( $str );
894
    }
895
    
896
    /**
897
    * Returns part of a string, interpreting $start and $length as number of bytes.
898
    * @param string 
899
    * @param integer start 
900
    * @param integer length 
901
    * @return integer length
0 ignored issues
show
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
902
    */
903
    function substr8( $string, $start, $length=false ) 
904
    {
905
        if ( $length === false ) {
906
            $length = $this->strlen8( $string ) - $start;
907
        }
908
        if ( $this->_mb_substr ) {
909
            return mb_substr( $string, $start, $length, "8bit" );
0 ignored issues
show
It seems like $length can also be of type boolean or double; however, mb_substr() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
910
        }
911
        return substr( $string, $start, $length );
912
    }
913
914
}
915
916
if (class_exists('PEAR_Error')) {
917
918
    class Services_JSON_Error extends PEAR_Error
919
    {
920
        function __construct($message = 'unknown error', $code = null,
921
                                     $mode = null, $options = null, $userinfo = null)
922
        {
923
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (PEAR_Error() instead of __construct()). Are you sure this is correct? If so, you might want to change this to $this->PEAR_Error().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
924
        }
925
926
	public function Services_JSON_Error($message = 'unknown error', $code = null,
927
                                     $mode = null, $options = null, $userinfo = null) {
928
		self::__construct($message = 'unknown error', $code = null,
929
                                     $mode = null, $options = null, $userinfo = null);
930
	}
931
    }
932
933
} else {
934
935
    /**
936
     * @todo Ultimately, this class shall be descended from PEAR_Error
937
     */
938
    class Services_JSON_Error
0 ignored issues
show
Comprehensibility Best Practice introduced by
The type Services_JSON_Error has been defined more than once; this definition is ignored, only the first definition in this file (L918-931) is considered.

This check looks for classes that have been defined more than once in the same file.

If you can, we would recommend to use standard object-oriented programming techniques. For example, to avoid multiple types, it might make sense to create a common interface, and then multiple, different implementations for that interface.

This also has the side-effect of providing you with better IDE auto-completion, static analysis and also better OPCode caching from PHP.

Loading history...
939
    {
940
	    /**
941
	     * PHP5 constructor.
942
	     */
943
        function __construct( $message = 'unknown error', $code = null,
944
                                     $mode = null, $options = null, $userinfo = null )
945
        {
946
947
        }
948
949
	    /**
950
	     * PHP4 constructor.
951
	     */
952
		public function Services_JSON_Error( $message = 'unknown error', $code = null,
953
	                                     $mode = null, $options = null, $userinfo = null ) {
954
			self::__construct( $message, $code, $mode, $options, $userinfo );
955
		}
956
    }
957
    
958
}
959
960
endif;
961