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:
1049
tests/PhpZip/ZipTest.php
Normal file
1049
tests/PhpZip/ZipTest.php
Normal file
File diff suppressed because it is too large
Load Diff
45
tests/PhpZip/ZipTestCase.php
Normal file
45
tests/PhpZip/ZipTestCase.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@@ -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.
@@ -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-----
|
@@ -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-----
|
Reference in New Issue
Block a user