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\Packable; |
17
|
|
|
|
18
|
|
|
class Packer |
19
|
|
|
{ |
20
|
|
|
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 Packable[] |
39
|
|
|
*/ |
40
|
|
|
private $transformers = []; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param PackOptions|int|null $options |
44
|
|
|
* |
45
|
|
|
* @throws InvalidOptionException |
46
|
|
|
*/ |
47
|
110 |
|
public function __construct($options = null) |
48
|
|
|
{ |
49
|
110 |
|
if (!$options instanceof PackOptions) { |
50
|
110 |
|
$options = PackOptions::fromBitmask($options); |
51
|
|
|
} |
52
|
|
|
|
53
|
110 |
|
$this->isDetectStrBin = $options->isDetectStrBinMode(); |
54
|
110 |
|
$this->isForceStr = $options->isForceStrMode(); |
55
|
110 |
|
$this->isDetectArrMap = $options->isDetectArrMapMode(); |
56
|
110 |
|
$this->isForceArr = $options->isForceArrMode(); |
57
|
110 |
|
$this->isForceFloat32 = $options->isForceFloat32Mode(); |
58
|
110 |
|
} |
59
|
|
|
|
60
|
2 |
|
public function registerTransformer(Packable $transformer) |
61
|
|
|
{ |
62
|
2 |
|
$this->transformers[] = $transformer; |
63
|
2 |
|
} |
64
|
|
|
|
65
|
99 |
|
public function pack($value) |
66
|
|
|
{ |
67
|
99 |
|
if (\is_int($value)) { |
68
|
52 |
|
return $this->packInt($value); |
69
|
|
|
} |
70
|
70 |
|
if (\is_string($value)) { |
71
|
33 |
|
if ($this->isDetectStrBin) { |
72
|
27 |
|
return \preg_match(self::UTF8_REGEX, $value) |
73
|
19 |
|
? $this->packStr($value) |
74
|
27 |
|
: $this->packBin($value); |
75
|
|
|
} |
76
|
|
|
|
77
|
6 |
|
return $this->isForceStr ? $this->packStr($value) : $this->packBin($value); |
78
|
|
|
} |
79
|
47 |
|
if (\is_array($value)) { |
80
|
26 |
|
if ($this->isDetectArrMap) { |
81
|
19 |
|
return \array_values($value) === $value |
82
|
10 |
|
? $this->packArray($value) |
83
|
19 |
|
: $this->packMap($value); |
84
|
|
|
} |
85
|
|
|
|
86
|
7 |
|
return $this->isForceArr ? $this->packArray($value) : $this->packMap($value); |
87
|
|
|
} |
88
|
25 |
|
if (null === $value) { |
89
|
3 |
|
return $this->packNil(); |
90
|
|
|
} |
91
|
24 |
|
if (\is_bool($value)) { |
92
|
6 |
|
return $this->packBool($value); |
93
|
|
|
} |
94
|
19 |
|
if (\is_float($value)) { |
95
|
6 |
|
return $this->isForceFloat32 |
96
|
1 |
|
? $this->packFloat32($value) |
97
|
6 |
|
: $this->packFloat64($value); |
98
|
|
|
} |
99
|
13 |
|
if ($value instanceof Ext) { |
100
|
9 |
|
return $this->packExt($value->type, $value->data); |
101
|
|
|
} |
102
|
4 |
|
if ($this->transformers) { |
|
|
|
|
103
|
2 |
|
foreach ($this->transformers as $transformer) { |
104
|
2 |
|
if (null !== $packed = $transformer->pack($this, $value)) { |
105
|
2 |
|
return $packed; |
106
|
|
|
} |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
3 |
|
throw new PackingFailedException($value, 'Unsupported type.'); |
111
|
|
|
} |
112
|
|
|
|
113
|
13 |
|
public function packArray(array $array) |
114
|
|
|
{ |
115
|
13 |
|
$size = \count($array); |
116
|
|
|
|
117
|
13 |
View Code Duplication |
if ($size <= 0xf) { |
|
|
|
|
118
|
10 |
|
$data = \chr(0x90 | $size); |
119
|
3 |
|
} elseif ($size <= 0xffff) { |
120
|
2 |
|
$data = "\xdc".\chr($size >> 8).\chr($size); |
121
|
|
|
} else { |
122
|
1 |
|
$data = \pack('CN', 0xdd, $size); |
123
|
|
|
} |
124
|
|
|
|
125
|
13 |
|
foreach ($array as $val) { |
126
|
12 |
|
$data .= $this->pack($val); |
127
|
|
|
} |
128
|
|
|
|
129
|
13 |
|
return $data; |
130
|
|
|
} |
131
|
|
|
|
132
|
15 |
|
public function packMap(array $map) |
133
|
|
|
{ |
134
|
15 |
|
$size = \count($map); |
135
|
|
|
|
136
|
15 |
View Code Duplication |
if ($size <= 0xf) { |
|
|
|
|
137
|
12 |
|
$data = \chr(0x80 | $size); |
138
|
3 |
|
} elseif ($size <= 0xffff) { |
139
|
2 |
|
$data = "\xde".\chr($size >> 8).\chr($size); |
140
|
|
|
} else { |
141
|
1 |
|
$data = \pack('CN', 0xdf, $size); |
142
|
|
|
} |
143
|
|
|
|
144
|
15 |
|
foreach ($map as $key => $val) { |
145
|
15 |
|
$data .= $this->pack($key); |
146
|
15 |
|
$data .= $this->pack($val); |
147
|
|
|
} |
148
|
|
|
|
149
|
15 |
|
return $data; |
150
|
|
|
} |
151
|
|
|
|
152
|
22 |
|
public function packStr($str) |
153
|
|
|
{ |
154
|
22 |
|
$len = \strlen($str); |
155
|
|
|
|
156
|
22 |
|
if ($len < 32) { |
157
|
15 |
|
return \chr(0xa0 | $len).$str; |
158
|
|
|
} |
159
|
7 |
|
if ($len <= 0xff) { |
160
|
4 |
|
return "\xd9".\chr($len).$str; |
161
|
|
|
} |
162
|
3 |
|
if ($len <= 0xffff) { |
163
|
2 |
|
return "\xda".\chr($len >> 8).\chr($len).$str; |
164
|
|
|
} |
165
|
|
|
|
166
|
1 |
|
return \pack('CN', 0xdb, $len).$str; |
167
|
|
|
} |
168
|
|
|
|
169
|
13 |
|
public function packBin($str) |
170
|
|
|
{ |
171
|
13 |
|
$len = \strlen($str); |
172
|
|
|
|
173
|
13 |
|
if ($len <= 0xff) { |
174
|
11 |
|
return "\xc4".\chr($len).$str; |
175
|
|
|
} |
176
|
2 |
|
if ($len <= 0xffff) { |
177
|
1 |
|
return "\xc5".\chr($len >> 8).\chr($len).$str; |
178
|
|
|
} |
179
|
|
|
|
180
|
1 |
|
return \pack('CN', 0xc6, $len).$str; |
181
|
|
|
} |
182
|
|
|
|
183
|
9 |
|
public function packExt($type, $data) |
184
|
|
|
{ |
185
|
9 |
|
$len = \strlen($data); |
186
|
|
|
|
187
|
9 |
|
switch ($len) { |
188
|
9 |
|
case 1: return "\xd4".\chr($type).$data; |
189
|
8 |
|
case 2: return "\xd5".\chr($type).$data; |
190
|
7 |
|
case 4: return "\xd6".\chr($type).$data; |
|
|
|
|
191
|
6 |
|
case 8: return "\xd7".\chr($type).$data; |
|
|
|
|
192
|
5 |
|
case 16: return "\xd8".\chr($type).$data; |
193
|
|
|
} |
194
|
|
|
|
195
|
4 |
|
if ($len <= 0xff) { |
196
|
2 |
|
return "\xc7".\chr($len).\chr($type).$data; |
197
|
|
|
} |
198
|
2 |
|
if ($len <= 0xffff) { |
199
|
1 |
|
return \pack('CnC', 0xc8, $len, $type).$data; |
200
|
|
|
} |
201
|
|
|
|
202
|
1 |
|
return \pack('CNC', 0xc9, $len, $type).$data; |
203
|
|
|
} |
204
|
|
|
|
205
|
3 |
|
public function packNil() |
206
|
|
|
{ |
207
|
3 |
|
return "\xc0"; |
208
|
|
|
} |
209
|
|
|
|
210
|
6 |
|
public function packBool($val) |
211
|
|
|
{ |
212
|
6 |
|
return $val ? "\xc3" : "\xc2"; |
213
|
|
|
} |
214
|
|
|
|
215
|
3 |
|
public function packFloat32($num) |
216
|
|
|
{ |
217
|
3 |
|
return "\xca".\strrev(\pack('f', $num)); |
218
|
|
|
} |
219
|
|
|
|
220
|
5 |
|
public function packFloat64($num) |
221
|
|
|
{ |
222
|
5 |
|
return "\xcb".\strrev(\pack('d', $num)); |
223
|
|
|
} |
224
|
|
|
|
225
|
52 |
|
public function packInt($num) |
226
|
|
|
{ |
227
|
52 |
|
if ($num >= 0) { |
228
|
37 |
|
if ($num <= 0x7f) { |
229
|
25 |
|
return \chr($num); |
230
|
|
|
} |
231
|
16 |
|
if ($num <= 0xff) { |
232
|
6 |
|
return "\xcc".\chr($num); |
233
|
|
|
} |
234
|
12 |
|
if ($num <= 0xffff) { |
235
|
6 |
|
return "\xcd".\chr($num >> 8).\chr($num); |
236
|
|
|
} |
237
|
7 |
|
if ($num <= 0xffffffff) { |
238
|
5 |
|
return \pack('CN', 0xce, $num); |
239
|
|
|
} |
240
|
|
|
|
241
|
3 |
|
return self::packUint64(0xcf, $num); |
242
|
|
|
} |
243
|
|
|
|
244
|
16 |
|
if ($num >= -0x20) { |
245
|
4 |
|
return \chr(0xe0 | $num); |
246
|
|
|
} |
247
|
12 |
|
if ($num >= -0x80) { |
248
|
3 |
|
return "\xd0".\chr($num); |
249
|
|
|
} |
250
|
9 |
|
if ($num >= -0x8000) { |
251
|
3 |
|
return "\xd1".\chr($num >> 8).\chr($num); |
252
|
|
|
} |
253
|
6 |
|
if ($num >= -0x80000000) { |
254
|
3 |
|
return \pack('CN', 0xd2, $num); |
255
|
|
|
} |
256
|
|
|
|
257
|
3 |
|
return self::packUint64(0xd3, $num); |
258
|
|
|
} |
259
|
|
|
|
260
|
6 |
|
private static function packUint64($code, $num) |
261
|
|
|
{ |
262
|
6 |
|
$hi = ($num & 0xffffffff00000000) >> 32; |
263
|
6 |
|
$lo = $num & 0x00000000ffffffff; |
264
|
|
|
|
265
|
6 |
|
return \pack('CNN', $code, $hi, $lo); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
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.