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