mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-29 09:50:40 +02:00
Add ZipModel for all changes.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* Traditional PKWARE Encryption Engine.
|
||||
@@ -13,7 +15,7 @@ use PhpZip\Util\CryptoUtil;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* Encryption header size
|
||||
@@ -66,7 +68,6 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
* @var array
|
||||
*/
|
||||
private $keys = [];
|
||||
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
@@ -80,7 +81,6 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
$this->initKeys($entry->getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,25 +107,8 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
{
|
||||
$this->keys[0] = self::crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff);
|
||||
$this->keys[1] = self::toInt($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = self::toInt(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast to int
|
||||
*
|
||||
* @param $i
|
||||
* @return int
|
||||
*/
|
||||
private static function toInt($i)
|
||||
{
|
||||
$i = (int)($i & 0xffffffff);
|
||||
if ($i > 2147483647) {
|
||||
return -(-$i & 0xffffffff);
|
||||
} elseif ($i < -2147483648) {
|
||||
return $i & -2147483648;
|
||||
}
|
||||
return $i;
|
||||
$this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +130,11 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
$password = $this->entry->getPassword();
|
||||
$this->initKeys($password);
|
||||
|
||||
$headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE)));
|
||||
$byte = 0;
|
||||
foreach ($headerBytes as &$byte) {
|
||||
$byte = ($byte ^ $this->decryptByte()) & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
@@ -198,7 +185,9 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
|
||||
|
||||
// Initialize again since the generated bytes were encrypted.
|
||||
$this->initKeys($this->entry->getPassword());
|
||||
$password = $this->entry->getPassword();
|
||||
$this->initKeys($password);
|
||||
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
|
||||
|
||||
@@ -233,4 +222,4 @@ class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
$this->updateKeys($byte);
|
||||
return $tempVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* WinZip Aes Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEngine implements CryptoEngine
|
||||
class WinZipAesEngine implements ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* The block size of the Advanced Encryption Specification (AES) Algorithm
|
||||
@@ -50,13 +52,16 @@ class WinZipAesEngine implements CryptoEngine
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
$extraFieldsCollection = $this->entry->getExtraFieldsCollection();
|
||||
|
||||
if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null === $field) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
|
||||
}
|
||||
$field = $extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()];
|
||||
|
||||
// Get key strength.
|
||||
$keyStrengthBits = $field->getKeyStrength();
|
||||
@@ -218,8 +223,8 @@ class WinZipAesEngine implements CryptoEngine
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$keyStrengthBytes = 32;
|
||||
$keyStrengthBits = $keyStrengthBytes * 8;
|
||||
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod());
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
|
||||
@@ -244,4 +249,4 @@ class WinZipAesEngine implements CryptoEngine
|
||||
substr($mac, 0, 10)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
|
||||
interface CryptoEngine
|
||||
/**
|
||||
* Encryption Engine
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* Decryption string.
|
||||
@@ -21,4 +29,4 @@ interface CryptoEngine
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($content);
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -6,7 +7,7 @@ namespace PhpZip\Exception;
|
||||
* Central File Header and the Data Descriptor or between the declared value
|
||||
* and the computed value from the decompressed data.
|
||||
*
|
||||
* The exception's detail message is the name of the ZIP entry.
|
||||
* The exception detail message is the name of the ZIP entry.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
@@ -36,12 +37,8 @@ class Crc32Exception extends ZipException
|
||||
*/
|
||||
public function __construct($name, $expected, $actual)
|
||||
{
|
||||
parent::__construct($name
|
||||
. " (expected CRC32 value 0x"
|
||||
. dechex($expected)
|
||||
. ", but is actually 0x"
|
||||
. dechex($actual)
|
||||
. ")");
|
||||
parent::__construct($name . " (expected CRC32 value 0x" .
|
||||
dechex($expected) . ", but is actually 0x" . dechex($actual) . ")");
|
||||
assert($expected != $actual);
|
||||
$this->expectedCrc = $expected;
|
||||
$this->actualCrc = $actual;
|
||||
@@ -66,5 +63,4 @@ class Crc32Exception extends ZipException
|
||||
{
|
||||
return $this->actualCrc;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -8,7 +9,6 @@ namespace PhpZip\Exception;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class InvalidArgumentException extends ZipException
|
||||
class InvalidArgumentException extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -9,5 +10,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class RuntimeException extends ZipException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -9,5 +10,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipAuthenticationException extends ZipCryptoException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -10,5 +11,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipCryptoException extends ZipException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -10,5 +11,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -10,5 +11,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipNotFoundEntry extends ZipException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
@@ -10,5 +11,4 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipUnsupportMethod extends ZipException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,98 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Default implementation for an Extra Field in a Local or Central Header of a
|
||||
* ZIP file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DefaultExtraField extends ExtraField
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $headerId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Constructs a new Extra Field.
|
||||
*
|
||||
* @param int $headerId an unsigned short integer (two bytes) indicating the
|
||||
* type of the Extra Field.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
self::$headerId = $headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return self::$headerId & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return null !== $this->data ? strlen($this->data) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if ($size > 0) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
$this->data = fread($handle, $size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param int $off
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
if (null !== $this->data) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, $this->data);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,120 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Abstract base class for an Extra Field in a Local or Central Header of a
|
||||
* ZIP archive.
|
||||
* Extra Field in a Local or Central Header of a ZIP archive.
|
||||
* It defines the common properties of all Extra Fields and how to
|
||||
* serialize/deserialize them to/from byte arrays.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ExtraField implements ExtraFieldHeader
|
||||
interface ExtraField
|
||||
{
|
||||
/** The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
const ZIP64_HEADER_ID = 0x0001;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private static $registry;
|
||||
|
||||
/**
|
||||
* A static factory method which creates a new Extra Field based on the
|
||||
* given Header ID.
|
||||
* The returned Extra Field still requires proper initialization, for
|
||||
* example by calling ExtraField::readFrom.
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @param int $headerId An unsigned short integer (two bytes) which indicates
|
||||
* the type of the returned Extra Field.
|
||||
* @return ExtraField A new Extra Field or null if not support header id.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
* @return int
|
||||
*/
|
||||
public static function create($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
if (isset(self::getRegistry()[$headerId])) {
|
||||
$extraClassName = self::getRegistry()[$headerId];
|
||||
$extraField = new $extraClassName;
|
||||
if ($extraField::getHeaderId() !== $headerId) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
$extraField = new DefaultExtraField($headerId);
|
||||
}
|
||||
return $extraField;
|
||||
}
|
||||
public static function getHeaderId();
|
||||
|
||||
/**
|
||||
* Registered extra field classes.
|
||||
*
|
||||
* @return array|null
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
private static function getRegistry()
|
||||
{
|
||||
if (null === self::$registry) {
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
public function serialize();
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the Data Block.
|
||||
*
|
||||
* @return resource
|
||||
* @throws ZipException If size data block out of range.
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function getDataBlock()
|
||||
{
|
||||
$size = $this->getDataSize();
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size data block out of range.');
|
||||
}
|
||||
$fp = fopen('php://memory', 'r+b');
|
||||
if (0 === $size) return $fp;
|
||||
$this->writeTo($fp, 0);
|
||||
rewind($fp);
|
||||
return $fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
abstract public function getDataSize();
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
*/
|
||||
abstract public function writeTo($handle, $off);
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
*/
|
||||
abstract public function readFrom($handle, $off, $size);
|
||||
}
|
||||
public function deserialize($data);
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
/**
|
||||
* Interface ExtraFieldHeader
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ExtraFieldHeader
|
||||
{
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId();
|
||||
|
||||
}
|
@@ -1,200 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Represents a collection of Extra Fields as they may
|
||||
* be present at several locations in ZIP files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFields
|
||||
{
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID to Extra Field.
|
||||
* Must not be null, but may be empty if no Extra Fields are used.
|
||||
* The map is sorted by Header IDs in ascending order.
|
||||
*
|
||||
* @var ExtraField[]
|
||||
*/
|
||||
private $extra = [];
|
||||
|
||||
/**
|
||||
* Returns the number of Extra Fields in this collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size()
|
||||
{
|
||||
return sizeof($this->extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function get($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->extra[$headerId])) {
|
||||
return $this->extra[$headerId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given Extra Field in this collection.
|
||||
*
|
||||
* @param ExtraField $extraField The Extra Field to store in this collection.
|
||||
* @return ExtraField The Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function add(ExtraField $extraField)
|
||||
{
|
||||
$headerId = $extraField::getHeaderId();
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
$this->extra[$headerId] = $extraField;
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($headerId)
|
||||
{
|
||||
return isset($this->extra[$headerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Extra Field with the given Header ID.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range or extra field not found.
|
||||
*/
|
||||
public function remove($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->extra[$headerId])) {
|
||||
$ef = $this->extra[$headerId];
|
||||
unset($this->extra[$headerId]);
|
||||
return $ef;
|
||||
}
|
||||
throw new ZipException('ExtraField not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the Extra Fields.
|
||||
* null is never returned.
|
||||
*
|
||||
* @return string
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
$size = $this->getExtraLength();
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if (0 === $size) return '';
|
||||
|
||||
$fp = fopen('php://memory', 'r+b');
|
||||
$offset = 0;
|
||||
/**
|
||||
* @var ExtraField $ef
|
||||
*/
|
||||
foreach ($this->extra as $ef) {
|
||||
fwrite($fp, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
|
||||
$offset += 4;
|
||||
fwrite($fp, $ef->writeTo($fp, $offset));
|
||||
$offset += $ef->getDataSize();
|
||||
}
|
||||
rewind($fp);
|
||||
$content = stream_get_contents($fp);
|
||||
fclose($fp);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes required to hold the Extra Fields.
|
||||
*
|
||||
* @return int The length of the Extra Fields in bytes. May be 0.
|
||||
* @see #getExtra
|
||||
*/
|
||||
public function getExtraLength()
|
||||
{
|
||||
if (empty($this->extra)) {
|
||||
return 0;
|
||||
}
|
||||
$length = 0;
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
foreach ($this->extra as $extraField) {
|
||||
$length += 4 + $extraField->getDataSize();
|
||||
}
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset
|
||||
* @param int $size Size
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
$map = [];
|
||||
if (null !== $handle && 0 < $size) {
|
||||
$end = $off + $size;
|
||||
while ($off < $end) {
|
||||
fseek($handle, $off);
|
||||
$unpack = unpack('vheaderId/vdataSize', fread($handle, 4));
|
||||
$off += 4;
|
||||
$extraField = ExtraField::create($unpack['headerId']);
|
||||
$extraField->readFrom($handle, $off, $unpack['dataSize']);
|
||||
$off += $unpack['dataSize'];
|
||||
$map[$unpack['headerId']] = $extraField;
|
||||
}
|
||||
assert($off === $end);
|
||||
}
|
||||
$this->extra = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* If clone extra fields.
|
||||
*/
|
||||
function __clone()
|
||||
{
|
||||
foreach ($this->extra as $k => $v) {
|
||||
$this->extra[$k] = clone $v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
240
src/PhpZip/Extra/ExtraFieldsCollection.php
Normal file
240
src/PhpZip/Extra/ExtraFieldsCollection.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Represents a collection of Extra Fields as they may
|
||||
* be present at several locations in ZIP files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID to Extra Field.
|
||||
* Must not be null, but may be empty if no Extra Fields are used.
|
||||
* The map is sorted by Header IDs in ascending order.
|
||||
*
|
||||
* @var ExtraField[]
|
||||
*/
|
||||
protected $collection = [];
|
||||
|
||||
/**
|
||||
* Returns the number of Extra Fields in this collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function get($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->collection[$headerId])) {
|
||||
return $this->collection[$headerId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given Extra Field in this collection.
|
||||
*
|
||||
* @param ExtraField $extraField The Extra Field to store in this collection.
|
||||
* @return ExtraField The Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function add(ExtraField $extraField)
|
||||
{
|
||||
$headerId = $extraField::getHeaderId();
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
$this->collection[$headerId] = $extraField;
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($headerId)
|
||||
{
|
||||
return isset($this->collection[$headerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Extra Field with the given Header ID.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range or extra field not found.
|
||||
*/
|
||||
public function remove($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->collection[$headerId])) {
|
||||
$ef = $this->collection[$headerId];
|
||||
unset($this->collection[$headerId]);
|
||||
return $ef;
|
||||
}
|
||||
throw new ZipException('ExtraField not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a offset exists
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param mixed $offset <p>
|
||||
* An offset to check for.
|
||||
* </p>
|
||||
* @return boolean true on success or false on failure.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->collection[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to retrieve.
|
||||
* </p>
|
||||
* @return mixed Can return all value types.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to assign the value to.
|
||||
* </p>
|
||||
* @param mixed $value <p>
|
||||
* The value to set.
|
||||
* </p>
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($value instanceof ExtraField) {
|
||||
assert($offset == $value::getHeaderId());
|
||||
$this->add($value);
|
||||
} else {
|
||||
throw new InvalidArgumentException('value is not instanceof ' . ExtraField::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
* @return void
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return current($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return void Any returned value is ignored.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
next($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->offsetExists($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
reset($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* If clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->collection as $k => $v) {
|
||||
$this->collection[$k] = clone $v;
|
||||
}
|
||||
}
|
||||
}
|
100
src/PhpZip/Extra/ExtraFieldsFactory.php
Normal file
100
src/PhpZip/Extra/ExtraFieldsFactory.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\Fields\DefaultExtraField;
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Extra Fields Factory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFieldsFactory
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected static $registry;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* A static factory method which creates a new Extra Field based on the
|
||||
* given Header ID.
|
||||
* The returned Extra Field still requires proper initialization, for
|
||||
* example by calling ExtraField::readFrom.
|
||||
*
|
||||
* @param int $headerId An unsigned short integer (two bytes) which indicates
|
||||
* the type of the returned Extra Field.
|
||||
* @return ExtraField A new Extra Field or null if not support header id.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public static function create($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
if (isset(self::getRegistry()[$headerId])) {
|
||||
$extraClassName = self::getRegistry()[$headerId];
|
||||
$extraField = new $extraClassName;
|
||||
if ($extraField::getHeaderId() !== $headerId) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
$extraField = new DefaultExtraField($headerId);
|
||||
}
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered extra field classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getRegistry()
|
||||
{
|
||||
if (null === self::$registry) {
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||
self::$registry[Zip64ExtraField::getHeaderId()] = Zip64ExtraField::class;
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WinZipAesEntryExtraField
|
||||
*/
|
||||
public static function createWinZipAesEntryExtra()
|
||||
{
|
||||
return new WinZipAesEntryExtraField();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NtfsExtraField
|
||||
*/
|
||||
public static function createNtfsExtra()
|
||||
{
|
||||
return new NtfsExtraField();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return Zip64ExtraField
|
||||
*/
|
||||
public static function createZip64Extra(ZipEntry $entry)
|
||||
{
|
||||
return new Zip64ExtraField($entry);
|
||||
}
|
||||
}
|
71
src/PhpZip/Extra/Fields/DefaultExtraField.php
Normal file
71
src/PhpZip/Extra/Fields/DefaultExtraField.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
|
||||
/**
|
||||
* Default implementation for an Extra Field in a Local or Central Header of a
|
||||
* ZIP file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DefaultExtraField implements ExtraField
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $headerId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructs a new Extra Field.
|
||||
*
|
||||
* @param int $headerId an unsigned short integer (two bytes) indicating the
|
||||
* type of the Extra Field.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
self::$headerId = $headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return self::$headerId & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
133
src/PhpZip/Extra/Fields/NtfsExtraField.php
Normal file
133
src/PhpZip/Extra/Fields/NtfsExtraField.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* NTFS Extra Field
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class NtfsExtraField implements ExtraField
|
||||
{
|
||||
|
||||
/**
|
||||
* Modify time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* Access Time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* Create Time
|
||||
*
|
||||
* @var int Unix Time
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x000a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$unpack = unpack('vtag/vsizeAttr', substr($data, 0, 4));
|
||||
if (24 === $unpack['sizeAttr']) {
|
||||
$tagData = substr($data, 4, $unpack['sizeAttr']);
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
$this->atime = PackUtil::unpackLongLE(substr($tagData, 8, 8)) / 10000000 - 11644473600;
|
||||
$this->ctime = PackUtil::unpackLongLE(substr($tagData, 16, 8)) / 10000000 - 11644473600;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
$serialize = '';
|
||||
if (null !== $this->mtime && null !== $this->atime && null !== $this->ctime) {
|
||||
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
|
||||
$atimeLong = ($this->atime + 11644473600) * 10000000;
|
||||
$ctimeLong = ($this->ctime + 11644473600) * 10000000;
|
||||
|
||||
$serialize .= pack('Vvv', 0, 1, 8 * 3)
|
||||
. PackUtil::packLongLE($mtimeLong)
|
||||
. PackUtil::packLongLE($atimeLong)
|
||||
. PackUtil::packLongLE($ctimeLong);
|
||||
}
|
||||
return $serialize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mtime
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = (int)$mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $atime
|
||||
*/
|
||||
public function setAtime($atime)
|
||||
{
|
||||
$this->atime = (int)$atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ctime
|
||||
*/
|
||||
public function setCtime($ctime)
|
||||
{
|
||||
$this->ctime = (int)$ctime;
|
||||
}
|
||||
}
|
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* WinZip AES Extra Field.
|
||||
@@ -11,7 +14,7 @@ use PhpZip\Exception\ZipException;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEntryExtraField extends ExtraField
|
||||
class WinZipAesEntryExtraField implements ExtraField
|
||||
{
|
||||
const DATA_SIZE = 7;
|
||||
const VENDOR_ID = 17729; // 'A' | ('E' << 8);
|
||||
@@ -32,32 +35,38 @@ class WinZipAesEntryExtraField extends ExtraField
|
||||
const KEY_STRENGTH_192BIT = 192;
|
||||
const KEY_STRENGTH_256BIT = 256;
|
||||
|
||||
private static $keyStrengths = [
|
||||
protected static $keyStrengths = [
|
||||
self::KEY_STRENGTH_128BIT => 0x01,
|
||||
self::KEY_STRENGTH_192BIT => 0x02,
|
||||
self::KEY_STRENGTH_256BIT => 0x03
|
||||
];
|
||||
|
||||
protected static $encryptionMethods = [
|
||||
self::KEY_STRENGTH_128BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||
self::KEY_STRENGTH_192BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||
self::KEY_STRENGTH_256BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
];
|
||||
|
||||
/**
|
||||
* Vendor version.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $vendorVersion = self::VV_AE_1;
|
||||
protected $vendorVersion = self::VV_AE_1;
|
||||
|
||||
/**
|
||||
* Encryption strength.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionStrength = self::KEY_STRENGTH_256BIT;
|
||||
protected $encryptionStrength = self::KEY_STRENGTH_256BIT;
|
||||
|
||||
/**
|
||||
* Zip compression method.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $method;
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
@@ -71,21 +80,6 @@ class WinZipAesEntryExtraField extends ExtraField
|
||||
return 0x9901;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return self::DATA_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vendor version.
|
||||
*
|
||||
@@ -155,6 +149,32 @@ class WinZipAesEntryExtraField extends ExtraField
|
||||
return $this->method & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal encryption method.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return isset(self::$encryptionMethods[$this->getKeyStrength()]) ?
|
||||
self::$encryptionMethods[$this->getKeyStrength()] :
|
||||
self::$encryptionMethods[self::KEY_STRENGTH_256BIT];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
* @return int
|
||||
* @throws ZipException
|
||||
*/
|
||||
public static function getKeyStrangeFromEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
$flipKey = array_flip(self::$encryptionMethods);
|
||||
if (!isset($flipKey[$encryptionMethod])) {
|
||||
throw new ZipException("Unsupport encryption method " . $encryptionMethod);
|
||||
}
|
||||
return $flipKey[$encryptionMethod];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets compression method.
|
||||
*
|
||||
@@ -169,37 +189,6 @@ class WinZipAesEntryExtraField extends ExtraField
|
||||
$this->method = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (self::DATA_SIZE != $size)
|
||||
throw new ZipException();
|
||||
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
/**
|
||||
* @var int $vendorVersion
|
||||
* @var int $vendorId
|
||||
* @var int $keyStrength
|
||||
* @var int $method
|
||||
*/
|
||||
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', fread($handle, 7));
|
||||
extract($unpack);
|
||||
$this->setVendorVersion($vendorVersion);
|
||||
if (self::VENDOR_ID != $vendorId) {
|
||||
throw new ZipException();
|
||||
}
|
||||
$this->setKeyStrength(self::keyStrength($keyStrength)); // checked
|
||||
$this->setMethod($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key strength.
|
||||
*
|
||||
@@ -218,19 +207,50 @@ class WinZipAesEntryExtraField extends ExtraField
|
||||
*/
|
||||
public static function encryptionStrength($keyStrength)
|
||||
{
|
||||
return isset(self::$keyStrengths[$keyStrength]) ? self::$keyStrengths[$keyStrength] : self::$keyStrengths[self::KEY_STRENGTH_128BIT];
|
||||
return isset(self::$keyStrengths[$keyStrength]) ?
|
||||
self::$keyStrengths[$keyStrength] :
|
||||
self::$keyStrengths[self::KEY_STRENGTH_128BIT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
public function serialize()
|
||||
{
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, pack('vvcv', $this->vendorVersion, self::VENDOR_ID, $this->encryptionStrength, $this->method));
|
||||
return pack(
|
||||
'vvcv',
|
||||
$this->vendorVersion,
|
||||
self::VENDOR_ID,
|
||||
$this->encryptionStrength,
|
||||
$this->method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$size = strlen($data);
|
||||
if (self::DATA_SIZE !== $size) {
|
||||
throw new ZipException('WinZip AES Extra data invalid size: ' . $size . '. Must be ' . self::DATA_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int $vendorVersion
|
||||
* @var int $vendorId
|
||||
* @var int $keyStrength
|
||||
* @var int $method
|
||||
*/
|
||||
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', $data);
|
||||
$this->setVendorVersion($unpack['vendorVersion']);
|
||||
if (self::VENDOR_ID !== $unpack['vendorId']) {
|
||||
throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID);
|
||||
}
|
||||
$this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked
|
||||
$this->setMethod($unpack['method']);
|
||||
}
|
||||
}
|
118
src/PhpZip/Extra/Fields/Zip64ExtraField.php
Normal file
118
src/PhpZip/Extra/Fields/Zip64ExtraField.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* ZIP64 Extra Field
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class Zip64ExtraField implements ExtraField
|
||||
{
|
||||
/** The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
const ZIP64_HEADER_ID = 0x0001;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* Zip64ExtraField constructor.
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry = null)
|
||||
{
|
||||
if (null !== $entry) {
|
||||
$this->setEntry($entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function setEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x0001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
if (null === $this->entry) {
|
||||
throw new RuntimeException("entry is null");
|
||||
}
|
||||
$data = '';
|
||||
// Write out Uncompressed Size.
|
||||
$size = $this->entry->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
$data .= PackUtil::packLongLE($size);
|
||||
}
|
||||
// Write out Compressed Size.
|
||||
$compressedSize = $this->entry->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
$data .= PackUtil::packLongLE($compressedSize);
|
||||
}
|
||||
// Write out Relative Header Offset.
|
||||
$offset = $this->entry->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
$data .= PackUtil::packLongLE($offset);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
if (null === $this->entry) {
|
||||
throw new RuntimeException("entry is null");
|
||||
}
|
||||
$off = 0;
|
||||
// Read in Uncompressed Size.
|
||||
$size = $this->entry->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
assert(0xffffffff === $size);
|
||||
$this->entry->setSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Compressed Size.
|
||||
$compressedSize = $this->entry->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
assert(0xffffffff === $compressedSize);
|
||||
$this->entry->setCompressedSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Relative Header Offset.
|
||||
$offset = $this->entry->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
assert(0xffffffff, $offset);
|
||||
$this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,176 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* NTFS Extra Field
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class NtfsExtraField extends ExtraField
|
||||
{
|
||||
|
||||
/**
|
||||
* Modify time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* Access Time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* Create Time
|
||||
*
|
||||
* @var int Unix Time
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rawData = "";
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x000a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return 8 * 4 + strlen($this->rawData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if ($size > 0) {
|
||||
$off += 4;
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
|
||||
$unpack = unpack('vtag/vsizeAttr', fread($handle, 4));
|
||||
if (24 === $unpack['sizeAttr']) {
|
||||
$tagData = fread($handle, $unpack['sizeAttr']);
|
||||
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
$this->atime = PackUtil::unpackLongLE(substr($tagData, 8, 8)) / 10000000 - 11644473600;
|
||||
$this->ctime = PackUtil::unpackLongLE(substr($tagData, 16, 8)) / 10000000 - 11644473600;
|
||||
}
|
||||
$off += $unpack['sizeAttr'];
|
||||
|
||||
if ($size > $off) {
|
||||
$this->rawData .= fread($handle, $size - $off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
if (null !== $this->mtime && null !== $this->atime && null !== $this->ctime) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, pack('Vvv', 0, 1, 8 * 3 + strlen($this->rawData)));
|
||||
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
|
||||
fwrite($handle, PackUtil::packLongLE($mtimeLong));
|
||||
$atimeLong = ($this->atime + 11644473600) * 10000000;
|
||||
fwrite($handle, PackUtil::packLongLE($atimeLong));
|
||||
$ctimeLong = ($this->ctime + 11644473600) * 10000000;
|
||||
fwrite($handle, PackUtil::packLongLE($ctimeLong));
|
||||
if (!empty($this->rawData)) {
|
||||
fwrite($handle, $this->rawData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mtime
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = (int)$mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $atime
|
||||
*/
|
||||
public function setAtime($atime)
|
||||
{
|
||||
$this->atime = (int)$atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ctime
|
||||
*/
|
||||
public function setCtime($ctime)
|
||||
{
|
||||
$this->ctime = (int)$ctime;
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
@@ -19,7 +20,7 @@ class OffsetPositionMapper extends PositionMapper
|
||||
*/
|
||||
public function __construct($offset)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->offset = (int)$offset;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,4 +40,4 @@ class OffsetPositionMapper extends PositionMapper
|
||||
{
|
||||
return parent::unmap($position) - $this->offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
@@ -26,4 +27,4 @@ class PositionMapper
|
||||
{
|
||||
return $position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,482 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipNotFoundEntry;
|
||||
use PhpZip\Model\Entry\ZipNewStringEntry;
|
||||
use PhpZip\Model\Entry\ZipReadEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Read Central Directory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class CentralDirectory
|
||||
{
|
||||
/** Central File Header signature. */
|
||||
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
|
||||
/**
|
||||
* @var EndOfCentralDirectory End of Central Directory
|
||||
*/
|
||||
private $endOfCentralDirectory;
|
||||
/**
|
||||
* @var ZipEntry[] Maps entry names to zip entries.
|
||||
*/
|
||||
private $entries = [];
|
||||
/**
|
||||
* @var ZipEntry[] New and modified entries
|
||||
*/
|
||||
private $modifiedEntries = [];
|
||||
/**
|
||||
* @var int Default compression level for the methods DEFLATED and BZIP2.
|
||||
*/
|
||||
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
/**
|
||||
* @var int|null ZipAlign setting
|
||||
*/
|
||||
private $zipAlign;
|
||||
/**
|
||||
* @var string New password
|
||||
*/
|
||||
private $password;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clearPassword;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->endOfCentralDirectory = new EndOfCentralDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the central directory from the given seekable byte channel
|
||||
* and populates the internal tables with ZipEntry instances.
|
||||
*
|
||||
* The ZipEntry's will know all data that can be obtained from the
|
||||
* central directory alone, but not the data that requires the local
|
||||
* file header or additional data to be read.
|
||||
*
|
||||
* @param resource $inputStream
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function mountCentralDirectory($inputStream)
|
||||
{
|
||||
$this->modifiedEntries = [];
|
||||
$this->checkZipFileSignature($inputStream);
|
||||
$this->endOfCentralDirectory->findCentralDirectory($inputStream);
|
||||
|
||||
$numEntries = $this->endOfCentralDirectory->getCentralDirectoryEntriesSize();
|
||||
$entries = [];
|
||||
for (; $numEntries > 0; $numEntries--) {
|
||||
$entry = new ZipReadEntry($inputStream);
|
||||
$entry->setCentralDirectory($this);
|
||||
// Re-load virtual offset after ZIP64 Extended Information
|
||||
// Extra Field may have been parsed, map it to the real
|
||||
// offset and conditionally update the preamble size from it.
|
||||
$lfhOff = $this->endOfCentralDirectory->getMapper()->map($entry->getOffset());
|
||||
if ($lfhOff < $this->endOfCentralDirectory->getPreamble()) {
|
||||
$this->endOfCentralDirectory->setPreamble($lfhOff);
|
||||
}
|
||||
$entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
if (0 !== $numEntries % 0x10000) {
|
||||
throw new ZipException("Expected " . abs($numEntries) .
|
||||
($numEntries > 0 ? " more" : " less") .
|
||||
" entries in the Central Directory!");
|
||||
}
|
||||
$this->entries = $entries;
|
||||
|
||||
if ($this->endOfCentralDirectory->getPreamble() + $this->endOfCentralDirectory->getPostamble() >= fstat($inputStream)['size']) {
|
||||
assert(0 === $numEntries);
|
||||
$this->checkZipFileSignature($inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check zip file signature
|
||||
*
|
||||
* @param resource $inputStream
|
||||
* @throws ZipException if this not .ZIP file.
|
||||
*/
|
||||
private function checkZipFileSignature($inputStream)
|
||||
{
|
||||
rewind($inputStream);
|
||||
// Constraint: A ZIP file must start with a Local File Header
|
||||
// or a (ZIP64) End Of Central Directory Record if it's empty.
|
||||
$signatureBytes = fread($inputStream, 4);
|
||||
if (strlen($signatureBytes) < 4) {
|
||||
throw new ZipException("Invalid zip file.");
|
||||
}
|
||||
$signature = unpack('V', $signatureBytes)[1];
|
||||
if (
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
|
||||
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
) {
|
||||
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compression method for new or rewrites entries.
|
||||
* @param int $compressionLevel
|
||||
* @throws InvalidArgumentException
|
||||
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFile::LEVEL_BEST_SPEED
|
||||
* @see ZipFile::LEVEL_BEST_COMPRESSION
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
|
||||
{
|
||||
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
|
||||
) {
|
||||
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
|
||||
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION);
|
||||
}
|
||||
$this->compressionLevel = $compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
public function &getEntries()
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getEntry($entryName)
|
||||
{
|
||||
if (!isset($this->entries[$entryName])) {
|
||||
throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found');
|
||||
}
|
||||
return $this->entries[$entryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getModifiedEntry($entryName){
|
||||
if (!isset($this->modifiedEntries[$entryName])) {
|
||||
throw new ZipNotFoundEntry('Zip modified entry ' . $entryName . ' not found');
|
||||
}
|
||||
return $this->modifiedEntries[$entryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EndOfCentralDirectory
|
||||
*/
|
||||
public function getEndOfCentralDirectory()
|
||||
{
|
||||
return $this->endOfCentralDirectory;
|
||||
}
|
||||
|
||||
public function getArchiveComment()
|
||||
{
|
||||
return null === $this->endOfCentralDirectory->getComment() ?
|
||||
'' :
|
||||
$this->endOfCentralDirectory->getComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry comment
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment)
|
||||
{
|
||||
if (isset($this->modifiedEntries[$entryName])) {
|
||||
$this->modifiedEntries[$entryName]->setComment($comment);
|
||||
} elseif (isset($this->entries[$entryName])) {
|
||||
$entry = clone $this->entries[$entryName];
|
||||
$entry->setComment($comment);
|
||||
$this->putInModified($entryName, $entry);
|
||||
} else {
|
||||
throw new ZipNotFoundEntry("Not found entry " . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $password
|
||||
* @param int|null $encryptionMethod
|
||||
*/
|
||||
public function setNewPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
$this->password = $password;
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
$this->clearPassword = $password === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getZipAlign()
|
||||
{
|
||||
return $this->zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $zipAlign
|
||||
*/
|
||||
public function setZipAlign($zipAlign = null)
|
||||
{
|
||||
if (null === $zipAlign) {
|
||||
$this->zipAlign = null;
|
||||
return;
|
||||
}
|
||||
$this->zipAlign = (int)$zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put modification or new entries.
|
||||
*
|
||||
* @param $entryName
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function putInModified($entryName, ZipEntry $entry)
|
||||
{
|
||||
$this->modifiedEntries[$entryName] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function deleteEntry($entryName)
|
||||
{
|
||||
if (isset($this->entries[$entryName])) {
|
||||
$this->modifiedEntries[$entryName] = null;
|
||||
} elseif (isset($this->modifiedEntries[$entryName])) {
|
||||
unset($this->modifiedEntries[$entryName]);
|
||||
} else {
|
||||
throw new ZipNotFoundEntry("Not found entry " . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexPattern
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteEntriesFromRegex($regexPattern)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($this->modifiedEntries as $entryName => &$entry) {
|
||||
if (preg_match($regexPattern, $entryName)) {
|
||||
unset($entry);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
foreach ($this->entries as $entryName => $entry) {
|
||||
if (preg_match($regexPattern, $entryName)) {
|
||||
$this->modifiedEntries[$entryName] = null;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $oldName
|
||||
* @param string $newName
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function rename($oldName, $newName)
|
||||
{
|
||||
$oldName = (string)$oldName;
|
||||
$newName = (string)$newName;
|
||||
|
||||
if (isset($this->entries[$newName]) || isset($this->modifiedEntries[$newName])) {
|
||||
throw new InvalidArgumentException("New entry name " . $newName . ' is exists.');
|
||||
}
|
||||
|
||||
if (isset($this->modifiedEntries[$oldName]) || isset($this->entries[$oldName])) {
|
||||
$newEntry = clone (isset($this->modifiedEntries[$oldName]) ?
|
||||
$this->modifiedEntries[$oldName] :
|
||||
$this->entries[$oldName]);
|
||||
$newEntry->setName($newName);
|
||||
|
||||
$this->modifiedEntries[$oldName] = null;
|
||||
$this->modifiedEntries[$newName] = $newEntry;
|
||||
return;
|
||||
}
|
||||
throw new ZipNotFoundEntry("Not found entry " . $oldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->modifiedEntries = [];
|
||||
foreach ($this->entries as $entry) {
|
||||
$this->modifiedEntries[$entry->getName()] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outputStream
|
||||
*/
|
||||
public function writeArchive($outputStream)
|
||||
{
|
||||
/**
|
||||
* @var ZipEntry[] $memoryEntriesResult
|
||||
*/
|
||||
$memoryEntriesResult = [];
|
||||
foreach ($this->entries as $entryName => $entry) {
|
||||
if (isset($this->modifiedEntries[$entryName])) continue;
|
||||
|
||||
if (
|
||||
(null !== $this->password || $this->clearPassword) &&
|
||||
$entry->isEncrypted() &&
|
||||
$entry->getPassword() !== null &&
|
||||
(
|
||||
$entry->getPassword() !== $this->password ||
|
||||
$entry->getEncryptionMethod() !== $this->encryptionMethod
|
||||
)
|
||||
) {
|
||||
$prototypeEntry = new ZipNewStringEntry($entry->getEntryContent());
|
||||
$prototypeEntry->setName($entry->getName());
|
||||
$prototypeEntry->setMethod($entry->getMethod());
|
||||
$prototypeEntry->setTime($entry->getTime());
|
||||
$prototypeEntry->setExternalAttributes($entry->getExternalAttributes());
|
||||
$prototypeEntry->setExtra($entry->getExtra());
|
||||
$prototypeEntry->setPassword($this->password, $this->encryptionMethod);
|
||||
if ($this->clearPassword) {
|
||||
$prototypeEntry->clearEncryption();
|
||||
}
|
||||
} else {
|
||||
$prototypeEntry = clone $entry;
|
||||
}
|
||||
$memoryEntriesResult[$entryName] = $prototypeEntry;
|
||||
}
|
||||
|
||||
foreach ($this->modifiedEntries as $entryName => $outputEntry) {
|
||||
if (null === $outputEntry) { // remove marked entry
|
||||
unset($memoryEntriesResult[$entryName]);
|
||||
} else {
|
||||
if (null !== $this->password) {
|
||||
$outputEntry->setPassword($this->password, $this->encryptionMethod);
|
||||
}
|
||||
$memoryEntriesResult[$entryName] = $outputEntry;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($memoryEntriesResult as $key => $outputEntry) {
|
||||
$outputEntry->setCentralDirectory($this);
|
||||
$outputEntry->writeEntry($outputStream);
|
||||
}
|
||||
$centralDirectoryOffset = ftell($outputStream);
|
||||
foreach ($memoryEntriesResult as $key => $outputEntry) {
|
||||
if (!$this->writeCentralFileHeader($outputStream, $outputEntry)) {
|
||||
unset($memoryEntriesResult[$key]);
|
||||
}
|
||||
}
|
||||
$centralDirectoryEntries = sizeof($memoryEntriesResult);
|
||||
$this->getEndOfCentralDirectory()->writeEndOfCentralDirectory(
|
||||
$outputStream,
|
||||
$centralDirectoryEntries,
|
||||
$centralDirectoryOffset
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @param ZipEntry $entry
|
||||
* @return bool false if and only if the record has been skipped,
|
||||
* i.e. not written for some other reason than an I/O error.
|
||||
*/
|
||||
private function writeCentralFileHeader($outputStream, ZipEntry $entry)
|
||||
{
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$size = $entry->getSize();
|
||||
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
|
||||
// UNKNOWN!
|
||||
if (ZipEntry::UNKNOWN === ($compressedSize | $size)) {
|
||||
return false;
|
||||
}
|
||||
$extra = $entry->getExtra();
|
||||
$extraSize = strlen($extra);
|
||||
|
||||
$commentLength = strlen($entry->getComment());
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
self::CENTRAL_FILE_HEADER_SIG,
|
||||
// version made by 2 bytes
|
||||
($entry->getPlatform() << 8) | 63,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$entry->getOffset()
|
||||
)
|
||||
);
|
||||
// file name (variable size)
|
||||
fwrite($outputStream, $entry->getName());
|
||||
if (0 < $extraSize) {
|
||||
// extra field (variable size)
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
if (0 < $commentLength) {
|
||||
// file comment (variable size)
|
||||
fwrite($outputStream, $entry->getComment());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function release()
|
||||
{
|
||||
unset($this->entries);
|
||||
unset($this->modifiedEntries);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->release();
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,6 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Mapper\OffsetPositionMapper;
|
||||
use PhpZip\Mapper\PositionMapper;
|
||||
use PhpZip\Util\PackUtil;
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* Read End of Central Directory
|
||||
@@ -77,172 +72,26 @@ class EndOfCentralDirectory
|
||||
* @var string|null The archive comment.
|
||||
*/
|
||||
private $comment;
|
||||
/**
|
||||
* @var int The number of bytes in the preamble of this ZIP file.
|
||||
*/
|
||||
private $preamble;
|
||||
/**
|
||||
* @var int The number of bytes in the postamble of this ZIP file.
|
||||
*/
|
||||
private $postamble;
|
||||
/**
|
||||
* @var PositionMapper Maps offsets specified in the ZIP file to real offsets in the file.
|
||||
*/
|
||||
private $mapper;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $centralDirectoryEntriesSize;
|
||||
private $entryCount;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64 = false;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $newComment;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $modified;
|
||||
|
||||
/**
|
||||
* EndOfCentralDirectory constructor.
|
||||
* @param int $entryCount
|
||||
* @param null|string $comment
|
||||
* @param bool $zip64
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct($entryCount, $comment, $zip64 = false)
|
||||
{
|
||||
$this->mapper = new PositionMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the file pointer at the first Central File Header.
|
||||
* Performs some means to check that this is really a ZIP file.
|
||||
*
|
||||
* @param resource $inputStream
|
||||
* @throws ZipException If the file is not compatible to the ZIP File
|
||||
* Format Specification.
|
||||
*/
|
||||
public function findCentralDirectory($inputStream)
|
||||
{
|
||||
// Search for End of central directory record.
|
||||
$stats = fstat($inputStream);
|
||||
$size = $stats['size'];
|
||||
$max = $size - self::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
|
||||
$min = $max >= 0xffff ? $max - 0xffff : 0;
|
||||
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
|
||||
fseek($inputStream, $endOfCentralDirRecordPos, SEEK_SET);
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
if (self::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($inputStream, 4))[1])
|
||||
continue;
|
||||
|
||||
// number of this disk - 2 bytes
|
||||
// number of the disk with the start of the
|
||||
// central directory - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory on this disk - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory - 2 bytes
|
||||
// size of the central directory - 4 bytes
|
||||
// offset of start of central directory with
|
||||
// respect to the starting disk number - 4 bytes
|
||||
// ZIP file comment length - 2 bytes
|
||||
$data = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
fread($inputStream, 18)
|
||||
);
|
||||
|
||||
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
|
||||
throw new ZipException(
|
||||
"ZIP file spanning/splitting is not supported!"
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable size)
|
||||
if (0 < $data['commentLength']) {
|
||||
$this->comment = fread($inputStream, $data['commentLength']);
|
||||
}
|
||||
$this->preamble = $endOfCentralDirRecordPos;
|
||||
$this->postamble = $size - ftell($inputStream);
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator.
|
||||
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
|
||||
|
||||
fseek($inputStream, $endOfCentralDirLocatorPos, SEEK_SET);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if (
|
||||
0 > $endOfCentralDirLocatorPos ||
|
||||
ftell($inputStream) === $size ||
|
||||
self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($inputStream, 4))[1]
|
||||
) {
|
||||
// Seek and check first CFH, probably requiring an offset mapper.
|
||||
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
|
||||
fseek($inputStream, $offset, SEEK_SET);
|
||||
$offset -= $data['cdPos'];
|
||||
if (0 !== $offset) {
|
||||
$this->mapper = new OffsetPositionMapper($offset);
|
||||
}
|
||||
$this->centralDirectoryEntriesSize = $data['cdEntries'];
|
||||
return;
|
||||
}
|
||||
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($inputStream, 4))[1];
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// total number of disks 4 bytes
|
||||
$totalDisks = unpack('V', fread($inputStream, 4))[1];
|
||||
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
fseek($inputStream, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
$zip64EndOfCentralDirSig = unpack('V', fread($inputStream, 4))[1];
|
||||
if (self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
|
||||
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
|
||||
}
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
fseek($inputStream, 12, SEEK_CUR);
|
||||
// number of this disk 4 bytes
|
||||
$diskNo = unpack('V', fread($inputStream, 4))[1];
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
$cdDiskNo = unpack('V', fread($inputStream, 4))[1];
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntries = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
|
||||
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
|
||||
}
|
||||
// size of the central directory 8 bytes
|
||||
fseek($inputStream, 8, SEEK_CUR);
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$cdPos = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// zip64 extensible data sector (variable size)
|
||||
fseek($inputStream, $cdPos, SEEK_SET);
|
||||
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
|
||||
$this->centralDirectoryEntriesSize = $cdEntries;
|
||||
$this->zip64 = true;
|
||||
return;
|
||||
}
|
||||
// Start recovering file entries from min.
|
||||
$this->preamble = $min;
|
||||
$this->postamble = $size - $min;
|
||||
$this->centralDirectoryEntriesSize = 0;
|
||||
$this->entryCount = $entryCount;
|
||||
$this->comment = $comment;
|
||||
$this->zip64 = $zip64;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,9 +105,9 @@ class EndOfCentralDirectory
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCentralDirectoryEntriesSize()
|
||||
public function getEntryCount()
|
||||
{
|
||||
return $this->centralDirectoryEntriesSize;
|
||||
return $this->entryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,152 +117,4 @@ class EndOfCentralDirectory
|
||||
{
|
||||
return $this->zip64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPreamble()
|
||||
{
|
||||
return $this->preamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPostamble()
|
||||
{
|
||||
return $this->postamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PositionMapper
|
||||
*/
|
||||
public function getMapper()
|
||||
{
|
||||
return $this->mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $preamble
|
||||
*/
|
||||
public function setPreamble($preamble)
|
||||
{
|
||||
$this->preamble = $preamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set archive comment
|
||||
* @param string|null $comment
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setComment($comment = null)
|
||||
{
|
||||
if (null !== $comment && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
$this->modified = $comment !== $this->comment;
|
||||
$this->newComment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write end of central directory.
|
||||
*
|
||||
* @param resource $outputStream Output stream
|
||||
* @param int $centralDirectoryEntries Size entries
|
||||
* @param int $centralDirectoryOffset Offset central directory
|
||||
*/
|
||||
public function writeEndOfCentralDirectory($outputStream, $centralDirectoryEntries, $centralDirectoryOffset)
|
||||
{
|
||||
$position = ftell($outputStream);
|
||||
$centralDirectorySize = $position - $centralDirectoryOffset;
|
||||
$centralDirectoryEntriesZip64 = $centralDirectoryEntries > 0xffff;
|
||||
$centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff;
|
||||
$centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff;
|
||||
$centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntries;
|
||||
$centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize;
|
||||
$centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset;
|
||||
$zip64 // ZIP64 extensions?
|
||||
= $centralDirectoryEntriesZip64
|
||||
|| $centralDirectorySizeZip64
|
||||
|| $centralDirectoryOffsetZip64;
|
||||
if ($zip64) {
|
||||
// relative offset of the zip64 end of central directory record
|
||||
$zip64EndOfCentralDirectoryOffset = $position;
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
fwrite($outputStream, pack('V', self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG));
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE(self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12));
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// due to potential use of BZIP2 compression
|
||||
// number of this disk 4 bytes
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
fwrite($outputStream, pack('vvVV', 63, 46, 0, 0));
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryEntries));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryEntries));
|
||||
// size of the central directory 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectorySize));
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryOffset));
|
||||
// zip64 extensible data sector (variable size)
|
||||
//
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
fwrite($outputStream, pack('VV', self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0));
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
|
||||
// total number of disks 4 bytes
|
||||
fwrite($outputStream, pack('V', 1));
|
||||
}
|
||||
$comment = $this->modified ? $this->newComment : $this->comment;
|
||||
$commentLength = strlen($comment);
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack('VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
self::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 2 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// size of the central directory 4 bytes
|
||||
$centralDirectorySize32,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$centralDirectoryOffset32,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($outputStream, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
49
src/PhpZip/Model/Entry/OutputOffsetEntry.php
Normal file
49
src/PhpZip/Model/Entry/OutputOffsetEntry.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Entry to write to the central directory.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class OutputOffsetEntry
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offset;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* @param int $pos
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct($pos, ZipEntry $entry)
|
||||
{
|
||||
$this->offset = $pos;
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function getEntry()
|
||||
{
|
||||
return $this->entry;
|
||||
}
|
||||
}
|
@@ -4,15 +4,14 @@ namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\DefaultExtraField;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Extra\ExtraFields;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\CentralDirectory;
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\DateTimeConverter;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\ZipFile;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Abstract ZIP entry.
|
||||
@@ -23,16 +22,10 @@ use PhpZip\ZipFile;
|
||||
*/
|
||||
abstract class ZipAbstractEntry implements ZipEntry
|
||||
{
|
||||
/**
|
||||
* @var CentralDirectory
|
||||
*/
|
||||
private $centralDirectory;
|
||||
|
||||
/**
|
||||
* @var int Bit flags for init state.
|
||||
*/
|
||||
private $init;
|
||||
|
||||
/**
|
||||
* @var string Entry name (filename in archive)
|
||||
*/
|
||||
@@ -45,14 +38,14 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* @var int
|
||||
*/
|
||||
private $versionNeededToExtract = 20;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $general;
|
||||
/**
|
||||
* @var int Compression method
|
||||
*/
|
||||
private $method;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $general;
|
||||
/**
|
||||
* @var int Dos time
|
||||
*/
|
||||
@@ -78,13 +71,13 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
private $offset = self::UNKNOWN;
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID [Integer] to Extra Field [ExtraField].
|
||||
* Collections of Extra Fields.
|
||||
* Keys from Header ID [int] and value Extra Field [ExtraField].
|
||||
* Should be null or may be empty if no Extra Fields are used.
|
||||
*
|
||||
* @var ExtraFields
|
||||
* @var ExtraFieldsCollection
|
||||
*/
|
||||
private $fields;
|
||||
private $extraFieldsCollection;
|
||||
/**
|
||||
* @var string Comment field.
|
||||
*/
|
||||
@@ -95,55 +88,48 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
private $password;
|
||||
/**
|
||||
* Encryption method.
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
|
||||
private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
private $compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
* ZipAbstractEntry constructor.
|
||||
*/
|
||||
private function isInit($mask)
|
||||
public function __construct()
|
||||
{
|
||||
return 0 !== ($this->init & $mask);
|
||||
$this->extraFieldsCollection = new ExtraFieldsCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @param bool $init
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
private function setInit($mask, $init)
|
||||
public function setEntry(ZipEntry $entry)
|
||||
{
|
||||
if ($init) {
|
||||
$this->init |= $mask;
|
||||
} else {
|
||||
$this->init &= ~$mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CentralDirectory
|
||||
*/
|
||||
public function getCentralDirectory()
|
||||
{
|
||||
return $this->centralDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CentralDirectory $centralDirectory
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCentralDirectory(CentralDirectory $centralDirectory)
|
||||
{
|
||||
$this->centralDirectory = $centralDirectory;
|
||||
return $this;
|
||||
$this->setName($entry->getName());
|
||||
$this->setPlatform($entry->getPlatform());
|
||||
$this->setVersionNeededToExtract($entry->getVersionNeededToExtract());
|
||||
$this->setMethod($entry->getMethod());
|
||||
$this->setGeneralPurposeBitFlags($entry->getGeneralPurposeBitFlags());
|
||||
$this->setDosTime($entry->getDosTime());
|
||||
$this->setCrc($entry->getCrc());
|
||||
$this->setCompressedSize($entry->getCompressedSize());
|
||||
$this->setSize($entry->getSize());
|
||||
$this->setExternalAttributes($entry->getExternalAttributes());
|
||||
$this->setOffset($entry->getOffset());
|
||||
$this->setExtra($entry->getExtra());
|
||||
$this->setComment($entry->getComment());
|
||||
$this->setPassword($entry->getPassword());
|
||||
$this->setEncryptionMethod($entry->getEncryptionMethod());
|
||||
$this->setCompressionLevel($entry->getCompressionLevel());
|
||||
$this->setEncrypted($entry->isEncrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,6 +160,23 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit)
|
||||
{
|
||||
if ($bit) {
|
||||
$this->general |= $mask;
|
||||
} else {
|
||||
$this->general &= ~$mask;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*/
|
||||
@@ -204,6 +207,28 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
protected function isInit($mask)
|
||||
{
|
||||
return 0 !== ($this->init & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @param bool $init
|
||||
*/
|
||||
protected function setInit($mask, $init)
|
||||
{
|
||||
if ($init) {
|
||||
$this->init |= $mask;
|
||||
} else {
|
||||
$this->init &= ~$mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
@@ -321,21 +346,9 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->name[strlen($this->name) - 1] === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return bool
|
||||
* @return int
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags()
|
||||
{
|
||||
@@ -355,33 +368,19 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
throw new ZipException('general out of range');
|
||||
}
|
||||
$this->general = $general;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask)
|
||||
{
|
||||
return 0 !== ($this->general & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit)
|
||||
{
|
||||
if ($bit)
|
||||
$this->general |= $mask;
|
||||
else
|
||||
$this->general &= ~$mask;
|
||||
if ($this->method === ZipFileInterface::METHOD_DEFLATED) {
|
||||
$bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1);
|
||||
$bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2);
|
||||
if ($bit1 && !$bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_BEST_COMPRESSION;
|
||||
} elseif (!$bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_FAST;
|
||||
} elseif ($bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_SUPER_FAST;
|
||||
} else {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -395,6 +394,17 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return $this->getGeneralPurposeBitFlag(self::GPBF_ENCRYPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask)
|
||||
{
|
||||
return 0 !== ($this->general & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
@@ -404,17 +414,16 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
public function clearEncryption()
|
||||
{
|
||||
$this->setEncrypted(false);
|
||||
if (null !== $this->fields) {
|
||||
$field = $this->fields->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$this->removeExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
}
|
||||
$headerId = WinZipAesEntryExtraField::getHeaderId();
|
||||
if (isset($this->extraFieldsCollection[$headerId])) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->extraFieldsCollection[$headerId];
|
||||
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
|
||||
$this->setMethod(null === $field ? self::UNKNOWN : $field->getMethod());
|
||||
}
|
||||
unset($this->extraFieldsCollection[$headerId]);
|
||||
}
|
||||
$this->password = null;
|
||||
return $this;
|
||||
@@ -428,6 +437,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted)
|
||||
{
|
||||
$encrypted = (bool)$encrypted;
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
|
||||
return $this;
|
||||
}
|
||||
@@ -451,6 +461,10 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
if (self::UNKNOWN === $method) {
|
||||
$this->method = $method;
|
||||
return $this;
|
||||
}
|
||||
if (0x0000 > $method || $method > 0xffff) {
|
||||
throw new ZipException('method out of range');
|
||||
}
|
||||
@@ -458,21 +472,15 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
case self::METHOD_WINZIP_AES:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
break;
|
||||
|
||||
case ZipFile::METHOD_STORED:
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
break;
|
||||
|
||||
case self::UNKNOWN:
|
||||
$this->method = ZipFile::METHOD_STORED;
|
||||
$this->setInit(self::BIT_METHOD, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($this->name . " (unsupported compression method $method)");
|
||||
}
|
||||
@@ -492,24 +500,6 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return DateTimeConverter::toUnixTimestamp($this->getDosTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp)
|
||||
{
|
||||
$known = self::UNKNOWN != $unixTimestamp;
|
||||
if ($known) {
|
||||
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
|
||||
} else {
|
||||
$this->dosTime = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_DATE_TIME, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
*
|
||||
@@ -535,6 +525,24 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
$this->setInit(self::BIT_DATE_TIME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp)
|
||||
{
|
||||
$known = self::UNKNOWN != $unixTimestamp;
|
||||
if ($known) {
|
||||
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
|
||||
} else {
|
||||
$this->dosTime = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_DATE_TIME, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
@@ -572,123 +580,43 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extra field from header id.
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function getExtraField($headerId)
|
||||
{
|
||||
return $this->fields === null ? null : $this->fields->get($headerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra field.
|
||||
*
|
||||
* @param ExtraField $field
|
||||
* @return ExtraField
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function addExtraField($field)
|
||||
{
|
||||
if (null === $field) {
|
||||
throw new ZipException("extra field null");
|
||||
}
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
return $this->fields->add($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return exists extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtraField($headerId)
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->fields === null ? false : $this->fields->has($headerId);
|
||||
return StringUtil::endsWith($this->name, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
* @return ExtraFieldsCollection
|
||||
*/
|
||||
public function removeExtraField($headerId)
|
||||
public function &getExtraFieldsCollection()
|
||||
{
|
||||
return null !== $this->fields ? $this->fields->remove($headerId) : null;
|
||||
return $this->extraFieldsCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
*
|
||||
* @return string A new byte array holding the serialized Extra Fields.
|
||||
* null is never returned.
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
return $this->getExtraFields(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $zip64
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function getExtraFields($zip64)
|
||||
public function getExtra()
|
||||
{
|
||||
if ($zip64) {
|
||||
$field = $this->composeZip64ExtraField();
|
||||
if (null !== $field) {
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
$this->fields->add($field);
|
||||
}
|
||||
} else {
|
||||
assert(null === $this->fields || null === $this->fields->get(ExtraField::ZIP64_HEADER_ID));
|
||||
$extraData = '';
|
||||
foreach ($this->getExtraFieldsCollection() as $extraField) {
|
||||
$data = $extraField->serialize();
|
||||
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
|
||||
$extraData .= $data;
|
||||
}
|
||||
return null === $this->fields ? null : $this->fields->getExtra();
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a ZIP64 Extended Information Extra Field from the properties
|
||||
* of this entry.
|
||||
* If no ZIP64 Extended Information Extra Field is required it is removed
|
||||
* from the collection of Extra Fields.
|
||||
*
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
private function composeZip64ExtraField()
|
||||
{
|
||||
$handle = fopen('php://memory', 'r+b');
|
||||
// Write out Uncompressed Size.
|
||||
$size = $this->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
fwrite($handle, PackUtil::packLongLE($size));
|
||||
$size = strlen($extraData);
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
|
||||
}
|
||||
// Write out Compressed Size.
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
fwrite($handle, PackUtil::packLongLE($compressedSize));
|
||||
}
|
||||
// Write out Relative Header Offset.
|
||||
$offset = $this->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
fwrite($handle, PackUtil::packLongLE($offset));
|
||||
}
|
||||
// Create ZIP64 Extended Information Extra Field from serialized data.
|
||||
$field = null;
|
||||
if (ftell($handle) > 0) {
|
||||
$field = new DefaultExtraField(ExtraField::ZIP64_HEADER_ID);
|
||||
$field->readFrom($handle, 0, ftell($handle));
|
||||
} else {
|
||||
$field = null;
|
||||
}
|
||||
return $field;
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -701,99 +629,31 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
* @return ZipEntry
|
||||
* or do not conform to the ZIP File Format Specification
|
||||
*/
|
||||
public function setExtra($data)
|
||||
{
|
||||
$this->extraFieldsCollection = new ExtraFieldsCollection();
|
||||
if (null !== $data) {
|
||||
$length = strlen($data);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new ZipException("Extra Fields too large");
|
||||
$extraLength = strlen($data);
|
||||
if (0x0000 > $extraLength || $extraLength > 0xffff) {
|
||||
throw new ZipException("Extra Fields too large: " . $extraLength);
|
||||
}
|
||||
}
|
||||
if (null === $data || strlen($data) <= 0) {
|
||||
$this->fields = null;
|
||||
} else {
|
||||
$this->setExtraFields($data, false);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $zip64
|
||||
*/
|
||||
private function setExtraFields($data, $zip64)
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
$handle = fopen('php://memory', 'r+b');
|
||||
fwrite($handle, $data);
|
||||
rewind($handle);
|
||||
|
||||
$this->fields->readFrom($handle, 0, strlen($data));
|
||||
$result = false;
|
||||
if ($zip64) {
|
||||
$result = $this->parseZip64ExtraField();
|
||||
}
|
||||
if ($result) {
|
||||
$this->fields->remove(ExtraField::ZIP64_HEADER_ID);
|
||||
if ($this->fields->size() <= 0) {
|
||||
if (0 !== $this->fields->size()) {
|
||||
$this->fields = null;
|
||||
$pos = 0;
|
||||
$endPos = $extraLength;
|
||||
while ($pos < $endPos) {
|
||||
$unpack = unpack('vheaderId/vdataSize', substr($data, $pos, 4));
|
||||
$pos += 4;
|
||||
$headerId = (int)$unpack['headerId'];
|
||||
$dataSize = (int)$unpack['dataSize'];
|
||||
$extraField = ExtraFieldsFactory::create($headerId);
|
||||
if ($extraField instanceof Zip64ExtraField) {
|
||||
$extraField->setEntry($this);
|
||||
}
|
||||
$extraField->deserialize(substr($data, $pos, $dataSize));
|
||||
$pos += $dataSize;
|
||||
$this->extraFieldsCollection[$headerId] = $extraField;
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the properties of this entry from the ZIP64 Extended Information
|
||||
* Extra Field, if present.
|
||||
* The ZIP64 Extended Information Extra Field is not removed.
|
||||
*
|
||||
* @return bool
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function parseZip64ExtraField()
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
return false;
|
||||
}
|
||||
$ef = $this->fields->get(ExtraField::ZIP64_HEADER_ID);
|
||||
if (null === $ef) {
|
||||
return false;
|
||||
}
|
||||
$dataBlockHandle = $ef->getDataBlock();
|
||||
$off = 0;
|
||||
// Read in Uncompressed Size.
|
||||
$size = $this->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
assert(0xffffffff === $size);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Compressed Size.
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
assert(0xffffffff === $compressedSize);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setCompressedSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Relative Header Offset.
|
||||
$offset = $this->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
assert(0xffffffff, $offset);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setOffset(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
//$off += 8;
|
||||
}
|
||||
fclose($dataBlockHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,7 +663,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return null != $this->comment ? $this->comment : "";
|
||||
return null !== $this->comment ? $this->comment : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -883,7 +743,11 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
if (null !== $encryptionMethod) {
|
||||
$this->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
$this->setEncrypted(!empty($this->password));
|
||||
if (!empty($this->password)) {
|
||||
$this->setEncrypted(true);
|
||||
} else {
|
||||
$this->clearEncryption();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -895,6 +759,34 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
return $this->encryptionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
if (null !== $encryptionMethod) {
|
||||
if (
|
||||
ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL !== $encryptionMethod
|
||||
&& ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 !== $encryptionMethod
|
||||
&& ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 !== $encryptionMethod
|
||||
&& ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 !== $encryptionMethod
|
||||
) {
|
||||
throw new ZipException('Invalid encryption method');
|
||||
}
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
@@ -908,46 +800,23 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* @return ZipEntry
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION)
|
||||
{
|
||||
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
|
||||
if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
) {
|
||||
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
|
||||
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION);
|
||||
ZipFileInterface::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFileInterface::LEVEL_BEST_COMPRESSION);
|
||||
}
|
||||
$this->compressionLevel = $compressionLevel;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
if (
|
||||
ZipFile::ENCRYPTION_METHOD_TRADITIONAL !== $encryptionMethod &&
|
||||
ZipFile::ENCRYPTION_METHOD_WINZIP_AES !== $encryptionMethod
|
||||
) {
|
||||
throw new ZipException('Invalid encryption method');
|
||||
}
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
$this->setEncrypted(true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
*/
|
||||
function __clone()
|
||||
public function __clone()
|
||||
{
|
||||
$this->fields = $this->fields !== null ? clone $this->fields : null;
|
||||
$this->extraFieldsCollection = clone $this->extraFieldsCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
63
src/PhpZip/Model/Entry/ZipChangesEntry.php
Normal file
63
src/PhpZip/Model/Entry/ZipChangesEntry.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Source Entry Changes
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipChangesEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry
|
||||
*/
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* ZipChangesEntry constructor.
|
||||
* @param ZipSourceEntry $entry
|
||||
*/
|
||||
public function __construct(ZipSourceEntry $entry)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entry = $entry;
|
||||
$this->setEntry($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isChangedContent()
|
||||
{
|
||||
return !(
|
||||
$this->getCompressionLevel() === $this->entry->getCompressionLevel() &&
|
||||
$this->getMethod() === $this->entry->getMethod() &&
|
||||
$this->isEncrypted() === $this->entry->isEncrypted() &&
|
||||
$this->getEncryptionMethod() === $this->entry->getEncryptionMethod() &&
|
||||
$this->getPassword() === $this->entry->getPassword()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->entry->getEntryContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipSourceEntry
|
||||
*/
|
||||
public function getSourceEntry()
|
||||
{
|
||||
return $this->entry;
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from empty dir.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewEmptyDirEntry extends ZipNewEntry
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,13 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\ZipFile;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Abstract class for new zip entry.
|
||||
@@ -16,12 +13,44 @@ use PhpZip\ZipFile;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ZipNewEntry extends ZipAbstractEntry
|
||||
class ZipNewEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Default compression level for bzip2
|
||||
* @var resource|string|null
|
||||
*/
|
||||
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
|
||||
protected $content;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipNewEntry constructor.
|
||||
* @param string|resource|null $content
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($content = null)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($content !== null && !is_string($content) && !is_resource($content)) {
|
||||
throw new InvalidArgumentException('invalid content');
|
||||
}
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (is_resource($this->content)) {
|
||||
return stream_get_contents($this->content, -1, 0);
|
||||
}
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
@@ -32,237 +61,29 @@ abstract class ZipNewEntry extends ZipAbstractEntry
|
||||
{
|
||||
$method = $this->getMethod();
|
||||
return self::METHOD_WINZIP_AES === $method ? 51 :
|
||||
(ZipFile::METHOD_BZIP2 === $method ? 46 :
|
||||
($this->isZip64ExtensionsRequired() ? 45 :
|
||||
(ZipFile::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
||||
(
|
||||
ZipFileInterface::METHOD_BZIP2 === $method ? 46 :
|
||||
(
|
||||
$this->isZip64ExtensionsRequired() ? 45 :
|
||||
(ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @throws ZipException
|
||||
* Clone extra fields
|
||||
*/
|
||||
public function writeEntry($outputStream)
|
||||
public function __clone()
|
||||
{
|
||||
$nameLength = strlen($this->getName());
|
||||
$size = $nameLength + strlen($this->getExtra()) + strlen($this->getComment());
|
||||
if (0xffff < $size) {
|
||||
throw new ZipException($this->getName()
|
||||
. " (the total size of "
|
||||
. $size
|
||||
. " bytes for the name, extra fields and comment exceeds the maximum size of "
|
||||
. 0xffff . " bytes)");
|
||||
}
|
||||
|
||||
if (self::UNKNOWN === $this->getPlatform()) {
|
||||
$this->setPlatform(self::PLATFORM_UNIX);
|
||||
}
|
||||
if (self::UNKNOWN === $this->getTime()) {
|
||||
$this->setTime(time());
|
||||
}
|
||||
$method = $this->getMethod();
|
||||
if (self::UNKNOWN === $method) {
|
||||
$this->setMethod($method = ZipFile::METHOD_DEFLATED);
|
||||
}
|
||||
$skipCrc = false;
|
||||
|
||||
$encrypted = $this->isEncrypted();
|
||||
$dd = $this->isDataDescriptorRequired();
|
||||
// Compose General Purpose Bit Flag.
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$utf8 = true;
|
||||
$general = ($encrypted ? self::GPBF_ENCRYPTED : 0)
|
||||
| ($dd ? self::GPBF_DATA_DESCRIPTOR : 0)
|
||||
| ($utf8 ? self::GPBF_UTF8 : 0);
|
||||
|
||||
$entryContent = $this->getEntryContent();
|
||||
|
||||
$this->setSize(strlen($entryContent));
|
||||
$this->setCrc(crc32($entryContent));
|
||||
|
||||
if ($encrypted && null === $this->getPassword()) {
|
||||
throw new ZipException("Can not password from entry " . $this->getName());
|
||||
}
|
||||
|
||||
if (
|
||||
$encrypted &&
|
||||
(
|
||||
self::METHOD_WINZIP_AES === $method ||
|
||||
$this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
)
|
||||
) {
|
||||
$field = null;
|
||||
$method = $this->getMethod();
|
||||
$keyStrength = 256; // bits
|
||||
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
|
||||
if (self::METHOD_WINZIP_AES === $method) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
$method = $field->getMethod();
|
||||
if (self::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize -= $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
}
|
||||
$this->setMethod($method);
|
||||
}
|
||||
}
|
||||
if (null === $field) {
|
||||
$field = new WinZipAesEntryExtraField();
|
||||
}
|
||||
$field->setKeyStrength($keyStrength);
|
||||
$field->setMethod($method);
|
||||
$size = $this->getSize();
|
||||
if (20 <= $size && ZipFile::METHOD_BZIP2 !== $method) {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||
} else {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||
$skipCrc = true;
|
||||
}
|
||||
$this->addExtraField($field);
|
||||
if (self::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize += $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
$this->setCompressedSize($compressedSize);
|
||||
}
|
||||
if ($skipCrc) {
|
||||
$this->setCrc(0);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
$entryContent = gzdeflate($entryContent, $this->getCompressionLevel());
|
||||
break;
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
$compressionLevel = $this->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
|
||||
self::LEVEL_DEFAULT_BZIP2_COMPRESSION :
|
||||
$this->getCompressionLevel();
|
||||
$entryContent = bzcompress($entryContent, $compressionLevel);
|
||||
if (is_int($entryContent)) {
|
||||
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ZipException($this->getName() . " (unsupported compression method " . $method . ")");
|
||||
}
|
||||
|
||||
if ($encrypted) {
|
||||
if ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES) {
|
||||
if ($skipCrc) {
|
||||
$this->setCrc(0);
|
||||
}
|
||||
$this->setMethod(self::METHOD_WINZIP_AES);
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$winZipAesEngine = new WinZipAesEngine($this, $field);
|
||||
$entryContent = $winZipAesEngine->encrypt($entryContent);
|
||||
} elseif ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
|
||||
$entryContent = $zipCryptoEngine->encrypt($entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
$compressedSize = strlen($entryContent);
|
||||
$this->setCompressedSize($compressedSize);
|
||||
|
||||
$offset = ftell($outputStream);
|
||||
|
||||
// Commit changes.
|
||||
$this->setGeneralPurposeBitFlags($general);
|
||||
$this->setOffset($offset);
|
||||
|
||||
$extra = $this->getExtra();
|
||||
|
||||
// zip align
|
||||
$padding = 0;
|
||||
$zipAlign = $this->getCentralDirectory()->getZipAlign();
|
||||
$extraLength = strlen($extra);
|
||||
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$zipAlign -
|
||||
(
|
||||
$offset +
|
||||
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
$nameLength + $extraLength
|
||||
) % $zipAlign
|
||||
) % $zipAlign;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
self::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$this->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$general,
|
||||
// compression method 2 bytes
|
||||
$this->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$this->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $this->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $this->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $this->getSize(),
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
)
|
||||
);
|
||||
fwrite($outputStream, $this->getName());
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($outputStream, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
if (null !== $entryContent) {
|
||||
fwrite($outputStream, $entryContent);
|
||||
}
|
||||
|
||||
assert(self::UNKNOWN !== $this->getCrc());
|
||||
assert(self::UNKNOWN !== $this->getSize());
|
||||
if ($this->getGeneralPurposeBitFlag(self::GPBF_DATA_DESCRIPTOR)) {
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
// crc-32 4 bytes
|
||||
fwrite($outputStream, pack('VV', self::DATA_DESCRIPTOR_SIG, $this->getCrc()));
|
||||
// compressed size 4 or 8 bytes
|
||||
// uncompressed size 4 or 8 bytes
|
||||
if ($this->isZip64ExtensionsRequired()) {
|
||||
fwrite($outputStream, PackUtil::packLongLE($compressedSize));
|
||||
fwrite($outputStream, PackUtil::packLongLE($this->getSize()));
|
||||
} else {
|
||||
fwrite($outputStream, pack('VV', $this->getCompressedSize(), $this->getSize()));
|
||||
}
|
||||
} elseif ($this->getCompressedSize() != $compressedSize) {
|
||||
throw new ZipException($this->getName()
|
||||
. " (expected compressed entry size of "
|
||||
. $this->getCompressedSize() . " bytes, but is actually " . $compressedSize . " bytes)");
|
||||
}
|
||||
$this->clone = true;
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && null !== $this->content && is_resource($this->content)) {
|
||||
fclose($this->content);
|
||||
$this->content = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from stream.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewStreamEntry extends ZipNewEntry
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* ZipNewStreamEntry constructor.
|
||||
* @param resource $stream
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($stream)
|
||||
{
|
||||
if (!is_resource($stream)) {
|
||||
throw new InvalidArgumentException('stream is not resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return stream_get_contents($this->stream, -1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release stream resource.
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if (null !== $this->stream) {
|
||||
fclose($this->stream);
|
||||
$this->stream = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from string.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewStringEntry extends ZipNewEntry
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $entryContent;
|
||||
|
||||
/**
|
||||
* ZipNewStringEntry constructor.
|
||||
* @param string $entryContent
|
||||
*/
|
||||
public function __construct($entryContent)
|
||||
{
|
||||
$this->entryContent = $entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->entryContent;
|
||||
}
|
||||
}
|
@@ -1,327 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethod;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\CentralDirectory;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* This class is used to represent a ZIP file entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipReadEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Max size cached content in memory.
|
||||
*/
|
||||
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 3145728; // 3 mb
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $inputStream;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $charset;
|
||||
/**
|
||||
* @var string|resource Cached entry content.
|
||||
*/
|
||||
private $entryContent;
|
||||
|
||||
/**
|
||||
* ZipFileEntry constructor.
|
||||
* @param $inputStream
|
||||
*/
|
||||
public function __construct($inputStream)
|
||||
{
|
||||
$this->inputStream = $inputStream;
|
||||
$this->readZipEntry($inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inputStream
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function readZipEntry($inputStream)
|
||||
{
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
$fileHeaderSig = unpack('V', fread($inputStream, 4))[1];
|
||||
if (CentralDirectory::CENTRAL_FILE_HEADER_SIG !== $fileHeaderSig) {
|
||||
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
|
||||
}
|
||||
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// general purpose bit flag 2 bytes
|
||||
// compression method 2 bytes
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
// crc-32 4 bytes
|
||||
// compressed size 4 bytes
|
||||
// uncompressed size 4 bytes
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
// file comment length 2 bytes
|
||||
// disk number start 2 bytes
|
||||
// internal file attributes 2 bytes
|
||||
// external file attributes 4 bytes
|
||||
// relative offset of local header 4 bytes
|
||||
$data = unpack(
|
||||
'vversionMadeBy/vversionNeededToExtract/vgpbf/vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
|
||||
'VrawSize/vfileLength/vextraLength/vcommentLength/VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
|
||||
fread($inputStream, 42)
|
||||
);
|
||||
|
||||
$utf8 = 0 !== ($data['gpbf'] & self::GPBF_UTF8);
|
||||
if ($utf8) {
|
||||
$this->charset = "UTF-8";
|
||||
}
|
||||
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$name = fread($inputStream, $data['fileLength']);
|
||||
|
||||
$this->setName($name);
|
||||
$this->setVersionNeededToExtract($data['versionNeededToExtract']);
|
||||
$this->setPlatform($data['versionMadeBy'] >> 8);
|
||||
$this->setGeneralPurposeBitFlags($data['gpbf']);
|
||||
$this->setMethod($data['rawMethod']);
|
||||
$this->setDosTime($data['rawTime']);
|
||||
$this->setCrc($data['rawCrc']);
|
||||
$this->setCompressedSize($data['rawCompressedSize']);
|
||||
$this->setSize($data['rawSize']);
|
||||
$this->setExternalAttributes($data['rawExternalAttributes']);
|
||||
$this->setOffset($data['lfhOff']); // must be unmapped!
|
||||
if (0 < $data['extraLength']) {
|
||||
$this->setExtra(fread($inputStream, $data['extraLength']));
|
||||
}
|
||||
if (0 < $data['commentLength']) {
|
||||
$this->setComment(fread($inputStream, $data['commentLength']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (null === $this->entryContent) {
|
||||
if ($this->isDirectory()) {
|
||||
$this->entryContent = null;
|
||||
return $this->entryContent;
|
||||
}
|
||||
$isEncrypted = $this->isEncrypted();
|
||||
$password = $this->getPassword();
|
||||
if ($isEncrypted && empty($password)) {
|
||||
throw new ZipException("Not set password");
|
||||
}
|
||||
|
||||
$pos = $this->getOffset();
|
||||
assert(self::UNKNOWN !== $pos);
|
||||
$startPos = $pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
|
||||
fseek($this->inputStream, $startPos);
|
||||
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
if (self::LOCAL_FILE_HEADER_SIG !== unpack('V', fread($this->inputStream, 4))[1]) {
|
||||
throw new ZipException($this->getName() . " (expected Local File Header)");
|
||||
}
|
||||
fseek($this->inputStream, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->inputStream, 4));
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
|
||||
|
||||
assert(self::UNKNOWN !== $this->getCrc());
|
||||
|
||||
$method = $this->getMethod();
|
||||
|
||||
fseek($this->inputStream, $pos);
|
||||
|
||||
// Get raw entry content
|
||||
$content = fread($this->inputStream, $this->getCompressedSize());
|
||||
|
||||
// Strong Encryption Specification - WinZip AES
|
||||
if ($this->isEncrypted()) {
|
||||
if (self::METHOD_WINZIP_AES === $method) {
|
||||
$winZipAesEngine = new WinZipAesEngine($this);
|
||||
$content = $winZipAesEngine->decrypt($content);
|
||||
// Disable redundant CRC-32 check.
|
||||
$isEncrypted = false;
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$method = $field->getMethod();
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
} else {
|
||||
// Traditional PKWARE Decryption
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
|
||||
$content = $zipCryptoEngine->decrypt($content);
|
||||
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
}
|
||||
}
|
||||
if ($isEncrypted) {
|
||||
// Check CRC32 in the Local File Header or Data Descriptor.
|
||||
$localCrc = null;
|
||||
if ($this->getGeneralPurposeBitFlag(self::GPBF_DATA_DESCRIPTOR)) {
|
||||
// The CRC32 is in the Data Descriptor after the compressed size.
|
||||
// Note the Data Descriptor's Signature is optional:
|
||||
// All newer apps should write it (and so does TrueVFS),
|
||||
// but older apps might not.
|
||||
fseek($this->inputStream, $pos + $this->getCompressedSize());
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
if (self::DATA_DESCRIPTOR_SIG === $localCrc) {
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
}
|
||||
} else {
|
||||
fseek($this->inputStream, $startPos + 14);
|
||||
// The CRC32 in the Local File Header.
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
}
|
||||
if ($this->getCrc() !== $localCrc) {
|
||||
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
$content = gzinflate($content);
|
||||
break;
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
if (!extension_loaded('bz2')) {
|
||||
throw new ZipException('Extension bzip2 not install');
|
||||
}
|
||||
$content = bzdecompress($content);
|
||||
break;
|
||||
default:
|
||||
throw new ZipUnsupportMethod($this->getName()
|
||||
. " (compression method "
|
||||
. $method
|
||||
. " is not supported)");
|
||||
}
|
||||
if ($isEncrypted) {
|
||||
$localCrc = crc32($content);
|
||||
if ($this->getCrc() !== $localCrc) {
|
||||
if ($this->isEncrypted()) {
|
||||
throw new ZipCryptoException("Wrong password");
|
||||
}
|
||||
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
|
||||
$this->entryContent = $content;
|
||||
} else {
|
||||
$this->entryContent = fopen('php://temp', 'rb');
|
||||
fwrite($this->entryContent, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
if (is_resource($this->entryContent)) {
|
||||
return stream_get_contents($this->entryContent, -1, 0);
|
||||
}
|
||||
return $this->entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
*/
|
||||
public function writeEntry($outputStream)
|
||||
{
|
||||
$pos = $this->getOffset();
|
||||
assert(ZipEntry::UNKNOWN !== $pos);
|
||||
$pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS;
|
||||
|
||||
$this->setOffset(ftell($outputStream));
|
||||
// zip align
|
||||
$padding = 0;
|
||||
$zipAlign = $this->getCentralDirectory()->getZipAlign();
|
||||
$extra = $this->getExtra();
|
||||
$extraLength = strlen($extra);
|
||||
$nameLength = strlen($this->getName());
|
||||
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$zipAlign -
|
||||
($this->getOffset() + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength)
|
||||
% $zipAlign
|
||||
) % $zipAlign;
|
||||
}
|
||||
$dd = $this->isDataDescriptorRequired();
|
||||
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
self::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$this->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$this->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$this->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$this->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $this->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $this->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $this->getSize(),
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
)
|
||||
);
|
||||
fwrite($outputStream, $this->getName());
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($outputStream, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
fseek($this->inputStream, $pos);
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->inputStream, 4));
|
||||
fseek($this->inputStream, $data['fileLength'] + $data['extraLength'], SEEK_CUR);
|
||||
|
||||
$length = $this->getCompressedSize();
|
||||
if ($this->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
$length += 12;
|
||||
if ($this->isZip64ExtensionsRequired()) {
|
||||
$length += 8;
|
||||
}
|
||||
}
|
||||
stream_copy_to_stream($this->inputStream, $outputStream, $length);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
if (null !== $this->entryContent && is_resource($this->entryContent)) {
|
||||
fclose($this->entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
95
src/PhpZip/Model/Entry/ZipSourceEntry.php
Normal file
95
src/PhpZip/Model/Entry/ZipSourceEntry.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Stream\ZipInputStreamInterface;
|
||||
|
||||
/**
|
||||
* This class is used to represent a ZIP file entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipSourceEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Max size cached content in memory.
|
||||
*/
|
||||
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 524288; // 512 kb
|
||||
/**
|
||||
* @var ZipInputStreamInterface
|
||||
*/
|
||||
protected $inputStream;
|
||||
/**
|
||||
* @var string|resource Cached entry content.
|
||||
*/
|
||||
protected $entryContent;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $readPassword;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipSourceEntry constructor.
|
||||
* @param ZipInputStreamInterface $inputStream
|
||||
*/
|
||||
public function __construct(ZipInputStreamInterface $inputStream)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->inputStream = $inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipInputStreamInterface
|
||||
*/
|
||||
public function getInputStream()
|
||||
{
|
||||
return $this->inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (null === $this->entryContent) {
|
||||
$content = $this->inputStream->readEntryContent($this);
|
||||
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
|
||||
$this->entryContent = $content;
|
||||
} else {
|
||||
$this->entryContent = fopen('php://temp', 'rb');
|
||||
fwrite($this->entryContent, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
if (is_resource($this->entryContent)) {
|
||||
return stream_get_contents($this->entryContent, -1, 0);
|
||||
}
|
||||
return $this->entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->clone = true;
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && null !== $this->entryContent && is_resource($this->entryContent)) {
|
||||
fclose($this->entryContent);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* ZIP file entry.
|
||||
@@ -39,11 +41,21 @@ interface ZipEntry
|
||||
const METHOD_WINZIP_AES = 99;
|
||||
|
||||
/** General Purpose Bit Flag mask for encrypted data. */
|
||||
const GPBF_ENCRYPTED = 1;
|
||||
const GPBF_ENCRYPTED = 1; // 1 << 0
|
||||
// (For Methods 8 and 9 - Deflating)
|
||||
// Bit 2 Bit 1
|
||||
// 0 0 Normal compression
|
||||
// 0 1 Maximum compression
|
||||
// 1 0 Fast compression
|
||||
// 1 1 Super Fast compression
|
||||
const GPBF_COMPRESSION_FLAG1 = 2; // 1 << 1
|
||||
const GPBF_COMPRESSION_FLAG2 = 4; // 1 << 2
|
||||
/** General Purpose Bit Flag mask for data descriptor. */
|
||||
const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3;
|
||||
const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3
|
||||
/** General Purpose Bit Flag mask for strong encryption. */
|
||||
const GPBF_STRONG_ENCRYPTION = 64; // 1 << 6
|
||||
/** General Purpose Bit Flag mask for UTF-8. */
|
||||
const GPBF_UTF8 = 2048;
|
||||
const GPBF_UTF8 = 2048; // 1 << 11
|
||||
|
||||
/** Local File Header signature. */
|
||||
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
|
||||
@@ -76,19 +88,11 @@ interface ZipEntry
|
||||
* Compressed Size 4
|
||||
* Uncompressed Size 4
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26; // 1 << 11;
|
||||
|
||||
|
||||
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26;
|
||||
/**
|
||||
* @return CentralDirectory
|
||||
* Default compression level for bzip2
|
||||
*/
|
||||
public function getCentralDirectory();
|
||||
|
||||
/**
|
||||
* @param CentralDirectory $centralDirectory
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCentralDirectory(CentralDirectory $centralDirectory);
|
||||
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
|
||||
|
||||
/**
|
||||
* Returns the ZIP entry name.
|
||||
@@ -197,7 +201,7 @@ interface ZipEntry
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return bool
|
||||
* @return int
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags();
|
||||
|
||||
@@ -234,14 +238,6 @@ interface ZipEntry
|
||||
*/
|
||||
public function isEncrypted();
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function clearEncryption();
|
||||
|
||||
/**
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
@@ -250,6 +246,14 @@ interface ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted);
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function clearEncryption();
|
||||
|
||||
/**
|
||||
* Returns the compression method for this entry.
|
||||
*
|
||||
@@ -312,37 +316,9 @@ interface ZipEntry
|
||||
public function setExternalAttributes($externalAttributes);
|
||||
|
||||
/**
|
||||
* Return extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
* @return ExtraFieldsCollection
|
||||
*/
|
||||
public function getExtraField($headerId);
|
||||
|
||||
/**
|
||||
* Add extra field.
|
||||
*
|
||||
* @param ExtraField $field
|
||||
* @return ExtraField
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function addExtraField($field);
|
||||
|
||||
/**
|
||||
* Return exists extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtraField($headerId);
|
||||
|
||||
/**
|
||||
* Remove extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function removeExtraField($headerId);
|
||||
public function getExtraFieldsCollection();
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
@@ -362,8 +338,6 @@ interface ZipEntry
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
* @return ZipEntry
|
||||
* or do not conform to the ZIP File Format Specification
|
||||
*/
|
||||
public function setExtra($data);
|
||||
|
||||
@@ -425,8 +399,10 @@ interface ZipEntry
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
@@ -443,10 +419,13 @@ interface ZipEntry
|
||||
public function getEntryContent();
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @throws ZipException
|
||||
* @param int $compressionLevel
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function writeEntry($outputStream);
|
||||
}
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionLevel();
|
||||
}
|
||||
|
166
src/PhpZip/Model/ZipEntryMatcher.php
Normal file
166
src/PhpZip/Model/ZipEntryMatcher.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipEntryMatcher implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $matches = [];
|
||||
|
||||
/**
|
||||
* ZipEntryMatcher constructor.
|
||||
* @param ZipModel $zipModel
|
||||
*/
|
||||
public function __construct(ZipModel $zipModel)
|
||||
{
|
||||
$this->zipModel = $zipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $entries
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function add($entries)
|
||||
{
|
||||
$entries = (array)$entries;
|
||||
$entries = array_map(function ($entry) {
|
||||
return $entry instanceof ZipEntry ? $entry->getName() : $entry;
|
||||
}, $entries);
|
||||
$this->matches = array_unique(
|
||||
array_merge(
|
||||
$this->matches,
|
||||
array_keys(
|
||||
array_intersect_key(
|
||||
$this->zipModel->getEntries(),
|
||||
array_flip($entries)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexp
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function match($regexp)
|
||||
{
|
||||
array_walk($this->zipModel->getEntries(), function (
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
$entry,
|
||||
$entryName
|
||||
) use ($regexp) {
|
||||
if (preg_match($regexp, $entryName)) {
|
||||
$this->matches[] = $entryName;
|
||||
}
|
||||
});
|
||||
$this->matches = array_unique($this->matches);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$this->matches = array_keys($this->zipModel->getEntries());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callable function for all select entries.
|
||||
*
|
||||
* Callable function signature:
|
||||
* function(string $entryName){}
|
||||
*
|
||||
* @param callable $callable
|
||||
*/
|
||||
public function invoke(callable $callable)
|
||||
{
|
||||
if (!empty($this->matches)) {
|
||||
array_walk($this->matches, function ($entryName) use ($callable) {
|
||||
call_user_func($callable, $entryName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMatches()
|
||||
{
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$this->zipModel->deleteEntry($entry);
|
||||
});
|
||||
$this->matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $password
|
||||
* @param int|null $encryptionMethod
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
array_walk($this->matches, function ($entry) use ($password, $encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
array_walk($this->matches, function ($entry) use ($encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function disableEncryption()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry = $this->zipModel->getEntryForChanges($entry);
|
||||
$entry->clearEncryption();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->matches);
|
||||
}
|
||||
}
|
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Extra\NtfsExtraField;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
use PhpZip\ZipFile;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Zip info
|
||||
@@ -86,7 +87,8 @@ class ZipInfo
|
||||
];
|
||||
|
||||
private static $valuesCompressionMethod = [
|
||||
ZipFile::METHOD_STORED => 'no compression',
|
||||
ZipEntry::UNKNOWN => 'unknown',
|
||||
ZipFileInterface::METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
@@ -94,7 +96,7 @@ class ZipInfo
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
ZipFile::METHOD_DEFLATED => 'deflate',
|
||||
ZipFileInterface::METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
@@ -114,72 +116,71 @@ class ZipInfo
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
private $name;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $folder;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $encrypted;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $crc;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
private $methodName;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionMethod;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $platform;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $attributes;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $encryptionMethod;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $compressionLevel;
|
||||
|
||||
/**
|
||||
* ZipInfo constructor.
|
||||
@@ -192,16 +193,17 @@ class ZipInfo
|
||||
$atime = null;
|
||||
$ctime = null;
|
||||
|
||||
$field = $entry->getExtraField(NtfsExtraField::getHeaderId());
|
||||
$field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId());
|
||||
if (null !== $field && $field instanceof NtfsExtraField) {
|
||||
/**
|
||||
* @var NtfsExtraField $field
|
||||
*/
|
||||
$atime = $field->getAtime();
|
||||
$ctime = $field->getCtime();
|
||||
$mtime = $field->getMtime();
|
||||
}
|
||||
|
||||
$this->path = $entry->getName();
|
||||
$this->name = $entry->getName();
|
||||
$this->folder = $entry->isDirectory();
|
||||
$this->size = $entry->getSize();
|
||||
$this->compressedSize = $entry->getCompressedSize();
|
||||
@@ -209,18 +211,22 @@ class ZipInfo
|
||||
$this->ctime = $ctime;
|
||||
$this->atime = $atime;
|
||||
$this->encrypted = $entry->isEncrypted();
|
||||
$this->encryptionMethod = $entry->getEncryptionMethod();
|
||||
$this->comment = $entry->getComment();
|
||||
$this->crc = $entry->getCrc();
|
||||
$this->method = self::getMethodName($entry);
|
||||
$this->compressionMethod = self::getMethodId($entry);
|
||||
$this->methodName = self::getEntryMethodName($entry);
|
||||
$this->platform = self::getPlatformName($entry);
|
||||
$this->version = $entry->getVersionNeededToExtract();
|
||||
$this->compressionLevel = $entry->getCompressionLevel();
|
||||
|
||||
$attributes = str_repeat(" ", 12);
|
||||
$externalAttributes = $entry->getExternalAttributes();
|
||||
$xattr = (($externalAttributes >> 16) & 0xFFFF);
|
||||
switch ($entry->getPlatform()) {
|
||||
case self::MADE_BY_MS_DOS:
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::MADE_BY_WINDOWS_NTFS:
|
||||
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
|
||||
($xattr & 0700) !=
|
||||
@@ -237,11 +243,12 @@ class ZipInfo
|
||||
if ($xattr & 0x10) {
|
||||
$attributes[0] = 'd';
|
||||
$attributes[3] = 'x';
|
||||
} else
|
||||
} else {
|
||||
$attributes[0] = '-';
|
||||
if ($xattr & 0x08)
|
||||
}
|
||||
if ($xattr & 0x08) {
|
||||
$attributes[0] = 'V';
|
||||
else {
|
||||
} else {
|
||||
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
|
||||
$attributes[3] = 'x';
|
||||
@@ -250,6 +257,7 @@ class ZipInfo
|
||||
break;
|
||||
} /* else: fall through! */
|
||||
|
||||
// no break
|
||||
default: /* assume Unix-like */
|
||||
switch ($xattr & self::UNX_IFMT) {
|
||||
case self::UNX_IFDIR:
|
||||
@@ -284,33 +292,59 @@ class ZipInfo
|
||||
$attributes[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
|
||||
$attributes[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
|
||||
|
||||
if ($xattr & self::UNX_IXUSR)
|
||||
if ($xattr & self::UNX_IXUSR) {
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
else
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
|
||||
if ($xattr & self::UNX_IXGRP)
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
|
||||
else
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
|
||||
if ($xattr & self::UNX_IXOTH)
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
|
||||
else
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
|
||||
} else {
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-';
|
||||
} /* S==undefined */
|
||||
if ($xattr & self::UNX_IXGRP) {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x';
|
||||
} /* == UNX_ENFMT */
|
||||
else {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-';
|
||||
} /* SunOS 4.1.x */
|
||||
if ($xattr & self::UNX_IXOTH) {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x';
|
||||
} /* "sticky bit" */
|
||||
else {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-';
|
||||
} /* T==undefined */
|
||||
}
|
||||
$this->attributes = trim($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return int
|
||||
*/
|
||||
private static function getMethodId(ZipEntry $entry)
|
||||
{
|
||||
$method = $entry->getMethod();
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*/
|
||||
public static function getMethodName(ZipEntry $entry)
|
||||
private static function getEntryMethodName(ZipEntry $entry)
|
||||
{
|
||||
$return = '';
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
@@ -348,34 +382,20 @@ class ZipInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
public function toArray()
|
||||
public function getName()
|
||||
{
|
||||
return [
|
||||
'path' => $this->getPath(),
|
||||
'folder' => $this->isFolder(),
|
||||
'size' => $this->getSize(),
|
||||
'compressed_size' => $this->getCompressedSize(),
|
||||
'modified' => $this->getMtime(),
|
||||
'created' => $this->getCtime(),
|
||||
'accessed' => $this->getAtime(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'encrypted' => $this->isEncrypted(),
|
||||
'comment' => $this->getComment(),
|
||||
'crc' => $this->getCrc(),
|
||||
'method' => $this->getMethod(),
|
||||
'platform' => $this->getPlatform(),
|
||||
'version' => $this->getVersion()
|
||||
];
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getName()
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,6 +446,14 @@ class ZipInfo
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
@@ -452,10 +480,19 @@ class ZipInfo
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
return $this->getMethodName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethodName()
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,35 +512,76 @@ class ZipInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return int|null
|
||||
*/
|
||||
public function getAttributes()
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return $this->attributes;
|
||||
return $this->encryptionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCompressionLevel()
|
||||
{
|
||||
return $this->compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionMethod()
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getName(),
|
||||
'path' => $this->getName(), // deprecated
|
||||
'folder' => $this->isFolder(),
|
||||
'size' => $this->getSize(),
|
||||
'compressed_size' => $this->getCompressedSize(),
|
||||
'modified' => $this->getMtime(),
|
||||
'created' => $this->getCtime(),
|
||||
'accessed' => $this->getAtime(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'encrypted' => $this->isEncrypted(),
|
||||
'encryption_method' => $this->getEncryptionMethod(),
|
||||
'comment' => $this->getComment(),
|
||||
'crc' => $this->getCrc(),
|
||||
'method' => $this->getMethodName(), // deprecated
|
||||
'method_name' => $this->getMethodName(),
|
||||
'compression_method' => $this->getCompressionMethod(),
|
||||
'platform' => $this->getPlatform(),
|
||||
'version' => $this->getVersion()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function __toString()
|
||||
public function __toString()
|
||||
{
|
||||
return 'ZipInfo {'
|
||||
. 'Path="' . $this->getPath() . '", '
|
||||
. ($this->isFolder() ? 'Folder, ' : '')
|
||||
. 'Size=' . FilesUtil::humanSize($this->getSize())
|
||||
. ', Compressed size=' . FilesUtil::humanSize($this->getCompressedSize())
|
||||
. ', Modified time=' . date(DATE_W3C, $this->getMtime()) . ', '
|
||||
. ($this->getCtime() !== null ? 'Created time=' . date(DATE_W3C, $this->getCtime()) . ', ' : '')
|
||||
. ($this->getAtime() !== null ? 'Accessed time=' . date(DATE_W3C, $this->getAtime()) . ', ' : '')
|
||||
. ($this->isEncrypted() ? 'Encrypted, ' : '')
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
. 'Method="' . $this->getMethod() . '", '
|
||||
. 'Attributes="' . $this->getAttributes() . '", '
|
||||
. 'Platform="' . $this->getPlatform() . '", '
|
||||
. 'Version=' . $this->getVersion()
|
||||
. '}';
|
||||
return __CLASS__ . ' {'
|
||||
. 'Name="' . $this->getName() . '", '
|
||||
. ($this->isFolder() ? 'Folder, ' : '')
|
||||
. 'Size="' . FilesUtil::humanSize($this->getSize()).'"'
|
||||
. ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()).'"'
|
||||
. ', Modified time="' . date(DATE_W3C, $this->getMtime()) . '", '
|
||||
. ($this->getCtime() !== null ? 'Created time="' . date(DATE_W3C, $this->getCtime()) . '", ' : '')
|
||||
. ($this->getAtime() !== null ? 'Accessed time="' . date(DATE_W3C, $this->getAtime()) . '", ' : '')
|
||||
. ($this->isEncrypted() ? 'Encrypted, ' : '')
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
. 'Method name="' . $this->getMethodName() . '", '
|
||||
. 'Attributes="' . $this->getAttributes() . '", '
|
||||
. 'Platform="' . $this->getPlatform() . '", '
|
||||
. 'Version=' . $this->getVersion()
|
||||
. '}';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
341
src/PhpZip/Model/ZipModel.php
Normal file
341
src/PhpZip/Model/ZipModel.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipNotFoundEntry;
|
||||
use PhpZip\Model\Entry\ZipChangesEntry;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Zip Model
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipModel implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry[]
|
||||
*/
|
||||
protected $inputEntries = [];
|
||||
/**
|
||||
* @var ZipEntry[]
|
||||
*/
|
||||
protected $outEntries = [];
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $archiveComment;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $archiveCommentChanges;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $archiveCommentChanged = false;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $zipAlign;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64;
|
||||
|
||||
/**
|
||||
* @param ZipSourceEntry[] $entries
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
* @return ZipModel
|
||||
*/
|
||||
public static function newSourceModel(array $entries, EndOfCentralDirectory $endOfCentralDirectory)
|
||||
{
|
||||
$model = new self;
|
||||
$model->inputEntries = $entries;
|
||||
$model->outEntries = $entries;
|
||||
$model->archiveComment = $endOfCentralDirectory->getComment();
|
||||
$model->zip64 = $endOfCentralDirectory->isZip64();
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getArchiveComment()
|
||||
{
|
||||
if ($this->archiveCommentChanged) {
|
||||
return $this->archiveCommentChanges;
|
||||
}
|
||||
return $this->archiveComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setArchiveComment($comment)
|
||||
{
|
||||
if (null !== $comment && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
if ($comment !== $this->archiveComment) {
|
||||
$this->archiveCommentChanges = $comment;
|
||||
$this->archiveCommentChanged = true;
|
||||
} else {
|
||||
$this->archiveCommentChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a password for extracting files.
|
||||
*
|
||||
* @param null|string $password
|
||||
*/
|
||||
public function setReadPassword($password)
|
||||
{
|
||||
foreach ($this->inputEntries as $entry) {
|
||||
if ($entry->isEncrypted()) {
|
||||
$entry->setPassword($password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password)
|
||||
{
|
||||
if (!isset($this->inputEntries[$entryName])) {
|
||||
throw new ZipNotFoundEntry('Not found entry ' . $entryName);
|
||||
}
|
||||
if ($this->inputEntries[$entryName]->isEncrypted()) {
|
||||
$this->inputEntries[$entryName]->setPassword($password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getZipAlign()
|
||||
{
|
||||
return $this->zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $zipAlign
|
||||
*/
|
||||
public function setZipAlign($zipAlign)
|
||||
{
|
||||
$this->zipAlign = $zipAlign === null ? null : (int)$zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZipAlign()
|
||||
{
|
||||
return $this->zipAlign != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $writePassword
|
||||
*/
|
||||
public function setWritePassword($writePassword)
|
||||
{
|
||||
$this->matcher()->all()->setPassword($writePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove password
|
||||
*/
|
||||
public function removePassword()
|
||||
{
|
||||
$this->matcher()->all()->setPassword(null);
|
||||
}
|
||||
|
||||
public function removePasswordEntry($entryName)
|
||||
{
|
||||
$this->matcher()->add($entryName)->setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isArchiveCommentChanged()
|
||||
{
|
||||
return $this->archiveCommentChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $old
|
||||
* @param string|ZipEntry $new
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function renameEntry($old, $new)
|
||||
{
|
||||
$old = $old instanceof ZipEntry ? $old->getName() : (string)$old;
|
||||
$new = $new instanceof ZipEntry ? $new->getName() : (string)$new;
|
||||
|
||||
if (isset($this->outEntries[$new])) {
|
||||
throw new InvalidArgumentException("New entry name " . $new . ' is exists.');
|
||||
}
|
||||
|
||||
$entry = $this->getEntryForChanges($old);
|
||||
$entry->setName($new);
|
||||
$this->deleteEntry($old);
|
||||
$this->addEntry($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
* @return ZipChangesEntry|ZipEntry
|
||||
*/
|
||||
public function getEntryForChanges($entry)
|
||||
{
|
||||
$entry = $this->getEntry($entry);
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry = new ZipChangesEntry($entry);
|
||||
$this->addEntry($entry);
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
if (isset($this->outEntries[$entryName])) {
|
||||
return $this->outEntries[$entryName];
|
||||
}
|
||||
throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteEntry($entry)
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
|
||||
if (isset($this->outEntries[$entry])) {
|
||||
unset($this->outEntries[$entry]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function addEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->outEntries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries with changes.
|
||||
*
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
public function &getEntries()
|
||||
{
|
||||
return $this->outEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
return isset($this->outEntries[$entryName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->outEntries = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($this->outEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive
|
||||
*/
|
||||
public function unchangeAll()
|
||||
{
|
||||
$this->outEntries = $this->inputEntries;
|
||||
$this->unchangeArchiveComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
*/
|
||||
public function unchangeArchiveComment()
|
||||
{
|
||||
$this->archiveCommentChanges = null;
|
||||
$this->archiveCommentChanged = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
* @return bool
|
||||
*/
|
||||
public function unchangeEntry($entry)
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
|
||||
if (isset($this->outEntries[$entry]) && isset($this->inputEntries[$entry])) {
|
||||
$this->outEntries[$entry] = $this->inputEntries[$entry];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256)
|
||||
{
|
||||
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function matcher()
|
||||
{
|
||||
return new ZipEntryMatcher($this);
|
||||
}
|
||||
}
|
298
src/PhpZip/Stream/ResponseStream.php
Normal file
298
src/PhpZip/Stream/ResponseStream.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Implement PSR Message Stream
|
||||
*/
|
||||
class ResponseStream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $readWriteHash = [
|
||||
'read' => [
|
||||
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
|
||||
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
|
||||
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a+' => true,
|
||||
],
|
||||
'write' => [
|
||||
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
||||
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
|
||||
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
|
||||
],
|
||||
];
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $seekable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $readable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $writable;
|
||||
/**
|
||||
* @var array|mixed|null
|
||||
*/
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @param resource $stream Stream resource to wrap.
|
||||
* @throws \InvalidArgumentException if the stream is not a stream resource
|
||||
*/
|
||||
public function __construct($stream)
|
||||
{
|
||||
if (!is_resource($stream)) {
|
||||
throw new \InvalidArgumentException('Stream must be a resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
$this->seekable = $meta['seekable'];
|
||||
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
|
||||
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
|
||||
$this->uri = $this->getMetadata('uri');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream metadata as an associative array or retrieve a specific key.
|
||||
*
|
||||
* The keys returned are identical to the keys returned from PHP's
|
||||
* stream_get_meta_data() function.
|
||||
*
|
||||
* @link http://php.net/manual/en/function.stream-get-meta-data.php
|
||||
* @param string $key Specific metadata to retrieve.
|
||||
* @return array|mixed|null Returns an associative array if no key is
|
||||
* provided. Returns a specific key value if a key is provided and the
|
||||
* value is found, or null if the key is not found.
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return $key ? null : [];
|
||||
}
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
return isset($meta[$key]) ? $meta[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all data from the stream into a string, from the beginning to end.
|
||||
*
|
||||
* This method MUST attempt to seek to the beginning of the stream before
|
||||
* reading data and read the stream until the end is reached.
|
||||
*
|
||||
* Warning: This could attempt to load a large amount of data into memory.
|
||||
*
|
||||
* This method MUST NOT raise an exception in order to conform with PHP's
|
||||
* string casting operations.
|
||||
*
|
||||
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return '';
|
||||
}
|
||||
$this->rewind();
|
||||
return (string)stream_get_contents($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the beginning of the stream.
|
||||
*
|
||||
* If the stream is not seekable, this method will raise an exception;
|
||||
* otherwise, it will perform a seek(0).
|
||||
*
|
||||
* @see seek()
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @throws \RuntimeException on failure.
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->seekable && rewind($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the stream if known.
|
||||
*
|
||||
* @return int|null Returns the size in bytes if known, or null if unknown.
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
if ($this->size !== null) {
|
||||
return $this->size;
|
||||
}
|
||||
if (!$this->stream) {
|
||||
return null;
|
||||
}
|
||||
// Clear the stat cache if the stream has a URI
|
||||
if ($this->uri) {
|
||||
clearstatcache(true, $this->uri);
|
||||
}
|
||||
$stats = fstat($this->stream);
|
||||
if (isset($stats['size'])) {
|
||||
$this->size = $stats['size'];
|
||||
return $this->size;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position of the file read/write pointer
|
||||
*
|
||||
* @return int Position of the file pointer
|
||||
* @throws \RuntimeException on error.
|
||||
*/
|
||||
public function tell()
|
||||
{
|
||||
return $this->stream ? ftell($this->stream) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream is at the end of the stream.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
return !$this->stream || feof($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is seekable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSeekable()
|
||||
{
|
||||
return $this->seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a position in the stream.
|
||||
*
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @param int $offset Stream offset
|
||||
* @param int $whence Specifies how the cursor position will be calculated
|
||||
* based on the seek offset. Valid values are identical to the built-in
|
||||
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
|
||||
* offset bytes SEEK_CUR: Set position to current location plus offset
|
||||
* SEEK_END: Set position to end-of-stream plus offset.
|
||||
* @throws \RuntimeException on failure.
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
$this->seekable && fseek($this->stream, $offset, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is writable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $string The string that is to be written.
|
||||
* @return int Returns the number of bytes written to the stream.
|
||||
* @throws \RuntimeException on failure.
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
$this->size = null;
|
||||
return $this->writable ? fwrite($this->stream, $string) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is readable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the stream.
|
||||
*
|
||||
* @param int $length Read up to $length bytes from the object and return
|
||||
* them. Fewer than $length bytes may be returned if underlying stream
|
||||
* call returns fewer bytes.
|
||||
* @return string Returns the data read from the stream, or an empty string
|
||||
* if no bytes are available.
|
||||
* @throws \RuntimeException if an error occurs.
|
||||
*/
|
||||
public function read($length)
|
||||
{
|
||||
return $this->readable ? fread($this->stream, $length) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining contents in a string
|
||||
*
|
||||
* @return string
|
||||
* @throws \RuntimeException if unable to read or an error occurs while
|
||||
* reading.
|
||||
*/
|
||||
public function getContents()
|
||||
{
|
||||
return $this->stream ? stream_get_contents($this->stream) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream when the destructed
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and any underlying resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates any underlying resources from the stream.
|
||||
*
|
||||
* After the stream has been detached, the stream is in an unusable state.
|
||||
*
|
||||
* @return resource|null Underlying PHP stream, if any
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
$result = $this->stream;
|
||||
$this->stream = $this->size = $this->uri = null;
|
||||
$this->readable = $this->writable = $this->seekable = false;
|
||||
return $result;
|
||||
}
|
||||
}
|
532
src/PhpZip/Stream/ZipInputStream.php
Normal file
532
src/PhpZip/Stream/ZipInputStream.php
Normal file
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethod;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Mapper\OffsetPositionMapper;
|
||||
use PhpZip\Mapper\PositionMapper;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipInputStream implements ZipInputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $in;
|
||||
/**
|
||||
* @var PositionMapper
|
||||
*/
|
||||
protected $mapper;
|
||||
/**
|
||||
* @var int The number of bytes in the preamble of this ZIP file.
|
||||
*/
|
||||
protected $preamble = 0;
|
||||
/**
|
||||
* @var int The number of bytes in the postamble of this ZIP file.
|
||||
*/
|
||||
protected $postamble = 0;
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipInputStream constructor.
|
||||
* @param resource $in
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct($in)
|
||||
{
|
||||
if (!is_resource($in)) {
|
||||
throw new RuntimeException('$in must be resource');
|
||||
}
|
||||
$this->in = $in;
|
||||
$this->mapper = new PositionMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipModel
|
||||
*/
|
||||
public function readZip()
|
||||
{
|
||||
$this->checkZipFileSignature();
|
||||
$endOfCentralDirectory = $this->readEndOfCentralDirectory();
|
||||
$entries = $this->mountCentralDirectory($endOfCentralDirectory);
|
||||
$this->zipModel = ZipModel::newSourceModel($entries, $endOfCentralDirectory);
|
||||
return $this->zipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check zip file signature
|
||||
*
|
||||
* @throws ZipException if this not .ZIP file.
|
||||
*/
|
||||
protected function checkZipFileSignature()
|
||||
{
|
||||
rewind($this->in);
|
||||
// Constraint: A ZIP file must start with a Local File Header
|
||||
// or a (ZIP64) End Of Central Directory Record if it's empty.
|
||||
$signatureBytes = fread($this->in, 4);
|
||||
if (strlen($signatureBytes) < 4) {
|
||||
throw new ZipException("Invalid zip file.");
|
||||
}
|
||||
$signature = unpack('V', $signatureBytes)[1];
|
||||
if (
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
|
||||
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
) {
|
||||
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EndOfCentralDirectory
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function readEndOfCentralDirectory()
|
||||
{
|
||||
$comment = null;
|
||||
// Search for End of central directory record.
|
||||
$stats = fstat($this->in);
|
||||
$size = $stats['size'];
|
||||
$max = $size - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
|
||||
$min = $max >= 0xffff ? $max - 0xffff : 0;
|
||||
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
|
||||
fseek($this->in, $endOfCentralDirRecordPos, SEEK_SET);
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
if (EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($this->in, 4))[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// number of this disk - 2 bytes
|
||||
// number of the disk with the start of the
|
||||
// central directory - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory on this disk - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory - 2 bytes
|
||||
// size of the central directory - 4 bytes
|
||||
// offset of start of central directory with
|
||||
// respect to the starting disk number - 4 bytes
|
||||
// ZIP file comment length - 2 bytes
|
||||
$data = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
fread($this->in, 18)
|
||||
);
|
||||
|
||||
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
|
||||
throw new ZipException(
|
||||
"ZIP file spanning/splitting is not supported!"
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable size)
|
||||
if (0 < $data['commentLength']) {
|
||||
$comment = fread($this->in, $data['commentLength']);
|
||||
}
|
||||
$this->preamble = $endOfCentralDirRecordPos;
|
||||
$this->postamble = $size - ftell($this->in);
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator.
|
||||
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
|
||||
|
||||
fseek($this->in, $endOfCentralDirLocatorPos, SEEK_SET);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if (
|
||||
0 > $endOfCentralDirLocatorPos ||
|
||||
ftell($this->in) === $size ||
|
||||
EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($this->in, 4))[1]
|
||||
) {
|
||||
// Seek and check first CFH, probably requiring an offset mapper.
|
||||
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
|
||||
fseek($this->in, $offset, SEEK_SET);
|
||||
$offset -= $data['cdPos'];
|
||||
if (0 !== $offset) {
|
||||
$this->mapper = new OffsetPositionMapper($offset);
|
||||
}
|
||||
$entryCount = $data['cdEntries'];
|
||||
return new EndOfCentralDirectory($entryCount, $comment);
|
||||
}
|
||||
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($this->in, 4))[1];
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of disks 4 bytes
|
||||
$totalDisks = unpack('V', fread($this->in, 4))[1];
|
||||
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
fseek($this->in, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
$zip64EndOfCentralDirSig = unpack('V', fread($this->in, 4))[1];
|
||||
if (EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
|
||||
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
|
||||
}
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
fseek($this->in, 12, SEEK_CUR);
|
||||
// number of this disk 4 bytes
|
||||
$diskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
$cdDiskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntries = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
|
||||
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
|
||||
}
|
||||
// size of the central directory 8 bytes
|
||||
fseek($this->in, 8, SEEK_CUR);
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$cdPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// zip64 extensible data sector (variable size)
|
||||
fseek($this->in, $cdPos, SEEK_SET);
|
||||
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
|
||||
$entryCount = $cdEntries;
|
||||
$zip64 = true;
|
||||
return new EndOfCentralDirectory($entryCount, $comment, $zip64);
|
||||
}
|
||||
// Start recovering file entries from min.
|
||||
$this->preamble = $min;
|
||||
$this->postamble = $size - $min;
|
||||
return new EndOfCentralDirectory(0, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the central directory from the given seekable byte channel
|
||||
* and populates the internal tables with ZipEntry instances.
|
||||
*
|
||||
* The ZipEntry's will know all data that can be obtained from the
|
||||
* central directory alone, but not the data that requires the local
|
||||
* file header or additional data to be read.
|
||||
*
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
* @return ZipEntry[]
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function mountCentralDirectory(EndOfCentralDirectory $endOfCentralDirectory)
|
||||
{
|
||||
$numEntries = $endOfCentralDirectory->getEntryCount();
|
||||
$entries = [];
|
||||
|
||||
for (; $numEntries > 0; $numEntries--) {
|
||||
$entry = $this->readEntry();
|
||||
// Re-load virtual offset after ZIP64 Extended Information
|
||||
// Extra Field may have been parsed, map it to the real
|
||||
// offset and conditionally update the preamble size from it.
|
||||
$lfhOff = $this->mapper->map($entry->getOffset());
|
||||
if ($lfhOff < $this->preamble) {
|
||||
$this->preamble = $lfhOff;
|
||||
}
|
||||
$entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
if (0 !== $numEntries % 0x10000) {
|
||||
throw new ZipException("Expected " . abs($numEntries) .
|
||||
($numEntries > 0 ? " more" : " less") .
|
||||
" entries in the Central Directory!");
|
||||
}
|
||||
|
||||
if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
|
||||
assert(0 === $numEntries);
|
||||
$this->checkZipFileSignature();
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function readEntry()
|
||||
{
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
$fileHeaderSig = unpack('V', fread($this->in, 4))[1];
|
||||
if (ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG !== $fileHeaderSig) {
|
||||
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
|
||||
}
|
||||
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// general purpose bit flag 2 bytes
|
||||
// compression method 2 bytes
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
// crc-32 4 bytes
|
||||
// compressed size 4 bytes
|
||||
// uncompressed size 4 bytes
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
// file comment length 2 bytes
|
||||
// disk number start 2 bytes
|
||||
// internal file attributes 2 bytes
|
||||
// external file attributes 4 bytes
|
||||
// relative offset of local header 4 bytes
|
||||
$data = unpack(
|
||||
'vversionMadeBy/vversionNeededToExtract/vgpbf/' .
|
||||
'vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
|
||||
'VrawSize/vfileLength/vextraLength/vcommentLength/' .
|
||||
'VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
|
||||
fread($this->in, 42)
|
||||
);
|
||||
|
||||
// $utf8 = 0 !== ($data['gpbf'] & self::GPBF_UTF8);
|
||||
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$name = fread($this->in, $data['fileLength']);
|
||||
|
||||
$entry = new ZipSourceEntry($this);
|
||||
$entry->setName($name);
|
||||
$entry->setVersionNeededToExtract($data['versionNeededToExtract']);
|
||||
$entry->setPlatform($data['versionMadeBy'] >> 8);
|
||||
$entry->setMethod($data['rawMethod']);
|
||||
$entry->setGeneralPurposeBitFlags($data['gpbf']);
|
||||
$entry->setDosTime($data['rawTime']);
|
||||
$entry->setCrc($data['rawCrc']);
|
||||
$entry->setCompressedSize($data['rawCompressedSize']);
|
||||
$entry->setSize($data['rawSize']);
|
||||
$entry->setExternalAttributes($data['rawExternalAttributes']);
|
||||
$entry->setOffset($data['lfhOff']); // must be unmapped!
|
||||
if (0 < $data['extraLength']) {
|
||||
$entry->setExtra(fread($this->in, $data['extraLength']));
|
||||
}
|
||||
if (0 < $data['commentLength']) {
|
||||
$entry->setComment(fread($this->in, $data['commentLength']));
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry)
|
||||
{
|
||||
if ($entry->isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
if (!($entry instanceof ZipSourceEntry)) {
|
||||
throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class);
|
||||
}
|
||||
$isEncrypted = $entry->isEncrypted();
|
||||
if ($isEncrypted && null === $entry->getPassword()) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
}
|
||||
|
||||
$pos = $entry->getOffset();
|
||||
assert(ZipEntry::UNKNOWN !== $pos);
|
||||
$startPos = $pos = $this->mapper->map($pos);
|
||||
fseek($this->in, $startPos);
|
||||
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
if (ZipEntry::LOCAL_FILE_HEADER_SIG !== unpack('V', fread($this->in, 4))[1]) {
|
||||
throw new ZipException($entry->getName() . " (expected Local File Header)");
|
||||
}
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->in, 4));
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
|
||||
$method = $entry->getMethod();
|
||||
|
||||
fseek($this->in, $pos);
|
||||
|
||||
// Get raw entry content
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
if ($compressedSize > 0) {
|
||||
$content = fread($this->in, $compressedSize);
|
||||
} else {
|
||||
$content = '';
|
||||
}
|
||||
|
||||
$skipCheckCrc = false;
|
||||
if ($isEncrypted) {
|
||||
if (ZipEntry::METHOD_WINZIP_AES === $method) {
|
||||
// Strong Encryption Specification - WinZip AES
|
||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||
$content = $winZipAesEngine->decrypt($content);
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
$method = $field->getMethod();
|
||||
$entry->setEncryptionMethod($field->getEncryptionMethod());
|
||||
$skipCheckCrc = true;
|
||||
} else {
|
||||
// Traditional PKWARE Decryption
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$content = $zipCryptoEngine->decrypt($content);
|
||||
$entry->setEncryptionMethod(ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
}
|
||||
|
||||
if (!$skipCheckCrc) {
|
||||
// Check CRC32 in the Local File Header or Data Descriptor.
|
||||
$localCrc = null;
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// The CRC32 is in the Data Descriptor after the compressed size.
|
||||
// Note the Data Descriptor's Signature is optional:
|
||||
// All newer apps should write it (and so does TrueVFS),
|
||||
// but older apps might not.
|
||||
fseek($this->in, $pos + $compressedSize);
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
if (ZipEntry::DATA_DESCRIPTOR_SIG === $localCrc) {
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
}
|
||||
} else {
|
||||
fseek($this->in, $startPos + 14);
|
||||
// The CRC32 in the Local File Header.
|
||||
$localCrc = sprintf('%u', fread($this->in, 4)[1]);
|
||||
}
|
||||
if ($entry->getCrc() != $localCrc) {
|
||||
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
break;
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
$content = gzinflate($content);
|
||||
break;
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
if (!extension_loaded('bz2')) {
|
||||
throw new ZipException('Extension bzip2 not install');
|
||||
}
|
||||
$content = bzdecompress($content);
|
||||
break;
|
||||
default:
|
||||
throw new ZipUnsupportMethod($entry->getName() .
|
||||
" (compression method " . $method . " is not supported)");
|
||||
}
|
||||
if (!$skipCheckCrc) {
|
||||
$localCrc = sprintf('%u', crc32($content));
|
||||
if ($entry->getCrc() != $localCrc) {
|
||||
if ($isEncrypted) {
|
||||
throw new ZipCryptoException("Wrong password");
|
||||
}
|
||||
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->in;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out)
|
||||
{
|
||||
$pos = $entry->getOffset();
|
||||
assert(ZipEntry::UNKNOWN !== $pos);
|
||||
$pos = $this->mapper->map($pos);
|
||||
|
||||
$extraLength = strlen($entry->getExtra());
|
||||
$nameLength = strlen($entry->getName());
|
||||
|
||||
$length = ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $extraLength + $nameLength;
|
||||
|
||||
$padding = 0;
|
||||
if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$this->zipModel->getZipAlign() -
|
||||
(ftell($out->getStream()) + $length) % $this->zipModel->getZipAlign()
|
||||
) % $this->zipModel->getZipAlign();
|
||||
}
|
||||
|
||||
fseek($this->in, $pos, SEEK_SET);
|
||||
if ($padding > 0) {
|
||||
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
|
||||
fwrite($out->getStream(), pack('v', $extraLength + $padding));
|
||||
fseek($this->in, 2, SEEK_CUR);
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $nameLength + $extraLength);
|
||||
fwrite($out->getStream(), str_repeat(chr(0), $padding));
|
||||
} else {
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
||||
}
|
||||
$this->copyEntryData($entry, $out);
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
$length = 12;
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
$length += 8;
|
||||
}
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out)
|
||||
{
|
||||
$position = $entry->getOffset() + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
strlen($entry->getName()) + strlen($entry->getExtra());
|
||||
$length = $entry->getCompressedSize();
|
||||
fseek($this->in, $position, SEEK_SET);
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->in != null) {
|
||||
fclose($this->in);
|
||||
$this->in = null;
|
||||
}
|
||||
}
|
||||
}
|
50
src/PhpZip/Stream/ZipInputStreamInterface.php
Normal file
50
src/PhpZip/Stream/ZipInputStreamInterface.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipInputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @return ZipModel
|
||||
*/
|
||||
public function readZip();
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function readEntry();
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry);
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream();
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
||||
public function close();
|
||||
}
|
543
src/PhpZip/Stream/ZipOutputStream.php
Normal file
543
src/PhpZip/Stream/ZipOutputStream.php
Normal file
@@ -0,0 +1,543 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Model\Entry\OutputOffsetEntry;
|
||||
use PhpZip\Model\Entry\ZipChangesEntry;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Write
|
||||
* ip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputStream implements ZipOutputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $out;
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipOutputStream constructor.
|
||||
* @param resource $out
|
||||
* @param ZipModel $zipModel
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($out, ZipModel $zipModel)
|
||||
{
|
||||
if (!is_resource($out)) {
|
||||
throw new InvalidArgumentException('$out must be resource');
|
||||
}
|
||||
$this->out = $out;
|
||||
$this->zipModel = $zipModel;
|
||||
}
|
||||
|
||||
public function writeZip()
|
||||
{
|
||||
$entries = $this->zipModel->getEntries();
|
||||
$outPosEntries = [];
|
||||
foreach ($entries as $entry) {
|
||||
$outPosEntries[] = new OutputOffsetEntry(ftell($this->out), $entry);
|
||||
$this->writeEntry($entry);
|
||||
}
|
||||
$centralDirectoryOffset = ftell($this->out);
|
||||
foreach ($outPosEntries as $outputEntry) {
|
||||
$this->writeCentralDirectoryHeader($outputEntry);
|
||||
}
|
||||
$this->writeEndOfCentralDirectoryRecord($centralDirectoryOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeEntry(ZipEntry $entry)
|
||||
{
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry->getInputStream()->copyEntry($entry, $this);
|
||||
return;
|
||||
}
|
||||
|
||||
$entryContent = $this->entryCommitChangesAndReturnContent($entry);
|
||||
|
||||
$offset = ftell($this->out);
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
|
||||
$extra = $entry->getExtra();
|
||||
|
||||
$nameLength = strlen($entry->getName());
|
||||
$extraLength = strlen($extra);
|
||||
$size = $nameLength + $extraLength;
|
||||
if (0xffff < $size) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (the total size of " . $size .
|
||||
" bytes for the name, extra fields and comment " .
|
||||
"exceeds the maximum size of " . 0xffff . " bytes)"
|
||||
);
|
||||
}
|
||||
|
||||
// zip align
|
||||
$padding = 0;
|
||||
if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$this->zipModel->getZipAlign() -
|
||||
(
|
||||
$offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength
|
||||
) % $this->zipModel->getZipAlign()
|
||||
) % $this->zipModel->getZipAlign();
|
||||
}
|
||||
|
||||
$dd = $entry->isDataDescriptorRequired();
|
||||
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
)
|
||||
);
|
||||
fwrite($this->out, $entry->getName());
|
||||
if ($extraLength > 0) {
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($this->out, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
if ($entry instanceof ZipChangesEntry && !$entry->isChangedContent()) {
|
||||
$entry->getSourceEntry()->getInputStream()->copyEntryData($entry->getSourceEntry(), $this);
|
||||
} elseif (null !== $entryContent) {
|
||||
fwrite($this->out, $entryContent);
|
||||
}
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getSize());
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
// crc-32 4 bytes
|
||||
fwrite($this->out, pack('VV', ZipEntry::DATA_DESCRIPTOR_SIG, $entry->getCrc()));
|
||||
// compressed size 4 or 8 bytes
|
||||
// uncompressed size 4 or 8 bytes
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
fwrite($this->out, PackUtil::packLongLE($compressedSize));
|
||||
fwrite($this->out, PackUtil::packLongLE($entry->getSize()));
|
||||
} else {
|
||||
fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize()));
|
||||
}
|
||||
} elseif ($entry->getCompressedSize() != $compressedSize) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (expected compressed entry size of "
|
||||
. $entry->getCompressedSize() . " bytes, " .
|
||||
"but is actually " . $compressedSize . " bytes)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function entryCommitChangesAndReturnContent(ZipEntry $entry)
|
||||
{
|
||||
if (ZipEntry::UNKNOWN === $entry->getPlatform()) {
|
||||
$entry->setPlatform(ZipEntry::PLATFORM_UNIX);
|
||||
}
|
||||
if (ZipEntry::UNKNOWN === $entry->getTime()) {
|
||||
$entry->setTime(time());
|
||||
}
|
||||
$method = $entry->getMethod();
|
||||
|
||||
$encrypted = $entry->isEncrypted();
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$utf8 = true;
|
||||
|
||||
if ($encrypted && null === $entry->getPassword()) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
}
|
||||
|
||||
// Compose General Purpose Bit Flag.
|
||||
$general = ($encrypted ? ZipEntry::GPBF_ENCRYPTED : 0)
|
||||
| ($entry->isDataDescriptorRequired() ? ZipEntry::GPBF_DATA_DESCRIPTOR : 0)
|
||||
| ($utf8 ? ZipEntry::GPBF_UTF8 : 0);
|
||||
|
||||
$skipCrc = false;
|
||||
$entryContent = null;
|
||||
$extraFieldsCollection = $entry->getExtraFieldsCollection();
|
||||
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
|
||||
$entryContent = $entry->getEntryContent();
|
||||
|
||||
if ($entryContent !== null) {
|
||||
$entry->setSize(strlen($entryContent));
|
||||
$entry->setCrc(crc32($entryContent));
|
||||
|
||||
if (
|
||||
$encrypted &&
|
||||
(
|
||||
ZipEntry::METHOD_WINZIP_AES === $method ||
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 ||
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 ||
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
)
|
||||
) {
|
||||
$field = null;
|
||||
$method = $entry->getMethod();
|
||||
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits
|
||||
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
|
||||
if (ZipEntry::METHOD_WINZIP_AES === $method) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
$method = $field->getMethod();
|
||||
if (ZipEntry::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize -= $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
}
|
||||
$entry->setMethod($method);
|
||||
}
|
||||
}
|
||||
if (null === $field) {
|
||||
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
|
||||
}
|
||||
$field->setKeyStrength($keyStrength);
|
||||
$field->setMethod($method);
|
||||
$size = $entry->getSize();
|
||||
if (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||
} else {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||
$skipCrc = true;
|
||||
}
|
||||
$extraFieldsCollection->add($field);
|
||||
if (ZipEntry::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize += $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
}
|
||||
if ($skipCrc) {
|
||||
$entry->setCrc(0);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
$entryContent = gzdeflate($entryContent, $entry->getCompressionLevel());
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
$compressionLevel = $entry->getCompressionLevel() === ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ?
|
||||
ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION :
|
||||
$entry->getCompressionLevel();
|
||||
$entryContent = bzcompress($entryContent, $compressionLevel);
|
||||
if (is_int($entryContent)) {
|
||||
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipEntry::UNKNOWN:
|
||||
$entryContent = $this->determineBestCompressionMethod($entry, $entryContent);
|
||||
$method = $entry->getMethod();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($entry->getName() . " (unsupported compression method " . $method . ")");
|
||||
}
|
||||
|
||||
if (ZipFileInterface::METHOD_DEFLATED === $method) {
|
||||
$bit1 = false;
|
||||
$bit2 = false;
|
||||
switch ($entry->getCompressionLevel()) {
|
||||
case ZipFileInterface::LEVEL_BEST_COMPRESSION:
|
||||
$bit1 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_FAST:
|
||||
$bit2 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_SUPER_FAST:
|
||||
$bit1 = true;
|
||||
$bit2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$general |= ($bit1 ? ZipEntry::GPBF_COMPRESSION_FLAG1 : 0);
|
||||
$general |= ($bit2 ? ZipEntry::GPBF_COMPRESSION_FLAG2 : 0);
|
||||
}
|
||||
|
||||
if ($encrypted) {
|
||||
if (
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 ||
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 ||
|
||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
) {
|
||||
if ($skipCrc) {
|
||||
$entry->setCrc(0);
|
||||
}
|
||||
$entry->setMethod(ZipEntry::METHOD_WINZIP_AES);
|
||||
|
||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||
$entryContent = $winZipAesEngine->encrypt($entryContent);
|
||||
} elseif ($entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$entryContent = $zipCryptoEngine->encrypt($entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
$compressedSize = strlen($entryContent);
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Commit changes.
|
||||
$entry->setGeneralPurposeBitFlags($general);
|
||||
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
$extraFieldsCollection->add(ExtraFieldsFactory::createZip64Extra($entry));
|
||||
} elseif ($extraFieldsCollection->has(Zip64ExtraField::getHeaderId())) {
|
||||
$extraFieldsCollection->remove(Zip64ExtraField::getHeaderId());
|
||||
}
|
||||
return $entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
|
||||
{
|
||||
if (null !== $content) {
|
||||
$entryContent = gzdeflate($content, $entry->getCompressionLevel());
|
||||
if (strlen($entryContent) < strlen($content)) {
|
||||
$entry->setMethod(ZipFileInterface::METHOD_DEFLATED);
|
||||
return $entryContent;
|
||||
}
|
||||
$entry->setMethod(ZipFileInterface::METHOD_STORED);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param OutputOffsetEntry $outEntry
|
||||
* @throws RuntimeException
|
||||
* @internal param OutPosEntry $entry
|
||||
*/
|
||||
protected function writeCentralDirectoryHeader(OutputOffsetEntry $outEntry)
|
||||
{
|
||||
$entry = $outEntry->getEntry();
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$size = $entry->getSize();
|
||||
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
|
||||
// UNKNOWN!
|
||||
if (ZipEntry::UNKNOWN === ($compressedSize | $size)) {
|
||||
throw new RuntimeException("invalid entry");
|
||||
}
|
||||
$extra = $entry->getExtra();
|
||||
$extraSize = strlen($extra);
|
||||
|
||||
$commentLength = strlen($entry->getComment());
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
self::CENTRAL_FILE_HEADER_SIG,
|
||||
// version made by 2 bytes
|
||||
($entry->getPlatform() << 8) | 63,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$outEntry->getOffset()
|
||||
)
|
||||
);
|
||||
// file name (variable size)
|
||||
fwrite($this->out, $entry->getName());
|
||||
if (0 < $extraSize) {
|
||||
// extra field (variable size)
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
if (0 < $commentLength) {
|
||||
// file comment (variable size)
|
||||
fwrite($this->out, $entry->getComment());
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset)
|
||||
{
|
||||
$centralDirectoryEntriesCount = count($this->zipModel);
|
||||
$position = ftell($this->out);
|
||||
$centralDirectorySize = $position - $centralDirectoryOffset;
|
||||
$centralDirectoryEntriesZip64 = $centralDirectoryEntriesCount > 0xffff;
|
||||
$centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff;
|
||||
$centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff;
|
||||
$centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntriesCount;
|
||||
$centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize;
|
||||
$centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset;
|
||||
$zip64 // ZIP64 extensions?
|
||||
= $centralDirectoryEntriesZip64
|
||||
|| $centralDirectorySizeZip64
|
||||
|| $centralDirectoryOffsetZip64;
|
||||
if ($zip64) {
|
||||
// [zip64 end of central directory record]
|
||||
// relative offset of the zip64 end of central directory record
|
||||
$zip64EndOfCentralDirectoryOffset = $position;
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG));
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE(EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12));
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// due to potential use of BZIP2 compression
|
||||
// number of this disk 4 bytes
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
fwrite($this->out, pack('vvVV', 63, 46, 0, 0));
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
// size of the central directory 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectorySize));
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryOffset));
|
||||
// zip64 extensible data sector (variable size)
|
||||
|
||||
// [zip64 end of central directory locator]
|
||||
// signature 4 bytes (0x07064b50)
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
fwrite($this->out, pack('VV', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0));
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
|
||||
// total number of disks 4 bytes
|
||||
fwrite($this->out, pack('V', 1));
|
||||
}
|
||||
$comment = $this->zipModel->getArchiveComment();
|
||||
$commentLength = strlen($comment);
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 2 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// size of the central directory 4 bytes
|
||||
$centralDirectorySize32,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$centralDirectoryOffset32,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($this->out, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->out;
|
||||
}
|
||||
}
|
29
src/PhpZip/Stream/ZipOutputStreamInterface.php
Normal file
29
src/PhpZip/Stream/ZipOutputStreamInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Write zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipOutputStreamInterface
|
||||
{
|
||||
/** Central File Header signature. */
|
||||
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
|
||||
|
||||
public function writeZip();
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function writeEntry(ZipEntry $entry);
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream();
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Crypto Utils
|
||||
@@ -17,7 +17,7 @@ class CryptoUtil
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static final function randomBytes($length)
|
||||
final public static function randomBytes($length)
|
||||
{
|
||||
$length = (int)$length;
|
||||
if (function_exists('random_bytes')) {
|
||||
@@ -30,4 +30,4 @@ class CryptoUtil
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
@@ -39,9 +40,9 @@ class DateTimeConverter
|
||||
|
||||
return mktime(
|
||||
($dosTime >> 11) & 0x1f, // hour
|
||||
($dosTime >> 5) & 0x3f, // minute
|
||||
2 * ($dosTime & 0x1f), // second
|
||||
($dosTime >> 21) & 0x0f, // month
|
||||
($dosTime >> 5) & 0x3f, // minute
|
||||
2 * ($dosTime & 0x1f), // second
|
||||
($dosTime >> 21) & 0x0f, // month
|
||||
($dosTime >> 16) & 0x1f, // day
|
||||
1980 + (($dosTime >> 25) & 0x7f) // year
|
||||
);
|
||||
@@ -74,4 +75,4 @@ class DateTimeConverter
|
||||
$date['mday'] << 16 | $date['hours'] << 11 |
|
||||
$date['minutes'] << 5 | $date['seconds'] >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
@@ -60,7 +61,7 @@ class FilesUtil
|
||||
$inCurrent = 0;
|
||||
$chars = str_split($globPattern);
|
||||
$regexPattern = '';
|
||||
foreach ($chars AS $currentChar) {
|
||||
foreach ($chars as $currentChar) {
|
||||
switch ($currentChar) {
|
||||
case '*':
|
||||
$regexPattern .= ($escaping ? "\\*" : '.*');
|
||||
@@ -103,19 +104,21 @@ class FilesUtil
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= ')';
|
||||
$inCurrent--;
|
||||
} else if ($escaping)
|
||||
} elseif ($escaping) {
|
||||
$regexPattern = "\\}";
|
||||
else
|
||||
} else {
|
||||
$regexPattern = "}";
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
case ',':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= '|';
|
||||
} else if ($escaping)
|
||||
} elseif ($escaping) {
|
||||
$regexPattern .= "\\,";
|
||||
else
|
||||
} else {
|
||||
$regexPattern = ",";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$escaping = false;
|
||||
@@ -211,12 +214,15 @@ class FilesUtil
|
||||
*/
|
||||
public static function humanSize($size, $unit = null)
|
||||
{
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === "GB")
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === "GB") {
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === "MB")
|
||||
}
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === "MB") {
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === "KB")
|
||||
}
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === "KB") {
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
}
|
||||
return number_format($size) . " bytes";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
@@ -44,7 +45,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
|
||||
&& StringUtil::endsWith($ignoreFile, '/')
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
@@ -57,4 +58,4 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
@@ -66,4 +67,4 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
@@ -39,10 +40,24 @@ class PackUtil
|
||||
public static function unpackLongLE($value)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.6.3') >= 0) {
|
||||
return current(unpack('P', $value));
|
||||
return unpack('P', $value)[1];
|
||||
}
|
||||
$unpack = unpack('Va/Vb', $value);
|
||||
return $unpack['a'] + ($unpack['b'] << 32);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Cast to signed int 32-bit
|
||||
*
|
||||
* @param int $int
|
||||
* @return int
|
||||
*/
|
||||
public static function toSignedInt32($int)
|
||||
{
|
||||
$int = $int & 0xffffffff;
|
||||
if (PHP_INT_SIZE === 8 && ($int & 0x80000000)) {
|
||||
return $int - 0x100000000;
|
||||
}
|
||||
return $int;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
@@ -25,6 +26,6 @@ class StringUtil
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
630
src/PhpZip/ZipFileInterface.php
Normal file
630
src/PhpZip/ZipFileInterface.php
Normal file
@@ -0,0 +1,630 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipNotFoundEntry;
|
||||
use PhpZip\Exception\ZipUnsupportMethod;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipEntryMatcher;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create, open .ZIP files, modify, get info and extract files.
|
||||
*
|
||||
* Implemented support traditional PKWARE encryption and WinZip AES encryption.
|
||||
* Implemented support ZIP64.
|
||||
* Implemented support skip a preamble like the one found in self extracting archives.
|
||||
* Support ZipAlign functional.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
/**
|
||||
* Method for Stored (uncompressed) entries.
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_STORED = 0;
|
||||
/**
|
||||
* Method for Deflated compressed entries.
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_DEFLATED = 8;
|
||||
/**
|
||||
* Method for BZIP2 compressed entries.
|
||||
* Require php extension bz2.
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_BZIP2 = 12;
|
||||
|
||||
/**
|
||||
* Default compression level.
|
||||
*/
|
||||
const LEVEL_DEFAULT_COMPRESSION = -1;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
const LEVEL_FAST = 2;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
const LEVEL_BEST_SPEED = 1;
|
||||
const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED;
|
||||
/**
|
||||
* Compression level for best compression.
|
||||
*/
|
||||
const LEVEL_BEST_COMPRESSION = 9;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to Traditional PKWARE encryption.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_TRADITIONAL = 0;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption.
|
||||
* Default value 256 bit
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES = self::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 128 bit.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_128 = 2;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 194 bit.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_192 = 3;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 256 bit.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_256 = 1;
|
||||
|
||||
/**
|
||||
* Open zip archive from file
|
||||
*
|
||||
* @param string $filename
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException if file doesn't exists.
|
||||
* @throws ZipException if can't open file.
|
||||
*/
|
||||
public function openFile($filename);
|
||||
|
||||
/**
|
||||
* Open zip archive from raw string data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException if data not available.
|
||||
* @throws ZipException if can't open temp stream.
|
||||
*/
|
||||
public function openFromString($data);
|
||||
|
||||
/**
|
||||
* Open zip archive from stream resource
|
||||
*
|
||||
* @param resource $handle
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException Invalid stream resource
|
||||
* or resource cannot seekable stream
|
||||
*/
|
||||
public function openFromStream($handle);
|
||||
|
||||
/**
|
||||
* @return string[] Returns the list files.
|
||||
*/
|
||||
public function getListFiles();
|
||||
|
||||
/**
|
||||
* Returns the file comment.
|
||||
*
|
||||
* @return string The file comment.
|
||||
*/
|
||||
public function getArchiveComment();
|
||||
|
||||
/**
|
||||
* Set archive comment.
|
||||
*
|
||||
* @param null|string $comment
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException Length comment out of range
|
||||
*/
|
||||
public function setArchiveComment($comment = null);
|
||||
|
||||
/**
|
||||
* Checks that the entry in the archive is a directory.
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return bool
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function isDirectory($entryName);
|
||||
|
||||
/**
|
||||
* Returns entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return string
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getEntryComment($entryName);
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment = null);
|
||||
|
||||
/**
|
||||
* Returns the entry contents.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContents($entryName);
|
||||
|
||||
/**
|
||||
* Checks if there is an entry in the archive.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName);
|
||||
|
||||
/**
|
||||
* Get info by entry.
|
||||
*
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipInfo
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getEntryInfo($entryName);
|
||||
|
||||
/**
|
||||
* Get info by all entries.
|
||||
*
|
||||
* @return ZipInfo[]
|
||||
*/
|
||||
public function getAllInfo();
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function matcher();
|
||||
|
||||
/**
|
||||
* Extract the archive contents
|
||||
*
|
||||
* Extract the complete archive or the given files to the specified destination.
|
||||
*
|
||||
* @param string $destination Location where to extract the files.
|
||||
* @param array|string|null $entries The entries to extract. It accepts either
|
||||
* a single entry name or an array of names.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function extractTo($destination, $entries = null);
|
||||
|
||||
/**
|
||||
* Add entry from the string.
|
||||
*
|
||||
* @param string $localName Zip entry name.
|
||||
* @param string $contents String contents.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException If incorrect data or entry name.
|
||||
* @throws ZipUnsupportMethod
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addFromString($localName, $contents, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add entry from the file.
|
||||
*
|
||||
* @param string $filename Destination file.
|
||||
* @param string|null $localName Zip Entry name.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipUnsupportMethod
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addFile($filename, $localName = null, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add entry from the stream.
|
||||
*
|
||||
* @param resource $stream Stream resource.
|
||||
* @param string $localName Zip Entry name.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipUnsupportMethod
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addFromStream($stream, $localName, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add an empty directory in the zip archive.
|
||||
*
|
||||
* @param string $dirName
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addEmptyDir($dirName);
|
||||
|
||||
/**
|
||||
* Add directory not recursively to the zip archive.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addDir($inputDir, $localPath = "/", $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add recursive directory to the zip archive.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipUnsupportMethod
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addDirRecursive($inputDir, $localPath = "/", $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add directories from directory iterator.
|
||||
*
|
||||
* @param \Iterator $iterator Directory iterator.
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipUnsupportMethod
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files from glob pattern.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern Glob pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files recursively from glob pattern.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern Glob pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files from regex pattern.
|
||||
*
|
||||
* @param string $inputDir Search files in this directory.
|
||||
* @param string $regexPattern Regex pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*/
|
||||
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files recursively from regex pattern.
|
||||
*
|
||||
* @param string $inputDir Search files in this directory.
|
||||
* @param string $regexPattern Regex pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*/
|
||||
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add array data to archive.
|
||||
* Keys is local names.
|
||||
* Values is contents.
|
||||
*
|
||||
* @param array $mapData Associative array for added to zip.
|
||||
*/
|
||||
public function addAll(array $mapData);
|
||||
|
||||
/**
|
||||
* Rename the entry.
|
||||
*
|
||||
* @param string $oldName Old entry name.
|
||||
* @param string $newName New entry name.
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function rename($oldName, $newName);
|
||||
|
||||
/**
|
||||
* Delete entry by name.
|
||||
*
|
||||
* @param string $entryName Zip Entry name.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipNotFoundEntry If entry not found.
|
||||
*/
|
||||
public function deleteFromName($entryName);
|
||||
|
||||
/**
|
||||
* Delete entries by glob pattern.
|
||||
*
|
||||
* @param string $globPattern Glob pattern
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function deleteFromGlob($globPattern);
|
||||
|
||||
/**
|
||||
* Delete entries by regex pattern.
|
||||
*
|
||||
* @param string $regexPattern Regex pattern
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function deleteFromRegex($regexPattern);
|
||||
|
||||
/**
|
||||
* Delete all entries
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
/**
|
||||
* Set compression level for new entries.
|
||||
*
|
||||
* @param int $compressionLevel
|
||||
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFileInterface::LEVEL_SUPER_FAST
|
||||
* @see ZipFileInterface::LEVEL_FAST
|
||||
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionLevel
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFileInterface::LEVEL_SUPER_FAST
|
||||
* @see ZipFileInterface::LEVEL_FAST
|
||||
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
*/
|
||||
public function setCompressionLevelEntry($entryName, $compressionLevel);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionMethod
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function setCompressionMethodEntry($entryName, $compressionMethod);
|
||||
|
||||
/**
|
||||
* zipalign is optimization to Android application (APK) files.
|
||||
*
|
||||
* @param int|null $align
|
||||
* @return ZipFileInterface
|
||||
* @link https://developer.android.com/studio/command-line/zipalign.html
|
||||
*/
|
||||
public function setZipAlign($align = null);
|
||||
|
||||
/**
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::setReadPassword()
|
||||
*/
|
||||
public function withReadPassword($password);
|
||||
|
||||
/**
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPassword($password);
|
||||
|
||||
/**
|
||||
* Set password to concrete input entry.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password);
|
||||
|
||||
/**
|
||||
* Set password for all entries for update.
|
||||
*
|
||||
* @param string $password If password null then encryption clear
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::setPassword()
|
||||
*/
|
||||
public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
|
||||
|
||||
/**
|
||||
* Set password for zip archive
|
||||
*
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod
|
||||
* @return mixed
|
||||
*/
|
||||
public function setPasswordEntry($entryName, $password, $encryptionMethod = null);
|
||||
|
||||
/**
|
||||
* Remove password for all entries for update.
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::removePassword()
|
||||
*/
|
||||
public function withoutPassword();
|
||||
|
||||
/**
|
||||
* Remove password for all entries for update.
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function removePassword();
|
||||
|
||||
/**
|
||||
* Remove password for concrete entry.
|
||||
* @param string $entryName
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function removePasswordEntry($entryName);
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeAll();
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeArchiveComment();
|
||||
|
||||
/**
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeEntry($entry);
|
||||
|
||||
/**
|
||||
* Save as file.
|
||||
*
|
||||
* @param string $filename Output filename
|
||||
* @return ZipFileInterface
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function saveAsFile($filename);
|
||||
|
||||
/**
|
||||
* Save as stream.
|
||||
*
|
||||
* @param resource $handle Output stream resource
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function saveAsStream($handle);
|
||||
|
||||
/**
|
||||
* Output .ZIP archive as attachment.
|
||||
* Die after output.
|
||||
*
|
||||
* @param string $outputFilename
|
||||
* @param string|null $mimeType
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function outputAsAttachment($outputFilename, $mimeType = null);
|
||||
|
||||
/**
|
||||
* Output .ZIP archive as PSR-Message Response.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param string $outputFilename
|
||||
* @param string|null $mimeType
|
||||
* @return ResponseInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null);
|
||||
|
||||
/**
|
||||
* Returns the zip archive as a string.
|
||||
* @return string
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function outputAsString();
|
||||
|
||||
/**
|
||||
* Save and reopen zip archive.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function rewrite();
|
||||
|
||||
/**
|
||||
* Close zip archive and release input stream.
|
||||
*/
|
||||
public function close();
|
||||
}
|
Reference in New Issue
Block a user