]> granicus.if.org Git - php/commitdiff
[RFC] Only unserialize Phar metadata when getMetadata() is called
authorTyson Andre <tysonandre775@hotmail.com>
Tue, 7 Jul 2020 22:47:52 +0000 (18:47 -0400)
committerTyson Andre <tysonandre775@hotmail.com>
Mon, 3 Aug 2020 17:28:51 +0000 (13:28 -0400)
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

19 files changed:
UPGRADING
ext/phar/phar.c
ext/phar/phar_internal.h
ext/phar/phar_object.c
ext/phar/phar_object.stub.php
ext/phar/phar_object_arginfo.h
ext/phar/stream.c
ext/phar/tar.c
ext/phar/tests/bug69720.phpt
ext/phar/tests/bug69958.phpt
ext/phar/tests/phar_metadata_write2.phpt [new file with mode: 0644]
ext/phar/tests/phar_metadata_write3.phpt [new file with mode: 0644]
ext/phar/tests/phar_metadata_write4.phpt [new file with mode: 0644]
ext/phar/tests/tar/all.phpt
ext/phar/tests/tar/bug70417.phpt
ext/phar/util.c
ext/phar/zip.c
ext/standard/php_var.h
ext/standard/var.c

index 52443866555c856a61c904b8edee839844e15452..dd91049f98068c3f86206dea45bb8fda8b2437ad 100644 (file)
--- 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
 
index 70f469cab5cf9ab285317b2fcf7d1ff9f756bd27..6434a75a75cef920d842f07af6577c41b02742ac 100644 (file)
@@ -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,49 +570,153 @@ 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;
 }
 /* }}}*/
 
+/**
+ * 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);
                        }
index e999d9d75746614255a92b6293f75d787d808fb4..f959cb29bc6834d07f7f6a7a68d827e8bdc84f5c 100644 (file)
@@ -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);
index 74f1ea4b737e81500232536032ce9621f2aa0a30..61a6473aca066fb68132e527d40a9b681e5b18fe 100644 (file)
@@ -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;
 
index f3439d851a235722bf86253ea27da64345761946..fe5fed140602a6532c30c868aaa2118e8b9346a9 100644 (file)
@@ -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() {}
index 8c6294d324639491d7ca2a8ed37008f63ce7f78e..a4b446d7c5c3590bd75fb785e85b62f37431c075 100644 (file)
@@ -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
 
index bdc15683de78383b5ee87f9d21bc7917a7192e4a..91ed30cf03eca4bf1fdf177c6159d43a449b73ad 100644 (file)
@@ -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;
 
index 89b91b25f9d758ea58f19b8f435f96fd38b6a627..fb9cacfc73c4bdee88e7baf2bb7712f244e0f17b 100644 (file)
@@ -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);
index cdb4741ff834cd6508cb0d3c15735174c9f28bfd..5c9b76f367c114c73a61f7c194fcc1e81545b247 100644 (file)
@@ -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"]=>
index 6df1178befb2cffe2c8c30222fc257115783aa38..15f1edc369abc497255c2f28e7aa81b3885c57be 100644 (file)
@@ -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 (file)
index 0000000..52e63a2
--- /dev/null
@@ -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 (file)
index 0000000..d42f95c
--- /dev/null
@@ -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 (file)
index 0000000..dfe46ba
--- /dev/null
@@ -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
index 66e9c1e9511591f0beaf9a079cc31f502ec51dcc..87d55eaaf62ef166b61dafa86acc4a08a42946e7 100644 (file)
@@ -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
index 0202ca9472afebd3f0574729add7cadbdcfe2f35..f288efc8f57dc211ba2d2ae9c164026a838a4c16 100644 (file)
@@ -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';
index 35322734d0992c54c09bc8bd9e879db62cc924e9..6c084d84584da6eb702a315caa14bff7ef81d6ee 100644 (file)
@@ -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);
index b241c0589b4eb35f1c004c7026443272e23fc63e..52a387bdbcb30c36079de5acf31b0bdc0c2e5cdb 100644 (file)
@@ -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) {
index cab0f6d4ddb431336c8044bd8658fb576a7ca473..2d9f5464f53b9100f2f8f76637fb1ea89c00a42b 100644 (file)
@@ -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);
index e835666ddb3bc2f67a072b56a7ff2e7de83a78e3..a9b85903a5c481b27cc020a2443109f9cd87aa89 100644 (file)
@@ -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;