123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898 |
- <?php
- namespace PhpZip\IO;
- use PhpZip\Constants\DosCodePage;
- use PhpZip\Constants\GeneralPurposeBitFlag;
- use PhpZip\Constants\ZipCompressionMethod;
- use PhpZip\Constants\ZipConstants;
- use PhpZip\Constants\ZipEncryptionMethod;
- use PhpZip\Constants\ZipOptions;
- use PhpZip\Exception\Crc32Exception;
- use PhpZip\Exception\InvalidArgumentException;
- use PhpZip\Exception\ZipException;
- use PhpZip\IO\Filter\Cipher\Pkware\PKDecryptionStreamFilter;
- use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesDecryptionStreamFilter;
- use PhpZip\Model\Data\ZipSourceFileData;
- use PhpZip\Model\EndOfCentralDirectory;
- use PhpZip\Model\Extra\ExtraFieldsCollection;
- use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
- use PhpZip\Model\Extra\Fields\UnrecognizedExtraField;
- use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
- use PhpZip\Model\Extra\Fields\Zip64ExtraField;
- use PhpZip\Model\Extra\ZipExtraDriver;
- use PhpZip\Model\Extra\ZipExtraField;
- use PhpZip\Model\ImmutableZipContainer;
- use PhpZip\Model\ZipEntry;
- use PhpZip\Util\PackUtil;
- /**
- * Zip reader.
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- */
- class ZipReader
- {
- /** @var int file size */
- protected $size;
- /** @var resource */
- protected $inStream;
- /** @var array */
- protected $options;
- /**
- * @param resource $inStream
- * @param array $options
- */
- public function __construct($inStream, array $options = [])
- {
- if (!\is_resource($inStream)) {
- throw new InvalidArgumentException('Stream must be a resource');
- }
- $type = get_resource_type($inStream);
- if ($type !== 'stream') {
- throw new InvalidArgumentException("Invalid resource type {$type}.");
- }
- $meta = stream_get_meta_data($inStream);
- $wrapperType = isset($meta['wrapper_type']) ? $meta['wrapper_type'] : 'Unknown';
- $supportStreamWrapperTypes = ['plainfile', 'PHP', 'user-space'];
- if (!\in_array($wrapperType, $supportStreamWrapperTypes, true)) {
- throw new InvalidArgumentException(
- 'The stream wrapper type "' . $wrapperType . '" is not supported. Support: ' . implode(
- ', ',
- $supportStreamWrapperTypes
- )
- );
- }
- if (
- $wrapperType === 'plainfile' &&
- (
- $meta['stream_type'] === 'dir' ||
- (isset($meta['uri']) && is_dir($meta['uri']))
- )
- ) {
- throw new InvalidArgumentException('Directory stream not supported');
- }
- $seekable = $meta['seekable'];
- if (!$seekable) {
- throw new InvalidArgumentException('Resource does not support seekable.');
- }
- $this->size = fstat($inStream)['size'];
- $this->inStream = $inStream;
- /** @noinspection AdditionOperationOnArraysInspection */
- $options += $this->getDefaultOptions();
- $this->options = $options;
- }
- /**
- * @return array
- */
- protected function getDefaultOptions()
- {
- return [
- ZipOptions::CHARSET => null,
- ];
- }
- /**
- * @throws ZipException
- *
- * @return ImmutableZipContainer
- */
- public function read()
- {
- if ($this->size < ZipConstants::END_CD_MIN_LEN) {
- throw new ZipException('Corrupt zip file');
- }
- $endOfCentralDirectory = $this->readEndOfCentralDirectory();
- $entries = $this->readCentralDirectory($endOfCentralDirectory);
- return new ImmutableZipContainer($entries, $endOfCentralDirectory->getComment());
- }
- /**
- * @return array
- */
- public function getStreamMetaData()
- {
- return stream_get_meta_data($this->inStream);
- }
- /**
- * Read End of central directory record.
- *
- * end of central dir signature 4 bytes (0x06054b50)
- * 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
- * .ZIP file comment (variable size)
- *
- * @throws ZipException
- *
- * @return EndOfCentralDirectory
- */
- protected function readEndOfCentralDirectory()
- {
- if (!$this->findEndOfCentralDirectory()) {
- throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
- }
- $positionECD = ftell($this->inStream) - 4;
- $sizeECD = $this->size - ftell($this->inStream);
- $buffer = fread($this->inStream, $sizeECD);
- $unpack = unpack(
- 'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
- 'vcdEntries/VcdSize/VcdPos/vcommentLength',
- substr($buffer, 0, 18)
- );
- if (
- $unpack['diskNo'] !== 0 ||
- $unpack['cdDiskNo'] !== 0 ||
- $unpack['cdEntriesDisk'] !== $unpack['cdEntries']
- ) {
- throw new ZipException(
- 'ZIP file spanning/splitting is not supported!'
- );
- }
- // .ZIP file comment (variable sizeECD)
- $comment = null;
- if ($unpack['commentLength'] > 0) {
- $comment = substr($buffer, 18, $unpack['commentLength']);
- }
- // Check for ZIP64 End Of Central Directory Locator exists.
- $zip64ECDLocatorPosition = $positionECD - ZipConstants::ZIP64_END_CD_LOC_LEN;
- fseek($this->inStream, $zip64ECDLocatorPosition);
- // zip64 end of central dir locator
- // signature 4 bytes (0x07064b50)
- if ($zip64ECDLocatorPosition > 0 && unpack(
- 'V',
- fread($this->inStream, 4)
- )[1] === ZipConstants::ZIP64_END_CD_LOC) {
- if (!$this->isZip64Support()) {
- throw new ZipException('ZIP64 not supported this archive.');
- }
- $positionECD = $this->findZip64ECDPosition();
- $endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
- $endCentralDirectory->setComment($comment);
- } else {
- $endCentralDirectory = new EndOfCentralDirectory(
- $unpack['cdEntries'],
- $unpack['cdPos'],
- $unpack['cdSize'],
- false,
- $comment
- );
- }
- return $endCentralDirectory;
- }
- /**
- * @return bool
- */
- protected function findEndOfCentralDirectory()
- {
- $max = $this->size - ZipConstants::END_CD_MIN_LEN;
- $min = $max >= 0xffff ? $max - 0xffff : 0;
- // Search for End of central directory record.
- for ($position = $max; $position >= $min; $position--) {
- fseek($this->inStream, $position);
- // end of central dir signature 4 bytes (0x06054b50)
- if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::END_CD) {
- continue;
- }
- return true;
- }
- return false;
- }
- /**
- * Read Zip64 end of central directory locator and returns
- * Zip64 end of central directory position.
- *
- * number of the disk with the
- * start of the zip64 end of
- * central directory 4 bytes
- * relative offset of the zip64
- * end of central directory record 8 bytes
- * total number of disks 4 bytes
- *
- * @throws ZipException
- *
- * @return int Zip64 End Of Central Directory position
- */
- protected function findZip64ECDPosition()
- {
- $diskNo = unpack('V', fread($this->inStream, 4))[1];
- $zip64ECDPos = PackUtil::unpackLongLE(fread($this->inStream, 8));
- $totalDisks = unpack('V', fread($this->inStream, 4))[1];
- if ($diskNo !== 0 || $totalDisks > 1) {
- throw new ZipException('ZIP file spanning/splitting is not supported!');
- }
- return $zip64ECDPos;
- }
- /**
- * Read zip64 end of central directory locator and zip64 end
- * of central directory record.
- *
- * zip64 end of central dir
- * signature 4 bytes (0x06064b50)
- * size of zip64 end of central
- * directory record 8 bytes
- * version made by 2 bytes
- * version needed to extract 2 bytes
- * number of this disk 4 bytes
- * number of the disk with the
- * start of the central directory 4 bytes
- * total number of entries in the
- * central directory on this disk 8 bytes
- * total number of entries in the
- * central directory 8 bytes
- * size of the central directory 8 bytes
- * offset of start of central
- * directory with respect to
- * the starting disk number 8 bytes
- * zip64 extensible data sector (variable size)
- *
- * @param int $zip64ECDPosition
- *
- * @throws ZipException
- *
- * @return EndOfCentralDirectory
- */
- protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
- {
- fseek($this->inStream, $zip64ECDPosition);
- $buffer = fread($this->inStream, ZipConstants::ZIP64_END_OF_CD_LEN);
- if (unpack('V', $buffer)[1] !== ZipConstants::ZIP64_END_CD) {
- throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
- }
- $data = unpack(
- // 'Psize/vversionMadeBy/vextractVersion/' .
- 'VdiskNo/VcdDiskNo',
- substr($buffer, 16, 8)
- );
- $cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
- $entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
- $cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
- $cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
- // $platform = ZipPlatform::fromValue(($data['versionMadeBy'] & 0xFF00) >> 8);
- // $softwareVersion = $data['versionMadeBy'] & 0x00FF;
- if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
- throw new ZipException('ZIP file spanning/splitting is not supported!');
- }
- if ($entryCount < 0 || $entryCount > 0x7fffffff) {
- throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
- }
- // skip zip64 extensible data sector (variable sizeEndCD)
- return new EndOfCentralDirectory(
- $entryCount,
- $cdPos,
- $cdSize,
- true
- );
- }
- /**
- * 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 $endCD
- *
- * @throws ZipException
- *
- * @return ZipEntry[]
- */
- protected function readCentralDirectory(EndOfCentralDirectory $endCD)
- {
- $entries = [];
- $cdOffset = $endCD->getCdOffset();
- fseek($this->inStream, $cdOffset);
- if (!($cdStream = fopen('php://temp', 'w+b'))) {
- // @codeCoverageIgnoreStart
- throw new ZipException('A temporary resource cannot be opened for writing.');
- // @codeCoverageIgnoreEnd
- }
- stream_copy_to_stream($this->inStream, $cdStream, $endCD->getCdSize());
- rewind($cdStream);
- for ($numEntries = $endCD->getEntryCount(); $numEntries > 0; $numEntries--) {
- $zipEntry = $this->readZipEntry($cdStream);
- $entryName = $zipEntry->getName();
- /** @var UnicodePathExtraField|null $unicodePathExtraField */
- $unicodePathExtraField = $zipEntry->getExtraField(UnicodePathExtraField::HEADER_ID);
- if ($unicodePathExtraField !== null && $unicodePathExtraField->getCrc32() === crc32($entryName)) {
- $unicodePath = $unicodePathExtraField->getUnicodeValue();
- if ($unicodePath !== null) {
- $unicodePath = str_replace('\\', '/', $unicodePath);
- if (
- $unicodePath !== '' &&
- substr_count($entryName, '/') === substr_count($unicodePath, '/')
- ) {
- $entryName = $unicodePath;
- }
- }
- }
- $entries[$entryName] = $zipEntry;
- }
- return $entries;
- }
- /**
- * Read central directory entry.
- *
- * central file header signature 4 bytes (0x02014b50)
- * 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
- *
- * file name (variable size)
- * extra field (variable size)
- * file comment (variable size)
- *
- * @param resource $stream
- *
- * @throws ZipException
- *
- * @return ZipEntry
- */
- protected function readZipEntry($stream)
- {
- if (unpack('V', fread($stream, 4))[1] !== ZipConstants::CENTRAL_FILE_HEADER) {
- throw new ZipException('Corrupt zip file. Cannot read zip entry.');
- }
- $unpack = unpack(
- 'vversionMadeBy/vversionNeededToExtract/' .
- 'vgeneralPurposeBitFlag/vcompressionMethod/' .
- 'VlastModFile/Vcrc/VcompressedSize/' .
- 'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
- 'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
- 'VexternalFileAttributes/VoffsetLocalHeader',
- fread($stream, 42)
- );
- if ($unpack['diskNumberStart'] !== 0) {
- throw new ZipException('ZIP file spanning/splitting is not supported!');
- }
- $generalPurposeBitFlags = $unpack['generalPurposeBitFlag'];
- $isUtf8 = ($generalPurposeBitFlags & GeneralPurposeBitFlag::UTF8) !== 0;
- $name = fread($stream, $unpack['fileNameLength']);
- $createdOS = ($unpack['versionMadeBy'] & 0xFF00) >> 8;
- $softwareVersion = $unpack['versionMadeBy'] & 0x00FF;
- $extractedOS = ($unpack['versionNeededToExtract'] & 0xFF00) >> 8;
- $extractVersion = $unpack['versionNeededToExtract'] & 0x00FF;
- $dosTime = $unpack['lastModFile'];
- $comment = null;
- if ($unpack['fileCommentLength'] > 0) {
- $comment = fread($stream, $unpack['fileCommentLength']);
- }
- // decode code page names
- $fallbackCharset = null;
- if (!$isUtf8 && isset($this->options[ZipOptions::CHARSET])) {
- $charset = $this->options[ZipOptions::CHARSET];
- $fallbackCharset = $charset;
- $name = DosCodePage::toUTF8($name, $charset);
- if ($comment !== null) {
- $comment = DosCodePage::toUTF8($comment, $charset);
- }
- }
- $zipEntry = ZipEntry::create(
- $name,
- $createdOS,
- $extractedOS,
- $softwareVersion,
- $extractVersion,
- $unpack['compressionMethod'],
- $generalPurposeBitFlags,
- $dosTime,
- $unpack['crc'],
- $unpack['compressedSize'],
- $unpack['uncompressedSize'],
- $unpack['internalFileAttributes'],
- $unpack['externalFileAttributes'],
- $unpack['offsetLocalHeader'],
- $comment,
- $fallbackCharset
- );
- if ($unpack['extraFieldLength'] > 0) {
- $this->parseExtraFields(
- fread($stream, $unpack['extraFieldLength']),
- $zipEntry,
- false
- );
- /** @var Zip64ExtraField|null $extraZip64 */
- $extraZip64 = $zipEntry->getCdExtraField(Zip64ExtraField::HEADER_ID);
- if ($extraZip64 !== null) {
- $this->handleZip64Extra($extraZip64, $zipEntry);
- }
- }
- $this->loadLocalExtraFields($zipEntry);
- $this->handleExtraEncryptionFields($zipEntry);
- $this->handleExtraFields($zipEntry);
- return $zipEntry;
- }
- /**
- * @param string $buffer
- * @param ZipEntry $zipEntry
- * @param bool $local
- *
- * @return ExtraFieldsCollection
- */
- protected function parseExtraFields($buffer, ZipEntry $zipEntry, $local = false)
- {
- $collection = $local ?
- $zipEntry->getLocalExtraFields() :
- $zipEntry->getCdExtraFields();
- if (!empty($buffer)) {
- $pos = 0;
- $endPos = \strlen($buffer);
- while ($endPos - $pos >= 4) {
- /** @var int[] $data */
- $data = unpack('vheaderId/vdataSize', substr($buffer, $pos, 4));
- $pos += 4;
- if ($endPos - $pos - $data['dataSize'] < 0) {
- break;
- }
- $bufferData = substr($buffer, $pos, $data['dataSize']);
- $headerId = $data['headerId'];
- /** @var string|ZipExtraField|null $className */
- $className = ZipExtraDriver::getClassNameOrNull($headerId);
- try {
- if ($className !== null) {
- try {
- $extraField = $local ?
- \call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) :
- \call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry);
- } catch (\Throwable $e) {
- // skip errors while parsing invalid data
- continue;
- }
- } else {
- $extraField = new UnrecognizedExtraField($headerId, $bufferData);
- }
- $collection->add($extraField);
- } finally {
- $pos += $data['dataSize'];
- }
- }
- }
- return $collection;
- }
- /**
- * @param Zip64ExtraField $extraZip64
- * @param ZipEntry $zipEntry
- */
- protected function handleZip64Extra(Zip64ExtraField $extraZip64, ZipEntry $zipEntry)
- {
- $uncompressedSize = $extraZip64->getUncompressedSize();
- $compressedSize = $extraZip64->getCompressedSize();
- $localHeaderOffset = $extraZip64->getLocalHeaderOffset();
- if ($uncompressedSize !== null) {
- $zipEntry->setUncompressedSize($uncompressedSize);
- }
- if ($compressedSize !== null) {
- $zipEntry->setCompressedSize($compressedSize);
- }
- if ($localHeaderOffset !== null) {
- $zipEntry->setLocalHeaderOffset($localHeaderOffset);
- }
- }
- /**
- * Read Local File Header.
- *
- * local file header signature 4 bytes (0x04034b50)
- * 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 name (variable size)
- * extra field (variable size)
- *
- * @param ZipEntry $entry
- *
- * @throws ZipException
- */
- protected function loadLocalExtraFields(ZipEntry $entry)
- {
- $offsetLocalHeader = $entry->getLocalHeaderOffset();
- fseek($this->inStream, $offsetLocalHeader);
- if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::LOCAL_FILE_HEADER) {
- throw new ZipException(sprintf('%s (expected Local File Header)', $entry->getName()));
- }
- fseek($this->inStream, $offsetLocalHeader + ZipConstants::LFH_FILENAME_LENGTH_POS);
- $unpack = unpack('vfileNameLength/vextraFieldLength', fread($this->inStream, 4));
- $offsetData = ftell($this->inStream)
- + $unpack['fileNameLength']
- + $unpack['extraFieldLength'];
- fseek($this->inStream, $unpack['fileNameLength'], \SEEK_CUR);
- if ($unpack['extraFieldLength'] > 0) {
- $this->parseExtraFields(
- fread($this->inStream, $unpack['extraFieldLength']),
- $entry,
- true
- );
- }
- $zipData = new ZipSourceFileData($this, $entry, $offsetData);
- $entry->setData($zipData);
- }
- /**
- * @param ZipEntry $zipEntry
- *
- * @throws ZipException
- */
- private function handleExtraEncryptionFields(ZipEntry $zipEntry)
- {
- if ($zipEntry->isEncrypted()) {
- if ($zipEntry->getCompressionMethod() === ZipCompressionMethod::WINZIP_AES) {
- /** @var WinZipAesExtraField|null $extraField */
- $extraField = $zipEntry->getExtraField(WinZipAesExtraField::HEADER_ID);
- if ($extraField === null) {
- throw new ZipException(
- sprintf(
- 'Extra field 0x%04x (WinZip-AES Encryption) expected for compression method %d',
- WinZipAesExtraField::HEADER_ID,
- $zipEntry->getCompressionMethod()
- )
- );
- }
- $zipEntry->setCompressionMethod($extraField->getCompressionMethod());
- $zipEntry->setEncryptionMethod($extraField->getEncryptionMethod());
- } else {
- $zipEntry->setEncryptionMethod(ZipEncryptionMethod::PKWARE);
- }
- }
- }
- /**
- * Handle extra data in zip records.
- *
- * This is a special method in which you can process ExtraField
- * and make changes to ZipEntry.
- *
- * @param ZipEntry $zipEntry
- */
- protected function handleExtraFields(ZipEntry $zipEntry)
- {
- }
- /**
- * @param ZipSourceFileData $zipFileData
- *
- * @throws ZipException
- * @throws Crc32Exception
- *
- * @return resource
- */
- public function getEntryStream(ZipSourceFileData $zipFileData)
- {
- $outStream = fopen('php://temp', 'w+b');
- $this->copyUncompressedDataToStream($zipFileData, $outStream);
- rewind($outStream);
- return $outStream;
- }
- /**
- * @param ZipSourceFileData $zipFileData
- * @param resource $outStream
- *
- * @throws Crc32Exception
- * @throws ZipException
- */
- public function copyUncompressedDataToStream(ZipSourceFileData $zipFileData, $outStream)
- {
- if (!\is_resource($outStream)) {
- throw new InvalidArgumentException('outStream is not resource');
- }
- $entry = $zipFileData->getSourceEntry();
- // if ($entry->isDirectory()) {
- // throw new InvalidArgumentException('Streams not supported for directories');
- // }
- if ($entry->isStrongEncryption()) {
- throw new ZipException('Not support encryption zip.');
- }
- $compressionMethod = $entry->getCompressionMethod();
- fseek($this->inStream, $zipFileData->getOffset());
- $filters = [];
- $skipCheckCrc = false;
- $isEncrypted = $entry->isEncrypted();
- if ($isEncrypted) {
- if ($entry->getPassword() === null) {
- throw new ZipException('Can not password from entry ' . $entry->getName());
- }
- if (ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
- /** @var WinZipAesExtraField|null $winZipAesExtra */
- $winZipAesExtra = $entry->getExtraField(WinZipAesExtraField::HEADER_ID);
- if ($winZipAesExtra === null) {
- throw new ZipException(
- sprintf('WinZip AES must contain the extra field %s', WinZipAesExtraField::HEADER_ID)
- );
- }
- $compressionMethod = $winZipAesExtra->getCompressionMethod();
- WinZipAesDecryptionStreamFilter::register();
- $cipherFilterName = WinZipAesDecryptionStreamFilter::FILTER_NAME;
- if ($winZipAesExtra->isV2()) {
- $skipCheckCrc = true;
- }
- } else {
- PKDecryptionStreamFilter::register();
- $cipherFilterName = PKDecryptionStreamFilter::FILTER_NAME;
- }
- $encContextFilter = stream_filter_append(
- $this->inStream,
- $cipherFilterName,
- \STREAM_FILTER_READ,
- [
- 'entry' => $entry,
- ]
- );
- if (!$encContextFilter) {
- throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
- }
- $filters[] = $encContextFilter;
- }
- // hack, see https://groups.google.com/forum/#!topic/alt.comp.lang.php/37_JZeW63uc
- $pos = ftell($this->inStream);
- rewind($this->inStream);
- fseek($this->inStream, $pos);
- $contextDecompress = null;
- switch ($compressionMethod) {
- case ZipCompressionMethod::STORED:
- // file without compression, do nothing
- break;
- case ZipCompressionMethod::DEFLATED:
- if (!($contextDecompress = stream_filter_append(
- $this->inStream,
- 'zlib.inflate',
- \STREAM_FILTER_READ
- ))) {
- throw new \RuntimeException('Could not append filter "zlib.inflate" to stream');
- }
- $filters[] = $contextDecompress;
- break;
- case ZipCompressionMethod::BZIP2:
- if (!($contextDecompress = stream_filter_append(
- $this->inStream,
- 'bzip2.decompress',
- \STREAM_FILTER_READ
- ))) {
- throw new \RuntimeException('Could not append filter "bzip2.decompress" to stream');
- }
- $filters[] = $contextDecompress;
- break;
- default:
- throw new ZipException(
- sprintf(
- '%s (compression method %d (%s) is not supported)',
- $entry->getName(),
- $compressionMethod,
- ZipCompressionMethod::getCompressionMethodName($compressionMethod)
- )
- );
- }
- $limit = $zipFileData->getUncompressedSize();
- $offset = 0;
- $chunkSize = 8192;
- try {
- if ($skipCheckCrc) {
- while ($offset < $limit) {
- $length = min($chunkSize, $limit - $offset);
- $buffer = fread($this->inStream, $length);
- if ($buffer === false) {
- throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
- }
- fwrite($outStream, $buffer);
- $offset += $length;
- }
- } else {
- $contextHash = hash_init('crc32b');
- while ($offset < $limit) {
- $length = min($chunkSize, $limit - $offset);
- $buffer = fread($this->inStream, $length);
- if ($buffer === false) {
- throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
- }
- fwrite($outStream, $buffer);
- hash_update($contextHash, $buffer);
- $offset += $length;
- }
- $expectedCrc = (int) hexdec(hash_final($contextHash));
- if ($expectedCrc !== $entry->getCrc()) {
- throw new Crc32Exception($entry->getName(), $expectedCrc, $entry->getCrc());
- }
- }
- } finally {
- for ($i = \count($filters); $i > 0; $i--) {
- stream_filter_remove($filters[$i - 1]);
- }
- }
- }
- /**
- * @param ZipSourceFileData $zipData
- * @param resource $outStream
- */
- public function copyCompressedDataToStream(ZipSourceFileData $zipData, $outStream)
- {
- if ($zipData->getCompressedSize() > 0) {
- fseek($this->inStream, $zipData->getOffset());
- stream_copy_to_stream($this->inStream, $outStream, $zipData->getCompressedSize());
- }
- }
- /**
- * @return bool
- */
- protected function isZip64Support()
- {
- return \PHP_INT_SIZE === 8; // true for 64bit system
- }
- public function close()
- {
- if (\is_resource($this->inStream)) {
- fclose($this->inStream);
- }
- }
- public function __destruct()
- {
- $this->close();
- }
- }
|