com php-src: Zip: add support for encrypted archive: NEWS UPGRADING ext/zip/config.m4 ext/zip/examples/encryption.php ext/zip/php _zip.c ext/zip/php_zip.h ext/zip/tests/oo_encrypt ion.phpt ext/zip/zip_stream.c

[email protected] (Remi Collet)
Newsgroups php.cvs
Message-ID <[email protected]>
Commit:    402eeb85988be91d90a92eff94635b6e9ef6160e
Author:    Remi Collet <[email protected]>         Thu, 2 Mar 2017 13:36:40 +0100
Parents:   859a6505d3bdb017f68ea1beedb118e3758ccb6f
Branches:  master

Link:       http://git.php.net/?p=php-src.git;a=commitdiff;h=402eeb85988be91d90a92eff94635b6e9ef6160e

Log:
Zip: add support for encrypted archive

Changed paths:
  M  NEWS
  M  UPGRADING
  M  ext/zip/config.m4
  A  ext/zip/examples/encryption.php
  M  ext/zip/php_zip.c
  M  ext/zip/php_zip.h
  A  ext/zip/tests/oo_encryption.phpt
  M  ext/zip/zip_stream.c
diff_402eeb85988be91d90a92eff94635b6e9ef6160e.txt (text/plain, 12.5 KB)
diff --git a/NEWS b/NEWS
index f465d7d..ea88623 100644
--- 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! >>>
diff --git a/UPGRADING b/UPGRADING
index da884dc..6121b9d 100644
--- 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
 ========================================
diff --git a/ext/zip/config.m4 b/ext/zip/config.m4
index bb653c20..83798ee 100644
--- a/ext/zip/config.m4
+++ b/ext/zip/config.m4
@@ -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
index 0000000..6988817
--- /dev/null
+++ b/ext/zip/examples/encryption.php
@@ -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));
+
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 28527cb..03f2184 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -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);
diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h
index f886da3..327ad54 100644
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@ -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
index 0000000..b703611
--- /dev/null
+++ b/ext/zip/tests/oo_encryption.phpt
@@ -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
diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c
index 2fa828a..2ed5840 100644
--- a/ext/zip/zip_stream.c
+++ b/ext/zip/zip_stream.c
@@ -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 = {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.