From c6dc9be48998b4766004364ca57221e01d50e1c1 Mon Sep 17 00:00:00 2001 From: Greg Beaver Date: Mon, 29 Jan 2007 03:59:55 +0000 Subject: [PATCH] implement Phar->getMetadata() Phar->setMetadata(), add tests, implement meta-data in the header --- ext/phar/TODO | 2 +- ext/phar/package.xml | 2 + ext/phar/phar.c | 65 ++++++++++++++++++++--- ext/phar/phar_internal.h | 3 +- ext/phar/phar_object.c | 47 +++++++++++++++-- ext/phar/tests/009.phpt | 2 +- ext/phar/tests/010.phpt | 2 +- ext/phar/tests/phar_metadata_read.phpt | 64 +++++++++++++++++++++++ ext/phar/tests/phar_metadata_write.phpt | 69 +++++++++++++++++++++++++ ext/phar/tests/phar_oo_001.phpt | 2 +- ext/phar/tests/phar_test.inc | 4 +- 11 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 ext/phar/tests/phar_metadata_read.phpt create mode 100644 ext/phar/tests/phar_metadata_write.phpt diff --git a/ext/phar/TODO b/ext/phar/TODO index 92116421de..3939b9b49e 100644 --- a/ext/phar/TODO +++ b/ext/phar/TODO @@ -25,7 +25,7 @@ Version 1.0.0 X add PharFileInfo::getMetaData() [Marcus] * always throw exceptions from the Phar object, and E_RECOVERABLE_ERROR from streams interface - * Phar archive metadata Phar::setMetaData($metadata) Phar::getMetaData() + X Phar archive metadata Phar::setMetaData($metadata) Phar::getMetaData() [Greg] X support rename() in stream wrapper [Greg] Version 1.1.0 diff --git a/ext/phar/package.xml b/ext/phar/package.xml index 37789e673e..5e1fd4c214 100644 --- a/ext/phar/package.xml +++ b/ext/phar/package.xml @@ -107,6 +107,8 @@ if the ini variable phar.require_hash is set to true. + + diff --git a/ext/phar/phar.c b/ext/phar/phar.c index a1de40f86f..9733534dd7 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -97,6 +97,10 @@ static void phar_destroy_phar_data(phar_archive_data *data TSRMLS_DC) /* {{{ */ if (data->manifest.arBuckets) { zend_hash_destroy(&data->manifest); } + if (data->metadata) { + zval_ptr_dtor(&data->metadata); + data->metadata = 0; + } if (data->fp) { php_stream_close(data->fp); data->fp = 0; @@ -802,8 +806,17 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)") } - /* set up our manifest */ mydata = ecalloc(sizeof(phar_archive_data), 1); + if (*(php_uint32 *) buffer) { + if (phar_parse_metadata(fp, &buffer, endbuffer, &mydata->metadata TSRMLS_CC) == FAILURE) { + MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\""); + } + } else { + mydata->metadata = 0; + buffer += 4; + } + + /* set up our manifest */ zend_hash_init(&mydata->manifest, sizeof(phar_entry_info), zend_get_hash_value, destroy_phar_manifest, 0); offset = 0; @@ -838,7 +851,7 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int PHAR_GET_32(buffer, entry.flags); if (*(php_uint32 *) buffer) { if (phar_parse_metadata(fp, &buffer, endbuffer, &entry.metadata TSRMLS_CC) == FAILURE) { - MAPPHAR_FAIL("unable to read metadata in .phar file \"%s\""); + MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\""); } } else { buffer += 4; @@ -1855,7 +1868,15 @@ int phar_flush(phar_archive_data *archive, char *user_stub, long len TSRMLS_DC) * we need to skip entries marked is_deleted. */ zend_hash_apply(&archive->manifest, phar_flush_clean_deleted_apply TSRMLS_CC); - /* compress as necessary, calculate crcs, manifest size, and file sizes */ + /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */ + metadata_str.c = 0; + if (archive->metadata) { + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&metadata_str, &archive->metadata, &metadata_hash TSRMLS_CC); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } else { + metadata_str.len = 0; + } new_manifest_count = 0; offset = 0; buf = (char *) emalloc(8192); @@ -1953,14 +1974,17 @@ int phar_flush(phar_archive_data *archive, char *user_stub, long len TSRMLS_DC) * 4: manifest entry count * 2: phar version * 4: phar global flags - * 4: alias length, the rest is the alias itself + * 4: alias length + * ?: the alias itself + * 4: phar metadata length + * ?: phar metadata */ restore_alias_len = archive->alias_len; if (!archive->is_explicit_alias) { archive->alias_len = 0; } - manifest_len = offset + archive->alias_len + sizeof(manifest) - 4; + manifest_len = offset + archive->alias_len + sizeof(manifest) + metadata_str.len; phar_set_32(manifest, manifest_len); phar_set_32(manifest+4, new_manifest_count); *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION) >> 8) & 0xFF); @@ -1976,11 +2000,40 @@ int phar_flush(phar_archive_data *archive, char *user_stub, long len TSRMLS_DC) } php_stream_close(newfile); archive->alias_len = restore_alias_len; - php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "unable to write manifest meta-data of new phar \"%s\"", archive->fname); + php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "unable to write manifest header of new phar \"%s\"", archive->fname); return EOF; } + archive->alias_len = restore_alias_len; + metadata_str.c = 0; + phar_set_32(manifest, metadata_str.len); + if (metadata_str.len) { + if (4 != php_stream_write(newfile, manifest, 4) || + metadata_str.len != php_stream_write(newfile, metadata_str.c, metadata_str.len)) { + smart_str_free(&metadata_str); + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + archive->alias_len = restore_alias_len; + php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "unable to write manifest meta-data of new phar \"%s\"", archive->fname); + return EOF; + } + } else { + if (4 != php_stream_write(newfile, manifest, 4)) { + smart_str_free(&metadata_str); + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + archive->alias_len = restore_alias_len; + php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "unable to write manifest header of new phar \"%s\"", archive->fname); + return EOF; + } + } + smart_str_free(&metadata_str); + /* re-calculate the manifest location to simplify later code */ manifest_ftell = php_stream_tell(newfile); diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h index 43fb5a4111..5130edb103 100755 --- a/ext/phar/phar_internal.h +++ b/ext/phar/phar_internal.h @@ -59,7 +59,7 @@ #define PHAR_VERSION_STR "1.0.0" /* x.y.z maps to 0xyz0 */ #define PHAR_API_VERSION 0x1000 -#define PHAR_API_MIN_READ 0x0900 +#define PHAR_API_MIN_READ 0x1000 #define PHAR_API_MAJORVERSION 0x1000 #define PHAR_API_MAJORVER_MASK 0xF000 #define PHAR_API_VER_MASK 0xFFF0 @@ -167,6 +167,7 @@ struct _phar_archive_data { php_uint32 sig_flags; int sig_len; char *signature; + zval *metadata; int is_explicit_alias:1; int is_modified:1; int is_writeable:1; diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 761f476c85..45f8f7dba6 100755 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -623,6 +623,41 @@ PHP_METHOD(Phar, getStub) } /* }}}*/ +/* {{{ proto int Phar::getMetaData() + * Returns the metadata of the phar + */ +PHP_METHOD(Phar, getMetadata) +{ + PHAR_ARCHIVE_OBJECT(); + + if (phar_obj->arc.archive->metadata) { + RETURN_ZVAL(phar_obj->arc.archive->metadata, 1, 0); + } +} +/* }}} */ + +/* {{{ proto int Phar::setMetaData(mixed $metadata) + * Returns the metadata of the phar + */ +PHP_METHOD(Phar, setMetadata) +{ + zval *metadata; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &metadata) == FAILURE) { + return; + } + + if (phar_obj->arc.archive->metadata) { + zval_ptr_dtor(&phar_obj->arc.archive->metadata); + phar_obj->arc.archive->metadata = NULL; + } + + MAKE_STD_ZVAL(phar_obj->arc.archive->metadata); + ZVAL_ZVAL(phar_obj->arc.archive->metadata, metadata, 1, 0); +} +/* }}} */ + /* {{{ proto void PharFileInfo::__construct(string entry) * Construct a Phar entry object */ @@ -964,6 +999,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetSet, 0, 0, 2) ZEND_END_ARG_INFO(); #endif +static +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry_setMetadata, 0, 0, 1) + ZEND_ARG_INFO(0, metadata) +ZEND_END_ARG_INFO(); + zend_function_entry php_archive_methods[] = { #if !HAVE_SPL PHP_ME(Phar, __construct, arginfo_phar___construct, ZEND_ACC_PRIVATE) @@ -984,6 +1024,8 @@ zend_function_entry php_archive_methods[] = { PHP_ME(Phar, offsetGet, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) PHP_ME(Phar, offsetSet, arginfo_phar_offsetSet, ZEND_ACC_PUBLIC) PHP_ME(Phar, offsetUnset, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getMetadata, NULL, 0) + PHP_ME(Phar, setMetadata, arginfo_entry_setMetadata, 0) #endif /* static member functions */ PHP_ME(Phar, apiVersion, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) @@ -1001,11 +1043,6 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_entry___construct, 0, 0, 1) ZEND_ARG_INFO(0, flags) ZEND_END_ARG_INFO(); -static -ZEND_BEGIN_ARG_INFO_EX(arginfo_entry_setMetadata, 0, 0, 1) - ZEND_ARG_INFO(0, metadata) -ZEND_END_ARG_INFO(); - zend_function_entry php_entry_methods[] = { PHP_ME(PharFileInfo, __construct, arginfo_entry___construct, 0) PHP_ME(PharFileInfo, getCompressedSize, NULL, 0) diff --git a/ext/phar/tests/009.phpt b/ext/phar/tests/009.phpt index d594e88f4f..6a5bb73187 100644 --- a/ext/phar/tests/009.phpt +++ b/ext/phar/tests/009.phpt @@ -9,7 +9,7 @@ phar.require_hash=0 $file = ""; -$file .= pack('VVnVV', 500, 500, 0x0900, 0x00000000, 0) . str_repeat('A', 500); +$file .= pack('VVnVVV', 500, 500, 0x1000, 0x00000000, 0, 0) . str_repeat('A', 500); file_put_contents(dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php', $file); include dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php'; ?> diff --git a/ext/phar/tests/010.phpt b/ext/phar/tests/010.phpt index dd905edada..e07c85743b 100644 --- a/ext/phar/tests/010.phpt +++ b/ext/phar/tests/010.phpt @@ -13,7 +13,7 @@ __HALT_COMPILER(); ?>"; // this fails because the manifest length does not include the other 10 byte manifest data $manifest = pack('V', 1) . 'a' . pack('VVVVVV', 0, time(), 0, crc32(''), 0x00000000, 0); -$file .= pack('VVnVV', strlen($manifest), 1, 0x0900, 0x00000000, 3) . 'hio' . $manifest; +$file .= pack('VVnVV', strlen($manifest), 1, 0x1000, 0x00000000, 3) . 'hio' . pack('V', 0) . $manifest; file_put_contents(dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php', $file); include dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php'; diff --git a/ext/phar/tests/phar_metadata_read.phpt b/ext/phar/tests/phar_metadata_read.phpt new file mode 100644 index 0000000000..eca2514284 --- /dev/null +++ b/ext/phar/tests/phar_metadata_read.phpt @@ -0,0 +1,64 @@ +--TEST-- +Phar with phar-level meta-data (read) +--SKIPIF-- + +--INI-- +phar.require_hash=0 +--FILE-- +"; + +$files = array(); +$pmeta = 'hi there'; +$files['a'] = array('cont' => 'a'); +$files['b'] = array('cont' => 'b'); +$files['c'] = array('cont' => 'c', 'meta' => array('hi', 'there')); +$files['d'] = array('cont' => 'd', 'meta' => array('hi'=>'there','foo'=>'bar')); +include 'phar_test.inc'; + +foreach($files as $name => $cont) { + var_dump(file_get_contents($pname.'/'.$name)); +} + +$phar = new Phar($fname); +var_dump($phar->getMetaData()); +foreach($files as $name => $cont) { + var_dump($phar[$name]->getMetadata()); +} + +unset($phar); + +foreach($files as $name => $cont) { + var_dump(file_get_contents($pname.'/'.$name)); +} +?> +===DONE=== +--CLEAN-- + +--EXPECT-- +string(1) "a" +string(1) "b" +string(1) "c" +string(1) "d" +string(8) "hi there" +NULL +NULL +array(2) { + [0]=> + string(2) "hi" + [1]=> + string(5) "there" +} +array(2) { + ["hi"]=> + string(5) "there" + ["foo"]=> + string(3) "bar" +} +string(1) "a" +string(1) "b" +string(1) "c" +string(1) "d" +===DONE=== diff --git a/ext/phar/tests/phar_metadata_write.phpt b/ext/phar/tests/phar_metadata_write.phpt new file mode 100644 index 0000000000..a49799a2b4 --- /dev/null +++ b/ext/phar/tests/phar_metadata_write.phpt @@ -0,0 +1,69 @@ +--TEST-- +Phar with phar meta-data (write) +--SKIPIF-- + +--INI-- +phar.require_hash=0 +--FILE-- +"; + +$files = array(); +$files['a'] = array('cont' => 'a'); +$files['b'] = array('cont' => 'b', 'meta' => 'hi there'); +$files['c'] = array('cont' => 'c', 'meta' => array('hi', 'there')); +$files['d'] = array('cont' => 'd', 'meta' => array('hi'=>'there','foo'=>'bar')); +include '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(array('my' => 'friend')); +var_dump($phar->getMetadata()); +$phar['a']->setMetadata(42); +$phar['b']->setMetadata(NULL); +$phar['c']->setMetadata(array(25, 'foo'=>'bar')); +$phar['d']->setMetadata(true); + +foreach($files as $name => $cont) { + var_dump($phar[$name]->getMetadata()); +} + +unset($phar); + +foreach($files as $name => $cont) { + var_dump(file_get_contents($pname.'/'.$name)); +} +?> +===DONE=== +--CLEAN-- + +--EXPECT-- +string(1) "a" +string(1) "b" +string(1) "c" +string(1) "d" +NULL +array(1) { + ["my"]=> + string(6) "friend" +} +int(42) +NULL +array(2) { + [0]=> + int(25) + ["foo"]=> + string(3) "bar" +} +bool(true) +string(1) "a" +string(1) "b" +string(1) "c" +string(1) "d" +===DONE=== diff --git a/ext/phar/tests/phar_oo_001.phpt b/ext/phar/tests/phar_oo_001.phpt index 8b18873a1f..b2bb8cc0a7 100755 --- a/ext/phar/tests/phar_oo_001.phpt +++ b/ext/phar/tests/phar_oo_001.phpt @@ -39,7 +39,7 @@ unlink(dirname(__FILE__) . '/phar_oo_test.phar.php'); __halt_compiler(); ?> --EXPECT-- -string(5) "0.9.0" +string(5) "1.0.0" int(5) string(50) "Cannot call method on an uninitialized Phar object" ===DONE=== diff --git a/ext/phar/tests/phar_test.inc b/ext/phar/tests/phar_test.inc index 36ab85a29b..213e95558c 100755 --- a/ext/phar/tests/phar_test.inc +++ b/ext/phar/tests/phar_test.inc @@ -49,7 +49,9 @@ foreach($files as $name => $cont) } $alias = 'hio'; -$manifest = pack('VnVV', count($files), 0x0900, $glags, strlen($alias)) . $alias . $manifest; + +if (isset($pmeta)) $pmeta = serialize($pmeta); else $pmeta = ''; +$manifest = pack('VnVV', count($files), 0x1000, $glags, strlen($alias)) . $alias . pack('V', strlen($pmeta)) . $pmeta . $manifest; $file .= pack('V', strlen($manifest)) . $manifest; foreach($files as $cont) -- 2.40.0