<?php
+/**
+ * ZIP file directories reader, for the purposes of upload verification.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
/**
- * A class for reading ZIP file directories, for the purposes of upload
- * verification.
+ * A class for reading ZIP file directories, for the purposes of upload
+ * verification.
*
* Only a functional interface is provided: ZipFileReader::read(). No access is
* given to object instances.
/**
* Read a ZIP file and call a function for each file discovered in it.
*
- * Because this class is aimed at verification, an error is raised on
+ * Because this class is aimed at verification, an error is raised on
* suspicious or ambiguous input, instead of emulating some standard
* behaviour.
*
- * @param $fileName The archive file name
- * @param $callback The callback function. It will be called for each file
+ * @param $fileName string The archive file name
+ * @param $callback Array The callback function. It will be called for each file
* with a single associative array each time, with members:
*
- * - name: The file name. Directories conventionally have a trailing
+ * - name: The file name. Directories conventionally have a trailing
* slash.
*
* - mtime: The file modification time, in MediaWiki 14-char format
*
* - size: The uncompressed file size
*
- * @param $options An associative array of read options, with the option
+ * @param $options Array An associative array of read options, with the option
* name in the key. This may currently contain:
*
- * - zip64: If this is set to true, then we will emulate a
- * library with ZIP64 support, like OpenJDK 7. If it is set to
- * false, then we will emulate a library with no knowledge of
+ * - zip64: If this is set to true, then we will emulate a
+ * library with ZIP64 support, like OpenJDK 7. If it is set to
+ * false, then we will emulate a library with no knowledge of
* ZIP64.
*
- * NOTE: The ZIP64 code is untested and probably doesn't work. It
- * turned out to be easier to just reject ZIP64 archive uploads,
- * since they are likely to be very rare. Confirming safety of a
- * ZIP64 file is fairly complex. What do you do with a file that is
- * ambiguous and broken when read with a non-ZIP64 reader, but valid
- * when read with a ZIP64 reader? This situation is normal for a
- * valid ZIP64 file, and working out what non-ZIP64 readers will make
+ * NOTE: The ZIP64 code is untested and probably doesn't work. It
+ * turned out to be easier to just reject ZIP64 archive uploads,
+ * since they are likely to be very rare. Confirming safety of a
+ * ZIP64 file is fairly complex. What do you do with a file that is
+ * ambiguous and broken when read with a non-ZIP64 reader, but valid
+ * when read with a ZIP64 reader? This situation is normal for a
+ * valid ZIP64 file, and working out what non-ZIP64 readers will make
* of such a file is not trivial.
*
- * @return A Status object. The following fatal errors are defined:
+ * @return Status object. The following fatal errors are defined:
*
* - zip-file-open-error: The file could not be opened.
*
* - zip-wrong-format: The file does not appear to be a ZIP file.
*
- * - zip-bad: There was something wrong or ambiguous about the file
+ * - zip-bad: There was something wrong or ambiguous about the file
* data.
*
- * - zip-unsupported: The ZIP file uses features which
+ * - zip-unsupported: The ZIP file uses features which
* ZipDirectoryReader does not support.
*
- * The default messages for those fatal errors are written in a way that
+ * The default messages for those fatal errors are written in a way that
* makes sense for upload verification.
*
- * If a fatal error is returned, more information about the error will be
+ * If a fatal error is returned, more information about the error will be
* available in the debug log.
*
* Note that the callback function may be called any number of times before
- * a fatal error is returned. If this occurs, the data sent to the callback
+ * a fatal error is returned. If this occurs, the data sent to the callback
* function should be discarded.
*/
public static function read( $fileName, $callback, $options = array() ) {
/** Stored headers */
var $eocdr, $eocdr64, $eocdr64Locator;
+ var $data;
+
/** The "extra field" ID for ZIP64 central directory entries */
const ZIP64_EXTRA_HEADER = 0x0001;
/** The index of the "general field" bit for central directory encryption */
const GENERAL_CD_ENCRYPTED = 13;
-
/**
* Private constructor
*/
/**
* Read the directory according to settings in $this.
+ *
+ * @return Status
*/
function execute() {
$this->file = fopen( $this->fileName, 'r' );
}
/**
- * Read the header which is at the end of the central directory,
- * unimaginatively called the "end of central directory record" by the ZIP
+ * Read the header which is at the end of the central directory,
+ * unimaginatively called the "end of central directory record" by the ZIP
* spec.
*/
function readEndOfCentralDirectoryRecord() {
$block = $this->getBlock( $startPos );
$sigPos = strrpos( $block, "PK\x05\x06" );
if ( $sigPos === false ) {
- $this->error( 'zip-wrong-format',
+ $this->error( 'zip-wrong-format',
"zip file lacks EOCDR signature. It probably isn't a zip file." );
}
}
/**
- * Read the header called the "ZIP64 end of central directory locator". An
+ * Read the header called the "ZIP64 end of central directory locator". An
* error will be raised if it does not exist.
*/
function readZip64EndOfCentralDirectoryLocator() {
);
$structSize = $this->getStructSize( $info );
- $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
+ $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
- $structSize, $structSize );
$this->eocdr64Locator = $data = $this->unpack( $block, $info );
if ( $data['signature'] !== "PK\x06\x07" ) {
- // Note: Java will allow this and continue to read the
- // EOCDR64, so we have to reject the upload, we can't
+ // Note: Java will allow this and continue to read the
+ // EOCDR64, so we have to reject the upload, we can't
// just use the EOCDR header instead.
$this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory locator' );
}
}
/**
- * Read the header called the "ZIP64 end of central directory record". It
+ * Read the header called the "ZIP64 end of central directory record". It
* may replace the regular "end of central directory record" in ZIP64 files.
*/
function readZip64EndOfCentralDirectoryRecord() {
$this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
}
if ( $data['disk'] !== 0
- || $data['CD start disk'] !== 0 )
+ || $data['CD start disk'] !== 0 )
{
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
}
}
/**
- * Find the location of the central directory, as would be seen by a
+ * Find the location of the central directory, as would be seen by a
* non-ZIP64 reader.
*
* @return List containing offset, size and end position.
// Some readers use the EOCDR position instead of the offset field
// to find the directory, so to be safe, we check if they both agree.
if ( $offset + $size != $endPos ) {
- $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+ $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
'of central directory record' );
}
return array( $offset, $size );
}
/**
- * Find the location of the central directory, as would be seen by a
+ * Find the location of the central directory, as would be seen by a
* ZIP64-compliant reader.
*
- * @return List containing offset, size and end position.
+ * @return array List containing offset, size and end position.
*/
function findZip64CentralDirectory() {
- // The spec is ambiguous about the exact rules of precedence between the
- // ZIP64 headers and the original headers. Here we follow zip_util.c
+ // The spec is ambiguous about the exact rules of precedence between the
+ // ZIP64 headers and the original headers. Here we follow zip_util.c
// from OpenJDK 7.
$size = $this->eocdr['CD size'];
$offset = $this->eocdr['CD offset'];
$numEntries = $this->eocdr['CD entries total'];
$endPos = $this->eocdr['position'];
- if ( $size == 0xffffffff
+ if ( $size == 0xffffffff
|| $offset == 0xffffffff
|| $numEntries == 0xffff )
{
// Some readers use the EOCDR position instead of the offset field
// to find the directory, so to be safe, we check if they both agree.
if ( $offset + $size != $endPos ) {
- $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+ $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
'of central directory record' );
}
return array( $offset, $size );
}
// Convert the timestamp into MediaWiki format
- // For the format, please see the MS-DOS 2.0 Programmer's Reference,
+ // For the format, please see the MS-DOS 2.0 Programmer's Reference,
// pages 3-5 and 3-6.
$time = $data['mod time'];
$date = $data['mod date'];
$year, $month, $day, $hour, $minute, $second );
// Convert the character set in the file name
- if ( !function_exists( 'iconv' )
- || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
+ if ( !function_exists( 'iconv' )
+ || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
{
$name = $data['name'];
} else {
/**
* Interpret ZIP64 "extra field" data and return an associative array.
+ * @return array|bool
*/
function unpackZip64Extra( $extraField ) {
$extraHeaderInfo = array(
);
$extraPos = 0;
- $extraField = $data['extra field'];
while ( $extraPos < strlen( $extraField ) ) {
$extra = $this->unpack( $extraField, $extraHeaderInfo, $extraPos );
$extraPos += $extraHeaderSize;
- $extra += $this->unpack( $extraField,
+ $extra += $this->unpack( $extraField,
array( 'data' => array( 'string', $extra['size'] ) ),
$extraPos );
$extraPos += $extra['size'];
* Get the file contents from a given offset. If there are not enough bytes
* in the file to satisfy the request, an exception will be thrown.
*
- * @param $start The byte offset of the start of the block.
- * @param $length The number of bytes to return. If omitted, the remainder
+ * @param $start int The byte offset of the start of the block.
+ * @param $length int The number of bytes to return. If omitted, the remainder
* of the file will be returned.
*
* @return string
$block .= $this->getSegment( $segIndex );
}
- $block = substr( $block,
+ $block = substr( $block,
$start - $startSeg * self::SEGSIZE,
$length );
-
+
if ( strlen( $block ) < $length ) {
$this->error( 'zip-bad', 'getBlock() returned an unexpectedly small amount of data' );
}
}
/**
- * Get a section of the file starting at position $segIndex * self::SEGSIZE,
- * of length self::SEGSIZE. The result is cached. This is a helper function
+ * Get a section of the file starting at position $segIndex * self::SEGSIZE,
+ * of length self::SEGSIZE. The result is cached. This is a helper function
* for getBlock().
*
- * If there are not enough bytes in the file to satsify the request, the
- * return value will be truncated. If a request is made for a segment beyond
+ * If there are not enough bytes in the file to satsify the request, the
+ * return value will be truncated. If a request is made for a segment beyond
* the end of the file, an empty string will be returned.
+ * @return string
*/
function getSegment( $segIndex ) {
if ( !isset( $this->buffer[$segIndex] ) ) {
/**
* Get the size of a structure in bytes. See unpack() for the format of $struct.
+ * @return int
*/
function getStructSize( $struct ) {
$size = 0;
- foreach ( $struct as $key => $type ) {
+ foreach ( $struct as $type ) {
if ( is_array( $type ) ) {
list( $typeName, $fieldSize ) = $type;
$size += $fieldSize;
}
/**
- * Unpack a binary structure. This is like the built-in unpack() function
+ * Unpack a binary structure. This is like the built-in unpack() function
* except nicer.
*
- * @param $string The binary data input
+ * @param $string string The binary data input
*
- * @param $struct An associative array giving structure members and their
- * types. In the key is the field name. The value may be either an
- * integer, in which case the field is a little-endian unsigned integer
- * encoded in the given number of bytes, or an array, in which case the
- * first element of the array is the type name, and the subsequent
+ * @param $struct array An associative array giving structure members and their
+ * types. In the key is the field name. The value may be either an
+ * integer, in which case the field is a little-endian unsigned integer
+ * encoded in the given number of bytes, or an array, in which case the
+ * first element of the array is the type name, and the subsequent
* elements are type-dependent parameters. Only one such type is defined:
- * - "string": The second array element gives the length of string.
+ * - "string": The second array element gives the length of string.
* Not null terminated.
*
- * @param $offset The offset into the string at which to start unpacking.
+ * @param $offset int The offset into the string at which to start unpacking.
*
- * @return Unpacked associative array. Note that large integers in the input
- * may be represented as floating point numbers in the return value, so
- * the use of weak comparison is advised.
+ * @return array Unpacked associative array. Note that large integers in the input
+ * may be represented as floating point numbers in the return value, so
+ * the use of weak comparison is advised.
*/
function unpack( $string, $struct, $offset = 0 ) {
$size = $this->getStructSize( $struct );
$length = intval( $type );
$bytes = substr( $string, $pos, $length );
- // Calculate the value. Use an algorithm which automatically
- // upgrades the value to floating point if necessary.
+ // Calculate the value. Use an algorithm which automatically
+ // upgrades the value to floating point if necessary.
$value = 0;
for ( $i = $length - 1; $i >= 0; $i-- ) {
$value *= 256;
}
/**
- * Returns a bit from a given position in an integer value, converted to
+ * Returns a bit from a given position in an integer value, converted to
* boolean.
*
* @param $value integer
- * @param $bitIndex The index of the bit, where 0 is the LSB.
+ * @param $bitIndex int The index of the bit, where 0 is the LSB.
+ * @return bool
*/
function testBit( $value, $bitIndex ) {
return (bool)( ( $value >> $bitIndex ) & 1 );
parent::__construct( "ZipDirectoryReader error: $code" );
}
+ /**
+ * @return mixed
+ */
function getErrorCode() {
return $this->code;
}