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