View file vendor/nelexa/zip/src/PhpZip/Model/Entry/ZipAbstractEntry.php

File size: 21.21Kb
<?php

namespace PhpZip\Model\Entry;

use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraFieldsCollection;
use PhpZip\Extra\ExtraFieldsFactory;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\DateTimeConverter;
use PhpZip\Util\StringUtil;
use PhpZip\ZipFile;

/**
 * Abstract ZIP entry.
 *
 * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
 *
 * @author Ne-Lexa [email protected]
 * @license MIT
 */
abstract class ZipAbstractEntry implements ZipEntry
{
    /** @var string Entry name (filename in archive) */
    private $name;

    /** @var int Made by platform */
    private $createdOS = self::UNKNOWN;

    /** @var int Extracted by platform */
    private $extractedOS = self::UNKNOWN;

    /** @var int */
    private $softwareVersion = self::UNKNOWN;

    /** @var int */
    private $versionNeededToExtract = self::UNKNOWN;

    /** @var int Compression method */
    private $method = self::UNKNOWN;

    /** @var int */
    private $generalPurposeBitFlags = 0;

    /** @var int Dos time */
    private $dosTime = self::UNKNOWN;

    /** @var int Crc32 */
    private $crc = self::UNKNOWN;

    /** @var int Compressed size */
    private $compressedSize = self::UNKNOWN;

    /** @var int Uncompressed size */
    private $size = self::UNKNOWN;

    /** @var int Internal attributes */
    private $internalAttributes = 0;

    /** @var int External attributes */
    private $externalAttributes = 0;

    /** @var int relative Offset Of Local File Header */
    private $offset = 0;

    /**
     * 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 ExtraFieldsCollection
     */
    private $extraFieldsCollection;

    /** @var string|null comment field */
    private $comment;

    /** @var string entry password for read or write encryption data */
    private $password;

    /**
     * Encryption method.
     *
     * @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
     *
     * @var int
     */
    private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;

    /** @var int */
    private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;

    /**
     * ZipAbstractEntry constructor.
     */
    public function __construct()
    {
        $this->extraFieldsCollection = new ExtraFieldsCollection();
    }

