PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.2
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Dependencies / SuperClosure / Serializer.php

Serializer.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 3.5.2, at includes/Dependencies/SuperClosure/Serializer.php

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