Skip to content

Commit 402eeb8

Browse files
committed
Zip: add support for encrypted archive
1 parent 859a650 commit 402eeb8

File tree

8 files changed

+256
-17
lines changed

8 files changed

+256
-17
lines changed

NEWS

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,6 @@ PHP NEWS
160160
. Use Zend MM for allocation in bundled libxmlrpc (Joe)
161161

162162
- ZIP:
163-
. Fixed bug #70103 (ZipArchive::addGlob ignores remove_all_path option). (cmb)
163+
. Add support for encrypted archives. (Remi)
164164

165165
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

UPGRADING

+12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ PHP 7.2 UPGRADE NOTES
100100
(https://wiki.php.net/rfc/argon2_password_hash).
101101
. proc_nice() is now supported on Windows platforms.
102102

103+
- Zip:
104+
. read/write encrypted archive, relying on libzip 1.2.0,
105+
using new methods:
106+
ZipArchive::setEncryptionName($name, $method [, $password]);
107+
ZipArchive::setEncryptionIndex($index, $method [, $password]);
108+
and new constants:
109+
ZipArchive::EM_NONE
110+
ZipArchive::EM_AES_128
111+
ZipArchive::EM_AES_192
112+
ZipArchive::EM_AES_256
113+
. accept 'password' from zip stream context
114+
103115
========================================
104116
3. Changes in SAPI modules
105117
========================================

ext/zip/config.m4

+9
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ if test "$PHP_ZIP" != "no"; then
9999
-L$LIBZIP_LIBDIR
100100
])
101101

102+
PHP_CHECK_LIBRARY(zip, zip_file_set_encryption,
103+
[
104+
PHP_ADD_LIBRARY_WITH_PATH(zip, $LIBZIP_LIBDIR, ZIP_SHARED_LIBADD)
105+
AC_DEFINE(HAVE_ENCRYPTION, 1, [Libzip >= 1.2.0 with encryption support])
106+
], [
107+
], [
108+
-L$LIBZIP_LIBDIR
109+
])
110+
102111
AC_DEFINE(HAVE_ZIP,1,[ ])
103112
PHP_NEW_EXTENSION(zip, php_zip.c zip_stream.c, $ext_shared,, $LIBZIP_CFLAGS)
104113
PHP_SUBST(ZIP_SHARED_LIBADD)

ext/zip/examples/encryption.php

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
error_reporting(E_ALL);
3+
if (!extension_loaded('zip')) {
4+
dl('zip.so');
5+
}
6+
7+
$name = __DIR__ . '/encrypted.zip';
8+
$pass = 'secret';
9+
$file = 'foo.php';
10+
11+
echo "== Create with per file password\n";
12+
13+
$zip = new ZipArchive;
14+
$zip->open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
15+
$zip->addFile(__FILE__, $file);
16+
$zip->setEncryptionName($file, ZipArchive::EM_AES_256, $pass);
17+
$zip->close();
18+
19+
echo "== Create with global password\n";
20+
21+
$zip = new ZipArchive;
22+
$zip->open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
23+
$zip->setPassword($pass);
24+
$zip->addFile(__FILE__, $file);
25+
$zip->setEncryptionName($file, ZipArchive::EM_AES_256);
26+
$zip->close();
27+
28+
echo "== Stat\n";
29+
30+
$zip->open($name);
31+
print_r($zip->statName($file));
32+
33+
echo "== Read\n";
34+
35+
$zip->setPassword($pass);
36+
$text = $zip->getFromName($file);
37+
printf("Size = %d\n", strlen($text));
38+
$zip->close();
39+
40+
echo "== Stream with context\n";
41+
42+
$ctx = stream_context_create(array(
43+
'zip' => array(
44+
'password' => $pass
45+
)
46+
));
47+
$text = file_get_contents("zip://$name#$file", false, $ctx);
48+
printf("Size = %d\n", strlen($text));
49+

ext/zip/php_zip.c

+109-14
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char
379379
/* }}} */
380380

