]> granicus.if.org Git - php/commitdiff
Zip: add support for encrypted archive
authorRemi Collet <remi@php.net>
Thu, 2 Mar 2017 12:36:40 +0000 (13:36 +0100)
committerRemi Collet <remi@php.net>
Thu, 2 Mar 2017 12:36:40 +0000 (13:36 +0100)
NEWS
UPGRADING
ext/zip/config.m4
ext/zip/examples/encryption.php [new file with mode: 0644]
ext/zip/php_zip.c
ext/zip/php_zip.h
ext/zip/tests/oo_encryption.phpt [new file with mode: 0644]
ext/zip/zip_stream.c

diff --git a/NEWS b/NEWS
index f465d7dbfd55451b16379e80711309461aba7088..ea88623979a0cd222d0941ccbb3ab7de4e79406a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -160,6 +160,6 @@ PHP                                                                        NEWS
   . Use Zend MM for allocation in bundled libxmlrpc (Joe)
 
 - ZIP:
-  . Fixed bug #70103 (ZipArchive::addGlob ignores remove_all_path option). (cmb)
+  . Add support for encrypted archives. (Remi)
 
 <<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
index da884dcc4fb691a378fbb43dc897e87b374ce339..6121b9d70d1f7755b316c0b1f8d8257828ff1f01 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -100,6 +100,18 @@ PHP 7.2 UPGRADE NOTES
     (https://wiki.php.net/rfc/argon2_password_hash).
   . proc_nice() is now supported on Windows platforms.
 
+- Zip:
+  . read/write encrypted archive, relying on libzip 1.2.0,
+    using new methods:
+      ZipArchive::setEncryptionName($name, $method [, $password]);
+      ZipArchive::setEncryptionIndex($index, $method [, $password]);
+    and new constants:
+      ZipArchive::EM_NONE
+      ZipArchive::EM_AES_128
+      ZipArchive::EM_AES_192
+      ZipArchive::EM_AES_256
+  . accept 'password' from zip stream context
+
 ========================================
 3. Changes in SAPI modules
 ========================================
index bb653c20689a1e42b1f6562d73f65b9d9e7eb1a3..83798ee7929893f34e460ab06be60c145b19f231 100644 (file)
@@ -99,6 +99,15 @@ if test "$PHP_ZIP" != "no"; then
       -L$LIBZIP_LIBDIR
     ])
 
+    PHP_CHECK_LIBRARY(zip, zip_file_set_encryption,
+    [
+      PHP_ADD_LIBRARY_WITH_PATH(zip, $LIBZIP_LIBDIR, ZIP_SHARED_LIBADD)
+      AC_DEFINE(HAVE_ENCRYPTION, 1, [Libzip >= 1.2.0 with encryption support])
+    ], [
+    ], [
+      -L$LIBZIP_LIBDIR
+    ])
+
     AC_DEFINE(HAVE_ZIP,1,[ ])
     PHP_NEW_EXTENSION(zip, php_zip.c zip_stream.c, $ext_shared,, $LIBZIP_CFLAGS)
     PHP_SUBST(ZIP_SHARED_LIBADD)
diff --git a/ext/zip/examples/encryption.php b/ext/zip/examples/encryption.php
new file mode 100644 (file)
index 0000000..6988817
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+error_reporting(E_ALL);
+if (!extension_loaded('zip')) {
+    dl('zip.so');
+}
+
+$name = __DIR__ . '/encrypted.zip';
+$pass = 'secret';
+$file = 'foo.php';
+
+echo "== Create with per file password\n";
+
+$zip  = new ZipArchive;
+$zip->open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
+$zip->addFile(__FILE__, $file);
+$zip->setEncryptionName($file, ZipArchive::EM_AES_256, $pass);
+$zip->close();
+
+echo "== Create with global password\n";
+
+$zip  = new ZipArchive;
+$zip->open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
+$zip->setPassword($pass);
+$zip->addFile(__FILE__, $file);
+$zip->setEncryptionName($file, ZipArchive::EM_AES_256);
+$zip->close();
+
+echo "== Stat\n";
+
+$zip->open($name);
+print_r($zip->statName($file));
+
+echo "== Read\n";
+
+$zip->setPassword($pass);
+$text = $zip->getFromName($file);
+printf("Size = %d\n", strlen($text));
+$zip->close();
+
+echo "== Stream with context\n";
+
+$ctx = stream_context_create(array(
+       'zip' => array(
+               'password' => $pass
+       )
+));
+$text = file_get_contents("zip://$name#$file", false, $ctx);
+printf("Size = %d\n", strlen($text));
+
index 28527cbee14f04fe5be5e952ba0de72ad91b656e..03f218439999cf0c28a9a9b056073026a66e2201 100644 (file)
@@ -379,6 +379,7 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char
 /* }}} */
 
 /* {{{ RETURN_SB(sb) */
