Skip to content

Commit 9074d86

Browse files
committed
Support listing entries without password
1 parent b129f6d commit 9074d86

File tree

3 files changed

+74
-54
lines changed

3 files changed

+74
-54
lines changed

src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -223,53 +223,55 @@ public function currentEntry() {
223223

224224
// AES vs. traditional PKZIP cipher
225225
if (99 === $header['compression']) {
226-
if (null === $this->password) {
227-
throw new IllegalAccessException('No password set');
228-
}
229-
226+
$stream= new ZipFileInputStream($this, $this->position, $header['compressed']);
230227
$aes= unpack('vheader/vsize/vversion/a2vendor/cstrength/vcompression', $extra);
231-
switch ($aes['strength']) {
232-
case 1: $sl= 8; $dl= 16; break;
233-
case 2: $sl= 12; $dl= 24; break;
234-
case 3: $sl= 16; $dl= 32; break;
235-
default: throw new IllegalArgumentException('Invalid AES strength '.$aes['strength']);
236-
}
228+
$header['compression']= $aes['compression'];
229+
$is= function() use($header, $stream, $aes) {
230+
if (null === $this->password) {
231+
throw new IllegalAccessException('No password set');
232+
}
237233

238-
// Verify password
239-
$salt= $this->streamRead($sl);
240-
$pvv= $this->streamRead(2);
241-
$dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true);
242-
if (0 !== substr_compare($dk, $pvv, 2 * $dl, 2)) {
243-
throw new IllegalAccessException('The password did not match');
244-
}
234+
switch ($aes['strength']) {
235+
case 1: $sl= 8; $dl= 16; break;
236+
case 2: $sl= 12; $dl= 24; break;
237+
case 3: $sl= 16; $dl= 32; break;
238+
default: throw new IllegalArgumentException('Invalid AES strength '.$aes['strength']);
239+
}
245240

246-
$this->skip-= $sl + 2;
247-
$header['compression']= $aes['compression'];
248-
$is= new AESInputStream(
249-
new ZipFileInputStream($this, $this->position, $header['compressed'] - $sl - 2),
250-
substr($dk, 0, $dl),
251-
substr($dk, $dl, $dl)
252-
);
241+
// Verify password
242+
$salt= $stream->read($sl);
243+
$pvv= $stream->read(2);
244+
$dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true);
245+
if (0 !== substr_compare($dk, $pvv, 2 * $dl, 2)) {
246+
throw new IllegalAccessException('The password did not match');
247+
}
248+
249+
return new AESInputStream(
250+
$stream,
251+
substr($dk, 0, $dl),
252+
substr($dk, $dl, $dl)
253+
);
254+
};
253255
} else if ($header['flags'] & 1) {
254-
if (null === $this->password) {
255-
throw new IllegalAccessException('No password set');
256-
}
256+
$stream= new ZipFileInputStream($this, $this->position, $header['compressed']);
257+
$is= function() use($header, $stream) {
258+
if (null === $this->password) {
259+
throw new IllegalAccessException('No password set');
260+
}
257261

258-
// Verify password
259-
$cipher= new ZipCipher();
260-
$cipher->initialize(iconv(\xp::ENCODING, 'cp437', $this->password->reveal()));
261-
$preamble= $cipher->decipher($this->streamRead(12));
262-
if (ord($preamble[11]) !== (($header['crc'] >> 24) & 0xff)) {
263-
throw new IllegalAccessException('The password did not match');
264-
}
265-
266-
$this->skip-= 12;
267-
$is= new DecipheringInputStream(
268-
new ZipFileInputStream($this, $this->position, $header['compressed'] - 12),
269-
$cipher
270-
);
262+
// Verify password
263+
$cipher= new ZipCipher();
264+
$cipher->initialize(iconv(\xp::ENCODING, 'cp437', $this->password->reveal()));
265+
$preamble= $cipher->decipher($stream->read(12));
266+
if (ord($preamble[11]) !== (($header['crc'] >> 24) & 0xff)) {
267+
throw new IllegalAccessException('The password did not match');
268+
}
269+
270+
return new DecipheringInputStream($stream, $cipher);
271+
};
271272
} else {
272-
$is= new ZipFileInputStream($this, $this->position, $header['compressed']);
273+
$stream= new ZipFileInputStream($this, $this->position, $header['compressed']);
274+
$is= function() use($stream) { return $stream; };
273275
}
274276

275277
// Create ZipEntry object and return it

src/main/php/io/archive/zip/ZipFileEntry.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public function isDirectory() {
117117
* @return io.streams.InputStream
118118
*/
119119
public function in() {
120-
return $this->compression[0]->getDecompressionStream($this->is);
120+
return $this->compression[0]->getDecompressionStream(($this->is)());
121121
}
122122

123123
/**

src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,45 @@ public function ppmd() {
6060

6161
#[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])]
6262
public function missing_password($fixture) {
63-
$this->archiveReaderFor($this->vendor(), $fixture)->iterator()->next();
63+
$this->archiveReaderFor($this->vendor(), $fixture)
64+
->iterator()
65+
->next()
66+
->in()
67+
;
6468
}
6569

6670
#[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])]
6771
public function incorrect_password($fixture) {
68-
$this->archiveReaderFor($this->vendor(), $fixture)->usingPassword('wrong')->iterator()->next();
72+
$this->archiveReaderFor($this->vendor(), $fixture)
73+
->usingPassword('wrong')
74+
->iterator()
75+
->next()
76+
->in()
77+
;
6978
}
7079

7180
#[Test, Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])]
72-
public function password_protected($fixture) {
81+
public function password_not_needed_for_listing($fixture) {
7382
$reader= $this->archiveReaderFor($this->vendor(), $fixture);
74-
with ($it= $reader->usingPassword('secret')->iterator()); {
75-
$entry= $it->next();
76-
Assert::equals('password.txt', $entry->getName());
77-
Assert::equals(15, $entry->getSize());
78-
Assert::equals('Secret contents', (string)Streams::readAll($entry->in()));
83+
$sizes= [];
84+
foreach ($reader->entries() as $entry) {
85+
$sizes[$entry->getName()]= $entry->getSize();
86+
}
87+
88+
Assert::equals(['password.txt' => 15, 'very.txt' => 20], $sizes);
89+
}
7990

80-
$entry= $it->next();
81-
Assert::equals('very.txt', $entry->getName());
82-
Assert::equals(20, $entry->getSize());
83-
Assert::equals('Very secret contents', (string)Streams::readAll($entry->in()));
91+
#[Test, Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])]
92+
public function password_protected($fixture) {
93+
$reader= $this->archiveReaderFor($this->vendor(), $fixture)->usingPassword('secret');
94+
$contents= [];
95+
foreach ($reader->entries() as $entry) {
96+
$contents[$entry->getName()]= Streams::readAll($entry->in());
8497
}
98+
99+
Assert::equals(
100+
['password.txt' => 'Secret contents', 'very.txt' => 'Very secret contents'],
101+
$contents
102+
);
85103
}
86104
}

0 commit comments

Comments
 (0)