init
- 框架初始化 - 安装插件 - 修复PHP8.4报错
This commit is contained in:
39
vendor/nelexa/zip/src/Constants/DosAttrs.php
vendored
Normal file
39
vendor/nelexa/zip/src/Constants/DosAttrs.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
interface DosAttrs
|
||||
{
|
||||
/** @var int DOS File Attribute Read Only */
|
||||
public const DOS_READ_ONLY = 0x01;
|
||||
|
||||
/** @var int DOS File Attribute Hidden */
|
||||
public const DOS_HIDDEN = 0x02;
|
||||
|
||||
/** @var int DOS File Attribute System */
|
||||
public const DOS_SYSTEM = 0x04;
|
||||
|
||||
/** @var int DOS File Attribute Label */
|
||||
public const DOS_LABEL = 0x08;
|
||||
|
||||
/** @var int DOS File Attribute Directory */
|
||||
public const DOS_DIRECTORY = 0x10;
|
||||
|
||||
/** @var int DOS File Attribute Archive */
|
||||
public const DOS_ARCHIVE = 0x20;
|
||||
|
||||
/** @var int DOS File Attribute Link */
|
||||
public const DOS_LINK = 0x40;
|
||||
|
||||
/** @var int DOS File Attribute Execute */
|
||||
public const DOS_EXE = 0x80;
|
||||
}
|
||||
103
vendor/nelexa/zip/src/Constants/DosCodePage.php
vendored
Normal file
103
vendor/nelexa/zip/src/Constants/DosCodePage.php
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
final class DosCodePage
|
||||
{
|
||||
public const CP_LATIN_US = 'cp437';
|
||||
|
||||
public const CP_GREEK = 'cp737';
|
||||
|
||||
public const CP_BALT_RIM = 'cp775';
|
||||
|
||||
public const CP_LATIN1 = 'cp850';
|
||||
|
||||
public const CP_LATIN2 = 'cp852';
|
||||
|
||||
public const CP_CYRILLIC = 'cp855';
|
||||
|
||||
public const CP_TURKISH = 'cp857';
|
||||
|
||||
public const CP_PORTUGUESE = 'cp860';
|
||||
|
||||
public const CP_ICELANDIC = 'cp861';
|
||||
|
||||
public const CP_HEBREW = 'cp862';
|
||||
|
||||
public const CP_CANADA = 'cp863';
|
||||
|
||||
public const CP_ARABIC = 'cp864';
|
||||
|
||||
public const CP_NORDIC = 'cp865';
|
||||
|
||||
public const CP_CYRILLIC_RUSSIAN = 'cp866';
|
||||
|
||||
public const CP_GREEK2 = 'cp869';
|
||||
|
||||
public const CP_THAI = 'cp874';
|
||||
|
||||
/** @var string[] */
|
||||
private const CP_CHARSETS = [
|
||||
self::CP_LATIN_US,
|
||||
self::CP_GREEK,
|
||||
self::CP_BALT_RIM,
|
||||
self::CP_LATIN1,
|
||||
self::CP_LATIN2,
|
||||
self::CP_CYRILLIC,
|
||||
self::CP_TURKISH,
|
||||
self::CP_PORTUGUESE,
|
||||
self::CP_ICELANDIC,
|
||||
self::CP_HEBREW,
|
||||
self::CP_CANADA,
|
||||
self::CP_ARABIC,
|
||||
self::CP_NORDIC,
|
||||
self::CP_CYRILLIC_RUSSIAN,
|
||||
self::CP_GREEK2,
|
||||
self::CP_THAI,
|
||||
];
|
||||
|
||||
/**
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
public static function toUTF8(string $str, ?string $sourceEncoding): string
|
||||
{
|
||||
$s = iconv($sourceEncoding, 'UTF-8', $str);
|
||||
|
||||
if ($s === false) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
public static function fromUTF8(string $str, ?string $destEncoding): string
|
||||
{
|
||||
$s = iconv('UTF-8', $destEncoding, $str);
|
||||
|
||||
if ($s === false) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getCodePages(): array
|
||||
{
|
||||
return self::CP_CHARSETS;
|
||||
}
|
||||
}
|
||||
77
vendor/nelexa/zip/src/Constants/GeneralPurposeBitFlag.php
vendored
Normal file
77
vendor/nelexa/zip/src/Constants/GeneralPurposeBitFlag.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
interface GeneralPurposeBitFlag
|
||||
{
|
||||
/**
|
||||
* General Purpose Bit Flag mask for encrypted data.
|
||||
* Bit 0: If set, indicates that the file is encrypted.
|
||||
*/
|
||||
public const ENCRYPTION = 1 << 0;
|
||||
|
||||
/**
|
||||
* Compression Flag Bit 1 for method Deflating.
|
||||
*
|
||||
* Bit 2 Bit 1
|
||||
* 0 0 Normal compression
|
||||
* 0 1 Maximum compression
|
||||
* 1 0 Fast compression
|
||||
* 1 1 Super Fast compression
|
||||
*
|
||||
* @see GeneralPurposeBitFlag::COMPRESSION_FLAG2
|
||||
*/
|
||||
public const COMPRESSION_FLAG1 = 1 << 1;
|
||||
|
||||
/**
|
||||
* Compression Flag Bit 2 for method Deflating.
|
||||
*
|
||||
* Bit 2 Bit 1
|
||||
* 0 0 Normal compression
|
||||
* 0 1 Maximum compression
|
||||
* 1 0 Fast compression
|
||||
* 1 1 Super Fast compression
|
||||
*
|
||||
* @see GeneralPurposeBitFlag::COMPRESSION_FLAG1
|
||||
*/
|
||||
public const COMPRESSION_FLAG2 = 1 << 2;
|
||||
|
||||
/**
|
||||
* General Purpose Bit Flag mask for data descriptor.
|
||||
*
|
||||
* Bit 3: If this bit is set, the fields crc-32, compressed
|
||||
* size and uncompressed size are set to zero in the
|
||||
* local header. The correct values are put in the data
|
||||
* descriptor immediately following the compressed data.
|
||||
*/
|
||||
public const DATA_DESCRIPTOR = 1 << 3;
|
||||
|
||||
/**
|
||||
* General Purpose Bit Flag mask for strong encryption.
|
||||
*
|
||||
* Bit 6: Strong encryption.
|
||||
* If this bit is set, you MUST set the version needed to extract
|
||||
* value to at least 50 and you MUST also set bit 0.
|
||||
* If AES encryption is used, the version needed to extract value
|
||||
* MUST be at least 51.
|
||||
*/
|
||||
public const STRONG_ENCRYPTION = 1 << 6;
|
||||
|
||||
/**
|
||||
* General Purpose Bit Flag mask for UTF-8.
|
||||
*
|
||||
* Bit 11: Language encoding flag (EFS).
|
||||
* If this bit is set, the filename and comment fields
|
||||
* for this file MUST be encoded using UTF-8. (see APPENDIX D)
|
||||
*/
|
||||
public const UTF8 = 1 << 11;
|
||||
}
|
||||
90
vendor/nelexa/zip/src/Constants/UnixStat.php
vendored
Normal file
90
vendor/nelexa/zip/src/Constants/UnixStat.php
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
/**
|
||||
* Unix stat constants.
|
||||
*/
|
||||
interface UnixStat
|
||||
{
|
||||
/** @var int unix file type mask */
|
||||
public const UNX_IFMT = 0170000;
|
||||
|
||||
/** @var int unix regular file */
|
||||
public const UNX_IFREG = 0100000;
|
||||
|
||||
/** @var int unix socket (BSD, not SysV or Amiga) */
|
||||
public const UNX_IFSOCK = 0140000;
|
||||
|
||||
/** @var int unix symbolic link (not SysV, Amiga) */
|
||||
public const UNX_IFLNK = 0120000;
|
||||
|
||||
/** @var int unix block special (not Amiga) */
|
||||
public const UNX_IFBLK = 0060000;
|
||||
|
||||
/** @var int unix directory */
|
||||
public const UNX_IFDIR = 0040000;
|
||||
|
||||
/** @var int unix character special (not Amiga) */
|
||||
public const UNX_IFCHR = 0020000;
|
||||
|
||||
/** @var int unix fifo (BCC, not MSC or Amiga) */
|
||||
public const UNX_IFIFO = 0010000;
|
||||
|
||||
/** @var int unix set user id on execution */
|
||||
public const UNX_ISUID = 04000;
|
||||
|
||||
/** @var int unix set group id on execution */
|
||||
public const UNX_ISGID = 02000;
|
||||
|
||||
/** @var int unix directory permissions control */
|
||||
public const UNX_ISVTX = 01000;
|
||||
|
||||
/** @var int unix record locking enforcement flag */
|
||||
public const UNX_ENFMT = 02000;
|
||||
|
||||
/** @var int unix read, write, execute: owner */
|
||||
public const UNX_IRWXU = 00700;
|
||||
|
||||
/** @var int unix read permission: owner */
|
||||
public const UNX_IRUSR = 00400;
|
||||
|
||||
/** @var int unix write permission: owner */
|
||||
public const UNX_IWUSR = 00200;
|
||||
|
||||
/** @var int unix execute permission: owner */
|
||||
public const UNX_IXUSR = 00100;
|
||||
|
||||
/** @var int unix read, write, execute: group */
|
||||
public const UNX_IRWXG = 00070;
|
||||
|
||||
/** @var int unix read permission: group */
|
||||
public const UNX_IRGRP = 00040;
|
||||
|
||||
/** @var int unix write permission: group */
|
||||
public const UNX_IWGRP = 00020;
|
||||
|
||||
/** @var int unix execute permission: group */
|
||||
public const UNX_IXGRP = 00010;
|
||||
|
||||
/** @var int unix read, write, execute: other */
|
||||
public const UNX_IRWXO = 00007;
|
||||
|
||||
/** @var int unix read permission: other */
|
||||
public const UNX_IROTH = 00004;
|
||||
|
||||
/** @var int unix write permission: other */
|
||||
public const UNX_IWOTH = 00002;
|
||||
|
||||
/** @var int unix execute permission: other */
|
||||
public const UNX_IXOTH = 00001;
|
||||
}
|
||||
63
vendor/nelexa/zip/src/Constants/ZipCompressionLevel.php
vendored
Normal file
63
vendor/nelexa/zip/src/Constants/ZipCompressionLevel.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
/**
|
||||
* Compression levels for Deflate and BZIP2.
|
||||
*
|
||||
* {@see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT} Section 4.4.4:
|
||||
*
|
||||
* For Methods 8 and 9 - Deflating
|
||||
* -------------------------------
|
||||
* Bit 2 Bit 1
|
||||
* 0 0 Normal (-en) compression option was used.
|
||||
* 0 1 Maximum (-exx/-ex) compression option was used.
|
||||
* 1 0 Fast (-ef) compression option was used.
|
||||
* 1 1 Super Fast (-es) compression option was used.
|
||||
*
|
||||
* Different programs encode compression level information in different ways:
|
||||
*
|
||||
* Deflate Compress Level pkzip zip 7z, WinRAR WinZip
|
||||
* ---------------------- ---------------- ------- ---------- ------
|
||||
* Super Fast compression 1 1
|
||||
* Fast compression 2 1, 2
|
||||
* Normal Compression 3 - 8 (5 default) 3 - 7 1 - 9
|
||||
* Maximum compression 9 8, 9 9
|
||||
*/
|
||||
interface ZipCompressionLevel
|
||||
{
|
||||
/** @var int Compression level for super fast compression. */
|
||||
public const SUPER_FAST = 1;
|
||||
|
||||
/** @var int compression level for fast compression */
|
||||
public const FAST = 2;
|
||||
|
||||
/** @var int compression level for normal compression */
|
||||
public const NORMAL = 5;
|
||||
|
||||
/** @var int compression level for maximum compression */
|
||||
public const MAXIMUM = 9;
|
||||
|
||||
/**
|
||||
* @var int int Minimum compression level
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const LEVEL_MIN = self::SUPER_FAST;
|
||||
|
||||
/**
|
||||
* @var int int Maximum compression level
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const LEVEL_MAX = self::MAXIMUM;
|
||||
}
|
||||
97
vendor/nelexa/zip/src/Constants/ZipCompressionMethod.php
vendored
Normal file
97
vendor/nelexa/zip/src/Constants/ZipCompressionMethod.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
use PhpZip\Exception\ZipUnsupportMethodException;
|
||||
|
||||
final class ZipCompressionMethod
|
||||
{
|
||||
/** @var int Compression method Store */
|
||||
public const STORED = 0;
|
||||
|
||||
/** @var int Compression method Deflate */
|
||||
public const DEFLATED = 8;
|
||||
|
||||
/** @var int Compression method Bzip2 */
|
||||
public const BZIP2 = 12;
|
||||
|
||||
/** @var int Compression method AES-Encryption */
|
||||
public const WINZIP_AES = 99;
|
||||
|
||||
/** @var array Compression Methods */
|
||||
private const ZIP_COMPRESSION_METHODS = [
|
||||
self::STORED => 'Stored',
|
||||
1 => 'Shrunk',
|
||||
2 => 'Reduced compression factor 1',
|
||||
3 => 'Reduced compression factor 2',
|
||||
4 => 'Reduced compression factor 3',
|
||||
5 => 'Reduced compression factor 4',
|
||||
6 => 'Imploded',
|
||||
7 => 'Reserved for Tokenizing compression algorithm',
|
||||
self::DEFLATED => 'Deflated',
|
||||
9 => 'Enhanced Deflating using Deflate64(tm)',
|
||||
10 => 'PKWARE Data Compression Library Imploding',
|
||||
11 => 'Reserved by PKWARE',
|
||||
self::BZIP2 => 'BZIP2',
|
||||
13 => 'Reserved by PKWARE',
|
||||
14 => 'LZMA',
|
||||
15 => 'Reserved by PKWARE',
|
||||
16 => 'Reserved by PKWARE',
|
||||
17 => 'Reserved by PKWARE',
|
||||
18 => 'File is compressed using IBM TERSE (new)',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
96 => 'WinZip JPEG Compression',
|
||||
97 => 'WavPack compressed data',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
self::WINZIP_AES => 'AES Encryption',
|
||||
];
|
||||
|
||||
public static function getCompressionMethodName(int $value): string
|
||||
{
|
||||
return self::ZIP_COMPRESSION_METHODS[$value] ?? 'Unknown Method';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public static function getSupportMethods(): array
|
||||
{
|
||||
static $methods;
|
||||
|
||||
if ($methods === null) {
|
||||
$methods = [
|
||||
self::STORED,
|
||||
self::DEFLATED,
|
||||
];
|
||||
|
||||
if (\extension_loaded('bz2')) {
|
||||
$methods[] = self::BZIP2;
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipUnsupportMethodException
|
||||
*/
|
||||
public static function checkSupport(int $compressionMethod): void
|
||||
{
|
||||
if (!\in_array($compressionMethod, self::getSupportMethods(), true)) {
|
||||
throw new ZipUnsupportMethodException(sprintf(
|
||||
'Compression method %d (%s) is not supported.',
|
||||
$compressionMethod,
|
||||
self::getCompressionMethodName($compressionMethod)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
105
vendor/nelexa/zip/src/Constants/ZipConstants.php
vendored
Normal file
105
vendor/nelexa/zip/src/Constants/ZipConstants.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
/**
|
||||
* Zip Constants.
|
||||
*/
|
||||
interface ZipConstants
|
||||
{
|
||||
/** @var int End Of Central Directory Record signature. */
|
||||
public const END_CD = 0x06054B50; // "PK\005\006"
|
||||
|
||||
/** @var int Zip64 End Of Central Directory Record. */
|
||||
public const ZIP64_END_CD = 0x06064B50; // "PK\006\006"
|
||||
|
||||
/** @var int Zip64 End Of Central Directory Locator. */
|
||||
public const ZIP64_END_CD_LOC = 0x07064B50; // "PK\006\007"
|
||||
|
||||
/** @var int Central File Header signature. */
|
||||
public const CENTRAL_FILE_HEADER = 0x02014B50; // "PK\001\002"
|
||||
|
||||
/** @var int Local File Header signature. */
|
||||
public const LOCAL_FILE_HEADER = 0x04034B50; // "PK\003\004"
|
||||
|
||||
/** @var int Data Descriptor signature. */
|
||||
public const DATA_DESCRIPTOR = 0x08074B50; // "PK\007\008"
|
||||
|
||||
/**
|
||||
* @var int value stored in four-byte size and similar fields
|
||||
* if ZIP64 extensions are used
|
||||
*/
|
||||
public const ZIP64_MAGIC = 0xFFFFFFFF;
|
||||
|
||||
/**
|
||||
* Local File Header signature 4
|
||||
* Version Needed To Extract 2
|
||||
* General Purpose Bit Flags 2
|
||||
* Compression Method 2
|
||||
* Last Mod File Time 2
|
||||
* Last Mod File Date 2
|
||||
* CRC-32 4
|
||||
* Compressed Size 4
|
||||
* Uncompressed Size 4.
|
||||
*
|
||||
* @var int Local File Header filename position
|
||||
*/
|
||||
public const LFH_FILENAME_LENGTH_POS = 26;
|
||||
|
||||
/**
|
||||
* The minimum length of the Local File Header record.
|
||||
*
|
||||
* local file header signature 4
|
||||
* version needed to extract 2
|
||||
* general purpose bit flag 2
|
||||
* compression method 2
|
||||
* last mod file time 2
|
||||
* last mod file date 2
|
||||
* crc-32 4
|
||||
* compressed size 4
|
||||
* uncompressed size 4
|
||||
* file name length 2
|
||||
* extra field length 2
|
||||
*/
|
||||
public const LFH_FILENAME_POS = 30;
|
||||
|
||||
/** @var int the length of the Zip64 End Of Central Directory Locator */
|
||||
public const ZIP64_END_CD_LOC_LEN = 20;
|
||||
|
||||
/** @var int the minimum length of the End Of Central Directory Record */
|
||||
public const END_CD_MIN_LEN = 22;
|
||||
|
||||
/**
|
||||
* The minimum length of the Zip64 End Of Central Directory Record.
|
||||
*
|
||||
* zip64 end of central dir
|
||||
* signature 4
|
||||
* size of zip64 end of central
|
||||
* directory record 8
|
||||
* version made by 2
|
||||
* version needed to extract 2
|
||||
* number of this disk 4
|
||||
* number of the disk with the
|
||||
* start of the central directory 4
|
||||
* total number of entries in the
|
||||
* central directory on this disk 8
|
||||
* total number of entries in
|
||||
* the central directory 8
|
||||
* size of the central directory 8
|
||||
* offset of start of central
|
||||
* directory with respect to
|
||||
* the starting disk number 8
|
||||
*
|
||||
* @var int ZIP64 End Of Central Directory length
|
||||
*/
|
||||
public const ZIP64_END_OF_CD_LEN = 56;
|
||||
}
|
||||
76
vendor/nelexa/zip/src/Constants/ZipEncryptionMethod.php
vendored
Normal file
76
vendor/nelexa/zip/src/Constants/ZipEncryptionMethod.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
|
||||
final class ZipEncryptionMethod
|
||||
{
|
||||
public const NONE = -1;
|
||||
|
||||
/** @var int Traditional PKWARE encryption. */
|
||||
public const PKWARE = 0;
|
||||
|
||||
/** @var int WinZip AES-256 */
|
||||
public const WINZIP_AES_256 = 1;
|
||||
|
||||
/** @var int WinZip AES-128 */
|
||||
public const WINZIP_AES_128 = 2;
|
||||
|
||||
/** @var int WinZip AES-192 */
|
||||
public const WINZIP_AES_192 = 3;
|
||||
|
||||
/** @var array<int, string> */
|
||||
private const ENCRYPTION_METHODS = [
|
||||
self::NONE => 'no encryption',
|
||||
self::PKWARE => 'Traditional PKWARE encryption',
|
||||
self::WINZIP_AES_128 => 'WinZip AES-128',
|
||||
self::WINZIP_AES_192 => 'WinZip AES-192',
|
||||
self::WINZIP_AES_256 => 'WinZip AES-256',
|
||||
];
|
||||
|
||||
public static function getEncryptionMethodName(int $value): string
|
||||
{
|
||||
return self::ENCRYPTION_METHODS[$value] ?? 'Unknown Encryption Method';
|
||||
}
|
||||
|
||||
public static function hasEncryptionMethod(int $encryptionMethod): bool
|
||||
{
|
||||
return isset(self::ENCRYPTION_METHODS[$encryptionMethod]);
|
||||
}
|
||||
|
||||
public static function isWinZipAesMethod(int $encryptionMethod): bool
|
||||
{
|
||||
return \in_array(
|
||||
$encryptionMethod,
|
||||
[
|
||||
self::WINZIP_AES_256,
|
||||
self::WINZIP_AES_192,
|
||||
self::WINZIP_AES_128,
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function checkSupport(int $encryptionMethod): void
|
||||
{
|
||||
if (!self::hasEncryptionMethod($encryptionMethod)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Encryption method %d is not supported.',
|
||||
$encryptionMethod
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
68
vendor/nelexa/zip/src/Constants/ZipOptions.php
vendored
Normal file
68
vendor/nelexa/zip/src/Constants/ZipOptions.php
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
use PhpZip\IO\ZipReader;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
interface ZipOptions
|
||||
{
|
||||
/**
|
||||
* Boolean option for store just file names (skip directory names).
|
||||
*
|
||||
* @see ZipFile::addFromFinder()
|
||||
*/
|
||||
public const STORE_ONLY_FILES = 'only_files';
|
||||
|
||||
/**
|
||||
* Uses the specified compression method.
|
||||
*
|
||||
* @see ZipFile::addFromFinder()
|
||||
* @see ZipFile::addSplFile()
|
||||
*/
|
||||
public const COMPRESSION_METHOD = 'compression_method';
|
||||
|
||||
/**
|
||||
* Set the specified record modification time.
|
||||
* The value can be {@see \DateTimeInterface}, integer timestamp
|
||||
* or a string of any format.
|
||||
*
|
||||
* @see ZipFile::addFromFinder()
|
||||
* @see ZipFile::addSplFile()
|
||||
*/
|
||||
public const MODIFIED_TIME = 'mtime';
|
||||
|
||||
/**
|
||||
* Specifies the encoding of the record name for cases when the UTF-8
|
||||
* usage flag is not set.
|
||||
*
|
||||
* The most commonly used encodings are compiled into the constants
|
||||
* of the {@see DosCodePage} class.
|
||||
*
|
||||
* @see ZipFile::openFile()
|
||||
* @see ZipFile::openFromString()
|
||||
* @see ZipFile::openFromStream()
|
||||
* @see ZipReader::getDefaultOptions()
|
||||
* @see DosCodePage::getCodePages()
|
||||
*/
|
||||
public const CHARSET = 'charset';
|
||||
|
||||
/**
|
||||
* Allows ({@see true}) or denies ({@see false}) unpacking unix symlinks.
|
||||
*
|
||||
* This is a potentially dangerous operation for uncontrolled zip files.
|
||||
* By default is ({@see false}).
|
||||
*
|
||||
* @see https://josipfranjkovic.blogspot.com/2014/12/reading-local-files-from-facebooks.html
|
||||
*/
|
||||
public const EXTRACT_SYMLINKS = 'extract_symlinks';
|
||||
}
|
||||
54
vendor/nelexa/zip/src/Constants/ZipPlatform.php
vendored
Normal file
54
vendor/nelexa/zip/src/Constants/ZipPlatform.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
final class ZipPlatform
|
||||
{
|
||||
/** @var int MS-DOS OS */
|
||||
public const OS_DOS = 0;
|
||||
|
||||
/** @var int Unix OS */
|
||||
public const OS_UNIX = 3;
|
||||
|
||||
/** @var int MacOS platform */
|
||||
public const OS_MAC_OSX = 19;
|
||||
|
||||
/** @var array Zip Platforms */
|
||||
private const PLATFORMS = [
|
||||
self::OS_DOS => 'MS-DOS',
|
||||
1 => 'Amiga',
|
||||
2 => 'OpenVMS',
|
||||
self::OS_UNIX => 'Unix',
|
||||
4 => 'VM/CMS',
|
||||
5 => 'Atari ST',
|
||||
6 => 'HPFS (OS/2, NT 3.x)',
|
||||
7 => 'Macintosh',
|
||||
8 => 'Z-System',
|
||||
9 => 'CP/M',
|
||||
10 => 'Windows NTFS or TOPS-20',
|
||||
11 => 'MVS or NTFS',
|
||||
12 => 'VSE or SMS/QDOS',
|
||||
13 => 'Acorn RISC OS',
|
||||
14 => 'VFAT',
|
||||
15 => 'alternate MVS',
|
||||
16 => 'BeOS',
|
||||
17 => 'Tandem',
|
||||
18 => 'OS/400',
|
||||
self::OS_MAC_OSX => 'OS/X (Darwin)',
|
||||
30 => 'AtheOS/Syllable',
|
||||
];
|
||||
|
||||
public static function getPlatformName(int $platform): string
|
||||
{
|
||||
return self::PLATFORMS[$platform] ?? 'Unknown';
|
||||
}
|
||||
}
|
||||
87
vendor/nelexa/zip/src/Constants/ZipVersion.php
vendored
Normal file
87
vendor/nelexa/zip/src/Constants/ZipVersion.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Constants;
|
||||
|
||||
/**
|
||||
* Version needed to extract or software version.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT Section 4.4.3
|
||||
*/
|
||||
interface ZipVersion
|
||||
{
|
||||
/** @var int 1.0 - Default value */
|
||||
public const v10_DEFAULT_MIN = 10;
|
||||
|
||||
/** @var int 1.1 - File is a volume label */
|
||||
public const v11_FILE_VOLUME_LABEL = 11;
|
||||
|
||||
/**
|
||||
* 2.0 - File is a folder (directory)
|
||||
* 2.0 - File is compressed using Deflate compression
|
||||
* 2.0 - File is encrypted using traditional PKWARE encryption.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const v20_DEFLATED_FOLDER_ZIPCRYPTO = 20;
|
||||
|
||||
/** @var int 2.1 - File is compressed using Deflate64(tm) */
|
||||
public const v21_DEFLATED64 = 21;
|
||||
|
||||
/** @var int 2.5 - File is compressed using PKWARE DCL Implode */
|
||||
public const v25_IMPLODED = 25;
|
||||
|
||||
/** @var int 2.7 - File is a patch data set */
|
||||
public const v27_PATCH_DATA = 27;
|
||||
|
||||
/** @var int 4.5 - File uses ZIP64 format extensions */
|
||||
public const v45_ZIP64_EXT = 45;
|
||||
|
||||
/** @var int 4.6 - File is compressed using BZIP2 compression */
|
||||
public const v46_BZIP2 = 46;
|
||||
|
||||
/**
|
||||
* 5.0 - File is encrypted using DES
|
||||
* 5.0 - File is encrypted using 3DES
|
||||
* 5.0 - File is encrypted using original RC2 encryption
|
||||
* 5.0 - File is encrypted using RC4 encryption.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const v50_ENCR_DES_3DES_RC2_ORIG_RC4 = 50;
|
||||
|
||||
/**
|
||||
* 5.1 - File is encrypted using AES encryption
|
||||
* 5.1 - File is encrypted using corrected RC2 encryption**.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const v51_ENCR_AES_RC2_CORRECT = 51;
|
||||
|
||||
/** @var int 5.2 - File is encrypted using corrected RC2-64 encryption** */
|
||||
public const v52_ENCR_RC2_64_CORRECT = 52;
|
||||
|
||||
/** @var int 6.1 - File is encrypted using non-OAEP key wrapping*** */
|
||||
public const v61_ENCR_NON_OAE_KEY_WRAP = 61;
|
||||
|
||||
/** @var int 6.2 - Central directory encryption */
|
||||
public const v62_ENCR_CENTRAL_DIR = 62;
|
||||
|
||||
/**
|
||||
* 6.3 - File is compressed using LZMA
|
||||
* 6.3 - File is compressed using PPMd+
|
||||
* 6.3 - File is encrypted using Blowfish
|
||||
* 6.3 - File is encrypted using Twofish.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const v63_LZMA_PPMD_BLOWFISH_TWOFISH = 63;
|
||||
}
|
||||
58
vendor/nelexa/zip/src/Exception/Crc32Exception.php
vendored
Normal file
58
vendor/nelexa/zip/src/Exception/Crc32Exception.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate a CRC32 mismatch between the declared value in the
|
||||
* Central File Header and the Data Descriptor or between the declared value
|
||||
* and the computed value from the decompressed data.
|
||||
*
|
||||
* The exception detail message is the name of the ZIP entry.
|
||||
*/
|
||||
class Crc32Exception extends ZipException
|
||||
{
|
||||
/** Expected crc. */
|
||||
private int $expectedCrc;
|
||||
|
||||
/** Actual crc. */
|
||||
private int $actualCrc;
|
||||
|
||||
public function __construct(string $name, int $expected, int $actual)
|
||||
{
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
'%s (expected CRC32 value 0x%x, but is actually 0x%x)',
|
||||
$name,
|
||||
$expected,
|
||||
$actual
|
||||
)
|
||||
);
|
||||
$this->expectedCrc = $expected;
|
||||
$this->actualCrc = $actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expected crc.
|
||||
*/
|
||||
public function getExpectedCrc(): int
|
||||
{
|
||||
return $this->expectedCrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns actual crc.
|
||||
*/
|
||||
public function getActualCrc(): int
|
||||
{
|
||||
return $this->actualCrc;
|
||||
}
|
||||
}
|
||||
20
vendor/nelexa/zip/src/Exception/InvalidArgumentException.php
vendored
Normal file
20
vendor/nelexa/zip/src/Exception/InvalidArgumentException.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a method has been passed an illegal or
|
||||
* inappropriate argument.
|
||||
*/
|
||||
class InvalidArgumentException extends RuntimeException
|
||||
{
|
||||
}
|
||||
20
vendor/nelexa/zip/src/Exception/RuntimeException.php
vendored
Normal file
20
vendor/nelexa/zip/src/Exception/RuntimeException.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Runtime exception.
|
||||
* Exception thrown if an error which can only be found on runtime occurs.
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
19
vendor/nelexa/zip/src/Exception/ZipAuthenticationException.php
vendored
Normal file
19
vendor/nelexa/zip/src/Exception/ZipAuthenticationException.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an authenticated ZIP entry has been tampered with.
|
||||
*/
|
||||
class ZipAuthenticationException extends ZipCryptoException
|
||||
{
|
||||
}
|
||||
20
vendor/nelexa/zip/src/Exception/ZipCryptoException.php
vendored
Normal file
20
vendor/nelexa/zip/src/Exception/ZipCryptoException.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if there is an issue when reading or writing an encrypted ZIP file
|
||||
* or entry.
|
||||
*/
|
||||
class ZipCryptoException extends ZipException
|
||||
{
|
||||
}
|
||||
40
vendor/nelexa/zip/src/Exception/ZipEntryNotFoundException.php
vendored
Normal file
40
vendor/nelexa/zip/src/Exception/ZipEntryNotFoundException.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Thrown if entry not found.
|
||||
*/
|
||||
class ZipEntryNotFoundException extends ZipException
|
||||
{
|
||||
private string $entryName;
|
||||
|
||||
/**
|
||||
* @param ZipEntry|string $entryName
|
||||
*/
|
||||
public function __construct($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : $entryName;
|
||||
parent::__construct(sprintf(
|
||||
'Zip Entry "%s" was not found in the archive.',
|
||||
$entryName
|
||||
));
|
||||
$this->entryName = $entryName;
|
||||
}
|
||||
|
||||
public function getEntryName(): string
|
||||
{
|
||||
return $this->entryName;
|
||||
}
|
||||
}
|
||||
21
vendor/nelexa/zip/src/Exception/ZipException.php
vendored
Normal file
21
vendor/nelexa/zip/src/Exception/ZipException.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Signals that a Zip exception of some sort has occurred.
|
||||
*
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
}
|
||||
16
vendor/nelexa/zip/src/Exception/ZipUnsupportMethodException.php
vendored
Normal file
16
vendor/nelexa/zip/src/Exception/ZipUnsupportMethodException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
class ZipUnsupportMethodException extends ZipException
|
||||
{
|
||||
}
|
||||
389
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKCryptContext.php
vendored
Normal file
389
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKCryptContext.php
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\Pkware;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Util\MathUtil;
|
||||
|
||||
/**
|
||||
* Traditional PKWARE Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*/
|
||||
class PKCryptContext
|
||||
{
|
||||
/** @var int Encryption header size */
|
||||
public const STD_DEC_HDR_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Crc table.
|
||||
*
|
||||
* @var int[]|array
|
||||
*/
|
||||
private const CRC_TABLE = [
|
||||
0x00000000,
|
||||
0x77073096,
|
||||
0xEE0E612C,
|
||||
0x990951BA,
|
||||
0x076DC419,
|
||||
0x706AF48F,
|
||||
0xE963A535,
|
||||
0x9E6495A3,
|
||||
0x0EDB8832,
|
||||
0x79DCB8A4,
|
||||
0xE0D5E91E,
|
||||
0x97D2D988,
|
||||
0x09B64C2B,
|
||||
0x7EB17CBD,
|
||||
0xE7B82D07,
|
||||
0x90BF1D91,
|
||||
0x1DB71064,
|
||||
0x6AB020F2,
|
||||
0xF3B97148,
|
||||
0x84BE41DE,
|
||||
0x1ADAD47D,
|
||||
0x6DDDE4EB,
|
||||
0xF4D4B551,
|
||||
0x83D385C7,
|
||||
0x136C9856,
|
||||
0x646BA8C0,
|
||||
0xFD62F97A,
|
||||
0x8A65C9EC,
|
||||
0x14015C4F,
|
||||
0x63066CD9,
|
||||
0xFA0F3D63,
|
||||
0x8D080DF5,
|
||||
0x3B6E20C8,
|
||||
0x4C69105E,
|
||||
0xD56041E4,
|
||||
0xA2677172,
|
||||
0x3C03E4D1,
|
||||
0x4B04D447,
|
||||
0xD20D85FD,
|
||||
0xA50AB56B,
|
||||
0x35B5A8FA,
|
||||
0x42B2986C,
|
||||
0xDBBBC9D6,
|
||||
0xACBCF940,
|
||||
0x32D86CE3,
|
||||
0x45DF5C75,
|
||||
0xDCD60DCF,
|
||||
0xABD13D59,
|
||||
0x26D930AC,
|
||||
0x51DE003A,
|
||||
0xC8D75180,
|
||||
0xBFD06116,
|
||||
0x21B4F4B5,
|
||||
0x56B3C423,
|
||||
0xCFBA9599,
|
||||
0xB8BDA50F,
|
||||
0x2802B89E,
|
||||
0x5F058808,
|
||||
0xC60CD9B2,
|
||||
0xB10BE924,
|
||||
0x2F6F7C87,
|
||||
0x58684C11,
|
||||
0xC1611DAB,
|
||||
0xB6662D3D,
|
||||
0x76DC4190,
|
||||
0x01DB7106,
|
||||
0x98D220BC,
|
||||
0xEFD5102A,
|
||||
0x71B18589,
|
||||
0x06B6B51F,
|
||||
0x9FBFE4A5,
|
||||
0xE8B8D433,
|
||||
0x7807C9A2,
|
||||
0x0F00F934,
|
||||
0x9609A88E,
|
||||
0xE10E9818,
|
||||
0x7F6A0DBB,
|
||||
0x086D3D2D,
|
||||
0x91646C97,
|
||||
0xE6635C01,
|
||||
0x6B6B51F4,
|
||||
0x1C6C6162,
|
||||
0x856530D8,
|
||||
0xF262004E,
|
||||
0x6C0695ED,
|
||||
0x1B01A57B,
|
||||
0x8208F4C1,
|
||||
0xF50FC457,
|
||||
0x65B0D9C6,
|
||||
0x12B7E950,
|
||||
0x8BBEB8EA,
|
||||
0xFCB9887C,
|
||||
0x62DD1DDF,
|
||||
0x15DA2D49,
|
||||
0x8CD37CF3,
|
||||
0xFBD44C65,
|
||||
0x4DB26158,
|
||||
0x3AB551CE,
|
||||
0xA3BC0074,
|
||||
0xD4BB30E2,
|
||||
0x4ADFA541,
|
||||
0x3DD895D7,
|
||||
0xA4D1C46D,
|
||||
0xD3D6F4FB,
|
||||
0x4369E96A,
|
||||
0x346ED9FC,
|
||||
0xAD678846,
|
||||
0xDA60B8D0,
|
||||
0x44042D73,
|
||||
0x33031DE5,
|
||||
0xAA0A4C5F,
|
||||
0xDD0D7CC9,
|
||||
0x5005713C,
|
||||
0x270241AA,
|
||||
0xBE0B1010,
|
||||
0xC90C2086,
|
||||
0x5768B525,
|
||||
0x206F85B3,
|
||||
0xB966D409,
|
||||
0xCE61E49F,
|
||||
0x5EDEF90E,
|
||||
0x29D9C998,
|
||||
0xB0D09822,
|
||||
0xC7D7A8B4,
|
||||
0x59B33D17,
|
||||
0x2EB40D81,
|
||||
0xB7BD5C3B,
|
||||
0xC0BA6CAD,
|
||||
0xEDB88320,
|
||||
0x9ABFB3B6,
|
||||
0x03B6E20C,
|
||||
0x74B1D29A,
|
||||
0xEAD54739,
|
||||
0x9DD277AF,
|
||||
0x04DB2615,
|
||||
0x73DC1683,
|
||||
0xE3630B12,
|
||||
0x94643B84,
|
||||
0x0D6D6A3E,
|
||||
0x7A6A5AA8,
|
||||
0xE40ECF0B,
|
||||
0x9309FF9D,
|
||||
0x0A00AE27,
|
||||
0x7D079EB1,
|
||||
0xF00F9344,
|
||||
0x8708A3D2,
|
||||
0x1E01F268,
|
||||
0x6906C2FE,
|
||||
0xF762575D,
|
||||
0x806567CB,
|
||||
0x196C3671,
|
||||
0x6E6B06E7,
|
||||
0xFED41B76,
|
||||
0x89D32BE0,
|
||||
0x10DA7A5A,
|
||||
0x67DD4ACC,
|
||||
0xF9B9DF6F,
|
||||
0x8EBEEFF9,
|
||||
0x17B7BE43,
|
||||
0x60B08ED5,
|
||||
0xD6D6A3E8,
|
||||
0xA1D1937E,
|
||||
0x38D8C2C4,
|
||||
0x4FDFF252,
|
||||
0xD1BB67F1,
|
||||
0xA6BC5767,
|
||||
0x3FB506DD,
|
||||
0x48B2364B,
|
||||
0xD80D2BDA,
|
||||
0xAF0A1B4C,
|
||||
0x36034AF6,
|
||||
0x41047A60,
|
||||
0xDF60EFC3,
|
||||
0xA867DF55,
|
||||
0x316E8EEF,
|
||||
0x4669BE79,
|
||||
0xCB61B38C,
|
||||
0xBC66831A,
|
||||
0x256FD2A0,
|
||||
0x5268E236,
|
||||
0xCC0C7795,
|
||||
0xBB0B4703,
|
||||
0x220216B9,
|
||||
0x5505262F,
|
||||
0xC5BA3BBE,
|
||||
0xB2BD0B28,
|
||||
0x2BB45A92,
|
||||
0x5CB36A04,
|
||||
0xC2D7FFA7,
|
||||
0xB5D0CF31,
|
||||
0x2CD99E8B,
|
||||
0x5BDEAE1D,
|
||||
0x9B64C2B0,
|
||||
0xEC63F226,
|
||||
0x756AA39C,
|
||||
0x026D930A,
|
||||
0x9C0906A9,
|
||||
0xEB0E363F,
|
||||
0x72076785,
|
||||
0x05005713,
|
||||
0x95BF4A82,
|
||||
0xE2B87A14,
|
||||
0x7BB12BAE,
|
||||
0x0CB61B38,
|
||||
0x92D28E9B,
|
||||
0xE5D5BE0D,
|
||||
0x7CDCEFB7,
|
||||
0x0BDBDF21,
|
||||
0x86D3D2D4,
|
||||
0xF1D4E242,
|
||||
0x68DDB3F8,
|
||||
0x1FDA836E,
|
||||
0x81BE16CD,
|
||||
0xF6B9265B,
|
||||
0x6FB077E1,
|
||||
0x18B74777,
|
||||
0x88085AE6,
|
||||
0xFF0F6A70,
|
||||
0x66063BCA,
|
||||
0x11010B5C,
|
||||
0x8F659EFF,
|
||||
0xF862AE69,
|
||||
0x616BFFD3,
|
||||
0x166CCF45,
|
||||
0xA00AE278,
|
||||
0xD70DD2EE,
|
||||
0x4E048354,
|
||||
0x3903B3C2,
|
||||
0xA7672661,
|
||||
0xD06016F7,
|
||||
0x4969474D,
|
||||
0x3E6E77DB,
|
||||
0xAED16A4A,
|
||||
0xD9D65ADC,
|
||||
0x40DF0B66,
|
||||
0x37D83BF0,
|
||||
0xA9BCAE53,
|
||||
0xDEBB9EC5,
|
||||
0x47B2CF7F,
|
||||
0x30B5FFE9,
|
||||
0xBDBDF21C,
|
||||
0xCABAC28A,
|
||||
0x53B39330,
|
||||
0x24B4A3A6,
|
||||
0xBAD03605,
|
||||
0xCDD70693,
|
||||
0x54DE5729,
|
||||
0x23D967BF,
|
||||
0xB3667A2E,
|
||||
0xC4614AB8,
|
||||
0x5D681B02,
|
||||
0x2A6F2B94,
|
||||
0xB40BBE37,
|
||||
0xC30C8EA1,
|
||||
0x5A05DF1B,
|
||||
0x2D02EF8D,
|
||||
];
|
||||
|
||||
/** @var array encryption keys */
|
||||
private array $keys;
|
||||
|
||||
public function __construct(string $password)
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
|
||||
}
|
||||
|
||||
$this->keys = [
|
||||
305419896,
|
||||
591751049,
|
||||
878082192,
|
||||
];
|
||||
|
||||
foreach (unpack('C*', $password) as $byte) {
|
||||
$this->updateKeys($byte);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function checkHeader(string $header, int $checkByte): void
|
||||
{
|
||||
$byte = 0;
|
||||
|
||||
foreach (unpack('C*', $header) as $byte) {
|
||||
$byte = ($byte ^ $this->decryptByte()) & 0xFF;
|
||||
$this->updateKeys($byte);
|
||||
}
|
||||
|
||||
if ($byte !== $checkByte) {
|
||||
throw new ZipAuthenticationException('Invalid password');
|
||||
}
|
||||
}
|
||||
|
||||
public function decryptString(string $content): string
|
||||
{
|
||||
$decryptContent = '';
|
||||
|
||||
foreach (unpack('C*', $content) as $byte) {
|
||||
$byte = ($byte ^ $this->decryptByte()) & 0xFF;
|
||||
$this->updateKeys($byte);
|
||||
$decryptContent .= \chr($byte);
|
||||
}
|
||||
|
||||
return $decryptContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt byte.
|
||||
*/
|
||||
private function decryptByte(): int
|
||||
{
|
||||
$temp = $this->keys[2] | 2;
|
||||
|
||||
return (($temp * ($temp ^ 1)) >> 8) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update keys.
|
||||
*/
|
||||
private function updateKeys(int $charAt): void
|
||||
{
|
||||
$this->keys[0] = $this->crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] += ($this->keys[0] & 0xFF);
|
||||
$this->keys[1] = MathUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = MathUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update crc.
|
||||
*/
|
||||
private function crc32(int $oldCrc, int $charAt): int
|
||||
{
|
||||
return (($oldCrc >> 8) & 0xFFFFFF) ^ self::CRC_TABLE[($oldCrc ^ $charAt) & 0xFF];
|
||||
}
|
||||
|
||||
public function encryptString(string $content): string
|
||||
{
|
||||
$encryptContent = '';
|
||||
|
||||
foreach (unpack('C*', $content) as $val) {
|
||||
$encryptContent .= pack('c', $this->encryptByte($val));
|
||||
}
|
||||
|
||||
return $encryptContent;
|
||||
}
|
||||
|
||||
private function encryptByte(int $byte): int
|
||||
{
|
||||
$tempVal = $byte ^ $this->decryptByte() & 0xFF;
|
||||
$this->updateKeys($byte);
|
||||
|
||||
return $tempVal;
|
||||
}
|
||||
}
|
||||
116
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php
vendored
Normal file
116
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\Pkware;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Decryption PKWARE Traditional Encryption.
|
||||
*/
|
||||
class PKDecryptionStreamFilter extends \php_user_filter
|
||||
{
|
||||
public const FILTER_NAME = 'phpzip.decryption.pkware';
|
||||
|
||||
private int $checkByte = 0;
|
||||
|
||||
private int $readLength = 0;
|
||||
|
||||
private int $size = 0;
|
||||
|
||||
private bool $readHeader = false;
|
||||
|
||||
private PKCryptContext $context;
|
||||
|
||||
public static function register(): bool
|
||||
{
|
||||
return stream_filter_register(self::FILTER_NAME, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://php.net/manual/en/php-user-filter.oncreate.php
|
||||
*/
|
||||
public function onCreate(): bool
|
||||
{
|
||||
if (!isset($this->params['entry'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($this->params['entry'] instanceof ZipEntry)) {
|
||||
throw new \RuntimeException('ZipEntry expected');
|
||||
}
|
||||
/** @var ZipEntry $entry */
|
||||
$entry = $this->params['entry'];
|
||||
$password = $entry->getPassword();
|
||||
|
||||
if ($password === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->size = $entry->getCompressedSize();
|
||||
|
||||
// init context
|
||||
$this->context = new PKCryptContext($password);
|
||||
|
||||
// init check byte
|
||||
if ($entry->isDataDescriptorEnabled()) {
|
||||
$this->checkByte = ($entry->getDosTime() >> 8) & 0xFF;
|
||||
} else {
|
||||
$this->checkByte = ($entry->getCrc() >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
$this->readLength = 0;
|
||||
$this->readHeader = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption filter.
|
||||
*
|
||||
* @todo USE FFI in php 7.4
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*
|
||||
* @param mixed $in
|
||||
* @param mixed $out
|
||||
* @param mixed $consumed
|
||||
* @param mixed $closing
|
||||
*
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function filter($in, $out, &$consumed, $closing): int
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$buffer = $bucket->data;
|
||||
$this->readLength += $bucket->datalen;
|
||||
|
||||
if ($this->readLength > $this->size) {
|
||||
$buffer = substr($buffer, 0, $this->size - $this->readLength);
|
||||
}
|
||||
|
||||
if (!$this->readHeader) {
|
||||
$header = substr($buffer, 0, PKCryptContext::STD_DEC_HDR_SIZE);
|
||||
$this->context->checkHeader($header, $this->checkByte);
|
||||
|
||||
$buffer = substr($buffer, PKCryptContext::STD_DEC_HDR_SIZE);
|
||||
$this->readHeader = true;
|
||||
}
|
||||
|
||||
$bucket->data = $this->context->decryptString($buffer);
|
||||
|
||||
$consumed += $bucket->datalen;
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return \PSFS_PASS_ON;
|
||||
}
|
||||
}
|
||||
127
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php
vendored
Normal file
127
vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\Pkware;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Encryption PKWARE Traditional Encryption.
|
||||
*/
|
||||
class PKEncryptionStreamFilter extends \php_user_filter
|
||||
{
|
||||
public const FILTER_NAME = 'phpzip.encryption.pkware';
|
||||
|
||||
private int $size;
|
||||
|
||||
private string $headerBytes;
|
||||
|
||||
private int $writeLength;
|
||||
|
||||
private bool $writeHeader;
|
||||
|
||||
private PKCryptContext $context;
|
||||
|
||||
public static function register(): bool
|
||||
{
|
||||
return stream_filter_register(self::FILTER_NAME, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://php.net/manual/en/php-user-filter.oncreate.php
|
||||
*/
|
||||
public function onCreate(): bool
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
|
||||
}
|
||||
|
||||
if (!isset($this->params['entry'], $this->params['size'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($this->params['entry'] instanceof ZipEntry)) {
|
||||
throw new \RuntimeException('ZipEntry expected');
|
||||
}
|
||||
/** @var ZipEntry $entry */
|
||||
$entry = $this->params['entry'];
|
||||
$password = $entry->getPassword();
|
||||
|
||||
if ($password === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->size = (int) $this->params['size'];
|
||||
|
||||
// init keys
|
||||
$this->context = new PKCryptContext($password);
|
||||
|
||||
$crc = $entry->isDataDescriptorRequired() || $entry->getCrc() === ZipEntry::UNKNOWN
|
||||
? ($entry->getDosTime() & 0x0000FFFF) << 16
|
||||
: $entry->getCrc();
|
||||
|
||||
try {
|
||||
$headerBytes = random_bytes(PKCryptContext::STD_DEC_HDR_SIZE);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
|
||||
}
|
||||
|
||||
$headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xFF);
|
||||
$headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xFF);
|
||||
|
||||
$this->headerBytes = $headerBytes;
|
||||
$this->writeLength = 0;
|
||||
$this->writeHeader = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption filter.
|
||||
*
|
||||
* @todo USE FFI in php 7.4
|
||||
*
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*
|
||||
* @param mixed $in
|
||||
* @param mixed $out
|
||||
* @param mixed $consumed
|
||||
* @param mixed $closing
|
||||
*/
|
||||
public function filter($in, $out, &$consumed, $closing): int
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$buffer = $bucket->data;
|
||||
$this->writeLength += $bucket->datalen;
|
||||
|
||||
if ($this->writeLength > $this->size) {
|
||||
$buffer = substr($buffer, 0, $this->size - $this->writeLength);
|
||||
}
|
||||
|
||||
$data = '';
|
||||
|
||||
if (!$this->writeHeader) {
|
||||
$data .= $this->context->encryptString($this->headerBytes);
|
||||
$this->writeHeader = true;
|
||||
}
|
||||
|
||||
$data .= $this->context->encryptString($buffer);
|
||||
|
||||
$bucket->data = $data;
|
||||
|
||||
$consumed += $bucket->datalen;
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return \PSFS_PASS_ON;
|
||||
}
|
||||
}
|
||||
141
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
vendored
Normal file
141
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* WinZip Aes Encryption.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT APPENDIX E
|
||||
* @see https://www.winzip.com/win/en/aes_info.html
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WinZipAesContext
|
||||
{
|
||||
/** @var int AES Block size */
|
||||
public const BLOCK_SIZE = self::IV_SIZE;
|
||||
|
||||
/** @var int Footer size */
|
||||
public const FOOTER_SIZE = 10;
|
||||
|
||||
/** @var int The iteration count for the derived keys of the cipher, KLAC and MAC. */
|
||||
public const ITERATION_COUNT = 1000;
|
||||
|
||||
/** @var int Password verifier size */
|
||||
public const PASSWORD_VERIFIER_SIZE = 2;
|
||||
|
||||
/** @var int IV size */
|
||||
public const IV_SIZE = 16;
|
||||
|
||||
private string $iv;
|
||||
|
||||
private string $key;
|
||||
|
||||
private \HashContext $hmacContext;
|
||||
|
||||
private string $passwordVerifier;
|
||||
|
||||
public function __construct(int $encryptionStrengthBits, ?string $password, ?string $salt)
|
||||
{
|
||||
if ($password === '') {
|
||||
throw new RuntimeException('$password is empty');
|
||||
}
|
||||
|
||||
if (empty($salt)) {
|
||||
throw new RuntimeException('$salt is empty');
|
||||
}
|
||||
|
||||
// WinZip 99-character limit https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$this->iv = str_repeat("\0", self::IV_SIZE);
|
||||
$keyStrengthBytes = (int) ($encryptionStrengthBits / 8);
|
||||
$hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8;
|
||||
|
||||
$hash = hash_pbkdf2(
|
||||
'sha1',
|
||||
$password,
|
||||
$salt,
|
||||
self::ITERATION_COUNT,
|
||||
$hashLength,
|
||||
true
|
||||
);
|
||||
|
||||
$this->key = substr($hash, 0, $keyStrengthBytes);
|
||||
$sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes);
|
||||
$this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac);
|
||||
$this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE);
|
||||
}
|
||||
|
||||
public function getPasswordVerifier(): string
|
||||
{
|
||||
return $this->passwordVerifier;
|
||||
}
|
||||
|
||||
public function updateIv(): void
|
||||
{
|
||||
for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) {
|
||||
$ivByte = \ord($this->iv[$ivCharIndex]);
|
||||
|
||||
if (++$ivByte === 256) {
|
||||
// overflow, set this one to 0, increment next
|
||||
$this->iv[$ivCharIndex] = "\0";
|
||||
} else {
|
||||
// no overflow, just write incremented number back and abort
|
||||
$this->iv[$ivCharIndex] = \chr($ivByte);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function decryption(string $data): string
|
||||
{
|
||||
hash_update($this->hmacContext, $data);
|
||||
|
||||
return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv);
|
||||
}
|
||||
|
||||
public function encrypt(string $data): string
|
||||
{
|
||||
$encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv);
|
||||
hash_update($this->hmacContext, $encryptionData);
|
||||
|
||||
return $encryptionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function checkAuthCode(string $authCode): void
|
||||
{
|
||||
$hmac = $this->getHmac();
|
||||
|
||||
// check authenticationCode
|
||||
if (strcmp($hmac, $authCode) !== 0) {
|
||||
throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.');
|
||||
}
|
||||
}
|
||||
|
||||
public function getHmac(): string
|
||||
{
|
||||
return substr(
|
||||
hash_final($this->hmacContext, true),
|
||||
0,
|
||||
self::FOOTER_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
184
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php
vendored
Normal file
184
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Decrypt WinZip AES stream.
|
||||
*/
|
||||
class WinZipAesDecryptionStreamFilter extends \php_user_filter
|
||||
{
|
||||
public const FILTER_NAME = 'phpzip.decryption.winzipaes';
|
||||
|
||||
private string $buffer;
|
||||
|
||||
private ?string $authenticationCode = null;
|
||||
|
||||
private int $encBlockPosition = 0;
|
||||
|
||||
private int $encBlockLength = 0;
|
||||
|
||||
private int $readLength = 0;
|
||||
|
||||
private ZipEntry $entry;
|
||||
|
||||
private ?WinZipAesContext $context = null;
|
||||
|
||||
public static function register(): bool
|
||||
{
|
||||
return stream_filter_register(self::FILTER_NAME, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
public function onCreate(): bool
|
||||
{
|
||||
if (!isset($this->params['entry'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($this->params['entry'] instanceof ZipEntry)) {
|
||||
throw new \RuntimeException('ZipEntry expected');
|
||||
}
|
||||
$this->entry = $this->params['entry'];
|
||||
|
||||
if (
|
||||
$this->entry->getPassword() === null
|
||||
|| !$this->entry->isEncrypted()
|
||||
|| !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->buffer = '';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*
|
||||
* @param mixed $in
|
||||
* @param mixed $out
|
||||
* @param mixed $consumed
|
||||
* @param mixed $closing
|
||||
*
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function filter($in, $out, &$consumed, $closing): int
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$this->buffer .= $bucket->data;
|
||||
$this->readLength += $bucket->datalen;
|
||||
|
||||
if ($this->readLength > $this->entry->getCompressedSize()) {
|
||||
$this->buffer = substr($this->buffer, 0, $this->entry->getCompressedSize() - $this->readLength);
|
||||
}
|
||||
|
||||
// read header
|
||||
if ($this->context === null) {
|
||||
/**
|
||||
* @var WinZipAesExtraField|null $winZipExtra
|
||||
*/
|
||||
$winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipExtra === null) {
|
||||
throw new RuntimeException('$winZipExtra is null');
|
||||
}
|
||||
$saltSize = $winZipExtra->getSaltSize();
|
||||
$headerSize = $saltSize + WinZipAesContext::PASSWORD_VERIFIER_SIZE;
|
||||
|
||||
if (\strlen($this->buffer) < $headerSize) {
|
||||
return \PSFS_FEED_ME;
|
||||
}
|
||||
|
||||
$salt = substr($this->buffer, 0, $saltSize);
|
||||
$passwordVerifier = substr($this->buffer, $saltSize, WinZipAesContext::PASSWORD_VERIFIER_SIZE);
|
||||
$password = $this->entry->getPassword();
|
||||
|
||||
if ($password === null) {
|
||||
throw new RuntimeException('$password is null');
|
||||
}
|
||||
$this->context = new WinZipAesContext($winZipExtra->getEncryptionStrength(), $password, $salt);
|
||||
unset($password);
|
||||
|
||||
// Verify password.
|
||||
if ($passwordVerifier !== $this->context->getPasswordVerifier()) {
|
||||
throw new ZipAuthenticationException('Invalid password');
|
||||
}
|
||||
|
||||
$this->encBlockPosition = 0;
|
||||
$this->encBlockLength = $this->entry->getCompressedSize() - $headerSize - WinZipAesContext::FOOTER_SIZE;
|
||||
|
||||
$this->buffer = substr($this->buffer, $headerSize);
|
||||
}
|
||||
|
||||
// encrypt data
|
||||
$plainText = '';
|
||||
$offset = 0;
|
||||
$len = \strlen($this->buffer);
|
||||
$remaining = $this->encBlockLength - $this->encBlockPosition;
|
||||
|
||||
if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
|
||||
return \PSFS_FEED_ME;
|
||||
}
|
||||
$limit = min($len, $remaining);
|
||||
|
||||
if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
|
||||
$limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
|
||||
}
|
||||
|
||||
while ($offset < $limit) {
|
||||
$this->context->updateIv();
|
||||
$length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
|
||||
$data = substr($this->buffer, 0, $length);
|
||||
$plainText .= $this->context->decryption($data);
|
||||
$offset += $length;
|
||||
$this->buffer = substr($this->buffer, $length);
|
||||
}
|
||||
$this->encBlockPosition += $offset;
|
||||
|
||||
if (
|
||||
$this->encBlockPosition === $this->encBlockLength
|
||||
&& \strlen($this->buffer) === WinZipAesContext::FOOTER_SIZE
|
||||
) {
|
||||
$this->authenticationCode = $this->buffer;
|
||||
$this->buffer = '';
|
||||
}
|
||||
|
||||
$bucket->data = $plainText;
|
||||
$consumed += $bucket->datalen;
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return \PSFS_PASS_ON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://php.net/manual/en/php-user-filter.onclose.php
|
||||
*
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function onClose(): void
|
||||
{
|
||||
$this->buffer = '';
|
||||
|
||||
if ($this->context !== null && $this->authenticationCode !== null) {
|
||||
$this->context->checkAuthCode($this->authenticationCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
149
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php
vendored
Normal file
149
vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Encrypt WinZip AES stream.
|
||||
*/
|
||||
class WinZipAesEncryptionStreamFilter extends \php_user_filter
|
||||
{
|
||||
public const FILTER_NAME = 'phpzip.encryption.winzipaes';
|
||||
|
||||
private string $buffer;
|
||||
|
||||
private int $remaining = 0;
|
||||
|
||||
private ZipEntry $entry;
|
||||
|
||||
private int $size;
|
||||
|
||||
private ?WinZipAesContext $context = null;
|
||||
|
||||
public static function register(): bool
|
||||
{
|
||||
return stream_filter_register(self::FILTER_NAME, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
public function onCreate(): bool
|
||||
{
|
||||
if (!isset($this->params['entry'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!($this->params['entry'] instanceof ZipEntry)) {
|
||||
throw new \RuntimeException('ZipEntry expected');
|
||||
}
|
||||
$this->entry = $this->params['entry'];
|
||||
|
||||
if (
|
||||
$this->entry->getPassword() === null
|
||||
|| !$this->entry->isEncrypted()
|
||||
|| !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->size = (int) $this->params['size'];
|
||||
$this->context = null;
|
||||
$this->buffer = '';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function filter($in, $out, &$consumed, $closing): int
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$this->buffer .= $bucket->data;
|
||||
$this->remaining += $bucket->datalen;
|
||||
|
||||
if ($this->remaining > $this->size) {
|
||||
$this->buffer = substr($this->buffer, 0, $this->size - $this->remaining);
|
||||
$this->remaining = $this->size;
|
||||
}
|
||||
|
||||
$encryptionText = '';
|
||||
|
||||
// write header
|
||||
if ($this->context === null) {
|
||||
/**
|
||||
* @var WinZipAesExtraField|null $winZipExtra
|
||||
*/
|
||||
$winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipExtra === null) {
|
||||
throw new RuntimeException('$winZipExtra is null');
|
||||
}
|
||||
$saltSize = $winZipExtra->getSaltSize();
|
||||
|
||||
try {
|
||||
$salt = random_bytes($saltSize);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
|
||||
}
|
||||
$password = $this->entry->getPassword();
|
||||
|
||||
if ($password === null) {
|
||||
throw new RuntimeException('$password is null');
|
||||
}
|
||||
$this->context = new WinZipAesContext(
|
||||
$winZipExtra->getEncryptionStrength(),
|
||||
$password,
|
||||
$salt
|
||||
);
|
||||
|
||||
$encryptionText .= $salt . $this->context->getPasswordVerifier();
|
||||
}
|
||||
|
||||
// encrypt data
|
||||
$offset = 0;
|
||||
$len = \strlen($this->buffer);
|
||||
$remaining = $this->remaining - $this->size;
|
||||
|
||||
if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
|
||||
return \PSFS_FEED_ME;
|
||||
}
|
||||
$limit = max($len, $remaining);
|
||||
|
||||
if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
|
||||
$limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
|
||||
}
|
||||
|
||||
while ($offset < $limit) {
|
||||
$this->context->updateIv();
|
||||
$length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
|
||||
$encryptionText .= $this->context->encrypt(
|
||||
substr($this->buffer, 0, $length)
|
||||
);
|
||||
$offset += $length;
|
||||
$this->buffer = substr($this->buffer, $length);
|
||||
}
|
||||
|
||||
if ($remaining === 0) {
|
||||
$encryptionText .= $this->context->getHmac();
|
||||
}
|
||||
|
||||
$bucket->data = $encryptionText;
|
||||
$consumed += $bucket->datalen;
|
||||
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return \PSFS_PASS_ON;
|
||||
}
|
||||
}
|
||||
290
vendor/nelexa/zip/src/IO/Stream/ResponseStream.php
vendored
Normal file
290
vendor/nelexa/zip/src/IO/Stream/ResponseStream.php
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Stream;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Implement PSR Message Stream.
|
||||
*/
|
||||
class ResponseStream implements StreamInterface
|
||||
{
|
||||
/** @var array */
|
||||
private const READ_WRITE_MAP = [
|
||||
'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|null */
|
||||
private $stream;
|
||||
|
||||
private ?int $size = null;
|
||||
|
||||
private bool $seekable;
|
||||
|
||||
private bool $readable;
|
||||
|
||||
private bool $writable;
|
||||
|
||||
private ?string $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::READ_WRITE_MAP['read'][$meta['mode']]);
|
||||
$this->writable = isset(self::READ_WRITE_MAP['write'][$meta['mode']]);
|
||||
$this->uri = $this->getMetadata('uri');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
if ($this->stream === null) {
|
||||
return $key ? null : [];
|
||||
}
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
|
||||
return $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
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
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).
|
||||
*
|
||||
* @throws \RuntimeException on failure
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.fseek.php
|
||||
* @see seek()
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->stream !== null && $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(): ?int
|
||||
{
|
||||
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 !== null) {
|
||||
clearstatcache(true, $this->uri);
|
||||
}
|
||||
$stats = fstat($this->stream);
|
||||
|
||||
if (isset($stats['size'])) {
|
||||
$this->size = $stats['size'];
|
||||
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
return $this->stream ? ftell($this->stream) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream is at the end of the stream.
|
||||
*/
|
||||
public function eof(): bool
|
||||
{
|
||||
return !$this->stream || feof($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is seekable.
|
||||
*/
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return $this->seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function seek($offset, $whence = \SEEK_SET): void
|
||||
{
|
||||
$this->stream !== null && $this->seekable && fseek($this->stream, $offset, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is writable.
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
$this->size = null;
|
||||
|
||||
return $this->stream !== null && $this->writable ? fwrite($this->stream, $string) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is readable.
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function read($length): string
|
||||
{
|
||||
return $this->stream !== null && $this->readable ? fread($this->stream, $length) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining contents in a string.
|
||||
*
|
||||
* @throws \RuntimeException if unable to read or an error occurs while
|
||||
* reading
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @psalm-suppress InvalidPropertyAssignmentValue
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
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 = null;
|
||||
$this->size = null;
|
||||
$this->uri = null;
|
||||
$this->readable = false;
|
||||
$this->writable = false;
|
||||
$this->seekable = false;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
309
vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php
vendored
Normal file
309
vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO\Stream;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* The class provides stream reuse functionality.
|
||||
*
|
||||
* Stream will not be closed at {@see fclose}.
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper
|
||||
*/
|
||||
final class ZipEntryStreamWrapper
|
||||
{
|
||||
/** @var string the registered protocol */
|
||||
public const PROTOCOL = 'zipentry';
|
||||
|
||||
/** @var resource */
|
||||
public $context;
|
||||
|
||||
/** @var resource */
|
||||
private $fp;
|
||||
|
||||
public static function register(): bool
|
||||
{
|
||||
$protocol = self::PROTOCOL;
|
||||
|
||||
if (!\in_array($protocol, stream_get_wrappers(), true)) {
|
||||
if (!stream_wrapper_register($protocol, self::class)) {
|
||||
throw new \RuntimeException("Failed to register '{$protocol}://' protocol");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function unregister(): void
|
||||
{
|
||||
stream_wrapper_unregister(self::PROTOCOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public static function wrap(ZipEntry $entry)
|
||||
{
|
||||
self::register();
|
||||
|
||||
$context = stream_context_create(
|
||||
[
|
||||
self::PROTOCOL => [
|
||||
'entry' => $entry,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$uri = self::PROTOCOL . '://' . $entry->getName();
|
||||
$fp = fopen($uri, 'r+b', false, $context);
|
||||
|
||||
if ($fp === false) {
|
||||
throw new \RuntimeException('Error open ' . $uri);
|
||||
}
|
||||
|
||||
return $fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens file or URL.
|
||||
*
|
||||
* This method is called immediately after the wrapper is
|
||||
* initialized (f.e. by {@see fopen()} and {@see file_get_contents()}).
|
||||
*
|
||||
* @param string $path specifies the URL that was passed to
|
||||
* the original function
|
||||
* @param string $mode the mode used to open the file, as detailed
|
||||
* for {@see fopen()}
|
||||
* @param int $options Holds additional flags set by the streams
|
||||
* API. It can hold one or more of the
|
||||
* following values OR'd together.
|
||||
* @param string|null $opened_path if the path is opened successfully, and
|
||||
* STREAM_USE_PATH is set in options,
|
||||
* opened_path should be set to the
|
||||
* full path of the file/resource that
|
||||
* was actually opened
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-open
|
||||
*/
|
||||
public function stream_open(string $path, ?string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
if ($this->context === null) {
|
||||
throw new \RuntimeException('stream context is null');
|
||||
}
|
||||
$streamOptions = stream_context_get_options($this->context);
|
||||
|
||||
if (!isset($streamOptions[self::PROTOCOL]['entry'])) {
|
||||
throw new \RuntimeException('no stream option ["' . self::PROTOCOL . '"]["entry"]');
|
||||
}
|
||||
$zipEntry = $streamOptions[self::PROTOCOL]['entry'];
|
||||
|
||||
if (!$zipEntry instanceof ZipEntry) {
|
||||
throw new \RuntimeException('invalid stream context');
|
||||
}
|
||||
|
||||
$zipData = $zipEntry->getData();
|
||||
|
||||
if ($zipData === null) {
|
||||
throw new ZipException(sprintf('No data for zip entry "%s"', $zipEntry->getName()));
|
||||
}
|
||||
$this->fp = $zipData->getDataAsStream();
|
||||
|
||||
return $this->fp !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from stream.
|
||||
*
|
||||
* This method is called in response to {@see fread()} and {@see fgets()}.
|
||||
*
|
||||
* Note: Remember to update the read/write position of the stream
|
||||
* (by the number of bytes that were successfully read).
|
||||
*
|
||||
* @param int $count how many bytes of data from the current
|
||||
* position should be returned
|
||||
*
|
||||
* @return false|string If there are less than count bytes available,
|
||||
* return as many as are available. If no more data
|
||||
* is available, return either FALSE or
|
||||
* an empty string.
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-read
|
||||
*/
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
return fread($this->fp, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to specific location in a stream.
|
||||
*
|
||||
* This method is called in response to {@see fseek()}.
|
||||
* The read/write position of the stream should be updated according
|
||||
* to the offset and whence.
|
||||
*
|
||||
* @param int $offset the stream offset to seek to
|
||||
* @param int $whence Possible values:
|
||||
* {@see \SEEK_SET} - Set position equal to offset bytes.
|
||||
* {@see \SEEK_CUR} - Set position to current location plus offset.
|
||||
* {@see \SEEK_END} - Set position to end-of-file plus offset.
|
||||
*
|
||||
* @return bool return TRUE if the position was updated, FALSE otherwise
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-seek
|
||||
*/
|
||||
public function stream_seek(int $offset, int $whence = \SEEK_SET): bool
|
||||
{
|
||||
return fseek($this->fp, $offset, $whence) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current position of a stream.
|
||||
*
|
||||
* This method is called in response to {@see fseek()} to determine
|
||||
* the current position.
|
||||
*
|
||||
* @return int should return the current position of the stream
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-tell
|
||||
*/
|
||||
public function stream_tell(): int
|
||||
{
|
||||
$pos = ftell($this->fp);
|
||||
|
||||
if ($pos === false) {
|
||||
throw new \RuntimeException('Cannot get stream position.');
|
||||
}
|
||||
|
||||
return $pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for end-of-file on a file pointer.
|
||||
*
|
||||
* This method is called in response to {@see feof()}.
|
||||
*
|
||||
* @return bool should return TRUE if the read/write position is at
|
||||
* the end of the stream and if no more data is available
|
||||
* to be read, or FALSE otherwise
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-eof
|
||||
*/
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return feof($this->fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about a file resource.
|
||||
*
|
||||
* This method is called in response to {@see fstat()}.
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-stat
|
||||
* @see https://www.php.net/stat
|
||||
* @see https://www.php.net/fstat
|
||||
*/
|
||||
public function stream_stat(): array
|
||||
{
|
||||
return fstat($this->fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the output.
|
||||
*
|
||||
* This method is called in response to {@see fflush()} and when the
|
||||
* stream is being closed while any unflushed data has been written to
|
||||
* it before.
|
||||
* If you have cached data in your stream but not yet stored it into
|
||||
* the underlying storage, you should do so now.
|
||||
*
|
||||
* @return bool should return TRUE if the cached data was successfully
|
||||
* stored (or if there was no data to store), or FALSE
|
||||
* if the data could not be stored
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-flush
|
||||
*/
|
||||
public function stream_flush(): bool
|
||||
{
|
||||
return fflush($this->fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate stream.
|
||||
*
|
||||
* Will respond to truncation, e.g., through {@see ftruncate()}.
|
||||
*
|
||||
* @param int $newSize the new size
|
||||
*
|
||||
* @return bool returns TRUE on success or FALSE on failure
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-truncate
|
||||
*/
|
||||
public function stream_truncate(int $newSize): bool
|
||||
{
|
||||
return ftruncate($this->fp, $newSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to stream.
|
||||
*
|
||||
* This method is called in response to {@see fwrite().}
|
||||
*
|
||||
* Note: Remember to update the current position of the stream by
|
||||
* number of bytes that were successfully written.
|
||||
*
|
||||
* @param string $data should be stored into the underlying stream
|
||||
*
|
||||
* @return int should return the number of bytes that were successfully stored, or 0 if none could be stored
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-write
|
||||
*/
|
||||
public function stream_write(string $data): int
|
||||
{
|
||||
$bytes = fwrite($this->fp, $data);
|
||||
|
||||
return $bytes === false ? 0 : $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlaying resource.
|
||||
*
|
||||
* This method is called in response to {@see stream_select()}.
|
||||
*
|
||||
* @param int $cast_as can be {@see STREAM_CAST_FOR_SELECT} when {@see stream_select()}
|
||||
* is callingstream_cast() or {@see STREAM_CAST_AS_STREAM} when
|
||||
* stream_cast() is called for other uses
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function stream_cast(int $cast_as)
|
||||
{
|
||||
return $this->fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a resource.
|
||||
*
|
||||
* This method is called in response to {@see fclose()}.
|
||||
* All resources that were locked, or allocated, by the wrapper should be released.
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-close
|
||||
*/
|
||||
public function stream_close(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
883
vendor/nelexa/zip/src/IO/ZipReader.php
vendored
Normal file
883
vendor/nelexa/zip/src/IO/ZipReader.php
vendored
Normal file
@@ -0,0 +1,883 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Zip reader.
|
||||
*/
|
||||
class ZipReader
|
||||
{
|
||||
/** @var int file size */
|
||||
protected int $size;
|
||||
|
||||
/** @var resource */
|
||||
protected $inStream;
|
||||
|
||||
protected array $options;
|
||||
|
||||
/**
|
||||
* @param resource $inStream
|
||||
*/
|
||||
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 = $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;
|
||||
}
|
||||
|
||||
protected function getDefaultOptions(): array
|
||||
{
|
||||
return [
|
||||
ZipOptions::CHARSET => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function read(): ImmutableZipContainer
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
public function getStreamMetaData(): array
|
||||
{
|
||||
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
|
||||
*/
|
||||
protected function readEndOfCentralDirectory(): EndOfCentralDirectory
|
||||
{
|
||||
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);
|
||||
|
||||
[
|
||||
'diskNo' => $diskNo,
|
||||
'cdDiskNo' => $cdDiskNo,
|
||||
'cdEntriesDisk' => $cdEntriesDisk,
|
||||
'cdEntries' => $cdEntries,
|
||||
'cdSize' => $cdSize,
|
||||
'cdPos' => $cdPos,
|
||||
'commentLength' => $commentLength,
|
||||
] = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/'
|
||||
. 'vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
substr($buffer, 0, 18)
|
||||
);
|
||||
|
||||
if (
|
||||
$diskNo !== 0
|
||||
|| $cdDiskNo !== 0
|
||||
|| $cdEntriesDisk !== $cdEntries
|
||||
) {
|
||||
throw new ZipException(
|
||||
'ZIP file spanning/splitting is not supported!'
|
||||
);
|
||||
}
|
||||
$comment = null;
|
||||
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable sizeECD)
|
||||
$comment = substr($buffer, 18, $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(
|
||||
$cdEntries,
|
||||
$cdPos,
|
||||
$cdSize,
|
||||
false,
|
||||
$comment
|
||||
);
|
||||
}
|
||||
|
||||
return $endCentralDirectory;
|
||||
}
|
||||
|
||||
protected function findEndOfCentralDirectory(): bool
|
||||
{
|
||||
$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(): int
|
||||
{
|
||||
[
|
||||
'diskNo' => $diskNo,
|
||||
'zip64ECDPos' => $zip64ECDPos,
|
||||
'totalDisks' => $totalDisks,
|
||||
] = unpack('VdiskNo/Pzip64ECDPos/VtotalDisks', fread($this->inStream, 16));
|
||||
|
||||
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)
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function readZip64EndOfCentralDirectory(int $zip64ECDPosition): EndOfCentralDirectory
|
||||
{
|
||||
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!');
|
||||
}
|
||||
|
||||
[
|
||||
// 'size' => $size,
|
||||
// 'versionMadeBy' => $versionMadeBy,
|
||||
// 'extractVersion' => $extractVersion,
|
||||
'diskNo' => $diskNo,
|
||||
'cdDiskNo' => $cdDiskNo,
|
||||
'cdEntriesDisk' => $cdEntriesDisk,
|
||||
'entryCount' => $entryCount,
|
||||
'cdSize' => $cdSize,
|
||||
'cdPos' => $cdPos,
|
||||
] = unpack(
|
||||
// 'Psize/vversionMadeBy/vextractVersion/'.
|
||||
'VdiskNo/VcdDiskNo/PcdEntriesDisk/PentryCount/PcdSize/PcdPos',
|
||||
substr($buffer, 16, 40)
|
||||
);
|
||||
|
||||
// $platform = ZipPlatform::fromValue(($versionMadeBy & 0xFF00) >> 8);
|
||||
// $softwareVersion = $versionMadeBy & 0x00FF;
|
||||
|
||||
if ($diskNo !== 0 || $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.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
protected function readCentralDirectory(EndOfCentralDirectory $endCD): array
|
||||
{
|
||||
$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 !== '') {
|
||||
$unicodePath = str_replace('\\', '/', $unicodePath);
|
||||
|
||||
if (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
|
||||
*/
|
||||
protected function readZipEntry($stream): ZipEntry
|
||||
{
|
||||
if (unpack('V', fread($stream, 4))[1] !== ZipConstants::CENTRAL_FILE_HEADER) {
|
||||
throw new ZipException('Corrupt zip file. Cannot read zip entry.');
|
||||
}
|
||||
|
||||
[
|
||||
'versionMadeBy' => $versionMadeBy,
|
||||
'versionNeededToExtract' => $versionNeededToExtract,
|
||||
'generalPurposeBitFlags' => $generalPurposeBitFlags,
|
||||
'compressionMethod' => $compressionMethod,
|
||||
'lastModFile' => $dosTime,
|
||||
'crc' => $crc,
|
||||
'compressedSize' => $compressedSize,
|
||||
'uncompressedSize' => $uncompressedSize,
|
||||
'fileNameLength' => $fileNameLength,
|
||||
'extraFieldLength' => $extraFieldLength,
|
||||
'fileCommentLength' => $fileCommentLength,
|
||||
'diskNumberStart' => $diskNumberStart,
|
||||
'internalFileAttributes' => $internalFileAttributes,
|
||||
'externalFileAttributes' => $externalFileAttributes,
|
||||
'offsetLocalHeader' => $offsetLocalHeader,
|
||||
] = unpack(
|
||||
'vversionMadeBy/vversionNeededToExtract/'
|
||||
. 'vgeneralPurposeBitFlags/vcompressionMethod/'
|
||||
. 'VlastModFile/Vcrc/VcompressedSize/'
|
||||
. 'VuncompressedSize/vfileNameLength/vextraFieldLength/'
|
||||
. 'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/'
|
||||
. 'VexternalFileAttributes/VoffsetLocalHeader',
|
||||
fread($stream, 42)
|
||||
);
|
||||
|
||||
if ($diskNumberStart !== 0) {
|
||||
throw new ZipException('ZIP file spanning/splitting is not supported!');
|
||||
}
|
||||
|
||||
$isUtf8 = ($generalPurposeBitFlags & GeneralPurposeBitFlag::UTF8) !== 0;
|
||||
|
||||
$name = fread($stream, $fileNameLength);
|
||||
|
||||
$createdOS = ($versionMadeBy & 0xFF00) >> 8;
|
||||
$softwareVersion = $versionMadeBy & 0x00FF;
|
||||
|
||||
$extractedOS = ($versionNeededToExtract & 0xFF00) >> 8;
|
||||
$extractVersion = $versionNeededToExtract & 0x00FF;
|
||||
$comment = null;
|
||||
|
||||
if ($fileCommentLength > 0) {
|
||||
$comment = fread($stream, $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,
|
||||
$compressionMethod,
|
||||
$generalPurposeBitFlags,
|
||||
$dosTime,
|
||||
$crc,
|
||||
$compressedSize,
|
||||
$uncompressedSize,
|
||||
$internalFileAttributes,
|
||||
$externalFileAttributes,
|
||||
$offsetLocalHeader,
|
||||
$comment,
|
||||
$fallbackCharset
|
||||
);
|
||||
|
||||
if ($extraFieldLength > 0) {
|
||||
$this->parseExtraFields(
|
||||
fread($stream, $extraFieldLength),
|
||||
$zipEntry
|
||||
);
|
||||
|
||||
/** @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;
|
||||
}
|
||||
|
||||
protected function parseExtraFields(string $buffer, ZipEntry $zipEntry, bool $local = false): ExtraFieldsCollection
|
||||
{
|
||||
$collection = $local
|
||||
? $zipEntry->getLocalExtraFields()
|
||||
: $zipEntry->getCdExtraFields();
|
||||
|
||||
if (!empty($buffer)) {
|
||||
$pos = 0;
|
||||
$endPos = \strlen($buffer);
|
||||
|
||||
while ($endPos - $pos >= 4) {
|
||||
[
|
||||
'headerId' => $headerId,
|
||||
'dataSize' => $dataSize,
|
||||
] = unpack('vheaderId/vdataSize', substr($buffer, $pos, 4));
|
||||
$pos += 4;
|
||||
|
||||
if ($endPos - $pos - $dataSize < 0) {
|
||||
break;
|
||||
}
|
||||
$bufferData = substr($buffer, $pos, $dataSize);
|
||||
|
||||
/** @var string|ZipExtraField|null $className */
|
||||
$className = ZipExtraDriver::getClassNameOrNull($headerId);
|
||||
|
||||
try {
|
||||
if ($className !== null) {
|
||||
try {
|
||||
$extraField = $local
|
||||
? $className::unpackLocalFileData($bufferData, $zipEntry)
|
||||
: $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 += $dataSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
protected function handleZip64Extra(Zip64ExtraField $extraZip64, ZipEntry $zipEntry): void
|
||||
{
|
||||
$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)
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function loadLocalExtraFields(ZipEntry $entry): void
|
||||
{
|
||||
$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);
|
||||
[
|
||||
'fileNameLength' => $fileNameLength,
|
||||
'extraFieldLength' => $extraFieldLength,
|
||||
] = unpack('vfileNameLength/vextraFieldLength', fread($this->inStream, 4));
|
||||
$offsetData = ftell($this->inStream) + $fileNameLength + $extraFieldLength;
|
||||
fseek($this->inStream, $fileNameLength, \SEEK_CUR);
|
||||
|
||||
if ($extraFieldLength > 0) {
|
||||
$this->parseExtraFields(
|
||||
fread($this->inStream, $extraFieldLength),
|
||||
$entry,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$zipData = new ZipSourceFileData($this, $entry, $offsetData);
|
||||
$entry->setData($zipData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function handleExtraEncryptionFields(ZipEntry $zipEntry): void
|
||||
{
|
||||
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.
|
||||
*/
|
||||
protected function handleExtraFields(ZipEntry $zipEntry): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 resource $outStream
|
||||
*
|
||||
* @throws Crc32Exception
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function copyUncompressedDataToStream(ZipSourceFileData $zipFileData, $outStream): void
|
||||
{
|
||||
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 resource $outStream
|
||||
*/
|
||||
public function copyCompressedDataToStream(ZipSourceFileData $zipData, $outStream): void
|
||||
{
|
||||
if ($zipData->getCompressedSize() > 0) {
|
||||
fseek($this->inStream, $zipData->getOffset());
|
||||
stream_copy_to_stream($this->inStream, $outStream, $zipData->getCompressedSize());
|
||||
}
|
||||
}
|
||||
|
||||
protected function isZip64Support(): bool
|
||||
{
|
||||
return \PHP_INT_SIZE === 8; // true for 64bit system
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress InvalidPropertyAssignmentValue
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if (\is_resource($this->inStream)) {
|
||||
fclose($this->inStream);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
791
vendor/nelexa/zip/src/IO/ZipWriter.php
vendored
Normal file
791
vendor/nelexa/zip/src/IO/ZipWriter.php
vendored
Normal file
@@ -0,0 +1,791 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\IO;
|
||||
|
||||
use PhpZip\Constants\DosCodePage;
|
||||
use PhpZip\Constants\ZipCompressionMethod;
|
||||
use PhpZip\Constants\ZipConstants;
|
||||
use PhpZip\Constants\ZipEncryptionMethod;
|
||||
use PhpZip\Constants\ZipPlatform;
|
||||
use PhpZip\Constants\ZipVersion;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethodException;
|
||||
use PhpZip\IO\Filter\Cipher\Pkware\PKEncryptionStreamFilter;
|
||||
use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesEncryptionStreamFilter;
|
||||
use PhpZip\Model\Data\ZipSourceFileData;
|
||||
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
|
||||
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipContainer;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
class ZipWriter
|
||||
{
|
||||
/** @var int Chunk read size */
|
||||
public const CHUNK_SIZE = 8192;
|
||||
|
||||
protected ZipContainer $zipContainer;
|
||||
|
||||
public function __construct(ZipContainer $container)
|
||||
{
|
||||
// we clone the container so that the changes made to
|
||||
// it do not affect the data in the ZipFile class
|
||||
$this->zipContainer = clone $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function write($outStream): void
|
||||
{
|
||||
if (!\is_resource($outStream)) {
|
||||
throw new \InvalidArgumentException('$outStream must be resource');
|
||||
}
|
||||
$this->beforeWrite();
|
||||
$this->writeLocalBlock($outStream);
|
||||
$cdOffset = ftell($outStream);
|
||||
$this->writeCentralDirectoryBlock($outStream);
|
||||
$cdSize = ftell($outStream) - $cdOffset;
|
||||
$this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize);
|
||||
}
|
||||
|
||||
protected function beforeWrite(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeLocalBlock($outStream): void
|
||||
{
|
||||
$zipEntries = $this->zipContainer->getEntries();
|
||||
|
||||
foreach ($zipEntries as $zipEntry) {
|
||||
$this->writeLocalHeader($outStream, $zipEntry);
|
||||
$this->writeData($outStream, $zipEntry);
|
||||
|
||||
if ($zipEntry->isDataDescriptorEnabled()) {
|
||||
$this->writeDataDescriptor($outStream, $zipEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeLocalHeader($outStream, ZipEntry $entry): void
|
||||
{
|
||||
$relativeOffset = ftell($outStream);
|
||||
$entry->setLocalHeaderOffset($relativeOffset);
|
||||
|
||||
if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
|
||||
$entry->enableDataDescriptor(true);
|
||||
}
|
||||
|
||||
$dd = $entry->isDataDescriptorRequired()
|
||||
|| $entry->isDataDescriptorEnabled();
|
||||
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
|
||||
$entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) {
|
||||
$entry->getLocalExtraFields()->add(
|
||||
new Zip64ExtraField($uncompressedSize, $compressedSize)
|
||||
);
|
||||
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra === null) {
|
||||
$winZipAesExtra = WinZipAesExtraField::create($entry);
|
||||
}
|
||||
|
||||
if ($winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
|
||||
}
|
||||
|
||||
$extra = $this->getExtraFieldsContents($entry, true);
|
||||
$name = $entry->getName();
|
||||
$dosCharset = $entry->getCharset();
|
||||
|
||||
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
|
||||
$name = DosCodePage::fromUTF8($name, $dosCharset);
|
||||
}
|
||||
|
||||
$nameLength = \strlen($name);
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
$size = $nameLength + $extraLength;
|
||||
|
||||
if ($size > 0xFFFF) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)',
|
||||
$entry->getName(),
|
||||
$size,
|
||||
0xFFFF
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion();
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
ZipConstants::LOCAL_FILE_HEADER,
|
||||
// version needed to extract 2 bytes
|
||||
$extractedBy,
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$compressionMethod,
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $crc,
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $uncompressedSize,
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($nameLength > 0) {
|
||||
fwrite($outStream, $name);
|
||||
}
|
||||
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outStream, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the local file data fields of the given ZipExtraFields.
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function getExtraFieldsContents(ZipEntry $entry, bool $local): string
|
||||
{
|
||||
$collection = $local
|
||||
? $entry->getLocalExtraFields()
|
||||
: $entry->getCdExtraFields();
|
||||
$extraData = '';
|
||||
|
||||
foreach ($collection as $extraField) {
|
||||
if ($local) {
|
||||
$data = $extraField->packLocalFileData();
|
||||
} else {
|
||||
$data = $extraField->packCentralDirData();
|
||||
}
|
||||
$extraData .= pack(
|
||||
'vv',
|
||||
$extraField->getHeaderId(),
|
||||
\strlen($data)
|
||||
);
|
||||
$extraData .= $data;
|
||||
}
|
||||
|
||||
$size = \strlen($extraData);
|
||||
|
||||
if ($size > 0xFFFF) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'Size extra out of range: %d. Extra data: %s',
|
||||
$size,
|
||||
$extraData
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeData($outStream, ZipEntry $entry): void
|
||||
{
|
||||
$zipData = $entry->getData();
|
||||
|
||||
if ($zipData === null) {
|
||||
if ($entry->isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName()));
|
||||
}
|
||||
|
||||
// data write variants:
|
||||
// --------------------
|
||||
// * data of source zip file -> copy compressed data
|
||||
// * store - simple write
|
||||
// * store and encryption - apply encryption filter and simple write
|
||||
// * deflate or bzip2 - apply compression filter and simple write
|
||||
// * (deflate or bzip2) and encryption - create temp stream and apply
|
||||
// compression filter to it, then apply encryption filter to root
|
||||
// stream and write temp stream data.
|
||||
// (PHP cannot apply the filter for encryption after the compression
|
||||
// filter, so a temporary stream is created for the compressed data)
|
||||
|
||||
if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) {
|
||||
// data of source zip file -> copy compressed data
|
||||
$zipData->copyCompressedDataToStream($outStream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$entryStream = $zipData->getDataAsStream();
|
||||
|
||||
if (stream_get_meta_data($entryStream)['seekable']) {
|
||||
rewind($entryStream);
|
||||
}
|
||||
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
|
||||
$posBeforeWrite = ftell($outStream);
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($compressionMethod === ZipCompressionMethod::STORED) {
|
||||
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
|
||||
} else {
|
||||
$compressStream = fopen('php://temp', 'w+b');
|
||||
$contextFilter = $this->appendCompressionFilter($compressStream, $entry);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize);
|
||||
|
||||
if ($contextFilter !== null) {
|
||||
stream_filter_remove($contextFilter);
|
||||
$contextFilter = null;
|
||||
}
|
||||
|
||||
rewind($compressStream);
|
||||
|
||||
$compressedSize = fstat($compressStream)['size'];
|
||||
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize);
|
||||
|
||||
stream_copy_to_stream($compressStream, $outStream);
|
||||
}
|
||||
} else {
|
||||
$contextFilter = $this->appendCompressionFilter($outStream, $entry);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
|
||||
}
|
||||
|
||||
if ($contextFilter !== null) {
|
||||
stream_filter_remove($contextFilter);
|
||||
$contextFilter = null;
|
||||
}
|
||||
|
||||
// my hack {@see https://bugs.php.net/bug.php?id=49874}
|
||||
fseek($outStream, 0, \SEEK_END);
|
||||
$compressedSize = ftell($outStream) - $posBeforeWrite;
|
||||
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
$entry->setCrc($checksum);
|
||||
|
||||
if (!$entry->isDataDescriptorEnabled()) {
|
||||
if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) {
|
||||
/** @var Zip64ExtraField|null $zip64ExtraLocal */
|
||||
$zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
// if there is a zip64 extra record, then update it;
|
||||
// if not, write data to data descriptor
|
||||
if ($zip64ExtraLocal !== null) {
|
||||
$zip64ExtraLocal->setCompressedSize($compressedSize);
|
||||
$zip64ExtraLocal->setUncompressedSize($uncompressedSize);
|
||||
|
||||
$posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName());
|
||||
fseek($outStream, $posExtra);
|
||||
fwrite($outStream, $this->getExtraFieldsContents($entry, true));
|
||||
} else {
|
||||
$posGPBF = $entry->getLocalHeaderOffset() + 6;
|
||||
$entry->enableDataDescriptor(true);
|
||||
fseek($outStream, $posGPBF);
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'v',
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$posChecksum = $entry->getLocalHeaderOffset() + 14;
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
|
||||
$checksum = 0;
|
||||
}
|
||||
|
||||
fseek($outStream, $posChecksum);
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VVV',
|
||||
// crc-32 4 bytes
|
||||
$checksum,
|
||||
// compressed size 4 bytes
|
||||
$compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$uncompressedSize
|
||||
)
|
||||
);
|
||||
fseek($outStream, 0, \SEEK_END);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inStream
|
||||
* @param resource $outStream
|
||||
*/
|
||||
private function writeAndCountChecksum($inStream, $outStream, int $size): int
|
||||
{
|
||||
$contextHash = hash_init('crc32b');
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $size) {
|
||||
$read = min(self::CHUNK_SIZE, $size - $offset);
|
||||
$buffer = fread($inStream, $read);
|
||||
fwrite($outStream, $buffer);
|
||||
hash_update($contextHash, $buffer);
|
||||
$offset += $read;
|
||||
}
|
||||
|
||||
return (int) hexdec(hash_final($contextHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipUnsupportMethodException
|
||||
*
|
||||
* @return resource|null
|
||||
*/
|
||||
protected function appendCompressionFilter($outStream, ZipEntry $entry)
|
||||
{
|
||||
$contextCompress = null;
|
||||
switch ($entry->getCompressionMethod()) {
|
||||
case ZipCompressionMethod::DEFLATED:
|
||||
if (!($contextCompress = stream_filter_append(
|
||||
$outStream,
|
||||
'zlib.deflate',
|
||||
\STREAM_FILTER_WRITE,
|
||||
['level' => $entry->getCompressionLevel()]
|
||||
))) {
|
||||
throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream');
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipCompressionMethod::BZIP2:
|
||||
if (!($contextCompress = stream_filter_append(
|
||||
$outStream,
|
||||
'bzip2.compress',
|
||||
\STREAM_FILTER_WRITE,
|
||||
['blocks' => $entry->getCompressionLevel(), 'work' => 0]
|
||||
))) {
|
||||
throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream');
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipCompressionMethod::STORED:
|
||||
// file without compression, do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipUnsupportMethodException(
|
||||
sprintf(
|
||||
'%s (compression method %d (%s) is not supported)',
|
||||
$entry->getName(),
|
||||
$entry->getCompressionMethod(),
|
||||
ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $contextCompress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @return resource|null
|
||||
*/
|
||||
protected function appendEncryptionFilter($outStream, ZipEntry $entry, int $size)
|
||||
{
|
||||
$encContextFilter = null;
|
||||
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
|
||||
PKEncryptionStreamFilter::register();
|
||||
$cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME;
|
||||
} else {
|
||||
WinZipAesEncryptionStreamFilter::register();
|
||||
$cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME;
|
||||
}
|
||||
$encContextFilter = stream_filter_append(
|
||||
$outStream,
|
||||
$cipherFilterName,
|
||||
\STREAM_FILTER_WRITE,
|
||||
[
|
||||
'entry' => $entry,
|
||||
'size' => $size,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$encContextFilter) {
|
||||
throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
|
||||
}
|
||||
}
|
||||
|
||||
return $encContextFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*/
|
||||
protected function writeDataDescriptor($outStream, ZipEntry $entry): void
|
||||
{
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VV',
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
ZipConstants::DATA_DESCRIPTOR,
|
||||
// crc-32 4 bytes
|
||||
$crc
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
$entry->isZip64ExtensionsRequired()
|
||||
|| $entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID)
|
||||
) {
|
||||
$dd = pack(
|
||||
'PP',
|
||||
// compressed size 8 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 8 bytes
|
||||
$entry->getUncompressedSize()
|
||||
);
|
||||
} else {
|
||||
$dd = pack(
|
||||
'VV',
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getUncompressedSize()
|
||||
);
|
||||
}
|
||||
|
||||
fwrite($outStream, $dd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeCentralDirectoryBlock($outStream): void
|
||||
{
|
||||
foreach ($this->zipContainer->getEntries() as $outputEntry) {
|
||||
$this->writeCentralDirectoryHeader($outStream, $outputEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry): void
|
||||
{
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
$localHeaderOffset = $entry->getLocalHeaderOffset();
|
||||
|
||||
$entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
if (
|
||||
$localHeaderOffset > ZipConstants::ZIP64_MAGIC
|
||||
|| $compressedSize > ZipConstants::ZIP64_MAGIC
|
||||
|| $uncompressedSize > ZipConstants::ZIP64_MAGIC
|
||||
) {
|
||||
$zip64ExtraField = new Zip64ExtraField();
|
||||
|
||||
if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setUncompressedSize($uncompressedSize);
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
if ($compressedSize >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setCompressedSize($compressedSize);
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setLocalHeaderOffset($localHeaderOffset);
|
||||
$localHeaderOffset = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$entry->getCdExtraFields()->add($zip64ExtraField);
|
||||
}
|
||||
|
||||
$extra = $this->getExtraFieldsContents($entry, false);
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
$name = $entry->getName();
|
||||
$comment = $entry->getComment();
|
||||
|
||||
$dosCharset = $entry->getCharset();
|
||||
|
||||
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
|
||||
$name = DosCodePage::fromUTF8($name, $dosCharset);
|
||||
|
||||
if ($comment) {
|
||||
$comment = DosCodePage::fromUTF8($comment, $dosCharset);
|
||||
}
|
||||
}
|
||||
|
||||
$commentLength = \strlen($comment);
|
||||
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null) {
|
||||
if ($winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
ZipConstants::CENTRAL_FILE_HEADER,
|
||||
// version made by 2 bytes
|
||||
($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
|
||||
// version needed to extract 2 bytes
|
||||
($entry->getExtractedOS() << 8) | $entry->getExtractVersion(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$compressionMethod,
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$crc,
|
||||
// compressed size 4 bytes
|
||||
$compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$uncompressedSize,
|
||||
// file name length 2 bytes
|
||||
\strlen($name),
|
||||
// extra field length 2 bytes
|
||||
$extraLength,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
$entry->getInternalAttributes(),
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$localHeaderOffset
|
||||
)
|
||||
);
|
||||
|
||||
// file name (variable size)
|
||||
fwrite($outStream, $name);
|
||||
|
||||
if ($extraLength > 0) {
|
||||
// extra field (variable size)
|
||||
fwrite($outStream, $extra);
|
||||
}
|
||||
|
||||
if ($commentLength > 0) {
|
||||
// file comment (variable size)
|
||||
fwrite($outStream, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*/
|
||||
protected function writeEndOfCentralDirectoryBlock(
|
||||
$outStream,
|
||||
int $centralDirectoryOffset,
|
||||
int $centralDirectorySize
|
||||
): void {
|
||||
$cdEntriesCount = \count($this->zipContainer);
|
||||
|
||||
$cdEntriesZip64 = $cdEntriesCount > 0xFFFF;
|
||||
$cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC;
|
||||
$cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC;
|
||||
|
||||
$zip64Required = $cdEntriesZip64
|
||||
|| $cdSizeZip64
|
||||
|| $cdOffsetZip64;
|
||||
|
||||
if ($zip64Required) {
|
||||
$zip64EndOfCentralDirectoryOffset = ftell($outStream);
|
||||
|
||||
// find max software version, version needed to extract and most common platform
|
||||
[$softwareVersion, $versionNeededToExtract] = array_reduce(
|
||||
$this->zipContainer->getEntries(),
|
||||
static function (array $carry, ZipEntry $entry) {
|
||||
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
|
||||
$carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF);
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT]
|
||||
);
|
||||
|
||||
$createdOS = $extractedOS = ZipPlatform::OS_DOS;
|
||||
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT);
|
||||
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT);
|
||||
|
||||
// write zip64 end of central directory signature
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VPvvVVPPPPVVPV',
|
||||
// signature 4 bytes (0x06064b50)
|
||||
ZipConstants::ZIP64_END_CD,
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
ZipConstants::ZIP64_END_OF_CD_LEN - 12,
|
||||
// version made by 2 bytes
|
||||
$versionMadeBy & 0xFFFF,
|
||||
// version needed to extract 2 bytes
|
||||
$versionExtractedBy & 0xFFFF,
|
||||
// number of this disk 4 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesCount,
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntriesCount,
|
||||
// size of the central directory 8 bytes
|
||||
$centralDirectorySize,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$centralDirectoryOffset,
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
ZipConstants::ZIP64_END_CD_LOC,
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
0,
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryOffset,
|
||||
// total number of disks 4 bytes
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$comment = $this->zipContainer->getArchiveComment();
|
||||
$commentLength = $comment !== null ? \strlen($comment) : 0;
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
ZipConstants::END_CD,
|
||||
// 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
|
||||
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
|
||||
// size of the central directory 4 bytes
|
||||
$cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($comment !== null && $commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($outStream, $comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
vendor/nelexa/zip/src/Model/Data/ZipFileData.php
vendored
Normal file
78
vendor/nelexa/zip/src/Model/Data/ZipFileData.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Data;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipData;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
class ZipFileData implements ZipData
|
||||
{
|
||||
private \SplFileInfo $file;
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct(ZipEntry $zipEntry, \SplFileInfo $fileInfo)
|
||||
{
|
||||
if (!$fileInfo->isFile()) {
|
||||
throw new ZipException('$fileInfo is not a file.');
|
||||
}
|
||||
|
||||
if (!$fileInfo->isReadable()) {
|
||||
throw new ZipException('$fileInfo is not readable.');
|
||||
}
|
||||
|
||||
$this->file = $fileInfo;
|
||||
$zipEntry->setUncompressedSize($fileInfo->getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return resource returns stream data
|
||||
*/
|
||||
public function getDataAsStream()
|
||||
{
|
||||
if (!$this->file->isReadable()) {
|
||||
throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
|
||||
}
|
||||
|
||||
return fopen($this->file->getPathname(), 'rb');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string returns data as string
|
||||
*/
|
||||
public function getDataAsString(): string
|
||||
{
|
||||
if (!$this->file->isReadable()) {
|
||||
throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
|
||||
}
|
||||
|
||||
return file_get_contents($this->file->getPathname());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function copyDataToStream($outStream): void
|
||||
{
|
||||
$stream = $this->getDataAsStream();
|
||||
stream_copy_to_stream($stream, $outStream);
|
||||
fclose($stream);
|
||||
}
|
||||
}
|
||||
138
vendor/nelexa/zip/src/Model/Data/ZipNewData.php
vendored
Normal file
138
vendor/nelexa/zip/src/Model/Data/ZipNewData.php
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Data;
|
||||
|
||||
use PhpZip\Model\ZipData;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* The class contains a streaming resource with new content added to the ZIP archive.
|
||||
*/
|
||||
class ZipNewData implements ZipData
|
||||
{
|
||||
/**
|
||||
* A static variable allows closing the stream in the destructor
|
||||
* only if it is its sole holder.
|
||||
*
|
||||
* @var array<int, int> array of resource ids and the number of class clones
|
||||
*/
|
||||
private static array $guardClonedStream = [];
|
||||
|
||||
private ZipEntry $zipEntry;
|
||||
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @param string|resource $data Raw string data or resource
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
public function __construct(ZipEntry $zipEntry, $data)
|
||||
{
|
||||
$this->zipEntry = $zipEntry;
|
||||
|
||||
if (\is_string($data)) {
|
||||
$zipEntry->setUncompressedSize(\strlen($data));
|
||||
|
||||
if (!($handle = fopen('php://temp', 'w+b'))) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('A temporary resource cannot be opened for writing.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
fwrite($handle, $data);
|
||||
rewind($handle);
|
||||
$this->stream = $handle;
|
||||
} elseif (\is_resource($data)) {
|
||||
$this->stream = $data;
|
||||
}
|
||||
|
||||
$resourceId = (int) $this->stream;
|
||||
self::$guardClonedStream[$resourceId]
|
||||
= isset(self::$guardClonedStream[$resourceId])
|
||||
? self::$guardClonedStream[$resourceId] + 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource returns stream data
|
||||
*/
|
||||
public function getDataAsStream()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
throw new \LogicException(sprintf('Resource has been closed (entry=%s).', $this->zipEntry->getName()));
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns data as string
|
||||
*/
|
||||
public function getDataAsString(): string
|
||||
{
|
||||
$stream = $this->getDataAsStream();
|
||||
$pos = ftell($stream);
|
||||
|
||||
try {
|
||||
rewind($stream);
|
||||
|
||||
return stream_get_contents($stream);
|
||||
} finally {
|
||||
fseek($stream, $pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*/
|
||||
public function copyDataToStream($outStream): void
|
||||
{
|
||||
$stream = $this->getDataAsStream();
|
||||
rewind($stream);
|
||||
stream_copy_to_stream($stream, $outStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://php.net/manual/en/language.oop5.cloning.php
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$resourceId = (int) $this->stream;
|
||||
self::$guardClonedStream[$resourceId]
|
||||
= isset(self::$guardClonedStream[$resourceId])
|
||||
? self::$guardClonedStream[$resourceId] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The stream will be closed when closing the zip archive.
|
||||
*
|
||||
* The method implements protection against closing the stream of the cloned object.
|
||||
*
|
||||
* @see ZipFile::close()
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$resourceId = (int) $this->stream;
|
||||
|
||||
if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) {
|
||||
self::$guardClonedStream[$resourceId]--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php
vendored
Normal file
146
vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Data;
|
||||
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\IO\ZipReader;
|
||||
use PhpZip\Model\ZipData;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
class ZipSourceFileData implements ZipData
|
||||
{
|
||||
private ZipReader $zipReader;
|
||||
|
||||
/** @var resource|null */
|
||||
private $stream;
|
||||
|
||||
private ZipEntry $sourceEntry;
|
||||
|
||||
private int $offset;
|
||||
|
||||
private int $uncompressedSize;
|
||||
|
||||
private int $compressedSize;
|
||||
|
||||
public function __construct(ZipReader $zipReader, ZipEntry $zipEntry, int $offsetData)
|
||||
{
|
||||
$this->zipReader = $zipReader;
|
||||
$this->offset = $offsetData;
|
||||
$this->sourceEntry = $zipEntry;
|
||||
$this->compressedSize = $zipEntry->getCompressedSize();
|
||||
$this->uncompressedSize = $zipEntry->getUncompressedSize();
|
||||
}
|
||||
|
||||
public function hasRecompressData(ZipEntry $entry): bool
|
||||
{
|
||||
return $this->sourceEntry->getCompressionLevel() !== $entry->getCompressionLevel()
|
||||
|| $this->sourceEntry->getCompressionMethod() !== $entry->getCompressionMethod()
|
||||
|| $this->sourceEntry->isEncrypted() !== $entry->isEncrypted()
|
||||
|| $this->sourceEntry->getEncryptionMethod() !== $entry->getEncryptionMethod()
|
||||
|| $this->sourceEntry->getPassword() !== $entry->getPassword()
|
||||
|| $this->sourceEntry->getCompressedSize() !== $entry->getCompressedSize()
|
||||
|| $this->sourceEntry->getUncompressedSize() !== $entry->getUncompressedSize()
|
||||
|| $this->sourceEntry->getCrc() !== $entry->getCrc();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return resource returns stream data
|
||||
*/
|
||||
public function getDataAsStream()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
$this->stream = $this->zipReader->getEntryStream($this);
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string returns data as string
|
||||
*/
|
||||
public function getDataAsString(): string
|
||||
{
|
||||
$autoClosable = $this->stream === null;
|
||||
|
||||
$stream = $this->getDataAsStream();
|
||||
$pos = ftell($stream);
|
||||
|
||||
try {
|
||||
rewind($stream);
|
||||
|
||||
return stream_get_contents($stream);
|
||||
} finally {
|
||||
if ($autoClosable) {
|
||||
fclose($stream);
|
||||
$this->stream = null;
|
||||
} else {
|
||||
fseek($stream, $pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream Output stream
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws Crc32Exception
|
||||
*/
|
||||
public function copyDataToStream($outStream): void
|
||||
{
|
||||
if (\is_resource($this->stream)) {
|
||||
rewind($this->stream);
|
||||
stream_copy_to_stream($this->stream, $outStream);
|
||||
} else {
|
||||
$this->zipReader->copyUncompressedDataToStream($this, $outStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outputStream Output stream
|
||||
*/
|
||||
public function copyCompressedDataToStream($outputStream): void
|
||||
{
|
||||
$this->zipReader->copyCompressedDataToStream($this, $outputStream);
|
||||
}
|
||||
|
||||
public function getSourceEntry(): ZipEntry
|
||||
{
|
||||
return $this->sourceEntry;
|
||||
}
|
||||
|
||||
public function getCompressedSize(): int
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
public function getUncompressedSize(): int
|
||||
{
|
||||
return $this->uncompressedSize;
|
||||
}
|
||||
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (\is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php
vendored
Normal file
71
vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* End of Central Directory.
|
||||
*/
|
||||
class EndOfCentralDirectory
|
||||
{
|
||||
/** @var int Count files. */
|
||||
private int $entryCount;
|
||||
|
||||
/** @var int Central Directory Offset. */
|
||||
private int $cdOffset;
|
||||
|
||||
private int $cdSize;
|
||||
|
||||
/** @var string|null The archive comment. */
|
||||
private ?string $comment;
|
||||
|
||||
/** @var bool Zip64 extension */
|
||||
private bool $zip64;
|
||||
|
||||
public function __construct(int $entryCount, int $cdOffset, int $cdSize, bool $zip64, ?string $comment = null)
|
||||
{
|
||||
$this->entryCount = $entryCount;
|
||||
$this->cdOffset = $cdOffset;
|
||||
$this->cdSize = $cdSize;
|
||||
$this->zip64 = $zip64;
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
public function setComment(?string $comment): void
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
public function getEntryCount(): int
|
||||
{
|
||||
return $this->entryCount;
|
||||
}
|
||||
|
||||
public function getCdOffset(): int
|
||||
{
|
||||
return $this->cdOffset;
|
||||
}
|
||||
|
||||
public function getCdSize(): int
|
||||
{
|
||||
return $this->cdSize;
|
||||
}
|
||||
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function isZip64(): bool
|
||||
{
|
||||
return $this->zip64;
|
||||
}
|
||||
}
|
||||
271
vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php
vendored
Normal file
271
vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra;
|
||||
|
||||
/**
|
||||
* Represents a collection of Extra Fields as they may
|
||||
* be present at several locations in ZIP files.
|
||||
*/
|
||||
class ExtraFieldsCollection implements \ArrayAccess, \Countable, \Iterator
|
||||
{
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID to Extra Field.
|
||||
* Must not be null, but may be empty if no Extra Fields are used.
|
||||
* The map is sorted by Header IDs in ascending order.
|
||||
*
|
||||
* @var ZipExtraField[]
|
||||
*/
|
||||
protected array $collection = [];
|
||||
|
||||
/**
|
||||
* Returns the number of Extra Fields in this collection.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
*
|
||||
* @param int $headerId the requested Header ID
|
||||
*
|
||||
* @return ZipExtraField|null the Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists
|
||||
*/
|
||||
public function get(int $headerId): ?ZipExtraField
|
||||
{
|
||||
$this->validateHeaderId($headerId);
|
||||
|
||||
return $this->collection[$headerId] ?? null;
|
||||
}
|
||||
|
||||
private function validateHeaderId(int $headerId): void
|
||||
{
|
||||
if ($headerId < 0 || $headerId > 0xFFFF) {
|
||||
throw new \InvalidArgumentException('$headerId out of range');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given Extra Field in this collection.
|
||||
*
|
||||
* @param ZipExtraField $extraField the Extra Field to store in this collection
|
||||
*
|
||||
* @return ZipExtraField the Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed
|
||||
*/
|
||||
public function add(ZipExtraField $extraField): ZipExtraField
|
||||
{
|
||||
$headerId = $extraField->getHeaderId();
|
||||
|
||||
$this->validateHeaderId($headerId);
|
||||
$this->collection[$headerId] = $extraField;
|
||||
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipExtraField[] $extraFields
|
||||
*/
|
||||
public function addAll(array $extraFields): void
|
||||
{
|
||||
foreach ($extraFields as $extraField) {
|
||||
$this->add($extraField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtraFieldsCollection $collection
|
||||
*/
|
||||
public function addCollection(self $collection): void
|
||||
{
|
||||
$this->addAll($collection->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipExtraField[]
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists.
|
||||
*
|
||||
* @param int $headerId the requested Header ID
|
||||
*/
|
||||
public function has(int $headerId): bool
|
||||
{
|
||||
return isset($this->collection[$headerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Extra Field with the given Header ID.
|
||||
*
|
||||
* @param int $headerId the requested Header ID
|
||||
*
|
||||
* @return ZipExtraField|null the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists
|
||||
*/
|
||||
public function remove(int $headerId): ?ZipExtraField
|
||||
{
|
||||
$this->validateHeaderId($headerId);
|
||||
|
||||
if (isset($this->collection[$headerId])) {
|
||||
$ef = $this->collection[$headerId];
|
||||
unset($this->collection[$headerId]);
|
||||
|
||||
return $ef;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a offset exists.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
*
|
||||
* @param mixed $offset an offset to check for
|
||||
*
|
||||
* @return bool true on success or false on failure
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->collection[(int) $offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
*
|
||||
* @param mixed $offset the offset to retrieve
|
||||
*/
|
||||
public function offsetGet($offset): ?ZipExtraField
|
||||
{
|
||||
return $this->collection[(int) $offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
*
|
||||
* @param mixed $offset the offset to assign the value to
|
||||
* @param mixed $value the value to set
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if (!$value instanceof ZipExtraField) {
|
||||
throw new \InvalidArgumentException('value is not instanceof ' . ZipExtraField::class);
|
||||
}
|
||||
$this->add($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
*
|
||||
* @param mixed $offset the offset to unset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.current.php
|
||||
*/
|
||||
public function current(): ZipExtraField
|
||||
{
|
||||
return current($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
next($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.key.php
|
||||
*
|
||||
* @return int scalar on success, or null on failure
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return key($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.valid.php
|
||||
*
|
||||
* @return bool The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return key($this->collection) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->collection);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->collection = [];
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$formats = [];
|
||||
|
||||
foreach ($this->collection as $key => $value) {
|
||||
$formats[] = (string) $value;
|
||||
}
|
||||
|
||||
return implode("\n", $formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* If clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->collection as $k => $v) {
|
||||
$this->collection[$k] = clone $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
133
vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php
vendored
Normal file
133
vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* A common base class for Unicode extra information extra fields.
|
||||
*/
|
||||
abstract class AbstractUnicodeExtraField implements ZipExtraField
|
||||
{
|
||||
public const DEFAULT_VERSION = 0x01;
|
||||
|
||||
private int $crc32;
|
||||
|
||||
private string $unicodeValue;
|
||||
|
||||
public function __construct(int $crc32, ?string $unicodeValue)
|
||||
{
|
||||
$this->crc32 = $crc32;
|
||||
$this->unicodeValue = $unicodeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int the CRC32 checksum of the filename or comment as
|
||||
* encoded in the central directory of the zip file
|
||||
*/
|
||||
public function getCrc32(): int
|
||||
{
|
||||
return $this->crc32;
|
||||
}
|
||||
|
||||
public function setCrc32(int $crc32): void
|
||||
{
|
||||
$this->crc32 = $crc32;
|
||||
}
|
||||
|
||||
public function getUnicodeValue(): string
|
||||
{
|
||||
return $this->unicodeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $unicodeValue the UTF-8 encoded name to set
|
||||
*/
|
||||
public function setUnicodeValue(string $unicodeValue): void
|
||||
{
|
||||
$this->unicodeValue = $unicodeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
if (\strlen($buffer) < 5) {
|
||||
throw new ZipException('Unicode path extra data must have at least 5 bytes.');
|
||||
}
|
||||
|
||||
[
|
||||
'version' => $version,
|
||||
'crc32' => $crc32,
|
||||
] = unpack('Cversion/Vcrc32', $buffer);
|
||||
|
||||
if ($version !== self::DEFAULT_VERSION) {
|
||||
throw new ZipException(sprintf('Unsupported version [%d] for Unicode path extra data.', $version));
|
||||
}
|
||||
|
||||
$unicodeValue = substr($buffer, 5);
|
||||
|
||||
return new static($crc32, $unicodeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return pack(
|
||||
'CV',
|
||||
self::DEFAULT_VERSION,
|
||||
$this->crc32
|
||||
)
|
||||
. $this->unicodeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
}
|
||||
156
vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php
vendored
Normal file
156
vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Apk Alignment Extra Field.
|
||||
*
|
||||
* @see https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/ApkSigner.java
|
||||
* @see https://developer.android.com/studio/command-line/zipalign
|
||||
*/
|
||||
final class ApkAlignmentExtraField implements ZipExtraField
|
||||
{
|
||||
/**
|
||||
* @var int Extensible data block/field header ID used for storing
|
||||
* information about alignment of uncompressed entries as
|
||||
* well as for aligning the entries's data. See ZIP
|
||||
* appnote.txt section 4.5 Extensible data fields.
|
||||
*/
|
||||
public const HEADER_ID = 0xD935;
|
||||
|
||||
/** @var int */
|
||||
public const ALIGNMENT_BYTES = 4;
|
||||
|
||||
/** @var int */
|
||||
public const COMMON_PAGE_ALIGNMENT_BYTES = 4096;
|
||||
|
||||
private int $multiple;
|
||||
|
||||
private int $padding;
|
||||
|
||||
public function __construct(int $multiple, int $padding)
|
||||
{
|
||||
$this->multiple = $multiple;
|
||||
$this->padding = $padding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
public function getMultiple(): int
|
||||
{
|
||||
return $this->multiple;
|
||||
}
|
||||
|
||||
public function getPadding(): int
|
||||
{
|
||||
return $this->padding;
|
||||
}
|
||||
|
||||
public function setMultiple(int $multiple): void
|
||||
{
|
||||
$this->multiple = $multiple;
|
||||
}
|
||||
|
||||
public function setPadding(int $padding): void
|
||||
{
|
||||
$this->padding = $padding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ApkAlignmentExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
|
||||
if ($length < 2) {
|
||||
// This is APK alignment field.
|
||||
// FORMAT:
|
||||
// * uint16 alignment multiple (in bytes)
|
||||
// * remaining bytes -- padding to achieve alignment of data which starts after
|
||||
// the extra field
|
||||
throw new ZipException(
|
||||
'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
|
||||
);
|
||||
}
|
||||
$multiple = unpack('v', $buffer)[1];
|
||||
$padding = $length - 2;
|
||||
|
||||
return new self($multiple, $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return ApkAlignmentExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return pack('vx' . $this->padding, $this->multiple);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x APK Alignment: Multiple=%d Padding=%d',
|
||||
self::HEADER_ID,
|
||||
$this->multiple,
|
||||
$this->padding
|
||||
);
|
||||
}
|
||||
}
|
||||
285
vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php
vendored
Normal file
285
vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Constants\UnixStat;
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* ASi Unix Extra Field:
|
||||
* ====================.
|
||||
*
|
||||
* The following is the layout of the ASi extra block for Unix. The
|
||||
* local-header and central-header versions are identical.
|
||||
* (Last Revision 19960916)
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (Unix3) 0x756e Short tag for this extra block type ("nu")
|
||||
* TSize Short total data size for this block
|
||||
* CRC Long CRC-32 of the remaining data
|
||||
* Mode Short file permissions
|
||||
* SizDev Long symlink'd size OR major/minor dev num
|
||||
* UID Short user ID
|
||||
* GID Short group ID
|
||||
* (var.) variable symbolic link filename
|
||||
*
|
||||
* Mode is the standard Unix st_mode field from struct stat, containing
|
||||
* user/group/other permissions, setuid/setgid and symlink info, etc.
|
||||
*
|
||||
* If Mode indicates that this file is a symbolic link, SizDev is the
|
||||
* size of the file to which the link points. Otherwise, if the file
|
||||
* is a device, SizDev contains the standard Unix st_rdev field from
|
||||
* struct stat (includes the major and minor numbers of the device).
|
||||
* SizDev is undefined in other cases.
|
||||
*
|
||||
* If Mode indicates that the file is a symbolic link, the final field
|
||||
* will be the name of the file to which the link points. The file-
|
||||
* name length can be inferred from TSize.
|
||||
*
|
||||
* [Note that TSize may incorrectly refer to the data size not counting
|
||||
* the CRC; i.e., it may be four bytes too small.]
|
||||
*
|
||||
* @see ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip Info-ZIP version Specification
|
||||
*/
|
||||
final class AsiExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id */
|
||||
public const HEADER_ID = 0x756E;
|
||||
|
||||
public const USER_GID_PID = 1000;
|
||||
|
||||
/** Bits used for permissions (and sticky bit). */
|
||||
public const PERM_MASK = 07777;
|
||||
|
||||
/** @var int Standard Unix stat(2) file mode. */
|
||||
private int $mode;
|
||||
|
||||
/** @var int User ID. */
|
||||
private int $uid;
|
||||
|
||||
/** @var int Group ID. */
|
||||
private int $gid;
|
||||
|
||||
/**
|
||||
* @var string File this entry points to, if it is a symbolic link.
|
||||
* Empty string - if entry is not a symbolic link.
|
||||
*/
|
||||
private string $link;
|
||||
|
||||
public function __construct(int $mode, int $uid = self::USER_GID_PID, int $gid = self::USER_GID_PID, ?string $link = '')
|
||||
{
|
||||
$this->mode = $mode;
|
||||
$this->uid = $uid;
|
||||
$this->gid = $gid;
|
||||
$this->link = $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws Crc32Exception
|
||||
*
|
||||
* @return AsiExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$givenChecksum = unpack('V', $buffer)[1];
|
||||
$buffer = substr($buffer, 4);
|
||||
$realChecksum = crc32($buffer);
|
||||
|
||||
if ($givenChecksum !== $realChecksum) {
|
||||
throw new Crc32Exception('Asi Unix Extra Filed Data', $givenChecksum, $realChecksum);
|
||||
}
|
||||
|
||||
[
|
||||
'mode' => $mode,
|
||||
'linkSize' => $linkSize,
|
||||
'uid' => $uid,
|
||||
'gid' => $gid,
|
||||
] = unpack('vmode/VlinkSize/vuid/vgid', $buffer);
|
||||
$link = '';
|
||||
|
||||
if ($linkSize > 0) {
|
||||
$link = substr($buffer, 10);
|
||||
}
|
||||
|
||||
return new self($mode, $uid, $gid, $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws Crc32Exception
|
||||
*
|
||||
* @return AsiExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
$data = pack(
|
||||
'vVvv',
|
||||
$this->mode,
|
||||
\strlen($this->link),
|
||||
$this->uid,
|
||||
$this->gid
|
||||
) . $this->link;
|
||||
|
||||
return pack('V', crc32($data)) . $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of linked file.
|
||||
*
|
||||
* @return string name of the file this entry links to if it is a
|
||||
* symbolic link, the empty string otherwise
|
||||
*/
|
||||
public function getLink(): string
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this entry is a symbolic link to the given filename.
|
||||
*
|
||||
* @param string $link name of the file this entry links to, empty
|
||||
* string if it is not a symbolic link
|
||||
*/
|
||||
public function setLink(string $link): void
|
||||
{
|
||||
$this->link = $link;
|
||||
$this->mode = $this->getPermissionsMode($this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this entry a symbolic link?
|
||||
*
|
||||
* @return bool true if this is a symbolic link
|
||||
*/
|
||||
public function isLink(): bool
|
||||
{
|
||||
return !empty($this->link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file mode for given permissions with the correct file type.
|
||||
*
|
||||
* @param int $mode the mode
|
||||
*
|
||||
* @return int the type with the mode
|
||||
*/
|
||||
private function getPermissionsMode(int $mode): int
|
||||
{
|
||||
$type = 0;
|
||||
|
||||
if ($this->isLink()) {
|
||||
$type = UnixStat::UNX_IFLNK;
|
||||
} elseif (($mode & UnixStat::UNX_IFREG) !== 0) {
|
||||
$type = UnixStat::UNX_IFREG;
|
||||
} elseif (($mode & UnixStat::UNX_IFDIR) !== 0) {
|
||||
$type = UnixStat::UNX_IFDIR;
|
||||
}
|
||||
|
||||
return $type | ($mode & self::PERM_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this entry a directory?
|
||||
*
|
||||
* @return bool true if this entry is a directory
|
||||
*/
|
||||
public function isDirectory(): bool
|
||||
{
|
||||
return ($this->mode & UnixStat::UNX_IFDIR) !== 0 && !$this->isLink();
|
||||
}
|
||||
|
||||
public function getMode(): int
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
public function setMode(int $mode): void
|
||||
{
|
||||
$this->mode = $this->getPermissionsMode($mode);
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function setUserId(int $uid): void
|
||||
{
|
||||
$this->uid = $uid;
|
||||
}
|
||||
|
||||
public function getGroupId(): int
|
||||
{
|
||||
return $this->gid;
|
||||
}
|
||||
|
||||
public function setGroupId(int $gid): void
|
||||
{
|
||||
$this->gid = $gid;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x ASI: Mode=%o UID=%d GID=%d Link="%s',
|
||||
self::HEADER_ID,
|
||||
$this->mode,
|
||||
$this->uid,
|
||||
$this->gid,
|
||||
$this->link
|
||||
);
|
||||
}
|
||||
}
|
||||
436
vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php
vendored
Normal file
436
vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php
vendored
Normal file
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Extended Timestamp Extra Field:
|
||||
* ==============================.
|
||||
*
|
||||
* The following is the layout of the extended-timestamp extra block.
|
||||
* (Last Revision 19970118)
|
||||
*
|
||||
* Local-header version:
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (time) 0x5455 Short tag for this extra block type ("UT")
|
||||
* TSize Short total data size for this block
|
||||
* Flags Byte info bits
|
||||
* (ModTime) Long time of last modification (UTC/GMT)
|
||||
* (AcTime) Long time of last access (UTC/GMT)
|
||||
* (CrTime) Long time of original creation (UTC/GMT)
|
||||
*
|
||||
* Central-header version:
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (time) 0x5455 Short tag for this extra block type ("UT")
|
||||
* TSize Short total data size for this block
|
||||
* Flags Byte info bits (refers to local header!)
|
||||
* (ModTime) Long time of last modification (UTC/GMT)
|
||||
*
|
||||
* The central-header extra field contains the modification time only,
|
||||
* or no timestamp at all. TSize is used to flag its presence or
|
||||
* absence. But note:
|
||||
*
|
||||
* If "Flags" indicates that Modtime is present in the local header
|
||||
* field, it MUST be present in the central header field, too!
|
||||
* This correspondence is required because the modification time
|
||||
* value may be used to support trans-timezone freshening and
|
||||
* updating operations with zip archives.
|
||||
*
|
||||
* The time values are in standard Unix signed-long format, indicating
|
||||
* the number of seconds since 1 January 1970 00:00:00. The times
|
||||
* are relative to Coordinated Universal Time (UTC), also sometimes
|
||||
* referred to as Greenwich Mean Time (GMT). To convert to local time,
|
||||
* the software must know the local timezone offset from UTC/GMT.
|
||||
*
|
||||
* The lower three bits of Flags in both headers indicate which time-
|
||||
* stamps are present in the LOCAL extra field:
|
||||
*
|
||||
* bit 0 if set, modification time is present
|
||||
* bit 1 if set, access time is present
|
||||
* bit 2 if set, creation time is present
|
||||
* bits 3-7 reserved for additional timestamps; not set
|
||||
*
|
||||
* Those times that are present will appear in the order indicated, but
|
||||
* any combination of times may be omitted. (Creation time may be
|
||||
* present without access time, for example.) TSize should equal
|
||||
* (1 + 4*(number of set bits in Flags)), as the block is currently
|
||||
* defined. Other timestamps may be added in the future.
|
||||
*
|
||||
* @see ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip Info-ZIP version Specification
|
||||
*/
|
||||
final class ExtendedTimestampExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id */
|
||||
public const HEADER_ID = 0x5455;
|
||||
|
||||
/**
|
||||
* @var int the bit set inside the flags by when the last modification time
|
||||
* is present in this extra field
|
||||
*/
|
||||
public const MODIFY_TIME_BIT = 1;
|
||||
|
||||
/**
|
||||
* @var int the bit set inside the flags by when the last access time is
|
||||
* present in this extra field
|
||||
*/
|
||||
public const ACCESS_TIME_BIT = 2;
|
||||
|
||||
/**
|
||||
* @var int the bit set inside the flags by when the original creation time
|
||||
* is present in this extra field
|
||||
*/
|
||||
public const CREATE_TIME_BIT = 4;
|
||||
|
||||
/**
|
||||
* @var int The 3 boolean fields (below) come from this flags byte. The remaining 5 bits
|
||||
* are ignored according to the current version of the spec (December 2012).
|
||||
*/
|
||||
private int $flags;
|
||||
|
||||
/** @var int|null Modify time */
|
||||
private ?int $modifyTime;
|
||||
|
||||
/** @var int|null Access time */
|
||||
private ?int $accessTime;
|
||||
|
||||
/** @var int|null Create time */
|
||||
private ?int $createTime;
|
||||
|
||||
public function __construct(int $flags, ?int $modifyTime, ?int $accessTime, ?int $createTime)
|
||||
{
|
||||
$this->flags = $flags;
|
||||
$this->modifyTime = $modifyTime;
|
||||
$this->accessTime = $accessTime;
|
||||
$this->createTime = $createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?int $modifyTime
|
||||
* @param ?int $accessTime
|
||||
* @param ?int $createTime
|
||||
*
|
||||
* @return ExtendedTimestampExtraField
|
||||
*/
|
||||
public static function create(?int $modifyTime, ?int $accessTime, ?int $createTime): self
|
||||
{
|
||||
$flags = 0;
|
||||
|
||||
if ($modifyTime !== null) {
|
||||
$flags |= self::MODIFY_TIME_BIT;
|
||||
}
|
||||
|
||||
if ($accessTime !== null) {
|
||||
$flags |= self::ACCESS_TIME_BIT;
|
||||
}
|
||||
|
||||
if ($createTime !== null) {
|
||||
$flags |= self::CREATE_TIME_BIT;
|
||||
}
|
||||
|
||||
return new self($flags, $modifyTime, $accessTime, $createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return ExtendedTimestampExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
$flags = unpack('C', $buffer)[1];
|
||||
$offset = 1;
|
||||
|
||||
$modifyTime = null;
|
||||
$accessTime = null;
|
||||
$createTime = null;
|
||||
|
||||
if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) {
|
||||
$modifyTime = unpack('V', substr($buffer, $offset, 4))[1];
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
// Notice the extra length check in case we are parsing the shorter
|
||||
// central data field (for both access and create timestamps).
|
||||
if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) {
|
||||
$accessTime = unpack('V', substr($buffer, $offset, 4))[1];
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) {
|
||||
$createTime = unpack('V', substr($buffer, $offset, 4))[1];
|
||||
}
|
||||
|
||||
return new self($flags, $modifyTime, $accessTime, $createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return ExtendedTimestampExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) {
|
||||
$data .= pack('V', $this->modifyTime);
|
||||
}
|
||||
|
||||
if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) {
|
||||
$data .= pack('V', $this->accessTime);
|
||||
}
|
||||
|
||||
if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) {
|
||||
$data .= pack('V', $this->createTime);
|
||||
}
|
||||
|
||||
return pack('C', $this->flags) . $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* Note: even if bit1 and bit2 are set, the Central data will still
|
||||
* not contain access/create fields: only local data ever holds those!
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
$cdLength = 1 + ($this->modifyTime !== null ? 4 : 0);
|
||||
|
||||
return substr($this->packLocalFileData(), 0, $cdLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets flags byte.
|
||||
*
|
||||
* The flags byte tells us which of the three datestamp fields are
|
||||
* present in the data:
|
||||
* bit0 - modify time
|
||||
* bit1 - access time
|
||||
* bit2 - create time
|
||||
*
|
||||
* Only first 3 bits of flags are used according to the
|
||||
* latest version of the spec (December 2012).
|
||||
*
|
||||
* @return int flags byte indicating which of the
|
||||
* three datestamp fields are present
|
||||
*/
|
||||
public function getFlags(): int
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modify time (seconds since epoch) of this zip entry,
|
||||
* or null if no such timestamp exists in the zip entry.
|
||||
*
|
||||
* @return int|null modify time (seconds since epoch) or null
|
||||
*/
|
||||
public function getModifyTime(): ?int
|
||||
{
|
||||
return $this->modifyTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the access time (seconds since epoch) of this zip entry,
|
||||
* or null if no such timestamp exists in the zip entry.
|
||||
*
|
||||
* @return int|null access time (seconds since epoch) or null
|
||||
*/
|
||||
public function getAccessTime(): ?int
|
||||
{
|
||||
return $this->accessTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the create time (seconds since epoch) of this zip entry,
|
||||
* or null if no such timestamp exists in the zip entry.
|
||||
*
|
||||
* Note: modern linux file systems (e.g., ext2)
|
||||
* do not appear to store a "create time" value, and so
|
||||
* it's usually omitted altogether in the zip extra
|
||||
* field. Perhaps other unix systems track this.
|
||||
*
|
||||
* @return int|null create time (seconds since epoch) or null
|
||||
*/
|
||||
public function getCreateTime(): ?int
|
||||
{
|
||||
return $this->createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modify time as a \DateTimeInterface
|
||||
* of this zip entry, or null if no such timestamp exists in the zip entry.
|
||||
* The milliseconds are always zeroed out, since the underlying data
|
||||
* offers only per-second precision.
|
||||
*
|
||||
* @return \DateTimeInterface|null modify time as \DateTimeInterface or null
|
||||
*/
|
||||
public function getModifyDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
return self::timestampToDateTime($this->modifyTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the access time as a \DateTimeInterface
|
||||
* of this zip entry, or null if no such timestamp exists in the zip entry.
|
||||
* The milliseconds are always zeroed out, since the underlying data
|
||||
* offers only per-second precision.
|
||||
*
|
||||
* @return \DateTimeInterface|null access time as \DateTimeInterface or null
|
||||
*/
|
||||
public function getAccessDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
return self::timestampToDateTime($this->accessTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the create time as a a \DateTimeInterface
|
||||
* of this zip entry, or null if no such timestamp exists in the zip entry.
|
||||
* The milliseconds are always zeroed out, since the underlying data
|
||||
* offers only per-second precision.
|
||||
*
|
||||
* Note: modern linux file systems (e.g., ext2)
|
||||
* do not appear to store a "create time" value, and so
|
||||
* it's usually omitted altogether in the zip extra
|
||||
* field. Perhaps other unix systems track $this->.
|
||||
*
|
||||
* @return \DateTimeInterface|null create time as \DateTimeInterface or null
|
||||
*/
|
||||
public function getCreateDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
return self::timestampToDateTime($this->createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the modify time (seconds since epoch) of this zip entry
|
||||
* using a integer.
|
||||
*
|
||||
* @param int|null $unixTime unix time of the modify time (seconds per epoch) or null
|
||||
*/
|
||||
public function setModifyTime(?int $unixTime): void
|
||||
{
|
||||
$this->modifyTime = $unixTime;
|
||||
$this->updateFlags();
|
||||
}
|
||||
|
||||
private function updateFlags(): void
|
||||
{
|
||||
$flags = 0;
|
||||
|
||||
if ($this->modifyTime !== null) {
|
||||
$flags |= self::MODIFY_TIME_BIT;
|
||||
}
|
||||
|
||||
if ($this->accessTime !== null) {
|
||||
$flags |= self::ACCESS_TIME_BIT;
|
||||
}
|
||||
|
||||
if ($this->createTime !== null) {
|
||||
$flags |= self::CREATE_TIME_BIT;
|
||||
}
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the access time (seconds since epoch) of this zip entry
|
||||
* using a integer.
|
||||
*
|
||||
* @param int|null $unixTime Unix time of the access time (seconds per epoch) or null
|
||||
*/
|
||||
public function setAccessTime(?int $unixTime): void
|
||||
{
|
||||
$this->accessTime = $unixTime;
|
||||
$this->updateFlags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the create time (seconds since epoch) of this zip entry
|
||||
* using a integer.
|
||||
*
|
||||
* @param int|null $unixTime Unix time of the create time (seconds per epoch) or null
|
||||
*/
|
||||
public function setCreateTime(?int $unixTime): void
|
||||
{
|
||||
$this->createTime = $unixTime;
|
||||
$this->updateFlags();
|
||||
}
|
||||
|
||||
private static function timestampToDateTime(?int $timestamp): ?\DateTimeInterface
|
||||
{
|
||||
try {
|
||||
return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$args = [self::HEADER_ID];
|
||||
$format = '0x%04x ExtendedTimestamp:';
|
||||
|
||||
if ($this->modifyTime !== null) {
|
||||
$format .= ' Modify:[%s]';
|
||||
$args[] = date(\DATE_W3C, $this->modifyTime);
|
||||
}
|
||||
|
||||
if ($this->accessTime !== null) {
|
||||
$format .= ' Access:[%s]';
|
||||
$args[] = date(\DATE_W3C, $this->accessTime);
|
||||
}
|
||||
|
||||
if ($this->createTime !== null) {
|
||||
$format .= ' Create:[%s]';
|
||||
$args[] = date(\DATE_W3C, $this->createTime);
|
||||
}
|
||||
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
}
|
||||
117
vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php
vendored
Normal file
117
vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipContainer;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Jar Marker Extra Field.
|
||||
* An executable Java program can be packaged in a JAR file with all the libraries it uses.
|
||||
* Executable JAR files can easily be distinguished from the files packed in the JAR file
|
||||
* by the extra field in the first file, which is hexadecimal in the 0xCAFE bytes series.
|
||||
* If this extra field is added as the very first extra field of
|
||||
* the archive, Solaris will consider it an executable jar file.
|
||||
*/
|
||||
final class JarMarkerExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id. */
|
||||
public const HEADER_ID = 0xCAFE;
|
||||
|
||||
public static function setJarMarker(ZipContainer $container): void
|
||||
{
|
||||
$zipEntries = $container->getEntries();
|
||||
|
||||
if (!empty($zipEntries)) {
|
||||
foreach ($zipEntries as $zipEntry) {
|
||||
$zipEntry->removeExtraField(self::HEADER_ID);
|
||||
}
|
||||
// set jar execute bit
|
||||
reset($zipEntries);
|
||||
$zipEntry = current($zipEntries);
|
||||
$zipEntry->getCdExtraFields()[] = new self();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return JarMarkerExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
if (!empty($buffer)) {
|
||||
throw new ZipException("JarMarker doesn't expect any data");
|
||||
}
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return JarMarkerExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('0x%04x Jar Marker', self::HEADER_ID);
|
||||
}
|
||||
}
|
||||
216
vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php
vendored
Normal file
216
vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Info-ZIP New Unix Extra Field:
|
||||
* ====================================.
|
||||
*
|
||||
* Currently stores Unix UIDs/GIDs up to 32 bits.
|
||||
* (Last Revision 20080509)
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (UnixN) 0x7875 Short tag for this extra block type ("ux")
|
||||
* TSize Short total data size for this block
|
||||
* Version 1 byte version of this extra field, currently 1
|
||||
* UIDSize 1 byte Size of UID field
|
||||
* UID Variable UID for this entry
|
||||
* GIDSize 1 byte Size of GID field
|
||||
* GID Variable GID for this entry
|
||||
*
|
||||
* Currently Version is set to the number 1. If there is a need
|
||||
* to change this field, the version will be incremented. Changes
|
||||
* may not be backward compatible so this extra field should not be
|
||||
* used if the version is not recognized.
|
||||
*
|
||||
* UIDSize is the size of the UID field in bytes. This size should
|
||||
* match the size of the UID field on the target OS.
|
||||
*
|
||||
* UID is the UID for this entry in standard little endian format.
|
||||
*
|
||||
* GIDSize is the size of the GID field in bytes. This size should
|
||||
* match the size of the GID field on the target OS.
|
||||
*
|
||||
* GID is the GID for this entry in standard little endian format.
|
||||
*
|
||||
* If both the old 16-bit Unix extra field (tag 0x7855, Info-ZIP Unix)
|
||||
* and this extra field are present, the values in this extra field
|
||||
* supercede the values in that extra field.
|
||||
*/
|
||||
final class NewUnixExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int header id */
|
||||
public const HEADER_ID = 0x7875;
|
||||
|
||||
/** ID of the first non-root user created on a unix system. */
|
||||
public const USER_GID_PID = 1000;
|
||||
|
||||
/** @var int version of this extra field, currently 1 */
|
||||
private int $version;
|
||||
|
||||
/** @var int User id */
|
||||
private int $uid;
|
||||
|
||||
/** @var int Group id */
|
||||
private int $gid;
|
||||
|
||||
public function __construct(int $version = 1, int $uid = self::USER_GID_PID, int $gid = self::USER_GID_PID)
|
||||
{
|
||||
$this->version = $version;
|
||||
$this->uid = $uid;
|
||||
$this->gid = $gid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return NewUnixExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
|
||||
if ($length < 3) {
|
||||
throw new ZipException(sprintf('X7875_NewUnix length is too short, only %s bytes', $length));
|
||||
}
|
||||
$offset = 0;
|
||||
[
|
||||
'version' => $version,
|
||||
'uidSize' => $uidSize,
|
||||
] = unpack('Cversion/CuidSize', $buffer);
|
||||
$offset += 2;
|
||||
$gid = self::readSizeIntegerLE(substr($buffer, $offset, $uidSize), $uidSize);
|
||||
$offset += $uidSize;
|
||||
$gidSize = unpack('C', $buffer[$offset])[1];
|
||||
$offset++;
|
||||
$uid = self::readSizeIntegerLE(substr($buffer, $offset, $gidSize), $gidSize);
|
||||
|
||||
return new self($version, $gid, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return NewUnixExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return pack(
|
||||
'CCVCV',
|
||||
$this->version,
|
||||
4, // UIDSize
|
||||
$this->uid,
|
||||
4, // GIDSize
|
||||
$this->gid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
private static function readSizeIntegerLE(string $data, int $size): int
|
||||
{
|
||||
$format = [
|
||||
1 => 'C', // unsigned byte
|
||||
2 => 'v', // unsigned short LE
|
||||
4 => 'V', // unsigned int LE
|
||||
];
|
||||
|
||||
if (!isset($format[$size])) {
|
||||
throw new ZipException(sprintf('Invalid size bytes: %d', $size));
|
||||
}
|
||||
|
||||
return unpack($format[$size], $data)[1];
|
||||
}
|
||||
|
||||
public function getUid(): int
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function setUid(int $uid): void
|
||||
{
|
||||
$this->uid = $uid & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
public function getGid(): int
|
||||
{
|
||||
return $this->gid;
|
||||
}
|
||||
|
||||
public function setGid(int $gid): void
|
||||
{
|
||||
$this->gid = $gid & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x NewUnix: UID=%d GID=%d',
|
||||
self::HEADER_ID,
|
||||
$this->uid,
|
||||
$this->gid
|
||||
);
|
||||
}
|
||||
}
|
||||
287
vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php
vendored
Normal file
287
vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* NTFS Extra Field.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*/
|
||||
final class NtfsExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id */
|
||||
public const HEADER_ID = 0x000A;
|
||||
|
||||
/** @var int Tag ID */
|
||||
public const TIME_ATTR_TAG = 0x0001;
|
||||
|
||||
/** @var int Attribute size */
|
||||
public const TIME_ATTR_SIZE = 24; // 3 * 8
|
||||
|
||||
/**
|
||||
* @var int A file time is a 64-bit value that represents the number of
|
||||
* 100-nanosecond intervals that have elapsed since 12:00
|
||||
* A.M. January 1, 1601 Coordinated Universal Time (UTC).
|
||||
* this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
|
||||
*/
|
||||
public const EPOCH_OFFSET = -116444736000000000;
|
||||
|
||||
/** @var int Modify ntfs time */
|
||||
private int $modifyNtfsTime;
|
||||
|
||||
/** @var int Access ntfs time */
|
||||
private int $accessNtfsTime;
|
||||
|
||||
/** @var int Create ntfs time */
|
||||
private int $createNtfsTime;
|
||||
|
||||
public function __construct(int $modifyNtfsTime, int $accessNtfsTime, int $createNtfsTime)
|
||||
{
|
||||
$this->modifyNtfsTime = $modifyNtfsTime;
|
||||
$this->accessNtfsTime = $accessNtfsTime;
|
||||
$this->createNtfsTime = $createNtfsTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NtfsExtraField
|
||||
*/
|
||||
public static function create(
|
||||
\DateTimeInterface $modifyDateTime,
|
||||
\DateTimeInterface $accessDateTime,
|
||||
\DateTimeInterface $createNtfsTime
|
||||
): self {
|
||||
return new self(
|
||||
self::dateTimeToNtfsTime($modifyDateTime),
|
||||
self::dateTimeToNtfsTime($accessDateTime),
|
||||
self::dateTimeToNtfsTime($createNtfsTime)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return NtfsExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
throw new ZipException('not supported for php-32bit');
|
||||
}
|
||||
|
||||
$buffer = substr($buffer, 4);
|
||||
|
||||
$modifyTime = 0;
|
||||
$accessTime = 0;
|
||||
$createTime = 0;
|
||||
|
||||
while ($buffer || $buffer !== '') {
|
||||
[
|
||||
'tag' => $tag,
|
||||
'sizeAttr' => $sizeAttr,
|
||||
] = unpack('vtag/vsizeAttr', $buffer);
|
||||
|
||||
if ($tag === self::TIME_ATTR_TAG && $sizeAttr === self::TIME_ATTR_SIZE) {
|
||||
[
|
||||
'modifyTime' => $modifyTime,
|
||||
'accessTime' => $accessTime,
|
||||
'createTime' => $createTime,
|
||||
] = unpack('PmodifyTime/PaccessTime/PcreateTime', substr($buffer, 4, 24));
|
||||
|
||||
break;
|
||||
}
|
||||
$buffer = substr($buffer, 4 + $sizeAttr);
|
||||
}
|
||||
|
||||
return new self($modifyTime, $accessTime, $createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return NtfsExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return pack(
|
||||
'VvvPPP',
|
||||
0,
|
||||
self::TIME_ATTR_TAG,
|
||||
self::TIME_ATTR_SIZE,
|
||||
$this->modifyNtfsTime,
|
||||
$this->accessNtfsTime,
|
||||
$this->createNtfsTime
|
||||
);
|
||||
}
|
||||
|
||||
public function getModifyNtfsTime(): int
|
||||
{
|
||||
return $this->modifyNtfsTime;
|
||||
}
|
||||
|
||||
public function setModifyNtfsTime(int $modifyNtfsTime): void
|
||||
{
|
||||
$this->modifyNtfsTime = $modifyNtfsTime;
|
||||
}
|
||||
|
||||
public function getAccessNtfsTime(): int
|
||||
{
|
||||
return $this->accessNtfsTime;
|
||||
}
|
||||
|
||||
public function setAccessNtfsTime(int $accessNtfsTime): void
|
||||
{
|
||||
$this->accessNtfsTime = $accessNtfsTime;
|
||||
}
|
||||
|
||||
public function getCreateNtfsTime(): int
|
||||
{
|
||||
return $this->createNtfsTime;
|
||||
}
|
||||
|
||||
public function setCreateNtfsTime(int $createNtfsTime): void
|
||||
{
|
||||
$this->createNtfsTime = $createNtfsTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
|
||||
public function getModifyDateTime(): \DateTimeInterface
|
||||
{
|
||||
return self::ntfsTimeToDateTime($this->modifyNtfsTime);
|
||||
}
|
||||
|
||||
public function setModifyDateTime(\DateTimeInterface $modifyTime): void
|
||||
{
|
||||
$this->modifyNtfsTime = self::dateTimeToNtfsTime($modifyTime);
|
||||
}
|
||||
|
||||
public function getAccessDateTime(): \DateTimeInterface
|
||||
{
|
||||
return self::ntfsTimeToDateTime($this->accessNtfsTime);
|
||||
}
|
||||
|
||||
public function setAccessDateTime(\DateTimeInterface $accessTime): void
|
||||
{
|
||||
$this->accessNtfsTime = self::dateTimeToNtfsTime($accessTime);
|
||||
}
|
||||
|
||||
public function getCreateDateTime(): \DateTimeInterface
|
||||
{
|
||||
return self::ntfsTimeToDateTime($this->createNtfsTime);
|
||||
}
|
||||
|
||||
public function setCreateDateTime(\DateTimeInterface $createTime): void
|
||||
{
|
||||
$this->createNtfsTime = self::dateTimeToNtfsTime($createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $timestamp Float timestamp
|
||||
*/
|
||||
public static function timestampToNtfsTime(float $timestamp): int
|
||||
{
|
||||
return (int) (($timestamp * 10000000) - self::EPOCH_OFFSET);
|
||||
}
|
||||
|
||||
public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime): int
|
||||
{
|
||||
return self::timestampToNtfsTime((float) $dateTime->format('U.u'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float Float unix timestamp
|
||||
*/
|
||||
public static function ntfsTimeToTimestamp(int $ntfsTime): float
|
||||
{
|
||||
return (float) (($ntfsTime + self::EPOCH_OFFSET) / 10000000);
|
||||
}
|
||||
|
||||
public static function ntfsTimeToDateTime(int $ntfsTime): \DateTimeInterface
|
||||
{
|
||||
$timestamp = self::ntfsTimeToTimestamp($ntfsTime);
|
||||
$dateTime = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $timestamp));
|
||||
|
||||
if ($dateTime === false) {
|
||||
throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp);
|
||||
}
|
||||
|
||||
return $dateTime;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$args = [self::HEADER_ID];
|
||||
$format = '0x%04x NtfsExtra:';
|
||||
|
||||
if ($this->modifyNtfsTime !== 0) {
|
||||
$format .= ' Modify:[%s]';
|
||||
$args[] = $this->getModifyDateTime()->format(\DATE_ATOM);
|
||||
}
|
||||
|
||||
if ($this->accessNtfsTime !== 0) {
|
||||
$format .= ' Access:[%s]';
|
||||
$args[] = $this->getAccessDateTime()->format(\DATE_ATOM);
|
||||
}
|
||||
|
||||
if ($this->createNtfsTime !== 0) {
|
||||
$format .= ' Create:[%s]';
|
||||
$args[] = $this->getCreateDateTime()->format(\DATE_ATOM);
|
||||
}
|
||||
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
}
|
||||
295
vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php
vendored
Normal file
295
vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Info-ZIP Unix Extra Field (type 1):
|
||||
* ==================================.
|
||||
*
|
||||
* The following is the layout of the old Info-ZIP extra block for
|
||||
* Unix. It has been replaced by the extended-timestamp extra block
|
||||
* (0x5455) and the Unix type 2 extra block (0x7855).
|
||||
* (Last Revision 19970118)
|
||||
*
|
||||
* Local-header version:
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (Unix1) 0x5855 Short tag for this extra block type ("UX")
|
||||
* TSize Short total data size for this block
|
||||
* AcTime Long time of last access (UTC/GMT)
|
||||
* ModTime Long time of last modification (UTC/GMT)
|
||||
* UID Short Unix user ID (optional)
|
||||
* GID Short Unix group ID (optional)
|
||||
*
|
||||
* Central-header version:
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (Unix1) 0x5855 Short tag for this extra block type ("UX")
|
||||
* TSize Short total data size for this block
|
||||
* AcTime Long time of last access (GMT/UTC)
|
||||
* ModTime Long time of last modification (GMT/UTC)
|
||||
*
|
||||
* The file access and modification times are in standard Unix signed-
|
||||
* long format, indicating the number of seconds since 1 January 1970
|
||||
* 00:00:00. The times are relative to Coordinated Universal Time
|
||||
* (UTC), also sometimes referred to as Greenwich Mean Time (GMT). To
|
||||
* convert to local time, the software must know the local timezone
|
||||
* offset from UTC/GMT. The modification time may be used by non-Unix
|
||||
* systems to support inter-timezone freshening and updating of zip
|
||||
* archives.
|
||||
*
|
||||
* The local-header extra block may optionally contain UID and GID
|
||||
* info for the file. The local-header TSize value is the only
|
||||
* indication of this. Note that Unix UIDs and GIDs are usually
|
||||
* specific to a particular machine, and they generally require root
|
||||
* access to restore.
|
||||
*
|
||||
* This extra field type is obsolete, but it has been in use since
|
||||
* mid-1994. Therefore future archiving software should continue to
|
||||
* support it.
|
||||
*/
|
||||
final class OldUnixExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id */
|
||||
public const HEADER_ID = 0x5855;
|
||||
|
||||
/** @var int|null Access timestamp */
|
||||
private ?int $accessTime;
|
||||
|
||||
/** @var int|null Modify timestamp */
|
||||
private ?int $modifyTime;
|
||||
|
||||
/** @var int|null User id */
|
||||
private ?int $uid;
|
||||
|
||||
/** @var int|null Group id */
|
||||
private ?int $gid;
|
||||
|
||||
public function __construct(?int $accessTime, ?int $modifyTime, ?int $uid, ?int $gid)
|
||||
{
|
||||
$this->accessTime = $accessTime;
|
||||
$this->modifyTime = $modifyTime;
|
||||
$this->uid = $uid;
|
||||
$this->gid = $gid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return OldUnixExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
|
||||
$accessTime = $modifyTime = $uid = $gid = null;
|
||||
|
||||
if ($length >= 4) {
|
||||
$accessTime = unpack('V', $buffer)[1];
|
||||
}
|
||||
|
||||
if ($length >= 8) {
|
||||
$modifyTime = unpack('V', substr($buffer, 4, 4))[1];
|
||||
}
|
||||
|
||||
if ($length >= 10) {
|
||||
$uid = unpack('v', substr($buffer, 8, 2))[1];
|
||||
}
|
||||
|
||||
if ($length >= 12) {
|
||||
$gid = unpack('v', substr($buffer, 10, 2))[1];
|
||||
}
|
||||
|
||||
return new self($accessTime, $modifyTime, $uid, $gid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return OldUnixExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
|
||||
$accessTime = $modifyTime = null;
|
||||
|
||||
if ($length >= 4) {
|
||||
$accessTime = unpack('V', $buffer)[1];
|
||||
}
|
||||
|
||||
if ($length >= 8) {
|
||||
$modifyTime = unpack('V', substr($buffer, 4, 4))[1];
|
||||
}
|
||||
|
||||
return new self($accessTime, $modifyTime, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if ($this->accessTime !== null) {
|
||||
$data .= pack('V', $this->accessTime);
|
||||
|
||||
if ($this->modifyTime !== null) {
|
||||
$data .= pack('V', $this->modifyTime);
|
||||
|
||||
if ($this->uid !== null) {
|
||||
$data .= pack('v', $this->uid);
|
||||
|
||||
if ($this->gid !== null) {
|
||||
$data .= pack('v', $this->gid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if ($this->accessTime !== null) {
|
||||
$data .= pack('V', $this->accessTime);
|
||||
|
||||
if ($this->modifyTime !== null) {
|
||||
$data .= pack('V', $this->modifyTime);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getAccessTime(): ?int
|
||||
{
|
||||
return $this->accessTime;
|
||||
}
|
||||
|
||||
public function setAccessTime(?int $accessTime): void
|
||||
{
|
||||
$this->accessTime = $accessTime;
|
||||
}
|
||||
|
||||
public function getAccessDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
try {
|
||||
return $this->accessTime === null ? null
|
||||
: new \DateTimeImmutable('@' . $this->accessTime);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getModifyTime(): ?int
|
||||
{
|
||||
return $this->modifyTime;
|
||||
}
|
||||
|
||||
public function setModifyTime(?int $modifyTime): void
|
||||
{
|
||||
$this->modifyTime = $modifyTime;
|
||||
}
|
||||
|
||||
public function getModifyDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
try {
|
||||
return $this->modifyTime === null ? null
|
||||
: new \DateTimeImmutable('@' . $this->modifyTime);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getUid(): ?int
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function setUid(?int $uid): void
|
||||
{
|
||||
$this->uid = $uid;
|
||||
}
|
||||
|
||||
public function getGid(): ?int
|
||||
{
|
||||
return $this->gid;
|
||||
}
|
||||
|
||||
public function setGid(?int $gid): void
|
||||
{
|
||||
$this->gid = $gid;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$args = [self::HEADER_ID];
|
||||
$format = '0x%04x OldUnix:';
|
||||
|
||||
if (($modifyTime = $this->getModifyDateTime()) !== null) {
|
||||
$format .= ' Modify:[%s]';
|
||||
$args[] = $modifyTime->format(\DATE_ATOM);
|
||||
}
|
||||
|
||||
if (($accessTime = $this->getAccessDateTime()) !== null) {
|
||||
$format .= ' Access:[%s]';
|
||||
$args[] = $accessTime->format(\DATE_ATOM);
|
||||
}
|
||||
|
||||
if ($this->uid !== null) {
|
||||
$format .= ' UID=%d';
|
||||
$args[] = $this->uid;
|
||||
}
|
||||
|
||||
if ($this->gid !== null) {
|
||||
$format .= ' GID=%d';
|
||||
$args[] = $this->gid;
|
||||
}
|
||||
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
}
|
||||
80
vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php
vendored
Normal file
80
vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
/**
|
||||
* Info-ZIP Unicode Comment Extra Field (0x6375):.
|
||||
*
|
||||
* Stores the UTF-8 version of the file comment as stored in the
|
||||
* central directory header. (Last Revision 20070912)
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (UCom) 0x6375 Short tag for this extra block type ("uc")
|
||||
* TSize Short total data size for this block
|
||||
* Version 1 byte version of this extra field, currently 1
|
||||
* ComCRC32 4 bytes Comment Field CRC32 Checksum
|
||||
* UnicodeCom Variable UTF-8 version of the entry comment
|
||||
*
|
||||
* Currently Version is set to the number 1. If there is a need
|
||||
* to change this field, the version will be incremented. Changes
|
||||
* may not be backward compatible so this extra field should not be
|
||||
* used if the version is not recognized.
|
||||
*
|
||||
* The ComCRC32 is the standard zip CRC32 checksum of the File Comment
|
||||
* field in the central directory header. This is used to verify that
|
||||
* the comment field has not changed since the Unicode Comment extra field
|
||||
* was created. This can happen if a utility changes the File Comment
|
||||
* field but does not update the UTF-8 Comment extra field. If the CRC
|
||||
* check fails, this Unicode Comment extra field should be ignored and
|
||||
* the File Comment field in the header should be used instead.
|
||||
*
|
||||
* The UnicodeCom field is the UTF-8 version of the File Comment field
|
||||
* in the header. As UnicodeCom is defined to be UTF-8, no UTF-8 byte
|
||||
* order mark (BOM) is used. The length of this field is determined by
|
||||
* subtracting the size of the previous fields from TSize. If both the
|
||||
* File Name and Comment fields are UTF-8, the new General Purpose Bit
|
||||
* Flag, bit 11 (Language encoding flag (EFS)), can be used to indicate
|
||||
* both the header File Name and Comment fields are UTF-8 and, in this
|
||||
* case, the Unicode Path and Unicode Comment extra fields are not
|
||||
* needed and should not be created. Note that, for backward
|
||||
* compatibility, bit 11 should only be used if the native character set
|
||||
* of the paths and comments being zipped up are already in UTF-8. It is
|
||||
* expected that the same file comment storage method, either general
|
||||
* purpose bit 11 or extra fields, be used in both the Local and Central
|
||||
* Directory Header for a file.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.6.8
|
||||
*/
|
||||
final class UnicodeCommentExtraField extends AbstractUnicodeExtraField
|
||||
{
|
||||
public const HEADER_ID = 0x6375;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x UnicodeComment: "%s"',
|
||||
self::HEADER_ID,
|
||||
$this->getUnicodeValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
81
vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php
vendored
Normal file
81
vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
/**
|
||||
* Info-ZIP Unicode Path Extra Field (0x7075):
|
||||
* ==========================================.
|
||||
*
|
||||
* Stores the UTF-8 version of the file name field as stored in the
|
||||
* local header and central directory header. (Last Revision 20070912)
|
||||
*
|
||||
* Value Size Description
|
||||
* ----- ---- -----------
|
||||
* (UPath) 0x7075 Short tag for this extra block type ("up")
|
||||
* TSize Short total data size for this block
|
||||
* Version 1 byte version of this extra field, currently 1
|
||||
* NameCRC32 4 bytes File Name Field CRC32 Checksum
|
||||
* UnicodeName Variable UTF-8 version of the entry File Name
|
||||
*
|
||||
* Currently Version is set to the number 1. If there is a need
|
||||
* to change this field, the version will be incremented. Changes
|
||||
* may not be backward compatible so this extra field should not be
|
||||
* used if the version is not recognized.
|
||||
*
|
||||
* The NameCRC32 is the standard zip CRC32 checksum of the File Name
|
||||
* field in the header. This is used to verify that the header
|
||||
* File Name field has not changed since the Unicode Path extra field
|
||||
* was created. This can happen if a utility renames the File Name but
|
||||
* does not update the UTF-8 path extra field. If the CRC check fails,
|
||||
* this UTF-8 Path Extra Field should be ignored and the File Name field
|
||||
* in the header should be used instead.
|
||||
*
|
||||
* The UnicodeName is the UTF-8 version of the contents of the File Name
|
||||
* field in the header. As UnicodeName is defined to be UTF-8, no UTF-8
|
||||
* byte order mark (BOM) is used. The length of this field is determined
|
||||
* by subtracting the size of the previous fields from TSize. If both
|
||||
* the File Name and Comment fields are UTF-8, the new General Purpose
|
||||
* Bit Flag, bit 11 (Language encoding flag (EFS)), can be used to
|
||||
* indicate that both the header File Name and Comment fields are UTF-8
|
||||
* and, in this case, the Unicode Path and Unicode Comment extra fields
|
||||
* are not needed and should not be created. Note that, for backward
|
||||
* compatibility, bit 11 should only be used if the native character set
|
||||
* of the paths and comments being zipped up are already in UTF-8. It is
|
||||
* expected that the same file name storage method, either general
|
||||
* purpose bit 11 or extra fields, be used in both the Local and Central
|
||||
* Directory Header for a file.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.6.9
|
||||
*/
|
||||
final class UnicodePathExtraField extends AbstractUnicodeExtraField
|
||||
{
|
||||
public const HEADER_ID = 0x7075;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x UnicodePath: "%s"',
|
||||
self::HEADER_ID,
|
||||
$this->getUnicodeValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
108
vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php
vendored
Normal file
108
vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Simple placeholder for all those extra fields we don't want to deal with.
|
||||
*/
|
||||
final class UnrecognizedExtraField implements ZipExtraField
|
||||
{
|
||||
private int $headerId;
|
||||
|
||||
/** @var string extra field data without Header-ID or length specifier */
|
||||
private string $data;
|
||||
|
||||
public function __construct(int $headerId, ?string $data)
|
||||
{
|
||||
$this->headerId = $headerId;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function setHeaderId(int $headerId): void
|
||||
{
|
||||
$this->headerId = $headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return $this->headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return UnrecognizedExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
throw new RuntimeException('Unsupport parse');
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return UnrecognizedExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
throw new RuntimeException('Unsupport parse');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getData(): string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setData(string $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$args = [$this->headerId, $this->data];
|
||||
$format = '0x%04x Unrecognized Extra Field: "%s"';
|
||||
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
}
|
||||
356
vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php
vendored
Normal file
356
vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Constants\ZipCompressionMethod;
|
||||
use PhpZip\Constants\ZipEncryptionMethod;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethodException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* WinZip AES Extra Field.
|
||||
*
|
||||
* @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers
|
||||
*/
|
||||
final class WinZipAesExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int Header id */
|
||||
public const HEADER_ID = 0x9901;
|
||||
|
||||
/**
|
||||
* @var int Data size (currently 7, but subject to possible increase
|
||||
* in the future)
|
||||
*/
|
||||
public const DATA_SIZE = 7;
|
||||
|
||||
/**
|
||||
* @var int The vendor ID field should always be set to the two ASCII
|
||||
* characters "AE"
|
||||
*/
|
||||
public const VENDOR_ID = 0x4541; // 'A' | ('E' << 8)
|
||||
|
||||
/**
|
||||
* @var int Entries of this type do include the standard ZIP CRC-32 value.
|
||||
* For use with {@see WinZipAesExtraField::setVendorVersion()}.
|
||||
*/
|
||||
public const VERSION_AE1 = 1;
|
||||
|
||||
/**
|
||||
* @var int Entries of this type do not include the standard ZIP CRC-32 value.
|
||||
* For use with {@see WinZipAesExtraField::setVendorVersion().
|
||||
*/
|
||||
public const VERSION_AE2 = 2;
|
||||
|
||||
/** @var int integer mode value indicating AES encryption 128-bit strength */
|
||||
public const KEY_STRENGTH_128BIT = 0x01;
|
||||
|
||||
/** @var int integer mode value indicating AES encryption 192-bit strength */
|
||||
public const KEY_STRENGTH_192BIT = 0x02;
|
||||
|
||||
/** @var int integer mode value indicating AES encryption 256-bit strength */
|
||||
public const KEY_STRENGTH_256BIT = 0x03;
|
||||
|
||||
/** @var int[] */
|
||||
private const ALLOW_VENDOR_VERSIONS = [
|
||||
self::VERSION_AE1,
|
||||
self::VERSION_AE2,
|
||||
];
|
||||
|
||||
/** @var array<int, int> */
|
||||
private const ENCRYPTION_STRENGTHS = [
|
||||
self::KEY_STRENGTH_128BIT => 128,
|
||||
self::KEY_STRENGTH_192BIT => 192,
|
||||
self::KEY_STRENGTH_256BIT => 256,
|
||||
];
|
||||
|
||||
/** @var array<int, int> */
|
||||
private const MAP_KEY_STRENGTH_METHODS = [
|
||||
self::KEY_STRENGTH_128BIT => ZipEncryptionMethod::WINZIP_AES_128,
|
||||
self::KEY_STRENGTH_192BIT => ZipEncryptionMethod::WINZIP_AES_192,
|
||||
self::KEY_STRENGTH_256BIT => ZipEncryptionMethod::WINZIP_AES_256,
|
||||
];
|
||||
|
||||
/** @var int Integer version number specific to the zip vendor */
|
||||
private int $vendorVersion = self::VERSION_AE1;
|
||||
|
||||
/** @var int Integer mode value indicating AES encryption strength */
|
||||
private int $keyStrength = self::KEY_STRENGTH_256BIT;
|
||||
|
||||
/** @var int The actual compression method used to compress the file */
|
||||
private int $compressionMethod;
|
||||
|
||||
/**
|
||||
* @param int $vendorVersion Integer version number specific to the zip vendor
|
||||
* @param int $keyStrength Integer mode value indicating AES encryption strength
|
||||
* @param int $compressionMethod The actual compression method used to compress the file
|
||||
*
|
||||
* @throws ZipUnsupportMethodException
|
||||
*/
|
||||
public function __construct(int $vendorVersion, int $keyStrength, int $compressionMethod)
|
||||
{
|
||||
$this->setVendorVersion($vendorVersion);
|
||||
$this->setKeyStrength($keyStrength);
|
||||
$this->setCompressionMethod($compressionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipUnsupportMethodException
|
||||
*
|
||||
* @return WinZipAesExtraField
|
||||
*/
|
||||
public static function create(ZipEntry $entry): self
|
||||
{
|
||||
$keyStrength = array_search($entry->getEncryptionMethod(), self::MAP_KEY_STRENGTH_METHODS, true);
|
||||
|
||||
if ($keyStrength === false) {
|
||||
throw new InvalidArgumentException('Not support encryption method ' . $entry->getEncryptionMethod());
|
||||
}
|
||||
|
||||
// WinZip 11 will continue to use AE-2, with no CRC, for very small files
|
||||
// of less than 20 bytes. It will also use AE-2 for files compressed in
|
||||
// BZIP2 format, because this format has internal integrity checks
|
||||
// equivalent to a CRC check built in.
|
||||
//
|
||||
// https://www.winzip.com/win/en/aes_info.html
|
||||
$vendorVersion = (
|
||||
$entry->getUncompressedSize() < 20
|
||||
|| $entry->getCompressionMethod() === ZipCompressionMethod::BZIP2
|
||||
)
|
||||
? self::VERSION_AE2
|
||||
: self::VERSION_AE1;
|
||||
|
||||
$field = new self($vendorVersion, $keyStrength, $entry->getCompressionMethod());
|
||||
|
||||
$entry->getLocalExtraFields()->add($field);
|
||||
$entry->getCdExtraFields()->add($field);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ?ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return WinZipAesExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$size = \strlen($buffer);
|
||||
|
||||
if ($size !== self::DATA_SIZE) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'WinZip AES Extra data invalid size: %d. Must be %d',
|
||||
$size,
|
||||
self::DATA_SIZE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[
|
||||
'vendorVersion' => $vendorVersion,
|
||||
'vendorId' => $vendorId,
|
||||
'keyStrength' => $keyStrength,
|
||||
'compressionMethod' => $compressionMethod,
|
||||
] = unpack('vvendorVersion/vvendorId/ckeyStrength/vcompressionMethod', $buffer);
|
||||
|
||||
if ($vendorId !== self::VENDOR_ID) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'Vendor id invalid: %d. Must be %d',
|
||||
$vendorId,
|
||||
self::VENDOR_ID
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new self($vendorVersion, $keyStrength, $compressionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ?ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return WinZipAesExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
return self::unpackLocalFileData($buffer, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
return pack(
|
||||
'vvcv',
|
||||
$this->vendorVersion,
|
||||
self::VENDOR_ID,
|
||||
$this->keyStrength,
|
||||
$this->compressionMethod
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
return $this->packLocalFileData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vendor version.
|
||||
*
|
||||
* @see WinZipAesExtraField::VERSION_AE2
|
||||
* @see WinZipAesExtraField::VERSION_AE1
|
||||
*/
|
||||
public function getVendorVersion(): int
|
||||
{
|
||||
return $this->vendorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vendor version.
|
||||
*
|
||||
* @param int $vendorVersion the vendor version
|
||||
*
|
||||
* @see WinZipAesExtraField::VERSION_AE2
|
||||
* @see WinZipAesExtraField::VERSION_AE1
|
||||
*/
|
||||
public function setVendorVersion(int $vendorVersion): void
|
||||
{
|
||||
if (!\in_array($vendorVersion, self::ALLOW_VENDOR_VERSIONS, true)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Unsupport WinZip AES vendor version: %d',
|
||||
$vendorVersion
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->vendorVersion = $vendorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns vendor id.
|
||||
*/
|
||||
public function getVendorId(): int
|
||||
{
|
||||
return self::VENDOR_ID;
|
||||
}
|
||||
|
||||
public function getKeyStrength(): int
|
||||
{
|
||||
return $this->keyStrength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key strength.
|
||||
*/
|
||||
public function setKeyStrength(int $keyStrength): void
|
||||
{
|
||||
if (!isset(self::ENCRYPTION_STRENGTHS[$keyStrength])) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Key strength %d not support value. Allow values: %s',
|
||||
$keyStrength,
|
||||
implode(', ', array_keys(self::ENCRYPTION_STRENGTHS))
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->keyStrength = $keyStrength;
|
||||
}
|
||||
|
||||
public function getCompressionMethod(): int
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipUnsupportMethodException
|
||||
*/
|
||||
public function setCompressionMethod(int $compressionMethod): void
|
||||
{
|
||||
ZipCompressionMethod::checkSupport($compressionMethod);
|
||||
$this->compressionMethod = $compressionMethod;
|
||||
}
|
||||
|
||||
public function getEncryptionStrength(): int
|
||||
{
|
||||
return self::ENCRYPTION_STRENGTHS[$this->keyStrength];
|
||||
}
|
||||
|
||||
public function getEncryptionMethod(): int
|
||||
{
|
||||
$keyStrength = $this->getKeyStrength();
|
||||
|
||||
if (!isset(self::MAP_KEY_STRENGTH_METHODS[$keyStrength])) {
|
||||
throw new InvalidArgumentException('Invalid encryption method');
|
||||
}
|
||||
|
||||
return self::MAP_KEY_STRENGTH_METHODS[$keyStrength];
|
||||
}
|
||||
|
||||
public function isV1(): bool
|
||||
{
|
||||
return $this->vendorVersion === self::VERSION_AE1;
|
||||
}
|
||||
|
||||
public function isV2(): bool
|
||||
{
|
||||
return $this->vendorVersion === self::VERSION_AE2;
|
||||
}
|
||||
|
||||
public function getSaltSize(): int
|
||||
{
|
||||
return (int) ($this->getEncryptionStrength() / 8 / 2);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'0x%04x WINZIP AES: VendorVersion=%d KeyStrength=0x%02x CompressionMethod=%s',
|
||||
__CLASS__,
|
||||
$this->vendorVersion,
|
||||
$this->keyStrength,
|
||||
$this->compressionMethod
|
||||
);
|
||||
}
|
||||
}
|
||||
277
vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php
vendored
Normal file
277
vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra\Fields;
|
||||
|
||||
use PhpZip\Constants\ZipConstants;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\ZipExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* ZIP64 Extra Field.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*/
|
||||
final class Zip64ExtraField implements ZipExtraField
|
||||
{
|
||||
/** @var int The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
public const HEADER_ID = 0x0001;
|
||||
|
||||
private ?int $uncompressedSize;
|
||||
|
||||
private ?int $compressedSize;
|
||||
|
||||
private ?int $localHeaderOffset;
|
||||
|
||||
private ?int $diskStart;
|
||||
|
||||
public function __construct(
|
||||
?int $uncompressedSize = null,
|
||||
?int $compressedSize = null,
|
||||
?int $localHeaderOffset = null,
|
||||
?int $diskStart = null
|
||||
) {
|
||||
$this->uncompressedSize = $uncompressedSize;
|
||||
$this->compressedSize = $compressedSize;
|
||||
$this->localHeaderOffset = $localHeaderOffset;
|
||||
$this->diskStart = $diskStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int
|
||||
{
|
||||
return self::HEADER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ?ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException on error
|
||||
*
|
||||
* @return Zip64ExtraField
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
$length = \strlen($buffer);
|
||||
|
||||
if ($length === 0) {
|
||||
// no local file data at all, may happen if an archive
|
||||
// only holds a ZIP64 extended information extra field
|
||||
// inside the central directory but not inside the local
|
||||
// file header
|
||||
return new self();
|
||||
}
|
||||
|
||||
if ($length < 16) {
|
||||
throw new ZipException(
|
||||
'Zip64 extended information must contain both size values in the local file header.'
|
||||
);
|
||||
}
|
||||
|
||||
[
|
||||
'uncompressedSize' => $uncompressedSize,
|
||||
'compressedSize' => $compressedSize,
|
||||
] = unpack('PuncompressedSize/PcompressedSize', substr($buffer, 0, 16));
|
||||
|
||||
return new self($uncompressedSize, $compressedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ?ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return Zip64ExtraField
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
|
||||
{
|
||||
if ($entry === null) {
|
||||
throw new RuntimeException('zipEntry is null');
|
||||
}
|
||||
|
||||
$length = \strlen($buffer);
|
||||
$remaining = $length;
|
||||
|
||||
$uncompressedSize = null;
|
||||
$compressedSize = null;
|
||||
$localHeaderOffset = null;
|
||||
$diskStart = null;
|
||||
|
||||
if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) {
|
||||
if ($remaining < 8) {
|
||||
throw new ZipException('ZIP64 extension corrupt (no uncompressed size).');
|
||||
}
|
||||
$uncompressedSize = unpack('P', substr($buffer, $length - $remaining, 8))[1];
|
||||
$remaining -= 8;
|
||||
}
|
||||
|
||||
if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) {
|
||||
if ($remaining < 8) {
|
||||
throw new ZipException('ZIP64 extension corrupt (no compressed size).');
|
||||
}
|
||||
$compressedSize = unpack('P', substr($buffer, $length - $remaining, 8))[1];
|
||||
$remaining -= 8;
|
||||
}
|
||||
|
||||
if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) {
|
||||
if ($remaining < 8) {
|
||||
throw new ZipException('ZIP64 extension corrupt (no relative local header offset).');
|
||||
}
|
||||
$localHeaderOffset = unpack('P', substr($buffer, $length - $remaining, 8))[1];
|
||||
$remaining -= 8;
|
||||
}
|
||||
|
||||
if ($remaining === 4) {
|
||||
$diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1];
|
||||
}
|
||||
|
||||
return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string
|
||||
{
|
||||
if ($this->uncompressedSize !== null || $this->compressedSize !== null) {
|
||||
if ($this->uncompressedSize === null || $this->compressedSize === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Zip64 extended information must contain both size values in the local file header.'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->packSizes();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function packSizes(): string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if ($this->uncompressedSize !== null) {
|
||||
$data .= pack('P', $this->uncompressedSize);
|
||||
}
|
||||
|
||||
if ($this->compressedSize !== null) {
|
||||
$data .= pack('P', $this->compressedSize);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string
|
||||
{
|
||||
$data = $this->packSizes();
|
||||
|
||||
if ($this->localHeaderOffset !== null) {
|
||||
$data .= pack('P', $this->localHeaderOffset);
|
||||
}
|
||||
|
||||
if ($this->diskStart !== null) {
|
||||
$data .= pack('V', $this->diskStart);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getUncompressedSize(): ?int
|
||||
{
|
||||
return $this->uncompressedSize;
|
||||
}
|
||||
|
||||
public function setUncompressedSize(?int $uncompressedSize): void
|
||||
{
|
||||
$this->uncompressedSize = $uncompressedSize;
|
||||
}
|
||||
|
||||
public function getCompressedSize(): ?int
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
public function setCompressedSize(?int $compressedSize): void
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
}
|
||||
|
||||
public function getLocalHeaderOffset(): ?int
|
||||
{
|
||||
return $this->localHeaderOffset;
|
||||
}
|
||||
|
||||
public function setLocalHeaderOffset(?int $localHeaderOffset): void
|
||||
{
|
||||
$this->localHeaderOffset = $localHeaderOffset;
|
||||
}
|
||||
|
||||
public function getDiskStart(): ?int
|
||||
{
|
||||
return $this->diskStart;
|
||||
}
|
||||
|
||||
public function setDiskStart(?int $diskStart): void
|
||||
{
|
||||
$this->diskStart = $diskStart;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$args = [self::HEADER_ID];
|
||||
$format = '0x%04x ZIP64: ';
|
||||
$formats = [];
|
||||
|
||||
if ($this->uncompressedSize !== null) {
|
||||
$formats[] = 'SIZE=%d';
|
||||
$args[] = $this->uncompressedSize;
|
||||
}
|
||||
|
||||
if ($this->compressedSize !== null) {
|
||||
$formats[] = 'COMP_SIZE=%d';
|
||||
$args[] = $this->compressedSize;
|
||||
}
|
||||
|
||||
if ($this->localHeaderOffset !== null) {
|
||||
$formats[] = 'OFFSET=%d';
|
||||
$args[] = $this->localHeaderOffset;
|
||||
}
|
||||
|
||||
if ($this->diskStart !== null) {
|
||||
$formats[] = 'DISK_START=%d';
|
||||
$args[] = $this->diskStart;
|
||||
}
|
||||
$format .= implode(' ', $formats);
|
||||
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
}
|
||||
103
vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php
vendored
Normal file
103
vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Model\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Model\Extra\Fields\AsiExtraField;
|
||||
use PhpZip\Model\Extra\Fields\ExtendedTimestampExtraField;
|
||||
use PhpZip\Model\Extra\Fields\JarMarkerExtraField;
|
||||
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
|
||||
use PhpZip\Model\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Model\Extra\Fields\OldUnixExtraField;
|
||||
use PhpZip\Model\Extra\Fields\UnicodeCommentExtraField;
|
||||
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
|
||||
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
|
||||
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
|
||||
|
||||
/**
|
||||
* Class ZipExtraManager.
|
||||
*/
|
||||
final class ZipExtraDriver
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
* @psalm-var array<int, class-string<ZipExtraField>>
|
||||
*/
|
||||
private static array $implementations = [
|
||||
ApkAlignmentExtraField::HEADER_ID => ApkAlignmentExtraField::class,
|
||||
AsiExtraField::HEADER_ID => AsiExtraField::class,
|
||||
ExtendedTimestampExtraField::HEADER_ID => ExtendedTimestampExtraField::class,
|
||||
JarMarkerExtraField::HEADER_ID => JarMarkerExtraField::class,
|
||||
NewUnixExtraField::HEADER_ID => NewUnixExtraField::class,
|
||||
NtfsExtraField::HEADER_ID => NtfsExtraField::class,
|
||||
OldUnixExtraField::HEADER_ID => OldUnixExtraField::class,
|
||||
UnicodeCommentExtraField::HEADER_ID => UnicodeCommentExtraField::class,
|
||||
UnicodePathExtraField::HEADER_ID => UnicodePathExtraField::class,
|
||||
WinZipAesExtraField::HEADER_ID => WinZipAesExtraField::class,
|
||||
Zip64ExtraField::HEADER_ID => Zip64ExtraField::class,
|
||||
];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipExtraField $extraField ZipExtraField object or class name
|
||||
*/
|
||||
public static function register($extraField): void
|
||||
{
|
||||
if (!is_a($extraField, ZipExtraField::class, true)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'$extraField "%s" is not implements interface %s',
|
||||
(string) $extraField,
|
||||
ZipExtraField::class
|
||||
)
|
||||
);
|
||||
}
|
||||
self::$implementations[\call_user_func([$extraField, 'getHeaderId'])] = $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|ZipExtraField $extraType ZipExtraField object or class name or extra header id
|
||||
*/
|
||||
public static function unregister($extraType): bool
|
||||
{
|
||||
$headerId = null;
|
||||
|
||||
if (\is_int($extraType)) {
|
||||
$headerId = $extraType;
|
||||
} elseif (is_a($extraType, ZipExtraField::class, true)) {
|
||||
$headerId = \call_user_func([$extraType, 'getHeaderId']);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset(self::$implementations[$headerId])) {
|
||||
unset(self::$implementations[$headerId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getClassNameOrNull(int $headerId): ?string
|
||||
{
|
||||
if ($headerId < 0 || $headerId > 0xFFFF) {
|
||||
throw new \InvalidArgumentException('$headerId out of range: ' . $headerId);
|
||||
}
|
||||
|
||||
return self::$implementations[$headerId] ?? null;
|
||||
}
|
||||
}
|
||||
67
vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php
vendored
Normal file
67
vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model\Extra;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Extra Field in a Local or Central Header of a ZIP archive.
|
||||
* It defines the common properties of all Extra Fields and how to
|
||||
* serialize/unserialize them to/from byte arrays.
|
||||
*/
|
||||
interface ZipExtraField
|
||||
{
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*/
|
||||
public function getHeaderId(): int;
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in local file data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self;
|
||||
|
||||
/**
|
||||
* Populate data from this array as if it was in central directory data.
|
||||
*
|
||||
* @param string $buffer the buffer to read data from
|
||||
* @param ZipEntry|null $entry optional zip entry
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self;
|
||||
|
||||
/**
|
||||
* The actual data to put into local file data - without Header-ID
|
||||
* or length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packLocalFileData(): string;
|
||||
|
||||
/**
|
||||
* The actual data to put into central directory - without Header-ID or
|
||||
* length specifier.
|
||||
*
|
||||
* @return string the data
|
||||
*/
|
||||
public function packCentralDirData(): string;
|
||||
|
||||
public function __toString(): string;
|
||||
}
|
||||
73
vendor/nelexa/zip/src/Model/ImmutableZipContainer.php
vendored
Normal file
73
vendor/nelexa/zip/src/Model/ImmutableZipContainer.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
class ImmutableZipContainer implements \Countable
|
||||
{
|
||||
/** @var ZipEntry[] */
|
||||
protected array $entries;
|
||||
|
||||
/** @var string|null Archive comment */
|
||||
protected ?string $archiveComment;
|
||||
|
||||
/**
|
||||
* @param ZipEntry[] $entries
|
||||
* @param ?string $archiveComment
|
||||
*/
|
||||
public function __construct(array $entries, ?string $archiveComment = null)
|
||||
{
|
||||
$this->entries = $entries;
|
||||
$this->archiveComment = $archiveComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
public function &getEntries(): array
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
public function getArchiveComment(): ?string
|
||||
{
|
||||
return $this->archiveComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object.
|
||||
*
|
||||
* @see https://php.net/manual/en/countable.count.php
|
||||
*
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
|
||||
* Any properties that are references to other variables, will remain references.
|
||||
* Once the cloning is complete, if a __clone() method is defined,
|
||||
* then the newly created object's __clone() method will be called, to allow any necessary properties that need to
|
||||
* be changed. NOT CALLABLE DIRECTLY.
|
||||
*
|
||||
* @see https://php.net/manual/en/language.oop5.cloning.php
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->entries as $key => $value) {
|
||||
$this->entries[$key] = clone $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
335
vendor/nelexa/zip/src/Model/ZipContainer.php
vendored
Normal file
335
vendor/nelexa/zip/src/Model/ZipContainer.php
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Constants\ZipEncryptionMethod;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Zip Container.
|
||||
*/
|
||||
class ZipContainer extends ImmutableZipContainer
|
||||
{
|
||||
/**
|
||||
* @var ImmutableZipContainer|null The source container contains zip entries from
|
||||
* an open zip archive. The source container makes
|
||||
* it possible to undo changes in the archive.
|
||||
* When cloning, this container is not cloned.
|
||||
*/
|
||||
private ?ImmutableZipContainer $sourceContainer;
|
||||
|
||||
public function __construct(?ImmutableZipContainer $sourceContainer = null)
|
||||
{
|
||||
$entries = [];
|
||||
$archiveComment = null;
|
||||
|
||||
if ($sourceContainer !== null) {
|
||||
foreach ($sourceContainer->getEntries() as $entryName => $entry) {
|
||||
$entries[$entryName] = clone $entry;
|
||||
}
|
||||
$archiveComment = $sourceContainer->getArchiveComment();
|
||||
}
|
||||
parent::__construct($entries, $archiveComment);
|
||||
$this->sourceContainer = $sourceContainer;
|
||||
}
|
||||
|
||||
public function getSourceContainer(): ?ImmutableZipContainer
|
||||
{
|
||||
return $this->sourceContainer;
|
||||
}
|
||||
|
||||
public function addEntry(ZipEntry $entry): void
|
||||
{
|
||||
$this->entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
*/
|
||||
public function deleteEntry($entry): bool
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
|
||||
|
||||
if (isset($this->entries[$entry])) {
|
||||
unset($this->entries[$entry]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $old
|
||||
* @param string|ZipEntry $new
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry New zip entry
|
||||
*/
|
||||
public function renameEntry($old, $new): ZipEntry
|
||||
{
|
||||
$old = $old instanceof ZipEntry ? $old->getName() : (string) $old;
|
||||
$new = $new instanceof ZipEntry ? $new->getName() : (string) $new;
|
||||
|
||||
if (isset($this->entries[$new])) {
|
||||
throw new InvalidArgumentException('New entry name ' . $new . ' is exists.');
|
||||
}
|
||||
|
||||
$entry = $this->getEntry($old);
|
||||
$newEntry = $entry->rename($new);
|
||||
|
||||
$this->deleteEntry($entry);
|
||||
$this->addEntry($newEntry);
|
||||
|
||||
return $newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function getEntry($entryName): ZipEntry
|
||||
{
|
||||
$entry = $this->getEntryOrNull($entryName);
|
||||
|
||||
if ($entry !== null) {
|
||||
return $entry;
|
||||
}
|
||||
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*/
|
||||
public function getEntryOrNull($entryName): ?ZipEntry
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
|
||||
|
||||
return $this->entries[$entryName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*/
|
||||
public function hasEntry($entryName): bool
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
|
||||
|
||||
return isset($this->entries[$entryName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*/
|
||||
public function deleteAll(): void
|
||||
{
|
||||
$this->entries = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entries by regex pattern.
|
||||
*
|
||||
* @param string $regexPattern Regex pattern
|
||||
*
|
||||
* @return ZipEntry[] Deleted entries
|
||||
*/
|
||||
public function deleteByRegex(string $regexPattern): array
|
||||
{
|
||||
if (empty($regexPattern)) {
|
||||
throw new InvalidArgumentException('The regex pattern is not specified');
|
||||
}
|
||||
|
||||
/** @var ZipEntry[] $found */
|
||||
$found = [];
|
||||
|
||||
foreach ($this->entries as $entryName => $entry) {
|
||||
if (preg_match($regexPattern, $entryName)) {
|
||||
$found[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($found as $entry) {
|
||||
$this->deleteEntry($entry);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive.
|
||||
*/
|
||||
public function unchangeAll(): void
|
||||
{
|
||||
$this->entries = [];
|
||||
|
||||
if ($this->sourceContainer !== null) {
|
||||
foreach ($this->sourceContainer->getEntries() as $entry) {
|
||||
$this->entries[$entry->getName()] = clone $entry;
|
||||
}
|
||||
}
|
||||
$this->unchangeArchiveComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo change archive comment.
|
||||
*/
|
||||
public function unchangeArchiveComment(): void
|
||||
{
|
||||
$this->archiveComment = null;
|
||||
|
||||
if ($this->sourceContainer !== null) {
|
||||
$this->archiveComment = $this->sourceContainer->archiveComment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
*/
|
||||
public function unchangeEntry($entry): bool
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
|
||||
|
||||
if (
|
||||
$this->sourceContainer !== null
|
||||
&& isset($this->entries[$entry], $this->sourceContainer->entries[$entry])
|
||||
) {
|
||||
$this->entries[$entry] = clone $this->sourceContainer->entries[$entry];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries sort by name.
|
||||
*
|
||||
* Example:
|
||||
* ```php
|
||||
* $zipContainer->sortByName(static function (string $nameA, ?string $nameB): int {
|
||||
* return strcmp($nameA, $nameB);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public function sortByName(callable $cmp): void
|
||||
{
|
||||
uksort($this->entries, $cmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries sort by entry.
|
||||
*
|
||||
* Example:
|
||||
* ```php
|
||||
* $zipContainer->sortByEntry(static function (ZipEntry $a, ZipEntry $b): int {
|
||||
* return strcmp($a->getName(), $b->getName());
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public function sortByEntry(callable $cmp): void
|
||||
{
|
||||
uasort($this->entries, $cmp);
|
||||
}
|
||||
|
||||
public function setArchiveComment(?string $archiveComment): void
|
||||
{
|
||||
if ($archiveComment !== null && $archiveComment !== '') {
|
||||
$length = \strlen($archiveComment);
|
||||
|
||||
if ($length > 0xFFFF) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
$this->archiveComment = $archiveComment;
|
||||
}
|
||||
|
||||
public function matcher(): ZipEntryMatcher
|
||||
{
|
||||
return new ZipEntryMatcher($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a password for extracting files.
|
||||
*
|
||||
* @param ?string $password
|
||||
*/
|
||||
public function setReadPassword(?string $password): void
|
||||
{
|
||||
if ($this->sourceContainer !== null) {
|
||||
foreach ($this->sourceContainer->entries as $entry) {
|
||||
if ($entry->isEncrypted()) {
|
||||
$entry->setPassword($password);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setReadPasswordEntry(string $entryName, ?string $password): void
|
||||
{
|
||||
if (!isset($this->sourceContainer->entries[$entryName])) {
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
|
||||
if ($this->sourceContainer->entries[$entryName]->isEncrypted()) {
|
||||
$this->sourceContainer->entries[$entryName]->setPassword($password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $writePassword
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function setWritePassword(?string $writePassword): void
|
||||
{
|
||||
$this->matcher()->all()->setPassword($writePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove password.
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function removePassword(): void
|
||||
{
|
||||
$this->matcher()->all()->setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function removePasswordEntry($entryName): void
|
||||
{
|
||||
$this->matcher()->add($entryName)->setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function setEncryptionMethod(int $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256): void
|
||||
{
|
||||
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
}
|
||||
34
vendor/nelexa/zip/src/Model/ZipData.php
vendored
Normal file
34
vendor/nelexa/zip/src/Model/ZipData.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
interface ZipData
|
||||
{
|
||||
/**
|
||||
* @return string returns data as string
|
||||
*/
|
||||
public function getDataAsString(): string;
|
||||
|
||||
/**
|
||||
* @return resource returns stream data
|
||||
*/
|
||||
public function getDataAsStream();
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function copyDataToStream($outStream);
|
||||
}
|
||||
1172
vendor/nelexa/zip/src/Model/ZipEntry.php
vendored
Normal file
1172
vendor/nelexa/zip/src/Model/ZipEntry.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
194
vendor/nelexa/zip/src/Model/ZipEntryMatcher.php
vendored
Normal file
194
vendor/nelexa/zip/src/Model/ZipEntryMatcher.php
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
|
||||
class ZipEntryMatcher implements \Countable
|
||||
{
|
||||
protected ZipContainer $zipContainer;
|
||||
|
||||
protected array $matches = [];
|
||||
|
||||
public function __construct(ZipContainer $zipContainer)
|
||||
{
|
||||
$this->zipContainer = $zipContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry|string[]|ZipEntry[] $entries
|
||||
*
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function add($entries): self
|
||||
{
|
||||
$entries = (array) $entries;
|
||||
$entries = array_map(
|
||||
static fn ($entry) => $entry instanceof ZipEntry ? $entry->getName() : (string) $entry,
|
||||
$entries
|
||||
);
|
||||
$this->matches = array_values(
|
||||
array_map(
|
||||
'strval',
|
||||
array_unique(
|
||||
array_merge(
|
||||
$this->matches,
|
||||
array_keys(
|
||||
array_intersect_key(
|
||||
$this->zipContainer->getEntries(),
|
||||
array_flip($entries)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function match(string $regexp): self
|
||||
{
|
||||
array_walk(
|
||||
$this->zipContainer->getEntries(),
|
||||
function (ZipEntry $entry, ?string $entryName) use ($regexp): void {
|
||||
if (preg_match($regexp, $entryName)) {
|
||||
$this->matches[] = $entryName;
|
||||
}
|
||||
}
|
||||
);
|
||||
$this->matches = array_unique($this->matches);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function all(): self
|
||||
{
|
||||
$this->matches = array_map(
|
||||
'strval',
|
||||
array_keys($this->zipContainer->getEntries())
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callable function for all select entries.
|
||||
*
|
||||
* Callable function signature:
|
||||
* function(string $entryName){}
|
||||
*/
|
||||
public function invoke(callable $callable): void
|
||||
{
|
||||
if (!empty($this->matches)) {
|
||||
array_walk(
|
||||
$this->matches,
|
||||
/** @param string $entryName */
|
||||
static function (string $entryName) use ($callable): void {
|
||||
$callable($entryName);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMatches(): array
|
||||
{
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
array_walk(
|
||||
$this->matches,
|
||||
/** @param string $entryName */
|
||||
function (string $entryName): void {
|
||||
$this->zipContainer->deleteEntry($entryName);
|
||||
}
|
||||
);
|
||||
$this->matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $password
|
||||
* @param ?int $encryptionMethod
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function setPassword(?string $password, ?int $encryptionMethod = null): void
|
||||
{
|
||||
array_walk(
|
||||
$this->matches,
|
||||
/** @param string $entryName */
|
||||
function (string $entryName) use ($password, $encryptionMethod): void {
|
||||
$entry = $this->zipContainer->getEntry($entryName);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry->setPassword($password, $encryptionMethod);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function setEncryptionMethod(int $encryptionMethod): void
|
||||
{
|
||||
array_walk(
|
||||
$this->matches,
|
||||
/** @param string $entryName */
|
||||
function (string $entryName) use ($encryptionMethod): void {
|
||||
$entry = $this->zipContainer->getEntry($entryName);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function disableEncryption(): void
|
||||
{
|
||||
array_walk(
|
||||
$this->matches,
|
||||
function (string $entryName): void {
|
||||
$entry = $this->zipContainer->getEntry($entryName);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry->disableEncryption();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object.
|
||||
*
|
||||
* @see http://php.net/manual/en/countable.count.php
|
||||
*
|
||||
* @return int the custom count as an integer
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->matches);
|
||||
}
|
||||
}
|
||||
62
vendor/nelexa/zip/src/Util/CryptoUtil.php
vendored
Normal file
62
vendor/nelexa/zip/src/Util/CryptoUtil.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Crypto Utils.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CryptoUtil
|
||||
{
|
||||
/**
|
||||
* Decrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Encrypted data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
*
|
||||
* @return string Raw data
|
||||
*/
|
||||
public static function decryptAesCtr(string $data, ?string $key, ?string $iv): string
|
||||
{
|
||||
if (\extension_loaded('openssl')) {
|
||||
$numBits = \strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Openssl extension not loaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Raw data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
*
|
||||
* @return string Encrypted data
|
||||
*/
|
||||
public static function encryptAesCtr(string $data, ?string $key, ?string $iv): string
|
||||
{
|
||||
if (\extension_loaded('openssl')) {
|
||||
$numBits = \strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Openssl extension not loaded');
|
||||
}
|
||||
}
|
||||
109
vendor/nelexa/zip/src/Util/DateTimeConverter.php
vendored
Normal file
109
vendor/nelexa/zip/src/Util/DateTimeConverter.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* Convert unix timestamp values to DOS date/time values and vice versa.
|
||||
*
|
||||
* The DOS date/time format is a bitmask:
|
||||
*
|
||||
* 24 16 8 0
|
||||
* +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
* |Y|Y|Y|Y|Y|Y|Y|M| |M|M|M|D|D|D|D|D| |h|h|h|h|h|m|m|m| |m|m|m|s|s|s|s|s|
|
||||
* +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
* \___________/\________/\_________/ \________/\____________/\_________/
|
||||
* year month day hour minute second
|
||||
*
|
||||
* The year is stored as an offset from 1980.
|
||||
* Seconds are stored in two-second increments.
|
||||
* (So if the "second" value is 15, it actually represents 30 seconds.)
|
||||
*
|
||||
* @see https://docs.microsoft.com/ru-ru/windows/win32/api/winbase/nf-winbase-filetimetodosdatetime?redirectedfrom=MSDN
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DateTimeConverter
|
||||
{
|
||||
/**
|
||||
* Smallest supported DOS date/time value in a ZIP file,
|
||||
* which is January 1st, 1980 AD 00:00:00 local time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const MIN_DOS_TIME = (1 << 21) | (1 << 16);
|
||||
|
||||
/**
|
||||
* Largest supported DOS date/time value in a ZIP file,
|
||||
* which is December 31st, 2107 AD 23:59:58 local time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const MAX_DOS_TIME = ((2107 - 1980) << 25) | (12 << 21) | (31 << 16) | (23 << 11) | (59 << 5) | (58 >> 1);
|
||||
|
||||
/**
|
||||
* Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
|
||||
*
|
||||
* @param int $dosTime Dos date/time
|
||||
*
|
||||
* @return int Unix timestamp
|
||||
*/
|
||||
public static function msDosToUnix(int $dosTime): int
|
||||
{
|
||||
if ($dosTime <= self::MIN_DOS_TIME) {
|
||||
$dosTime = 0;
|
||||
} elseif ($dosTime > self::MAX_DOS_TIME) {
|
||||
$dosTime = self::MAX_DOS_TIME;
|
||||
}
|
||||
// date_default_timezone_set('UTC');
|
||||
return mktime(
|
||||
(($dosTime >> 11) & 0x1F), // hours
|
||||
(($dosTime >> 5) & 0x3F), // minutes
|
||||
(($dosTime << 1) & 0x3E), // seconds
|
||||
(($dosTime >> 21) & 0x0F), // month
|
||||
(($dosTime >> 16) & 0x1F), // day
|
||||
((($dosTime >> 25) & 0x7F) + 1980) // year
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UNIX timestamp value to a DOS date/time value.
|
||||
*
|
||||
* @param int $unixTimestamp the number of seconds since midnight, January 1st,
|
||||
* 1970 AD UTC
|
||||
*
|
||||
* @return int a DOS date/time value reflecting the local time zone and
|
||||
* rounded down to even seconds
|
||||
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME
|
||||
*/
|
||||
public static function unixToMsDos(int $unixTimestamp): int
|
||||
{
|
||||
if ($unixTimestamp < 0) {
|
||||
throw new \InvalidArgumentException('Negative unix timestamp: ' . $unixTimestamp);
|
||||
}
|
||||
|
||||
$date = getdate($unixTimestamp);
|
||||
$dosTime = (
|
||||
(($date['year'] - 1980) << 25)
|
||||
| ($date['mon'] << 21)
|
||||
| ($date['mday'] << 16)
|
||||
| ($date['hours'] << 11)
|
||||
| ($date['minutes'] << 5)
|
||||
| ($date['seconds'] >> 1)
|
||||
);
|
||||
|
||||
if ($dosTime <= self::MIN_DOS_TIME) {
|
||||
$dosTime = 0;
|
||||
}
|
||||
|
||||
return $dosTime;
|
||||
}
|
||||
}
|
||||
106
vendor/nelexa/zip/src/Util/FileAttribUtil.php
vendored
Normal file
106
vendor/nelexa/zip/src/Util/FileAttribUtil.php
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Constants\DosAttrs;
|
||||
use PhpZip\Constants\UnixStat;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FileAttribUtil implements DosAttrs, UnixStat
|
||||
{
|
||||
/**
|
||||
* Get DOS mode.
|
||||
*/
|
||||
public static function getDosMode(int $xattr): string
|
||||
{
|
||||
$mode = (($xattr & self::DOS_DIRECTORY) === self::DOS_DIRECTORY) ? 'd' : '-';
|
||||
$mode .= (($xattr & self::DOS_ARCHIVE) === self::DOS_ARCHIVE) ? 'a' : '-';
|
||||
$mode .= (($xattr & self::DOS_READ_ONLY) === self::DOS_READ_ONLY) ? 'r' : '-';
|
||||
$mode .= (($xattr & self::DOS_HIDDEN) === self::DOS_HIDDEN) ? 'h' : '-';
|
||||
$mode .= (($xattr & self::DOS_SYSTEM) === self::DOS_SYSTEM) ? 's' : '-';
|
||||
$mode .= (($xattr & self::DOS_LABEL) === self::DOS_LABEL) ? 'l' : '-';
|
||||
|
||||
return $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
public static function getUnixMode(int $permission): string
|
||||
{
|
||||
$mode = '';
|
||||
switch ($permission & self::UNX_IFMT) {
|
||||
case self::UNX_IFDIR:
|
||||
$mode .= 'd';
|
||||
break;
|
||||
|
||||
case self::UNX_IFREG:
|
||||
$mode .= '-';
|
||||
break;
|
||||
|
||||
case self::UNX_IFLNK:
|
||||
$mode .= 'l';
|
||||
break;
|
||||
|
||||
case self::UNX_IFBLK:
|
||||
$mode .= 'b';
|
||||
break;
|
||||
|
||||
case self::UNX_IFCHR:
|
||||
$mode .= 'c';
|
||||
break;
|
||||
|
||||
case self::UNX_IFIFO:
|
||||
$mode .= 'p';
|
||||
break;
|
||||
|
||||
case self::UNX_IFSOCK:
|
||||
$mode .= 's';
|
||||
break;
|
||||
|
||||
default:
|
||||
$mode .= '?';
|
||||
break;
|
||||
}
|
||||
$mode .= ($permission & self::UNX_IRUSR) ? 'r' : '-';
|
||||
$mode .= ($permission & self::UNX_IWUSR) ? 'w' : '-';
|
||||
|
||||
if ($permission & self::UNX_IXUSR) {
|
||||
$mode .= ($permission & self::UNX_ISUID) ? 's' : 'x';
|
||||
} else {
|
||||
$mode .= ($permission & self::UNX_ISUID) ? 'S' : '-'; // S==undefined
|
||||
}
|
||||
$mode .= ($permission & self::UNX_IRGRP) ? 'r' : '-';
|
||||
$mode .= ($permission & self::UNX_IWGRP) ? 'w' : '-';
|
||||
|
||||
if ($permission & self::UNX_IXGRP) {
|
||||
$mode .= ($permission & self::UNX_ISGID) ? 's' : 'x';
|
||||
} // == self::UNX_ENFMT
|
||||
else {
|
||||
$mode .= ($permission & self::UNX_ISGID) ? 'S' : '-';
|
||||
} // SunOS 4.1.x
|
||||
|
||||
$mode .= ($permission & self::UNX_IROTH) ? 'r' : '-';
|
||||
$mode .= ($permission & self::UNX_IWOTH) ? 'w' : '-';
|
||||
|
||||
if ($permission & self::UNX_IXOTH) {
|
||||
$mode .= ($permission & self::UNX_ISVTX) ? 't' : 'x';
|
||||
} // "sticky bit"
|
||||
else {
|
||||
$mode .= ($permission & self::UNX_ISVTX) ? 'T' : '-';
|
||||
} // T==undefined
|
||||
|
||||
return $mode;
|
||||
}
|
||||
}
|
||||
399
vendor/nelexa/zip/src/Util/FilesUtil.php
vendored
Normal file
399
vendor/nelexa/zip/src/Util/FilesUtil.php
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Files util.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FilesUtil
|
||||
{
|
||||
/**
|
||||
* Is empty directory.
|
||||
*
|
||||
* @param string $dir Directory
|
||||
*/
|
||||
public static function isEmptyDir(string $dir): bool
|
||||
{
|
||||
if (!is_readable($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \count(scandir($dir)) === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove recursive directory.
|
||||
*
|
||||
* @param string $dir directory path
|
||||
*/
|
||||
public static function removeDir(string $dir): void
|
||||
{
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
/** @var \SplFileInfo $fileInfo */
|
||||
foreach ($files as $fileInfo) {
|
||||
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$function($fileInfo->getPathname());
|
||||
}
|
||||
@rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert glob pattern to regex pattern.
|
||||
*/
|
||||
public static function convertGlobToRegEx(string $globPattern): string
|
||||
{
|
||||
// Remove beginning and ending * globs because they're useless
|
||||
$globPattern = trim($globPattern, '*');
|
||||
$escaping = false;
|
||||
$inCurrent = 0;
|
||||
$chars = str_split($globPattern);
|
||||
$regexPattern = '';
|
||||
|
||||
foreach ($chars as $currentChar) {
|
||||
switch ($currentChar) {
|
||||
case '*':
|
||||
$regexPattern .= ($escaping ? '\\*' : '.*');
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
$regexPattern .= ($escaping ? '\\?' : '.');
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '.':
|
||||
case '(':
|
||||
case ')':
|
||||
case '+':
|
||||
case '|':
|
||||
case '^':
|
||||
case '$':
|
||||
case '@':
|
||||
case '%':
|
||||
$regexPattern .= '\\' . $currentChar;
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
if ($escaping) {
|
||||
$regexPattern .= '\\\\';
|
||||
$escaping = false;
|
||||
} else {
|
||||
$escaping = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case '{':
|
||||
if ($escaping) {
|
||||
$regexPattern .= '\\{';
|
||||
} else {
|
||||
$regexPattern = '(';
|
||||
$inCurrent++;
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= ')';
|
||||
$inCurrent--;
|
||||
} elseif ($escaping) {
|
||||
$regexPattern = '\\}';
|
||||
} else {
|
||||
$regexPattern = '}';
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= '|';
|
||||
} elseif ($escaping) {
|
||||
$regexPattern .= '\\,';
|
||||
} else {
|
||||
$regexPattern = ',';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$escaping = false;
|
||||
$regexPattern .= $currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
return $regexPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files.
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function fileSearchWithIgnore(string $inputDir, bool $recursive = true, ?array $ignoreFiles = []): array
|
||||
{
|
||||
if ($recursive) {
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($inputDir);
|
||||
|
||||
if (!empty($ignoreFiles)) {
|
||||
$directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
|
||||
}
|
||||
$iterator = new \RecursiveIteratorIterator($directoryIterator);
|
||||
} else {
|
||||
$directoryIterator = new \DirectoryIterator($inputDir);
|
||||
|
||||
if (!empty($ignoreFiles)) {
|
||||
$directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
|
||||
}
|
||||
$iterator = new \IteratorIterator($directoryIterator);
|
||||
}
|
||||
|
||||
$fileList = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from glob pattern.
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function globFileSearch(string $globPattern, int $flags = 0, bool $recursive = true): array
|
||||
{
|
||||
$files = glob($globPattern, $flags);
|
||||
|
||||
if (!$recursive) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
|
||||
// Unpacking the argument via ... is supported starting from php 5.6 only
|
||||
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||
$files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from regex pattern.
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function regexFileSearch(string $folder, ?string $pattern, bool $recursive = true): array
|
||||
{
|
||||
if ($recursive) {
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($folder);
|
||||
$iterator = new \RecursiveIteratorIterator($directoryIterator);
|
||||
} else {
|
||||
$directoryIterator = new \DirectoryIterator($folder);
|
||||
$iterator = new \IteratorIterator($directoryIterator);
|
||||
}
|
||||
|
||||
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
|
||||
$fileList = [];
|
||||
|
||||
foreach ($regexIterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to human size.
|
||||
*
|
||||
* @param int $size Size bytes
|
||||
* @param string|null $unit Unit support 'GB', 'MB', 'KB'
|
||||
*/
|
||||
public static function humanSize(int $size, ?string $unit = null): string
|
||||
{
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
|
||||
return number_format($size / (1 << 30), 2) . 'GB';
|
||||
}
|
||||
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
|
||||
return number_format($size / (1 << 20), 2) . 'MB';
|
||||
}
|
||||
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
|
||||
return number_format($size / (1 << 10), 2) . 'KB';
|
||||
}
|
||||
|
||||
return number_format($size) . ' bytes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes zip path.
|
||||
*
|
||||
* @param string $path Zip path
|
||||
*/
|
||||
public static function normalizeZipPath(string $path): string
|
||||
{
|
||||
return implode(
|
||||
\DIRECTORY_SEPARATOR,
|
||||
array_filter(
|
||||
explode('/', $path),
|
||||
static fn ($part) => $part !== '.' && $part !== '..'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the file path is an absolute path.
|
||||
*
|
||||
* @param string $file A file path
|
||||
*
|
||||
* @see source symfony filesystem component
|
||||
*/
|
||||
public static function isAbsolutePath(string $file): bool
|
||||
{
|
||||
return strspn($file, '/\\', 0, 1)
|
||||
|| (
|
||||
\strlen($file) > 3 && ctype_alpha($file[0])
|
||||
&& $file[1] === ':'
|
||||
&& strspn($file, '/\\', 2, 1)
|
||||
)
|
||||
|| parse_url($file, \PHP_URL_SCHEME) !== null;
|
||||
}
|
||||
|
||||
public static function symlink(string $target, ?string $path, bool $allowSymlink): bool
|
||||
{
|
||||
if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) {
|
||||
return file_put_contents($path, $target) !== false;
|
||||
}
|
||||
|
||||
return symlink($target, $path);
|
||||
}
|
||||
|
||||
public static function isBadCompressionFile(string $file): bool
|
||||
{
|
||||
$badCompressFileExt = [
|
||||
'dic',
|
||||
'dng',
|
||||
'f4v',
|
||||
'flipchart',
|
||||
'h264',
|
||||
'lrf',
|
||||
'mobi',
|
||||
'mts',
|
||||
'nef',
|
||||
'pspimage',
|
||||
];
|
||||
|
||||
$ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION));
|
||||
|
||||
if (\in_array($ext, $badCompressFileExt, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mimeType = self::getMimeTypeFromFile($file);
|
||||
|
||||
return self::isBadCompressionMimeType($mimeType);
|
||||
}
|
||||
|
||||
public static function isBadCompressionMimeType(string $mimeType): bool
|
||||
{
|
||||
static $badDeflateCompMimeTypes = [
|
||||
'application/epub+zip',
|
||||
'application/gzip',
|
||||
'application/vnd.debian.binary-package',
|
||||
'application/vnd.oasis.opendocument.graphics',
|
||||
'application/vnd.oasis.opendocument.presentation',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/vnd.oasis.opendocument.text-master',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.rn-realmedia',
|
||||
'application/x-7z-compressed',
|
||||
'application/x-arj',
|
||||
'application/x-bzip2',
|
||||
'application/x-hwp',
|
||||
'application/x-lzip',
|
||||
'application/x-lzma',
|
||||
'application/x-ms-reader',
|
||||
'application/x-rar',
|
||||
'application/x-rpm',
|
||||
'application/x-stuffit',
|
||||
'application/x-tar',
|
||||
'application/x-xz',
|
||||
'application/zip',
|
||||
'application/zlib',
|
||||
'audio/flac',
|
||||
'audio/mpeg',
|
||||
'audio/ogg',
|
||||
'audio/vnd.dolby.dd-raw',
|
||||
'audio/webm',
|
||||
'audio/x-ape',
|
||||
'audio/x-hx-aac-adts',
|
||||
'audio/x-m4a',
|
||||
'audio/x-m4a',
|
||||
'audio/x-wav',
|
||||
'image/gif',
|
||||
'image/heic',
|
||||
'image/jp2',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/vnd.djvu',
|
||||
'image/webp',
|
||||
'image/x-canon-cr2',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'video/x-matroska',
|
||||
'video/x-ms-asf',
|
||||
'x-epoc/x-sisx-app',
|
||||
];
|
||||
|
||||
return \in_array($mimeType, $badDeflateCompMimeTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
public static function getMimeTypeFromFile(string $file): string
|
||||
{
|
||||
if (\function_exists('mime_content_type')) {
|
||||
return mime_content_type($file);
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
public static function getMimeTypeFromString(string $contents): string
|
||||
{
|
||||
$finfo = new \finfo(\FILEINFO_MIME);
|
||||
$mimeType = $finfo->buffer($contents);
|
||||
|
||||
if ($mimeType === false) {
|
||||
$mimeType = 'application/octet-stream';
|
||||
}
|
||||
|
||||
return explode(';', $mimeType)[0];
|
||||
}
|
||||
}
|
||||
62
vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php
vendored
Normal file
62
vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Iterator for ignore files.
|
||||
*/
|
||||
class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
{
|
||||
/** Ignore list files. */
|
||||
private array $ignoreFiles = ['..'];
|
||||
|
||||
public function __construct(\Iterator $iterator, ?array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable.
|
||||
*
|
||||
* @see http://php.net/manual/en/filteriterator.accept.php
|
||||
*
|
||||
* @return bool true if the current element is acceptable, otherwise false
|
||||
*/
|
||||
public function accept(): bool
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& StringUtil::endsWith($ignoreFile, '/')
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
72
vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
vendored
Normal file
72
vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Recursive iterator for ignore files.
|
||||
*/
|
||||
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
/** Ignore list files. */
|
||||
private array $ignoreFiles = ['..'];
|
||||
|
||||
public function __construct(\RecursiveIterator $iterator, ?array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable.
|
||||
*
|
||||
* @see http://php.net/manual/en/filteriterator.accept.php
|
||||
*
|
||||
* @return bool true if the current element is acceptable, otherwise false
|
||||
*/
|
||||
public function accept(): bool
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& $ignoreFile[\strlen($ignoreFile) - 1] === '/'
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IgnoreFilesRecursiveFilterIterator
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
* @noinspection PhpPossiblePolymorphicInvocationInspection
|
||||
*/
|
||||
public function getChildren(): self
|
||||
{
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
|
||||
}
|
||||
}
|
||||
36
vendor/nelexa/zip/src/Util/MathUtil.php
vendored
Normal file
36
vendor/nelexa/zip/src/Util/MathUtil.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* Math util.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MathUtil
|
||||
{
|
||||
/**
|
||||
* Cast to signed int 32-bit.
|
||||
*/
|
||||
public static function toSignedInt32(int $int): int
|
||||
{
|
||||
if (\PHP_INT_SIZE === 8) {
|
||||
$int &= 0xFFFFFFFF;
|
||||
|
||||
if ($int & 0x80000000) {
|
||||
return $int - 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
return $int;
|
||||
}
|
||||
}
|
||||
35
vendor/nelexa/zip/src/Util/StringUtil.php
vendored
Normal file
35
vendor/nelexa/zip/src/Util/StringUtil.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the nelexa/zip package.
|
||||
* (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* String Util.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class StringUtil
|
||||
{
|
||||
public static function endsWith(string $haystack, ?string $needle): bool
|
||||
{
|
||||
return $needle === '' || ($haystack !== '' && substr_compare($haystack, $needle, -\strlen($needle)) === 0);
|
||||
}
|
||||
|
||||
public static function isBinary(string $string): bool
|
||||
{
|
||||
return strpos($string, "\0") !== false;
|
||||
}
|
||||
|
||||
public static function isASCII(string $name): bool
|
||||
{
|
||||
return preg_match('~[^\x20-\x7e]~', $name) === 0;
|
||||
}
|
||||
}
|
||||
1944
vendor/nelexa/zip/src/ZipFile.php
vendored
Normal file
1944
vendor/nelexa/zip/src/ZipFile.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user