PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.15.2
Independent Analytics – WordPress Analytics Plugin v2.15.2
2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / maxmind-db / reader / src / MaxMind / Db / Reader / Decoder.php
independent-analytics / vendor / maxmind-db / reader / src / MaxMind / Db / Reader Last commit date
Decoder.php 2 weeks ago InvalidDatabaseException.php 2 years ago Metadata.php 2 weeks ago Util.php 2 years ago
Decoder.php
333 lines
1 <?php
2
3 declare (strict_types=1);
4 namespace IAWPSCOPED\MaxMind\Db\Reader;
5
6 // @codingStandardsIgnoreLine
7 /** @internal */
8 class Decoder
9 {
10 /**
11 * @var resource
12 */
13 private $fileStream;
14 /**
15 * @var int
16 */
17 private $pointerBase;
18 /**
19 * This is only used for unit testing.
20 *
21 * @var bool
22 */
23 private $pointerTestHack;
24 /**
25 * @var bool
26 */
27 private $switchByteOrder;
28 private const _EXTENDED = 0;
29 private const _POINTER = 1;
30 private const _UTF8_STRING = 2;
31 private const _DOUBLE = 3;
32 private const _BYTES = 4;
33 private const _UINT16 = 5;
34 private const _UINT32 = 6;
35 private const _MAP = 7;
36 private const _INT32 = 8;
37 private const _UINT64 = 9;
38 private const _UINT128 = 10;
39 private const _ARRAY = 11;
40 // 12 is the container type
41 // 13 is the end marker type
42 private const _BOOLEAN = 14;
43 private const _FLOAT = 15;
44 /**
45 * @param resource $fileStream
46 */
47 public function __construct($fileStream, int $pointerBase = 0, bool $pointerTestHack = \false)
48 {
49 $this->fileStream = $fileStream;
50 $this->pointerBase = $pointerBase;
51 $this->pointerTestHack = $pointerTestHack;
52 $this->switchByteOrder = $this->isPlatformLittleEndian();
53 }
54 /**
55 * @return array<mixed>
56 */
57 public function decode(int $offset) : array
58 {
59 $ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
60 ++$offset;
61 $type = $ctrlByte >> 5;
62 // Pointers are a special case, we don't read the next $size bytes, we
63 // use the size to determine the length of the pointer and then follow
64 // it.
65 if ($type === self::_POINTER) {
66 [$pointer, $offset] = $this->decodePointer($ctrlByte, $offset);
67 // for unit testing
68 if ($this->pointerTestHack) {
69 return [$pointer];
70 }
71 [$result] = $this->decode($pointer);
72 return [$result, $offset];
73 }
74 if ($type === self::_EXTENDED) {
75 $nextByte = \ord(Util::read($this->fileStream, $offset, 1));
76 $type = $nextByte + 7;
77 if ($type < 8) {
78 throw new InvalidDatabaseException('Something went horribly wrong in the decoder. An extended type ' . 'resolved to a type number < 8 (' . $type . ')');
79 }
80 ++$offset;
81 }
82 [$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset);
83 return $this->decodeByType($type, $offset, $size);
84 }
85 /**
86 * @param int<0, max> $size
87 *
88 * @return array{0:mixed, 1:int}
89 */
90 private function decodeByType(int $type, int $offset, int $size) : array
91 {
92 switch ($type) {
93 case self::_MAP:
94 return $this->decodeMap($size, $offset);
95 case self::_ARRAY:
96 return $this->decodeArray($size, $offset);
97 case self::_BOOLEAN:
98 return [$this->decodeBoolean($size), $offset];
99 }
100 $newOffset = $offset + $size;
101 $bytes = Util::read($this->fileStream, $offset, $size);
102 switch ($type) {
103 case self::_BYTES:
104 case self::_UTF8_STRING:
105 return [$bytes, $newOffset];
106 case self::_DOUBLE:
107 $this->verifySize(8, $size);
108 return [$this->decodeDouble($bytes), $newOffset];
109 case self::_FLOAT:
110 $this->verifySize(4, $size);
111 return [$this->decodeFloat($bytes), $newOffset];
112 case self::_INT32:
113 return [$this->decodeInt32($bytes, $size), $newOffset];
114 case self::_UINT16:
115 case self::_UINT32:
116 case self::_UINT64:
117 case self::_UINT128:
118 return [$this->decodeUint($bytes, $size), $newOffset];
119 default:
120 throw new InvalidDatabaseException('Unknown or unexpected type: ' . $type);
121 }
122 }
123 private function verifySize(int $expected, int $actual) : void
124 {
125 if ($expected !== $actual) {
126 throw new InvalidDatabaseException("The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)");
127 }
128 }
129 /**
130 * @return array{0:array<mixed>, 1:int}
131 */
132 private function decodeArray(int $size, int $offset) : array
133 {
134 $array = [];
135 for ($i = 0; $i < $size; ++$i) {
136 [$value, $offset] = $this->decode($offset);
137 $array[] = $value;
138 }
139 return [$array, $offset];
140 }
141 private function decodeBoolean(int $size) : bool
142 {
143 return $size !== 0;
144 }
145 private function decodeDouble(string $bytes) : float
146 {
147 // This assumes IEEE 754 doubles, but most (all?) modern platforms
148 // use them.
149 $rc = \unpack('E', $bytes);
150 if ($rc === \false) {
151 throw new InvalidDatabaseException('Could not unpack a double value from the given bytes.');
152 }
153 [, $double] = $rc;
154 return $double;
155 }
156 private function decodeFloat(string $bytes) : float
157 {
158 // This assumes IEEE 754 floats, but most (all?) modern platforms
159 // use them.
160 $rc = \unpack('G', $bytes);
161 if ($rc === \false) {
162 throw new InvalidDatabaseException('Could not unpack a float value from the given bytes.');
163 }
164 [, $float] = $rc;
165 return $float;
166 }
167 private function decodeInt32(string $bytes, int $size) : int
168 {
169 switch ($size) {
170 case 0:
171 return 0;
172 case 1:
173 case 2:
174 case 3:
175 $bytes = \str_pad($bytes, 4, "\x00", \STR_PAD_LEFT);
176 break;
177 case 4:
178 break;
179 default:
180 throw new InvalidDatabaseException("The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)");
181 }
182 $rc = \unpack('l', $this->maybeSwitchByteOrder($bytes));
183 if ($rc === \false) {
184 throw new InvalidDatabaseException('Could not unpack a 32bit integer value from the given bytes.');
185 }
186 [, $int] = $rc;
187 return $int;
188 }
189 /**
190 * @return array{0:array<string, mixed>, 1:int}
191 */
192 private function decodeMap(int $size, int $offset) : array
193 {
194 $map = [];
195 for ($i = 0; $i < $size; ++$i) {
196 [$key, $offset] = $this->decode($offset);
197 [$value, $offset] = $this->decode($offset);
198 $map[$key] = $value;
199 }
200 return [$map, $offset];
201 }
202 /**
203 * @return array{0:int, 1:int}
204 */
205 private function decodePointer(int $ctrlByte, int $offset) : array
206 {
207 $pointerSize = ($ctrlByte >> 3 & 0x3) + 1;
208 $buffer = Util::read($this->fileStream, $offset, $pointerSize);
209 $offset += $pointerSize;
210 switch ($pointerSize) {
211 case 1:
212 $packed = \chr($ctrlByte & 0x7) . $buffer;
213 $rc = \unpack('n', $packed);
214 if ($rc === \false) {
215 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes (pointerSize is 1).');
216 }
217 [, $pointer] = $rc;
218 $pointer += $this->pointerBase;
219 break;
220 case 2:
221 $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
222 $rc = \unpack('N', $packed);
223 if ($rc === \false) {
224 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes (pointerSize is 2).');
225 }
226 [, $pointer] = $rc;
227 $pointer += $this->pointerBase + 2048;
228 break;
229 case 3:
230 $packed = \chr($ctrlByte & 0x7) . $buffer;
231 // It is safe to use 'N' here, even on 32 bit machines as the
232 // first bit is 0.
233 $rc = \unpack('N', $packed);
234 if ($rc === \false) {
235 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes (pointerSize is 3).');
236 }
237 [, $pointer] = $rc;
238 $pointer += $this->pointerBase + 526336;
239 break;
240 case 4:
241 // We cannot use unpack here as we might overflow on 32 bit
242 // machines
243 $pointerOffset = $this->decodeUint($buffer, $pointerSize);
244 $pointerBase = $this->pointerBase;
245 if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
246 $pointer = $pointerOffset + $pointerBase;
247 } else {
248 throw new \RuntimeException('The database offset is too large to be represented on your platform.');
249 }
250 break;
251 default:
252 throw new InvalidDatabaseException('Unexpected pointer size ' . $pointerSize);
253 }
254 return [$pointer, $offset];
255 }
256 // @phpstan-ignore-next-line
257 private function decodeUint(string $bytes, int $byteLength)
258 {
259 if ($byteLength === 0) {
260 return 0;
261 }
262 // PHP integers are signed. PHP_INT_SIZE - 1 is the number of
263 // complete bytes that can be converted to an integer. However,
264 // we can convert another byte if the leading bit is zero.
265 $useRealInts = $byteLength <= \PHP_INT_SIZE - 1 || $byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0;
266 if ($useRealInts) {
267 $integer = 0;
268 for ($i = 0; $i < $byteLength; ++$i) {
269 $part = \ord($bytes[$i]);
270 $integer = ($integer << 8) + $part;
271 }
272 return $integer;
273 }
274 // We only use gmp or bcmath if the final value is too big
275 $integerAsString = '0';
276 for ($i = 0; $i < $byteLength; ++$i) {
277 $part = \ord($bytes[$i]);
278 if (\extension_loaded('gmp')) {
279 $integerAsString = \gmp_strval(\gmp_add(\gmp_mul($integerAsString, '256'), $part));
280 } elseif (\extension_loaded('bcmath')) {
281 $integerAsString = \bcadd(\bcmul($integerAsString, '256'), (string) $part);
282 } else {
283 throw new \RuntimeException('The gmp or bcmath extension must be installed to read this database.');
284 }
285 }
286 return $integerAsString;
287 }
288 /**
289 * @return array{0:int, 1:int}
290 */
291 private function sizeFromCtrlByte(int $ctrlByte, int $offset) : array
292 {
293 $size = $ctrlByte & 0x1f;
294 if ($size < 29) {
295 return [$size, $offset];
296 }
297 $bytesToRead = $size - 28;
298 $bytes = Util::read($this->fileStream, $offset, $bytesToRead);
299 if ($size === 29) {
300 $size = 29 + \ord($bytes);
301 } elseif ($size === 30) {
302 $rc = \unpack('n', $bytes);
303 if ($rc === \false) {
304 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes.');
305 }
306 [, $adjust] = $rc;
307 $size = 285 + $adjust;
308 } else {
309 $rc = \unpack('N', "\x00" . $bytes);
310 if ($rc === \false) {
311 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes.');
312 }
313 [, $adjust] = $rc;
314 $size = $adjust + 65821;
315 }
316 return [$size, $offset + $bytesToRead];
317 }
318 private function maybeSwitchByteOrder(string $bytes) : string
319 {
320 return $this->switchByteOrder ? \strrev($bytes) : $bytes;
321 }
322 private function isPlatformLittleEndian() : bool
323 {
324 $testint = 0xff;
325 $packed = \pack('S', $testint);
326 $rc = \unpack('v', $packed);
327 if ($rc === \false) {
328 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes.');
329 }
330 return $testint === \current($rc);
331 }
332 }
333