ZipPasswordTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <?php
  2. namespace PhpZip\Tests;
  3. use PhpZip\Constants\ZipCompressionMethod;
  4. use PhpZip\Constants\ZipEncryptionMethod;
  5. use PhpZip\Exception\InvalidArgumentException;
  6. use PhpZip\Exception\RuntimeException;
  7. use PhpZip\Exception\ZipAuthenticationException;
  8. use PhpZip\Exception\ZipEntryNotFoundException;
  9. use PhpZip\Exception\ZipException;
  10. use PhpZip\Model\ZipInfo;
  11. use PhpZip\ZipFile;
  12. /**
  13. * Tests with zip password.
  14. *
  15. * @internal
  16. *
  17. * @small
  18. */
  19. class ZipPasswordTest extends ZipFileSetTestCase
  20. {
  21. /**
  22. * Test archive password.
  23. *
  24. * @throws ZipException
  25. * @throws \Exception
  26. * @noinspection PhpRedundantCatchClauseInspection
  27. */
  28. public function testSetPassword()
  29. {
  30. if (\PHP_INT_SIZE === 4) { // php 32 bit
  31. $this->setExpectedException(
  32. RuntimeException::class,
  33. 'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
  34. );
  35. }
  36. $password = base64_encode(random_bytes(100));
  37. $badPassword = 'bad password';
  38. // create encryption password with Traditional PKWARE encryption
  39. $zipFile = new ZipFile();
  40. $zipFile->addDir(__DIR__);
  41. $zipFile->setPassword($password, ZipEncryptionMethod::PKWARE);
  42. $zipFile->saveAsFile($this->outputFilename);
  43. $zipFile->close();
  44. static::assertCorrectZipArchive($this->outputFilename, $password);
  45. $zipFile->openFile($this->outputFilename);
  46. // check bad password for Traditional PKWARE encryption
  47. $zipFile->setReadPassword($badPassword);
  48. foreach ($zipFile->getListFiles() as $entryName) {
  49. try {
  50. $zipFile[$entryName];
  51. static::fail('Expected Exception has not been raised.');
  52. } catch (ZipException $e) {
  53. }
  54. }
  55. // check correct password for Traditional PKWARE encryption
  56. $zipFile->setReadPassword($password);
  57. foreach ($zipFile->getAllInfo() as $info) {
  58. static::assertTrue($info->isEncrypted());
  59. static::assertContains('Traditional PKWARE encryption', $info->getEncryptionMethodName());
  60. $decryptContent = $zipFile[$info->getName()];
  61. static::assertNotEmpty($decryptContent);
  62. static::assertContains('<?php', $decryptContent);
  63. }
  64. // change encryption method to WinZip Aes and update file
  65. $zipFile->setPassword($password, ZipEncryptionMethod::WINZIP_AES_256);
  66. $zipFile->saveAsFile($this->outputFilename);
  67. $zipFile->close();
  68. static::assertCorrectZipArchive($this->outputFilename, $password);
  69. // check from WinZip AES encryption
  70. $zipFile->openFile($this->outputFilename);
  71. // set bad password WinZip AES
  72. $zipFile->setReadPassword($badPassword);
  73. foreach ($zipFile->getListFiles() as $entryName) {
  74. try {
  75. $zipFile[$entryName];
  76. static::fail('Expected Exception has not been raised.');
  77. } catch (ZipAuthenticationException $ae) {
  78. static::assertNotNull($ae);
  79. }
  80. }
  81. // set correct password WinZip AES
  82. $zipFile->setReadPassword($password);
  83. foreach ($zipFile->getAllInfo() as $info) {
  84. static::assertTrue($info->isEncrypted());
  85. static::assertContains('Deflated', $info->getMethodName());
  86. static::assertContains('WinZip AES-256', $info->getEncryptionMethodName());
  87. $decryptContent = $zipFile[$info->getName()];
  88. static::assertNotEmpty($decryptContent);
  89. static::assertContains('<?php', $decryptContent);
  90. }
  91. // clear password
  92. $zipFile->addFromString('file1', '');
  93. $zipFile->disableEncryption();
  94. $zipFile->addFromString('file2', '');
  95. $zipFile->saveAsFile($this->outputFilename);
  96. $zipFile->close();
  97. static::assertCorrectZipArchive($this->outputFilename);
  98. // check remove password
  99. $zipFile->openFile($this->outputFilename);
  100. foreach ($zipFile->getAllInfo() as $info) {
  101. static::assertFalse($info->isEncrypted());
  102. }
  103. $zipFile->close();
  104. }
  105. /**
  106. * @throws ZipException
  107. * @throws \Exception
  108. */
  109. public function testTraditionalEncryption()
  110. {
  111. if (\PHP_INT_SIZE === 4) { // php 32 bit
  112. $this->setExpectedException(
  113. RuntimeException::class,
  114. 'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
  115. );
  116. }
  117. $password = base64_encode(random_bytes(50));
  118. $zip = new ZipFile();
  119. $zip->addDirRecursive($this->outputDirname);
  120. $zip->setPassword($password, ZipEncryptionMethod::PKWARE);
  121. $zip->saveAsFile($this->outputFilename);
  122. $zip->close();
  123. static::assertCorrectZipArchive($this->outputFilename, $password);
  124. $zip->openFile($this->outputFilename);
  125. $zip->setReadPassword($password);
  126. static::assertFilesResult($zip, array_keys(self::$files));
  127. foreach ($zip->getAllInfo() as $info) {
  128. if (!$info->isFolder()) {
  129. static::assertTrue($info->isEncrypted());
  130. static::assertContains('Traditional PKWARE encryption', $info->getEncryptionMethodName());
  131. }
  132. }
  133. $zip->close();
  134. }
  135. /**
  136. * @dataProvider winZipKeyStrengthProvider
  137. *
  138. * @param int $encryptionMethod
  139. * @param int $bitSize
  140. *
  141. * @throws ZipException
  142. * @throws \Exception
  143. */
  144. public function testWinZipAesEncryption($encryptionMethod, $bitSize)
  145. {
  146. $password = base64_encode(random_bytes(50));
  147. $zip = new ZipFile();
  148. $zip->addDirRecursive($this->outputDirname);
  149. $zip->setPassword($password, $encryptionMethod);
  150. $zip->saveAsFile($this->outputFilename);
  151. $zip->close();
  152. static::assertCorrectZipArchive($this->outputFilename, $password);
  153. $zip->openFile($this->outputFilename);
  154. $zip->setReadPassword($password);
  155. static::assertFilesResult($zip, array_keys(self::$files));
  156. foreach ($zip->getAllInfo() as $info) {
  157. if (!$info->isFolder()) {
  158. static::assertTrue($info->isEncrypted());
  159. static::assertSame($info->getEncryptionMethod(), $encryptionMethod);
  160. static::assertContains('WinZip AES-' . $bitSize, $info->getEncryptionMethodName());
  161. }
  162. }
  163. $zip->close();
  164. }
  165. /**
  166. * @return array
  167. */
  168. public function winZipKeyStrengthProvider()
  169. {
  170. return [
  171. [ZipEncryptionMethod::WINZIP_AES_128, 128],
  172. [ZipEncryptionMethod::WINZIP_AES_192, 192],
  173. [ZipEncryptionMethod::WINZIP_AES_256, 256],
  174. ];
  175. }
  176. /**
  177. * @throws ZipEntryNotFoundException
  178. * @throws ZipException
  179. */
  180. public function testEncryptionEntries()
  181. {
  182. if (\PHP_INT_SIZE === 4) { // php 32 bit
  183. $this->setExpectedException(
  184. RuntimeException::class,
  185. 'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
  186. );
  187. }
  188. $password1 = '353442434235424234';
  189. $password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
  190. $zip = new ZipFile();
  191. $zip->addDir($this->outputDirname);
  192. $zip->setPasswordEntry('.hidden', $password1, ZipEncryptionMethod::PKWARE);
  193. $zip->setPasswordEntry('text file.txt', $password2, ZipEncryptionMethod::WINZIP_AES_256);
  194. $zip->saveAsFile($this->outputFilename);
  195. $zip->close();
  196. $zip->openFile($this->outputFilename);
  197. $zip->setReadPasswordEntry('.hidden', $password1);
  198. $zip->setReadPasswordEntry('text file.txt', $password2);
  199. static::assertFilesResult(
  200. $zip,
  201. [
  202. '.hidden',
  203. 'text file.txt',
  204. 'Текстовый документ.txt',
  205. 'empty dir/',
  206. 'LoremIpsum.txt',
  207. ]
  208. );
  209. $info = $zip->getEntryInfo('.hidden');
  210. static::assertTrue($info->isEncrypted());
  211. static::assertContains('Traditional PKWARE encryption', $info->getEncryptionMethodName());
  212. $info = $zip->getEntryInfo('text file.txt');
  213. static::assertTrue($info->isEncrypted());
  214. static::assertContains('WinZip AES', $info->getEncryptionMethodName());
  215. static::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
  216. static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
  217. $zip->close();
  218. }
  219. /**
  220. * @throws ZipEntryNotFoundException
  221. * @throws ZipException
  222. */
  223. public function testEncryptionEntriesWithDefaultPassword()
  224. {
  225. if (\PHP_INT_SIZE === 4) { // php 32 bit
  226. $this->setExpectedException(
  227. RuntimeException::class,
  228. 'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
  229. );
  230. }
  231. $password1 = '353442434235424234';
  232. $password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
  233. $defaultPassword = ' f f f f f ffff f5 ';
  234. $zip = new ZipFile();
  235. $zip->addDir($this->outputDirname);
  236. $zip->setPassword($defaultPassword);
  237. $zip->setPasswordEntry('.hidden', $password1, ZipEncryptionMethod::PKWARE);
  238. $zip->setPasswordEntry('text file.txt', $password2, ZipEncryptionMethod::WINZIP_AES_256);
  239. $zip->saveAsFile($this->outputFilename);
  240. $zip->close();
  241. $zip->openFile($this->outputFilename);
  242. $zip->setReadPassword($defaultPassword);
  243. $zip->setReadPasswordEntry('.hidden', $password1);
  244. $zip->setReadPasswordEntry('text file.txt', $password2);
  245. static::assertFilesResult(
  246. $zip,
  247. [
  248. '.hidden',
  249. 'text file.txt',
  250. 'Текстовый документ.txt',
  251. 'empty dir/',
  252. 'LoremIpsum.txt',
  253. ]
  254. );
  255. $info = $zip->getEntryInfo('.hidden');
  256. static::assertTrue($info->isEncrypted());
  257. static::assertContains('Traditional PKWARE encryption', $info->getEncryptionMethodName());
  258. $info = $zip->getEntryInfo('text file.txt');
  259. static::assertTrue($info->isEncrypted());
  260. static::assertContains('WinZip AES', $info->getEncryptionMethodName());
  261. $info = $zip->getEntryInfo('Текстовый документ.txt');
  262. static::assertTrue($info->isEncrypted());
  263. static::assertContains('WinZip AES', $info->getEncryptionMethodName());
  264. static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
  265. $zip->close();
  266. }
  267. /**
  268. * @throws ZipException
  269. */
  270. public function testSetEncryptionMethodInvalid()
  271. {
  272. $this->setExpectedException(InvalidArgumentException::class, 'Encryption method 9999 is not supported.');
  273. $zipFile = new ZipFile();
  274. $encryptionMethod = 9999;
  275. $zipFile['entry'] = 'content';
  276. $zipFile->setPassword('pass', $encryptionMethod);
  277. $zipFile->outputAsString();
  278. }
  279. /**
  280. * @throws ZipEntryNotFoundException
  281. * @throws ZipException
  282. */
  283. public function testEntryPassword()
  284. {
  285. $zipFile = new ZipFile();
  286. $zipFile->setPassword('pass');
  287. $zipFile['file'] = 'content';
  288. static::assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
  289. for ($i = 1; $i <= 10; $i++) {
  290. $zipFile['file' . $i] = 'content';
  291. if ($i < 6) {
  292. $zipFile->setPasswordEntry('file' . $i, 'pass');
  293. static::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
  294. } else {
  295. static::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
  296. }
  297. }
  298. $zipFile->disableEncryptionEntry('file3');
  299. static::assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
  300. static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
  301. $zipFile->disableEncryption();
  302. $infoList = $zipFile->getAllInfo();
  303. array_walk(
  304. $infoList,
  305. function (ZipInfo $zipInfo) {
  306. $this->assertFalse($zipInfo->isEncrypted());
  307. }
  308. );
  309. $zipFile->close();
  310. }
  311. /**
  312. * @throws ZipException
  313. */
  314. public function testInvalidEncryptionMethodEntry()
  315. {
  316. $this->setExpectedException(InvalidArgumentException::class, 'Encryption method 99 is not supported.');
  317. $zipFile = new ZipFile();
  318. $zipFile->addFromString('file', 'content', ZipCompressionMethod::STORED);
  319. $zipFile->setPasswordEntry('file', 'pass', ZipCompressionMethod::WINZIP_AES);
  320. }
  321. /**
  322. * @throws ZipEntryNotFoundException
  323. * @throws ZipException
  324. */
  325. public function testArchivePasswordUpdateWithoutSetReadPassword()
  326. {
  327. $zipFile = new ZipFile();
  328. $zipFile['file1'] = 'content';
  329. $zipFile['file2'] = 'content';
  330. $zipFile['file3'] = 'content';
  331. $zipFile->setPassword('password');
  332. $zipFile->saveAsFile($this->outputFilename);
  333. $zipFile->close();
  334. static::assertCorrectZipArchive($this->outputFilename, 'password');
  335. $zipFile->openFile($this->outputFilename);
  336. static::assertCount(3, $zipFile);
  337. foreach ($zipFile->getAllInfo() as $info) {
  338. static::assertTrue($info->isEncrypted());
  339. }
  340. unset($zipFile['file3']);
  341. $zipFile['file4'] = 'content';
  342. $zipFile->rewrite();
  343. static::assertCorrectZipArchive($this->outputFilename, 'password');
  344. static::assertCount(3, $zipFile);
  345. static::assertFalse(isset($zipFile['file3']));
  346. static::assertTrue(isset($zipFile['file4']));
  347. static::assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
  348. static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
  349. static::assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
  350. static::assertSame($zipFile['file4'], 'content');
  351. $zipFile->extractTo($this->outputDirname, ['file4']);
  352. static::assertFileExists($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4');
  353. static::assertStringEqualsFile($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4', $zipFile['file4']);
  354. $zipFile->close();
  355. }
  356. /**
  357. * @see https://github.com/Ne-Lexa/php-zip/issues/9
  358. *
  359. * @throws ZipException
  360. * @throws \Exception
  361. */
  362. public function testIssues9()
  363. {
  364. $contents = str_pad('', 1000, 'test;test2;test3' . \PHP_EOL, \STR_PAD_RIGHT);
  365. $password = base64_encode(random_bytes(20));
  366. $zipFile = new ZipFile();
  367. $zipFile
  368. ->addFromString('codes.csv', $contents, ZipCompressionMethod::DEFLATED)
  369. ->setPassword($password, ZipEncryptionMethod::WINZIP_AES_256)
  370. ->saveAsFile($this->outputFilename)
  371. ->close()
  372. ;
  373. static::assertCorrectZipArchive($this->outputFilename, $password);
  374. $zipFile->openFile($this->outputFilename);
  375. $zipFile->setReadPassword($password);
  376. static::assertSame($zipFile['codes.csv'], $contents);
  377. $zipFile->close();
  378. }
  379. /**
  380. * @throws ZipEntryNotFoundException
  381. * @throws ZipException
  382. */
  383. public function testReadAesEncryptedAndRewriteArchive()
  384. {
  385. $file = __DIR__ . '/resources/aes_password_archive.zip';
  386. $password = '1234567890';
  387. $zipFile = new ZipFile();
  388. $zipFile->openFile($file);
  389. $zipFile->setReadPassword($password);
  390. $zipFile->setPassword($password);
  391. $zipFile->setEntryComment('contents.txt', 'comment'); // change entry, but not changed contents
  392. $zipFile->saveAsFile($this->outputFilename);
  393. $zipFile2 = new ZipFile();
  394. $zipFile2->openFile($this->outputFilename);
  395. $zipFile2->setReadPassword($password);
  396. static::assertSame($zipFile2->getListFiles(), $zipFile->getListFiles());
  397. foreach ($zipFile as $name => $contents) {
  398. static::assertNotEmpty($name);
  399. static::assertNotEmpty($contents);
  400. static::assertContains('test contents', $contents);
  401. static::assertSame($zipFile2[$name], $contents);
  402. }
  403. $zipFile2->close();
  404. $zipFile->close();
  405. }
  406. }