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