PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/SafeForKids/vendor/jeremeamia/SuperClosure/src/Serializer.php

https://gitlab.com/rocs/Streaming-Safe-for-Kids
PHP | 221 lines | 123 code | 22 blank | 76 comment | 17 complexity | fd7b6a7983d2124957cb5bbc9576224f MD5 | raw file
  1. <?php namespace SuperClosure;
  2. use SuperClosure\Analyzer\AstAnalyzer as DefaultAnalyzer;
  3. use SuperClosure\Analyzer\ClosureAnalyzer;
  4. use SuperClosure\Exception\ClosureSerializationException;
  5. use SuperClosure\Exception\ClosureUnserializationException;
  6. /**
  7. * This is the serializer class used for serializing Closure objects.
  8. *
  9. * We're abstracting away all the details, impossibilities, and scary things
  10. * that happen within.
  11. */
  12. class Serializer implements SerializerInterface
  13. {
  14. /**
  15. * The special value marking a recursive reference to a closure.
  16. *
  17. * @var string
  18. */
  19. const RECURSION = "{{RECURSION}}";
  20. /**
  21. * The keys of closure data required for serialization.
  22. *
  23. * @var array
  24. */
  25. private static $dataToKeep = [
  26. 'code' => true,
  27. 'context' => true,
  28. 'binding' => true,
  29. 'scope' => true,
  30. 'isStatic' => true,
  31. ];
  32. /**
  33. * The closure analyzer instance.
  34. *
  35. * @var ClosureAnalyzer
  36. */
  37. private $analyzer;
  38. /**
  39. * The HMAC key to sign serialized closures.
  40. *
  41. * @var string
  42. */
  43. private $signingKey;
  44. /**
  45. * Create a new serializer instance.
  46. *
  47. * @param ClosureAnalyzer|null $analyzer Closure analyzer instance.
  48. * @param string|null $signingKey HMAC key to sign closure data.
  49. */
  50. public function __construct(
  51. ClosureAnalyzer $analyzer = null,
  52. $signingKey = null
  53. ) {
  54. $this->analyzer = $analyzer ?: new DefaultAnalyzer;
  55. $this->signingKey = $signingKey;
  56. }
  57. /**
  58. * @inheritDoc
  59. */
  60. public function serialize(\Closure $closure)
  61. {
  62. $serialized = serialize(new SerializableClosure($closure, $this));
  63. if ($serialized === null) {
  64. throw new ClosureSerializationException(
  65. 'The closure could not be serialized.'
  66. );
  67. }
  68. if ($this->signingKey) {
  69. $signature = $this->calculateSignature($serialized);
  70. $serialized = '%' . base64_encode($signature) . $serialized;
  71. }
  72. return $serialized;
  73. }
  74. /**
  75. * @inheritDoc
  76. */
  77. public function unserialize($serialized)
  78. {
  79. // Strip off the signature from the front of the string.
  80. $signature = null;
  81. if ($serialized[0] === '%') {
  82. $signature = base64_decode(substr($serialized, 1, 44));
  83. $serialized = substr($serialized, 45);
  84. }
  85. // If a key was provided, then verify the signature.
  86. if ($this->signingKey) {
  87. $this->verifySignature($signature, $serialized);
  88. }
  89. set_error_handler(function () {});
  90. $unserialized = unserialize($serialized);
  91. restore_error_handler();
  92. if ($unserialized === false) {
  93. throw new ClosureUnserializationException(
  94. 'The closure could not be unserialized.'
  95. );
  96. } elseif (!$unserialized instanceof SerializableClosure) {
  97. throw new ClosureUnserializationException(
  98. 'The closure did not unserialize to a SuperClosure.'
  99. );
  100. }
  101. return $unserialized->getClosure();
  102. }
  103. /**
  104. * @inheritDoc
  105. */
  106. public function getData(\Closure $closure, $forSerialization = false)
  107. {
  108. // Use the closure analyzer to get data about the closure.
  109. $data = $this->analyzer->analyze($closure);
  110. // If the closure data is getting retrieved solely for the purpose of
  111. // serializing the closure, then make some modifications to the data.
  112. if ($forSerialization) {
  113. // If there is no reference to the binding, don't serialize it.
  114. if (!$data['hasThis']) {
  115. $data['binding'] = null;
  116. }
  117. // Remove data about the closure that does not get serialized.
  118. $data = array_intersect_key($data, self::$dataToKeep);
  119. // Wrap any other closures within the context.
  120. foreach ($data['context'] as &$value) {
  121. if ($value instanceof \Closure) {
  122. $value = ($value === $closure)
  123. ? self::RECURSION
  124. : new SerializableClosure($value, $this);
  125. }
  126. }
  127. }
  128. return $data;
  129. }
  130. /**
  131. * Recursively traverses and wraps all Closure objects within the value.
  132. *
  133. * NOTE: THIS MAY NOT WORK IN ALL USE CASES, SO USE AT YOUR OWN RISK.
  134. *
  135. * @param mixed $data Any variable that contains closures.
  136. * @param SerializerInterface $serializer The serializer to use.
  137. */
  138. public static function wrapClosures(&$data, SerializerInterface $serializer)
  139. {
  140. if ($data instanceof \Closure) {
  141. // Handle and wrap closure objects.
  142. $reflection = new \ReflectionFunction($data);
  143. if ($binding = $reflection->getClosureThis()) {
  144. self::wrapClosures($binding, $serializer);
  145. $scope = $reflection->getClosureScopeClass();
  146. $scope = $scope ? $scope->getName() : 'static';
  147. $data = $data->bindTo($binding, $scope);
  148. }
  149. $data = new SerializableClosure($data, $serializer);
  150. } elseif (is_array($data) || $data instanceof \stdClass || $data instanceof \Traversable) {
  151. // Handle members of traversable values.
  152. foreach ($data as &$value) {
  153. self::wrapClosures($value, $serializer);
  154. }
  155. } elseif (is_object($data) && !$data instanceof \Serializable) {
  156. // Handle objects that are not already explicitly serializable.
  157. $reflection = new \ReflectionObject($data);
  158. if (!$reflection->hasMethod('__sleep')) {
  159. foreach ($reflection->getProperties() as $property) {
  160. if ($property->isPrivate() || $property->isProtected()) {
  161. $property->setAccessible(true);
  162. }
  163. $value = $property->getValue($data);
  164. self::wrapClosures($value, $serializer);
  165. $property->setValue($data, $value);
  166. }
  167. }
  168. }
  169. }
  170. /**
  171. * Calculates a signature for a closure's serialized data.
  172. *
  173. * @param string $data Serialized closure data.
  174. *
  175. * @return string Signature of the closure's data.
  176. */
  177. private function calculateSignature($data)
  178. {
  179. return hash_hmac('sha256', $data, $this->signingKey, true);
  180. }
  181. /**
  182. * Verifies the signature for a closure's serialized data.
  183. *
  184. * @param string $signature The provided signature of the data.
  185. * @param string $data The data for which to verify the signature.
  186. *
  187. * @throws ClosureUnserializationException if the signature is invalid.
  188. */
  189. private function verifySignature($signature, $data)
  190. {
  191. // Verify that the provided signature matches the calculated signature.
  192. if (!hash_equals($signature, $this->calculateSignature($data))) {
  193. throw new ClosureUnserializationException('The signature of the'
  194. . ' closure\'s data is invalid, which means the serialized '
  195. . 'closure has been modified and is unsafe to unserialize.'
  196. );
  197. }
  198. }
  199. }