mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-04-13 19:21:56 +02:00
Merge branch 'release/1.0.0'
This commit is contained in:
commit
d075309b78
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/vendor/
|
||||
*.iml
|
||||
/.idea/
|
250
README.md
Normal file
250
README.md
Normal file
@ -0,0 +1,250 @@
|
||||
## Documentation
|
||||
|
||||
Create and manipulate zip archives. No use ZipArchive class and php-zip extension.
|
||||
|
||||
### class \Nelexa\Zip\ZipFile
|
||||
Initialization
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
```
|
||||
Create archive
|
||||
```php
|
||||
$zip->create();
|
||||
```
|
||||
Open archive file
|
||||
```php
|
||||
$zip->open($filename);
|
||||
```
|
||||
Open archive from string
|
||||
```php
|
||||
$zip->openFromString($string)
|
||||
```
|
||||
Set password
|
||||
```php
|
||||
$zip->setPassword($password);
|
||||
```
|
||||
List files
|
||||
```php
|
||||
$listFiles = $zip->getListFiles();
|
||||
```
|
||||
Get count files
|
||||
```php
|
||||
$countFiles = $zip->getCountFiles();
|
||||
```
|
||||
Add empty dir
|
||||
```php
|
||||
$zip->addEmptyDir($dirName);
|
||||
```
|
||||
Add dir
|
||||
```php
|
||||
$directory = "/tmp";
|
||||
$ignoreFiles = array("xxx.file", "xxx2.file");
|
||||
$zip->addDir($directory); // add path /tmp to /
|
||||
$zip->addDir($directory, "var/temp"); // add path /tmp to var/temp
|
||||
$zip->addDir($directory, "var/temp", $ignoreFiles); // add path /tmp to var/temp and ignore files xxx.file and xxx2.file
|
||||
```
|
||||
Add files from glob pattern
|
||||
```php
|
||||
$zip->addGlob("music/*.mp3"); // add all mp3 files
|
||||
```
|
||||
Add files from regex pattern
|
||||
```php
|
||||
$zip->addPattern("~file[0-9]+\.jpg$~", "picture/");
|
||||
```
|
||||
Add file
|
||||
```php
|
||||
$zip->addFile($filename);
|
||||
$zip->addFile($filename, $localName);
|
||||
$zip->addFile($filename, $localName, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_STORED); // no compression
|
||||
$zip->addFile($filename, $localName, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_DEFLATED);
|
||||
```
|
||||
Add file from string
|
||||
```php
|
||||
$zip->addFromString($localName, $contents);
|
||||
$zip->addFromString($localName, $contents, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_STORED); // no compression
|
||||
$zip->addFromString($localName, $contents, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_DEFLATED);
|
||||
```
|
||||
Update timestamp for all files
|
||||
```php
|
||||
$timestamp = time(); // now time
|
||||
$zip->updateTimestamp($timestamp);
|
||||
```
|
||||
Delete files from glob pattern
|
||||
```php
|
||||
$zip->deleteGlob("*.jpg"); // remove all jpg files
|
||||
```
|
||||
Delete files from regex pattern
|
||||
```php
|
||||
$zip->deletePattern("~\.jpg$~i"); // remove all jpg files
|
||||
```
|
||||
Delete file from index
|
||||
```php
|
||||
$zip->deleteIndex(0);
|
||||
```
|
||||
Delete all files
|
||||
```php
|
||||
$zip->deleteAll();
|
||||
```
|
||||
Delete from file name
|
||||
```php
|
||||
$zip->deleteName($filename);
|
||||
```
|
||||
Extract zip archive
|
||||
```php
|
||||
$zip->extractTo($toPath)
|
||||
$zip->extractTo($toPath, array("file1", "file2")); // extract only files file1 and file2
|
||||
```
|
||||
Get archive comment
|
||||
```php
|
||||
$archiveComment = $zip->getArchiveComment();
|
||||
```
|
||||
Set archive comment
|
||||
```php
|
||||
$zip->setArchiveComment($comment)
|
||||
```
|
||||
Get comment file from index
|
||||
```php
|
||||
$commentFile = $zip->getCommentIndex($index);
|
||||
```
|
||||
Set comment file from index
|
||||
```php
|
||||
$zip->setCommentIndex($index, $comment);
|
||||
```
|
||||
Get comment file from filename
|
||||
```php
|
||||
$commentFile = $zip->getCommentName($filename);
|
||||
```
|
||||
Set comment file from filename
|
||||
```php
|
||||
$zip->setCommentName($name, $comment);
|
||||
```
|
||||
Get file content from index
|
||||
```php
|
||||
$content = $zip->getFromIndex($index);
|
||||
```
|
||||
Get file content from filename
|
||||
```php
|
||||
$content = $zip->getFromName($name);
|
||||
```
|
||||
Get filename from index
|
||||
```php
|
||||
$filename = $zip->getNameIndex($index);
|
||||
```
|
||||
Rename file from index
|
||||
```php
|
||||
$zip->renameIndex($index, $newFilename);
|
||||
```
|
||||
Rename file from filename
|
||||
```php
|
||||
$zip->renameName($oldName, $newName);
|
||||
```
|
||||
Get zip entries
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry[] $zipEntries
|
||||
*/
|
||||
$zipEntries = $zip->getZipEntries();
|
||||
```
|
||||
Get zip entry from index
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry $zipEntry
|
||||
*/
|
||||
$zipEntry = $zip->getZipEntryIndex($index);
|
||||
```
|
||||
Get zip entry from filename
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry $zipEntry
|
||||
*/
|
||||
$zipEntry = $zip->getZipEntryName($name);
|
||||
```
|
||||
Get info from index
|
||||
```php
|
||||
$info = $zip->statIndex($index);
|
||||
// [
|
||||
// 'name' - filename
|
||||
// 'index' - index number
|
||||
// 'crc' - crc32
|
||||
// 'size' - uncompressed size
|
||||
// 'mtime' - last modify date time
|
||||
// 'comp_size' - compressed size
|
||||
// 'comp_method' - compressed method
|
||||
// ]
|
||||
```
|
||||
Get info from name
|
||||
```php
|
||||
$info = $zip->statName($name);
|
||||
// [
|
||||
// 'name' - filename
|
||||
// 'index' - index number
|
||||
// 'crc' - crc32
|
||||
// 'size' - uncompressed size
|
||||
// 'mtime' - last modify date time
|
||||
// 'comp_size' - compressed size
|
||||
// 'comp_method' - compressed method
|
||||
// ]
|
||||
```
|
||||
Get info from all files
|
||||
```php
|
||||
$info = $zip->getExtendedListFiles();
|
||||
```
|
||||
Get output contents
|
||||
```php
|
||||
$content = $zip->output();
|
||||
```
|
||||
Save opened file
|
||||
```php
|
||||
$isSuccessSave = $zip->save();
|
||||
```
|
||||
Save file as
|
||||
```php
|
||||
$zip->saveAs($outputFile);
|
||||
```
|
||||
Close archive
|
||||
```php
|
||||
$zip->close();
|
||||
```
|
||||
|
||||
### Example create zip archive
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->create();
|
||||
$zip->addFile("README.md");
|
||||
$zip->addFile("README.md", "folder/README");
|
||||
$zip->addFromString("folder/file.txt", "File content");
|
||||
$zip->addEmptyDir("f/o/l/d/e/r");
|
||||
$zip->setArchiveComment("Archive comment");
|
||||
$zip->setCommentIndex(0, "Comment file with index 0");
|
||||
$zip->saveAs("output.zip");
|
||||
$zip->close();
|
||||
|
||||
// $ zipinfo output.zip
|
||||
// Archive: output.zip
|
||||
// Zip file size: 912 bytes, number of entries: 4
|
||||
// -rw---- 1.0 fat 387 b- defN README.md
|
||||
// -rw---- 1.0 fat 387 b- defN folder/README
|
||||
// -rw---- 1.0 fat 12 b- defN folder/file.txt
|
||||
// -rw---- 1.0 fat 0 b- stor f/o/l/d/e/r/
|
||||
// 4 files, 786 bytes uncompressed, 448 bytes compressed: 43.0%
|
||||
```
|
||||
|
||||
### Example modification zip archive
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->open("output.zip");
|
||||
$zip->addFromString("new-file", file_get_contents(__FILE__));
|
||||
$zip->saveAs("output2.zip");
|
||||
$zip->save();
|
||||
$zip->close();
|
||||
|
||||
// $ zipinfo output2.zip
|
||||
// Archive: output2.zip
|
||||
// Zip file size: 1331 bytes, number of entries: 5
|
||||
// -rw---- 1.0 fat 387 b- defN README.md
|
||||
// -rw---- 1.0 fat 387 b- defN folder/README
|
||||
// -rw---- 1.0 fat 12 b- defN folder/file.txt
|
||||
// -rw---- 1.0 fat 0 b- stor f/o/l/d/e/r/
|
||||
// -rw---- 1.0 fat 593 b- defN new-file
|
||||
// 5 files, 1379 bytes uncompressed, 775 bytes compressed: 43.8%
|
||||
```
|
25
composer.json
Normal file
25
composer.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "nelexa/zip",
|
||||
"description": "Zip create, modify and extract tool. Alternative ZipArchive.",
|
||||
"type": "library",
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.5"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ne-Lexa",
|
||||
"email": "alexey@nelexa.ru"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"nelexa/buffer": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nelexa\\Zip\\": "src"
|
||||
}
|
||||
}
|
||||
}
|
45
src/FilterFileIterator.php
Normal file
45
src/FilterFileIterator.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class FilterFileIterator extends \FilterIterator
|
||||
{
|
||||
private $ignoreFiles;
|
||||
private static $ignoreAlways = array('..');
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge(self::$ignoreAlways, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $value
|
||||
*/
|
||||
$value = $this->current();
|
||||
$pathName = $value->getRealPath();
|
||||
foreach ($this->ignoreFiles AS $ignoreFile) {
|
||||
if ($this->endsWith($pathName, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function endsWith($haystack, $needle)
|
||||
{
|
||||
// search forward starting from end minus needle length characters
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== FALSE);
|
||||
}
|
||||
}
|
905
src/ZipEntry.php
Normal file
905
src/ZipEntry.php
Normal file
@ -0,0 +1,905 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\Buffer;
|
||||
use Nelexa\Buffer\StringBuffer;
|
||||
|
||||
class ZipEntry
|
||||
{
|
||||
// made by constants
|
||||
const MADE_BY_MS_DOS = 0;
|
||||
const MADE_BY_AMIGA = 1;
|
||||
const MADE_BY_OPEN_VMS = 2;
|
||||
const MADE_BY_UNIX = 3;
|
||||
const MADE_BY_VM_CMS = 4;
|
||||
const MADE_BY_ATARI = 5;
|
||||
const MADE_BY_OS_2 = 6;
|
||||
const MADE_BY_MACINTOSH = 7;
|
||||
const MADE_BY_Z_SYSTEM = 8;
|
||||
const MADE_BY_CP_M = 9;
|
||||
const MADE_BY_WINDOWS_NTFS = 10;
|
||||
const MADE_BY_MVS = 11;
|
||||
const MADE_BY_VSE = 12;
|
||||
const MADE_BY_ACORN_RISC = 13;
|
||||
const MADE_BY_VFAT = 14;
|
||||
const MADE_BY_ALTERNATE_MVS = 15;
|
||||
const MADE_BY_BEOS = 16;
|
||||
const MADE_BY_TANDEM = 17;
|
||||
const MADE_BY_OS_400 = 18;
|
||||
const MADE_BY_OS_X = 19;
|
||||
const MADE_BY_UNKNOWN = 20;
|
||||
|
||||
private static $valuesMadeBy = array(
|
||||
self::MADE_BY_MS_DOS => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
self::MADE_BY_UNIX => 'UNIX',
|
||||
self::MADE_BY_VM_CMS => 'VM/CMS',
|
||||
self::MADE_BY_ATARI => 'Atari ST',
|
||||
self::MADE_BY_OS_2 => 'OS/2 H.P.F.S.',
|
||||
self::MADE_BY_MACINTOSH => 'Macintosh',
|
||||
self::MADE_BY_Z_SYSTEM => 'Z-System',
|
||||
self::MADE_BY_CP_M => 'CP/M',
|
||||
self::MADE_BY_WINDOWS_NTFS => 'Windows NTFS',
|
||||
self::MADE_BY_MVS => 'MVS (OS/390 - Z/OS)',
|
||||
self::MADE_BY_VSE => 'VSE',
|
||||
self::MADE_BY_ACORN_RISC => 'Acorn Risc',
|
||||
self::MADE_BY_VFAT => 'VFAT',
|
||||
self::MADE_BY_ALTERNATE_MVS => 'alternate MVS',
|
||||
self::MADE_BY_BEOS => 'BeOS',
|
||||
self::MADE_BY_TANDEM => 'Tandem',
|
||||
self::MADE_BY_OS_400 => 'OS/400',
|
||||
self::MADE_BY_OS_X => 'OS X (Darwin)',
|
||||
);
|
||||
|
||||
// constants version by extract
|
||||
const EXTRACT_VERSION_10 = 10;
|
||||
const EXTRACT_VERSION_11 = 11;
|
||||
const EXTRACT_VERSION_20 = 20;
|
||||
//1.0 - Default value
|
||||
//1.1 - File is a volume label
|
||||
//2.0 - File is a folder (directory)
|
||||
//2.0 - File is compressed using Deflate compression
|
||||
//2.0 - File is encrypted using traditional PKWARE encryption
|
||||
//2.1 - File is compressed using Deflate64(tm)
|
||||
//2.5 - File is compressed using PKWARE DCL Implode
|
||||
//2.7 - File is a patch data set
|
||||
//4.5 - File uses ZIP64 format extensions
|
||||
//4.6 - File is compressed using BZIP2 compression*
|
||||
//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
|
||||
//5.1 - File is encrypted using AES encryption
|
||||
//5.1 - File is encrypted using corrected RC2 encryption**
|
||||
//5.2 - File is encrypted using corrected RC2-64 encryption**
|
||||
//6.1 - File is encrypted using non-OAEP key wrapping***
|
||||
//6.2 - Central directory encryption
|
||||
//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
|
||||
|
||||
const FLAG_ENCRYPTION = 0;
|
||||
const FLAG_DATA_DESCRIPTION = 3;
|
||||
const FLAG_UTF8 = 11;
|
||||
private static $valuesFlag = array(
|
||||
self::FLAG_ENCRYPTION => 'encrypted file', // 1 << 0
|
||||
1 => 'compression option', // 1 << 1
|
||||
2 => 'compression option', // 1 << 2
|
||||
self::FLAG_DATA_DESCRIPTION => 'data descriptor', // 1 << 3
|
||||
4 => 'enhanced deflation', // 1 << 4
|
||||
5 => 'compressed patched data', // 1 << 5
|
||||
6 => 'strong encryption', // 1 << 6
|
||||
7 => 'unused', // 1 << 7
|
||||
8 => 'unused', // 1 << 8
|
||||
9 => 'unused', // 1 << 9
|
||||
10 => 'unused', // 1 << 10
|
||||
self::FLAG_UTF8 => 'language encoding', // 1 << 11
|
||||
12 => 'reserved', // 1 << 12
|
||||
13 => 'mask header values', // 1 << 13
|
||||
14 => 'reserved', // 1 << 14
|
||||
15 => 'reserved', // 1 << 15
|
||||
);
|
||||
|
||||
// compression method constants
|
||||
const COMPRESS_METHOD_STORED = 0;
|
||||
const COMPRESS_METHOD_DEFLATED = 8;
|
||||
const COMPRESS_METHOD_AES = 99;
|
||||
|
||||
private static $valuesCompressionMethod = array(
|
||||
self::COMPRESS_METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
4 => 'reduce level 3',
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
self::COMPRESS_METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
12 => 'bzip2',
|
||||
13 => 'reserved by PKWARE',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'reserved by PKWARE',
|
||||
16 => 'reserved by PKWARE',
|
||||
17 => 'reserved by PKWARE',
|
||||
18 => 'IBM TERSE',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
self::COMPRESS_METHOD_AES => 'AES Encryption',
|
||||
);
|
||||
|
||||
const INTERNAL_ATTR_DEFAULT = 0;
|
||||
const EXTERNAL_ATTR_DEFAULT = 0;
|
||||
|
||||
/*
|
||||
* Extra field header ID
|
||||
*/
|
||||
const EXTID_ZIP64 = 0x0001; // Zip64
|
||||
const EXTID_NTFS = 0x000a; // NTFS (for storing full file times information)
|
||||
const EXTID_UNIX = 0x000d; // UNIX
|
||||
const EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp
|
||||
const EXTID_UNICODE_FILENAME = 0x7075; // for Unicode filenames
|
||||
const EXTID_UNICODE_ = 0x6375; // for Unicode file comments
|
||||
const EXTID_STORING_STRINGS = 0x5A4C; // for storing strings code pages and Unicode filenames using custom Unicode implementation (see Unicode Support: Using Non-English Characters in Filenames, Comments and Passwords).
|
||||
const EXTID_OFFSETS_COMPRESS_DATA = 0x5A4D; // for saving offsets array from seekable compressed data
|
||||
const EXTID_AES_ENCRYPTION = 0x9901; // WinZip AES encryption (http://www.winzip.com/aes_info.htm)
|
||||
|
||||
/**
|
||||
* entry name
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* version made by
|
||||
* @var int
|
||||
*/
|
||||
private $versionMadeBy = self::MADE_BY_WINDOWS_NTFS;
|
||||
/**
|
||||
* version needed to extract
|
||||
* @var int
|
||||
*/
|
||||
private $versionExtract = self::EXTRACT_VERSION_20;
|
||||
/**
|
||||
* general purpose bit flag
|
||||
* @var int
|
||||
*/
|
||||
private $flag = 0;
|
||||
/**
|
||||
* compression method
|
||||
* @var int
|
||||
*/
|
||||
private $compressionMethod = self::COMPRESS_METHOD_DEFLATED;
|
||||
/**
|
||||
* last mod file datetime
|
||||
* @var int Unix timestamp
|
||||
*/
|
||||
private $lastModDateTime;
|
||||
/**
|
||||
* crc-32
|
||||
* @var int
|
||||
*/
|
||||
private $crc32;
|
||||
/**
|
||||
* compressed size
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
/**
|
||||
* uncompressed size
|
||||
* @var int
|
||||
*/
|
||||
private $unCompressedSize;
|
||||
/**
|
||||
* disk number start
|
||||
* @var int
|
||||
*/
|
||||
private $diskNumber = 0;
|
||||
/**
|
||||
* internal file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $internalAttributes = self::INTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* external file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $externalAttributes = self::EXTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* relative offset of local header
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfLocal;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfCentral;
|
||||
|
||||
/**
|
||||
* optional extra field data for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $extraCentral = "";
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $extraLocal = "";
|
||||
/**
|
||||
* optional comment string for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $comment = "";
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getLengthOfLocal()
|
||||
{
|
||||
return $this->getLengthLocalHeader() + $this->compressedSize + ($this->hasDataDescriptor() ? 12 : 0);
|
||||
}
|
||||
|
||||
public function getLengthLocalHeader()
|
||||
{
|
||||
return 30 + strlen($this->name) + strlen($this->extraLocal);
|
||||
}
|
||||
|
||||
public function getLengthOfCentral()
|
||||
{
|
||||
return 46 + strlen($this->name) + strlen($this->extraCentral) + strlen($this->comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Buffer $buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readCentralHeader(Buffer $buffer)
|
||||
{
|
||||
$signature = $buffer->getUnsignedInt(); // after offset 4
|
||||
if ($signature !== ZipFile::SIGNATURE_CENTRAL_DIR) {
|
||||
throw new ZipException("Can not read central directory. Bad signature: " . $signature);
|
||||
}
|
||||
$this->versionMadeBy = $buffer->getUnsignedShort(); // after offset 6
|
||||
$this->versionExtract = $buffer->getUnsignedShort(); // after offset 8
|
||||
$this->flag = $buffer->getUnsignedShort(); // after offset 10
|
||||
$this->compressionMethod = $buffer->getUnsignedShort(); // after offset 12
|
||||
$lastModTime = $buffer->getUnsignedShort(); // after offset 14
|
||||
$lastModDate = $buffer->getUnsignedShort(); // after offset 16
|
||||
$this->setLastModifyDosDatetime($lastModTime, $lastModDate);
|
||||
$this->crc32 = $buffer->getUnsignedInt(); // after offset 20
|
||||
$this->compressedSize = $buffer->getUnsignedInt(); // after offset 24
|
||||
$this->unCompressedSize = $buffer->getUnsignedInt(); // after offset 28
|
||||
$fileNameLength = $buffer->getUnsignedShort(); // after offset 30
|
||||
$extraCentralLength = $buffer->getUnsignedShort(); // after offset 32
|
||||
$fileCommentLength = $buffer->getUnsignedShort(); // after offset 34
|
||||
$this->diskNumber = $buffer->getUnsignedShort(); // after offset 36
|
||||
$this->internalAttributes = $buffer->getUnsignedShort(); // after offset 38
|
||||
$this->externalAttributes = $buffer->getUnsignedInt(); // after offset 42
|
||||
$this->offsetOfLocal = $buffer->getUnsignedInt(); // after offset 46
|
||||
$this->name = $buffer->getString($fileNameLength);
|
||||
$this->setExtra($buffer->getString($extraCentralLength));
|
||||
$this->comment = $buffer->getString($fileCommentLength);
|
||||
|
||||
$currentPos = $buffer->position();
|
||||
$buffer->setPosition($this->offsetOfLocal + 28);
|
||||
$extraLocalLength = $buffer->getUnsignedShort();
|
||||
$buffer->skip($fileNameLength);
|
||||
$this->extraLocal = $buffer->getString($extraLocalLength);
|
||||
$buffer->setPosition($currentPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional extra field data for the entry.
|
||||
*
|
||||
* @param string $extra the extra field data bytes
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function setExtra($extra)
|
||||
{
|
||||
if (!empty($extra)) {
|
||||
$len = strlen($extra);
|
||||
if ($len > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$buffer = new StringBuffer($extra);
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
// extra fields are in "HeaderID(2)DataSize(2)Data... format
|
||||
while ($buffer->position() + 4 < $len) {
|
||||
$tag = $buffer->getUnsignedShort();
|
||||
$sz = $buffer->getUnsignedShort();
|
||||
if ($buffer->position() + $sz > $len) // invalid data
|
||||
break;
|
||||
switch ($tag) {
|
||||
case self::EXTID_ZIP64:
|
||||
// not support zip64
|
||||
break;
|
||||
case self::EXTID_NTFS:
|
||||
$buffer->skip(4); // reserved 4 bytes
|
||||
if ($buffer->getUnsignedShort() != 0x0001 || $buffer->getUnsignedShort() != 24)
|
||||
break;
|
||||
// $mtime = winTimeToFileTime($buffer->getLong());
|
||||
// $atime = winTimeToFileTime($buffer->getLong());
|
||||
// $ctime = winTimeToFileTime($buffer->getLong());
|
||||
break;
|
||||
case self::EXTID_EXTT:
|
||||
$flag = $buffer->getUnsignedByte();
|
||||
$sz0 = 1;
|
||||
// The CEN-header extra field contains the modification
|
||||
// time only, or no timestamp at all. 'sz' is used to
|
||||
// flag its presence or absence. But if mtime is present
|
||||
// in LOC it must be present in CEN as well.
|
||||
if (($flag & 0x1) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$mtime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x2) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$atime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x4) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$ctime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
*/
|
||||
public function writeLocalHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_LOCAL_HEADER);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
if ($this->hasDataDescriptor()) {
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
} else {
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
}
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraLocal)); // offset 30
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraLocal);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function setFlagBit($bit)
|
||||
{
|
||||
if ($bit < 0 || $bit > 15) {
|
||||
return false;
|
||||
}
|
||||
$this->flag |= 1 << $bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function testFlagBit($bit)
|
||||
{
|
||||
return (($this->flag & (1 << $bit)) !== 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDataDescriptor()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_DATA_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_ENCRYPTION);
|
||||
}
|
||||
|
||||
public function writeDataDescriptor()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeCentralHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_CENTRAL_DIR);
|
||||
$buffer->insertShort($this->versionMadeBy);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraCentral));
|
||||
$buffer->insertShort(strlen($this->comment));
|
||||
$buffer->insertShort($this->diskNumber);
|
||||
$buffer->insertShort($this->internalAttributes);
|
||||
$buffer->insertInt($this->externalAttributes);
|
||||
$buffer->insertInt($this->offsetOfLocal);
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraCentral);
|
||||
$buffer->insertString($this->comment);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->name[strlen($this->name) - 1] === "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesMadeBy()
|
||||
{
|
||||
return self::$valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesMadeBy
|
||||
*/
|
||||
public static function setValuesMadeBy($valuesMadeBy)
|
||||
{
|
||||
self::$valuesMadeBy = $valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesFlag()
|
||||
{
|
||||
return self::$valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesFlag
|
||||
*/
|
||||
public static function setValuesFlag($valuesFlag)
|
||||
{
|
||||
self::$valuesFlag = $valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesCompressionMethod()
|
||||
{
|
||||
return self::$valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesCompressionMethod
|
||||
*/
|
||||
public static function setValuesCompressionMethod($valuesCompressionMethod)
|
||||
{
|
||||
self::$valuesCompressionMethod = $valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
if (strlen($name) > 0xFFFF) {
|
||||
throw new ZipException("entry name too long");
|
||||
}
|
||||
$this->name = $name;
|
||||
$encoding = mb_detect_encoding($this->name, "ASCII, UTF-8", true);
|
||||
if ($encoding === 'UTF-8') {
|
||||
$this->setFlagBit(self::FLAG_UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionMadeBy()
|
||||
{
|
||||
return $this->versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionMadeBy
|
||||
*/
|
||||
public function setVersionMadeBy($versionMadeBy)
|
||||
{
|
||||
$this->versionMadeBy = $versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionExtract()
|
||||
{
|
||||
return $this->versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionExtract
|
||||
*/
|
||||
public function setVersionExtract($versionExtract)
|
||||
{
|
||||
$this->versionExtract = $versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFlag()
|
||||
{
|
||||
return $this->flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $flag
|
||||
*/
|
||||
public function setFlag($flag)
|
||||
{
|
||||
$this->flag = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionMethod()
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressionMethod
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressionMethod($compressionMethod)
|
||||
{
|
||||
if (!isset(self::$valuesCompressionMethod[$compressionMethod])) {
|
||||
throw new ZipException("invalid compression method " . $compressionMethod);
|
||||
}
|
||||
$this->compressionMethod = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModDateTime()
|
||||
{
|
||||
return $this->lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModDateTime
|
||||
*/
|
||||
public function setLastModDateTime($lastModDateTime)
|
||||
{
|
||||
$this->lastModDateTime = $lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc32()
|
||||
{
|
||||
return $this->crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $crc32
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc32($crc32)
|
||||
{
|
||||
if ($crc32 < 0 || $crc32 > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry crc-32");
|
||||
}
|
||||
$this->crc32 = $crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressedSize
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUnCompressedSize()
|
||||
{
|
||||
return $this->unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $unCompressedSize
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setUnCompressedSize($unCompressedSize)
|
||||
{
|
||||
if ($unCompressedSize < 0 || $unCompressedSize > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry size");
|
||||
}
|
||||
$this->unCompressedSize = $unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDiskNumber()
|
||||
{
|
||||
return $this->diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $diskNumber
|
||||
*/
|
||||
public function setDiskNumber($diskNumber)
|
||||
{
|
||||
$this->diskNumber = $diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getInternalAttributes()
|
||||
{
|
||||
return $this->internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $internalAttributes
|
||||
*/
|
||||
public function setInternalAttributes($internalAttributes)
|
||||
{
|
||||
$this->internalAttributes = $internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExternalAttributes()
|
||||
{
|
||||
return $this->externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $externalAttributes
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfLocal()
|
||||
{
|
||||
return $this->offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfLocal
|
||||
*/
|
||||
public function setOffsetOfLocal($offsetOfLocal)
|
||||
{
|
||||
$this->offsetOfLocal = $offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfCentral()
|
||||
{
|
||||
return $this->offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfCentral
|
||||
*/
|
||||
public function setOffsetOfCentral($offsetOfCentral)
|
||||
{
|
||||
$this->offsetOfCentral = $offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraCentral()
|
||||
{
|
||||
return $this->extraCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraCentral($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraLocal($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraLocal = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraLocal()
|
||||
{
|
||||
return $this->extraLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModTime
|
||||
* @param int $lastModDate
|
||||
*/
|
||||
private function setLastModifyDosDatetime($lastModTime, $lastModDate)
|
||||
{
|
||||
$hour = ($lastModTime & 0xF800) >> 11;
|
||||
$minute = ($lastModTime & 0x07E0) >> 5;
|
||||
$seconds = ($lastModTime & 0x001F) * 2;
|
||||
|
||||
$year = (($lastModDate & 0xFE00) >> 9) + 1980;
|
||||
$month = ($lastModDate & 0x01E0) >> 5;
|
||||
$day = $lastModDate & 0x001F;
|
||||
|
||||
// ----- Get UNIX date format
|
||||
$this->lastModDateTime = mktime($hour, $minute, $seconds, $month, $day, $year);
|
||||
}
|
||||
|
||||
public function getLastModifyDosTime()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;
|
||||
}
|
||||
|
||||
public function getLastModifyDosDate()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];
|
||||
}
|
||||
|
||||
public function versionMadeToString()
|
||||
{
|
||||
if (isset(self::$valuesMadeBy[$this->versionMadeBy])) {
|
||||
return self::$valuesMadeBy[$this->versionMadeBy];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function compressionMethodToString()
|
||||
{
|
||||
if (isset(self::$valuesCompressionMethod[$this->compressionMethod])) {
|
||||
return self::$valuesCompressionMethod[$this->compressionMethod];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function flagToString()
|
||||
{
|
||||
$return = array();
|
||||
foreach (self::$valuesFlag AS $bit => $value) {
|
||||
if ($this->testFlagBit($bit)) {
|
||||
$return[] = $value;
|
||||
}
|
||||
}
|
||||
if (!empty($return)) {
|
||||
return implode(', ', $return);
|
||||
} else if ($this->flag === 0) {
|
||||
return "default";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return __CLASS__ . '{' .
|
||||
'name="' . $this->name . '"' .
|
||||
', versionMadeBy={' . $this->versionMadeBy . ' => "' . $this->versionMadeToString() . '"}' .
|
||||
', versionExtract="' . $this->versionExtract . '"' .
|
||||
', flag={' . $this->flag . ' => ' . $this->flagToString() . '}' .
|
||||
', compressionMethod={' . $this->compressionMethod . ' => ' . $this->compressionMethodToString() . '}' .
|
||||
', lastModify=' . date("Y-m-d H:i:s", $this->lastModDateTime) .
|
||||
', crc32=0x' . dechex($this->crc32) .
|
||||
', compressedSize=' . ZipUtils::humanSize($this->compressedSize) .
|
||||
', unCompressedSize=' . ZipUtils::humanSize($this->unCompressedSize) .
|
||||
', diskNumber=' . $this->diskNumber .
|
||||
', internalAttributes=' . $this->internalAttributes .
|
||||
', externalAttributes=' . $this->externalAttributes .
|
||||
', offsetOfLocal=' . $this->offsetOfLocal .
|
||||
', offsetOfCentral=' . $this->offsetOfCentral .
|
||||
', extraCentral="' . $this->extraCentral . '"' .
|
||||
', extraLocal="' . $this->extraLocal . '"' .
|
||||
', comment="' . $this->comment . '"' .
|
||||
'}';
|
||||
}
|
||||
}
|
7
src/ZipException.php
Normal file
7
src/ZipException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
|
||||
}
|
1374
src/ZipFile.php
Normal file
1374
src/ZipFile.php
Normal file
File diff suppressed because it is too large
Load Diff
104
src/ZipUtils.php
Normal file
104
src/ZipUtils.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\MathHelper;
|
||||
|
||||
class ZipUtils
|
||||
{
|
||||
const DECRYPT_HEADER_SIZE = 12;
|
||||
public static $CFH_SIGNATURE = array(0x50, 0x4b, 0x01, 0x02);
|
||||
public static $LFH_SIGNATURE = array(0x50, 0x4b, 0x03, 0x04);
|
||||
public static $ECD_SIGNATURE = array(0x50, 0x4b, 0x05, 0x06);
|
||||
public static $DD_SIGNATURE = array(0x50, 0x4b, 0x07, 0x08);
|
||||
|
||||
private static $CRC_TABLE = array(
|
||||
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,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param int $charAt
|
||||
* @param int[] $keys
|
||||
* @return array
|
||||
*/
|
||||
public static function updateKeys($charAt, array $keys)
|
||||
{
|
||||
$keys[0] = self::crc32($keys[0], $charAt);
|
||||
$keys[1] = MathHelper::add($keys[1], MathHelper::bitwiseAnd($keys[0], 0xff));
|
||||
$keys[1] = MathHelper::castToInt(MathHelper::add(MathHelper::mul($keys[1], 134775813), 1));
|
||||
$keys[2] = self::crc32($keys[2], MathHelper::rightShift32($keys[1], 24));
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alg: ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff])
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param int $charAt
|
||||
* @return int|string
|
||||
*/
|
||||
public static function crc32($oldCrc, $charAt)
|
||||
{
|
||||
return MathHelper::castToInt(MathHelper::bitwiseXor(MathHelper::unsignedRightShift32($oldCrc, 8), self::$CRC_TABLE[MathHelper::bitwiseAnd(MathHelper::bitwiseXor($oldCrc, $charAt), 0xff)]));
|
||||
}
|
||||
|
||||
public static function decryptByte($byte)
|
||||
{
|
||||
$temp = $byte | 2;
|
||||
return MathHelper::unsignedRightShift32($temp * ($temp ^ 1), 8);
|
||||
}
|
||||
|
||||
public static function humanSize($size, $unit = "")
|
||||
{
|
||||
if ((!$unit && $size >= 1 << 30) || $unit == "GB")
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if ((!$unit && $size >= 1 << 20) || $unit == "MB")
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
if ((!$unit && $size >= 1 << 10) || $unit == "KB")
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
return number_format($size) . " bytes";
|
||||
}
|
||||
|
||||
|
||||
}
|
244
tests/TestZipFile.php
Normal file
244
tests/TestZipFile.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
class TestZipFile extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$output = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create.zip';
|
||||
$extractOutputDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create';
|
||||
|
||||
$listFilename = 'files.txt';
|
||||
$listFileContent = implode(PHP_EOL, glob('*'));
|
||||
$dirName = 'src/';
|
||||
$archiveComment = 'Archive comment - 😀';
|
||||
$commentIndex0 = basename(__FILE__);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->create();
|
||||
$zip->addFile(__FILE__);
|
||||
$zip->addFromString($listFilename, $listFileContent);
|
||||
$zip->addEmptyDir($dirName);
|
||||
$zip->setArchiveComment($archiveComment);
|
||||
$zip->setCommentIndex(0, $commentIndex0);
|
||||
$zip->saveAs($output);
|
||||
$zip->close();
|
||||
|
||||
$this->assertTrue(file_exists($output));
|
||||
$this->assertCorrectZipArchive($output);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->open($output);
|
||||
$listFiles = $zip->getListFiles();
|
||||
|
||||
$this->assertEquals(sizeof($listFiles), 3);
|
||||
$filenameIndex0 = basename(__FILE__);
|
||||
$this->assertEquals($listFiles[0], $filenameIndex0);
|
||||
$this->assertEquals($listFiles[1], $listFilename);
|
||||
$this->assertEquals($listFiles[2], $dirName);
|
||||
|
||||
$this->assertEquals($zip->getFromIndex(0), $zip->getFromName(basename(__FILE__)));
|
||||
$this->assertEquals($zip->getFromIndex(0), file_get_contents(__FILE__));
|
||||
$this->assertEquals($zip->getFromIndex(1), $zip->getFromName($listFilename));
|
||||
$this->assertEquals($zip->getFromIndex(1), $listFileContent);
|
||||
|
||||
$this->assertEquals($zip->getArchiveComment(), $archiveComment);
|
||||
$this->assertEquals($zip->getCommentIndex(0), $commentIndex0);
|
||||
|
||||
if (!file_exists($extractOutputDir)) {
|
||||
$this->assertTrue(mkdir($extractOutputDir, 0755, true));
|
||||
}
|
||||
|
||||
$zip->extractTo($extractOutputDir);
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0));
|
||||
$this->assertEquals(md5_file($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0), md5_file(__FILE__));
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename));
|
||||
$this->assertEquals(file_get_contents($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename), $listFileContent);
|
||||
|
||||
$zip->close();
|
||||
|
||||
unlink($output);
|
||||
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($extractOutputDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileInfo) {
|
||||
$todo = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$todo($fileInfo->getRealPath());
|
||||
}
|
||||
|
||||
rmdir($extractOutputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function testUpdate()
|
||||
{
|
||||
$file = __DIR__ . '/res/file.apk';
|
||||
$privateKey = __DIR__ . '/res/private.pem';
|
||||
$publicKey = __DIR__ . '/res/public.pem';
|
||||
$outputFile = sys_get_temp_dir() . '/test-update.apk';
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile($file);
|
||||
$zip->open($file);
|
||||
|
||||
// signed apk file
|
||||
$certList = array();
|
||||
$manifestMf = new Manifest();
|
||||
$manifestMf->appendLine("Manifest-Version: 1.0");
|
||||
$manifestMf->appendLine("Created-By: 1.0 (Android)");
|
||||
$manifestMf->appendLine('');
|
||||
for ($i = 0, $length = $zip->getCountFiles(); $i < $length; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if ($name[strlen($name) - 1] === '/') continue; // is path
|
||||
$content = $zip->getFromIndex($i);
|
||||
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($name, $content);
|
||||
$manifestMf->appendManifest($certManifest);
|
||||
$certList[$name] = $certManifest;
|
||||
}
|
||||
$manifestMf = $manifestMf->getContent();
|
||||
|
||||
$certSf = new Manifest();
|
||||
$certSf->appendLine('Signature-Version: 1.0');
|
||||
$certSf->appendLine('Created-By: 1.0 (Android)');
|
||||
$certSf->appendLine('SHA1-Digest-Manifest: ' . base64_encode(sha1($manifestMf, 1)));
|
||||
$certSf->appendLine('');
|
||||
foreach ($certList AS $filename => $content) {
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($filename, $content->getContent());
|
||||
$certSf->appendManifest($certManifest);
|
||||
}
|
||||
$certSf = $certSf->getContent();
|
||||
unset($certList);
|
||||
|
||||
$zip->addFromString('META-INF/MANIFEST.MF', $manifestMf);
|
||||
$zip->addFromString('META-INF/CERT.SF', $certSf);
|
||||
|
||||
if (`which openssl`) {
|
||||
$openssl_cmd = 'printf ' . escapeshellarg($certSf) . ' | openssl smime -md sha1 -sign -inkey ' . escapeshellarg($privateKey) . ' -signer ' . $publicKey . ' -binary -outform DER -noattr';
|
||||
|
||||
ob_start();
|
||||
passthru($openssl_cmd, $error);
|
||||
$rsaContent = ob_get_clean();
|
||||
$this->assertEquals($error, 0);
|
||||
|
||||
$zip->addFromString('META-INF/CERT.RSA', $rsaContent);
|
||||
}
|
||||
|
||||
$zip->saveAs($outputFile);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($outputFile);
|
||||
|
||||
if (`which jarsigner`) {
|
||||
ob_start();
|
||||
passthru('jarsigner -verify -verbose -certs ' . escapeshellarg($outputFile), $error);
|
||||
$verifedResult = ob_get_clean();
|
||||
|
||||
$this->assertEquals($error, 0);
|
||||
$this->assertContains('jar verified', $verifedResult);
|
||||
}
|
||||
|
||||
unlink($outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $filename
|
||||
*/
|
||||
private function assertCorrectZipArchive($filename)
|
||||
{
|
||||
exec("zip -T " . escapeshellarg($filename), $output, $returnCode);
|
||||
$this->assertEquals($returnCode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $content
|
||||
* @return Manifest
|
||||
*/
|
||||
private function createSha1EncodeEntryManifest($filename, $content)
|
||||
{
|
||||
$manifest = new Manifest();
|
||||
$manifest->appendLine('Name: ' . $filename);
|
||||
$manifest->appendLine('SHA1-Digest: ' . base64_encode(sha1($content, 1)));
|
||||
return $manifest;
|
||||
}
|
||||
}
|
||||
|
||||
class Manifest
|
||||
{
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return trim($this->content) . "\r\n\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a long manifest line and add continuation if required
|
||||
* @param $line string
|
||||
* @return Manifest
|
||||
*/
|
||||
public function appendLine($line)
|
||||
{
|
||||
$begin = 0;
|
||||
$sb = '';
|
||||
$lineLength = mb_strlen($line, "UTF-8");
|
||||
for ($end = 70; $lineLength - $begin > 70; $end += 69) {
|
||||
$sb .= mb_substr($line, $begin, $end - $begin, "UTF-8") . "\r\n ";
|
||||
$begin = $end;
|
||||
}
|
||||
$this->content .= $sb . mb_substr($line, $begin, $lineLength, "UTF-8") . "\r\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function appendManifest(Manifest $manifest)
|
||||
{
|
||||
$this->content .= $manifest->getContent();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->content = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $manifestContent
|
||||
* @return Manifest
|
||||
*/
|
||||
public static function createFromManifest($manifestContent)
|
||||
{
|
||||
$manifestContent = trim($manifestContent);
|
||||
$lines = explode("\n", $manifestContent);
|
||||
|
||||
// normalize manifest
|
||||
$content = '';
|
||||
$trim = array("\r", "\n");
|
||||
foreach ($lines AS $line) {
|
||||
|
||||
$line = str_replace($trim, '', $line);
|
||||
if ($line[0] === ' ') {
|
||||
$content = rtrim($content, "\n\r");
|
||||
$line = ltrim($line);
|
||||
}
|
||||
$content .= $line . "\r\n";
|
||||
}
|
||||
|
||||
$manifset = new self;
|
||||
$lines = explode("\n", $content);
|
||||
foreach ($lines AS $line) {
|
||||
$line = trim($line, "\n\r");
|
||||
$manifset->appendLine($line);
|
||||
}
|
||||
return $manifset;
|
||||
}
|
||||
}
|
BIN
tests/res/file.apk
Normal file
BIN
tests/res/file.apk
Normal file
Binary file not shown.
28
tests/res/private.pem
Normal file
28
tests/res/private.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwiURw5w8TAS0G
|
||||
iWaBzJqbQyacFsIzKc+orHDzBdBdKlrx/aUlw3fBA310aP7343hc7vMm6koar/1n
|
||||
8iEh2W4retDHzNf9j3IXETa5/D+3WJY4aVJkYcFr8v6OEnuSXIwKZduMeL4BpQwM
|
||||
Xu/z6gkoTa9o+Dzhl46l71UX8umdPIx/4YWwX2oSm6EGklcGJyYdqMvwOXVXDE/J
|
||||
qqPzC7ZQiu422cDDvqBYt32CVyjLKCo5YjWBb24jxjtl5M4y8xPOcHlVEG2WwPBU
|
||||
sv2aw4Z9/Q0erZ35gMyzu+Y+2623B3tuAbfsswgBINq0bl2v+M17Kpv2tjhMKj1H
|
||||
ds6CK/BVAgMBAAECggEAdTUt86f1IjENq+Fd5Z/qplsXL1sM5NtFvD+BXljl1nVg
|
||||
nHpDQ6dbwxKGINv1LLAiIdGkLpovSTi/jlv8E3VA6C1KoN0oKnkqzpXnN+R6iUiP
|
||||
tDR5N5yPxxQ2Xi13Td2UPPMTqVghDwZ90VjXB6LDIbcyVwc5pK3zT8hvPs9Qu8t1
|
||||
S2pCEKcowvTRSB1DMTZ3lrNjEEIMdV0H8Qik3lf7ognRGoDywu5pA1bc/Yg+XlmP
|
||||
/ZmQinFeg3izNQzDdP6Ppo1i/QFeVXVuMs2ergMMHJRNUhBXKz8iNyVupqfroE8a
|
||||
xRpD3eO+KvSNb0TJR5TXf64t62zEEpHaRsmgACEMAQKBgQDXo0jVUa67oqfJBFBU
|
||||
3zfHIlgcrydRE4Av+LJ0hdEnFpMwVpUihJXUaJijawTzTKOgpVImhxfr/T1HMalm
|
||||
MTXH5Tc7inJTiB9A1IffLPqgoOr2JRwQ2q8lgWkQPkq1ySd+q0vhkj1tuAe3qI1i
|
||||
jiMo1Vb9zdVjcxmvPnZRKJgiIQKBgQDRlFm6PKc2Zx46BXeNPtXnHhSduUBJf2iO
|
||||
n9/pKTANQuDlPwC3Q4edSKe44fZ/oj4KRAnzX254wXBMX+ktKX/kqXbwEanxcd/v
|
||||
Lnvgv8QhsEKO3Ye09yasAfC2lYsSVSwHv+dYurb0nZ2JEPL1IP+V76RgTbdeMdic
|
||||
Mt53jN/vtQKBgQC+D+mOO+Sq9X61qtuzMtvS5O6MucUJrQp7PdTs51WmAjvRiz7/
|
||||
oaT+BwMiZp2CZLaETbLOypvHIPn12kvZCt7ARcQc8rY58ey6E5l+mAJ/udXfBm5q
|
||||
XJWrlRipfH4VJCtvdkP3mhIStvX2ZtXXXDiZMRDvu5CtizHESGW4uvL8gQKBgCI7
|
||||
ukBagfG3/E7776BJwETlO/bbeK3Iuvp5EOkUCj5QS04G8YX96Nv/Ly5a8pm8lae1
|
||||
n256ix/8cOx4yizPV42xRLVIHVtL/4khLajzigT6tpSBiRY9PLriAkDAwpu2/98w
|
||||
MIjkzte8Gyx1cUorHrSOFWqJp0cim0BAauhaQYX1AoGAPvb5TG/A6LhYKgCWUMOH
|
||||
lucrnV3Ns1BzaaMmOaokuYGtyTv2loVlrv+7QGdC9UBRXDz46DTE7CPHBwReNnWB
|
||||
R7YW50VwB9YfD7dqRM24Y08F3B7RCNhqsAnpAtVgXf+/01o2nfJbzxTty/STiBNB
|
||||
OjjxKHnAgIfhe7xAIiY2eow=
|
||||
-----END PRIVATE KEY-----
|
21
tests/res/public.pem
Normal file
21
tests/res/public.pem
Normal file
@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDeTCCAmGgAwIBAgIEDeVWNjANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJT
|
||||
QTEPMA0GA1UECBMGUml5YWRoMQ8wDQYDVQQHEwZSaXlhZGgxEDAOBgNVBAoTB1lv
|
||||
dXR5cGUxEDAOBgNVBAsTB1lvdXR5cGUxGDAWBgNVBAMTD0x1Y2llbm5lIEFuc2Vs
|
||||
YTAeFw0xNjA5MDgxNDM2MjJaFw00NDAxMjUxNDM2MjJaMG0xCzAJBgNVBAYTAlNB
|
||||
MQ8wDQYDVQQIEwZSaXlhZGgxDzANBgNVBAcTBlJpeWFkaDEQMA4GA1UEChMHWW91
|
||||
dHlwZTEQMA4GA1UECxMHWW91dHlwZTEYMBYGA1UEAxMPTHVjaWVubmUgQW5zZWxh
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsIlEcOcPEwEtBolmgcya
|
||||
m0MmnBbCMynPqKxw8wXQXSpa8f2lJcN3wQN9dGj+9+N4XO7zJupKGq/9Z/IhIdlu
|
||||
K3rQx8zX/Y9yFxE2ufw/t1iWOGlSZGHBa/L+jhJ7klyMCmXbjHi+AaUMDF7v8+oJ
|
||||
KE2vaPg84ZeOpe9VF/LpnTyMf+GFsF9qEpuhBpJXBicmHajL8Dl1VwxPyaqj8wu2
|
||||
UIruNtnAw76gWLd9glcoyygqOWI1gW9uI8Y7ZeTOMvMTznB5VRBtlsDwVLL9msOG
|
||||
ff0NHq2d+YDMs7vmPtuttwd7bgG37LMIASDatG5dr/jNeyqb9rY4TCo9R3bOgivw
|
||||
VQIDAQABoyEwHzAdBgNVHQ4EFgQUEPoIQyYzpjseEK7hqm6UALvjJj8wDQYJKoZI
|
||||
hvcNAQEFBQADggEBAD/C/48B4MvF2WzhMtLIAWuhtp73xBy6GCQBKT1dn9dgtXfD
|
||||
LuHAvkx28CoOTso4Ia+JhWuu7jGfYdtL00ezV8d8Ma1k/SJfWyHpgDDk1MEhvn+h
|
||||
tOoUQpt0S+QhKFxDm+INv2zw/P/TDIIodHQqkX+YVSLQMhUGRTq3vhDnfJqedAUr
|
||||
QIhZjCZx9VjjiM4yhcabKEHpxqLQOcoeHB8zchnP1j/N+QSIW6hICqjcPLPLzpPu
|
||||
M0RmEuRYz3EJ2P3jINhaCLFRLHTnoN2lVDS32v5Cr+IC7A1hPUcHG+07junRMEiG
|
||||
uTYj9+UYI6phGJBABfFp7/oxs080RXCrKUhR+Go=
|
||||
-----END CERTIFICATE-----
|
Loading…
x
Reference in New Issue
Block a user