PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.3.1
Independent Analytics – WordPress Analytics Plugin v2.3.1
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 years ago InvalidDatabaseException.php 2 years ago Metadata.php 2 years ago Util.php 2 years ago
Decoder.php
316 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 public function decode(int $offset) : array
55 {
56 $ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
57 ++$offset;
58 $type = $ctrlByte >> 5;
59 // Pointers are a special case, we don't read the next $size bytes, we
60 // use the size to determine the length of the pointer and then follow
61 // it.
62 if ($type === self::_POINTER) {
63 [$pointer, $offset] = $this->decodePointer($ctrlByte, $offset);
64 // for unit testing
65 if ($this->pointerTestHack) {
66 return [$pointer];
67 }
68 [$result] = $this->decode($pointer);
69 return [$result, $offset];
70 }
71 if ($type === self::_EXTENDED) {
72 $nextByte = \ord(Util::read($this->fileStream, $offset, 1));
73 $type = $nextByte + 7;
74 if ($type < 8) {
75 throw new InvalidDatabaseException('Something went horribly wrong in the decoder. An extended type ' . 'resolved to a type number < 8 (' . $type . ')');
76 }
77 ++$offset;
78 }
79 [$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset);
80 return $this->decodeByType($type, $offset, $size);
81 }
82 /**
83 * @param int<0, max> $size
84 */
85 private function decodeByType(int $type, int $offset, int $size) : array
86 {
87 switch ($type) {
88 case self::_MAP:
89 return $this->decodeMap($size, $offset);
90 case self::_ARRAY:
91 return $this->decodeArray($size, $offset);
92 case self::_BOOLEAN:
93 return [$this->decodeBoolean($size), $offset];
94 }
95 $newOffset = $offset + $size;
96 $bytes = Util::read($this->fileStream, $offset, $size);
97 switch ($type) {
98 case self::_BYTES:
99 case self::_UTF8_STRING:
100 return [$bytes, $newOffset];
101 case self::_DOUBLE:
102 $this->verifySize(8, $size);
103 return [$this->decodeDouble($bytes), $newOffset];
104 case self::_FLOAT:
105 $this->verifySize(4, $size);
106 return [$this->decodeFloat($bytes), $newOffset];
107 case self::_INT32:
108 return [$this->decodeInt32($bytes, $size), $newOffset];
109 case self::_UINT16:
110 case self::_UINT32:
111 case self::_UINT64:
112 case self::_UINT128:
113 return [$this->decodeUint($bytes, $size), $newOffset];
114 default:
115 throw new InvalidDatabaseException('Unknown or unexpected type: ' . $type);
116 }
117 }
118 private function verifySize(int $expected, int $actual) : void
119 {
120 if ($expected !== $actual) {
121 throw new InvalidDatabaseException("The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)");
122 }
123 }
124 private function decodeArray(int $size, int $offset) : array
125 {
126 $array = [];
127 for ($i = 0; $i < $size; ++$i) {
128 [$value, $offset] = $this->decode($offset);
129 $array[] = $value;
130 }
131 return [$array, $offset];
132 }
133 private function decodeBoolean(int $size) : bool
134 {
135 return $size !== 0;
136 }
137 private function decodeDouble(string $bytes) : float
138 {
139 // This assumes IEEE 754 doubles, but most (all?) modern platforms
140 // use them.
141 $rc = \unpack('E', $bytes);
142 if ($rc === \false) {
143 throw new InvalidDatabaseException('Could not unpack a double value from the given bytes.');
144 }
145 [, $double] = $rc;
146 return $double;
147 }
148 private function decodeFloat(string $bytes) : float
149 {
150 // This assumes IEEE 754 floats, but most (all?) modern platforms
151 // use them.
152 $rc = \unpack('G', $bytes);
153 if ($rc === \false) {
154 throw new InvalidDatabaseException('Could not unpack a float value from the given bytes.');
155 }
156 [, $float] = $rc;
157 return $float;
158 }
159 private function decodeInt32(string $bytes, int $size) : int
160 {
161 switch ($size) {
162 case 0:
163 return 0;
164 case 1:
165 case 2:
166 case 3:
167 $bytes = \str_pad($bytes, 4, "\x00", \STR_PAD_LEFT);
168 break;
169 case 4:
170 break;
171 default:
172 throw new InvalidDatabaseException("The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)");
173 }
174 $rc = \unpack('l', $this->maybeSwitchByteOrder($bytes));
175 if ($rc === \false) {
176 throw new InvalidDatabaseException('Could not unpack a 32bit integer value from the given bytes.');
177 }
178 [, $int] = $rc;
179 return $int;
180 }
181 private function decodeMap(int $size, int $offset) : array
182 {
183 $map = [];
184 for ($i = 0; $i < $size; ++$i) {
185 [$key, $offset] = $this->decode($offset);
186 [$value, $offset] = $this->decode($offset);
187 $map[$key] = $value;
188 }
189 return [$map, $offset];
190 }
191 private function decodePointer(int $ctrlByte, int $offset) : array
192 {
193 $pointerSize = ($ctrlByte >> 3 & 0x3) + 1;
194 $buffer = Util::read($this->fileStream, $offset, $pointerSize);
195 $offset += $pointerSize;
196 switch ($pointerSize) {
197 case 1:
198 $packed = \chr($ctrlByte & 0x7) . $buffer;
199 $rc = \unpack('n', $packed);
200 if ($rc === \false) {
201 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes (pointerSize is 1).');
202 }
203 [, $pointer] = $rc;
204 $pointer += $this->pointerBase;
205 break;
206 case 2:
207 $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
208 $rc = \unpack('N', $packed);
209 if ($rc === \false) {
210 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes (pointerSize is 2).');
211 }
212 [, $pointer] = $rc;
213 $pointer += $this->pointerBase + 2048;
214 break;
215 case 3:
216 $packed = \chr($ctrlByte & 0x7) . $buffer;
217 // It is safe to use 'N' here, even on 32 bit machines as the
218 // first bit is 0.
219 $rc = \unpack('N', $packed);
220 if ($rc === \false) {
221 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes (pointerSize is 3).');
222 }
223 [, $pointer] = $rc;
224 $pointer += $this->pointerBase + 526336;
225 break;
226 case 4:
227 // We cannot use unpack here as we might overflow on 32 bit
228 // machines
229 $pointerOffset = $this->decodeUint($buffer, $pointerSize);
230 $pointerBase = $this->pointerBase;
231 if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
232 $pointer = $pointerOffset + $pointerBase;
233 } else {
234 throw new \RuntimeException('The database offset is too large to be represented on your platform.');
235 }
236 break;
237 default:
238 throw new InvalidDatabaseException('Unexpected pointer size ' . $pointerSize);
239 }
240 return [$pointer, $offset];
241 }
242 // @phpstan-ignore-next-line
243 private function decodeUint(string $bytes, int $byteLength)
244 {
245 if ($byteLength === 0) {
246 return 0;
247 }
248 // PHP integers are signed. PHP_INT_SIZE - 1 is the number of
249 // complete bytes that can be converted to an integer. However,
250 // we can convert another byte if the leading bit is zero.
251 $useRealInts = $byteLength <= \PHP_INT_SIZE - 1 || $byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0;
252 if ($useRealInts) {
253 $integer = 0;
254 for ($i = 0; $i < $byteLength; ++$i) {
255 $part = \ord($bytes[$i]);
256 $integer = ($integer << 8) + $part;
257 }
258 return $integer;
259 }
260 // We only use gmp or bcmath if the final value is too big
261 $integerAsString = '0';
262 for ($i = 0; $i < $byteLength; ++$i) {
263 $part = \ord($bytes[$i]);
264 if (\extension_loaded('gmp')) {
265 $integerAsString = \gmp_strval(\gmp_add(\gmp_mul($integerAsString, '256'), $part));
266 } elseif (\extension_loaded('bcmath')) {
267 $integerAsString = \bcadd(\bcmul($integerAsString, '256'), (string) $part);
268 } else {
269 throw new \RuntimeException('The gmp or bcmath extension must be installed to read this database.');
270 }
271 }
272 return $integerAsString;
273 }
274 private function sizeFromCtrlByte(int $ctrlByte, int $offset) : array
275 {
276 $size = $ctrlByte & 0x1f;
277 if ($size < 29) {
278 return [$size, $offset];
279 }
280 $bytesToRead = $size - 28;
281 $bytes = Util::read($this->fileStream, $offset, $bytesToRead);
282 if ($size === 29) {
283 $size = 29 + \ord($bytes);
284 } elseif ($size === 30) {
285 $rc = \unpack('n', $bytes);
286 if ($rc === \false) {
287 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes.');
288 }
289 [, $adjust] = $rc;
290 $size = 285 + $adjust;
291 } else {
292 $rc = \unpack('N', "\x00" . $bytes);
293 if ($rc === \false) {
294 throw new InvalidDatabaseException('Could not unpack an unsigned long value from the given bytes.');
295 }
296 [, $adjust] = $rc;
297 $size = $adjust + 65821;
298 }
299 return [$size, $offset + $bytesToRead];
300 }
301 private function maybeSwitchByteOrder(string $bytes) : string
302 {
303 return $this->switchByteOrder ? \strrev($bytes) : $bytes;
304 }
305 private function isPlatformLittleEndian() : bool
306 {
307 $testint = 0xff;
308 $packed = \pack('S', $testint);
309 $rc = \unpack('v', $packed);
310 if ($rc === \false) {
311 throw new InvalidDatabaseException('Could not unpack an unsigned short value from the given bytes.');
312 }
313 return $testint === \current($rc);
314 }
315 }
316