1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This file is part of the rybakit/msgpack.php package. |
||
5 | * |
||
6 | * (c) Eugene Leonovich <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace MessagePack; |
||
13 | |||
14 | use MessagePack\Exception\InvalidOptionException; |
||
15 | use MessagePack\Exception\PackingFailedException; |
||
16 | |||
17 | class Packer |
||
18 | { |
||
19 | private const UTF8_REGEX = '/\A(?: |
||
20 | [\x00-\x7F]++ # ASCII |
||
21 | | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte |
||
22 | | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs |
||
23 | | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte |
||
24 | | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates |
||
25 | | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 |
||
26 | | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 |
||
27 | | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 |
||
28 | )*+\z/x'; |
||
29 | |||
30 | /** @var bool */ |
||
31 | private $isDetectStrBin; |
||
32 | |||
33 | /** @var bool */ |
||
34 | private $isForceStr; |
||
35 | |||
36 | /** @var bool */ |
||
37 | private $isDetectArrMap; |
||
38 | |||
39 | /** @var bool */ |
||
40 | private $isForceArr; |
||
41 | |||
42 | /** @var bool */ |
||
43 | private $isForceFloat32; |
||
44 | |||
45 | /** @var CanPack[] */ |
||
46 | private $transformers = []; |
||
47 | |||
48 | /** |
||
49 | * @param PackOptions|int|null $options |
||
50 | * @param CanPack[] $transformers |
||
51 | * |
||
52 | * @throws InvalidOptionException |
||
53 | */ |
||
54 | 212 | public function __construct($options = null, array $transformers = []) |
|
55 | { |
||
56 | 212 | if (\is_null($options)) { |
|
57 | 14 | $options = PackOptions::fromDefaults(); |
|
58 | 204 | } elseif (!$options instanceof PackOptions) { |
|
59 | 204 | $options = PackOptions::fromBitmask($options); |
|
60 | } |
||
61 | |||
62 | 212 | $this->isDetectStrBin = $options->isDetectStrBinMode(); |
|
63 | 212 | $this->isForceStr = $options->isForceStrMode(); |
|
64 | 212 | $this->isDetectArrMap = $options->isDetectArrMapMode(); |
|
65 | 212 | $this->isForceArr = $options->isForceArrMode(); |
|
66 | 212 | $this->isForceFloat32 = $options->isForceFloat32Mode(); |
|
67 | |||
68 | 212 | if ($transformers) { |
|
69 | 209 | $this->transformers = $transformers; |
|
70 | } |
||
71 | } |
||
72 | |||
73 | 2 | public function extendWith(CanPack $transformer, CanPack ...$transformers) : self |
|
74 | { |
||
75 | 2 | $new = clone $this; |
|
76 | 2 | $new->transformers[] = $transformer; |
|
77 | |||
78 | 2 | if ($transformers) { |
|
0 ignored issues
–
show
|
|||
79 | 1 | $new->transformers = \array_merge($new->transformers, $transformers); |
|
80 | } |
||
81 | |||
82 | 2 | return $new; |
|
83 | } |
||
84 | |||
85 | /** |
||
86 | * @param mixed $value |
||
87 | * |
||
88 | * @return string |
||
89 | */ |
||
90 | 137 | public function pack($value) |
|
91 | { |
||
92 | 137 | if (\is_int($value)) { |
|
93 | 68 | return $this->packInt($value); |
|
94 | } |
||
95 | 97 | if (\is_string($value)) { |
|
96 | 38 | if ('' === $value) { |
|
97 | 1 | return $this->isForceStr || $this->isDetectStrBin ? "\xa0" : "\xc4\x00"; |
|
98 | } |
||
99 | 37 | if ($this->isForceStr) { |
|
100 | 8 | return $this->packStr($value); |
|
101 | } |
||
102 | 29 | if ($this->isDetectStrBin && \preg_match(self::UTF8_REGEX, $value)) { |
|
103 | 20 | return $this->packStr($value); |
|
104 | } |
||
105 | |||
106 | 13 | return $this->packBin($value); |
|
107 | } |
||
108 | 72 | if (\is_array($value)) { |
|
109 | 31 | if ([] === $value) { |
|
110 | 1 | return $this->isDetectArrMap || $this->isForceArr ? "\x90" : "\x80"; |
|
111 | } |
||
112 | 30 | if ($this->isDetectArrMap) { |
|
113 | 23 | if (!isset($value[0]) && !\array_key_exists(0, $value)) { |
|
114 | 14 | return $this->packMap($value); |
|
115 | } |
||
116 | |||
117 | 13 | return \array_values($value) === $value |
|
118 | 13 | ? $this->packArray($value) |
|
119 | 13 | : $this->packMap($value); |
|
120 | } |
||
121 | |||
122 | 7 | return $this->isForceArr ? $this->packArray($value) : $this->packMap($value); |
|
123 | } |
||
124 | 48 | if (\is_null($value)) { |
|
125 | 7 | return "\xc0"; |
|
126 | } |
||
127 | 45 | if (\is_bool($value)) { |
|
128 | 10 | return $value ? "\xc3" : "\xc2"; |
|
129 | } |
||
130 | 37 | if (\is_float($value)) { |
|
131 | 7 | return $this->packFloat($value); |
|
132 | } |
||
133 | 30 | if ($this->transformers) { |
|
0 ignored issues
–
show
The expression
$this->transformers of type MessagePack\CanPack[] 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
Loading history...
|
|||
134 | 27 | foreach ($this->transformers as $transformer) { |
|
135 | 27 | if (!\is_null($packed = $transformer->pack($this, $value))) { |
|
136 | 13 | return $packed; |
|
137 | } |
||
138 | } |
||
139 | } |
||
140 | 17 | if ($value instanceof CanBePacked) { |
|
141 | 14 | return $value->pack($this); |
|
142 | } |
||
143 | |||
144 | 3 | throw PackingFailedException::unsupportedType($value); |
|
145 | } |
||
146 | |||
147 | /** |
||
148 | * @return string |
||
149 | */ |
||
150 | 1 | public function packNil() |
|
151 | { |
||
152 | 1 | return "\xc0"; |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * @param string $bool |
||
157 | * |
||
158 | * @return string |
||
159 | */ |
||
160 | 2 | public function packBool($bool) |
|
161 | { |
||
162 | 2 | return $bool ? "\xc3" : "\xc2"; |
|
163 | } |
||
164 | |||
165 | /** |
||
166 | * @param int $int |
||
167 | * |
||
168 | * @return string |
||
169 | */ |
||
170 | 103 | public function packInt($int) |
|
171 | { |
||
172 | 103 | if ($int >= 0) { |
|
173 | 71 | if ($int <= 0x7f) { |
|
174 | 45 | return \chr($int); |
|
175 | } |
||
176 | 34 | if ($int <= 0xff) { |
|
177 | 12 | return "\xcc".\chr($int); |
|
178 | } |
||
179 | 26 | if ($int <= 0xffff) { |
|
180 | 12 | return "\xcd".\chr($int >> 8).\chr($int); |
|
181 | } |
||
182 | 16 | if ($int <= 0xffffffff) { |
|
183 | 10 | return \pack('CN', 0xce, $int); |
|
184 | } |
||
185 | |||
186 | 8 | return \pack('CJ', 0xcf, $int); |
|
187 | } |
||
188 | |||
189 | 34 | if ($int >= -0x20) { |
|
190 | 8 | return \chr(0xe0 | $int); |
|
191 | } |
||
192 | 26 | if ($int >= -0x80) { |
|
193 | 6 | return "\xd0".\chr($int); |
|
194 | } |
||
195 | 20 | if ($int >= -0x8000) { |
|
196 | 6 | return "\xd1".\chr($int >> 8).\chr($int); |
|
197 | } |
||
198 | 14 | if ($int >= -0x80000000) { |
|
199 | 6 | return \pack('CN', 0xd2, $int); |
|
200 | } |
||
201 | |||
202 | 8 | return \pack('CJ', 0xd3, $int); |
|
203 | } |
||
204 | |||
205 | /** |
||
206 | * @param float $float |
||
207 | * |
||
208 | * @return string |
||
209 | */ |
||
210 | 10 | public function packFloat($float) |
|
211 | { |
||
212 | 10 | return $this->isForceFloat32 |
|
213 | 1 | ? "\xca".\pack('G', $float) |
|
214 | 10 | : "\xcb".\pack('E', $float); |
|
215 | } |
||
216 | |||
217 | /** |
||
218 | * @param float $float |
||
219 | * |
||
220 | * @return string |
||
221 | */ |
||
222 | 2 | public function packFloat32($float) |
|
223 | { |
||
224 | 2 | return "\xca".\pack('G', $float); |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * @param float $float |
||
229 | * |
||
230 | * @return string |
||
231 | */ |
||
232 | 3 | public function packFloat64($float) |
|
233 | { |
||
234 | 3 | return "\xcb".\pack('E', $float); |
|
235 | } |
||
236 | |||
237 | /** |
||
238 | * @param string $str |
||
239 | * |
||
240 | * @return string |
||
241 | */ |
||
242 | 42 | public function packStr($str) |
|
243 | { |
||
244 | 42 | $length = \strlen($str); |
|
245 | |||
246 | 42 | if ($length < 32) { |
|
247 | 28 | return \chr(0xa0 | $length).$str; |
|
248 | } |
||
249 | 14 | if ($length <= 0xff) { |
|
250 | 8 | return "\xd9".\chr($length).$str; |
|
251 | } |
||
252 | 6 | if ($length <= 0xffff) { |
|
253 | 4 | return "\xda".\chr($length >> 8).\chr($length).$str; |
|
254 | } |
||
255 | |||
256 | 2 | return \pack('CN', 0xdb, $length).$str; |
|
257 | } |
||
258 | |||
259 | /** |
||
260 | * @param string $str |
||
261 | * |
||
262 | * @return string |
||
263 | */ |
||
264 | 21 | public function packBin($str) |
|
265 | { |
||
266 | 21 | $length = \strlen($str); |
|
267 | |||
268 | 21 | if ($length <= 0xff) { |
|
269 | 17 | return "\xc4".\chr($length).$str; |
|
270 | } |
||
271 | 4 | if ($length <= 0xffff) { |
|
272 | 2 | return "\xc5".\chr($length >> 8).\chr($length).$str; |
|
273 | } |
||
274 | |||
275 | 2 | return \pack('CN', 0xc6, $length).$str; |
|
276 | } |
||
277 | |||
278 | /** |
||
279 | * @param array $array |
||
280 | * |
||
281 | * @return string |
||
282 | */ |
||
283 | 22 | public function packArray($array) |
|
284 | { |
||
285 | 22 | $data = $this->packArrayHeader(\count($array)); |
|
286 | |||
287 | 22 | foreach ($array as $val) { |
|
288 | 21 | $data .= $this->pack($val); |
|
289 | } |
||
290 | |||
291 | 22 | return $data; |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * @param int $size |
||
296 | * |
||
297 | * @return string |
||
298 | */ |
||
299 | 23 | public function packArrayHeader($size) |
|
300 | { |
||
301 | 23 | if ($size <= 0xf) { |
|
302 | 17 | return \chr(0x90 | $size); |
|
303 | } |
||
304 | 6 | if ($size <= 0xffff) { |
|
305 | 4 | return "\xdc".\chr($size >> 8).\chr($size); |
|
306 | } |
||
307 | |||
308 | 2 | return \pack('CN', 0xdd, $size); |
|
309 | } |
||
310 | |||
311 | /** |
||
312 | * @param array $map |
||
313 | * |
||
314 | * @return string |
||
315 | */ |
||
316 | 26 | public function packMap($map) |
|
317 | { |
||
318 | 26 | $data = $this->packMapHeader(\count($map)); |
|
319 | |||
320 | 26 | if ($this->isForceStr) { |
|
321 | 7 | foreach ($map as $key => $val) { |
|
322 | 7 | $data .= \is_string($key) ? $this->packStr($key) : $this->packInt($key); |
|
323 | 7 | $data .= $this->pack($val); |
|
324 | } |
||
325 | |||
326 | 7 | return $data; |
|
327 | } |
||
328 | |||
329 | 19 | if ($this->isDetectStrBin) { |
|
330 | 18 | foreach ($map as $key => $val) { |
|
331 | 18 | $data .= \is_string($key) |
|
332 | 4 | ? (\preg_match(self::UTF8_REGEX, $key) ? $this->packStr($key) : $this->packBin($key)) |
|
333 | 18 | : $this->packInt($key); |
|
334 | 18 | $data .= $this->pack($val); |
|
335 | } |
||
336 | |||
337 | 18 | return $data; |
|
338 | } |
||
339 | |||
340 | 1 | foreach ($map as $key => $val) { |
|
341 | 1 | $data .= \is_string($key) ? $this->packBin($key) : $this->packInt($key); |
|
342 | 1 | $data .= $this->pack($val); |
|
343 | } |
||
344 | |||
345 | 1 | return $data; |
|
346 | } |
||
347 | |||
348 | /** |
||
349 | * @param int $size |
||
350 | * |
||
351 | * @return string |
||
352 | */ |
||
353 | 27 | public function packMapHeader($size) |
|
354 | { |
||
355 | 27 | if ($size <= 0xf) { |
|
356 | 21 | return \chr(0x80 | $size); |
|
357 | } |
||
358 | 6 | if ($size <= 0xffff) { |
|
359 | 4 | return "\xde".\chr($size >> 8).\chr($size); |
|
360 | } |
||
361 | |||
362 | 2 | return \pack('CN', 0xdf, $size); |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * @param int $type |
||
367 | * @param string $data |
||
368 | * |
||
369 | * @return string |
||
370 | */ |
||
371 | 30 | public function packExt($type, $data) |
|
372 | { |
||
373 | 30 | $length = \strlen($data); |
|
374 | |||
375 | switch ($length) { |
||
376 | 30 | case 1: return "\xd4".\chr($type).$data; |
|
377 | 27 | case 2: return "\xd5".\chr($type).$data; |
|
378 | 25 | case 4: return "\xd6".\chr($type).$data; |
|
379 | 20 | case 8: return "\xd7".\chr($type).$data; |
|
380 | 15 | case 16: return "\xd8".\chr($type).$data; |
|
381 | } |
||
382 | |||
383 | 13 | if ($length <= 0xff) { |
|
384 | 9 | return "\xc7".\chr($length).\chr($type).$data; |
|
385 | } |
||
386 | 4 | if ($length <= 0xffff) { |
|
387 | 2 | return \pack('CnC', 0xc8, $length, $type).$data; |
|
388 | } |
||
389 | |||
390 | 2 | return \pack('CNC', 0xc9, $length, $type).$data; |
|
391 | } |
||
392 | } |
||
393 |
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.