    /**
     * @param ZipEntry $entry
     *
     * @throws ZipException
     */
    public function setEntry(ZipEntry $entry)
    {
        $this->setName($entry->getName());
        $this->setSoftwareVersion($entry->getSoftwareVersion());
        $this->setCreatedOS($entry->getCreatedOS());
        $this->setExtractedOS($entry->getExtractedOS());
        $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->setInternalAttributes($entry->getInternalAttributes());
        $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());
    }

    /**
     * Returns the ZIP entry name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set entry name.
     *
     * @param string $name New entry name
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setName($name)
    {
        $length = \strlen($name);

        if ($length < 0x0000 || $length > 0xffff) {
            throw new ZipException('Illegal zip entry name parameter');
        }
        $this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
        $this->name = $name;
        $this->externalAttributes = $this->isDirectory() ? 0x10 : 0;

        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->generalPurposeBitFlags |= $mask;
        } else {
            $this->generalPurposeBitFlags &= ~$mask;
        }

        return $this;
    }

    /**
     * @return int Get platform
     *
     * @deprecated Use {@see ZipEntry::getCreatedOS()}
     * @noinspection PhpUsageOfSilenceOperatorInspection
     */
    public function getPlatform()
    {
        @trigger_error('ZipEntry::getPlatform() is deprecated. Use ZipEntry::getCreatedOS()', \E_USER_DEPRECATED);

        return $this->getCreatedOS();
    }

    /**
     * @param int $platform
     *
     * @throws ZipException
     *
     * @return ZipEntry
     *
     * @deprecated Use {@see ZipEntry::setCreatedOS()}
     * @noinspection PhpUsageOfSilenceOperatorInspection
     */
    public function setPlatform($platform)
    {
        @trigger_error('ZipEntry::setPlatform() is deprecated. Use ZipEntry::setCreatedOS()', \E_USER_DEPRECATED);

        return $this->setCreatedOS($platform);
    }

    /**
     * @return int platform
     */
    public function getCreatedOS()
    {
        return $this->createdOS;
    }

    /**
     * Set platform.
     *
     * @param int $platform
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setCreatedOS($platform)
    {
        $platform = (int) $platform;

        if ($platform < 0x00 || $platform > 0xff) {
            throw new ZipException('Platform out of range');
        }
        $this->createdOS = $platform;

        return $this;
    }

    /**
     * @return int
     */
    public function getExtractedOS()
    {
        return $this->extractedOS;
    }

    /**
     * Set extracted OS.
     *
     * @param int $platform
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setExtractedOS($platform)
    {
        $platform = (int) $platform;

        if ($platform < 0x00 || $platform > 0xff) {
            throw new ZipException('Platform out of range');
        }
        $this->extractedOS = $platform;

        return $this;
    }

    /**
     * @return int
     */
    public function getSoftwareVersion()
    {
        return $this->softwareVersion;
    }

    /**
     * @param int $softwareVersion
     *
     * @return ZipEntry
     */
    public function setSoftwareVersion($softwareVersion)
    {
        $this->softwareVersion = (int) $softwareVersion;

        return $this;
    }

    /**
     * Version needed to extract.
     *
     * @return int
     */
    public function getVersionNeededToExtract()
    {
        if ($this->versionNeededToExtract === self::UNKNOWN) {
            $method = $this->getMethod();

            if ($method === self::METHOD_WINZIP_AES) {
                return 51;
            }

            if ($method === ZipFile::METHOD_BZIP2) {
                return 46;
            }

            if ($this->isZip64ExtensionsRequired()) {
                return 45;
            }

            return $method === ZipFile::METHOD_DEFLATED || $this->isDirectory() ? 20 : 10;
        }

        return $this->versionNeededToExtract;
    }

    /**
     * Set version needed to extract.
     *
     * @param int $version
     *
     * @return ZipEntry
     */
    public function setVersionNeededToExtract($version)
    {
        $this->versionNeededToExtract = $version;

        return $this;
    }

    /**
     * @return bool
     */
    public function isZip64ExtensionsRequired()
    {
        return $this->getCompressedSize() >= 0xffffffff
            || $this->getSize() >= 0xffffffff;
    }

    /**
     * Returns the compressed size of this entry.
     *
     * @see int
     */
    public function getCompressedSize()
    {
        return $this->compressedSize;
    }

    /**
     * Sets the compressed size of this entry.
     *
     * @param int $compressedSize the Compressed Size
     *
     * @return ZipEntry
     */
    public function setCompressedSize($compressedSize)
    {
        $this->compressedSize = $compressedSize;

        return $this;
    }

    /**
     * Returns the uncompressed size of this entry.
     *
     * @see ZipEntry::setCompressedSize
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * Sets the uncompressed size of this entry.
     *
     * @param int $size the (Uncompressed) Size
     *
     * @return ZipEntry
     */
    public function setSize($size)
    {
        $this->size = $size;

        return $this;
    }

    /**
     * Return relative Offset Of Local File Header.
     *
     * @return int
     */
    public function getOffset()
    {
        return $this->offset;
    }

    /**
     * @param int $offset
     *
     * @return ZipEntry
     */
    public function setOffset($offset)
    {
        $this->offset = (int) $offset;

        return $this;
    }

    /**
     * Returns the General Purpose Bit Flags.
     *
     * @return int
     */
    public function getGeneralPurposeBitFlags()
    {
        return $this->generalPurposeBitFlags & 0xffff;
    }

    /**
     * Sets the General Purpose Bit Flags.
     *
     * @param mixed $general
     *
     * @throws ZipException
     *
     * @return ZipEntry
     *
     * @var int general
     */
    public function setGeneralPurposeBitFlags($general)
    {
        if ($general < 0x0000 || $general > 0xffff) {
            throw new ZipException('general out of range');
        }
        $this->generalPurposeBitFlags = $general;

        if ($this->method === ZipFile::METHOD_DEFLATED) {
            $bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1);
            $bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2);

            if ($bit1 && !$bit2) {
                $this->compressionLevel = ZipFile::LEVEL_BEST_COMPRESSION;
            } elseif (!$bit1 && $bit2) {
                $this->compressionLevel = ZipFile::LEVEL_FAST;
            } elseif ($bit1 && $bit2) {
                $this->compressionLevel = ZipFile::LEVEL_SUPER_FAST;
            } else {
                $this->compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
            }
        }

        return $this;
    }

    /**
     * Returns true if and only if this ZIP entry is encrypted.
     *
     * @return bool
     */
    public function isEncrypted()
    {
        return $this->getGeneralPurposeBitFlag(self::GPBF_ENCRYPTED);
    }

    /**
     * Returns the indexed General Purpose Bit Flag.
     *
     * @param int $mask
     *
     * @return bool
     */
    public function getGeneralPurposeBitFlag($mask)
    {
        return ($this->generalPurposeBitFlags & $mask) !== 0;
    }

    /**
     * Sets the encryption property to false and removes any other
     * encryption artifacts.
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function disableEncryption()
    {
        $this->setEncrypted(false);
        $headerId = WinZipAesEntryExtraField::getHeaderId();

        if (isset($this->extraFieldsCollection[$headerId])) {
            /** @var WinZipAesEntryExtraField $field */
            $field = $this->extraFieldsCollection[$headerId];

            if ($this->getMethod() === self::METHOD_WINZIP_AES) {
                $this->setMethod($field === null ? self::UNKNOWN : $field->getMethod());
            }
            unset($this->extraFieldsCollection[$headerId]);
        }
        $this->password = null;

        return $this;
    }

    /**
     * Sets the encryption flag for this ZIP entry.
     *
     * @param bool $encrypted
     *
     * @return ZipEntry
     */
    public function setEncrypted($encrypted)
    {
        $encrypted = (bool) $encrypted;
        $this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);

        return $this;
    }

    /**
     * Returns the compression method for this entry.
     *
     * @return int
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * Sets the compression method for this entry.
     *
     * @param int $method
     *
     * @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
     *
     * @return ZipEntry
     */
    public function setMethod($method)
    {
        if ($method === self::UNKNOWN) {
            $this->method = $method;

            return $this;
        }

        if ($method < 0x0000 || $method > 0xffff) {
            throw new ZipException('method out of range: ' . $method);
        }
        switch ($method) {
            case self::METHOD_WINZIP_AES:
            case ZipFile::METHOD_STORED:
            case ZipFile::METHOD_DEFLATED:
            case ZipFile::METHOD_BZIP2:
                $this->method = $method;
                break;

            default:
                throw new ZipException($this->name . " (unsupported compression method {$method})");
        }

        return $this;
    }

    /**
     * Get Unix Timestamp.
     *
     * @return int
     */
    public function getTime()
    {
        if ($this->getDosTime() === self::UNKNOWN) {
            return self::UNKNOWN;
        }

        return DateTimeConverter::toUnixTimestamp($this->getDosTime());
    }

    /**
     * Get Dos Time.
     *
     * @return int
     */
    public function getDosTime()
    {
        return $this->dosTime;
    }

    /**
     * Set Dos Time.
     *
     * @param int $dosTime
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setDosTime($dosTime)
    {
        $dosTime = (int) $dosTime;

        if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
            throw new ZipException('DosTime out of range');
        }
        $this->dosTime = $dosTime;

        return $this;
    }

    /**
     * Set time from unix timestamp.
     *
     * @param int $unixTimestamp
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setTime($unixTimestamp)
    {
        $known = $unixTimestamp !== self::UNKNOWN;

        if ($known) {
            $this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
        } else {
            $this->dosTime = 0;
        }

        return $this;
    }

    /**
     * Returns the external file attributes.
     *
     * @return int the external file attributes
     */
    public function getExternalAttributes()
    {
        return $this->externalAttributes;
    }

    /**
     * Sets the external file attributes.
     *
     * @param int $externalAttributes the external file attributes
     *
     * @return ZipEntry
     */
    public function setExternalAttributes($externalAttributes)
    {
        $this->externalAttributes = $externalAttributes;

        return $this;
    }

    /**
     * Sets the internal file attributes.
     *
     * @param int $attributes the internal file attributes
     *
     * @return ZipEntry
     */
    public function setInternalAttributes($attributes)
    {
        $this->internalAttributes = (int) $attributes;

        return $this;
    }

    /**
     * Returns the internal file attributes.
     *
     * @return int the internal file attributes
     */
    public function getInternalAttributes()
    {
        return $this->internalAttributes;
    }

    /**
     * Returns true if and only if this ZIP entry represents a directory entry
     * (i.e. end with '/').
     *
     * @return bool
     */
    public function isDirectory()
    {
        return StringUtil::endsWith($this->name, '/');
    }

    /**
     * @return ExtraFieldsCollection
     */
    public function &getExtraFieldsCollection()
    {
        return $this->extraFieldsCollection;
    }

    /**
     * Returns a protective copy of the serialized Extra Fields.
     *
     * @throws ZipException
     *
     * @return string
     */
    public function getExtra()
    {
        return ExtraFieldsFactory::createSerializedData($this->extraFieldsCollection);
    }

    /**
     * Sets the serialized Extra Fields by making a protective copy.
     * Note that this method parses the serialized Extra Fields according to
     * the ZIP File Format Specification and limits its size to 64 KB.
     * Therefore, this property cannot not be used to hold arbitrary
     * (application) data.
     * Consider storing such data in a separate entry instead.
     *
     * @param string $data the byte array holding the serialized Extra Fields
     *
     * @throws ZipException if the serialized Extra Fields exceed 64 KB
     *
     * @return ZipEntry
     */
    public function setExtra($data)
    {
        $this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);

        return $this;
    }

    /**
     * Returns comment entry.
     *
     * @return string
     */
    public function getComment()
    {
        return $this->comment !== null ? $this->comment : '';
    }

    /**
     * Set entry comment.
     *
     * @param string|null $comment
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setComment($comment)
    {
        if ($comment !== null) {
            $commentLength = \strlen($comment);

            if ($commentLength < 0x0000 || $commentLength > 0xffff) {
                throw new ZipException('Comment too long');
            }
            $this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
        }
        $this->comment = $comment;

        return $this;
    }

    /**
     * @return bool
     */
    public function isDataDescriptorRequired()
    {
        return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) === self::UNKNOWN;
    }

    /**
     * Return crc32 content or 0 for WinZip AES v2.
     *
     * @return int
     */
    public function getCrc()
    {
        return $this->crc;
    }

    /**
     * Set crc32 content.
     *
     * @param int $crc
     *
     * @return ZipEntry
     */
    public function setCrc($crc)
    {
        $this->crc = (int) $crc;

        return $this;
    }

    /**
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Set password and encryption method from entry.
     *
     * @param string   $password
     * @param int|null $encryptionMethod
     *
     * @throws ZipException
     *
     * @return ZipEntry
     */
    public function setPassword($password, $encryptionMethod = null)
    {
        $this->password = $password;

        if ($encryptionMethod !== null) {
            $this->setEncryptionMethod($encryptionMethod);
        }

        if (!empty($this->password)) {
            $this->setEncrypted(true);
        } else {
            $this->disableEncryption();
        }

        return $this;
    }

    /**
     * @return int
     */
    public function getEncryptionMethod()
    {
        return $this->encryptionMethod;
    }

    /**
     * Set encryption method.
     *
     * @param int $encryptionMethod
     *
     * @throws ZipException
     *
     * @return ZipEntry
     *
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
     * @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
     * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
     */
    public function setEncryptionMethod($encryptionMethod)
    {
        if ($encryptionMethod !== null) {
            if (
                $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_TRADITIONAL
                && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
                && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
                && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
            ) {
                throw new ZipException('Invalid encryption method');
            }
            $this->encryptionMethod = $encryptionMethod;
        }

        return $this;
    }

    /**
     * @return int
     */
    public function getCompressionLevel()
    {
        return $this->compressionLevel;
    }

    /**
     * @param int $compressionLevel
     *
     * @return ZipEntry
     */
    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 $this;
    }

    /**
     * Clone extra fields.
     */
    public function __clone()
    {
        $this->extraFieldsCollection = clone $this->extraFieldsCollection;
    }
}