diff options
Diffstat (limited to 'vendor/nelexa/zip/src/PhpZip/Stream')
5 files changed, 1511 insertions, 0 deletions
diff --git a/vendor/nelexa/zip/src/PhpZip/Stream/ResponseStream.php b/vendor/nelexa/zip/src/PhpZip/Stream/ResponseStream.php new file mode 100644 index 0000000..172de1e --- /dev/null +++ b/vendor/nelexa/zip/src/PhpZip/Stream/ResponseStream.php @@ -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; + } +} diff --git a/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStream.php b/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStream.php new file mode 100644 index 0000000..8c8adf5 --- /dev/null +++ b/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStream.php @@ -0,0 +1,603 @@ +<?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\ExtraFieldsCollection; +use PhpZip\Extra\ExtraFieldsFactory; +use PhpZip\Extra\Fields\ApkAlignmentExtraField; +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\Util\StringUtil; +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()); + $lfhOff = PHP_INT_SIZE === 4 ? sprintf('%u', $lfhOff) : $lfhOff; + 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); + $pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $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(); + $compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize; + 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]); + $localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc; + } + + $crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc(); + + if ($crc != $localCrc) { + throw new Crc32Exception($entry->getName(), $crc, $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 AND false) { + $localCrc = crc32($content); + $localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc; + $crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc(); + if ($crc != $localCrc) { + if ($isEncrypted) { + throw new ZipCryptoException("Wrong password"); + } + throw new Crc32Exception($entry->getName(), $crc, $localCrc); + } + } + return $content; + } + + /** + * @return resource + */ + public function getStream() + { + return $this->in; + } + + /** + * Copy the input stream of the LOC entry zip and the data into + * the output stream and zip the alignment if necessary. + * + * @param ZipEntry $entry + * @param ZipOutputStreamInterface $out + */ + public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out) + { + $pos = $entry->getOffset(); + assert(ZipEntry::UNKNOWN !== $pos); + $pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $pos; + $pos = $this->mapper->map($pos); + + $nameLength = strlen($entry->getName()); + + fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET); + $sourceExtraLength = $destExtraLength = unpack('v', fread($this->in, 2))[1]; + + if ($sourceExtraLength > 0) { + // read Local File Header extra fields + fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET); + $extra = fread($this->in, $sourceExtraLength); + $extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry); + if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) { + unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]); + $destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection)); + } + } else { + $extraFieldsCollection = new ExtraFieldsCollection(); + } + + $dataAlignmentMultiple = $this->zipModel->getZipAlign(); + $copyInToOutLength = $entry->getCompressedSize(); + + fseek($this->in, $pos, SEEK_SET); + + if ( + $this->zipModel->isZipAlign() && + !$entry->isEncrypted() && + $entry->getMethod() === ZipFileInterface::METHOD_STORED + ) { + if (StringUtil::endsWith($entry->getName(), '.so')) { + $dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; + } + + $dataMinStartOffset = + ftell($out->getStream()) + + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + + $destExtraLength + + $nameLength + + ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES; + $padding = + ($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple)) + % $dataAlignmentMultiple; + + $alignExtra = new ApkAlignmentExtraField(); + $alignExtra->setMultiple($dataAlignmentMultiple); + $alignExtra->setPadding($padding); + $extraFieldsCollection->add($alignExtra); + + $extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection); + + // copy Local File Header without extra field length + // from input stream to output stream + stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2); + // write new extra field length (2 bytes) to output stream + fwrite($out->getStream(), pack('v', strlen($extra))); + // skip 2 bytes to input stream + fseek($this->in, 2, SEEK_CUR); + // copy name from input stream to output stream + stream_copy_to_stream($this->in, $out->getStream(), $nameLength); + // write extra field to output stream + fwrite($out->getStream(), $extra); + // skip source extraLength from input stream + fseek($this->in, $sourceExtraLength, SEEK_CUR); + } else { + $copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength; + ; + } + if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) { +// crc-32 4 bytes +// compressed size 4 bytes +// uncompressed size 4 bytes + $copyInToOutLength += 12; + if ($entry->isZip64ExtensionsRequired()) { +// compressed size +4 bytes +// uncompressed size +4 bytes + $copyInToOutLength += 8; + } + } + // copy loc, data, data descriptor from input to output stream + stream_copy_to_stream($this->in, $out->getStream(), $copyInToOutLength); + } + + /** + * @param ZipEntry $entry + * @param ZipOutputStreamInterface $out + */ + public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out) + { + $offset = $entry->getOffset(); + $offset = PHP_INT_SIZE === 4 ? sprintf('%u', $offset) : $offset; + $offset = $this->mapper->map($offset); + $nameLength = strlen($entry->getName()); + + fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET); + $extraLength = unpack('v', fread($this->in, 2))[1]; + + fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET); + // copy raw data from input stream to output stream + stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize()); + } + + public function __destruct() + { + $this->close(); + } + + public function close() + { + if ($this->in != null) { + fclose($this->in); + $this->in = null; + } + } +} diff --git a/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStreamInterface.php b/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStreamInterface.php new file mode 100644 index 0000000..2093c03 --- /dev/null +++ b/vendor/nelexa/zip/src/PhpZip/Stream/ZipInputStreamInterface.php @@ -0,0 +1,53 @@ +<?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(); + + /** + * Copy the input stream of the LOC entry zip and the data into + * the output stream and zip the alignment if necessary. + * + * @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(); +} diff --git a/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStream.php b/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStream.php new file mode 100644 index 0000000..c1c875d --- /dev/null +++ b/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStream.php @@ -0,0 +1,528 @@ +<?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\ApkAlignmentExtraField; +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\Util\StringUtil; +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); + + // zip align + if ( + $this->zipModel->isZipAlign() && + !$entry->isEncrypted() && + $entry->getMethod() === ZipFileInterface::METHOD_STORED + ) { + $dataAlignmentMultiple = $this->zipModel->getZipAlign(); + if (StringUtil::endsWith($entry->getName(), '.so')) { + $dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; + } + $dataMinStartOffset = + $offset + + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + + $extraLength + + $nameLength + + ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES; + + $padding = + ($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple)) + % $dataAlignmentMultiple; + + $alignExtra = new ApkAlignmentExtraField(); + $alignExtra->setMultiple($dataAlignmentMultiple); + $alignExtra->setPadding($padding); + + $extraFieldsCollection = clone $entry->getExtraFieldsCollection(); + $extraFieldsCollection->add($alignExtra); + + $extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection); + $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)" + ); + } + + $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 + ) + ); + if ($nameLength > 0) { + fwrite($this->out, $entry->getName()); + } + if ($extraLength > 0) { + fwrite($this->out, $extra); + } + + 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); + + $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) { + /** + * @var WinZipAesEntryExtraField $field + */ + $field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId()); + if (null !== $field) { + $method = $field->getMethod(); + } + } + + 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 (in_array($entry->getEncryptionMethod(), [ + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, + ], true)) { + $keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits + $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); + $entry->setCrc(0); + } + $extraFieldsCollection->add($field); + $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 + * @throws ZipException + */ + 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; + } +} diff --git a/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStreamInterface.php b/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStreamInterface.php new file mode 100644 index 0000000..57c397e --- /dev/null +++ b/vendor/nelexa/zip/src/PhpZip/Stream/ZipOutputStreamInterface.php @@ -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(); +} |