Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
91.59% |
98 / 107 |
|
70.00% |
7 / 10 |
CRAP | |
0.00% |
0 / 1 |
PHP | |
91.59% |
98 / 107 |
|
70.00% |
7 / 10 |
39.91 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
close | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
get | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
read | |
81.48% |
22 / 27 |
|
0.00% |
0 / 1 |
9.51 | |||
readInt31 | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 | |||
readInt32 | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
7 | |||
find | |
93.33% |
28 / 30 |
|
0.00% |
0 / 1 |
8.02 | |||
exists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
firstkey | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
nextkey | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace Cdb\Reader; |
20 | |
21 | use Cdb\Exception; |
22 | use Cdb\Reader; |
23 | use Cdb\Util; |
24 | |
25 | /** |
26 | * CDB reader class |
27 | * |
28 | * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that |
29 | * appears in PHP 5.3. |
30 | */ |
31 | class PHP extends Reader { |
32 | /** |
33 | * The file name of the CDB file. |
34 | * @var string |
35 | */ |
36 | protected $fileName; |
37 | |
38 | /** |
39 | * The file handle |
40 | */ |
41 | protected $handle; |
42 | |
43 | /** |
44 | * @var string |
45 | * First 2048 bytes of CDB file, containing pointers to hash table. |
46 | */ |
47 | protected $index; |
48 | |
49 | /** |
50 | * Offset in file where value of found key starts. |
51 | * @var int |
52 | */ |
53 | protected $dataPos; |
54 | |
55 | /** |
56 | * Byte length of found key's value. |
57 | * @var int |
58 | */ |
59 | protected $dataLen; |
60 | |
61 | /** |
62 | * File position indicator when iterating over keys. |
63 | * @var int |
64 | */ |
65 | protected $keyIterPos = 2048; |
66 | |
67 | /** |
68 | * Offset in file where hash tables start. |
69 | * @var int |
70 | */ |
71 | protected $keyIterStop; |
72 | |
73 | /** |
74 | * Read buffer for CDB file. |
75 | * @var string |
76 | */ |
77 | protected $buf; |
78 | |
79 | /** |
80 | * File offset where read buffer starts. |
81 | * @var int |
82 | */ |
83 | protected $bufStart; |
84 | |
85 | /** |
86 | * File handle position indicator. |
87 | * @var int |
88 | */ |
89 | protected $filePos = 2048; |
90 | |
91 | /** |
92 | * @param string $fileName |
93 | * @throws Exception If CDB file cannot be opened or if it contains fewer |
94 | * than 2048 bytes of data. |
95 | */ |
96 | public function __construct( string $fileName ) { |
97 | $this->fileName = $fileName; |
98 | $this->handle = fopen( $fileName, 'rb' ); |
99 | if ( !$this->handle ) { |
100 | throw new Exception( 'Unable to open CDB file "' . $this->fileName . '".' ); |
101 | } |
102 | $this->index = fread( $this->handle, 2048 ); |
103 | if ( strlen( $this->index ) !== 2048 ) { |
104 | throw new Exception( 'CDB file contains fewer than 2048 bytes of data.' ); |
105 | } |
106 | } |
107 | |
108 | /** |
109 | * Close the handle on the CDB file. |
110 | */ |
111 | public function close(): void { |
112 | if ( $this->handle ) { |
113 | fclose( $this->handle ); |
114 | } |
115 | $this->handle = null; |
116 | } |
117 | |
118 | /** |
119 | * Get the value of a key. |
120 | * |
121 | * @param string|int $key |
122 | * @return string|false The key's value or false if not found |
123 | */ |
124 | public function get( $key ) { |
125 | if ( $this->find( (string)$key ) ) { |
126 | return $this->read( $this->dataPos, $this->dataLen ); |
127 | } |
128 | |
129 | return false; |
130 | } |
131 | |
132 | /** |
133 | * Read data from the CDB file. |
134 | * |
135 | * @param int $start Start reading from this position |
136 | * @param int $len Number of bytes to read |
137 | * @return string Read data. |
138 | */ |
139 | protected function read( $start, $len ) { |
140 | $end = $start + $len; |
141 | |
142 | // The first 2048 bytes are the lookup table, which is read into |
143 | // memory on initialization. |
144 | if ( $end <= 2048 ) { |
145 | return substr( $this->index, $start, $len ); |
146 | } |
147 | |
148 | // Read data from the internal buffer first. |
149 | $bytes = ''; |
150 | if ( $this->buf && $start >= $this->bufStart ) { |
151 | $bytes .= substr( $this->buf, $start - $this->bufStart, $len ); |
152 | $bytesRead = strlen( $bytes ); |
153 | $len -= $bytesRead; |
154 | $start += $bytesRead; |
155 | } else { |
156 | $bytesRead = 0; |
157 | } |
158 | |
159 | if ( !$len ) { |
160 | return $bytes; |
161 | } |
162 | |
163 | // Many reads are sequential, so the file position indicator may |
164 | // already be in the right place, in which case we can avoid the |
165 | // call to fseek(). |
166 | if ( $start !== $this->filePos ) { |
167 | if ( fseek( $this->handle, $start ) === -1 ) { |
168 | // This can easily happen if the internal pointers are incorrect |
169 | throw new Exception( |
170 | 'Seek failed, file "' . $this->fileName . '" may be corrupted.' ); |
171 | } |
172 | } |
173 | |
174 | $buf = fread( $this->handle, max( $len, 1024 ) ); |
175 | if ( $buf === false ) { |
176 | $buf = ''; |
177 | } |
178 | |
179 | $bytes .= substr( $buf, 0, $len ); |
180 | if ( strlen( $bytes ) !== $len + $bytesRead ) { |
181 | throw new Exception( |
182 | 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' ); |
183 | } |
184 | |
185 | $this->filePos = $end; |
186 | $this->bufStart = $start; |
187 | $this->buf = $buf; |
188 | |
189 | return $bytes; |
190 | } |
191 | |
192 | /** |
193 | * Unpack an unsigned integer and throw an exception if it needs more than 31 bits. |
194 | * |
195 | * @param int $pos Position to read from. |
196 | * @throws Exception When the integer cannot be represented in 31 bits. |
197 | * @return int |
198 | */ |
199 | protected function readInt31( $pos = 0 ) { |
200 | $uint31 = $this->readInt32( $pos ); |
201 | if ( $uint31 > 0x7fffffff ) { |
202 | throw new Exception( |
203 | 'Error in CDB file "' . $this->fileName . '", integer too big.' ); |
204 | } |
205 | |
206 | return $uint31; |
207 | } |
208 | |
209 | /** |
210 | * Unpack a 32-bit integer. |
211 | * |
212 | * @param int $pos |
213 | * @return int |
214 | */ |
215 | protected function readInt32( $pos = 0 ) { |
216 | static $lookups; |
217 | |
218 | if ( !$lookups ) { |
219 | $lookups = []; |
220 | for ( $i = 1; $i < 256; $i++ ) { |
221 | $lookups[ chr( $i ) ] = $i; |
222 | } |
223 | } |
224 | |
225 | $buf = $this->read( $pos, 4 ); |
226 | |
227 | $rv = 0; |
228 | |
229 | if ( $buf[0] !== "\x0" ) { |
230 | $rv = $lookups[ $buf[0] ]; |
231 | } |
232 | if ( $buf[1] !== "\x0" ) { |
233 | $rv |= ( $lookups[ $buf[1] ] << 8 ); |
234 | } |
235 | if ( $buf[2] !== "\x0" ) { |
236 | $rv |= ( $lookups[ $buf[2] ] << 16 ); |
237 | } |
238 | if ( $buf[3] !== "\x0" ) { |
239 | $rv |= ( $lookups[ $buf[3] ] << 24 ); |
240 | } |
241 | |
242 | return $rv; |
243 | } |
244 | |
245 | /** |
246 | * Search the CDB file for a key. |
247 | * |
248 | * Sets `dataLen` and `dataPos` properties if successful. |
249 | * |
250 | * @param string $key |
251 | * @return bool Whether the key was found. |
252 | */ |
253 | protected function find( $key ) { |
254 | $keyLen = strlen( $key ); |
255 | |
256 | $u = Util::hash( $key ); |
257 | $upos = ( $u << 3 ) & 2047; |
258 | $hashSlots = $this->readInt31( $upos + 4 ); |
259 | if ( !$hashSlots ) { |
260 | return false; |
261 | } |
262 | $hashPos = $this->readInt31( $upos ); |
263 | $keyHash = $u; |
264 | $u = Util::unsignedShiftRight( $u, 8 ); |
265 | $u = Util::unsignedMod( $u, $hashSlots ); |
266 | $u <<= 3; |
267 | $keyPos = $hashPos + $u; |
268 | |
269 | for ( $i = 0; $i < $hashSlots; $i++ ) { |
270 | $hash = $this->readInt32( $keyPos ); |
271 | $pos = $this->readInt31( $keyPos + 4 ); |
272 | if ( !$pos ) { |
273 | return false; |
274 | } |
275 | $keyPos += 8; |
276 | if ( $keyPos == $hashPos + ( $hashSlots << 3 ) ) { |
277 | $keyPos = $hashPos; |
278 | } |
279 | if ( $hash === $keyHash ) { |
280 | if ( $keyLen === $this->readInt31( $pos ) ) { |
281 | $dataLen = $this->readInt31( $pos + 4 ); |
282 | $dataPos = $pos + 8 + $keyLen; |
283 | $foundKey = $this->read( $pos + 8, $keyLen ); |
284 | if ( $foundKey === $key ) { |
285 | // Found |
286 | $this->dataLen = $dataLen; |
287 | $this->dataPos = $dataPos; |
288 | |
289 | return true; |
290 | } |
291 | } |
292 | } |
293 | } |
294 | |
295 | return false; |
296 | } |
297 | |
298 | /** |
299 | * Check if a key exists in the CDB file. |
300 | * |
301 | * @param string|int $key |
302 | * @return bool Whether the key exists. |
303 | */ |
304 | public function exists( $key ): bool { |
305 | return $this->find( (string)$key ); |
306 | } |
307 | |
308 | /** |
309 | * Get the first key from the CDB file and reset the key iterator. |
310 | * |
311 | * @return string|bool Key, or false if no keys in file. |
312 | */ |
313 | public function firstkey() { |
314 | $this->keyIterPos = 4; |
315 | |
316 | if ( !$this->keyIterStop ) { |
317 | $pos = INF; |
318 | for ( $i = 0; $i < 2048; $i += 8 ) { |
319 | $pos = min( $this->readInt31( $i ), $pos ); |
320 | } |
321 | $this->keyIterStop = $pos; |
322 | } |
323 | |
324 | $this->keyIterPos = 2048; |
325 | return $this->nextkey(); |
326 | } |
327 | |
328 | /** |
329 | * Get the next key from the CDB file. |
330 | * |
331 | * @return string|bool Key, or false if no more keys. |
332 | */ |
333 | public function nextkey() { |
334 | if ( $this->keyIterPos >= $this->keyIterStop ) { |
335 | return false; |
336 | } |
337 | $keyLen = $this->readInt31( $this->keyIterPos ); |
338 | $dataLen = $this->readInt31( $this->keyIterPos + 4 ); |
339 | $key = $this->read( $this->keyIterPos + 8, $keyLen ); |
340 | $this->keyIterPos += 8 + $keyLen + $dataLen; |
341 | |
342 | return $key; |
343 | } |
344 | } |