GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (21)

Security Analysis    no request data  

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/JsonSerializer/JsonSerializer.php (5 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
3
namespace Zumba\JsonSerializer;
4
5
use InvalidArgumentException;
6
use ReflectionClass;
7
use ReflectionException;
8
use SplObjectStorage;
9
use Zumba\JsonSerializer\Exception\JsonSerializerException;
10
use SuperClosure\SerializerInterface as ClosureSerializerInterface;
11
12
class JsonSerializer
13
{
14
15
    const CLASS_IDENTIFIER_KEY = '@type';
16
    const CLOSURE_IDENTIFIER_KEY = '@closure';
17
    const UTF8ENCODED_IDENTIFIER_KEY = '@utf8encoded';
18
    const SCALAR_IDENTIFIER_KEY = '@scalar';
19
    const FLOAT_ADAPTER = 'JsonSerializerFloatAdapter';
20
21
    const KEY_UTF8ENCODED = 1;
22
    const VALUE_UTF8ENCODED = 2;
23
24
    const UNDECLARED_PROPERTY_MODE_SET = 1;
25
    const UNDECLARED_PROPERTY_MODE_IGNORE = 2;
26
    const UNDECLARED_PROPERTY_MODE_EXCEPTION = 3;
27
28
    /**
29
     * Storage for object
30
     *
31
     * Used for recursion
32
     *
33
     * @var SplObjectStorage
34
     */
35
    protected $objectStorage;
36
37
    /**
38
     * Object mapping for recursion
39
     *
40
     * @var array
41
     */
42
    protected $objectMapping = array();
43
44
    /**
45
     * Object mapping index
46
     *
47
     * @var integer
48
     */
49
    protected $objectMappingIndex = 0;
50
51
    /**
52
     * Support PRESERVE_ZERO_FRACTION json option
53
     *
54
     * @var boolean
55
     */
56
    protected $preserveZeroFractionSupport;
57
58
    /**
59
     * Closure serializer instance
60
     *
61
     * @var ClosureSerializerInterface
62
     */
63
    protected $closureSerializer;
64
65
    /**
66
     * Map of custom object serializers
67
     *
68
     * @var array
69
     */
70
    protected $customObjectSerializerMap;
71
72
    /**
73
     * Undefined Attribute Mode
74
     *
75
     * @var integer
76
     */
77
    protected $undefinedAttributeMode = self::UNDECLARED_PROPERTY_MODE_SET;
78
79
    /**
80
     * Constructor.
81
     *
82
     * @param ClosureSerializerInterface $closureSerializer
83
     * @param array                      $customObjectSerializerMap
84
     */
85
    public function __construct(
86
        ClosureSerializerInterface $closureSerializer = null,
87
        $customObjectSerializerMap = array()
88
    ) {
89
        $this->preserveZeroFractionSupport = defined('JSON_PRESERVE_ZERO_FRACTION');
90
        $this->closureSerializer = $closureSerializer;
91
        $this->customObjectSerializerMap = (array)$customObjectSerializerMap;
92
    }
93
94
    /**
95
     * Serialize the value in JSON
96
     *
97
     * @param  mixed $value
98
     * @return string JSON encoded
99
     * @throws JsonSerializerException
100
     */
101
    public function serialize($value)
102
    {
103
        $this->reset();
104
        $serializedData = $this->serializeData($value);
105
        $encoded = json_encode($serializedData, $this->calculateEncodeOptions());
106
        if ($encoded === false || json_last_error() != JSON_ERROR_NONE) {
107
            if (json_last_error() != JSON_ERROR_UTF8) {
108
                throw new JsonSerializerException('Invalid data to encode to JSON. Error: ' . json_last_error());
109
            }
110
111
            $serializedData = $this->encodeNonUtf8ToUtf8($serializedData);
112
            $encoded = json_encode($serializedData, $this->calculateEncodeOptions());
113
114
            if ($encoded === false || json_last_error() != JSON_ERROR_NONE) {
115
                throw new JsonSerializerException('Invalid data to encode to JSON. Error: ' . json_last_error());
116
            }
117
        }
118
        return $this->processEncodedValue($encoded);
119
    }
120
121
    /**
122
     * Calculate encoding options
123
     *
124
     * @return integer
125
     */
126
    protected function calculateEncodeOptions()
127
    {
128
        $options = JSON_UNESCAPED_UNICODE;
129
        if ($this->preserveZeroFractionSupport) {
130
            $options |= JSON_PRESERVE_ZERO_FRACTION;
131
        }
132
        return $options;
133
    }
134
135
    /**
136
     *
137
     * @param mixed $serializedData
138
     *
139
     * @return array
140
     */
141
    protected function encodeNonUtf8ToUtf8($serializedData)
142
    {
143
        if (is_string($serializedData)) {
144
            if (!mb_check_encoding($serializedData, 'UTF-8')) {
145
                $serializedData = [
146
                    static::SCALAR_IDENTIFIER_KEY => mb_convert_encoding($serializedData, 'UTF-8', '8bit'),
147
                    static::UTF8ENCODED_IDENTIFIER_KEY => static::VALUE_UTF8ENCODED,
148
                ];
149
            }
150
151
            return $serializedData;
152
        }
153
154
        $encodedKeys = [];
155
        $encodedData = [];
156
        foreach ($serializedData as $key => $value) {
0 ignored issues
show
The expression $serializedData of type object|integer|double|null|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
157
            if (is_array($value)) {
158
                $value = $this->encodeNonUtf8ToUtf8($value);
159
            }
160
161
            if (!mb_check_encoding($key, 'UTF-8')) {
162
                $key = mb_convert_encoding($key, 'UTF-8', '8bit');
163
                $encodedKeys[$key] = isset($encodedKeys[$key]) ? $encodedKeys[$key] : 0;
164
                $encodedKeys[$key] |= static::KEY_UTF8ENCODED;
165
            }
166
167
            if (is_string($value)) {
168
                if (!mb_check_encoding($value, 'UTF-8')) {
169
                    $value = mb_convert_encoding($value, 'UTF-8', '8bit');
170
                    $encodedKeys[$key] = isset($encodedKeys[$key]) ? $encodedKeys[$key] : 0;
171
                    $encodedKeys[$key] |= static::VALUE_UTF8ENCODED;
172
                }
173
            }
174
175
            $encodedData[$key] = $value;
176
        }
177
178
        if ($encodedKeys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encodedKeys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
179
            $encodedData[self::UTF8ENCODED_IDENTIFIER_KEY] = $encodedKeys;
180
        }
181
182
        return $encodedData;
183
    }
184
185
    /**
186
     * Execute post-encoding actions
187
     *
188
     * @param  string $encoded
189
     * @return string
190
     */
191
    protected function processEncodedValue($encoded)
192
    {
193
        if (!$this->preserveZeroFractionSupport) {
194
            $encoded = preg_replace('/"' . static::FLOAT_ADAPTER . '\((.*?)\)"/', '\1', $encoded);
195
        }
196
        return $encoded;
197
    }
198
199
    /**
200
     * Unserialize the value from JSON
201
     *
202
     * @param  string $value
203
     * @return mixed
204
     */
205
    public function unserialize($value)
206
    {
207
        $this->reset();
208
        $data = json_decode($value, true);
209
        if ($data === null && json_last_error() != JSON_ERROR_NONE) {
210
            throw new JsonSerializerException('Invalid JSON to unserialize.');
211
        }
212
213
        if (mb_strpos($value, static::UTF8ENCODED_IDENTIFIER_KEY) !== false) {
214
            $data = $this->decodeNonUtf8FromUtf8($data);
215
        }
216
217
        return $this->unserializeData($data);
218
    }
219
220
    /**
221
     * Set unserialization mode for undeclared class properties
222
     *
223
     * @param  integer $value One of the JsonSerializer::UNDECLARED_PROPERTY_MODE_*
224
     * @return self
225
     * @throws InvalidArgumentException When the value is not one of the UNDECLARED_PROPERTY_MODE_* options
226
     */
227
    public function setUnserializeUndeclaredPropertyMode($value)
228
    {
229
        $availableOptions = [
230
            static::UNDECLARED_PROPERTY_MODE_SET,
231
            static::UNDECLARED_PROPERTY_MODE_IGNORE,
232
            static::UNDECLARED_PROPERTY_MODE_EXCEPTION
233
        ];
234
        if (!in_array($value, $availableOptions)) {
235
            throw new InvalidArgumentException('Invalid value.');
236
        }
237
        $this->undefinedAttributeMode = $value;
238
        return $this;
239
    }
240
241
    /**
242
     * Parse the data to be json encoded
243
     *
244
     * @param  mixed $value
245
     * @return mixed
246
     * @throws JsonSerializerException
247
     */
248
    protected function serializeData($value)
249
    {
250
        if (is_scalar($value) || $value === null) {
251
            if (!$this->preserveZeroFractionSupport && is_float($value) && ctype_digit((string)$value)) {
252
                // Because the PHP bug #50224, the float numbers with no
253
                // precision numbers are converted to integers when encoded
254
                $value = static::FLOAT_ADAPTER . '(' . $value . '.0)';
255
            }
256
            return $value;
257
        }
258
        if (is_resource($value)) {
259
            throw new JsonSerializerException('Resource is not supported in JsonSerializer');
260
        }
261
        if (is_array($value)) {
262
            return array_map(array($this, __FUNCTION__), $value);
263
        }
264
        if ($value instanceof \Closure) {
265
            if (!$this->closureSerializer) {
266
                throw new JsonSerializerException('Closure serializer not given. Unable to serialize closure.');
267
            }
268
            return array(
269
                static::CLOSURE_IDENTIFIER_KEY => true,
270
                'value' => $this->closureSerializer->serialize($value)
271
            );
272
        }
273
        return $this->serializeObject($value);
274
    }
275
276
    /**
277
     * Extract the data from an object
278
     *
279
     * @param  object $value
280
     * @return array
281
     */
282
    protected function serializeObject($value)
283
    {
284
        if ($this->objectStorage->contains($value)) {
285
            return array(static::CLASS_IDENTIFIER_KEY => '@' . $this->objectStorage[$value]);
286
        }
287
        $this->objectStorage->attach($value, $this->objectMappingIndex++);
288
289
        $ref = new ReflectionClass($value);
290
        $className = $ref->getName();
291
        if (array_key_exists($className, $this->customObjectSerializerMap)) {
292
            $data = array(static::CLASS_IDENTIFIER_KEY => $className);
293
            $data += $this->customObjectSerializerMap[$className]->serialize($value);
294
            return $data;
295
        }
296
297
        $paramsToSerialize = $this->getObjectProperties($ref, $value);
298
        $data = array(static::CLASS_IDENTIFIER_KEY => $className);
299
300
        if ($value instanceof \SplDoublyLinkedList) {
301
            return $data + array('value' => $value->serialize());
302
        }
303
304
        $data += array_map(array($this, 'serializeData'), $this->extractObjectData($value, $ref, $paramsToSerialize));
305
        return $data;
306
    }
307
308
    /**
309
     * Return the list of properties to be serialized
310
     *
311
     * @param  ReflectionClass $ref
312
     * @param  object          $value
313
     * @return array
314
     */
315
    protected function getObjectProperties($ref, $value)
316
    {
317
        if (method_exists($value, '__sleep')) {
318
            return $value->__sleep();
319
        }
320
321
        $props = array();
322
        foreach ($ref->getProperties() as $prop) {
323
            $props[] = $prop->getName();
324
        }
325
        return array_unique(array_merge($props, array_keys(get_object_vars($value))));
326
    }
327
328
    /**
329
     * Extract the object data
330
     *
331
     * @param  object          $value
332
     * @param  ReflectionClass $ref
333
     * @param  array           $properties
334
     * @return array
335
     */
336
    protected function extractObjectData($value, $ref, $properties)
337
    {
338
        $data = array();
339
        foreach ($properties as $property) {
340
            try {
341
                $propRef = $ref->getProperty($property);
342
                $propRef->setAccessible(true);
343
                $data[$property] = $propRef->getValue($value);
344
            } catch (ReflectionException $e) {
345
                $data[$property] = $value->$property;
346
            }
347
        }
348
        return $data;
349
    }
350
351
    /**
352
     * Parse the json decode to convert to objects again
353
     *
354
     * @param  mixed $value
355
     * @return mixed
356
     */
357
    protected function unserializeData($value)
358
    {
359
        if (is_scalar($value) || $value === null) {
360
            return $value;
361
        }
362
363
        if (isset($value[static::CLASS_IDENTIFIER_KEY])) {
364
            return $this->unserializeObject($value);
0 ignored issues
show
It seems like $value defined by parameter $value on line 357 can also be of type object; however, Zumba\JsonSerializer\Jso...er::unserializeObject() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
365
        }
366
367
        if (!empty($value[static::CLOSURE_IDENTIFIER_KEY])) {
368
            if (!$this->closureSerializer) {
369
                throw new JsonSerializerException('Closure serializer not provided to unserialize closure');
370
            }
371
            return $this->closureSerializer->unserialize($value['value']);
372
        }
373
374
        return array_map(array($this, __FUNCTION__), $value);
375
    }
376
377
    /**
378
     *
379
     * @param mixed $serializedData
380
     *
381
     * @return mixed
382
     */
383
    protected function decodeNonUtf8FromUtf8($serializedData)
384
    {
385
        if (is_array($serializedData) && isset($serializedData[static::SCALAR_IDENTIFIER_KEY])) {
386
            $serializedData = mb_convert_encoding($serializedData[static::SCALAR_IDENTIFIER_KEY], '8bit', 'UTF-8');
387
            return $serializedData;
388
        } elseif (is_scalar($serializedData) || $serializedData === null) {
389
            return $serializedData;
390
        }
391
392
        $encodedKeys = [];
393
        if (isset($serializedData[static::UTF8ENCODED_IDENTIFIER_KEY])) {
394
            $encodedKeys = $serializedData[static::UTF8ENCODED_IDENTIFIER_KEY];
395
            unset($serializedData[static::UTF8ENCODED_IDENTIFIER_KEY]);
396
        }
397
398
        $decodedData = [];
399
        foreach ($serializedData as $key => $value) {
400
            if (is_array($value)) {
401
                $value = $this->decodeNonUtf8FromUtf8($value);
402
            }
403
404
            if (isset($encodedKeys[$key])) {
405
                $originalKey = $key;
406
                if ($encodedKeys[$key] & static::KEY_UTF8ENCODED) {
407
                    $key = mb_convert_encoding($key, '8bit', 'UTF-8');
408
                }
409
                if ($encodedKeys[$originalKey] & static::VALUE_UTF8ENCODED) {
410
                    $value = mb_convert_encoding($value, '8bit', 'UTF-8');
411
                }
412
            }
413
414
            $decodedData[$key] = $value;
415
        }
416
417
        return $decodedData;
418
    }
419
420
    /**
421
     * Convert the serialized array into an object
422
     *
423
     * @param  array $value
424
     * @return object
425
     * @throws JsonSerializerException
426
     */
427
    protected function unserializeObject($value)
428
    {
429
        $className = $value[static::CLASS_IDENTIFIER_KEY];
430
        unset($value[static::CLASS_IDENTIFIER_KEY]);
431
432
        if ($className[0] === '@') {
433
            $index = substr($className, 1);
434
            return $this->objectMapping[$index];
435
        }
436
437
        if (array_key_exists($className, $this->customObjectSerializerMap)) {
438
            $obj = $this->customObjectSerializerMap[$className]->unserialize($value);
439
            $this->objectMapping[$this->objectMappingIndex++] = $obj;
440
            return $obj;
441
        }
442
443
        if (!class_exists($className)) {
444
            throw new JsonSerializerException('Unable to find class ' . $className);
445
        }
446
447
        if ($className === 'DateTime') {
448
            $obj = $this->restoreUsingUnserialize($className, $value);
449
            $this->objectMapping[$this->objectMappingIndex++] = $obj;
450
            return $obj;
451
        }
452
453
        if (!$this->isSplList($className)) {
454
            $ref = new ReflectionClass($className);
455
            $obj = $ref->newInstanceWithoutConstructor();
456
        } else {
457
            $obj = new $className();
458
        }
459
460
        if ($obj instanceof \SplDoublyLinkedList) {
461
            $obj->unserialize($value['value']);
462
            $this->objectMapping[$this->objectMappingIndex++] = $obj;
463
            return $obj;
464
        }
465
466
        $this->objectMapping[$this->objectMappingIndex++] = $obj;
467
        foreach ($value as $property => $propertyValue) {
468
            try {
469
                $propRef = $ref->getProperty($property);
0 ignored issues
show
The variable $ref 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...
470
                $propRef->setAccessible(true);
471
                $propRef->setValue($obj, $this->unserializeData($propertyValue));
472
            } catch (ReflectionException $e) {
473
                switch ($this->undefinedAttributeMode) {
474
                    case static::UNDECLARED_PROPERTY_MODE_SET:
475
                        $obj->$property = $this->unserializeData($propertyValue);
476
                        break;
477
                    case static::UNDECLARED_PROPERTY_MODE_IGNORE:
478
                        break;
479
                    case static::UNDECLARED_PROPERTY_MODE_EXCEPTION:
480
                        throw new JsonSerializerException('Undefined attribute detected during unserialization');
481
                        break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
482
                }
483
            }
484
        }
485
        if (method_exists($obj, '__wakeup')) {
486
            $obj->__wakeup();
487
        }
488
        return $obj;
489
    }
490
491
    /**
492
     *
493
     * @return boolean
494
     */
495
    protected function isSplList($className)
496
    {
497
        return in_array($className, array('SplQueue', 'SplDoublyLinkedList', 'SplStack'));
498
    }
499
500
    protected function restoreUsingUnserialize($className, $attributes)
501
    {
502
        $obj = (object)$attributes;
503
        $serialized = preg_replace(
504
            '|^O:\d+:"\w+":|',
505
            'O:' . strlen($className) . ':"' . $className . '":',
506
            serialize($obj)
507
        );
508
        return unserialize($serialized);
509
    }
510
511
    /**
512
     * Reset variables
513
     *
514
     * @return void
515
     */
516
    protected function reset()
517
    {
518
        $this->objectStorage = new SplObjectStorage();
519
        $this->objectMapping = array();
520
        $this->objectMappingIndex = 0;
521
    }
522
}
523