+#ifdef HAVE_ENCRYPTION
 #define RETURN_SB(sb) \
        { \
                array_init(return_value); \
@@ -389,7 +390,21 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char
                add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \
                add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \
                add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \
+               add_ascii_assoc_long(return_value, "encryption_method", (zend_long) (sb)->encryption_method); \
        }
+#else
+#define RETURN_SB(sb) \
+       { \
+               array_init(return_value); \
+               add_ascii_assoc_string(return_value, "name", (char *)(sb)->name); \
+               add_ascii_assoc_long(return_value, "index", (zend_long) (sb)->index); \
+               add_ascii_assoc_long(return_value, "crc", (zend_long) (sb)->crc); \
+               add_ascii_assoc_long(return_value, "size", (zend_long) (sb)->size); \
+               add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \
+               add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \
+               add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \
+       }
+#endif
 /* }}} */
 
 static int php_zip_status(struct zip *za) /* {{{ */
@@ -2222,6 +2237,74 @@ static ZIPARCHIVE_METHOD(getExternalAttributesIndex)
 /* }}} */
 #endif /* ifdef ZIP_OPSYS_DEFAULT */
 
+#ifdef HAVE_ENCRYPTION
+/* {{{ proto bool ZipArchive::setEncryptionName(string name, int method, [string password])
+Set encryption method for file in zip, using its name */
+static ZIPARCHIVE_METHOD(setEncryptionName)
+{
+       struct zip *intern;
+       zval *self = getThis();
+       zend_long method;
+       zip_int64_t idx;
+       char *name, *password = NULL;
+       size_t name_len, password_len;
+
+       if (!self) {
+               RETURN_FALSE;
+       }
+
+       ZIP_FROM_OBJECT(intern, self);
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|s",
+                       &name, &name_len, &method, &password, &password_len) == FAILURE) {
+               return;
+       }
+
+       if (name_len < 1) {
+               php_error_docref(NULL, E_NOTICE, "Empty string as entry name");
+       }
+
+       idx = zip_name_locate(intern, name, 0);
+       if (idx < 0) {
+               RETURN_FALSE;
+       }
+
+       if (zip_file_set_encryption(intern, idx, (zip_uint16_t)method, password)) {
+               RETURN_FALSE;
+       }
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto bool ZipArchive::setEncryptionIndex(int index, int method, [string password])
+Set encryption method for file in zip, using its index */
+static ZIPARCHIVE_METHOD(setEncryptionIndex)
+{
+       struct zip *intern;
+       zval *self = getThis();
+       zend_long index, method;
+       char *password = NULL;
+       size_t password_len;
+
+       if (!self) {
+               RETURN_FALSE;
+       }
+
+       ZIP_FROM_OBJECT(intern, self);
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|s",
+                       &index, &method, &password, &password_len) == FAILURE) {
+               return;
+       }
+
+       if (zip_file_set_encryption(intern, index, (zip_uint16_t)method, password)) {
+               RETURN_FALSE;
+       }
+       RETURN_TRUE;
+}
+/* }}} */
+#endif
+
 /* {{{ proto string ZipArchive::getCommentName(string name[, int flags])
 Returns the comment of an entry using its name */
 static ZIPARCHIVE_METHOD(getCommentName)
@@ -2952,6 +3035,20 @@ ZEND_END_ARG_INFO()
 #endif /* ifdef ZIP_OPSYS_DEFAULT */
 /* }}} */
 
