1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-07-30 20:20:11 +02:00

Completely rewritten code.

Implement read-only zip file - class \PhpZip\ZipFile, create and update zip file - \PhpZip\ZipOutputFile.
Supports ZIP64 ext, Traditional PKWARE Encryption, WinZip AES Encryption.
This commit is contained in:
Ne-Lexa
2016-09-26 12:04:38 +03:00
parent 5c23e588ff
commit 560a94c910
48 changed files with 7785 additions and 2905 deletions

1049
tests/PhpZip/ZipTest.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
<?php
namespace PhpZip;
/**
* PHPUnit test case and helper methods.
*/
class ZipTestCase extends \PHPUnit_Framework_TestCase
{
/**
* Assert correct zip archive.
*
* @param $filename
*/
public static function assertCorrectZipArchive($filename)
{
if (DIRECTORY_SEPARATOR !== '\\' && `which zip`) {
exec("zip -T " . escapeshellarg($filename), $output, $returnCode);
$output = implode(PHP_EOL, $output);
self::assertEquals($returnCode, 0);
self::assertNotContains('zip error', $output);
self::assertContains(' OK', $output);
}
}
/**
* Assert correct empty zip archive.
*
* @param $filename
*/
public static function assertCorrectEmptyZip($filename)
{
if (DIRECTORY_SEPARATOR !== '\\' && `which zipinfo`) {
exec("zipinfo " . escapeshellarg($filename), $output, $returnCode);
$output = implode(PHP_EOL, $output);
self::assertContains('Empty zipfile', $output);
}
$actualEmptyZipData = pack('VVVVVv', ZipConstants::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
self::assertEquals(file_get_contents($filename), $actualEmptyZipData);
}
}

View File

@@ -1,244 +0,0 @@
<?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;
}
}

Binary file not shown.

View File

@@ -1,28 +0,0 @@
-----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-----

View File

@@ -1,21 +0,0 @@
-----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-----