PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / vendor / opis / closure / src / SerializableClosure.php

SerializableClosure.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at vendor/opis/closure/src/SerializableClosure.php

679 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* ===========================================================================
3 * Copyright (c) 2018-2021 Zindex Software
4 *
5 * Licensed under the MIT License
6 * =========================================================================== */
7
8 namespace Opis\Closure;
9
10 use Closure;
11 use Serializable;
12 use SplObjectStorage;
13 use ReflectionObject;
14
15 /**
16 * Provides a wrapper for serialization of closures
17 */
18 class SerializableClosure implements Serializable
19 {
20 /**
21 * @var Closure Wrapped closure
22 *
23 * @see \Opis\Closure\SerializableClosure::getClosure()
24 */
25 protected $closure;
26
27 /**
28 * @var ReflectionClosure A reflection instance for closure
29 *
30 * @see \Opis\Closure\SerializableClosure::getReflector()
31 */
32 protected $reflector;
33
34 /**
35 * @var mixed Used at deserialization to hold variables
36 *
37 * @see \Opis\Closure\SerializableClosure::unserialize()
38 * @see \Opis\Closure\SerializableClosure::getReflector()
39 */
40 protected $code;
41
42 /**
43 * @var string Closure's ID
44 */
45 protected $reference;
46
47 /**
48 * @var string Closure scope
49 */
50 protected $scope;
51
52 /**
53 * @var ClosureContext Context of closure, used in serialization
54 */
55 protected static $context;
56
57 /**
58 * @var ISecurityProvider|null
59 */
60 protected static $securityProvider;
61
62 /** Array recursive constant*/
63 const ARRAY_RECURSIVE_KEY = '¯\_(ツ)_/¯';
64
65 /**
66 * Constructor
67 *
68 * @param Closure $closure Closure you want to serialize
69 */
70 public function __construct(Closure $closure)
71 {
72 $this->closure = $closure;
73 if (static::$context !== null) {
74 $this->scope = static::$context->scope;
75 $this->scope->toserialize++;
76 }
77 }
78
79 /**
80 * Get the Closure object
81 *
82 * @return Closure The wrapped closure
83 */
84 public function getClosure()
85 {
86 return $this->closure;
87 }
88
89 /**
90 * Get the reflector for closure
91 *
92 * @return ReflectionClosure
93 */
94 public function getReflector()
95 {
96 if ($this->reflector === null) {
97 $this->reflector = new ReflectionClosure($this->closure);
98 $this->code = null;
99 }
100
101 return $this->reflector;
102 }
103
104 /**
105 * Implementation of magic method __invoke()
106 */
107 public function __invoke()
108 {
109 return call_user_func_array($this->closure, func_get_args());
110 }
111
112 /**
113 * Implementation of Serializable::serialize()
114 *
115 * @return string The serialized closure
116 */
117 public function serialize()
118 {
119 if ($this->scope === null) {
120 $this->scope = new ClosureScope();
121 $this->scope->toserialize++;
122 }
123
124 $this->scope->serializations++;
125
126 $scope = $object = null;
127 $reflector = $this->getReflector();
128
129 if($reflector->isBindingRequired()){
130 $object = $reflector->getClosureThis();
131 static::wrapClosures($object, $this->scope);
132 if($scope = $reflector->getClosureScopeClass()){
133 $scope = $scope->name;
134 }
135 } else {
136 if($scope = $reflector->getClosureScopeClass()){
137 $scope = $scope->name;
138 }
139 }
140
141 $this->reference = spl_object_hash($this->closure);
142
143 $this->scope[$this->closure] = $this;
144
145 $use = $this->transformUseVariables($reflector->getUseVariables());
146 $code = $reflector->getCode();
147
148 $this->mapByReference($use);
149
150 $ret = \serialize(array(
151 'use' => $use,
152 'function' => $code,
153 'scope' => $scope,
154 'this' => $object,
155 'self' => $this->reference,
156 ));
157
158 if (static::$securityProvider !== null) {
159 $data = static::$securityProvider->sign($ret);
160 $ret = '@' . $data['hash'] . '.' . $data['closure'];
161 }
162
163 if (!--$this->scope->serializations && !--$this->scope->toserialize) {
164 $this->scope = null;
165 }
166
167 return $ret;
168 }
169
170 /**
171 * Transform the use variables before serialization.
172 *
173 * @param array $data The Closure's use variables
174 * @return array
175 */
176 protected function transformUseVariables($data)
177 {
178 return $data;
179 }
180
181 /**
182 * Implementation of Serializable::unserialize()
183 *
184 * @param string $data Serialized data
185 * @throws SecurityException
186 */
187 public function unserialize($data)
188 {
189 ClosureStream::register();
190
191 if (static::$securityProvider !== null) {
192 if ($data[0] !== '@') {
193 throw new SecurityException("The serialized closure is not signed. ".
194 "Make sure you use a security provider for both serialization and unserialization.");
195 }
196
197 if ($data[1] !== '{') {
198 $separator = strpos($data, '.');
199 if ($separator === false) {
200 throw new SecurityException('Invalid signed closure');
201 }
202 $hash = substr($data, 1, $separator - 1);
203 $closure = substr($data, $separator + 1);
204
205 $data = ['hash' => $hash, 'closure' => $closure];
206
207 unset($hash, $closure);
208 } else {
209 $data = json_decode(substr($data, 1), true);
210 }
211
212 if (!is_array($data) || !static::$securityProvider->verify($data)) {
213 throw new SecurityException("Your serialized closure might have been modified and it's unsafe to be unserialized. " .
214 "Make sure you use the same security provider, with the same settings, " .
215 "both for serialization and unserialization.");
216 }
217
218 $data = $data['closure'];
219 } elseif ($data[0] === '@') {
220 if ($data[1] !== '{') {
221 $separator = strpos($data, '.');
222 if ($separator === false) {
223 throw new SecurityException('Invalid signed closure');
224 }
225 $hash = substr($data, 1, $separator - 1);
226 $closure = substr($data, $separator + 1);
227
228 $data = ['hash' => $hash, 'closure' => $closure];
229
230 unset($hash, $closure);
231 } else {
232 $data = json_decode(substr($data, 1), true);
233 }
234
235 if (!is_array($data) || !isset($data['closure']) || !isset($data['hash'])) {
236 throw new SecurityException('Invalid signed closure');
237 }
238
239 $data = $data['closure'];
240 }
241
242 $this->code = \unserialize($data);
243
244 // unset data
245 unset($data);
246
247 $this->code['objects'] = array();
248
249 if ($this->code['use']) {
250 $this->scope = new ClosureScope();
251 $this->code['use'] = $this->resolveUseVariables($this->code['use']);
252 $this->mapPointers($this->code['use']);
253 extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);
254 $this->scope = null;
255 }
256
257 $this->closure = include(ClosureStream::STREAM_PROTO . '://' . $this->code['function']);
258
259 if($this->code['this'] === $this){
260 $this->code['this'] = null;
261 }
262
263 $this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
264
265 if(!empty($this->code['objects'])){
266 foreach ($this->code['objects'] as $item){
267 $item['property']->setValue($item['instance'], $item['object']->getClosure());
268 }
269 }
270
271 $this->code = $this->code['function'];
272 }
273
274 /**
275 * Resolve the use variables after unserialization.
276 *
277 * @param array $data The Closure's transformed use variables
278 * @return array
279 */
280 protected function resolveUseVariables($data)
281 {
282 return $data;
283 }
284
285 /**
286 * Wraps a closure and sets the serialization context (if any)
287 *
288 * @param Closure $closure Closure to be wrapped
289 *
290 * @return self The wrapped closure
291 */
292 public static function from(Closure $closure)
293 {
294 if (static::$context === null) {
295 $instance = new static($closure);
296 } elseif (isset(static::$context->scope[$closure])) {
297 $instance = static::$context->scope[$closure];
298 } else {
299 $instance = new static($closure);
300 static::$context->scope[$closure] = $instance;
301 }
302
303 return $instance;
304 }
305
306 /**
307 * Increments the context lock counter or creates a new context if none exist
308 */
309 public static function enterContext()
310 {
311 if (static::$context === null) {
312 static::$context = new ClosureContext();
313 }
314
315 static::$context->locks++;
316 }
317
318 /**
319 * Decrements the context lock counter and destroy the context when it reaches to 0
320 */
321 public static function exitContext()
322 {
323 if (static::$context !== null && !--static::$context->locks) {
324 static::$context = null;
325 }
326 }
327
328 /**
329 * @param string $secret
330 */
331 public static function setSecretKey($secret)
332 {
333 if(static::$securityProvider === null){
334 static::$securityProvider = new SecurityProvider($secret);
335 }
336 }
337
338 /**
339 * @param ISecurityProvider $securityProvider
340 */
341 public static function addSecurityProvider(ISecurityProvider $securityProvider)
342 {
343 static::$securityProvider = $securityProvider;
344 }
345
346 /**
347 * Remove security provider
348 */
349 public static function removeSecurityProvider()
350 {
351 static::$securityProvider = null;
352 }
353
354 /**
355 * @return null|ISecurityProvider
356 */
357 public static function getSecurityProvider()
358 {
359 return static::$securityProvider;
360 }
361
362 /**
363 * Wrap closures
364 *
365 * @internal
366 * @param $data
367 * @param ClosureScope|SplObjectStorage|null $storage
368 */
369 public static function wrapClosures(&$data, SplObjectStorage $storage = null)
370 {
371 if($storage === null){
372 $storage = static::$context->scope;
373 }
374
375 if($data instanceof Closure){
376 $data = static::from($data);
377 } elseif (is_array($data)){
378 if(isset($data[self::ARRAY_RECURSIVE_KEY])){
379 return;
380 }
381 $data[self::ARRAY_RECURSIVE_KEY] = true;
382 foreach ($data as $key => &$value){
383 if($key === self::ARRAY_RECURSIVE_KEY){
384 continue;
385 }
386 static::wrapClosures($value, $storage);
387 }
388 unset($value);
389 unset($data[self::ARRAY_RECURSIVE_KEY]);
390 } elseif($data instanceof \stdClass){
391 if(isset($storage[$data])){
392 $data = $storage[$data];
393 return;
394 }
395 $data = $storage[$data] = clone($data);
396 foreach ($data as &$value){
397 static::wrapClosures($value, $storage);
398 }
399 unset($value);
400 } elseif (is_object($data) && ! $data instanceof static){
401 if(isset($storage[$data])){
402 $data = $storage[$data];
403 return;
404 }
405 $instance = $data;
406 $reflection = new ReflectionObject($instance);
407 if(!$reflection->isUserDefined()){
408 $storage[$instance] = $data;
409 return;
410 }
411 $storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
412
413 do{
414 if(!$reflection->isUserDefined()){
415 break;
416 }
417 foreach ($reflection->getProperties() as $property){
418 if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
419 continue;
420 }
421 $property->setAccessible(true);
422 if (PHP_VERSION >= 7.4 && !$property->isInitialized($instance)) {
423 continue;
424 }
425 $value = $property->getValue($instance);
426 if(is_array($value) || is_object($value)){
427 static::wrapClosures($value, $storage);
428 }
429 $property->setValue($data, $value);
430 };
431 } while($reflection = $reflection->getParentClass());
432 }
433 }
434
435 /**
436 * Unwrap closures
437 *
438 * @internal
439 * @param $data
440 * @param SplObjectStorage|null $storage
441 */
442 public static function unwrapClosures(&$data, SplObjectStorage $storage = null)
443 {
444 if($storage === null){
445 $storage = static::$context->scope;
446 }
447
448 if($data instanceof static){
449 $data = $data->getClosure();
450 } elseif (is_array($data)){
451 if(isset($data[self::ARRAY_RECURSIVE_KEY])){
452 return;
453 }
454 $data[self::ARRAY_RECURSIVE_KEY] = true;
455 foreach ($data as $key => &$value){
456 if($key === self::ARRAY_RECURSIVE_KEY){
457 continue;
458 }
459 static::unwrapClosures($value, $storage);
460 }
461 unset($data[self::ARRAY_RECURSIVE_KEY]);
462 }elseif ($data instanceof \stdClass){
463 if(isset($storage[$data])){
464 return;
465 }
466 $storage[$data] = true;
467 foreach ($data as &$property){
468 static::unwrapClosures($property, $storage);
469 }
470 } elseif (is_object($data) && !($data instanceof Closure)){
471 if(isset($storage[$data])){
472 return;
473 }
474 $storage[$data] = true;
475 $reflection = new ReflectionObject($data);
476
477 do{
478 if(!$reflection->isUserDefined()){
479 break;
480 }
481 foreach ($reflection->getProperties() as $property){
482 if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
483 continue;
484 }
485 $property->setAccessible(true);
486 if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) {
487 continue;
488 }
489 $value = $property->getValue($data);
490 if(is_array($value) || is_object($value)){
491 static::unwrapClosures($value, $storage);
492 $property->setValue($data, $value);
493 }
494 };
495 } while($reflection = $reflection->getParentClass());
496 }
497 }
498
499 /**
500 * Creates a new closure from arbitrary code,
501 * emulating create_function, but without using eval
502 *
503 * @param string$args
504 * @param string $code
505 * @return Closure
506 */
507 public static function createClosure($args, $code)
508 {
509 ClosureStream::register();
510 return include(ClosureStream::STREAM_PROTO . '://function(' . $args. '){' . $code . '};');
511 }
512
513 /**
514 * Internal method used to map closure pointers
515 * @internal
516 * @param $data
517 */
518 protected function mapPointers(&$data)
519 {
520 $scope = $this->scope;
521
522 if ($data instanceof static) {
523 $data = &$data->closure;
524 } elseif (is_array($data)) {
525 if(isset($data[self::ARRAY_RECURSIVE_KEY])){
526 return;
527 }
528 $data[self::ARRAY_RECURSIVE_KEY] = true;
529 foreach ($data as $key => &$value){
530 if($key === self::ARRAY_RECURSIVE_KEY){
531 continue;
532 } elseif ($value instanceof static) {
533 $data[$key] = &$value->closure;
534 } elseif ($value instanceof SelfReference && $value->hash === $this->code['self']){
535 $data[$key] = &$this->closure;
536 } else {
537 $this->mapPointers($value);
538 }
539 }
540 unset($value);
541 unset($data[self::ARRAY_RECURSIVE_KEY]);
542 } elseif ($data instanceof \stdClass) {
543 if(isset($scope[$data])){
544 return;
545 }
546 $scope[$data] = true;
547 foreach ($data as $key => &$value){
548 if ($value instanceof SelfReference && $value->hash === $this->code['self']){
549 $data->{$key} = &$this->closure;
550 } elseif(is_array($value) || is_object($value)) {
551 $this->mapPointers($value);
552 }
553 }
554 unset($value);
555 } elseif (is_object($data) && !($data instanceof Closure)){
556 if(isset($scope[$data])){
557 return;
558 }
559 $scope[$data] = true;
560 $reflection = new ReflectionObject($data);
561 do{
562 if(!$reflection->isUserDefined()){
563 break;
564 }
565 foreach ($reflection->getProperties() as $property){
566 if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
567 continue;
568 }
569 $property->setAccessible(true);
570 if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) {
571 continue;
572 }
573 $item = $property->getValue($data);
574 if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
575 $this->code['objects'][] = array(
576 'instance' => $data,
577 'property' => $property,
578 'object' => $item instanceof SelfReference ? $this : $item,
579 );
580 } elseif (is_array($item) || is_object($item)) {
581 $this->mapPointers($item);
582 $property->setValue($data, $item);
583 }
584 }
585 } while($reflection = $reflection->getParentClass());
586 }
587 }
588
589 /**
590 * Internal method used to map closures by reference
591 *
592 * @internal
593 * @param mixed &$data
594 */
595 protected function mapByReference(&$data)
596 {
597 if ($data instanceof Closure) {
598 if($data === $this->closure){
599 $data = new SelfReference($this->reference);
600 return;
601 }
602
603 if (isset($this->scope[$data])) {
604 $data = $this->scope[$data];
605 return;
606 }
607
608 $instance = new static($data);
609
610 if (static::$context !== null) {
611 static::$context->scope->toserialize--;
612 } else {
613 $instance->scope = $this->scope;
614 }
615
616 $data = $this->scope[$data] = $instance;
617 } elseif (is_array($data)) {
618 if(isset($data[self::ARRAY_RECURSIVE_KEY])){
619 return;
620 }
621 $data[self::ARRAY_RECURSIVE_KEY] = true;
622 foreach ($data as $key => &$value){
623 if($key === self::ARRAY_RECURSIVE_KEY){
624 continue;
625 }
626 $this->mapByReference($value);
627 }
628 unset($value);
629 unset($data[self::ARRAY_RECURSIVE_KEY]);
630 } elseif ($data instanceof \stdClass) {
631 if(isset($this->scope[$data])){
632 $data = $this->scope[$data];
633 return;
634 }
635 $instance = $data;
636 $this->scope[$instance] = $data = clone($data);
637
638 foreach ($data as &$value){
639 $this->mapByReference($value);
640 }
641 unset($value);
642 } elseif (is_object($data) && !$data instanceof SerializableClosure){
643 if(isset($this->scope[$data])){
644 $data = $this->scope[$data];
645 return;
646 }
647
648 $instance = $data;
649 $reflection = new ReflectionObject($data);
650 if(!$reflection->isUserDefined()){
651 $this->scope[$instance] = $data;
652 return;
653 }
654 $this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
655
656 do{
657 if(!$reflection->isUserDefined()){
658 break;
659 }
660 foreach ($reflection->getProperties() as $property){
661 if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
662 continue;
663 }
664 $property->setAccessible(true);
665 if (PHP_VERSION >= 7.4 && !$property->isInitialized($instance)) {
666 continue;
667 }
668 $value = $property->getValue($instance);
669 if(is_array($value) || is_object($value)){
670 $this->mapByReference($value);
671 }
672 $property->setValue($data, $value);
673 }
674 } while($reflection = $reflection->getParentClass());
675 }
676 }
677
678 }
679