+#ifdef HAVE_ENCRYPTION
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_name, 0, 0, 2)
+       ZEND_ARG_INFO(0, name)
+       ZEND_ARG_INFO(0, method)
+       ZEND_ARG_INFO(0, password)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_index, 0, 0, 2)
+       ZEND_ARG_INFO(0, index)
+       ZEND_ARG_INFO(0, method)
+       ZEND_ARG_INFO(0, password)
+ZEND_END_ARG_INFO()
+#endif
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcompname, 0, 0, 2)
        ZEND_ARG_INFO(0, name)
        ZEND_ARG_INFO(0, method)
@@ -3003,6 +3100,10 @@ static const zend_function_entry zip_class_functions[] = {
        ZIPARCHIVE_ME(getExternalAttributesIndex,       arginfo_ziparchive_getextattrindex, ZEND_ACC_PUBLIC)
        ZIPARCHIVE_ME(setCompressionName,               arginfo_ziparchive_setcompname, ZEND_ACC_PUBLIC)
        ZIPARCHIVE_ME(setCompressionIndex,              arginfo_ziparchive_setcompindex, ZEND_ACC_PUBLIC)
+#ifdef HAVE_ENCRYPTION
+       ZIPARCHIVE_ME(setEncryptionName,                arginfo_ziparchive_setencryption_name, ZEND_ACC_PUBLIC)
+       ZIPARCHIVE_ME(setEncryptionIndex,               arginfo_ziparchive_setencryption_index, ZEND_ACC_PUBLIC)
+#endif
        PHP_FE_END
 };
 /* }}} */
@@ -3047,6 +3148,7 @@ static PHP_MINIT_FUNCTION(zip)
        REGISTER_ZIP_CLASS_CONST_LONG("FL_NODIR", ZIP_FL_NODIR);
        REGISTER_ZIP_CLASS_CONST_LONG("FL_COMPRESSED", ZIP_FL_COMPRESSED);
        REGISTER_ZIP_CLASS_CONST_LONG("FL_UNCHANGED", ZIP_FL_UNCHANGED);
+
 #ifdef ZIP_FL_ENC_GUESS
        /* Default filename encoding policy. */
        REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_GUESS", ZIP_FL_ENC_GUESS);
@@ -3064,20 +3166,6 @@ static PHP_MINIT_FUNCTION(zip)
        REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_CP437", ZIP_FL_ENC_CP437);
 #endif
 
-/* XXX The below are rather not implemented or to check whether makes sense to expose. */
-/*#ifdef ZIP_FL_RECOMPRESS
-       REGISTER_ZIP_CLASS_CONST_LONG("FL_RECOMPRESS", ZIP_FL_RECOMPRESS);
-#endif
-#ifdef ZIP_FL_ENCRYPTED
-       REGISTER_ZIP_CLASS_CONST_LONG("FL_ENCRYPTED", ZIP_FL_ENCRYPTED);
-#endif
-#ifdef ZIP_FL_LOCAL
-       REGISTER_ZIP_CLASS_CONST_LONG("FL_LOCAL", ZIP_FL_LOCAL);
-#endif
-#ifdef ZIP_FL_CENTRAL
-       REGISTER_ZIP_CLASS_CONST_LONG("FL_CENTRAL", ZIP_FL_CENTRAL);
-#endif */
-
        REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFAULT", ZIP_CM_DEFAULT);
        REGISTER_ZIP_CLASS_CONST_LONG("CM_STORE", ZIP_CM_STORE);
        REGISTER_ZIP_CLASS_CONST_LONG("CM_SHRINK", ZIP_CM_SHRINK);
@@ -3147,6 +3235,13 @@ static PHP_MINIT_FUNCTION(zip)
        REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_DEFAULT", ZIP_OPSYS_DEFAULT);
 #endif /* ifdef ZIP_OPSYS_DEFAULT */
 
+#ifdef HAVE_ENCRYPTION
+       REGISTER_ZIP_CLASS_CONST_LONG("EM_NONE",                                ZIP_EM_NONE);
+       REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_128",                             ZIP_EM_AES_128);
+       REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_192",                             ZIP_EM_AES_192);
+       REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_256",                             ZIP_EM_AES_256);
+#endif
+
        php_register_url_stream_wrapper("zip", &php_stream_zip_wrapper);
 
        le_zip_dir   = zend_register_list_destructors_ex(php_zip_free_dir,   NULL, le_zip_dir_name,   module_number);