381381
/* {{{ RETURN_SB(sb) */
382+
#ifdef HAVE_ENCRYPTION
382383
#define RETURN_SB(sb) \
383384
{ \
384385
array_init(return_value); \
@@ -389,7 +390,21 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char
389390
add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \
390391
add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \
391392
add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \
393+
add_ascii_assoc_long(return_value, "encryption_method", (zend_long) (sb)->encryption_method); \
392394
}
395+
#else
396+
#define RETURN_SB(sb) \
397+
{ \
398+
array_init(return_value); \
399+
add_ascii_assoc_string(return_value, "name", (char *)(sb)->name); \
400+
add_ascii_assoc_long(return_value, "index", (zend_long) (sb)->index); \
401+
add_ascii_assoc_long(return_value, "crc", (zend_long) (sb)->crc); \
402+
add_ascii_assoc_long(return_value, "size", (zend_long) (sb)->size); \
403+
add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \
404+
add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \
405+
add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \
406+
}
407+
#endif
393408
/* }}} */
394409

395410
static int php_zip_status(struct zip *za) /* {{{ */
@@ -2222,6 +2237,74 @@ static ZIPARCHIVE_METHOD(getExternalAttributesIndex)
22222237
/* }}} */
22232238
#endif /* ifdef ZIP_OPSYS_DEFAULT */
22242239

2240+
#ifdef HAVE_ENCRYPTION
2241+
/* {{{ proto bool ZipArchive::setEncryptionName(string name, int method, [string password])
2242+
Set encryption method for file in zip, using its name */
2243+
static ZIPARCHIVE_METHOD(setEncryptionName)
2244+
{
2245+
struct zip *intern;
2246+
zval *self = getThis();
2247+
zend_long method;
2248+
zip_int64_t idx;
2249+
char *name, *password = NULL;
2250+
size_t name_len, password_len;
2251+
2252+
if (!self) {
2253+
RETURN_FALSE;
2254+
}
2255+
2256+
ZIP_FROM_OBJECT(intern, self);
2257+
2258+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|s",
2259+
&name, &name_len, &method, &password, &password_len) == FAILURE) {
2260+
return;
2261+
}
2262+
2263+
if (name_len < 1) {
2264+
php_error_docref(NULL, E_NOTICE, "Empty string as entry name");
2265+
}
2266+
2267+
idx = zip_name_locate(intern, name, 0);
2268+
if (idx < 0) {
2269+
RETURN_FALSE;
2270+
}
2271+
2272+
if (zip_file_set_encryption(intern, idx, (zip_uint16_t)method, password)) {
2273+
RETURN_FALSE;
2274+
}
2275+
RETURN_TRUE;
2276+
}
2277+
/* }}} */
2278+
2279+
/* {{{ proto bool ZipArchive::setEncryptionIndex(int index, int method, [string password])
2280+
Set encryption method for file in zip, using its index */
2281+
static ZIPARCHIVE_METHOD(setEncryptionIndex)
2282+
{
2283+
struct zip *intern;
2284+
zval *self = getThis();
2285+
zend_long index, method;
2286+
char *password = NULL;
2287+
size_t password_len;
2288+
2289+
if (!self) {
2290+
RETURN_FALSE;
2291+
}
2292+
2293+
ZIP_FROM_OBJECT(intern, self);
2294+
2295+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|s",
2296+
&index, &method, &password, &password_len) == FAILURE) {
2297+
return;
2298+
}
2299+
2300+
if (zip_file_set_encryption(intern, index, (zip_uint16_t)method, password)) {
2301+
RETURN_FALSE;
2302+
}
2303+
RETURN_TRUE;
2304+
}
2305+
/* }}} */
2306+
#endif
2307+
22252308
/* {{{ proto string ZipArchive::getCommentName(string name[, int flags])
22262309
Returns the comment of an entry using its name */
22272310
static ZIPARCHIVE_METHOD(getCommentName)
@@ -2952,6 +3035,20 @@ ZEND_END_ARG_INFO()
29523035
#endif /* ifdef ZIP_OPSYS_DEFAULT */
29533036
/* }}} */
29543037

