]> granicus.if.org Git - php/commitdiff
MFB: add full metadata support for tar-based archives and test
authorGreg Beaver <cellog@php.net>
Thu, 15 May 2008 23:46:54 +0000 (23:46 +0000)
committerGreg Beaver <cellog@php.net>
Thu, 15 May 2008 23:46:54 +0000 (23:46 +0000)
ext/phar/phar_object.c
ext/phar/tar.c
ext/phar/tests/badparameters.phpt
ext/phar/tests/pharfileinfo_setmetadata.phpt
ext/phar/tests/tar/all.phpt [new file with mode: 0644]

index 7cea524af4aaae894b71088aba48d9f750b6e6c8..ac810d4c4840f1f1fa33c86ef3480b0d40398fde 100755 (executable)
@@ -1999,6 +1999,7 @@ static zval *phar_convert_to_other(phar_archive_data *source, int convert, char
                        newentry.tmp = estrdup(newentry.tmp);
                        goto no_copy;
                }
+               newentry.metadata_str.c = 0;
                if (FAILURE == phar_copy_file_contents(&newentry, phar->fp TSRMLS_CC)) {
                        zend_hash_destroy(&(phar->manifest));
                        php_stream_close(phar->fp);
@@ -3537,11 +3538,7 @@ PHP_METHOD(Phar, setMetadata)
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Write operations disabled by INI setting");
                return;
        }
-       if (phar_obj->arc.archive->is_tar) {
-               zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC,
-                       "Cannot set metadata, not possible with tar-based phar archives");
-               return;
-       }
+
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &metadata) == FAILURE) {
                return;
        }
@@ -3553,6 +3550,7 @@ PHP_METHOD(Phar, setMetadata)
 
        MAKE_STD_ZVAL(phar_obj->arc.archive->metadata);
        ZVAL_ZVAL(phar_obj->arc.archive->metadata, metadata, 1, 0);
+       phar_obj->arc.archive->is_modified = 1;
 
        phar_flush(phar_obj->arc.archive, 0, 0, 0, &error TSRMLS_CC);
        if (error) {
@@ -3574,14 +3572,11 @@ PHP_METHOD(Phar, delMetadata)
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Write operations disabled by INI setting");
                return;
        }
-       if (phar_obj->arc.archive->is_tar) {
-               zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC,
-                       "Cannot delete metadata, not possible with tar-based phar archives");
-               return;
-       }
+
        if (phar_obj->arc.archive->metadata) {
                zval_ptr_dtor(&phar_obj->arc.archive->metadata);
                phar_obj->arc.archive->metadata = NULL;
+               phar_obj->arc.archive->is_modified = 1;
 
                phar_flush(phar_obj->arc.archive, 0, 0, 0, &error TSRMLS_CC);
                if (error) {
@@ -4117,11 +4112,6 @@ PHP_METHOD(PharFileInfo, setMetadata)
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Write operations disabled by phar.readonly INI setting");
                return;
        }
-       if (entry_obj->ent.entry->is_tar) {
-               zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC,
-                       "Cannot set metadata, not possible with tar-based phar archives");
-               return;
-       }
        if (entry_obj->ent.entry->is_temp_dir) {
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, \
                        "Phar entry is a temporary directory (not an actual entry in the archive), cannot set metadata"); \
@@ -4139,6 +4129,8 @@ PHP_METHOD(PharFileInfo, setMetadata)
        MAKE_STD_ZVAL(entry_obj->ent.entry->metadata);
        ZVAL_ZVAL(entry_obj->ent.entry->metadata, metadata, 1, 0);
 
+       entry_obj->ent.entry->is_modified = 1;
+       entry_obj->ent.entry->phar->is_modified = 1;
        phar_flush(entry_obj->ent.entry->phar, 0, 0, 0, &error TSRMLS_CC);
        if (error) {
                zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC, error);
@@ -4159,11 +4151,6 @@ PHP_METHOD(PharFileInfo, delMetadata)
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Write operations disabled by phar.readonly INI setting");
                return;
        }
