summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyson Andre <tysonandre775@hotmail.com>2020-07-07 18:47:52 -0400
committerTyson Andre <tysonandre775@hotmail.com>2020-08-03 13:28:51 -0400
commit0c238ede019f6ffbe7c996ec1695a747f4bca966 (patch)
treee808fc0f2b31d73f42d80f51087d611c98673142
parentf9f769d4b9af367af864d35cf09dca5b08da2046 (diff)
downloadphp-git-0c238ede019f6ffbe7c996ec1695a747f4bca966.tar.gz
[RFC] Only unserialize Phar metadata when getMetadata() is called
In other words, don't automatically unserialize when the magic phar:// stream wrappers are used. RFC: https://wiki.php.net/rfc/phar_stop_autoloading_metadata Also, change the signature from `getMetadata()` to `getMetadata(array $unserialize_options = [])`. Start throwing earlier if setMetadata() is called and serialization threw. See https://externals.io/message/110856 and https://bugs.php.net/bug.php?id=76774 This was refactored to add a phar_metadata_tracker for the following reasons: - The way to properly copy a zval was previously implicit and undocumented (e.g. is it a pointer to a raw string or an actual value) - Avoid unnecessary serialization and unserialization in the most common case - If a metadata value is serialized once while saving a new/modified phar file, this allows reusing the same serialized string. - Have as few ways to copy/clone/lazily parse metadata (etc.) as possible, so that code changes can be limited to only a few places in the future. - Performance is hopefully not a concern - copying a string should be faster than unserializing a value, and metadata should be rare in most cases. Remove unnecessary skip in a test(Compression's unused) Add additional assertions about usage of persistent phars Improve robustness of `Phar*->setMetadata()` - Add sanity checks for edge cases freeing metadata, when destructors or serializers modify the phar recursively. - Typical use cases of php have phar.readonly=1 and would not be affected. Closes GH-5855
-rw-r--r--UPGRADING5
-rw-r--r--ext/phar/phar.c269
-rw-r--r--ext/phar/phar_internal.h29
-rw-r--r--ext/phar/phar_object.c139
-rw-r--r--ext/phar/phar_object.stub.php6
-rw-r--r--ext/phar/phar_object_arginfo.h10
-rw-r--r--ext/phar/stream.c9
-rw-r--r--ext/phar/tar.c56
-rw-r--r--ext/phar/tests/bug69720.phpt6
-rw-r--r--ext/phar/tests/bug69958.phpt2
-rw-r--r--ext/phar/tests/phar_metadata_write2.phpt47
-rw-r--r--ext/phar/tests/phar_metadata_write3.phpt106
-rw-r--r--ext/phar/tests/phar_metadata_write4.phpt103
-rw-r--r--ext/phar/tests/tar/all.phpt2
-rw-r--r--ext/phar/tests/tar/bug70417.phpt2
-rw-r--r--ext/phar/util.c25
-rw-r--r--ext/phar/zip.c81
-rw-r--r--ext/standard/php_var.h2
-rw-r--r--ext/standard/var.c38
19 files changed, 611 insertions, 326 deletions
diff --git a/UPGRADING b/UPGRADING
index 5244386655..dd91049f98 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -425,6 +425,11 @@ PHP 8.0 UPGRADE NOTES
- PDO_ODBC:
. The php.ini directive pdo_odbc.db2_instance_name has been removed
+- Phar:
+ . Metadata associated with a phar will no longer be automatically unserialized,
+ to fix potential security vulnerabilities due to object instantiation, autoloading, etc.
+ RFC: https://wiki.php.net/rfc/phar_stop_autoloading_metadata
+
- Reflection:
. The method signatures
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
index 70f469cab5..6434a75a75 100644
--- a/ext/phar/phar.c
+++ b/ext/phar/phar.c
@@ -21,6 +21,7 @@
#include "phar_internal.h"
#include "SAPI.h"
#include "func_interceptors.h"
+#include "ext/standard/php_var.h"
static void destroy_phar_data(zval *zv);
@@ -229,20 +230,7 @@ void phar_destroy_phar_data(phar_archive_data *phar) /* {{{ */
HT_INVALIDATE(&phar->virtual_dirs);
}
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
- if (phar->is_persistent) {
- if (phar->metadata_len) {
- /* for zip comments that are strings */
- free(Z_PTR(phar->metadata));
- } else {
- zval_internal_ptr_dtor(&phar->metadata);
- }
- } else {
- zval_ptr_dtor(&phar->metadata);
- }
- phar->metadata_len = 0;
- ZVAL_UNDEF(&phar->metadata);
- }
+ phar_metadata_tracker_free(&phar->metadata_tracker, phar->is_persistent);
if (phar->fp) {
php_stream_close(phar->fp);
@@ -383,25 +371,7 @@ void destroy_phar_manifest_entry_int(phar_entry_info *entry) /* {{{ */
entry->fp = 0;
}
- if (Z_TYPE(entry->metadata) != IS_UNDEF) {
- if (entry->is_persistent) {
- if (entry->metadata_len) {
- /* for zip comments that are strings */
- free(Z_PTR(entry->metadata));
- } else {
- zval_internal_ptr_dtor(&entry->metadata);
- }
- } else {
- zval_ptr_dtor(&entry->metadata);
- }
- entry->metadata_len = 0;
- ZVAL_UNDEF(&entry->metadata);
- }
-
- if (entry->metadata_str.s) {
- smart_str_free(&entry->metadata_str);
- entry->metadata_str.s = NULL;
- }
+ phar_metadata_tracker_free(&entry->metadata_tracker, entry->is_persistent);
pefree(entry->filename, entry->is_persistent);
@@ -600,43 +570,67 @@ int phar_open_parsed_phar(char *fname, size_t fname_len, char *alias, size_t ali
/* }}}*/
/**
- * Parse out metadata from the manifest for a single file
- *
- * Meta-data is in this format:
- * [len32][data...]
+ * Attempt to serialize the data.
+ * Callers are responsible for handling EG(exception) if one occurs.
+ */
+void phar_metadata_tracker_try_ensure_has_serialized_data(phar_metadata_tracker *tracker, int persistent) /* {{{ */
+{
+ php_serialize_data_t metadata_hash;
+ smart_str metadata_str = {0};
+ if (tracker->str || Z_ISUNDEF(tracker->val)) {
+ /* Already has serialized the value or there is no value */
+ return;
+ }
+ /* Assert it should not be possible to create raw zvals in a persistent phar (i.e. from cache_list) */
+ ZEND_ASSERT(!persistent);
+
+ PHP_VAR_SERIALIZE_INIT(metadata_hash);
+ php_var_serialize(&metadata_str, &tracker->val, &metadata_hash);
+ PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
+ if (!metadata_str.s) {
+ return;
+ }
+ tracker->str = metadata_str.s;
+}
+/* }}} */
+
+/**
+ * Parse out metadata when phar_metadata_tracker_has_data is true.
*
- * data is the serialized zval
+ * Precondition: phar_metadata_tracker_has_data is true
*/
-int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len) /* {{{ */
+int phar_metadata_tracker_unserialize_or_copy(phar_metadata_tracker *tracker, zval *metadata, int persistent, HashTable *unserialize_options, const char* method_name) /* {{{ */
{
- php_unserialize_data_t var_hash;
+ const zend_bool has_unserialize_options = unserialize_options != NULL && zend_array_count(unserialize_options) > 0;
+ /* It should be impossible to create a zval in a persistent phar/entry. */
+ ZEND_ASSERT(!persistent || Z_ISUNDEF(tracker->val));
+
+ if (Z_ISUNDEF(tracker->val) || has_unserialize_options) {
+ if (EG(exception)) {
+ /* Because other parts of the phar code haven't been updated to check for exceptions after doing something that may throw,
+ * check for exceptions before potentially serializing/unserializing instead. */
+ return FAILURE;
+ }
+ /* Persistent phars should always be unserialized. */
+ const char *start;
+ /* Assert it should not be possible to create raw data in a persistent phar (i.e. from cache_list) */
- if (zip_metadata_len) {
- const unsigned char *p;
- unsigned char *p_buff = (unsigned char *)estrndup(*buffer, zip_metadata_len);
- p = p_buff;
+ /* Precondition: This has serialized data, either from setMetadata or the phar file. */
+ ZEND_ASSERT(tracker->str != NULL);
ZVAL_NULL(metadata);
- PHP_VAR_UNSERIALIZE_INIT(var_hash);
+ start = ZSTR_VAL(tracker->str);
- if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {
- efree(p_buff);
- PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+ php_unserialize_with_options(metadata, start, ZSTR_LEN(tracker->str), unserialize_options, method_name);
+ if (EG(exception)) {
zval_ptr_dtor(metadata);
ZVAL_UNDEF(metadata);
return FAILURE;
}
- efree(p_buff);
- PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
-
- if (PHAR_G(persist)) {
- /* lazy init metadata */
- zval_ptr_dtor(metadata);
- Z_PTR_P(metadata) = pemalloc(zip_metadata_len, 1);
- memcpy(Z_PTR_P(metadata), *buffer, zip_metadata_len);
- return SUCCESS;
- }
+ return SUCCESS;
} else {
- ZVAL_UNDEF(metadata);
+ /* TODO: what is the current/expected behavior when fetching an object set with setMetadata then getting it
+ * with getMetadata() and modifying a property? Previously, it was underdefined, and probably unimportant to support. */
+ ZVAL_COPY(metadata, &tracker->val);
}
return SUCCESS;
@@ -644,6 +638,86 @@ int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len
/* }}}*/
/**
+ * Check if this has any data, serialized or as a raw value.
+ */
+zend_bool phar_metadata_tracker_has_data(const phar_metadata_tracker *tracker, int persistent) /* {{{ */
+{
+ ZEND_ASSERT(!persistent || Z_ISUNDEF(tracker->val));
+ return !Z_ISUNDEF(tracker->val) || tracker->str != NULL;
+}
+/* }}} */
+
+/**
+ * Free memory used to track the metadata and set all fields to be null/undef.
+ */
+void phar_metadata_tracker_free(phar_metadata_tracker *tracker, int persistent) /* {{{ */
+{
+ /* Free the string before the zval in case the zval's destructor modifies the metadata */
+ if (tracker->str) {
+ zend_string_release(tracker->str);
+ tracker->str = NULL;
+ }
+ if (!Z_ISUNDEF(tracker->val)) {
+ /* Here, copy the original zval to a different pointer without incrementing the refcount in case something uses the original while it's being freed. */
+ zval zval_copy;
+
+ ZEND_ASSERT(!persistent);
+ ZVAL_COPY_VALUE(&zval_copy, &tracker->val);
+ ZVAL_UNDEF(&tracker->val);
+ zval_ptr_dtor(&zval_copy);
+ }
+}
+/* }}} */
+
+/**
+ * Free memory used to track the metadata and set all fields to be null/undef.
+ */
+void phar_metadata_tracker_copy(phar_metadata_tracker *dest, const phar_metadata_tracker *source, int persistent) /* {{{ */
+{
+ ZEND_ASSERT(dest != source);
+ phar_metadata_tracker_free(dest, persistent);
+
+ if (!Z_ISUNDEF(source->val)) {
+ ZEND_ASSERT(!persistent);
+ ZVAL_COPY(&dest->val, &source->val);
+ }
+ if (source->str) {
+ dest->str = zend_string_copy(source->str);
+ }
+}
+/* }}} */
+
+/**
+ * Increment reference counts after a metadata entry was copied
+ */
+void phar_metadata_tracker_clone(phar_metadata_tracker *tracker) /* {{{ */
+{
+ Z_TRY_ADDREF_P(&tracker->val);
+ if (tracker->str) {
+ tracker->str = zend_string_copy(tracker->str);
+ }
+}
+/* }}} */
+
+/**
+ * Parse out metadata from the manifest for a single file, saving it into a string.
+ *
+ * Meta-data is in this format:
+ * [len32][data...]
+ *
+ * data is the serialized zval
+ */
+void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker, uint32_t zip_metadata_len, int persistent) /* {{{ */
+{
+ phar_metadata_tracker_free(tracker, persistent);
+ if (zip_metadata_len) {
+ /* lazy init metadata */
+ tracker->str = zend_string_init(buffer, zip_metadata_len, persistent);
+ }
+}
+/* }}}*/
+
+/**
* Size of fixed fields in the manifest.
* See: http://php.net/manual/en/phar.fileformat.phar.php
*/
@@ -1028,7 +1102,6 @@ static int phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, ch
/* check whether we have meta data, zero check works regardless of byte order */
SAFE_PHAR_GET_32(buffer, endbuffer, len);
if (mydata->is_persistent) {
- mydata->metadata_len = len;
if (!len) {
/* FIXME: not sure why this is needed but removing it breaks tests */
SAFE_PHAR_GET_32(buffer, endbuffer, len);
@@ -1037,9 +1110,8 @@ static int phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, ch
if(len > (size_t)(endbuffer - buffer)) {
MAPPHAR_FAIL("internal corruption of phar \"%s\" (trying to read past buffer end)");
}
- if (phar_parse_metadata(&buffer, &mydata->metadata, len) == FAILURE) {
- MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\"");
- }
+ /* Don't implicitly call unserialize() on potentially untrusted input unless getMetadata() is called directly. */
+ phar_parse_metadata_lazy(buffer, &mydata->metadata_tracker, len, mydata->is_persistent);
buffer += len;
/* set up our manifest */
@@ -1112,19 +1184,15 @@ static int phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, ch
}
PHAR_GET_32(buffer, len);
- if (entry.is_persistent) {
- entry.metadata_len = len;
- } else {
- entry.metadata_len = 0;
- }
if (len > (size_t)(endbuffer - buffer)) {
pefree(entry.filename, entry.is_persistent);
MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)");
}
- if (phar_parse_metadata(&buffer, &entry.metadata, len) == FAILURE) {
- pefree(entry.filename, entry.is_persistent);
- MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\"");
- }
+ /* Don't implicitly call unserialize() on potentially untrusted input unless getMetadata() is called directly. */
+ /* The same local variable entry is reused in a loop, so reset the state before reading data. */
+ ZVAL_UNDEF(&entry.metadata_tracker.val);
+ entry.metadata_tracker.str = NULL;
+ phar_parse_metadata_lazy(buffer, &entry.metadata_tracker, len, entry.is_persistent);
buffer += len;
entry.offset = entry.offset_abs = offset;
@@ -1133,39 +1201,21 @@ static int phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, ch
switch (entry.flags & PHAR_ENT_COMPRESSION_MASK) {
case PHAR_ENT_COMPRESSED_GZ:
if (!PHAR_G(has_zlib)) {
- if (Z_TYPE(entry.metadata) != IS_UNDEF) {
- if (entry.is_persistent) {
- free(Z_PTR(entry.metadata));
- } else {
- zval_ptr_dtor(&entry.metadata);
- }
- }
+ phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
pefree(entry.filename, entry.is_persistent);
MAPPHAR_FAIL("zlib extension is required for gz compressed .phar file \"%s\"");
}
break;
case PHAR_ENT_COMPRESSED_BZ2:
if (!PHAR_G(has_bz2)) {
- if (Z_TYPE(entry.metadata) != IS_UNDEF) {
- if (entry.is_persistent) {
- free(Z_PTR(entry.metadata));
- } else {
- zval_ptr_dtor(&entry.metadata);
- }
- }
+ phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
pefree(entry.filename, entry.is_persistent);
MAPPHAR_FAIL("bz2 extension is required for bzip2 compressed .phar file \"%s\"");
}
break;
default:
if (entry.uncompressed_filesize != entry.compressed_filesize) {
- if (Z_TYPE(entry.metadata) != IS_UNDEF) {
- if (entry.is_persistent) {
- free(Z_PTR(entry.metadata));
- } else {
- zval_ptr_dtor(&entry.metadata);
- }
- }
+ phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
pefree(entry.filename, entry.is_persistent);
MAPPHAR_FAIL("internal corruption of phar \"%s\" (compressed and uncompressed size does not match for uncompressed entry)");
}
@@ -2673,9 +2723,11 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
/* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */
main_metadata_str.s = NULL;
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
+ if (phar->metadata_tracker.str) {
+ smart_str_appendl(&main_metadata_str, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
+ } else if (!Z_ISUNDEF(phar->metadata_tracker.val)) {
PHP_VAR_SERIALIZE_INIT(metadata_hash);
- php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash);
+ php_var_serialize(&main_metadata_str, &phar->metadata_tracker.val, &metadata_hash);
PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
}
new_manifest_count = 0;
@@ -2710,23 +2762,18 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
/* we use this to calculate API version, 1.1.1 is used for phars with directories */
has_dirs = 1;
}
- if (Z_TYPE(entry->metadata) != IS_UNDEF) {
- if (entry->metadata_str.s) {
- smart_str_free(&entry->metadata_str);
- }
- entry->metadata_str.s = NULL;
+ if (!Z_ISUNDEF(entry->metadata_tracker.val) && !entry->metadata_tracker.str) {
+ ZEND_ASSERT(!entry->is_persistent);
+ /* Assume serialization will succeed. TODO: Set error and throw if EG(exception) != NULL */
+ smart_str buf = {0};
PHP_VAR_SERIALIZE_INIT(metadata_hash);
- php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash);
+ php_var_serialize(&buf, &entry->metadata_tracker.val, &metadata_hash);
PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
- } else {
- if (entry->metadata_str.s) {
- smart_str_free(&entry->metadata_str);
- }
- entry->metadata_str.s = NULL;
+ entry->metadata_tracker.str = buf.s;
}
/* 32 bits for filename length, length of filename, manifest + metadata, and add 1 for trailing / if a directory */
- offset += 4 + entry->filename_len + sizeof(entry_buffer) + (entry->metadata_str.s ? ZSTR_LEN(entry->metadata_str.s) : 0) + (entry->is_dir ? 1 : 0);
+ offset += 4 + entry->filename_len + sizeof(entry_buffer) + (entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0) + (entry->is_dir ? 1 : 0);
/* compress and rehash as necessary */
if ((oldfile && !entry->is_modified) || entry->is_dir) {
@@ -2916,6 +2963,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
/* now write the manifest */
ZEND_HASH_FOREACH_PTR(&phar->manifest, entry) {
+ const zend_string *metadata_str;
if (entry->is_deleted || entry->is_mounted) {
/* remove this from the new phar if deleted, ignore if mounted */
continue;
@@ -2960,11 +3008,12 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
phar_set_32(entry_buffer+8, entry->compressed_filesize);
phar_set_32(entry_buffer+12, entry->crc32);
phar_set_32(entry_buffer+16, entry->flags);
- phar_set_32(entry_buffer+20, entry->metadata_str.s ? ZSTR_LEN(entry->metadata_str.s) : 0);
+ metadata_str = entry->metadata_tracker.str;
+ phar_set_32(entry_buffer+20, metadata_str ? ZSTR_LEN(metadata_str) : 0);
if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer))
- || (entry->metadata_str.s &&
- ZSTR_LEN(entry->metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s)))) {
+ || (metadata_str &&
+ ZSTR_LEN(metadata_str) != php_stream_write(newfile, ZSTR_VAL(metadata_str), ZSTR_LEN(metadata_str)))) {
if (closeoldfile) {
php_stream_close(oldfile);
}
diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h
index e999d9d757..f959cb29bc 100644
--- a/ext/phar/phar_internal.h
+++ b/ext/phar/phar_internal.h
@@ -212,6 +212,17 @@ enum phar_fp_type {
PHAR_TMP
};
+/*
+ * Represents the metadata of the phar file or a file entry within the phar.
+ * Can contain any combination of serialized data and the value as needed.
+ */
+typedef struct _phar_metadata_tracker {
+ /* Can be IS_UNDEF or a regular value */
+ zval val;
+ /* Nullable string with the serialized value, if the serialization was performed or read from a file. */
+ zend_string *str;
+} phar_metadata_tracker;
+
/* entry for one file in a phar file */
typedef struct _phar_entry_info {
/* first bytes are exactly as in file */
@@ -223,8 +234,7 @@ typedef struct _phar_entry_info {
/* remainder */
/* when changing compression, save old flags in case fp is NULL */
uint32_t old_flags;
- zval metadata;
- uint32_t metadata_len; /* only used for cached manifests */
+ phar_metadata_tracker metadata_tracker;
uint32_t filename_len;
char *filename;
enum phar_fp_type fp_type;
@@ -239,7 +249,6 @@ typedef struct _phar_entry_info {
int fp_refcount;
char *tmp;
phar_archive_data *phar;
- smart_str metadata_str;
char *link; /* symbolic link to another file */
char tar_type;
/* position in the manifest */
@@ -291,8 +300,7 @@ struct _phar_archive_data {
uint32_t sig_flags;
uint32_t sig_len;
char *signature;
- zval metadata;
- uint32_t metadata_len; /* only used for cached manifests */
+ phar_metadata_tracker metadata_tracker;
uint32_t phar_pos;
/* if 1, then this alias was manually specified by the user and is not a permanent alias */
uint32_t is_temporary_alias:1;
@@ -544,7 +552,16 @@ int phar_mount_entry(phar_archive_data *phar, char *filename, size_t filename_le
zend_string *phar_find_in_include_path(char *file, size_t file_len, phar_archive_data **pphar);
char *phar_fix_filepath(char *path, size_t *new_len, int use_cwd);
phar_entry_info * phar_open_jit(phar_archive_data *phar, phar_entry_info *entry, char **error);
-int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len);
+void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker, uint32_t zip_metadata_len, int persistent);
+zend_bool phar_metadata_tracker_has_data(const phar_metadata_tracker* tracker, int persistent);
+/* If this has data, free it and set all values to undefined. */
+void phar_metadata_tracker_free(phar_metadata_tracker* val, int persistent);
+void phar_metadata_tracker_copy(phar_metadata_tracker* dest, const phar_metadata_tracker *source, int persistent);
+void phar_metadata_tracker_clone(phar_metadata_tracker* tracker);
+void phar_metadata_tracker_try_ensure_has_serialized_data(phar_metadata_tracker* tracker, int persistent);
+int phar_metadata_tracker_unserialize_or_copy(phar_metadata_tracker* tracker, zval *value, int persistent, HashTable *unserialize_options, const char* method_name);
+void phar_release_entry_metadata(phar_entry_info *entry);
+void phar_release_archive_metadata(phar_archive_data *phar);
void destroy_phar_manifest_entry(zval *zv);
int phar_seek_efp(phar_entry_info *entry, zend_off_t offset, int whence, zend_off_t position, int follow_links);
php_stream *phar_get_efp(phar_entry_info *entry, int follow_links);
diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index 74f1ea4b73..61a6473aca 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -2266,10 +2266,7 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert
phar->is_temporary_alias = source->is_temporary_alias;
phar->alias = source->alias;
- if (Z_TYPE(source->metadata) != IS_UNDEF) {
- ZVAL_DUP(&phar->metadata, &source->metadata);
- phar->metadata_len = 0;
- }
+ phar_metadata_tracker_copy(&phar->metadata_tracker, &source->metadata_tracker, phar->is_persistent);
/* first copy each file's uncompressed contents to a temporary file and set per-file flags */
ZEND_HASH_FOREACH_PTR(&source->manifest, entry) {
@@ -2286,8 +2283,6 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert
goto no_copy;
}
- newentry.metadata_str.s = NULL;
-
if (FAILURE == phar_copy_file_contents(&newentry, phar->fp)) {
zend_hash_destroy(&(phar->manifest));
php_stream_close(phar->fp);
@@ -2298,10 +2293,7 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert
no_copy:
newentry.filename = estrndup(newentry.filename, newentry.filename_len);
- if (Z_TYPE(newentry.metadata) != IS_UNDEF) {
- zval_copy_ctor(&newentry.metadata);
- newentry.metadata_str.s = NULL;
- }
+ phar_metadata_tracker_clone(&newentry.metadata_tracker);
newentry.is_zip = phar->is_zip;
newentry.is_tar = phar->is_tar;
@@ -3444,10 +3436,7 @@ PHP_METHOD(Phar, copy)
memcpy((void *) &newentry, oldentry, sizeof(phar_entry_info));
- if (Z_TYPE(newentry.metadata) != IS_UNDEF) {
- zval_copy_ctor(&newentry.metadata);
- newentry.metadata_str.s = NULL;
- }
+ phar_metadata_tracker_clone(&newentry.metadata_tracker);
newentry.filename = estrndup(newfile, newfile_len);
newentry.filename_len = newfile_len;
@@ -3951,32 +3940,60 @@ PHP_METHOD(Phar, hasMetadata)
RETURN_THROWS();
}
- RETURN_BOOL(Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF);
+ RETURN_BOOL(phar_metadata_tracker_has_data(&phar_obj->archive->metadata_tracker, phar_obj->archive->is_persistent));
}
/* }}} */
/* {{{ Returns the global metadata of the phar */
PHP_METHOD(Phar, getMetadata)
{
+ HashTable *unserialize_options = NULL;
+ phar_metadata_tracker *tracker;
PHAR_ARCHIVE_OBJECT();
- if (zend_parse_parameters_none() == FAILURE) {
- RETURN_THROWS();
- }
+ ZEND_PARSE_PARAMETERS_START(0, 1)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ARRAY_HT(unserialize_options)
+ ZEND_PARSE_PARAMETERS_END();
- if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) {
- if (phar_obj->archive->is_persistent) {
- char *buf = estrndup((char *) Z_PTR(phar_obj->archive->metadata), phar_obj->archive->metadata_len);
- /* assume success, we would have failed before */
- phar_parse_metadata(&buf, return_value, phar_obj->archive->metadata_len);
- efree(buf);
- } else {
- ZVAL_COPY(return_value, &phar_obj->archive->metadata);
- }
+ tracker = &phar_obj->archive->metadata_tracker;
+ if (phar_metadata_tracker_has_data(tracker, phar_obj->archive->is_persistent)) {
+ phar_metadata_tracker_unserialize_or_copy(tracker, return_value, phar_obj->archive->is_persistent, unserialize_options, "Phar::getMetadata");
}
}
/* }}} */
+/* {{{ Modifies the phar metadata or throws */
+static int serialize_metadata_or_throw(phar_metadata_tracker *tracker, int persistent, zval *metadata)
+{
+ php_serialize_data_t metadata_hash;
+ smart_str main_metadata_str = {0};
+
+ PHP_VAR_SERIALIZE_INIT(metadata_hash);
+ php_var_serialize(&main_metadata_str, metadata, &metadata_hash);
+ PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
+ if (EG(exception)) {
+ /* Serialization can throw. Don't overwrite the original value or original string. */
+ return FAILURE;
+ }
+
+ phar_metadata_tracker_free(tracker, persistent);
+ if (EG(exception)) {
+ /* Destructor can throw. */
+ zend_string_release(main_metadata_str.s);
+ return FAILURE;
+ }
+
+ if (tracker->str) {
+ zend_throw_exception_ex(phar_ce_PharException, 0, "Metadata unexpectedly changed during setMetadata()");
+ zend_string_release(main_metadata_str.s);
+ return FAILURE;
+ }
+ ZVAL_COPY(&tracker->val, metadata);
+ tracker->str = main_metadata_str.s;
+ return SUCCESS;
+}
+
/* {{{ Sets the global metadata of the phar */
PHP_METHOD(Phar, setMetadata)
{
@@ -3998,12 +4015,12 @@ PHP_METHOD(Phar, setMetadata)
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname);
RETURN_THROWS();
}
- if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) {
- zval_ptr_dtor(&phar_obj->archive->metadata);
- ZVAL_UNDEF(&phar_obj->archive->metadata);
+
+ ZEND_ASSERT(!phar_obj->archive->is_persistent); /* Should no longer be persistent */
+ if (serialize_metadata_or_throw(&phar_obj->archive->metadata_tracker, phar_obj->archive->is_persistent, metadata) != SUCCESS) {
+ RETURN_THROWS();
}
- ZVAL_COPY(&phar_obj->archive->metadata, metadata);
phar_obj->archive->is_modified = 1;
phar_flush(phar_obj->archive, 0, 0, 0, &error);
@@ -4030,20 +4047,18 @@ PHP_METHOD(Phar, delMetadata)
RETURN_THROWS();
}
- if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) {
- zval_ptr_dtor(&phar_obj->archive->metadata);
- ZVAL_UNDEF(&phar_obj->archive->metadata);
- phar_obj->archive->is_modified = 1;
- phar_flush(phar_obj->archive, 0, 0, 0, &error);
+ if (!phar_metadata_tracker_has_data(&phar_obj->archive->metadata_tracker, phar_obj->archive->is_persistent)) {
+ RETURN_TRUE;
+ }
- if (error) {
- zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error);
- efree(error);
- RETURN_THROWS();
- } else {
- RETURN_TRUE;
- }
+ phar_metadata_tracker_free(&phar_obj->archive->metadata_tracker, phar_obj->archive->is_persistent);
+ phar_obj->archive->is_modified = 1;
+ phar_flush(phar_obj->archive, 0, 0, 0, &error);
+ if (error) {
+ zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error);
+ efree(error);
+ RETURN_THROWS();
} else {
RETURN_TRUE;
}
@@ -4630,28 +4645,25 @@ PHP_METHOD(PharFileInfo, hasMetadata)
RETURN_THROWS();
}
- RETURN_BOOL(Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF);
+ RETURN_BOOL(phar_metadata_tracker_has_data(&entry_obj->entry->metadata_tracker, entry_obj->entry->is_persistent));
}
/* }}} */
/* {{{ Returns the metadata of the entry */
PHP_METHOD(PharFileInfo, getMetadata)
{
+ HashTable *unserialize_options = NULL;
+ phar_metadata_tracker *tracker;
PHAR_ENTRY_OBJECT();
- if (zend_parse_parameters_none() == FAILURE) {
- RETURN_THROWS();
- }
+ ZEND_PARSE_PARAMETERS_START(0, 1)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ARRAY_HT(unserialize_options)
+ ZEND_PARSE_PARAMETERS_END();
- if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) {
- if (entry_obj->entry->is_persistent) {
- char *buf = estrndup((char *) Z_PTR(entry_obj->entry->metadata), entry_obj->entry->metadata_len);
- /* assume success, we would have failed before */
- phar_parse_metadata(&buf, return_value, entry_obj->entry->metadata_len);
- efree(buf);
- } else {
- ZVAL_COPY(return_value, &entry_obj->entry->metadata);
- }
+ tracker = &entry_obj->entry->metadata_tracker;
+ if (phar_metadata_tracker_has_data(tracker, entry_obj->entry->is_persistent)) {
+ phar_metadata_tracker_unserialize_or_copy(tracker, return_value, entry_obj->entry->is_persistent, unserialize_options, "PharFileInfo::getMetadata");
}
}
/* }}} */
@@ -4688,13 +4700,12 @@ PHP_METHOD(PharFileInfo, setMetadata)
}
/* re-populate after copy-on-write */
entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len);
- }
- if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) {
- zval_ptr_dtor(&entry_obj->entry->metadata);
- ZVAL_UNDEF(&entry_obj->entry->metadata);
+ ZEND_ASSERT(!entry_obj->entry->is_persistent); /* Should no longer be persistent */
}
- ZVAL_COPY(&entry_obj->entry->metadata, metadata);
+ if (serialize_metadata_or_throw(&entry_obj->entry->metadata_tracker, entry_obj->entry->is_persistent, metadata) != SUCCESS) {
+ RETURN_THROWS();
+ }
entry_obj->entry->is_modified = 1;
entry_obj->entry->phar->is_modified = 1;
@@ -4729,7 +4740,7 @@ PHP_METHOD(PharFileInfo, delMetadata)
RETURN_THROWS();
}
- if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) {
+ if (phar_metadata_tracker_has_data(&entry_obj->entry->metadata_tracker, entry_obj->entry->is_persistent)) {
if (entry_obj->entry->is_persistent) {
phar_archive_data *phar = entry_obj->entry->phar;
@@ -4740,8 +4751,8 @@ PHP_METHOD(PharFileInfo, delMetadata)
/* re-populate after copy-on-write */
entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len);
}
- zval_ptr_dtor(&entry_obj->entry->metadata);
- ZVAL_UNDEF(&entry_obj->entry->metadata);
+ /* multiple values may reference the metadata */
+ phar_metadata_tracker_free(&entry_obj->entry->metadata_tracker, entry_obj->entry->is_persistent);
entry_obj->entry->is_modified = 1;
entry_obj->entry->phar->is_modified = 1;
diff --git a/ext/phar/phar_object.stub.php b/ext/phar/phar_object.stub.php
index f3439d851a..fe5fed1406 100644
--- a/ext/phar/phar_object.stub.php
+++ b/ext/phar/phar_object.stub.php
@@ -67,7 +67,7 @@ class Phar extends RecursiveDirectoryIterator implements Countable, ArrayAccess
public function getPath() {}
/** @return mixed */
- public function getMetadata() {}
+ public function getMetadata(array $unserialize_options = []) {}
/** @return bool */
public function getModified() {}
@@ -303,7 +303,7 @@ class PharData extends RecursiveDirectoryIterator implements Countable, ArrayAcc
* @return mixed
* @alias Phar::getMetadata
*/
- public function getMetadata() {}
+ public function getMetadata(array $unserialize_options = []) {}
/**
* @return bool
@@ -510,7 +510,7 @@ class PharFileInfo extends SplFileInfo
public function getContent() {}
/** @return mixed */
- public function getMetadata() {}
+ public function getMetadata(array $unserialize_options = []) {}
/** @return int */
public function getPharFlags() {}
diff --git a/ext/phar/phar_object_arginfo.h b/ext/phar/phar_object_arginfo.h
index 8c6294d324..a4b446d7c5 100644
--- a/ext/phar/phar_object_arginfo.h
+++ b/ext/phar/phar_object_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: fd4f05b74248e4f7efb234cac8e3a90e17037ee0 */
+ * Stub hash: 586c79f097e9153b70f6c6e208daa08acc0ce1d4 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Phar___construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
@@ -82,7 +82,9 @@ ZEND_END_ARG_INFO()
#define arginfo_class_Phar_getPath arginfo_class_Phar___destruct
-#define arginfo_class_Phar_getMetadata arginfo_class_Phar___destruct
+ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Phar_getMetadata, 0, 0, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, unserialize_options, IS_ARRAY, 0, "[]")
+ZEND_END_ARG_INFO()
#define arginfo_class_Phar_getModified arginfo_class_Phar___destruct
@@ -252,7 +254,7 @@ ZEND_END_ARG_INFO()
#define arginfo_class_PharData_getPath arginfo_class_Phar___destruct
-#define arginfo_class_PharData_getMetadata arginfo_class_Phar___destruct
+#define arginfo_class_PharData_getMetadata arginfo_class_Phar_getMetadata
#define arginfo_class_PharData_getModified arginfo_class_Phar___destruct
@@ -346,7 +348,7 @@ ZEND_END_ARG_INFO()
#define arginfo_class_PharFileInfo_getContent arginfo_class_Phar___destruct
-#define arginfo_class_PharFileInfo_getMetadata arginfo_class_Phar___destruct
+#define arginfo_class_PharFileInfo_getMetadata arginfo_class_Phar_getMetadata
#define arginfo_class_PharFileInfo_getPharFlags arginfo_class_Phar___destruct
diff --git a/ext/phar/stream.c b/ext/phar/stream.c
index bdc15683de..91ed30cf03 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -223,13 +223,10 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
idata->internal_file->flags |= Z_LVAL_P(pzoption);
}
if ((pzoption = zend_hash_str_find(pharcontext, "metadata", sizeof("metadata")-1)) != NULL) {
- if (Z_TYPE(idata->internal_file->metadata) != IS_UNDEF) {
- zval_ptr_dtor(&idata->internal_file->metadata);
- ZVAL_UNDEF(&idata->internal_file->metadata);
- }
+ phar_metadata_tracker_free(&idata->internal_file->metadata_tracker, idata->internal_file->is_persistent);
metadata = pzoption;
- ZVAL_COPY_DEREF(&idata->internal_file->metadata, metadata);
+ ZVAL_COPY_DEREF(&idata->internal_file->metadata_tracker.val, metadata);
idata->phar->is_modified = 1;
}
}
@@ -846,7 +843,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
/* mark the old one for deletion */
entry->is_deleted = 1;
entry->fp = NULL;
- ZVAL_UNDEF(&entry->metadata);
+ ZVAL_UNDEF(&entry->metadata_tracker.val);
entry->link = entry->tmp = NULL;
source = entry;
diff --git a/ext/phar/tar.c b/ext/phar/tar.c
index 89b91b25f9..fb9cacfc73 100644
--- a/ext/phar/tar.c
+++ b/ext/phar/tar.c
@@ -173,28 +173,25 @@ static int phar_tar_process_metadata(phar_entry_info *entry, php_stream *fp) /*
return FAILURE;
}
- if (phar_parse_metadata(&metadata, &entry->metadata, entry->uncompressed_filesize) == FAILURE) {
- /* if not valid serialized data, it is a regular string */
- efree(metadata);
- php_stream_seek(fp, save, SEEK_SET);
- return FAILURE;
- }
+ phar_parse_metadata_lazy(metadata, &entry->metadata_tracker, entry->uncompressed_filesize, entry->is_persistent);
if (entry->filename_len == sizeof(".phar/.metadata.bin")-1 && !memcmp(entry->filename, ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1)) {
- if (Z_TYPE(entry->phar->metadata) != IS_UNDEF) {
+ if (phar_metadata_tracker_has_data(&entry->phar->metadata_tracker, entry->phar->is_persistent)) {
efree(metadata);
return FAILURE;
}
- entry->phar->metadata = entry->metadata;
- ZVAL_UNDEF(&entry->metadata);
+ entry->phar->metadata_tracker = entry->metadata_tracker;
+ entry->metadata_tracker.str = NULL;
+ ZVAL_UNDEF(&entry->metadata_tracker.val);
} else if (entry->filename_len >= sizeof(".phar/.metadata/") + sizeof("/.metadata.bin") - 1 && NULL != (mentry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->filename + sizeof(".phar/.metadata/") - 1, entry->filename_len - (sizeof("/.metadata.bin") - 1 + sizeof(".phar/.metadata/") - 1)))) {
- if (Z_TYPE(mentry->metadata) != IS_UNDEF) {
+ if (phar_metadata_tracker_has_data(&mentry->metadata_tracker, mentry->is_persistent)) {
efree(metadata);
return FAILURE;
}
/* transfer this metadata to the entry it refers */
- mentry->metadata = entry->metadata;
- ZVAL_UNDEF(&entry->metadata);
+ mentry->metadata_tracker = entry->metadata_tracker;
+ entry->metadata_tracker.str = NULL;
+ ZVAL_UNDEF(&entry->metadata_tracker.val);
}
efree(metadata);
@@ -864,19 +861,16 @@ static int phar_tar_writeheaders(zval *zv, void *argument) /* {{{ */
}
/* }}} */
-int phar_tar_setmetadata(zval *metadata, phar_entry_info *entry, char **error) /* {{{ */
+int phar_tar_setmetadata(const phar_metadata_tracker *tracker, phar_entry_info *entry, char **error) /* {{{ */
{
- php_serialize_data_t metadata_hash;
-
- if (entry->metadata_str.s) {
- smart_str_free(&entry->metadata_str);
- }
+ /* Copy the metadata from tracker to the new entry being written out to temporary files */
+ const zend_string *serialized_str;
+ phar_metadata_tracker_copy(&entry->metadata_tracker, tracker, entry->is_persistent);
+ phar_metadata_tracker_try_ensure_has_serialized_data(&entry->metadata_tracker, entry->is_persistent);
+ serialized_str = entry->metadata_tracker.str;
- entry->metadata_str.s = NULL;
- PHP_VAR_SERIALIZE_INIT(metadata_hash);
- php_var_serialize(&entry->metadata_str, metadata, &metadata_hash);
- PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
- entry->uncompressed_filesize = entry->compressed_filesize = entry->metadata_str.s ? ZSTR_LEN(entry->metadata_str.s) : 0;
+ /* If there is no data, this will replace the metadata file (e.g. .phar/.metadata.bin) with an empty file */
+ entry->uncompressed_filesize = entry->compressed_filesize = serialized_str ? ZSTR_LEN(serialized_str) : 0;
if (entry->fp && entry->fp_type == PHAR_MOD) {
php_stream_close(entry->fp);
@@ -890,7 +884,7 @@ int phar_tar_setmetadata(zval *metadata, phar_entry_info *entry, char **error) /
spprintf(error, 0, "phar error: unable to create temporary file");
return -1;
}
- if (ZSTR_LEN(entry->metadata_str.s) != php_stream_write(entry->fp, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s))) {
+ if (serialized_str && ZSTR_LEN(serialized_str) != php_stream_write(entry->fp, ZSTR_VAL(serialized_str), ZSTR_LEN(serialized_str))) {
spprintf(error, 0, "phar tar error: unable to write metadata to magic metadata file \"%s\"", entry->filename);
zend_hash_str_del(&(entry->phar->manifest), entry->filename, entry->filename_len);
return ZEND_HASH_APPLY_STOP;
@@ -909,7 +903,7 @@ static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */
if (entry->filename_len >= sizeof(".phar/.metadata") && !memcmp(entry->filename, ".phar/.metadata", sizeof(".phar/.metadata")-1)) {
if (entry->filename_len == sizeof(".phar/.metadata.bin")-1 && !memcmp(entry->filename, ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1)) {
- return phar_tar_setmetadata(&entry->phar->metadata, entry, error);
+ return phar_tar_setmetadata(&entry->phar->metadata_tracker, entry, error);
}
/* search for the file this metadata entry references */
if (entry->filename_len >= sizeof(".phar/.metadata/") + sizeof("/.metadata.bin") - 1 && !zend_hash_str_exists(&(entry->phar->manifest), entry->filename + sizeof(".phar/.metadata/") - 1, entry->filename_len - (sizeof("/.metadata.bin") - 1 + sizeof(".phar/.metadata/") - 1))) {
@@ -927,7 +921,7 @@ static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */
/* now we are dealing with regular files, so look for metadata */
lookfor_len = spprintf(&lookfor, 0, ".phar/.metadata/%s/.metadata.bin", entry->filename);
- if (Z_TYPE(entry->metadata) == IS_UNDEF) {
+ if (!phar_metadata_tracker_has_data(&entry->metadata_tracker, entry->is_persistent)) {
zend_hash_str_del(&(entry->phar->manifest), lookfor, lookfor_len);
efree(lookfor);
return ZEND_HASH_APPLY_KEEP;
@@ -935,7 +929,7 @@ static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */
if (NULL != (metadata = zend_hash_str_find_ptr(&(entry->phar->manifest), lookfor, lookfor_len))) {
int ret;
- ret = phar_tar_setmetadata(&entry->metadata, metadata, error);
+ ret = phar_tar_setmetadata(&entry->metadata_tracker, metadata, error);
efree(lookfor);
return ret;
}
@@ -952,7 +946,7 @@ static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */
return ZEND_HASH_APPLY_STOP;
}
- return phar_tar_setmetadata(&entry->metadata, metadata, error);
+ return phar_tar_setmetadata(&entry->metadata_tracker, metadata, error);
}
/* }}} */
@@ -1165,10 +1159,10 @@ nostub:
pass.free_fp = 1;
pass.free_ufp = 1;
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
+ if (phar_metadata_tracker_has_data(&phar->metadata_tracker, phar->is_persistent)) {
phar_entry_info *mentry;
if (NULL != (mentry = zend_hash_str_find_ptr(&(phar->manifest), ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1))) {
- if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(&phar->metadata, mentry, error)) {
+ if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(&phar->metadata_tracker, mentry, error)) {
if (closeoldfile) {
php_stream_close(oldfile);
}
@@ -1191,7 +1185,7 @@ nostub:
return EOF;
}
- if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(&phar->metadata, mentry, error)) {
+ if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(&phar->metadata_tracker, mentry, error)) {
zend_hash_str_del(&(phar->manifest), ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1);
if (closeoldfile) {
php_stream_close(oldfile);
diff --git a/ext/phar/tests/bug69720.phpt b/ext/phar/tests/bug69720.phpt
index cdb4741ff8..5c9b76f367 100644
--- a/ext/phar/tests/bug69720.phpt
+++ b/ext/phar/tests/bug69720.phpt
@@ -11,10 +11,10 @@ try {
echo $p->getMetadata();
foreach (new RecursiveIteratorIterator($p) as $file) {
// $file is a PharFileInfo class, and inherits from SplFileInfo
- $temp="";
+ $temp="";
$temp= $file->getFileName() . "\n";
$temp.=file_get_contents($file->getPathName()) . "\n"; // display contents
- var_dump($file->getMetadata());
+ var_dump($file->getMetadata());
}
}
catch (Exception $e) {
@@ -29,7 +29,7 @@ array(1) {
["whatever"]=>
int(123)
}
-object(DateTime)#2 (3) {
+object(DateTime)#6 (3) {
["date"]=>
string(26) "2000-01-01 00:00:00.000000"
["timezone_type"]=>
diff --git a/ext/phar/tests/bug69958.phpt b/ext/phar/tests/bug69958.phpt
index 6df1178bef..15f1edc369 100644
--- a/ext/phar/tests/bug69958.phpt
+++ b/ext/phar/tests/bug69958.phpt
@@ -9,7 +9,7 @@ Still has memory leaks, see https://bugs.php.net/bug.php?id=70005
$tarphar = new PharData(__DIR__.'/bug69958.tar');
$phar = $tarphar->convertToData(Phar::TAR);
--EXPECTF--
-Fatal error: Uncaught exception 'BadMethodCallException' with message 'phar "%s/bug69958.tar" exists and must be unlinked prior to conversion' in %s/bug69958.php:%d
+Fatal error: Uncaught BadMethodCallException: phar "%s/bug69958.tar" exists and must be unlinked prior to conversion in %s/bug69958.php:%d
Stack trace:
#0 %s/bug69958.php(%d): PharData->convertToData(%d)
#1 {main}
diff --git a/ext/phar/tests/phar_metadata_write2.phpt b/ext/phar/tests/phar_metadata_write2.phpt
new file mode 100644
index 0000000000..52e63a290c
--- /dev/null
+++ b/ext/phar/tests/phar_metadata_write2.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Phar with object in metadata
+--SKIPIF--
+<?php
+if (!extension_loaded("phar")) die("skip");
+?>
+--INI--
+phar.require_hash=0
+phar.readonly=0
+--FILE--
+<?php
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar.php';
+$pname = 'phar://' . $fname;
+$file = "<?php __HALT_COMPILER(); ?>";
+
+$files = array();
+$files['a'] = array('cont' => 'a');
+include 'files/phar_test.inc';
+
+foreach($files as $name => $cont) {
+ var_dump(file_get_contents($pname.'/'.$name));
+}
+
+$phar = new Phar($fname);
+var_dump($phar->getMetadata());
+$phar->setMetadata((object) ['my' => 'friend']);
+unset($phar);
+// NOTE: Phar will use the cached value of metadata if setMetaData was called on that Phar path before.
+// Save the writes to the phar and use a different file path.
+$fname_new = "$fname.copy.php";
+copy($fname, $fname_new);
+$phar = new Phar($fname_new);
+var_dump($phar->getMetadata());
+
+?>
+--CLEAN--
+<?php
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php');
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php.copy.php');
+?>
+--EXPECT--
+string(1) "a"
+NULL
+object(stdClass)#2 (1) {
+ ["my"]=>
+ string(6) "friend"
+}
diff --git a/ext/phar/tests/phar_metadata_write3.phpt b/ext/phar/tests/phar_metadata_write3.phpt
new file mode 100644
index 0000000000..d42f95c161
--- /dev/null
+++ b/ext/phar/tests/phar_metadata_write3.phpt
@@ -0,0 +1,106 @@
+--TEST--
+Phar with unsafe object in metadata does not unserialize on reading a file.
+--SKIPIF--
+<?php
+if (!extension_loaded("phar")) die("skip");
+?>
+--INI--
+phar.require_hash=0
+phar.readonly=0
+--FILE--
+<?php
+class EchoesOnWakeup {
+ public function __wakeup() {
+ echo "In wakeup\n";
+ }
+}
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar.php';
+$pname = 'phar://' . $fname;
+$file = "<?php __HALT_COMPILER(); ?>";
+
+$files = array();
+$files['a'] = array('cont' => 'contents of file a');
+include 'files/phar_test.inc';
+
+echo "Reading file contents through stream wrapper\n";
+foreach($files as $name => $cont) {
+ var_dump(file_get_contents($pname.'/'.$name));
+}
+
+$phar = new Phar($fname);
+echo "Original metadata\n";
+var_dump($phar->getMetadata());
+$phar->setMetadata(new EchoesOnWakeup());
+unset($phar);
+// NOTE: Phar will use the cached value of metadata if setMetaData was called on that Phar path before.
+// Save the writes to the phar and use a different file path.
+$fname_new = "$fname.copy.php";
+copy($fname, $fname_new);
+$phar = new Phar($fname_new);
+echo "Calling getMetadata\n";
+var_dump($phar->getMetadata());
+echo "Calling getMetadata with no allowed_classes\n";
+var_dump($phar->getMetadata(['allowed_classes' => []]));
+echo "Calling getMetadata with EchoesOnWakeup allowed\n";
+var_dump($phar->getMetadata(['allowed_classes' => [EchoesOnWakeup::class]]));
+// Part of this is a test that there are no unexpected behaviors when both selMetadata and getMetadata are used
+$phar->setMetaData([new EchoesOnWakeup(), new stdClass()]);
+echo "Calling getMetadata with too low max_depth\n";
+var_dump($phar->getMetadata(['max_depth' => 1]));
+echo "Calling getMetadata with some allowed classes\n";
+var_dump($phar->getMetadata(['allowed_classes' => [EchoesOnWakeup::class]]));
+echo "Calling getMetadata with no options returns the original metadata value\n";
+var_dump($phar->getMetadata());
+unset($phar);
+
+?>
+--CLEAN--
+<?php
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php');
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php.copy.php');
+?>
+--EXPECTF--
+Reading file contents through stream wrapper
+string(18) "contents of file a"
+Original metadata
+NULL
+Calling getMetadata
+In wakeup
+object(EchoesOnWakeup)#2 (0) {
+}
+Calling getMetadata with no allowed_classes
+object(__PHP_Incomplete_Class)#2 (1) {
+ ["__PHP_Incomplete_Class_Name"]=>
+ string(14) "EchoesOnWakeup"
+}
+Calling getMetadata with EchoesOnWakeup allowed
+In wakeup
+object(EchoesOnWakeup)#2 (0) {
+}
+Calling getMetadata with too low max_depth
+
+Warning: Phar::getMetadata(): Maximum depth of 1 exceeded. The depth limit can be changed using the max_depth unserialize() option or the unserialize_max_depth ini setting in %sphar_metadata_write3.php on line 39
+
+Notice: Phar::getMetadata(): Error at offset 34 of 59 bytes in %sphar_metadata_write3.php on line 39
+bool(false)
+Calling getMetadata with some allowed classes
+In wakeup
+array(2) {
+ [0]=>
+ object(EchoesOnWakeup)#4 (0) {
+ }
+ [1]=>
+ object(__PHP_Incomplete_Class)#5 (1) {
+ ["__PHP_Incomplete_Class_Name"]=>
+ string(8) "stdClass"
+ }
+}
+Calling getMetadata with no options returns the original metadata value
+array(2) {
+ [0]=>
+ object(EchoesOnWakeup)#2 (0) {
+ }
+ [1]=>
+ object(stdClass)#3 (0) {
+ }
+}
diff --git a/ext/phar/tests/phar_metadata_write4.phpt b/ext/phar/tests/phar_metadata_write4.phpt
new file mode 100644
index 0000000000..dfe46bab10
--- /dev/null
+++ b/ext/phar/tests/phar_metadata_write4.phpt
@@ -0,0 +1,103 @@
+--TEST--
+Phar with object in metadata
+--SKIPIF--
+<?php
+if (!extension_loaded("phar")) die("skip");
+?>
+--INI--
+phar.require_hash=0
+phar.readonly=0
+--FILE--
+<?php
+class EchoesOnWakeup {
+ public function __wakeup() {
+ echo "In __wakeup " . spl_object_id($this) . "\n";
+ }
+ public function __destruct() {
+ echo "In __destruct " . spl_object_id($this) . "\n";
+ }
+}
+class ThrowsOnSerialize {
+ public function __sleep() {
+ throw new RuntimeException("In sleep");
+ }
+}
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar.php';
+$pname = 'phar://' . $fname;
+$file = "<?php __HALT_COMPILER(); ?>";
+
+$files = array();
+$files['a'] = array('cont' => 'a', 'meta' => new EchoesOnWakeup());
+include 'files/phar_test.inc';
+
+foreach($files as $name => $cont) {
+ var_dump(file_get_contents($pname.'/'.$name));
+}
+unset($files);
+
+$phar = new Phar($fname);
+echo "Loading metadata for 'a' without allowed_classes\n";
+var_dump($phar['a']->getMetadata(['allowed_classes' => []]));
+echo "Loading metadata for 'a' with allowed_classes\n";
+var_dump($phar['a']->getMetadata(['allowed_classes' => true]));
+unset($phar);
+// NOTE: Phar will use the cached value of metadata if setMetaData was called on that Phar path before.
+// Save the writes to the phar and use a different file path.
+$fname_new = "$fname.copy.php";
+copy($fname, $fname_new);
+$phar = new Phar($fname_new);
+echo "Loading metadata from 'a' from the new phar\n";
+var_dump($phar['a']->getMetadata());
+echo "Loading metadata from 'a' from the new phar with unserialize options\n";
+var_dump($phar['a']->getMetadata(['allowed_classes' => true]));
+// PharEntry->setMetaData will do the following:
+// 1. serialize, checking for exceptions
+// 2. free the original data, checking for exceptions or the data getting set from destructors or error handlers.
+// 3. set the new data.
+try {
+ var_dump($phar['a']->setMetadata(new ThrowsOnSerialize()));
+} catch (RuntimeException $e) {
+ echo "Caught {$e->getMessage()} at {$e->getFile()}:{$e->getLine()}\n";
+ unset($e);
+}
+var_dump($phar['a']->getMetadata([]));
+var_dump($phar['a']->getMetadata(['allowed_classes' => false]));
+
+?>
+--CLEAN--
+<?php
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php');
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php.copy.php');
+?>
+--EXPECTF--
+In __destruct 1
+string(1) "a"
+Loading metadata for 'a' without allowed_classes
+object(__PHP_Incomplete_Class)#3 (1) {
+ ["__PHP_Incomplete_Class_Name"]=>
+ string(14) "EchoesOnWakeup"
+}
+Loading metadata for 'a' with allowed_classes
+In __wakeup 2
+object(EchoesOnWakeup)#2 (0) {
+}
+In __destruct 2
+Loading metadata from 'a' from the new phar
+In __wakeup 3
+object(EchoesOnWakeup)#3 (0) {
+}
+In __destruct 3
+Loading metadata from 'a' from the new phar with unserialize options
+In __wakeup 2
+object(EchoesOnWakeup)#2 (0) {
+}
+In __destruct 2
+Caught In sleep at %sphar_metadata_write4.php:12
+In __wakeup 3
+object(EchoesOnWakeup)#3 (0) {
+}
+In __destruct 3
+object(__PHP_Incomplete_Class)#4 (1) {
+ ["__PHP_Incomplete_Class_Name"]=>
+ string(14) "EchoesOnWakeup"
+} \ No newline at end of file
diff --git a/ext/phar/tests/tar/all.phpt b/ext/phar/tests/tar/all.phpt
index 66e9c1e951..87d55eaaf6 100644
--- a/ext/phar/tests/tar/all.phpt
+++ b/ext/phar/tests/tar/all.phpt
@@ -3,8 +3,6 @@ Phar: test that creation of tar-based phar generates valid tar with all bells/wh
--SKIPIF--
<?php
if (!extension_loaded("phar")) die("skip");
-if (!extension_loaded("zlib")) die("skip zlib not available");
-if (!extension_loaded("bz2")) die("skip bz2 not available");
?>
--INI--
phar.readonly=0
diff --git a/ext/phar/tests/tar/bug70417.phpt b/ext/phar/tests/tar/bug70417.phpt
index 0202ca9472..f288efc8f5 100644
--- a/ext/phar/tests/tar/bug70417.phpt
+++ b/ext/phar/tests/tar/bug70417.phpt
@@ -13,7 +13,7 @@ if ($status !== 0) {
--FILE--
<?php
function countOpenFiles() {
- exec('lsof -p ' . escapeshellarg(getmypid()) . ' 2> /dev/null', $out);
+ exec('lsof -p ' . escapeshellarg(getmypid()) . ' 2> /dev/null', $out); // Note: valgrind can produce false positives for /usr/bin/lsof
return count($out);
}
$filename = __DIR__ . '/bug70417.tar';
diff --git a/ext/phar/util.c b/ext/phar/util.c
index 35322734d0..6c084d8458 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -1968,21 +1968,11 @@ static int phar_update_cached_entry(zval *data, void *argument) /* {{{ */
entry->tmp = estrdup(entry->tmp);
}
- entry->metadata_str.s = NULL;
entry->filename = estrndup(entry->filename, entry->filename_len);
entry->is_persistent = 0;
- if (Z_TYPE(entry->metadata) != IS_UNDEF) {
- if (entry->metadata_len) {
- char *buf = estrndup((char *) Z_PTR(entry->metadata), entry->metadata_len);
- /* assume success, we would have failed before */
- phar_parse_metadata((char **) &buf, &entry->metadata, entry->metadata_len);
- efree(buf);
- } else {
- zval_copy_ctor(&entry->metadata);
- entry->metadata_str.s = NULL;
- }
- }
+ /* Replace metadata with non-persistent clones of the metadata. */
+ phar_metadata_tracker_clone(&entry->metadata_tracker);
return ZEND_HASH_APPLY_KEEP;
}
/* }}} */
@@ -2017,16 +2007,7 @@ static void phar_copy_cached_phar(phar_archive_data **pphar) /* {{{ */
phar->signature = estrdup(phar->signature);
}
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
- /* assume success, we would have failed before */
- if (phar->metadata_len) {
- char *buf = estrndup((char *) Z_PTR(phar->metadata), phar->metadata_len);
- phar_parse_metadata(&buf, &phar->metadata, phar->metadata_len);
- efree(buf);
- } else {
- zval_copy_ctor(&phar->metadata);
- }
- }
+ phar_metadata_tracker_clone(&phar->metadata_tracker);
zend_hash_init(&newmanifest, sizeof(phar_entry_info),
zend_get_hash_value, destroy_phar_manifest_entry, 0);
diff --git a/ext/phar/zip.c b/ext/phar/zip.c
index b241c0589b..52a387bdbc 100644
--- a/ext/phar/zip.c
+++ b/ext/phar/zip.c
@@ -242,16 +242,9 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
return FAILURE;
}
- mydata->metadata_len = PHAR_GET_16(locator.comment_len);
-
- if (phar_parse_metadata(&metadata, &mydata->metadata, PHAR_GET_16(locator.comment_len)) == FAILURE) {
- mydata->metadata_len = 0;
- /* if not valid serialized data, it is a regular string */
-
- ZVAL_NEW_STR(&mydata->metadata, zend_string_init(metadata, PHAR_GET_16(locator.comment_len), mydata->is_persistent));
- }
+ phar_parse_metadata_lazy(metadata, &mydata->metadata_tracker, PHAR_GET_16(locator.comment_len), mydata->is_persistent);
} else {
- ZVAL_UNDEF(&mydata->metadata);
+ ZVAL_UNDEF(&mydata->metadata_tracker.val);
}
goto foundit;
@@ -306,7 +299,7 @@ foundit:
zend_hash_destroy(&mydata->virtual_dirs); \
HT_INVALIDATE(&mydata->virtual_dirs); \
php_stream_close(fp); \
- zval_ptr_dtor(&mydata->metadata); \
+ phar_metadata_tracker_free(&mydata->metadata_tracker, mydata->is_persistent); \
if (mydata->signature) { \
efree(mydata->signature); \
} \
@@ -328,7 +321,7 @@ foundit:
zend_hash_destroy(&mydata->virtual_dirs); \
HT_INVALIDATE(&mydata->virtual_dirs); \
php_stream_close(fp); \
- zval_ptr_dtor(&mydata->metadata); \
+ phar_metadata_tracker_free(&mydata->metadata_tracker, mydata->is_persistent); \
if (mydata->signature) { \
efree(mydata->signature); \
} \
@@ -347,6 +340,9 @@ foundit:
phar_zip_central_dir_file zipentry;
zend_off_t beforeus = php_stream_tell(fp);
+ ZVAL_UNDEF(&entry.metadata_tracker.val);
+ entry.metadata_tracker.str = NULL;
+
if (sizeof(zipentry) != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry))) {
PHAR_ZIP_FAIL("unable to read central directory entry, truncated");
}
@@ -533,16 +529,9 @@ foundit:
}
p = buf;
- entry.metadata_len = PHAR_GET_16(zipentry.comment_len);
-
- if (phar_parse_metadata(&p, &(entry.metadata), PHAR_GET_16(zipentry.comment_len)) == FAILURE) {
- entry.metadata_len = 0;
- /* if not valid serialized data, it is a regular string */
-
- ZVAL_NEW_STR(&entry.metadata, zend_string_init(buf, PHAR_GET_16(zipentry.comment_len), entry.is_persistent));
- }
+ phar_parse_metadata_lazy(buf, &entry.metadata_tracker, PHAR_GET_16(zipentry.comment_len), entry.is_persistent);
} else {
- ZVAL_UNDEF(&entry.metadata);
+ ZVAL_UNDEF(&entry.metadata_tracker.val);
}
if (!actual_alias && entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) {
@@ -969,17 +958,9 @@ not_compressed:
PHAR_SET_32(local.crc32, entry->crc32);
continue_dir:
/* set file metadata */
- if (Z_TYPE(entry->metadata) != IS_UNDEF) {
- php_serialize_data_t metadata_hash;
-
- if (entry->metadata_str.s) {
- smart_str_free(&entry->metadata_str);
- }
- entry->metadata_str.s = NULL;
- PHP_VAR_SERIALIZE_INIT(metadata_hash);
- php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash);
- PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
- PHAR_SET_16(central.comment_len, ZSTR_LEN(entry->metadata_str.s));
+ if (phar_metadata_tracker_has_data(&entry->metadata_tracker, entry->is_persistent)) {
+ phar_metadata_tracker_try_ensure_has_serialized_data(&entry->metadata_tracker, entry->is_persistent);
+ PHAR_SET_16(central.comment_len, entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0);
}
entry->header_offset = php_stream_tell(p->filefp);
@@ -1089,14 +1070,11 @@ continue_dir:
entry->offset = entry->offset_abs = offset;
entry->fp_type = PHAR_FP;
- if (entry->metadata_str.s) {
- if (ZSTR_LEN(entry->metadata_str.s) != php_stream_write(p->centralfp, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s))) {
+ if (entry->metadata_tracker.str) {
+ if (ZSTR_LEN(entry->metadata_tracker.str) != php_stream_write(p->centralfp, ZSTR_VAL(entry->metadata_tracker.str), ZSTR_LEN(entry->metadata_tracker.str))) {
spprintf(p->error, 0, "unable to write metadata as file comment for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
- smart_str_free(&entry->metadata_str);
return ZEND_HASH_APPLY_STOP;
}
-
- smart_str_free(&entry->metadata_str);
}
return ZEND_HASH_APPLY_KEEP;
@@ -1109,8 +1087,7 @@ static int phar_zip_changed_apply(zval *zv, void *arg) /* {{{ */
}
/* }}} */
-static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass,
- smart_str *metadata) /* {{{ */
+static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass) /* {{{ */
{
/* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */
if (!phar->is_data || phar->sig_flags) {
@@ -1132,8 +1109,8 @@ static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pas
tell = php_stream_tell(pass->centralfp);
php_stream_seek(pass->centralfp, 0, SEEK_SET);
php_stream_copy_to_stream_ex(pass->centralfp, newfile, tell, NULL);
- if (metadata->s) {
- php_stream_write(newfile, ZSTR_VAL(metadata->s), ZSTR_LEN(metadata->s));
+ if (phar->metadata_tracker.str) {
+ php_stream_write(newfile, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
}
if (FAILURE == phar_create_signature(phar, newfile, &signature, &signature_length, pass->error)) {
@@ -1189,13 +1166,11 @@ static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pas
int phar_zip_flush(phar_archive_data *phar, char *user_stub, zend_long len, int defaultstub, char **error) /* {{{ */
{
char *pos;
- smart_str main_metadata_str = {0};
static const char newstub[] = "<?php // zip-based phar archive stub file\n__HALT_COMPILER();";
char halt_stub[] = "__HALT_COMPILER();";
char *tmp;
php_stream *stubfile, *oldfile;
- php_serialize_data_t metadata_hash;
int free_user_stub, closeoldfile = 0;
phar_entry_info entry = {0};
char *temperr = NULL;
@@ -1422,12 +1397,7 @@ fperror:
}
zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass);
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
- /* set phar metadata */
- PHP_VAR_SERIALIZE_INIT(metadata_hash);
- php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash);
- PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
- }
+ phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
if (temperr) {
if (error) {
spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr);
@@ -1436,9 +1406,6 @@ fperror:
temperror:
php_stream_close(pass.centralfp);
nocentralerror:
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
- smart_str_free(&main_metadata_str);
- }
php_stream_close(pass.filefp);
if (closeoldfile) {
php_stream_close(oldfile);
@@ -1446,7 +1413,7 @@ nocentralerror:
return EOF;
}
- if (FAILURE == phar_zip_applysignature(phar, &pass, &main_metadata_str)) {
+ if (FAILURE == phar_zip_applysignature(phar, &pass)) {
goto temperror;
}
@@ -1470,9 +1437,10 @@ nocentralerror:
php_stream_close(pass.centralfp);
- if (Z_TYPE(phar->metadata) != IS_UNDEF) {
+ phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
+ if (phar->metadata_tracker.str) {
/* set phar metadata */
- PHAR_SET_16(eocd.comment_len, ZSTR_LEN(main_metadata_str.s));
+ PHAR_SET_16(eocd.comment_len, ZSTR_LEN(phar->metadata_tracker.str));
if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
if (error) {
@@ -1481,15 +1449,12 @@ nocentralerror:
goto nocentralerror;
}
- if (ZSTR_LEN(main_metadata_str.s) != php_stream_write(pass.filefp, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s))) {
+ if (ZSTR_LEN(phar->metadata_tracker.str) != php_stream_write(pass.filefp, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str))) {
if (error) {
spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write metadata to zip comment", phar->fname);
}
goto nocentralerror;
}
-
- smart_str_free(&main_metadata_str);
-
} else {
if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
if (error) {
diff --git a/ext/standard/php_var.h b/ext/standard/php_var.h
index cab0f6d4dd..2d9f5464f5 100644
--- a/ext/standard/php_var.h
+++ b/ext/standard/php_var.h
@@ -59,6 +59,8 @@ PHPAPI zend_long php_var_unserialize_get_cur_depth(php_unserialize_data_t d);
#define PHP_VAR_UNSERIALIZE_DESTROY(d) \
php_var_unserialize_destroy(d)
+PHPAPI void php_unserialize_with_options(zval *return_value, const char *buf, const size_t buf_len, HashTable *options, const char* function_name);
+
PHPAPI void var_replace(php_unserialize_data_t *var_hash, zval *ozval, zval *nzval);
PHPAPI void var_push_dtor(php_unserialize_data_t *var_hash, zval *val);
PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx);
diff --git a/ext/standard/var.c b/ext/standard/var.c
index e835666ddb..a9b85903a5 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -1171,24 +1171,15 @@ PHP_FUNCTION(serialize)
}
/* }}} */
-/* {{{ Takes a string representation of variable and recreates it */
-PHP_FUNCTION(unserialize)
+/* {{{ Takes a string representation of variable and recreates it, subject to the optional unserialize options HashTable */
+PHPAPI void php_unserialize_with_options(zval *return_value, const char *buf, const size_t buf_len, HashTable *options, const char* function_name)
{
- char *buf = NULL;
- size_t buf_len;
const unsigned char *p;
php_unserialize_data_t var_hash;
- zval *options = NULL;
zval *retval;
HashTable *class_hash = NULL, *prev_class_hash;
zend_long prev_max_depth, prev_cur_depth;
- ZEND_PARSE_PARAMETERS_START(1, 2)
- Z_PARAM_STRING(buf, buf_len)
- Z_PARAM_OPTIONAL
- Z_PARAM_ARRAY(options)
- ZEND_PARSE_PARAMETERS_END();
-
if (buf_len == 0) {
RETURN_FALSE;
}
@@ -1202,7 +1193,7 @@ PHP_FUNCTION(unserialize)
if (options != NULL) {
zval *classes, *max_depth;
- classes = zend_hash_str_find_deref(Z_ARRVAL_P(options), "allowed_classes", sizeof("allowed_classes")-1);
+ classes = zend_hash_str_find_deref(options, "allowed_classes", sizeof("allowed_classes")-1);
if (classes && Z_TYPE_P(classes) != IS_ARRAY && Z_TYPE_P(classes) != IS_TRUE && Z_TYPE_P(classes) != IS_FALSE) {
php_error_docref(NULL, E_WARNING, "allowed_classes option should be array or boolean");
RETVAL_FALSE;
@@ -1231,14 +1222,14 @@ PHP_FUNCTION(unserialize)
}
php_var_unserialize_set_allowed_classes(var_hash, class_hash);
- max_depth = zend_hash_str_find_deref(Z_ARRVAL_P(options), "max_depth", sizeof("max_depth") - 1);
+ max_depth = zend_hash_str_find_deref(options, "max_depth", sizeof("max_depth") - 1);
if (max_depth) {
if (Z_TYPE_P(max_depth) != IS_LONG) {
- zend_type_error("unserialize(): \"max_depth\" option must be of type int, %s given", zend_zval_type_name(max_depth));
+ zend_type_error("%s(): \"max_depth\" option must be of type int, %s given", function_name, zend_zval_type_name(max_depth));
goto cleanup;
}
if (Z_LVAL_P(max_depth) < 0) {
- zend_value_error("unserialize(): \"max_depth\" option must be greater than or equal to 0");
+ zend_value_error("%s(): \"max_depth\" option must be greater than or equal to 0", function_name);
goto cleanup;
}
@@ -1291,6 +1282,23 @@ cleanup:
}
/* }}} */
+/* {{{ Takes a string representation of variable and recreates it */
+PHP_FUNCTION(unserialize)
+{
+ char *buf = NULL;
+ size_t buf_len;
+ HashTable *options = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_STRING(buf, buf_len)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ARRAY_HT(options)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_unserialize_with_options(return_value, buf, buf_len, options, "unserialize");
+}
+/* }}} */
+
/* {{{ Returns the allocated by PHP memory */
PHP_FUNCTION(memory_get_usage) {
zend_bool real_usage = 0;