3038+
#ifdef HAVE_ENCRYPTION
3039+
ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_name, 0, 0, 2)
3040+
ZEND_ARG_INFO(0, name)
3041+
ZEND_ARG_INFO(0, method)
3042+
ZEND_ARG_INFO(0, password)
3043+
ZEND_END_ARG_INFO()
3044+
3045+
ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_index, 0, 0, 2)
3046+
ZEND_ARG_INFO(0, index)
3047+
ZEND_ARG_INFO(0, method)
3048+
ZEND_ARG_INFO(0, password)
3049+
ZEND_END_ARG_INFO()
3050+
#endif
3051+
29553052
ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcompname, 0, 0, 2)
29563053
ZEND_ARG_INFO(0, name)
29573054
ZEND_ARG_INFO(0, method)
@@ -3003,6 +3100,10 @@ static const zend_function_entry zip_class_functions[] = {
30033100
ZIPARCHIVE_ME(getExternalAttributesIndex, arginfo_ziparchive_getextattrindex, ZEND_ACC_PUBLIC)
30043101
ZIPARCHIVE_ME(setCompressionName, arginfo_ziparchive_setcompname, ZEND_ACC_PUBLIC)
30053102
ZIPARCHIVE_ME(setCompressionIndex, arginfo_ziparchive_setcompindex, ZEND_ACC_PUBLIC)
3103+
#ifdef HAVE_ENCRYPTION
3104+
ZIPARCHIVE_ME(setEncryptionName, arginfo_ziparchive_setencryption_name, ZEND_ACC_PUBLIC)
3105+
ZIPARCHIVE_ME(setEncryptionIndex, arginfo_ziparchive_setencryption_index, ZEND_ACC_PUBLIC)
3106+
#endif
30063107
PHP_FE_END
30073108
};
30083109
/* }}} */
@@ -3047,6 +3148,7 @@ static PHP_MINIT_FUNCTION(zip)
30473148
REGISTER_ZIP_CLASS_CONST_LONG("FL_NODIR", ZIP_FL_NODIR);
30483149
REGISTER_ZIP_CLASS_CONST_LONG("FL_COMPRESSED", ZIP_FL_COMPRESSED);
30493150
REGISTER_ZIP_CLASS_CONST_LONG("FL_UNCHANGED", ZIP_FL_UNCHANGED);
3151+
30503152
#ifdef ZIP_FL_ENC_GUESS
30513153
/* Default filename encoding policy. */
30523154
REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_GUESS", ZIP_FL_ENC_GUESS);
@@ -3064,20 +3166,6 @@ static PHP_MINIT_FUNCTION(zip)
30643166
REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_CP437", ZIP_FL_ENC_CP437);
30653167
#endif
30663168

3067-
/* XXX The below are rather not implemented or to check whether makes sense to expose. */
3068-
/*#ifdef ZIP_FL_RECOMPRESS
3069-
REGISTER_ZIP_CLASS_CONST_LONG("FL_RECOMPRESS", ZIP_FL_RECOMPRESS);
3070-
#endif
3071-
#ifdef ZIP_FL_ENCRYPTED
3072-
REGISTER_ZIP_CLASS_CONST_LONG("FL_ENCRYPTED", ZIP_FL_ENCRYPTED);
3073-
#endif
3074-
#ifdef ZIP_FL_LOCAL
3075-
REGISTER_ZIP_CLASS_CONST_LONG("FL_LOCAL", ZIP_FL_LOCAL);
3076-
#endif
3077-
#ifdef ZIP_FL_CENTRAL
3078-
REGISTER_ZIP_CLASS_CONST_LONG("FL_CENTRAL", ZIP_FL_CENTRAL);
3079-
#endif */
3080-
30813169
REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFAULT", ZIP_CM_DEFAULT);
30823170
REGISTER_ZIP_CLASS_CONST_LONG("CM_STORE", ZIP_CM_STORE);
30833171
REGISTER_ZIP_CLASS_CONST_LONG("CM_SHRINK", ZIP_CM_SHRINK);
@@ -3147,6 +3235,13 @@ static PHP_MINIT_FUNCTION(zip)
31473235
REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_DEFAULT", ZIP_OPSYS_DEFAULT);
31483236
#endif /* ifdef ZIP_OPSYS_DEFAULT */
31493237

3238+
#ifdef HAVE_ENCRYPTION
3239+
REGISTER_ZIP_CLASS_CONST_LONG("EM_NONE", ZIP_EM_NONE);
3240+
REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_128", ZIP_EM_AES_128);
3241+
REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_192", ZIP_EM_AES_192);
3242+
REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_256", ZIP_EM_AES_256);
3243+
#endif
3244+
31503245
php_register_url_stream_wrapper("zip", &php_stream_zip_wrapper);
31513246

