Skip to content

Commit d62b611

Browse files
committed
Add withPasswordCallback
1 parent abf95a0 commit d62b611

5 files changed

Lines changed: 80 additions & 0 deletions

File tree

libarchive.stub.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,22 @@ public function __construct(string $file, int $flags = 0) {}
363363
*/
364364
public static function fromStream(mixed $stream, int $flags = 0): static {}
365365

366+
/**
367+
* Set a PHP callable that will be invoked by libarchive whenever a
368+
* passphrase is needed to decrypt an entry.
369+
*
370+
* The callable receives no arguments and must return either a string
371+
* (the passphrase) or null to signal that no passphrase is available.
372+
* It may be called multiple times if several passphrases need to be
373+
* tried.
374+
*
375+
* Must be called before iteration begins.
376+
*
377+
* @param callable $callable Callback with signature {@code function(): ?string}.
378+
* @throws Exception If the archive has already been opened.
379+
*/
380+
public function withPasswordCallback(callable $callable): static {}
381+
366382
/**
367383
* Restrict reading to specific archive formats.
368384
*

libarchive_arginfo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_libarchive_Archive_fromStr
1414
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0")
1515
ZEND_END_ARG_INFO()
1616

17+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_libarchive_Archive_withPasswordCallback, 0, 1, IS_STATIC, 0)
18+
ZEND_ARG_TYPE_INFO(0, callable, IS_CALLABLE, 0)
19+
ZEND_END_ARG_INFO()
20+
1721
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_libarchive_Archive_supportFormats, 0, 1, IS_STATIC, 0)
1822
ZEND_ARG_VARIADIC_TYPE_INFO(0, formats, IS_LONG, 0)
1923
ZEND_END_ARG_INFO()
@@ -36,6 +40,7 @@ ZEND_END_ARG_INFO()
3640
ZEND_METHOD(libarchive_Entry, __construct);
3741
ZEND_METHOD(libarchive_Archive, __construct);
3842
ZEND_METHOD(libarchive_Archive, fromStream);
43+
ZEND_METHOD(libarchive_Archive, withPasswordCallback);
3944
ZEND_METHOD(libarchive_Archive, supportFormats);
4045
ZEND_METHOD(libarchive_Archive, supportFilters);
4146
ZEND_METHOD(libarchive_Archive, extractCurrent);
@@ -57,6 +62,7 @@ static const zend_function_entry class_libarchive_Entry_methods[] = {
5762
static const zend_function_entry class_libarchive_Archive_methods[] = {
5863
ZEND_ME(libarchive_Archive, __construct, arginfo_class_libarchive_Archive___construct, ZEND_ACC_PUBLIC)
5964
ZEND_ME(libarchive_Archive, fromStream, arginfo_class_libarchive_Archive_fromStream, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
65+
ZEND_ME(libarchive_Archive, withPasswordCallback, arginfo_class_libarchive_Archive_withPasswordCallback, ZEND_ACC_PUBLIC)
6066
ZEND_ME(libarchive_Archive, supportFormats, arginfo_class_libarchive_Archive_supportFormats, ZEND_ACC_PUBLIC)
6167
ZEND_ME(libarchive_Archive, supportFilters, arginfo_class_libarchive_Archive_supportFilters, ZEND_ACC_PUBLIC)
6268
ZEND_ME(libarchive_Archive, extractCurrent, arginfo_class_libarchive_Archive_extractCurrent, ZEND_ACC_PUBLIC)

php_libarchive.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ typedef struct _arch_object {
5151
int *nullable filters; /* NULL = auto-detect all; emalloc'd array of ARCHIVE_FILTER_* codes */
5252
uint32_t filters_count;
5353
uint32_t entry_generation; /* incremented before each archive_read_next_header2 call */
54+
zend_fcall_info password_fci; /* size == 0 if not set */
55+
zend_fcall_info_cache password_fcc;
56+
zend_string *nullable password_cb_last_result; /* owns the last passphrase string */
5457
zend_object parent;
5558
} arch_object;
5659

436 Bytes
Binary file not shown.

tests/password-callback.phpt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
withPasswordCallback() supplies a passphrase for encrypted archives
3+
--FILE--
4+
<?php
5+
6+
echo "-- callback is invoked when reading data --\n";
7+
$called = 0;
8+
$a = (new libarchive\Archive('arch/encrypted/test_encrypted.zip'))
9+
->withPasswordCallback(function() use (&$called) {
10+
$called++;
11+
return 'password';
12+
});
13+
foreach ($a as $e) {
14+
if ($e->size) {
15+
$content = trim(stream_get_contents($a->currentEntryStream()));
16+
echo $e->pathname, ' ', $content, "\n";
17+
}
18+
}
19+
echo "callback invoked: ", $called, " time(s)\n";
20+
21+
echo "-- wrong password, give up after 10 attempts --\n";
22+
$called = 0;
23+
$a = (new libarchive\Archive('arch/encrypted/test_encrypted.zip'))
24+
->withPasswordCallback(function() use (&$called) {
25+
$called++;
26+
return $called <= 10 ? 'wrong' : null;
27+
});
28+
foreach ($a as $e) {
29+
if (!$e->size) continue;
30+
$content = stream_get_contents($a->currentEntryStream());
31+
echo $e->pathname, ' len=', strlen($content), "\n";
32+
}
33+
echo "callback invoked: ", $called, " time(s)\n";
34+
35+
echo "-- withPasswordCallback after open --\n";
36+
$a = new libarchive\Archive('arch/encrypted/test_encrypted.zip');
37+
foreach ($a as $e) { break; }
38+
try {
39+
$a->withPasswordCallback(fn() => 'password');
40+
} catch (libarchive\Exception $e) {
41+
echo "Exception: ", $e->getMessage(), "\n";
42+
}
43+
44+
?>
45+
--EXPECTF--
46+
-- callback is invoked when reading data --
47+
encrypted/file.txt plaintext
48+
callback invoked: 1 time(s)
49+
-- wrong password, give up after 10 attempts --
50+
51+
Warning: stream_get_contents(): Error reading data: Incorrect passphrase [-1] in %s on line %d
52+
encrypted/file.txt len=0
53+
callback invoked: 11 time(s)
54+
-- withPasswordCallback after open --
55+
Exception: Cannot set password callback after archive has been opened

0 commit comments

Comments
 (0)