-       if (entry_obj->ent.entry->is_tar) {
-               zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC,
-                       "Cannot delete metadata, not possible with tar-based phar archives");
-               return;
-       }
        if (entry_obj->ent.entry->is_temp_dir) {
                zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, \
                        "Phar entry is a temporary directory (not an actual entry in the archive), cannot delete metadata"); \
@@ -4172,6 +4159,8 @@ PHP_METHOD(PharFileInfo, delMetadata)
        if (entry_obj->ent.entry->metadata) {
                zval_ptr_dtor(&entry_obj->ent.entry->metadata);
                entry_obj->ent.entry->metadata = NULL;
+               entry_obj->ent.entry->is_modified = 1;
+               entry_obj->ent.entry->phar->is_modified = 1;
 
                phar_flush(entry_obj->ent.entry->phar, 0, 0, 0, &error TSRMLS_CC);
                if (error) {
index ac19ef3f68c8a31d77082690ba719eb0c8350dfb..bcde5dc0a017227231c1a064418507db426c1cf1 100644 (file)
@@ -152,6 +152,40 @@ int phar_open_or_create_tar(char *fname, int fname_len, char *alias, int alias_l
        return FAILURE;
 }
 
+int phar_tar_process_metadata(phar_entry_info *entry, php_stream *fp TSRMLS_DC)
+{
+       char *metadata;
+       size_t save = php_stream_tell(fp), read;
+       phar_entry_info *mentry;
+
+       metadata = (char *) emalloc(entry->uncompressed_filesize + 1);
+
+       read = php_stream_read(fp, metadata, entry->uncompressed_filesize);
+       if (read != entry->uncompressed_filesize) {
+               efree(metadata);
+               php_stream_seek(fp, save, SEEK_SET);
+               return FAILURE;
+       }
+
+       if (phar_parse_metadata(&metadata, &entry->metadata, entry->uncompressed_filesize TSRMLS_CC) == FAILURE) {
+               /* if not valid serialized data, it is a regular string */
+               efree(metadata);
+               php_stream_seek(fp, save, SEEK_SET);
+               return FAILURE;
+       }
+       if (entry->filename_len == sizeof(".phar/.metadata.bin")-1 && !memcmp(entry->filename, ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1)) {
+               entry->phar->metadata = entry->metadata;
+               entry->metadata = NULL;
+       } else if (entry->filename_len >= sizeof(".phar/.metadata/") + sizeof("/.metadata.bin") - 1 && SUCCESS == zend_hash_find(&(entry->phar->manifest), entry->filename + sizeof(".phar/.metadata/") - 1, entry->filename_len - (sizeof("/.metadata.bin") - 1 + sizeof(".phar/.metadata/") - 1), (void *)&mentry)) {
+               /* transfer this metadata to the entry it refers */
+               mentry->metadata = entry->metadata;
+               entry->metadata = NULL;
+       }
+       efree(metadata);
+       php_stream_seek(fp, save, SEEK_SET);
+       return SUCCESS;
+}
+
 int phar_open_tarfile(php_stream* fp, char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, php_uint32 compression, char **error TSRMLS_DC) /* {{{ */
 {
        char buf[512], *actual_alias = NULL, *p;
@@ -192,6 +226,8 @@ int phar_open_tarfile(php_stream* fp, char *fname, int fname_len, char *alias, i
        entry.is_crc_checked = 1;
        entry.phar = myphar;
        do {
+               phar_entry_info *newentry;
+
                pos += sizeof(buf);
                hdr = (tar_header*) buf;
                sum1 = phar_tar_number(hdr->checksum, sizeof(hdr->checksum));
@@ -280,7 +316,21 @@ int phar_open_tarfile(php_stream* fp, char *fname, int fname_len, char *alias, i
                } else if (entry.tar_type == TAR_SYMLINK) {
                        entry.link = estrdup(hdr->linkname);
                }
-               zend_hash_add(&myphar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL);
+               zend_hash_add(&myphar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), (void **) &newentry);
+               if (entry.filename_len >= sizeof(".phar/.metadata")-1 && !memcmp(entry.filename, ".phar/.metadata", sizeof(".phar/.metadata")-1)) {
+                       if (FAILURE == phar_tar_process_metadata(newentry, fp TSRMLS_CC)) {
+                               if (error) {
+                                       spprintf(error, 4096, "phar error: tar-based phar \"%s\" has invalid metadata in magic file \"%s\"", fname, entry.filename);
+                               }
+                               php_stream_close(fp);
+                               zend_hash_destroy(&myphar->manifest);
+                               myphar->manifest.arBuckets = 0;
+                               zend_hash_destroy(&myphar->mounted_dirs);
+                               myphar->mounted_dirs.arBuckets = 0;
+                               efree(myphar);
+                               return FAILURE;
+                       }
+               }
                if (!actual_alias && entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) {
                        size_t read;
                        /* found explicit alias */
@@ -567,6 +617,87 @@ int phar_tar_writeheaders(void *pDest, void *argument TSRMLS_DC)
        return ZEND_HASH_APPLY_KEEP;
 }
 
+int phar_tar_setmetadata(zval *metadata, phar_entry_info *entry, char **error, php_stream *fp TSRMLS_DC)
+{
+       php_serialize_data_t metadata_hash;
+
+       if (entry->metadata_str.c) {
+               smart_str_free(&entry->metadata_str);
+       }
+       entry->metadata_str.c = 0;
+       entry->metadata_str.len = 0;
+       PHP_VAR_SERIALIZE_INIT(metadata_hash);
+       php_var_serialize(&entry->metadata_str, &metadata, &metadata_hash TSRMLS_CC);
+       PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
+       entry->uncompressed_filesize = entry->compressed_filesize = entry->metadata_str.len;
+       if (entry->fp && entry->fp_type == PHAR_MOD) {
+               php_stream_close(entry->fp);
+       }
+       entry->fp_type = PHAR_MOD;
+       entry->is_modified = 1;
+       entry->fp = php_stream_fopen_tmpfile();
+       entry->offset = entry->offset_abs = 0;
+       if (entry->metadata_str.len != php_stream_write(entry->fp, entry->metadata_str.c, entry->metadata_str.len)) {
+               spprintf(error, 0, "phar tar error: unable to write metadata to magic metadata file \"%s\"", entry->filename);
+               zend_hash_del(&(entry->phar->manifest), entry->filename, entry->filename_len);
+               return ZEND_HASH_APPLY_STOP;
+       }
+       return ZEND_HASH_APPLY_KEEP;
+}
+
+int phar_tar_setupmetadata(void *pDest, void *argument TSRMLS_DC)
+{
+       int lookfor_len;
+       struct _phar_pass_tar_info *i = (struct _phar_pass_tar_info *)argument;
+       char *lookfor, **error = i->error;
+       php_stream *fp = i->old;
+       phar_entry_info *entry = (phar_entry_info *)pDest, *metadata, newentry = {0};
+
+       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, fp TSRMLS_CC);
+               }
+               /* search for the file this metadata entry references */
+               if (entry->filename_len >= sizeof(".phar/.metadata/") + sizeof("/.metadata.bin") - 1 && !zend_hash_exists(&(entry->phar->manifest), entry->filename + sizeof(".phar/.metadata/") - 1, entry->filename_len - (sizeof("/.metadata.bin") - 1 + sizeof(".phar/.metadata/") - 1))) {
+                       /* this is orphaned metadata, erase it */
+                       return ZEND_HASH_APPLY_REMOVE;
+               }
+               /* we can keep this entry, the file that refers to it exists */
+               return ZEND_HASH_APPLY_KEEP;
+       }
+
+       if (!entry->is_modified) {
+               return ZEND_HASH_APPLY_KEEP;
+       }
+       /* now we are dealing with regular files, so look for metadata */
+       lookfor_len = spprintf(&lookfor, 0, ".phar/.metadata/%s/.metadata.bin", entry->filename);
+       if (!entry->metadata) {
+               zend_hash_del(&(entry->phar->manifest), lookfor, lookfor_len);
+               efree(lookfor);
+               return ZEND_HASH_APPLY_KEEP;
+       }
+       if (SUCCESS == zend_hash_find(&(entry->phar->manifest), lookfor, lookfor_len, (void **)&metadata)) {
+               int ret;
+               ret = phar_tar_setmetadata(entry->metadata, metadata, error, fp TSRMLS_CC);
+               efree(lookfor);
+               return ret;
+       }
+
+       newentry.filename = lookfor;
+       newentry.filename_len = lookfor_len;
+       newentry.phar = entry->phar;
+       newentry.tar_type = TAR_FILE;
+       newentry.is_tar = 1;
+
+       if (SUCCESS != zend_hash_add(&(entry->phar->manifest), lookfor, lookfor_len, (void *)&newentry, sizeof(phar_entry_info), (void **)&metadata)) {
+               efree(lookfor);
+               spprintf(error, 0, "phar tar error: unable to add magic metadata file to manifest for file \"%s\"", entry->filename);
+               return ZEND_HASH_APPLY_STOP;
+       }
+
+       return phar_tar_setmetadata(entry->metadata, metadata, error, fp TSRMLS_CC);
+}
+
 int phar_tar_flush(phar_archive_data *phar, char *user_stub, long len, int defaultstub, char **error TSRMLS_DC) /* {{{ */
 {
        phar_entry_info entry = {0};
@@ -739,6 +870,51 @@ nostub:
        pass.free_fp = 1;
        pass.free_ufp = 1;
 
+       if (phar->metadata) {
+               phar_entry_info *mentry;
+               if (SUCCESS == zend_hash_find(&(phar->manifest), ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1, (void **)&mentry)) {
+                       if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(phar->metadata, mentry, error, oldfile TSRMLS_CC)) {
+                               if (closeoldfile) {
+                                       php_stream_close(oldfile);
+                               }
+                               return EOF;
+                       }
+               } else {
+                       phar_entry_info newentry = {0};
+
+                       newentry.filename = estrndup(".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1);
+                       newentry.filename_len = sizeof(".phar/.metadata.bin")-1;
+                       newentry.phar = phar;
+                       newentry.tar_type = TAR_FILE;
+                       newentry.is_tar = 1;
+
+                       if (SUCCESS != zend_hash_add(&(phar->manifest), ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1, (void *)&newentry, sizeof(phar_entry_info), (void **)&mentry)) {
+                               spprintf(error, 0, "phar tar error: unable to add magic metadata file to manifest for phar archive \"%s\"", phar->fname);
+                               if (closeoldfile) {
+                                       php_stream_close(oldfile);
+                               }
+                               return EOF;
+                       }
+                       if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(phar->metadata, mentry, error, oldfile TSRMLS_CC)) {
+                               zend_hash_del(&(phar->manifest), ".phar/.metadata.bin", sizeof(".phar/.metadata.bin")-1);
+                               if (closeoldfile) {
+                                       php_stream_close(oldfile);
+                               }
+                               return EOF;
+                       }
+               }
+       }
+       zend_hash_apply_with_argument(&phar->manifest, (apply_func_arg_t) phar_tar_setupmetadata, (void *) &pass TSRMLS_CC);
+
+       if (error && *error) {
+               if (closeoldfile) {
+                       php_stream_close(oldfile);
+               }
+               /* on error in the hash iterator above, error is set */
+               php_stream_close(newfile);
+               return EOF;
+       }
+
        zend_hash_apply_with_argument(&phar->manifest, (apply_func_arg_t) phar_tar_writeheaders, (void *) &pass TSRMLS_CC);
 
        /* add final zero blocks */
index a6ce37b3f9e620d61ea71989ceff234faa79013f..c644424d25a304945403591b2d4a1fff07021d8c 100644 (file)
@@ -123,11 +123,6 @@ $a->setMetadata('a');
 } catch (Exception $e) {
 echo $e->getMessage() . "\n";
 }