31523247
le_zip_dir = zend_register_list_destructors_ex(php_zip_free_dir, NULL, le_zip_dir_name, module_number);

ext/zip/php_zip.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ extern zend_module_entry zip_module_entry;
3737
#define ZIP_OVERWRITE ZIP_TRUNCATE
3838
#endif
3939

40-
#define PHP_ZIP_VERSION "1.13.5"
40+
#define PHP_ZIP_VERSION "1.14.0-dev"
4141

4242
#define ZIP_OPENBASEDIR_CHECKPATH(filename) php_check_open_basedir(filename)
4343

ext/zip/tests/oo_encryption.phpt

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
ZipArchive::setEncryption*() functions
3+
--SKIPIF--
4+
<?php
5+
/* $Id$ */
6+
if (!extension_loaded('zip')) die('skip');
7+
if (!method_exists('ZipArchive', 'setEncryptionName')) die('skip encrytion not supported');
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$name = __DIR__ . '/encrypted.zip';
13+
$pass = 'secret';
14+
15+
echo "== Write\n";
16+
$zip = new ZipArchive;
17+
$r = $zip->open($name, ZIPARCHIVE::CREATE);
18+
// Clear
19+
$zip->addFromString('foo.txt', 'foo');
20+
// Encrypted
21+
$zip->addFromString('bar.txt', 'bar');
22+
var_dump($zip->setEncryptionName('bar.txt', 9999, $pass)); // Fails
23+
var_dump($zip->setEncryptionName('bar.txt', ZipArchive::EM_AES_256, $pass));
24+
$zip->close();
25+
26+
echo "== Read\n";
27+
$r = $zip->open($name);
28+
$s = $zip->statName('foo.txt');
29+
var_dump($s['encryption_method'] === ZipArchive::EM_NONE);
30+
$s = $zip->statName('bar.txt');
31+
var_dump($s['encryption_method'] === ZipArchive::EM_AES_256);
32+
var_dump($zip->getFromName('foo.txt')); // Clear, ok
33+
var_dump($zip->getFromName('bar.txt')); // Encrypted, fails
34+
$zip->setPassword($pass);
35+
var_dump($zip->getFromName('bar.txt')); // Ecnrypted, ok
36+
$zip->close();
37+
38+
echo "== Stream\n";
39+
var_dump(file_get_contents("zip://$name#foo.txt")); // Clear, ok
40+
var_dump(file_get_contents("zip://$name#bar.txt")); // Encrypted, fails
41+
$ctx = stream_context_create(array('zip' => array('password' => $pass)));
42+
var_dump(file_get_contents("zip://$name#bar.txt", false, $ctx)); // Ecnrypted, ok
43+
?>
44+
== Done
45+
--CLEAN--
46+
<?php
47+
$name = __DIR__ . '/encrypted.zip';
48+
@unlink($name);
49+
?>
50+
--EXPECTF--
51+
== Write
52+
bool(false)
53+
bool(true)
54+
== Read
55+
bool(true)
56+
bool(true)
57+
string(3) "foo"
58+
bool(false)
59+
string(3) "bar"
60+
== Stream
61+
string(3) "foo"
62+
63+
Warning: file_get_contents(%s): failed to open stream: operation failed in %s on line %d
64+
bool(false)
65+
string(3) "bar"
66+
== Done

ext/zip/zip_stream.c

+9-1
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
309309

310310
za = zip_open(file_dirname, ZIP_CREATE, &err);
311311
if (za) {
312+
zval *tmpzval;
313+
314+
if (NULL != (tmpzval = php_stream_context_get_option(context, "zip", "password"))) {
315+
if (Z_TYPE_P(tmpzval) != IS_STRING || zip_set_default_password(za, Z_STRVAL_P(tmpzval))) {
316+
php_error_docref(NULL, E_WARNING, "Can't set zip password");
317+
}
318+
}
319+
312320
zf = zip_fopen(za, fragment, 0);
313321
if (zf) {
314322
self = emalloc(sizeof(*self));
@@ -348,7 +356,7 @@ static php_stream_wrapper_ops zip_stream_wops = {
348356
NULL, /* rename */
349357
NULL, /* mkdir */
350358
NULL, /* rmdir */
351-
NULL
359+
NULL /* metadata */
352360
};
353361

354362
php_stream_wrapper php_stream_zip_wrapper = {

0 commit comments

Comments
 (0)