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/Serializer.php (3 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 NilPortugues\Serializer;
4
5
use Closure;
6
use NilPortugues\Serializer\Strategy\StrategyInterface;
7
use ReflectionClass;
8
use ReflectionException;
9
use SplObjectStorage;
10
11
class Serializer
12
{
13
    const CLASS_IDENTIFIER_KEY = '@type';
14
    const SCALAR_TYPE = '@scalar';
15
    const SCALAR_VALUE = '@value';
16
    const NULL_VAR = null;
17
    const MAP_TYPE = '@map';
18
19
    /**
20
     * Storage for object.
21
     *
22
     * Used for recursion
23
     *
24
     * @var SplObjectStorage
25
     */
26
    protected static $objectStorage;
27
28
    /**
29
     * Object mapping for recursion.
30
     *
31
     * @var array
32
     */
33
    protected static $objectMapping = [];
34
35
    /**
36
     * Object mapping index.
37
     *
38
     * @var int
39
     */
40
    protected static $objectMappingIndex = 0;
41
42
    /**
43
     * @var \NilPortugues\Serializer\Strategy\StrategyInterface|\NilPortugues\Serializer\Strategy\JsonStrategy
44
     */
45
    protected $serializationStrategy;
46
47
    /**
48
     * @var array
49
     */
50
    private $dateTimeClassType = ['DateTime', 'DateTimeImmutable', 'DateTimeZone', 'DateInterval', 'DatePeriod'];
51
52
    /**
53
     * @var array
54
     */
55
    protected $serializationMap = [
56
        'array' => 'serializeArray',
57
        'integer' => 'serializeScalar',
58
        'double' => 'serializeScalar',
59
        'boolean' => 'serializeScalar',
60
        'string' => 'serializeScalar',
61
    ];
62
63
    /**
64
     * @var bool
65
     */
66
    protected $isHHVM;
67
68
    /**
69
     * Hack specific serialization classes.
70
     *
71
     * @var array
72
     */
73
    protected $unserializationMapHHVM = [];
74
75
    /**
76
     * @param StrategyInterface $strategy
77
     */
78
    public function __construct(StrategyInterface $strategy)
79
    {
80
        $this->isHHVM = \defined('HHVM_VERSION');
81
        if ($this->isHHVM) {
82
            // @codeCoverageIgnoreStart
83
            $this->serializationMap = \array_merge(
84
                $this->serializationMap,
85
                include \realpath(\dirname(__FILE__).'/Mapping/serialization_hhvm.php')
86
            );
87
            $this->unserializationMapHHVM = include \realpath(\dirname(__FILE__).'/Mapping/unserialization_hhvm.php');
88
            // @codeCoverageIgnoreEnd
89
        }
90
        $this->serializationStrategy = $strategy;
91
    }
92
93
    /**
94
     * This is handly specially in order to add additional data before the
95
     * serialization process takes place using the transformer public methods, if any.
96
     *
97
     * @return StrategyInterface
98
     */
99
    public function getTransformer()
100
    {
101
        return $this->serializationStrategy;
102
    }
103
104
    /**
105
     * Serialize the value in JSON.
106
     *
107
     * @param mixed $value
108
     *
109
     * @return string JSON encoded
110
     *
111
     * @throws SerializerException
112
     */
113
    public function serialize($value)
114
    {
115
        $this->reset();
116
117
        return $this->serializationStrategy->serialize($this->serializeData($value));
118
    }
119
120
    /**
121
     * Reset variables.
122
     */
123
    protected function reset()
124
    {
125
        self::$objectStorage = new SplObjectStorage();
126
        self::$objectMapping = [];
127
        self::$objectMappingIndex = 0;
128
    }
129
130
    /**
131
     * Parse the data to be json encoded.
132
     *
133
     * @param mixed $value
134
     *
135
     * @return mixed
136
     *
137
     * @throws SerializerException
138
     */
139
    protected function serializeData($value)
140
    {
141
        $this->guardForUnsupportedValues($value);
142
143
        if ($this->isHHVM && ($value instanceof \DateTimeZone || $value instanceof \DateInterval)) {
144
            // @codeCoverageIgnoreStart
145
            return \call_user_func_array($this->serializationMap[get_class($value)], [$this, $value]);
146
            // @codeCoverageIgnoreEnd
147
        }
148
149
        if (\is_object($value)) {
150
            return $this->serializeObject($value);
151
        }
152
153
        $type = (\gettype($value) && $value !== null) ? \gettype($value) : 'string';
154
        $func = $this->serializationMap[$type];
155
156
        return $this->$func($value);
157
    }
158
159
    /**
160
     * @param mixed $value
161
     *
162
     * @throws SerializerException
163
     */
164
    protected function guardForUnsupportedValues($value)
165
    {
166
        if ($value instanceof Closure) {
167
            throw new SerializerException('Closures are not supported in Serializer');
168
        }
169
170
        if ($value instanceof \DatePeriod) {
171
            throw new SerializerException(
172
                'DatePeriod is not supported in Serializer. Loop through it and serialize the output.'
173
            );
174
        }
175
176
        if (\is_resource($value)) {
177
            throw new SerializerException('Resource is not supported in Serializer');
178
        }
179
    }
180
181
    /**
182
     * Unserialize the value from string.
183
     *
184
     * @param mixed $value
185
     *
186
     * @return mixed
187
     */
188
    public function unserialize($value)
189
    {
190
        if (\is_array($value) && isset($value[self::SCALAR_TYPE])) {
191
            return $this->unserializeData($value);
192
        }
193
194
        $this->reset();
195
196
        return $this->unserializeData($this->serializationStrategy->unserialize($value));
197
    }
198
199
    /**
200
     * Parse the json decode to convert to objects again.
201
     *
202
     * @param mixed $value
203
     *
204
     * @return mixed
205
     */
206
    protected function unserializeData($value)
207
    {
208
        if ($value === null || !is_array($value)) {
209
            return $value;
210
        }
211
212
        if (isset($value[self::MAP_TYPE]) && !isset($value[self::CLASS_IDENTIFIER_KEY])) {
213
            $value = $value[self::SCALAR_VALUE];
214
215
            return $this->unserializeData($value);
216
        }
217
218
        if (isset($value[self::SCALAR_TYPE])) {
219
            return $this->getScalarValue($value);
220
        }
221
222
        if (isset($value[self::CLASS_IDENTIFIER_KEY])) {
223
            return $this->unserializeObject($value);
224
        }
225
226
        return \array_map([$this, __FUNCTION__], $value);
227
    }
228
229
    /**
230
     * @param $value
231
     *
232
     * @return float|int|null|bool
233
     */
234
    protected function getScalarValue($value)
235
    {
236
        switch ($value[self::SCALAR_TYPE]) {
237
            case 'integer':
238
                return \intval($value[self::SCALAR_VALUE]);
239
            case 'float':
240
                return \floatval($value[self::SCALAR_VALUE]);
241
            case 'boolean':
242
                return $value[self::SCALAR_VALUE];
243
            case 'NULL':
244
                return self::NULL_VAR;
245
        }
246
247
        return $value[self::SCALAR_VALUE];
248
    }
249
250
    /**
251
     * Convert the serialized array into an object.
252
     *
253
     * @param array $value
254
     *
255
     * @return object
256
     *
257
     * @throws SerializerException
258
     */
259
    protected function unserializeObject(array $value)
260
    {
261
        $className = $value[self::CLASS_IDENTIFIER_KEY];
262
        unset($value[self::CLASS_IDENTIFIER_KEY]);
263
264
        if (isset($value[self::MAP_TYPE])) {
265
            unset($value[self::MAP_TYPE]);
266
            unset($value[self::SCALAR_VALUE]);
267
        }
268
269
        if ($className[0] === '@') {
270
            return self::$objectMapping[substr($className, 1)];
271
        }
272
273
        if (!class_exists($className)) {
274
            throw new SerializerException('Unable to find class '.$className);
275
        }
276
277
        return (null === ($obj = $this->unserializeDateTimeFamilyObject($value, $className)))
278
            ? $this->unserializeUserDefinedObject($value, $className) : $obj;
279
    }
280
281
    /**
282
     * @param array  $value
283
     * @param string $className
284
     *
285
     * @return mixed
286
     */
287
    protected function unserializeDateTimeFamilyObject(array $value, $className)
288
    {
289
        $obj = null;
290
291
        if ($this->isDateTimeFamilyObject($className)) {
292
            if ($this->isHHVM) {
293
                // @codeCoverageIgnoreStart
294
                return \call_user_func_array(
295
                    $this->unserializationMapHHVM[$className],
296
                    [$this, $className, $value]
297
                );
298
                // @codeCoverageIgnoreEnd
299
            }
300
301
            $obj = $this->restoreUsingUnserialize($className, $value);
302
            self::$objectMapping[self::$objectMappingIndex++] = $obj;
303
        }
304
305
        return $obj;
306
    }
307
308
    /**
309
     * @param string $className
310
     *
311
     * @return bool
312
     */
313
    protected function isDateTimeFamilyObject($className)
314
    {
315
        $isDateTime = false;
316
317
        foreach ($this->dateTimeClassType as $class) {
318
            $isDateTime = $isDateTime || \is_subclass_of($className, $class, true) || $class === $className;
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
319
        }
320
321
        return $isDateTime;
322
    }
323
324
    /**
325
     * @param string $className
326
     * @param array  $attributes
327
     *
328
     * @return mixed
329
     */
330
    protected function restoreUsingUnserialize($className, array $attributes)
331
    {
332
        foreach ($attributes as &$attribute) {
333
            $attribute = $this->unserializeData($attribute);
334
        }
335
336
        $obj = (object) $attributes;
337
        $serialized = \preg_replace(
338
            '|^O:\d+:"\w+":|',
339
            'O:'.strlen($className).':"'.$className.'":',
340
            \serialize($obj)
341
        );
342
343
        return \unserialize($serialized);
344
    }
345
346
    /**
347
     * @param array  $value
348
     * @param string $className
349
     *
350
     * @return object
351
     */
352
    protected function unserializeUserDefinedObject(array $value, $className)
353
    {
354
        $ref = new ReflectionClass($className);
355
        $obj = $ref->newInstanceWithoutConstructor();
356
357
        self::$objectMapping[self::$objectMappingIndex++] = $obj;
358
        $this->setUnserializedObjectProperties($value, $ref, $obj);
359
360
        if (\method_exists($obj, '__wakeup')) {
361
            $obj->__wakeup();
362
        }
363
364
        return $obj;
365
    }
366
367
    /**
368
     * @param array           $value
369
     * @param ReflectionClass $ref
370
     * @param mixed           $obj
371
     *
372
     * @return mixed
373
     */
374
    protected function setUnserializedObjectProperties(array $value, ReflectionClass $ref, $obj)
375
    {
376
        foreach ($value as $property => $propertyValue) {
377
            try {
378
                $propRef = $ref->getProperty($property);
379
                $propRef->setAccessible(true);
380
                $propRef->setValue($obj, $this->unserializeData($propertyValue));
381
            } catch (ReflectionException $e) {
382
                $obj->$property = $this->unserializeData($propertyValue);
383
            }
384
        }
385
386
        return $obj;
387
    }
388
389
    /**
390
     * @param $value
391
     *
392
     * @return string
393
     */
394
    protected function serializeScalar($value)
395
    {
396
        $type = \gettype($value);
397
        if ($type === 'double') {
398
            $type = 'float';
399
        }
400
401
        return [
402
            self::SCALAR_TYPE => $type,
403
            self::SCALAR_VALUE => $value,
404
        ];
405
    }
406
407
    /**
408
     * @param array $value
409
     *
410
     * @return array
411
     */
412
    protected function serializeArray(array $value)
413
    {
414
        if (\array_key_exists(self::MAP_TYPE, $value)) {
415
            return $value;
416
        }
417
418
        $toArray = [self::MAP_TYPE => 'array', self::SCALAR_VALUE => []];
419
        foreach ($value as $key => $field) {
420
            $toArray[self::SCALAR_VALUE][$key] = $this->serializeData($field);
421
        }
422
423
        return $this->serializeData($toArray);
424
    }
425
426
    /**
427
     * Extract the data from an object.
428
     *
429
     * @param mixed $value
430
     *
431
     * @return array
432
     */
433
    protected function serializeObject($value)
434
    {
435
        if (self::$objectStorage->contains($value)) {
436
            return [self::CLASS_IDENTIFIER_KEY => '@'.self::$objectStorage[$value]];
437
        }
438
439
        self::$objectStorage->attach($value, self::$objectMappingIndex++);
440
441
        $reflection = new ReflectionClass($value);
442
        $className = $reflection->getName();
0 ignored issues
show
Consider using $reflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
443
444
        return $this->serializeInternalClass($value, $className, $reflection);
445
    }
446
447
    /**
448
     * @param mixed           $value
449
     * @param string          $className
450
     * @param ReflectionClass $ref
451
     *
452
     * @return array
453
     */
454
    protected function serializeInternalClass($value, $className, ReflectionClass $ref)
455
    {
456
        $paramsToSerialize = $this->getObjectProperties($ref, $value);
457
        $data = [self::CLASS_IDENTIFIER_KEY => $className];
458
        $data += \array_map([$this, 'serializeData'], $this->extractObjectData($value, $ref, $paramsToSerialize));
459
460
        return $data;
461
    }
462
463
    /**
464
     * Return the list of properties to be serialized.
465
     *
466
     * @param ReflectionClass $ref
467
     * @param $value
468
     *
469
     * @return array
470
     */
471
    protected function getObjectProperties(ReflectionClass $ref, $value)
472
    {
473
        $props = [];
474
        foreach ($ref->getProperties() as $prop) {
475
            $props[] = $prop->getName();
476
        }
477
478
        return \array_unique(\array_merge($props, \array_keys(\get_object_vars($value))));
479
    }
480
481
    /**
482
     * Extract the object data.
483
     *
484
     * @param mixed            $value
485
     * @param \ReflectionClass $rc
486
     * @param array            $properties
487
     *
488
     * @return array
489
     */
490
    protected function extractObjectData($value, ReflectionClass $rc, array $properties)
491
    {
492
        $data = [];
493
494
        $this->extractCurrentObjectProperties($value, $rc, $properties, $data);
495
        $this->extractAllInhertitedProperties($value, $rc, $data);
496
497
        return $data;
498
    }
499
500
    /**
501
     * @param mixed           $value
502
     * @param ReflectionClass $rc
503
     * @param array           $properties
504
     * @param array           $data
505
     */
506
    protected function extractCurrentObjectProperties($value, ReflectionClass $rc, array $properties, array &$data)
507
    {
508
        foreach ($properties as $propertyName) {
509
            try {
510
                $propRef = $rc->getProperty($propertyName);
511
                $propRef->setAccessible(true);
512
                $data[$propertyName] = $propRef->getValue($value);
513
            } catch (ReflectionException $e) {
514
                $data[$propertyName] = $value->$propertyName;
515
            }
516
        }
517
    }
518
519
    /**
520
     * @param mixed           $value
521
     * @param ReflectionClass $rc
522
     * @param array           $data
523
     */
524
    protected function extractAllInhertitedProperties($value, ReflectionClass $rc, array &$data)
0 ignored issues
show
The parameter $value is not used and could be removed.

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

Loading history...
525
    {
526
        do {
527
            $rp = array();
528
            /* @var $property \ReflectionProperty */
529
            foreach ($rc->getProperties() as $property) {
530
                $property->setAccessible(true);
531
                $rp[$property->getName()] = $property->getValue($this);
532
            }
533
            $data = \array_merge($rp, $data);
534
        } while ($rc = $rc->getParentClass());
535
    }
536
}
537