| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- <?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;
- }
- }
|