1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * Abstract base class for all the readers 6 * 7 * A reader is a compilation of serveral files that can be read 8 * 9 * PHP versions 4 and 5 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Lesser General Public 13 * License as published by the Free Software Foundation; either 14 * version 2.1 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Lesser General Public License for more details. 20 * 21 * You should have received a copy of the GNU Lesser General Public 22 * License along with this library; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA 24 * 25 * @category File Formats 26 * @package File_Archive 27 * @author Vincent Lascaux <vincentlascaux@php.net> 28 * @copyright 1997-2005 The PHP Group 29 * @license http://www.gnu.org/copyleft/lesser.html LGPL 30 * @version CVS: $Id: Reader.php,v 1.33 2005/07/07 12:24:57 vincentlascaux Exp $ 31 * @link http://pear.php.net/package/File_Archive 32 */ 33 34require_once "PEAR.php"; 35 36/** 37 * Abstract base class for all the readers 38 * 39 * A reader is a compilation of serveral files that can be read 40 */ 41class File_Archive_Reader 42{ 43 /** 44 * Move to the next file in the reader 45 * 46 * @return bool false iif no more files are available 47 */ 48 function next() 49 { 50 return false; 51 } 52 53 /** 54 * Move to the next file whose name is in directory $filename 55 * or is exactly $filename 56 * 57 * @param string $filename Name of the file to find in the archive 58 * @param bool $close If true, close the reader and search from the first file 59 * @return bool whether the file was found in the archive or not 60 */ 61 function select($filename, $close = true) 62 { 63 $std = $this->getStandardURL($filename); 64 65 if ($close) { 66 $error = $this->close(); 67 if (PEAR::isError($error)) { 68 return $error; 69 } 70 } 71 while (($error = $this->next()) === true) { 72 $sourceName = $this->getFilename(); 73 if ( 74 empty($std) || 75 76 //$std is a file 77 $std == $sourceName || 78 79 //$std is a directory 80 strncmp($std.'/', $sourceName, strlen($std)+1) == 0 81 ) { 82 return true; 83 } 84 } 85 return $error; 86 } 87 88 /** 89 * Returns the standard path 90 * Changes \ to / 91 * Removes the .. and . from the URL 92 * @param string $path a valid URL that may contain . or .. and \ 93 * @static 94 */ 95 function getStandardURL($path) 96 { 97 if ($path == '.') { 98 return ''; 99 } 100 $std = str_replace("\\", "/", $path); 101 while ($std != ($std = preg_replace("/[^\/:?]+\/\.\.\//", "", $std))) ; 102 $std = str_replace("/./", "", $std); 103 if (strncmp($std, "./", 2) == 0) { 104 return substr($std, 2); 105 } else { 106 return $std; 107 } 108 } 109 110 /** 111 * Returns the name of the file currently read by the reader 112 * 113 * Warning: undefined behaviour if no call to next have been 114 * done or if last call to next has returned false 115 * 116 * @return string Name of the current file 117 */ 118 function getFilename() 119 { 120 return PEAR::raiseError("Reader abstract function call (getFilename)"); 121 } 122 123 /** 124 * Returns the list of filenames from the current pos to the end of the source 125 * The source will be closed after having called this function 126 * This function goes through the whole archive (which may be slow). 127 * If you intend to work on the reader, doing it in one pass would be faster 128 * 129 * @return array filenames from the current pos to the end of the source 130 */ 131 function getFileList() 132 { 133 $result = array(); 134 while ( ($error = $this->next()) === true) { 135 $result[] = $this->getFilename(); 136 } 137 $this->close(); 138 if (PEAR::isError($error)) { 139 return $error; 140 } else { 141 return $result; 142 } 143 } 144 145 /** 146 * Returns an array of statistics about the file 147 * (see the PHP stat function for more information) 148 * 149 * The returned array may be empty, even if readers should try 150 * their best to return as many data as possible 151 */ 152 function getStat() { return array(); } 153 154 /** 155 * Returns the MIME associated with the current file 156 * The default function does that by looking at the extension of the file 157 */ 158 function getMime() 159 { 160 require_once "File/Archive/Reader/MimeList.php"; 161 return File_Archive_Reader_GetMime($this->getFilename()); 162 } 163 164 /** 165 * If the current file of the archive is a physical file, 166 * 167 * @return the name of the physical file containing the data 168 * or null if no such file exists 169 * 170 * The data filename may not be the same as the filename. 171 */ 172 function getDataFilename() { return null; } 173 174 /** 175 * Reads some data from the current file 176 * If the end of the file is reached, returns null 177 * If $length is not specified, reads up to the end of the file 178 * If $length is specified reads up to $length 179 */ 180 function getData($length = -1) 181 { 182 return PEAR::raiseError("Reader abstract function call (getData)"); 183 } 184 185 /** 186 * Skip some data and returns how many bytes have been skipped 187 * This is strictly equivalent to 188 * return strlen(getData($length)) 189 * But could be far more efficient 190 */ 191 function skip($length = -1) 192 { 193 $data = $this->getData($length); 194 if (PEAR::isError($data)) { 195 return $data; 196 } else { 197 return strlen($data); 198 } 199 } 200 201 /** 202 * Move the current position back of a given amount of bytes. 203 * Not all readers may implement this function (a PEAR error will 204 * be returned if the reader can't rewind) 205 * 206 * @param int $length number of bytes to seek before the current pos 207 * or -1 to move back to the begining of the current file 208 * @return the number of bytes really rewinded (which may be less than 209 * $length if the current pos is less than $length 210 */ 211 function rewind($length = -1) 212 { 213 return PEAR::raiseError('Rewind function is not implemented on this reader'); 214 } 215 216 /** 217 * Returns the current offset in the current file 218 */ 219 function tell() 220 { 221 $offset = $this->rewind(); 222 $this->skip($offset); 223 return $offset; 224 } 225 226 /** 227 * Put back the reader in the state it was before the first call 228 * to next() 229 */ 230 function close() 231 { 232 } 233 234 /** 235 * Sends the current file to the Writer $writer 236 * The data will be sent by chunks of at most $bufferSize bytes 237 * If $bufferSize <= 0 (default), the blockSize option is used 238 */ 239 function sendData(&$writer, $bufferSize = 0) 240 { 241 if (PEAR::isError($writer)) { 242 return $writer; 243 } 244 if ($bufferSize <= 0) { 245 $bufferSize = File_Archive::getOption('blockSize'); 246 } 247 248 $filename = $this->getDataFilename(); 249 if ($filename !== null) { 250 $error = $writer->writeFile($filename); 251 if (PEAR::isError($error)) { 252 return $error; 253 } 254 } else { 255 while (($data = $this->getData($bufferSize)) !== null) { 256 if (PEAR::isError($data)) { 257 return $data; 258 } 259 $error = $writer->writeData($data); 260 if (PEAR::isError($error)) { 261 return $error; 262 } 263 } 264 } 265 } 266 267 /** 268 * Sends the whole reader to $writer and close the reader 269 * 270 * @param File_Archive_Writer $writer Where to write the files of the reader 271 * @param bool $autoClose If true, close $writer at the end of the function. 272 * Default value is true 273 * @param int $bufferSize Size of the chunks that will be sent to the writer 274 * If $bufferSize <= 0 (default value), the blockSize option is used 275 */ 276 function extract(&$writer, $autoClose = true, $bufferSize = 0) 277 { 278 if (PEAR::isError($writer)) { 279 $this->close(); 280 return $writer; 281 } 282 283 while (($error = $this->next()) === true) { 284 if ($writer->newFileNeedsMIME()) { 285 $mime = $this->getMime(); 286 } else { 287 $mime = null; 288 } 289 $error = $writer->newFile( 290 $this->getFilename(), 291 $this->getStat(), 292 $mime 293 ); 294 if (PEAR::isError($error)) { 295 break; 296 } 297 $error = $this->sendData($writer, $bufferSize); 298 if (PEAR::isError($error)) { 299 break; 300 } 301 } 302 $this->close(); 303 if ($autoClose) { 304 $writer->close(); 305 } 306 if (PEAR::isError($error)) { 307 return $error; 308 } 309 } 310 311 /** 312 * Extract only one file (given by the URL) 313 * 314 * @param string $filename URL of the file to extract from this 315 * @param File_Archive_Writer $writer Where to write the file 316 * @param bool $autoClose If true, close $writer at the end of the function 317 * Default value is true 318 * @param int $bufferSize Size of the chunks that will be sent to the writer 319 * If $bufferSize <= 0 (default value), the blockSize option is used 320 */ 321 function extractFile($filename, &$writer, 322 $autoClose = true, $bufferSize = 0) 323 { 324 if (PEAR::isError($writer)) { 325 return $writer; 326 } 327 328 if (($error = $this->select($filename)) === true) { 329 $result = $this->sendData($writer, $bufferSize); 330 if (!PEAR::isError($result)) { 331 $result = true; 332 } 333 } else if ($error === false) { 334 $result = PEAR::raiseError("File $filename not found"); 335 } else { 336 $result = $error; 337 } 338 if ($autoClose) { 339 $error = $writer->close(); 340 if (PEAR::isError($error)) { 341 return $error; 342 } 343 } 344 return $result; 345 } 346 347 /** 348 * Return a writer that allows appending files to the archive 349 * After having called makeAppendWriter, $this is closed and should not be 350 * used until the returned writer is closed. 351 * 352 * @return a writer that will allow to append files to an existing archive 353 * @see makeWriter 354 */ 355 function makeAppendWriter() 356 { 357 require_once "File/Archive/Predicate/False.php"; 358 return $this->makeWriterRemoveFiles(new File_Archive_Predicate_False()); 359 } 360 361 /** 362 * Return a writer that has the same properties as the one returned by 363 * makeAppendWriter, but after having removed all the files that follow a 364 * given predicate. 365 * After a call to makeWriterRemoveFiles, $this is closed and should not 366 * be used until the returned writer is closed 367 * 368 * @param File_Archive_Predicate $pred the predicate verified by removed files 369 * @return File_Archive_Writer that allows to append files to the archive 370 */ 371 function makeWriterRemoveFiles($pred) 372 { 373 return PEAR::raiseError("Reader abstract function call (makeWriterRemoveFiles)"); 374 } 375 376 /** 377 * Returns a writer that removes the current file 378 * This is a syntaxic sugar for makeWriterRemoveFiles(new File_Archive_Predicate_Current()); 379 */ 380 function makeWriterRemove() 381 { 382 require_once "File/Archive/Predicate/Current.php"; 383 return $this->makeWriterRemoveFiles(new File_Archive_Predicate_Current()); 384 } 385 386 /** 387 * Removes the current file from the reader 388 */ 389 function remove() 390 { 391 $writer = $this->makeWriterRemove(); 392 if (PEAR::isError($writer)) { 393 return $writer; 394 } 395 $writer->close(); 396 } 397 398 /** 399 * Return a writer that has the same properties as the one returned by makeWriter, but after 400 * having removed a block of data from the current file. The writer will append data to the current file 401 * no data (other than the block) will be removed 402 * 403 * @param array Lengths of the blocks. The first one will be discarded, the second one kept, the third 404 * one discarded... If the sum of the blocks is less than the size of the file, the comportment is the 405 * same as if a last block was set in the array to reach the size of the file 406 * if $length is -1, the file is truncated from the specified pos 407 * It is possible to specify blocks of size 0 408 * @param int $seek relative pos of the block 409 */ 410 function makeWriterRemoveBlocks($blocks, $seek = 0) 411 { 412 return PEAR::raiseError("Reader abstract function call (makeWriterRemoveBlocks)"); 413 } 414} 415 416?>