* @copyright Copyright (C) 2013-2015 Nicolai Ehemann and contributors
* @license GNU GPL
* @version 0.7
*/
namespace ZipStreamer;
require "lib/Count64.php";
class COMPR {
// compression method
const STORE = 0x0000; // 0 - The file is stored (no compression)
const DEFLATE = 0x0008; // 8 - The file is deflated
// compression level (for deflate compression)
const NONE = 0;
const NORMAL = 1;
const MAXIMUM = 2;
const SUPERFAST = 3;
}
class ZipStreamer {
const VERSION = "0.7";
const ZIP_LOCAL_FILE_HEADER = 0x04034b50; // local file header signature
const ZIP_CENTRAL_FILE_HEADER = 0x02014b50; // central file header signature
const ZIP_END_OF_CENTRAL_DIRECTORY = 0x06054b50; // end of central directory record
const ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; //zip64 end of central directory record
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR = 0x07064b50; // zip64 end of central directory locator
const ATTR_MADE_BY_VERSION = 0x032d; // made by version (upper byte: UNIX, lower byte v4.5)
const STREAM_CHUNK_SIZE = 1048560; // 16 * 65535 = almost 1mb chunks, for best deflate performance
private $extFileAttrFile;
private $extFileAttrDir;
/** @var stream output stream zip file is written to */
private $outStream;
/** @var boolean zip64 enabled */
private $zip64 = True;
/** @var int compression method */
private $compress;
/** @var int compression level */
private $level;
/** @var array central directory record */
private $cdRec = array();
/** @var int offset of next file to be added */
private $offset;
/** @var boolean indicates zip is finalized and sent to client; no further addition possible */
private $isFinalized = false;
/**
* Constructor. Initializes ZipStreamer object for immediate usage.
* @param array $options Optional, ZipStreamer and zip file options as key/value pairs.
* Valid options are:
* * outstream: stream the zip file is output to (default: stdout)
* * zip64: enabled/disable zip64 support (default: True)
* * compress: int, compression method (one of COMPR::STORE,
* COMPR::DEFLATE, default COMPR::STORE)
* can be overridden for single files
* * level: int, compression level (one of COMPR::NORMAL,
* COMPR::MAXIMUM, COMPR::SUPERFAST, default COMPR::NORMAL)
*/
function __construct($options = NULL) {
$defaultOptions = array(
'outstream' => NULL,
'zip64' => True,
'compress' => COMPR::STORE,
'level' => COMPR::NORMAL,
);
if (is_null($options)) {
$options = array();
}
$options = array_merge($defaultOptions, $options);
if ($options['outstream']) {
$this->outstream = $options['outstream'];
} else {
$this->outstream = fopen('php://output', 'w');
}
$this->zip64 = $options['zip64'];
$this->compress = $options['compress'];
$this->level = $options['level'];
$this->validateCompressionOptions($this->compress, $this->level);
//TODO: is this advisable/necessary?
if (ini_get('zlib.output_compression')) {
ini_set('zlib.output_compression', 'Off');
}
// initialize default external file attributes
$this->extFileAttrFile = UNIX::getExtFileAttr(UNIX::S_IFREG |
UNIX::S_IRUSR | UNIX::S_IWUSR | UNIX::S_IRGRP |
UNIX::S_IROTH);
$this->extFileAttrDir = UNIX::getExtFileAttr(UNIX::S_IFDIR |
UNIX::S_IRWXU | UNIX::S_IRGRP | UNIX::S_IXGRP |
UNIX::S_IROTH | UNIX::S_IXOTH) |
DOS::getExtFileAttr(DOS::DIR);
$this->offset = Count64::construct(0, !$this->zip64);
}
function __destruct() {
$this->isFinalized = true;
$this->cdRec = null;
}
private function getVersionToExtract($isDir) {
if ($this->zip64) {
$version = 0x2d; // 4.5 - File uses ZIP64 format extensions
} else if ($isDir) {
$version = 0x14; // 2.0 - File is a folder (directory)
} else {
$version = 0x0a; // 1.0 - Default value
}
return $version;
}
/**
* Send appropriate http headers before streaming the zip file and disable output buffering.
* This method, if used, has to be called before adding anything to the zip file.
*
* @param string $archiveName Filename of archive to be created (optional, default 'archive.zip')
* @param string $contentType Content mime type to be set (optional, default 'application/zip')
*/
public function sendHeaders($archiveName = 'archive.zip', $contentType = 'application/zip') {
$headerFile = null;
$headerLine = null;
if (!headers_sent($headerFile, $headerLine)
or die("Error: Unable to send file " .
"$archiveName. HTML Headers have already been sent from " .
"$headerFile in line $headerLine" .
"
")) {
if ((ob_get_contents() === false || ob_get_contents() == '')
or die("\nError: Unable to send file " .
"$archiveName.epub. Output buffer " .
"already contains text (typically warnings or errors).
")) {
header('Pragma: public');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s T'));
header('Expires: 0');
header('Accept-Ranges: bytes');
header('Connection: Keep-Alive');
header('Content-Type: ' . $contentType);
header('Content-Disposition: attachment; filename="' . $archiveName . '";');
header('Content-Transfer-Encoding: binary');
}
}
$this->flush();
// turn off output buffering
@ob_end_flush();
}
/**
* Add a file to the archive at the specified location and file name.
*
* @param string $stream Stream to read data from
* @param string $filePath Filepath and name to be used in the archive.
* @param array $options Optional, additional options
* Valid options are:
* * int timestamp: timestamp for the file (default: current time)
* * string comment: comment to be added for this file (default: none)
* * int compress: compression method (override global option for this file)
* * int level: compression level (override global option for this file)
* @return bool $success
*/
public function addFileFromStream($stream, $filePath, $options = NULL) {
if ($this->isFinalized) {
return false;
}
$defaultOptions = array(
'timestamp' => NULL,
'comment' => NULL,
'compress' => $this->compress,
'level' => $this->level,
);
if (is_null($options)) {
$options = array();
}
$options = array_merge($defaultOptions, $options);
$this->validateCompressionOptions($options['compress'], $options['level']);
if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
return false;
}
$filePath = self::normalizeFilePath($filePath);
$gpFlags = GPFLAGS::ADD;
list($gpFlags, $lfhLength) = $this->beginFile($filePath, False, $options['comment'], $options['timestamp'], $gpFlags, $options['compress']);
list($dataLength, $gzLength, $dataCRC32) = $this->streamFileData($stream, $options['compress'], $options['level']);
$ddLength = $this->addDataDescriptor($dataLength, $gzLength, $dataCRC32);
// build cdRec
$this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], $gpFlags, $options['compress'],
$dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile, False);
// calc offset
$this->offset->add($ddLength)->add($lfhLength)->add($gzLength);
return true;
}
/**
* Add an empty directory entry to the zip archive.
*
* @param string $directoryPath Directory Path and name to be added to the archive.
* @param array $options Optional, additional options
* Valid options are:
* * int timestamp: timestamp for the file (default: current time)
* * string comment: comment to be added for this file (default: none)
* @return bool $success
*/
public function addEmptyDir($directoryPath, $options = NULL) {
if ($this->isFinalized) {
return false;
}
$defaultOptions = array(
'timestamp' => NULL,
'comment' => NULL,
);
if (is_null($options)) {
$options = array();
}
$options = array_merge($defaultOptions, $options);
$directoryPath = self::normalizeFilePath($directoryPath) . '/';
if (strlen($directoryPath) > 0) {
$gpFlags = 0x0000;
$gzMethod = COMPR::STORE; // Compression type 0 = stored
list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, True, $options['comment'], $options['timestamp'], $gpFlags, $gzMethod);
// build cdRec
$this->cdRec[] = $this->buildCentralDirectoryHeader($directoryPath, $options['timestamp'], $gpFlags, $gzMethod,
Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, True);
// calc offset
$this->offset->add($lfhLength);
return true;
}
return false;
}
/**
* Close the archive.
* A closed archive can no longer have new files added to it. After
* closing, the zip file is completely written to the output stream.
* @return bool $success
*/
public function finalize() {
if (!$this->isFinalized) {
// print central directory
$cd = implode('', $this->cdRec);
$this->write($cd);
if ($this->zip64) {
// print the zip64 end of central directory record
$this->write($this->buildZip64EndOfCentralDirectoryRecord(strlen($cd)));
// print the zip64 end of central directory locator
$this->write($this->buildZip64EndOfCentralDirectoryLocator(strlen($cd)));
}
// print end of central directory record
$this->write($this->buildEndOfCentralDirectoryRecord(strlen($cd)));
$this->flush();
$this->isFinalized = true;
$cd = null;
$this->cdRec = null;
return true;
}
return false;
}
private function validateCompressionOptions($compress, $level) {
if (COMPR::STORE === $compress) {
} else if (COMPR::DEFLATE === $compress) {
if (COMPR::NONE !== $level
&& !class_exists(DeflatePeclStream::PECL1_DEFLATE_STREAM_CLASS)
&& !class_exists(DeflatePeclStream::PECL2_DEFLATE_STREAM_CLASS)) {
throw new \Exception('unable to use compression method DEFLATE with level other than NONE (requires pecl_http >= 0.10)');
}
} else {
throw new \Exception('invalid option ' . $compress . ' (compression method)');
}
if (!(COMPR::NONE === $level ||
COMPR::NORMAL === $level ||
COMPR::MAXIMUM === $level ||
COMPR::SUPERFAST === $level)) {
throw new \Exception('invalid option ' . $level . ' (compression level');
}
}
private function write($data) {
return fwrite($this->outstream, $data);
}
private function flush() {
return fflush($this->outstream);
}
private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags, $gzMethod,
$dataLength = 0, $gzLength = 0, $dataCRC32 = 0) {
$isFileUTF8 = mb_check_encoding($filePath, 'UTF-8') && !mb_check_encoding($filePath, 'ASCII');
$isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, 'UTF-8')
&& !mb_check_encoding($fileComment, 'ASCII');
if ($isFileUTF8 || $isCommentUTF8) {
$gpFlags |= GPFLAGS::EFS;
}
$localFileHeader = $this->buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength,
$gzLength, $isDir, $dataCRC32);
$this->write($localFileHeader);
return array($gpFlags, strlen($localFileHeader));
}
private function streamFileData($stream, $compress, $level) {
$dataLength = Count64::construct(0, !$this->zip64);
$gzLength = Count64::construct(0, !$this->zip64);
$hashCtx = hash_init('crc32b');
if (COMPR::DEFLATE === $compress) {
$compStream = DeflateStream::create($level);
}
while (!feof($stream)) {
$data = fread($stream, self::STREAM_CHUNK_SIZE);
$dataLength->add(strlen($data));
hash_update($hashCtx, $data);
if (COMPR::DEFLATE === $compress) {
$data = $compStream->update($data);
}
$gzLength->add(strlen($data));
$this->write($data);
$this->flush();
}
if (COMPR::DEFLATE === $compress) {
$data = $compStream->finish();
$gzLength->add(strlen($data));
$this->write($data);
$this->flush();
}
$crc = unpack('N', hash_final($hashCtx, true));
return array($dataLength, $gzLength, $crc[1]);
}
private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) {
return ''
. pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001)
. pack16le(28) // size of this "extra" block 2 bytes
. pack64le($dataLength) // original uncompressed file size 8 bytes
. pack64le($gzLength) // size of compressed data 8 bytes
. pack64le($this->offset) // offset of local header record 8 bytes
. pack32le(0); // number of the disk on which this file starts 4 bytes
}
private function buildLocalFileHeader($filePath, $timestamp, $gpFlags,
$gzMethod, $dataLength, $gzLength, $isDir = False, $dataCRC32 = 0) {
$versionToExtract = $this->getVersionToExtract($isDir);
$dosTime = self::getDosTime($timestamp);
if ($this->zip64) {
$zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength);
$dataLength = -1;
$gzLength = -1;
} else {
$zip64Ext = '';
}
return ''
. pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50)
. pack16le($versionToExtract) // version needed to extract 2 bytes
. pack16le($gpFlags) // general purpose bit flag 2 bytes
. pack16le($gzMethod) // compression method 2 bytes
. pack32le($dosTime) // last mod file time 2 bytes
// last mod file date 2 bytes
. pack32le($dataCRC32) // crc-32 4 bytes
. pack32le($gzLength) // compressed size 4 bytes
. pack32le($dataLength) // uncompressed size 4 bytes
. pack16le(strlen($filePath)) // file name length 2 bytes
. pack16le(strlen($zip64Ext)) // extra field length 2 bytes
. $filePath // file name (variable size)
. $zip64Ext; // extra field (variable size)
}
private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) {
if ($this->zip64) {
$length = 20;
$packedGzLength = pack64le($gzLength);
$packedDataLength = pack64le($dataLength);
} else {
$length = 12;
$packedGzLength = pack32le($gzLength->getLoBytes());
$packedDataLength = pack32le($dataLength->getLoBytes());
}
$this->write(''
. pack32le($dataCRC32) // crc-32 4 bytes
. $packedGzLength // compressed size 4/8 bytes (depending on zip64 enabled)
. $packedDataLength // uncompressed size 4/8 bytes (depending on zip64 enabled)
.'');
return $length;
}
private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) {
$versionToExtract = $this->getVersionToExtract(False);
$cdRecCount = sizeof($this->cdRec);
return ''
. pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50)
. pack64le(44) // size of zip64 end of central directory
// record 8 bytes
. pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes
. pack16le($versionToExtract) // version needed to extract 2 bytes
. pack32le(0) // number of this disk 4 bytes
. pack32le(0) // number of the disk with the start of the
// central directory 4 bytes
. pack64le($cdRecCount) // total number of entries in the central
// directory on this disk 8 bytes
. pack64le($cdRecCount) // total number of entries in the
// central directory 8 bytes
. pack64le($cdRecLength) // size of the central directory 8 bytes
. pack64le($this->offset) // offset of start of central directory
// with respect to the starting disk number 8 bytes
. ''; // zip64 extensible data sector (variable size)
}
private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) {
$zip64RecStart = Count64::construct($this->offset, !$this->zip64)->add($cdRecLength);
return ''
. pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50)
. pack32le(0) // number of the disk with the start of the
// zip64 end of central directory 4 bytes
. pack64le($zip64RecStart) // relative offset of the zip64 end of
// central directory record 8 bytes
. pack32le(1); // total number of disks 4 bytes
}
private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags,
$gzMethod, $dataLength, $gzLength, $dataCRC32, $extFileAttr, $isDir) {
$versionToExtract = $this->getVersionToExtract($isDir);
$dosTime = self::getDosTime($timestamp);
if ($this->zip64) {
$zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength);
$dataLength = -1;
$gzLength = -1;
$diskNo = -1;
$offset = -1;
} else {
$zip64Ext = '';
$dataLength = $dataLength->getLoBytes();
$gzLength = $gzLength->getLoBytes();
$diskNo = 0;
$offset = $this->offset->getLoBytes();
}
return ''
. pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50)
. pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes
. pack16le($versionToExtract) // version needed to extract 2 bytes
. pack16le($gpFlags) //general purpose bit flag 2 bytes
. pack16le($gzMethod) //compression method 2 bytes
. pack32le($dosTime) //last mod file time 2 bytes
//last mod file date 2 bytes
. pack32le($dataCRC32) //crc-32 4 bytes
. pack32le($gzLength) //compressed size 4 bytes
. pack32le($dataLength) //uncompressed size 4 bytes
. pack16le(strlen($filePath)) //file name length 2 bytes
. pack16le(strlen($zip64Ext)) //extra field length 2 bytes
. pack16le(0) //file comment length 2 bytes
. pack16le($diskNo) //disk number start 2 bytes
. pack16le(0) //internal file attributes 2 bytes
. pack32le($extFileAttr) //external file attributes 4 bytes
. pack32le($offset) //relative offset of local header 4 bytes
. $filePath //file name (variable size)
. $zip64Ext //extra field (variable size)
//TODO: implement?
. ''; //file comment (variable size)
}
private function buildEndOfCentralDirectoryRecord($cdRecLength) {
if ($this->zip64) {
$diskNumber = -1;
$cdRecCount = -1;
$cdRecLength = -1;
$offset = -1;
} else {
$diskNumber = 0;
$cdRecCount = sizeof($this->cdRec);
$offset = $this->offset->getLoBytes();
}
//throw new \Exception(sprintf("zip64 %d diskno %d", $this->zip64, $diskNumber));
return ''
. pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50)
. pack16le($diskNumber) // number of this disk 2 bytes
. pack16le($diskNumber) // number of the disk with the
// start of the central directory 2 bytes
. pack16le($cdRecCount) // total number of entries in the
// central directory on this disk 2 bytes
. pack16le($cdRecCount) // total number of entries in the
// central directory 2 bytes
. pack32le($cdRecLength) // size of the central directory 4 bytes
. pack32le($offset) // offset of start of central
// directory with respect to the
// starting disk number 4 bytes
. pack16le(0) // .ZIP file comment length 2 bytes
//TODO: implement?
. ''; // .ZIP file comment (variable size)
}
// Utility methods ////////////////////////////////////////////////////////
private static function normalizeFilePath($filePath) {
return trim(str_replace('\\', '/', $filePath), '/');
}
/**
* Calculate the 2 byte dostime used in the zip entries.
*
* @param int $timestamp
* @return 2-byte encoded DOS Date
*/
public static function getDosTime($timestamp = 0) {
$timestamp = (int) $timestamp;
$oldTZ = @date_default_timezone_get();
date_default_timezone_set('UTC');
$date = ($timestamp == 0 ? getdate() : getdate($timestamp));
date_default_timezone_set($oldTZ);
if ($date['year'] >= 1980) {
return (($date['mday'] + ($date['mon'] << 5) + (($date['year'] - 1980) << 9)) << 16)
| (($date['seconds'] >> 1) + ($date['minutes'] << 5) + ($date['hours'] << 11));
}
return 0x0000;
}
}
abstract class ExtFileAttr {
/*
ZIP external file attributes layout
TTTTsstrwxrwxrwx0000000000ADVSHR
^^^^____________________________ UNIX file type
^^^_________________________ UNIX setuid, setgid, sticky
^^^^^^^^^________________ UNIX permissions
^^^^^^^^________ "lower-middle byte" (TODO: what is this?)
^^^^^^^^ DOS attributes (reserved, reserved, archived, directory, volume, system, hidden, read-only
*/
public static function getExtFileAttr($attr) {
return $attr;
}
}
class UNIX extends ExtFileAttr {
// Octal
const S_IFIFO = 0010000; /* named pipe (fifo) */
const S_IFCHR = 0020000; /* character special */
const S_IFDIR = 0040000; /* directory */
const S_IFBLK = 0060000; /* block special */
const S_IFREG = 0100000; /* regular */
const S_IFLNK = 0120000; /* symbolic link */
const S_IFSOCK = 0140000; /* socket */
const S_ISUID = 0004000; /* set user id on execution */
const S_ISGID = 0002000; /* set group id on execution */
const S_ISTXT = 0001000; /* sticky bit */
const S_IRWXU = 0000700; /* RWX mask for owner */
const S_IRUSR = 0000400; /* R for owner */
const S_IWUSR = 0000200; /* W for owner */
const S_IXUSR = 0000100; /* X for owner */
const S_IRWXG = 0000070; /* RWX mask for group */
const S_IRGRP = 0000040; /* R for group */
const S_IWGRP = 0000020; /* W for group */
const S_IXGRP = 0000010; /* X for group */
const S_IRWXO = 0000007; /* RWX mask for other */
const S_IROTH = 0000004; /* R for other */
const S_IWOTH = 0000002; /* W for other */
const S_IXOTH = 0000001; /* X for other */
const S_ISVTX = 0001000; /* save swapped text even after use */
public static function getExtFileAttr($attr) {
return parent::getExtFileAttr($attr) << 16;
}
}
abstract class DeflateStream {
static public function create($level) {
if (COMPR::NONE === $level) {
return new DeflateStoreStream($level);
} else {
return new DeflatePeclStream($level);
}
}
protected function __construct($level) {}
abstract public function update($data);
abstract public function finish();
}
class DeflatePeclStream extends DeflateStream {
private $peclDeflateStream;
const PECL1_DEFLATE_STREAM_CLASS = '\HttpDeflateStream';
const PECL2_DEFLATE_STREAM_CLASS = '\http\encoding\Stream\Deflate';
protected function __construct($level) {
$class = self::PECL1_DEFLATE_STREAM_CLASS;
if (!class_exists($class)) {
$class = self::PECL2_DEFLATE_STREAM_CLASS;
}
if (!class_exists($class)) {
new \Exception('unable to instantiate PECL deflate stream (requires pecl_http >= 0.10)');
}
$deflateFlags = constant($class . '::TYPE_RAW');
switch ($level) {
case COMPR::NORMAL:
$deflateFlags |= constant($class . '::LEVEL_DEF');
break;
case COMPR::MAXIMUM:
$deflateFlags |= constant($class . '::LEVEL_MAX');
break;
case COMPR::SUPERFAST:
$deflateFlags |= constant($class . '::LEVEL_MIN');
break;
}
$this->peclDeflateStream = new $class($deflateFlags);
}
public function update($data) {
return $this->peclDeflateStream->update($data);
}
public function finish() {
return $this->peclDeflateStream->finish();
}
}
class DeflateStoreStream extends DeflateStream {
const BLOCK_HEADER_NORMAL = 0x00;
const BLOCK_HEADER_FINAL = 0x01;
const BLOCK_HEADER_ERROR = 0x03;
const MAX_UNCOMPR_BLOCK_SIZE = 0xffff;
public function update($data) {
$result = '';
for ($pos = 0, $len = strlen($data); $pos < $len; $pos += self::MAX_UNCOMPR_BLOCK_SIZE) {
$result .= $this->write_block(self::BLOCK_HEADER_NORMAL, substr($data, $pos, self::MAX_UNCOMPR_BLOCK_SIZE));
}
return $result;
}
public function finish() {
return $this->write_block(self::BLOCK_HEADER_FINAL, '');
}
private function write_block($header, $data) {
return ''
. pack8($header) // block header 3 bits, null padding = 1 byte
. pack16le(strlen($data)) // block data length 2 bytes
. pack16le(0xffff ^ strlen($data)) // complement of block data size 2 bytes
. $data // data
. '';
}
}
class DOS extends ExtFileAttr {
const READ_ONLY = 0x1;
const HIDDEN = 0x2;
const SYSTEM = 0x4;
const VOLUME = 0x8;
const DIR = 0x10;
const ARCHIVE = 0x20;
const RESERVED1 = 0x40;
const RESERVED2 = 0x80;
}
class GPFLAGS {
const NONE = 0x0000; // no flags set
const COMP1 = 0x0002; // compression flag 1 (compression settings, see APPNOTE for details)
const COMP2 = 0x0004; // compression flag 2 (compression settings, see APPNOTE for details)
const ADD = 0x0008; // ADD flag (sizes and crc32 are append in data descriptor)
const EFS = 0x0800; // EFS flag (UTF-8 encoded filename and/or comment)
// compression settings for deflate/deflate64
const DEFL_NORM = 0x0000; // normal compression (COMP1 and COMP2 not set)
const DEFL_MAX = COMP1; // maximum compression
const DEFL_FAST = COMP2; // fast compression
const DEFL_SFAST = 0x0006; // superfast compression (COMP1 and COMP2 set)
}
PHPZipStreamer-0.7/src/lib/ 0000775 0000000 0000000 00000000000 12567341621 0015541 5 ustar 00root root 0000000 0000000 PHPZipStreamer-0.7/src/lib/Count64.php 0000664 0000000 0000000 00000020562 12567341621 0017521 0 ustar 00root root 0000000 0000000 .
*
* @author Nicolai Ehemann
* @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors
* @license GNU GPL
*/
namespace ZipStreamer;
const INT64_HIGH_MAP = 0xffffffff00000000;
const INT64_LOW_MAP = 0x00000000ffffffff;
const INT_MAX_32 = 0xffffffff;
/**
* Unsigned right shift
*
* @param int $bits integer to be shifted
* @param int $shift number of bits to be shifted
* @return int shifted integer
*/
function urShift($bits, $shift) {
if ($shift == 0) {
return $bits;
}
return ($bits >> $shift) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($shift - 1));
}
/**
* Convert binary data string to readable hex string
*
* @param string $data binary string
* @return string readable hex string
*/
function byte2hex($data) {
return unpack("h*", $data);
}
/**
* Pack 1 byte data into binary string
*
* @param mixed $data data
* @return string 1 byte binary string
*/
function pack8($data) {
return pack('C', $data);
}
/**
* Pack 2 byte data into binary string, little endian format
*
* @param mixed $data data
* @return string 2 byte binary string
*/
function pack16le($data) {
return pack('v', $data);
}
/**
* Unpack 2 byte binary string, little endian format to 2 byte data
*
* @param string $data binary string
* @return integer 2 byte data
*/
function unpack16le($data) {
$result = unpack('v', $data);
return $result[1];
}
/**
* Pack 4 byte data into binary string, little endian format
*
* @param mixed $data data
* @return 4 byte binary string
*/
function pack32le($data) {
return pack('V', $data);
}
/**
* Unpack 4 byte binary string, little endian format to 4 byte data
*
* @param string $data binary string
* @return integer 4 byte data
*/
function unpack32le($data) {
$result = unpack('V', $data);
return $result[1];
}
/**
* Pack 8 byte data into binary string, little endian format
*
* @param mixed $data data
* @return string 8 byte binary string
*/
function pack64le($data) {
if (is_object($data)) {
if ("Count64_32" == get_class($data)) {
$value = $data->_getValue();
$hiBytess = $value[0];
$loBytess = $value[1];
} else {
$hiBytess = ($data->_getValue() & INT64_HIGH_MAP) >> 32;
$loBytess = $data->_getValue() & INT64_LOW_MAP;
}
} else if (4 == PHP_INT_SIZE) {
$hiBytess = 0;
$loBytess = $data;
} else {
$hiBytess = ($data & INT64_HIGH_MAP) >> 32;
$loBytess = $data & INT64_LOW_MAP;
}
return pack('VV', $loBytess, $hiBytess);
}
/**
* Unpack 8 byte binary string, little endian format to 8 byte data
*
* @param string $data binary string
* @return Count64Base data
*/
function unpack64le($data) {
$bytes = unpack('V2', $data);
return Count64::construct(array(
$bytes[1],
$bytes[2]
));
}
abstract class Count64Base {
protected $limit32Bit = False;
function __construct($value = 0, $limit32Bit = False) {
$this->limit32Bit = $limit32Bit;
$this->set($value);
}
abstract public function set($value);
abstract public function add($value);
abstract public function getHiBytes();
abstract public function getLoBytes();
abstract public function _getValue();
const EXCEPTION_SET_INVALID_ARGUMENT = "Count64 object can only be set() to integer or Count64 values";
const EXCEPTION_ADD_INVALID_ARGUMENT = "Count64 object can only be add()ed integer or Count64 values";
const EXCEPTION_32BIT_OVERFLOW = "Count64 object limited to 32 bit (overflow)";
}
class Count64_32 extends Count64Base {
private $loBytes;
private $hiBytes;
public function getHiBytes() {
return $this->hiBytes;
}
public function getLoBytes() {
return $this->loBytes;
}
public function _getValue() {
return array($this->hiBytes, $this->loBytes);
}
public function set($value) {
if (is_int($value)) {
$this->loBytes = $value;
$this->hiBytes = 0;
} else if (is_array($value) && 2 == sizeof($value)) {
$this->loBytes = $value[0];
if ($this->limit32Bit && 0 !== $value[1]) {
throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->hiBytes = $value[1];
} else if (is_object($value) && __CLASS__ == get_class($value)) {
$value = $value->_getValue();
if ($this->limit32Bit && 0 !== $value[0]) {
throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->hiBytes = $value[0];
$this->loBytes = $value[1];
} else {
throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT);
}
return $this;
}
public function add($value) {
if (is_int($value)) {
$sum = (int) ($this->loBytes + $value);
// overflow!
if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1)
|| ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) {
if ($this->limit32Bit) {
throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->hiBytes = (int) ($this->hiBytes + 1);
}
$this->loBytes = $sum;
} else if (is_object($value) && __CLASS__ == get_class($value)) {
$value = $value->_getValue();
$sum = (int) ($this->loBytes + $value[1]);
if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1)
|| ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) {
if ($this->limit32Bit) {
throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->hiBytes = (int) ($this->hiBytes + 1);
}
$this->loBytes = $sum;
if ($this->limit32Bit && 0 !== $value[0]) {
throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->hiBytes = (int) ($this->hiBytes + $value[0]);
} else {
throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT);
}
return $this;
}
}
class Count64_64 extends Count64Base {
private $value;
public function getHiBytes() {
return urShift($this->value, 32);
}
public function getLoBytes() {
return $this->value & INT64_LOW_MAP;
}
public function _getValue() {
return $this->value;
}
public function set($value) {
if (is_int($value)) {
if ($this->limit32Bit && INT_MAX_32 < $value) {
throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->value = $value;
} else if (is_array($value) && 2 == sizeof($value)) {
if ($this->limit32Bit && 0 !== $value[1]) {
throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->value = $value[1];
$this->value = $this->value << 32;
$this->value = $this->value + $value[0];
} else if (is_object($value) && __CLASS__ == get_class($value)) {
$value = $value->_getValue();
if ($this->limit32Bit && INT_MAX_32 < $value) {
throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->value = $value;
} else {
throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT);
}
return $this;
}
public function add($value) {
if (is_int($value)) {
$sum = (int) ($this->value + $value);
} else if (is_object($value) && __CLASS__ == get_class($value)) {
$sum = (int) ($this->value + $value->_getValue());
} else {
throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT);
}
if ($this->limit32Bit && INT_MAX_32 < $sum) {
throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW);
}
$this->value = $sum;
return $this;
}
}
abstract class Count64 {
public static function construct($value = 0, $limit32Bit = False) {
if (4 == PHP_INT_SIZE) {
return new Count64_32($value, $limit32Bit);
} else {
return new Count64_64($value, $limit32Bit);
}
}
}
?>
PHPZipStreamer-0.7/test/ 0000775 0000000 0000000 00000000000 12567341621 0015163 5 ustar 00root root 0000000 0000000 PHPZipStreamer-0.7/test/ZipComponents.php 0000664 0000000 0000000 00000051067 12567341621 0020515 0 ustar 00root root 0000000 0000000
*
* This file is licensed under the GNU GPL version 3 or later.
* See COPYING for details.
*/
namespace ZipStreamer;
require_once "src/ZipStreamer.php";
/**
* @codeCoverageIgnore
*/
class ParseException extends \Exception {
}
function readstr($str, &$pos, $len) {
$str = substr($str, $pos, $len);
$pos += $len;
return $str;
}
function hexIfFFFF($value) {
return $value == 0xffff ? '0x' . dechex($value) : $value;
}
function hexIfFFFFFFFF($value) {
return $value == 0xffffffff ? '0x' . dechex($value) : $value;
}
/**
* @codeCoverageIgnore
*/
abstract class zipRecord {
protected static $magicBytes = array();
protected static $unitTest = null;
protected static $shortName = "";
protected static $magicLength = 4;
public $begin;
public $end;
public function getLength() {
return $this->end - $this->begin + 1;
}
public static function setUnitTest($unitTest) {
self::$unitTest = $unitTest;
}
public static function getMagicBytes() {
if (!array_key_exists(static::$MAGIC, self::$magicBytes)) {
if (2 == static::$magicLength) {
self::$magicBytes[static::$MAGIC] = pack16le(static::$MAGIC);
} else {
self::$magicBytes[static::$MAGIC] = pack32le(static::$MAGIC);
}
}
return self::$magicBytes[static::$MAGIC];
}
protected static function __constructFromString($str, $pos, $size = -1) {
$eocdrec = new static();
try {
$eocdrec->readFromString($str, $pos, $size);
} catch (Exception $e) {
$this->fail("error parsing end of central directory record");
}
return $eocdrec;
}
public static function constructFromString($str, $offset = 0, $size = -1) {
return static::__constructFromString($str, $offset, $size);
}
protected abstract function readFromString($str, $pos, $size = -1);
public function assertValues($values) {
if (self::$unitTest) {
foreach ($values as $key => $value) {
self::$unitTest->assertEquals($value, $this->{$key}, static::$shortName . " " . $key);
}
}
}
}
/**
* @codeCoverageIgnore
*/
class EndOfCentralDirectoryRecord extends zipRecord {
protected static $MAGIC = 0x06054b50; // end of central directory record
protected static $shortName = "EOCDR";
public $numberDisk;
public $numberDiskStartCD;
public $numberEntriesDisk;
public $numberEntriesCD;
public $size;
public $offsetStart;
public $lengthComment;
public $comment;
public function __toString() {
return sprintf(
"Number of this disk: %d\n" .
"Number of disk with start of eocd record: %d\n" .
"Number of cd record entries on this disk: %d\n" .
"Total number of cd record entries: %d\n" .
"Size of central directory: %d\n" .
"Offset of central directory: %d\n" .
"Zip file comment length: %d\n" .
"Zip file comment following (if any)\n%s\n",
$this->numberDisk,
$this->numberDiskStartCD,
$this->numberEntriesDisk,
$this->numberEntriesCD,
$this->size,
$this->offsetStart,
$this->lengthComment,
$this->comment);
}
public static function constructFromString($str, $offset = 0, $size = -1) {
$eocdrecPos = strrpos($str, static::getMagicBytes());
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $eocdrecPos, "end of central directory record missing");
self::$unitTest->assertGreaterThanOrEqual(22, strlen($str) - $eocdrecPos, "end of central directory record incomplete (smaller than minimum length)");
}
return static::__constructFromString($str, $eocdrecPos);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 4);
if (self::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->numberDisk = (int) unpack16le(readstr($str, $pos, 2));
$this->numberDiskStartCD = (int) unpack16le(readstr($str, $pos, 2));
$this->numberEntriesDisk = (int) unpack16le(readstr($str, $pos, 2));
$this->numberEntriesCD = (int) unpack16le(readstr($str, $pos, 2));
$this->size = (int) unpack32le(readstr($str, $pos, 4));
$this->offsetStart = (int) unpack32le(readstr($str, $pos, 4));
$this->lengthComment = unpack16le(readstr($str, $pos, 2));
if (0 < $this->lengthComment) {
$this->comment = (string) readstr($str, $pos, $this->lengthComment);
} else {
$this->comment = '';
}
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class Zip64EndOfCentralDirectoryLocator extends zipRecord {
protected static $MAGIC = 0x07064b50; // zip64 end of central directory locator
protected static $shortName = "Z64EOCDL";
public $numberDiskStartZ64EOCDL;
public $offsetStart;
public $numberDisks;
public function __toString() {
return sprintf(
"Number of disk with start of zip64 eocd locator: %d\n" .
"Offset of zip64 eocd record: %d\n" .
"Number of disks: %d\n" .
$this->numberDiskStartZ64EOCDL,
$this->offsetStart,
$this->numberDisks);
}
public static function constructFromString($str, $offset = 0, $size = -1) {
$z64eocdlPos = strrpos($str, static::getMagicBytes(), -$offset);
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $z64eocdlPos, "zip64 end of central directory locator missing");
}
$z64eocdl = static::__constructFromString($str, $z64eocdlPos);
if (self::$unitTest) {
self::$unitTest->assertGreaterThanOrEqual(20, $z64eocdl->getLength(), "zip64 end of central directory locator incomplete (to short)");
self::$unitTest->assertLessThanOrEqual(20, $z64eocdl->getLength(), "garbage after end of zip64 end of central directory locator");
}
return $z64eocdl;
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 4);
if (static::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->numberDiskStartZ64EOCDL = (int) unpack32le(readstr($str, $pos, 4));
$this->offsetStart = unpack64le(readstr($str, $pos, 8));
$this->numberDisks = (int) unpack32le(readstr($str, $pos, 4));
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class Zip64EndOfCentralDirectoryRecord extends zipRecord {
protected static $MAGIC = 0x06064b50; // zip64 end of central directory locator
protected static $shortName = "Z64EOCDR";
public $size;
public $madeByVersion;
public $versionToExtract;
public $numberDisk;
public $numberDiskStartCDR;
public $numberEntriesDisk;
public $numberEntriesCD;
public $sizeCD;
public $offsetStart;
public function __toString() {
return sprintf(
"Size of Zip64 EOCDR: %d\n" .
"Made by version: %s\n" .
"Version needed to extract: %s\n" .
"Number of this disk: %d\n" .
"Number of disk with start of cd: %d\n" .
"Number of cd record entries on this disk: %d\n" .
"Total number of cd record entries: %d\n" .
"Size of central directory: %d\n" .
"Offset of central directory: %d\n",
$this->size,
$this->madeByVersion,
$this->versionToExtract,
$this->numberDisk,
$this->numberDiskStartCDR,
$this->numberEntriesDisk,
$this->numberEntriesCD,
$this->sizeCD,
$this->offsetStart);
}
public static function constructFromString($str, $offset = 0, $size = -1) {
$z64eocdlPos = strrpos($str, static::getMagicBytes(), -$offset);
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $z64eocdlPos, "zip64 end of central directory record missing");
}
$z64eocdl = static::__constructFromString($str, $z64eocdlPos);
if (self::$unitTest) {
self::$unitTest->assertGreaterThanOrEqual(56, $z64eocdl->getLength(), "zip64 end of central directory record incomplete (to short)");
self::$unitTest->assertLessThanOrEqual(56, $z64eocdl->getLength(), "garbage after end of zip64 end of central directory record");
}
return $z64eocdl;
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 4);
if (static::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->size = unpack64le(readstr($str, $pos, 8));
$this->madeByVersion = readstr($str, $pos, 2);
$this->versionToExtract = readstr($str, $pos, 2);
$this->numberDisk = (int) unpack32le(readstr($str, $pos, 4));
$this->numberDiskStartCDR = (int) unpack32le(readstr($str, $pos, 4));
$this->numberEntriesDisk = unpack64le(readstr($str, $pos, 8));
$this->numberEntriesCD = unpack64le(readstr($str, $pos, 8));
$this->sizeCD = unpack64le(readstr($str, $pos, 8));
$this->offsetStart = unpack64le(readstr($str, $pos, 8));
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class CentralDirectoryHeader extends zipRecord {
protected static $MAGIC = 0x02014b50; // central file header signature
protected static $shortName = "CDH";
public $madeByVersion;
public $versionToExtract;
public $gpFlags;
public $gzMethod;
public $dosTime;
public $dataCRC32;
public $sizeCompressed;
public $size;
public $lengthFilename;
public $lengthExtraField;
public $lengthComment;
public $diskNumberStart;
public $fileAttrInternal;
public $fileAttrExternal;
public $offsetStart;
public $filename;
public $z64Ext;
public $comment;
public function __toString() {
return sprintf(
"Made by version: 0x%s\n" .
"Version needed to extract: 0x%s\n" .
"General purpose flags: 0x%s\n" .
"Compression method: 0x%s\n" .
"Dos time: %s\n" .
"Data CRC32: %s\n" .
"Compressed file size: %s\n" .
"Uncompressed file size: %s\n" .
"Filename length: %d\n" .
"Extra field length: %d\n" .
"Comment length: %d\n" .
"Number of disk with file start: %s\n" .
"Internal file attributes. %s\n" .
"External file attributes: %s\n" .
"Offset of start of local file header: %s\n" .
"Filename: %s\n" .
"Comment: %s\n",
bin2hex($this->madeByVersion),
bin2hex($this->versionToExtract),
bin2hex($this->gpFlags),
bin2hex($this->gzMethod),
$this->dosTime,
$this->dataCRC32,
hexIfFFFFFFFF($this->sizeCompressed),
hexIfFFFFFFFF($this->size),
$this->lengthFilename,
$this->lengthExtraField,
$this->lengthComment,
hexIfFFFF($this->diskNumberStart),
$this->fileAttrInternal,
$this->fileAttrExternal,
hexIfFFFFFFFF($this->offsetStart),
$this->filename,
$this->comment);
}
public static function constructFromString($str, $offset = 0, $size = -1) {
$cdheadPos = strpos($str, static::getMagicBytes(), $offset);
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $cdheadPos, "central directory header missing");
self::$unitTest->assertEquals($offset, $cdheadPos, "garbage before central directory header");
}
return static::__constructFromString($str, $cdheadPos);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 4);
if (static::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->madeByVersion = readstr($str, $pos, 2);
$this->versionToExtract = readstr($str, $pos, 2);
$this->gpFlags = readstr($str, $pos, 2);
$this->gzMethod = readstr($str, $pos, 2);
$this->dosTime = readstr($str, $pos, 4);
$this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4));
$this->sizeCompressed = (int) unpack32le(readstr($str, $pos, 4));
$this->size = (int) unpack32le(readstr($str, $pos, 4));
$this->lengthFilename = (int) unpack16le(readstr($str, $pos, 2));
$this->lengthExtraField = (int) unpack16le(readstr($str, $pos, 2));
$this->lengthComment = (int) unpack16le(readstr($str, $pos, 2));
$this->diskNumberStart = (int) unpack16le(readstr($str, $pos, 2));
$this->fileAttrInternal = readstr($str, $pos, 2);
$this->fileAttrExternal = readstr($str, $pos, 4);
$this->offsetStart = (int) unpack32le(readstr($str, $pos, 4));
if (0 < $this->lengthFilename) {
$this->filename = (string) readstr($str, $pos, $this->lengthFilename);
} else {
$this->filename = '';
}
if (0 < $this->lengthExtraField) {
$this->z64Ext = Zip64ExtendedInformationField::constructFromString($str, $pos);
if (self::$unitTest) {
self::$unitTest->assertEquals($this->lengthExtraField, $this->z64Ext->getLength(), "Z64EIF is only field and fits into propagated length");
}
$pos = $this->z64Ext->end + 1;
}
if (0 < $this->lengthComment) {
$this->comment = (string) readstr($str, $pos, $this->lengthComment);
} else {
$this->comment = '';
}
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class Zip64ExtendedInformationField extends zipRecord {
protected static $MAGIC = 0x0001; // central file header signature
protected static $magicLength = 2;
protected static $shortName = "Z64EIF";
public $sizeField;
public $size;
public $sizeCompressed;
public $offsetStart;
public $diskNumberStart;
public function __toString() {
return sprintf(
"Size of this 'extra' block: %d\n" .
"Uncompressed file size: %d\n" .
"Compressed file size: %d\n" .
"Offset of begin of local file header: %d\n" .
"Number of disk with file start: %d\n",
$this->sizeField,
$this->size,
$this->sizeCompressed,
$this->offsetStart,
$this->diskNumberStart);
}
public static function constructFromString($str, $offsetStart = 0, $size = -1) {
$pos = strpos($str, static::getMagicBytes(), $offsetStart);
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $pos, "extra field magic bytes missing");
self::$unitTest->assertEquals($offsetStart, $pos, "garbage before extra field");
}
return static::__constructFromString($str, $pos);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 2);
if (static::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->sizeField = (int) unpack16le(readstr($str, $pos, 2));
$this->size = unpack64le(readstr($str, $pos, 8));
$this->sizeCompressed = unpack64le(readstr($str, $pos, 8));
$this->offsetStart = unpack64le(readstr($str, $pos, 8));
$this->diskNumberStart = (int) unpack16le(readstr($str, $pos, 4));
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class FileEntry extends zipRecord {
protected static $shortName = "FILE";
public $lfh;
public $dataCompressed;
public $data;
public $dd;
public function __toString() {
return sprintf("File content:\n" . "%s", $this->data);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$this->lfh = LocalFileHeader::constructFromString($str, $pos);
$pos = $this->lfh->end + 1;
if (self::$unitTest) {
$this->dataCompressed = readStr($str, $pos, $size);
if (0 < strlen($this->dataCompressed) && COMPR::DEFLATE & $this->lfh->gzMethod) {
$this->data = gzinflate($this->dataCompressed);
} else {
$this->data = $this->dataCompressed;
}
}
if (GPFLAGS::ADD & $this->lfh->gpFlags) {
if (is_null($this->lfh->z64Ext)) {
$ddLength = 12;
} else {
$ddLength = 20;
}
$this->dd = DataDescriptor::constructFromString($str, $pos, $ddLength);
$pos = $this->dd->end + 1;
}
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class LocalFileHeader extends zipRecord {
protected static $MAGIC = 0x04034b50; // central file header signature
protected static $shortName = "LFH";
public $versionToExtract;
public $gpFlags;
public $gzMethod;
public $dosTime;
public $dataCRC32;
public $sizeCompressed;
public $size;
public $lengthFilename;
public $lengthExtraField;
public $filename;
public $z64Ext;
public function __toString() {
return sprintf(
"Version needed to extract: %s\n" .
"General purpose flags: %s\n" .
"Compression method: %s\n" .
"Dos time: %s\n" .
"Data CRC32: %s\n" .
"Compressed file size: %d\n" .
"Uncompressed file size: %d\n" .
"Filename length: %d\n" .
"Extra field length: %d\n" .
"Filename: %s\n" ,
bin2hex($this->versionToExtract),
bin2hex($this->gpFlags),
bin2hex($this->gzMethod),
$this->dosTime,
$this->dataCRC32,
hexIfFFFFFFFF($this->sizeCompressed),
hexIfFFFFFFFF($this->size),
$this->lengthFilename,
$this->lengthExtraField,
$this->filename);
}
public static function constructFromString($str, $offset = 0, $size = -1) {
$cdheadPos = strpos($str, static::getMagicBytes(), $offset);
if (self::$unitTest) {
self::$unitTest->assertFalse(False === $cdheadPos, "local file header missing");
self::$unitTest->assertEquals($offset, $cdheadPos, "garbage before local file header");
}
return static::__constructFromString($str, $cdheadPos, $size);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$magic = readstr($str, $pos, 4);
if (static::getMagicBytes() != $magic) {
throw new ParseException("invalid magic");
}
$this->versionToExtract = readstr($str, $pos, 2);
$this->gpFlags = (int) unpack16le(readstr($str, $pos, 2));
$this->gzMethod = (int) unpack16le(readstr($str, $pos, 2));
$this->dosTime = readstr($str, $pos, 4);
$this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4));
$this->sizeCompressed = (int) unpack32le(readstr($str, $pos, 4));
$this->size = (int) unpack32le(readstr($str, $pos, 4));
$this->lengthFilename = (int) unpack16le(readstr($str, $pos, 2));
$this->lengthExtraField = (int) unpack16le(readstr($str, $pos, 2));
if (0 < $this->lengthFilename) {
$this->filename = (string) readstr($str, $pos, $this->lengthFilename);
} else {
$this->filename = '';
}
if (0 < $this->lengthExtraField) {
$this->z64Ext = Zip64ExtendedInformationField::constructFromString($str, $pos);
if (self::$unitTest) {
self::$unitTest->assertEquals($this->lengthExtraField, $this->z64Ext->getLength(), "Z64EIF is only field and fits into propagated length");
}
$pos = $this->z64Ext->end + 1;
}
$this->end = $pos - 1;
}
}
/**
* @codeCoverageIgnore
*/
class DataDescriptor extends zipRecord {
protected static $shortName = "DD";
public $dataCRC32;
public $sizeCompressed;
public $size;
public function __toString() {
return sprintf(
"Data CRC32: %s\n" .
"Compressed file size: %d\n" .
"Uncompressed file size: %d\n" ,
$this->dataCRC32,
hexIfFFFFFFFF($this->sizeCompressed->getLoBytes()),
hexIfFFFFFFFF($this->size->getLoBytes()));
}
public static function constructFromString($str, $offset = 0, $size = -1) {
return static::__constructFromString($str, $offset, $size);
}
public function readFromString($str, $pos, $size = -1) {
$this->begin = $pos;
$this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4));
if (20 == $size) {
$this->sizeCompressed = unpack64le(readstr($str, $pos, 8));
$this->size = unpack64le(readstr($str, $pos, 8));
} else {
$this->sizeCompressed = Count64::construct((int) unpack32le(readstr($str, $pos, 4)));
$this->size = Count64::construct((int) unpack32le(readstr($str, $pos, 4)));
}
$this->end = $pos - 1;
}
}
?>
PHPZipStreamer-0.7/test/ZipStreamerTest.php 0000664 0000000 0000000 00000035424 12567341621 0021011 0 ustar 00root root 0000000 0000000
*
* This file is licensed under the GNU GPL version 3 or later.
* See COPYING for details.
*/
namespace ZipStreamer;
require "src/ZipStreamer.php";
require "test/ZipComponents.php";
class File {
const FILE = 1;
const DIR = 2;
public $filename;
public $date;
public $type;
public $data;
public function __construct($filename, $type, $date, $data = "") {
$this->filename = $filename;
$this->type = $type;
$this->date = $date;
$this->data = $data;
}
public function getSize() {
return strlen($this->data);
}
}
class TestZipStreamer extends \PHPUnit_Framework_TestCase {
const ATTR_MADE_BY_VERSION = 0x032d; // made by version (upper byte: UNIX, lower byte v4.5)
const EXT_FILE_ATTR_DIR = 0x41ed0010;
const EXT_FILE_ATTR_FILE = 0x81a40000;
protected $outstream;
protected function setUp() {
parent::setUp();
$this->outstream = fopen('php://memory', 'rw');
zipRecord::setUnitTest($this);
}
protected function tearDown() {
fclose($this->outstream);
parent::tearDown();
}
protected function getOutput() {
rewind($this->outstream);
return stream_get_contents($this->outstream);
}
protected static function getVersionToExtract($zip64, $isDir) {
if ($zip64) {
$version = 0x2d; // 4.5 - File uses ZIP64 format extensions
} else if ($isDir) {
$version = 0x14; // 2.0 - File is a folder (directory)
} else {
$version = 0x0a; // 1.0 - Default value
}
return $version;
}
protected function assertOutputEqualsFile($filename) {
return $this->assertEquals(file_get_contents($filename), $this->getOutput());
}
protected function assertContainsOneMatch($pattern, $input) {
$results = preg_grep($pattern, $input);
return $this->assertEquals(1, sizeof($results));
}
protected function assertOutputZipfileOK($files, $options) {
if (0 < sizeof($files)) { // php5.3 does not combine empty arrays
$files = array_combine(array_map(function ($element) {
return $element->filename;
}, $files), $files);
}
$output = $this->getOutput();
$eocdrec = EndOfCentralDirectoryRecord::constructFromString($output);
$this->assertEquals(strlen($output) - 1, $eocdrec->end, "EOCDR last item in file");
if ($options['zip64']) {
$eocdrec->assertValues(array(
"numberDisk" => 0xffff,
"numberDiskStartCD" => 0xffff,
"numberEntriesDisk" => 0xffff,
"numberEntriesCD" => 0xffff,
"size" => 0xffffffff,
"offsetStart" => 0xffffffff,
"lengthComment" => 0,
"comment" => ''
));
$z64eocdloc = Zip64EndOfCentralDirectoryLocator::constructFromString($output, strlen($output) - ($eocdrec->begin + 1));
$this->assertEquals($z64eocdloc->end + 1, $eocdrec->begin, "Z64EOCDL directly before EOCDR");
$z64eocdloc->assertValues(array(
"numberDiskStartZ64EOCDL" => 0,
"numberDisks" => 1
));
$z64eocdrec = Zip64EndOfCentralDirectoryRecord::constructFromString($output, strlen($output) - ($z64eocdloc->begin + 1));
$this->assertEquals(Count64::construct($z64eocdrec->begin), $z64eocdloc->offsetStart, "Z64EOCDR begin");
$this->assertEquals($z64eocdrec->end + 1, $z64eocdloc->begin, "Z64EOCDR directly before Z64EOCDL");
$z64eocdrec->assertValues(array(
"size" => Count64::construct(44),
"madeByVersion" => pack16le(self::ATTR_MADE_BY_VERSION),
"versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], False)),
"numberDisk" => 0,
"numberDiskStartCDR" => 0,
"numberEntriesDisk" => Count64::construct(sizeof($files)),
"numberEntriesCD" => Count64::construct(sizeof($files))
));
$sizeCD = $z64eocdrec->sizeCD->getLoBytes();
$offsetCD = $z64eocdrec->offsetStart->getLoBytes();
$beginFollowingRecord = $z64eocdrec->begin;
} else {
$eocdrec->assertValues(array(
"numberDisk" => 0,
"numberDiskStartCD" => 0,
"numberEntriesDisk" => sizeof($files),
"numberEntriesCD" => sizeof($files),
"lengthComment" => 0,
"comment" => ''
));
$sizeCD = $eocdrec->size;
$offsetCD = $eocdrec->offsetStart;
$beginFollowingRecord = $eocdrec->begin;
}
$cdheaders = array();
$pos = $offsetCD;
$cdhead = null;
while ($pos < $beginFollowingRecord) {
$cdhead = CentralDirectoryHeader::constructFromString($output, $pos);
$filename = $cdhead->filename;
$pos = $cdhead->end + 1;
$cdheaders[$filename] = $cdhead;
$this->assertArrayHasKey($filename, $files, "CDH entry has valid name");
$cdhead->assertValues(array(
"madeByVersion" => pack16le(self::ATTR_MADE_BY_VERSION),
"versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)),
"gpFlags" => (File::FILE == $files[$filename]->type ? pack16le(GPFLAGS::ADD) : pack16le(GPFLAGS::NONE)),
"gzMethod" => (File::FILE == $files[$filename]->type ? pack16le($options['compress']) : pack16le(COMPR::STORE)),
"dosTime" => pack32le(ZipStreamer::getDosTime($files[$filename]->date)),
"lengthFilename" => strlen($filename),
"lengthComment" => 0,
"fileAttrInternal" => pack16le(0x0000),
"fileAttrExternal" => (File::FILE == $files[$filename]->type ? pack32le(self::EXT_FILE_ATTR_FILE) : pack32le(self::EXT_FILE_ATTR_DIR))
));
if ($options['zip64']) {
$cdhead->assertValues(array(
"sizeCompressed" => 0xffffffff,
"size" => 0xffffffff,
"lengthExtraField" => 32,
"diskNumberStart" => 0xffff,
"offsetStart" => 0xffffffff
));
$cdhead->z64Ext->assertValues(array(
"sizeField" => 28,
"size" => Count64::construct($files[$filename]->getSize()),
"diskNumberStart" => 0
));
} else {
$cdhead->assertValues(array(
"size" => $files[$filename]->getSize(),
"lengthExtraField" => 0,
"diskNumberStart" => 0
));
}
}
if (0 < sizeof($files)) {
$this->assertEquals($cdhead->end + 1, $beginFollowingRecord, "CDH directly before following record");
$this->assertEquals(sizeof($files), sizeof($cdheaders), "CDH has correct number of entries");
$this->assertEquals($sizeCD, $beginFollowingRecord - $offsetCD, "CDH has correct size");
} else {
$this->assertNull($cdhead);
}
$first = True;
foreach ($cdheaders as $filename => $cdhead) {
if ($options['zip64']) {
$sizeCompressed = $cdhead->z64Ext->sizeCompressed->getLoBytes();
$offsetStart = $cdhead->z64Ext->offsetStart->getLoBytes();
} else {
$sizeCompressed = $cdhead->sizeCompressed;
$offsetStart = $cdhead->offsetStart;
}
if ($first) {
$this->assertEquals(0, $offsetStart, "first file directly at beginning of zipfile");
} else {
$this->assertEquals($endLastFile + 1, $offsetStart, "file immediately after last file");
}
$file = FileEntry::constructFromString($output, $offsetStart, $sizeCompressed);
$this->assertEquals($files[$filename]->data, $file->data);
$this->assertEquals(crc32($files[$filename]->data), $cdhead->dataCRC32);
if (GPFLAGS::ADD & $file->lfh->gpFlags) {
$this->assertNotNull($file->dd, "data descriptor present (flag ADD set)");
}
if ($options['zip64']) {
$file->lfh->assertValues(array(
"sizeCompressed" => 0xffffffff,
"size" => 0xffffffff,
));
$file->lfh->z64Ext->assertValues(array(
"sizeField" => 28,
"size" => Count64::construct(0),
"sizeCompressed" => Count64::construct(0),
"diskNumberStart" => 0
));
} else {
$file->lfh->assertValues(array(
"sizeCompressed" => 0,
"size" => 0,
));
}
$file->lfh->assertValues(array(
"versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)),
"gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE),
"gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE),
"dosTime" => pack32le(ZipStreamer::getDosTime($files[$filename]->date)),
"dataCRC32" => 0x0000,
"lengthFilename" => strlen($filename),
"filename" => $filename
));
$endLastFile = $file->end;
$first = False;
}
if (0 < sizeof($files)) {
$this->assertEquals($offsetCD, $endLastFile + 1, "last file directly before CDH");
} else {
$this->assertEquals(0, $beginFollowingRecord, "empty zip file, CD records at beginning of file");
}
}
public function providerSendHeadersOK() {
// array(filename, mimetype), expectedMimetype, expextedFilename, $description
return array(
array(array(), 'application/zip', 'archive.zip', 'default headers'),
array(array('file.zip', 'application/octet-stream'), 'application/octet-stream', 'file.zip', 'specific headers')
);
}
/**
* @dataProvider providerSendHeadersOK
* @preserveGlobalState disabled
* @runInSeparateProcess
*/
public function testSendHeadersOK($arguments, $expectedMimetype, $expectedFilename, $description) {
$zip = new ZipStreamer(array(
'outstream' => $this->outstream
));
call_user_func_array(array($zip, "sendHeaders"), $arguments);
$headers = xdebug_get_headers();
$this->assertContains('Pragma: public', $headers);
$this->assertContains('Expires: 0', $headers);
$this->assertContains('Accept-Ranges: bytes', $headers);
$this->assertContains('Connection: Keep-Alive', $headers);
$this->assertContains('Content-Transfer-Encoding: binary', $headers);
$this->assertContains('Content-Type: ' . $expectedMimetype, $headers);
$this->assertContains('Content-Disposition: attachment; filename="' . $expectedFilename . '";', $headers);
$this->assertContainsOneMatch('/^Last-Modified: /', $headers);
}
public function providerZipfileOK() {
$zip64Options = array(array(True, 'True'), array(False, 'False'));
$defaultLevelOption = array(array(COMPR::NORMAL, 'COMPR::NORMAL'));
$compressOptions = array(array(COMPR::STORE, 'COMPR::STORE'), array(COMPR::DEFLATE, 'COMPR::DEFLATE'));
$levelOptions = array(array(COMPR::NONE, 'COMPR::NONE'), array(COMPR::SUPERFAST, 'COMPR::SUPERFAST'), array(COMPR::MAXIMUM, 'COMPR::MAXIMUM'));
$fileSets = array(
array(
array(),
"empty"
),
array(
array(
new File('test/', File::DIR, 1)
),
"one empty dir"
),
array(
array(
new File('test1.txt', File::FILE, 1, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed elit diam, posuere vel aliquet et, malesuada quis purus. Aliquam mattis aliquet massa, a semper sem porta in. Aliquam consectetur ligula a nulla vestibulum dictum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam luctus faucibus urna, accumsan cursus neque laoreet eu. Suspendisse potenti. Nulla ut feugiat neque. Maecenas molestie felis non purus tempor, in blandit ligula tincidunt. Ut in tortor sit amet nisi rutrum vestibulum vel quis tortor. Sed bibendum mauris sit amet gravida tristique. Ut hendrerit sapien vel tellus dapibus, eu pharetra nulla adipiscing. Donec in quam faucibus, cursus lacus sed, elementum ligula. Morbi volutpat vel lacus malesuada condimentum. Fusce consectetur nisl euismod justo volutpat sodales.')
),
"one file"
),
array(
array(
new File('test1.txt', File::FILE, 1, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed elit diam, posuere vel aliquet et, malesuada quis purus. Aliquam mattis aliquet massa, a semper sem porta in. Aliquam consectetur ligula a nulla vestibulum dictum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam luctus faucibus urna, accumsan cursus neque laoreet eu. Suspendisse potenti. Nulla ut feugiat neque. Maecenas molestie felis non purus tempor, in blandit ligula tincidunt. Ut in tortor sit amet nisi rutrum vestibulum vel quis tortor. Sed bibendum mauris sit amet gravida tristique. Ut hendrerit sapien vel tellus dapibus, eu pharetra nulla adipiscing. Donec in quam faucibus, cursus lacus sed, elementum ligula. Morbi volutpat vel lacus malesuada condimentum. Fusce consectetur nisl euismod justo volutpat sodales.'),
new File('test/', File::DIR, 1),
new File('test/test12.txt', File::FILE, 1, 'Duis malesuada lorem lorem, id sodales sapien sagittis ac. Donec in porttitor tellus, eu aliquam elit. Curabitur eu aliquam eros. Nulla accumsan augue quam, et consectetur quam eleifend eget. Donec cursus dolor lacus, eget pellentesque risus tincidunt at. Pellentesque rhoncus purus eget semper porta. Duis in magna tincidunt, fermentum orci non, consectetur nibh. Aliquam tortor eros, dignissim a posuere ac, rhoncus a justo. Sed sagittis velit ac massa pulvinar, ac pharetra ipsum fermentum. Etiam commodo lorem a scelerisque facilisis.')
),
"simple structure"
)
);
$data = array();
foreach ($zip64Options as $zip64) {
foreach ($compressOptions as $compress) {
$levels = $defaultLevelOption;
if (COMPR::DEFLATE == $compress[0]) {
$levels = array_merge($levels, $levelOptions);
}
foreach ($levels as $level) {
foreach ($fileSets as $fileSet) {
$options = array(
'zip64' => $zip64[0],
'compress' => $compress[0],
'level' => $level[0]
);
$description = $fileSet[1] . ' (options = array(zip64=' . $zip64[1] . ', compress=' . $compress[1] . ', level=' . $level[1] . '))';
array_push($data, array(
$options,
$fileSet[0],
$description
));
}
}
}
}
return $data;
}
/**
* @dataProvider providerZipfileOK
* preserveGlobalState disabled
* runInSeparateProcess
*/
public function testZipfile($options, $files, $description) {
$options = array_merge($options, array('outstream' => $this->outstream));
$zip = new ZipStreamer($options);
foreach ($files as $file) {
if (File::DIR == $file->type) {
$zip->addEmptyDir($file->filename, array('timestamp' => $file->date));
} else {
$stream = fopen('php://memory', 'r+');
fwrite($stream, $file->data);
rewind($stream);
$zip->addFileFromStream($stream, $file->filename, array('timestamp' => $file->date));
fclose($stream);
}
}
$zip->finalize();
$this->assertOutputZipfileOK($files, $options);
}
}
?>
PHPZipStreamer-0.7/test/lib/ 0000775 0000000 0000000 00000000000 12567341621 0015731 5 ustar 00root root 0000000 0000000 PHPZipStreamer-0.7/test/lib/Count64Test.php 0000664 0000000 0000000 00000014637 12567341621 0020557 0 ustar 00root root 0000000 0000000
*
* This file is licensed under the GNU GPL version 3 or later.
* See COPYING for details.
*/
class TestPack extends PHPUnit_Framework_TestCase
{
public function providerPack16leValues() {
# input value, description
return array(
array(0, "packing 0"),
array(1, "packing positive integer"),
array(-1, "packing negative integer"),
array(0x0f0f, "packing pattern (0x0f0f)"),
array(0xf0f0, "packing pattern (0xf0f0)"),
array(0x00ff, "packing pattern (0x00ff)"),
array(0xff00, "packing pattern (0xff00)"),
array(0xffff, "packing maximum 16 bit value (0xffff)")
);
}
/**
* @dataProvider providerPack16leValues
*/
public function testPack16le($value, $description) {
$this->assertEquals(ZipStreamer\pack16le($value), pack('v', $value), $description);
}
public function providerPack32leValues() {
# input value, description
return array(
array(0, "packing 0"),
array(1, "packing positive integer"),
array(-1, "packing negative integer"),
array(0x0000ffff, "packing pattern (0x0000ffff)"),
array(0xffff0000, "packing pattern (0xffff0000"),
array(0x0f0f0f0f, "packing pattern (0x0f0f0f0f)"),
array(0xf0f0f0f0, "packing pattern (0xf0f0f0f0)"),
array(0xffffffff, "packing maximum 32 bit value (0xffffffff)")
);
}
/**
* @dataProvider providerPack32leValues
*/
public function testPack32le($value, $description) {
$this->assertEquals(ZipStreamer\pack32le($value), pack('V', $value), $description);
}
public function providerPack64leValues() {
# input value, expected high bytes, expected low bytes, description
return array(
array(0, 0, 0, "packing 0"),
array(ZipStreamer\Count64::construct(array(0xffffffff, 0x00000000)), 0xffffffff, 0x00000000, "packing pattern 0x00000000ffffffff"),
array(ZipStreamer\Count64::construct(array(0x00000000, 0xffffffff)), 0x00000000, 0xffffffff, "packing pattern 0xffffffff00000000"),
array(ZipStreamer\Count64::construct(array(0x0f0f0f0f, 0x0f0f0f0f)), 0x0f0f0f0f, 0x0f0f0f0f, "packing pattern 0x0f0f0f0f0f0f0f0f"),
array(ZipStreamer\Count64::construct(array(0xf0f0f0f0, 0xf0f0f0f0)), 0xf0f0f0f0, 0xf0f0f0f0, "packing pattern 0x00f0f0f0f0f0f0f0"),
array(ZipStreamer\Count64::construct(array(0xffffffff, 0xffffffff)), 0xffffffff, 0xffffffff, "packing maximum 64 bit value (0xffffffffffffffff)")
);
}
/**
* @dataProvider providerPack64leValues
*/
public function testPack64le($inVal, $cmpVal1, $cmpVal2, $description) {
$this->assertEquals(ZipStreamer\pack64le($inVal), pack('VV', $cmpVal1, $cmpVal2), $description);
}
public function providerGoodCount64InitializationValues() {
// expected low bytes, expected high bytes, input value, message
return array(
array(0x00000000, 0x00000000, 0, "integer 0"),
array(0x00000000, 0x00000000, array(0, 0), "integer array(0, 0)"),
array(0xffffffff, 0xffffffff, array(0xffffffff, 0xffffffff), "bit pattern array(0xffffffff, 0xffffffff)"),
array(0x00000000, 0xffffffff, array(0x00000000, 0xffffffff), "bit pattern array(0x00000000, 0xffffffff)"),
array(0xffffffff, 0x00000000, array(0xffffffff, 0x00000000), "bit pattern array(0xffffffff, 0x00000000)"),
array(0x0f0f0f0f, 0x0f0f0f0f, array(0x0f0f0f0f, 0x0f0f0f0f), "bit pattern array(0x0f0f0f0f, 0x0f0f0f0f)"),
array(0xf0f0f0f0, 0xf0f0f0f0, array(0xf0f0f0f0, 0xf0f0f0f0), "bit pattern array(0xf0f0f0f0, 0xf0f0f0f0)"),
array(0x00000000, 0x00000000, ZipStreamer\Count64::construct(0), "Count64Base object (value 0)")
);
}
/**
* @dataProvider providerGoodCount64InitializationValues
*/
public function testCount64Construct($loBytes, $hiBytes, $value, $description) {
$count64 = ZipStreamer\Count64::construct($value);
$this->assertInstanceOf('ZipStreamer\Count64Base', $count64, $description . ' (instanceof)');
$this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)");
$this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)");
}
public function providerBadCount64InitializationValues() {
# input value
return array(
array("a"),
array(0.0),
array(1.0),
array(array())
);
}
/**
* @dataProvider providerBadCount64InitializationValues
* @expectedException InvalidArgumentException
*/
public function testCount64ConstructFail($badValue) {
$count64 = ZipStreamer\Count64::construct($badValue);
}
/**
* @dataProvider providerGoodCount64InitializationValues
*/
public function testCount64Set($loBytes, $hiBytes, $value, $description) {
$count64 = ZipStreamer\Count64::construct();
$count64->set($value);
$this->assertInstanceOf('ZipStreamer\Count64Base', $count64, $description . ' (instanceof)');
$this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)");
$this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)");
}
/**
* @dataProvider providerBadCount64InitializationValues
* @expectedException InvalidArgumentException
*/
public function testCount64SetFail($badValue) {
$count64 = ZipStreamer\Count64::construct();
$count64->set($badValue);
}
/**
* @dataProvider providerCount64AddValues
*/
public function providerCount64AddValues() {
# input start value, input add value, expected low bytes, expected high bytes, description
return array(
array(0, 0, 0x00000000, 0x00000000, "adding 0 to 0"),
array(0, 1, 0x00000001, 0x00000000, "adding 1 to 0"),
array(1, 0, 0x00000001, 0x00000000, "adding 0 to 1"),
array(0xffffffff, 1, 0x00000000, 0x00000001, "adding 1 to 0xffffffff"),
array(0x00000001, 0xffffffff, 0x00000000, 0x00000001, "adding 0xfffffff to 0x00000001"),
array(0xffffffff, 0xffffffff, 0xfffffffe, 0x00000001, "adding 0xfffffff to 0xffffffff")
);
}
/**
* @dataProvider providerCount64AddValues
*/
public function testCount64Add($value, $add, $loBytes, $hiBytes, $description) {
$count64 = ZipStreamer\Count64::construct($value);
$count64->add($add);
$this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)".sprintf("%x=%x", $loBytes, $count64->getLoBytes()));
$this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)");
}
}
?>
PHPZipStreamer-0.7/test/phpunit.xml 0000664 0000000 0000000 00000000330 12567341621 0017370 0 ustar 00root root 0000000 0000000
./
PHPZipStreamer-0.7/test/travis/ 0000775 0000000 0000000 00000000000 12567341621 0016473 5 ustar 00root root 0000000 0000000 PHPZipStreamer-0.7/test/travis/php-modules-install.sh 0000775 0000000 0000000 00000002000 12567341621 0022723 0 ustar 00root root 0000000 0000000 #!/bin/bash
MODULE_CACHE_DIR=${TRAVIS_BUILD_DIR}/travis/module-cache/`php-config --vernum`
PHP_CONFIG=${TRAVIS_BUILD_DIR}/travis/phpconfig.ini
PHP_TARGET_DIR=`php-config --extension-dir`
if [ -d ${MODULE_CACHE_DIR} ]; then
cp ${MODULE_CACHE_DIR}/* ${PHP_TARGET_DIR}
fi
touch ${PHP_CONFIG}
mkdir -p ${MODULE_CACHE_DIR}
pecl_module_install() {
if [[ "-f" = $1 ]]; then
force="-f"
shift
else
force=""
fi
package=$1
filename=$2
if [ ! -f ${PHP_TARGET_DIR}/${filename} ]
then
echo "$filename not found in extension dir, compiling"
pecl install $force ${package}
else
echo "Adding $filename to php config"
echo "extension = $filename" >> ${PHP_CONFIG}
fi
cp ${PHP_TARGET_DIR}/${filename} ${MODULE_CACHE_DIR}
}
if [[ 1 < $(echo ${PECL_HTTP_VERSION} | cut -d. -f 1) ]]; then
yes | pecl_module_install raphf raphf.so
yes | pecl_module_install propro propro.so
fi
printf "\n\n\n" | pecl_module_install -f pecl_http-$PECL_HTTP_VERSION http.so
phpenv config-add ${PHP_CONFIG}