index f886da3400b5957993d06d62d409fb70e7401db9..327ad54262d918c6c7273f8654ee77bf33a0eb18 100644 (file)
@@ -37,7 +37,7 @@ extern zend_module_entry zip_module_entry;
 #define ZIP_OVERWRITE ZIP_TRUNCATE
 #endif
 
-#define PHP_ZIP_VERSION "1.13.5"
+#define PHP_ZIP_VERSION "1.14.0-dev"
 
 #define ZIP_OPENBASEDIR_CHECKPATH(filename) php_check_open_basedir(filename)
 
diff --git a/ext/zip/tests/oo_encryption.phpt b/ext/zip/tests/oo_encryption.phpt
new file mode 100644 (file)
index 0000000..b703611
--- /dev/null
@@ -0,0 +1,66 @@
+--TEST--
+ZipArchive::setEncryption*() functions
+--SKIPIF--
+<?php
+/* $Id$ */
+if (!extension_loaded('zip')) die('skip');
+if (!method_exists('ZipArchive', 'setEncryptionName')) die('skip encrytion not supported');
+?>
+--FILE--
+<?php
+
+$name = __DIR__ . '/encrypted.zip';
+$pass = 'secret';
+
+echo "== Write\n";
+$zip  = new ZipArchive;
+$r = $zip->open($name, ZIPARCHIVE::CREATE);
+// Clear
+$zip->addFromString('foo.txt', 'foo'); 
+// Encrypted
+$zip->addFromString('bar.txt', 'bar');
+var_dump($zip->setEncryptionName('bar.txt', 9999, $pass)); // Fails
+var_dump($zip->setEncryptionName('bar.txt', ZipArchive::EM_AES_256, $pass));
+$zip->close();
+
+echo "== Read\n";
+$r = $zip->open($name);
+$s = $zip->statName('foo.txt');
+var_dump($s['encryption_method'] === ZipArchive::EM_NONE);
+$s = $zip->statName('bar.txt');
+var_dump($s['encryption_method'] === ZipArchive::EM_AES_256);
+var_dump($zip->getFromName('foo.txt')); // Clear, ok
+var_dump($zip->getFromName('bar.txt')); // Encrypted, fails
+$zip->setPassword($pass);
+var_dump($zip->getFromName('bar.txt')); // Ecnrypted, ok
+$zip->close();
+
+echo "== Stream\n";
+var_dump(file_get_contents("zip://$name#foo.txt")); // Clear, ok
+var_dump(file_get_contents("zip://$name#bar.txt")); // Encrypted, fails
+$ctx = stream_context_create(array('zip' => array('password' => $pass)));
+var_dump(file_get_contents("zip://$name#bar.txt", false, $ctx)); // Ecnrypted, ok
+?>
+== Done
+--CLEAN--
+<?php
+$name = __DIR__ . '/encrypted.zip';
+@unlink($name);
+?>
+--EXPECTF--
+== Write
+bool(false)
+bool(true)
+== Read
+bool(true)
+bool(true)
+string(3) "foo"
+bool(false)
+string(3) "bar"
+== Stream
+string(3) "foo"
+
+Warning: file_get_contents(%s): failed to open stream: operation failed in %s on line %d
+bool(false)
+string(3) "bar"
+== Done
index 2fa828a572dbfe9d6048a0d6b04c933412208788..2ed584021d59d65d6f0296a8d9cf9bce0d0cd3c5 100644 (file)
@@ -309,6 +309,14 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
 
        za = zip_open(file_dirname, ZIP_CREATE, &err);
        if (za) {
+               zval *tmpzval;
+
+               if (NULL != (tmpzval = php_stream_context_get_option(context, "zip", "password"))) {
+                       if (Z_TYPE_P(tmpzval) != IS_STRING || zip_set_default_password(za, Z_STRVAL_P(tmpzval))) {
+                               php_error_docref(NULL, E_WARNING, "Can't set zip password");
+                       }
+               }
+
                zf = zip_fopen(za, fragment, 0);
                if (zf) {
                        self = emalloc(sizeof(*self));
@@ -348,7 +356,7 @@ static php_stream_wrapper_ops zip_stream_wops = {
        NULL,   /* rename */
        NULL,   /* mkdir */
        NULL,   /* rmdir */
-       NULL
+       NULL    /* metadata */
 };
 
 php_stream_wrapper php_stream_zip_wrapper = {