-try {
-$b->setMetadata('a');
-} catch (Exception $e) {
-echo $e->getMessage() . "\n";
-}
 ini_set('phar.readonly', 0);
 $a->setMetadata(1,2);
 ini_set('phar.readonly', 1);
@@ -136,11 +131,6 @@ $a->delMetadata();
 } catch (Exception $e) {
 echo $e->getMessage() . "\n";
 }
-try {
-$b->delMetadata();
-} catch (Exception $e) {
-echo $e->getMessage() . "\n";
-}
 ?>
 ===DONE===
 --EXPECTF--
@@ -202,9 +192,7 @@ Warning: Phar::addFile() expects parameter 1 to be string, array given in %sbadp
 
 Warning: Phar::addFromString() expects exactly 2 parameters, 1 given in %sbadparameters.php on line %d
 Write operations disabled by INI setting
-Cannot set metadata, not possible with tar-based phar archives
 
 Warning: Phar::setMetadata() expects exactly 1 parameter, 2 given in %sbadparameters.php on line %d
 Write operations disabled by INI setting
-Cannot delete metadata, not possible with tar-based phar archives
 ===DONE===
index 8589fcb1d89aa3a3046e8456ff363472f35aa9bb..5b34d7dde8951670587af5e6c6ae83dcae7d1251 100644 (file)
@@ -16,16 +16,6 @@ $tar = $phar->convertToData(Phar::TAR);
 
 $b = $phar['a/b'];
 try {
-$tar['a/b']->setMetadata('hi');
-} catch (Exception $e) {
-echo $e->getMessage(), "\n";
-}
-try {
-$tar['a/b']->delMetadata();
-} catch (Exception $e) {
-echo $e->getMessage(), "\n";
-}
-try {
 $phar['a']->setMetadata('hi');
 } catch (Exception $e) {
 echo $e->getMessage(), "\n";
@@ -54,8 +44,6 @@ $b->setMetadata(1,2,3);
 <?php unlink(dirname(__FILE__) . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
 <?php unlink(dirname(__FILE__) . '/' . basename(__FILE__, '.clean.php') . '.tar'); ?>
 --EXPECTF--
-Cannot set metadata, not possible with tar-based phar archives
-Cannot delete metadata, not possible with tar-based phar archives
 Phar entry is a temporary directory (not an actual entry in the archive), cannot set metadata
 Phar entry is a temporary directory (not an actual entry in the archive), cannot delete metadata
 Write operations disabled by phar.readonly INI setting
diff --git a/ext/phar/tests/tar/all.phpt b/ext/phar/tests/tar/all.phpt
new file mode 100644 (file)
index 0000000..5eb1839
--- /dev/null
@@ -0,0 +1,56 @@
+--TEST--
+Phar: test that creation of tar-based phar generates valid tar with all bells/whistles
+--SKIPIF--
+<?php if (!extension_loaded("phar")) die("skip"); ?>
+<?php if (!extension_loaded("spl")) die("skip SPL not available"); ?>
+<?php if (!extension_loaded("zlib")) die("skip zlib not available"); ?>
+<?php if (!extension_loaded("bz2")) die("skip bz2 not available"); ?>
+--INI--
+phar.readonly=0
+--FILE--
+<?php
+
+$fname = dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.tar.php';
+$pname = 'phar://' . $fname;
+$fname2 = dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.2.phar.tar.php';
+$pname2 = 'phar://' . $fname2;
+
+$phar = new Phar($fname);
+
+$phar->setMetadata('hi there');
+$phar['a'] = 'hi';
+$phar['a']->setMetadata('a meta');
+$phar['b'] = 'hi2';
+$phar['c'] = 'hi3';
+$phar['b']->chmod(0444);
+$phar->setStub("<?php ok __HALT_COMPILER();");
+$phar->setAlias("hime");
+unset($phar);
+copy($fname, $fname2);
+Phar::unlinkArchive($fname);
+var_dump(file_exists($fname), file_exists($pname . '/a'));
+
+$phar = new Phar($fname2);
+var_dump($phar['a']->getContent(), $phar['b']->getContent(), $phar['c']->getContent());
+var_dump((string) decoct(fileperms($pname2 . '/b')));
+var_dump($phar->getStub());
+var_dump($phar->getAlias());
+var_dump($phar->getMetadata());
+var_dump($phar['a']->getMetadata());
+?>
+===DONE===
+--CLEAN--
+<?php unlink(dirname(__FILE__) . '/' . basename(__FILE__, '.clean.php') . '.2.phar.tar.php'); ?>
+--EXPECT--
+bool(false)
+bool(false)
+string(2) "hi"
+string(3) "hi2"
+string(3) "hi3"
+string(6) "100444"
+string(32) "<?php ok __HALT_COMPILER(); ?>
+"
+string(4) "hime"
+string(8) "hi there"
+string(6) "a meta"
+===DONE===