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