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
<file name="open_for_write_newfile_c.phpt" role="test"/>
<file name="phar_ctx_001.phpt" role="test"/>
<file name="phar_commitwrite.phpt" role="test"/>
+ <file name="phar_metadata_read.phpt" role="test"/>
+ <file name="phar_metadata_write.phpt" role="test"/>
<file name="phar_oo_001.phpt" role="test"/>
<file name="phar_oo_002.phpt" role="test"/>
<file name="phar_oo_003.phpt" role="test"/>
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;
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;
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;
* 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);
* 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);
}
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);
#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
php_uint32 sig_flags;
int sig_len;
char *signature;
+ zval *metadata;
int is_explicit_alias:1;
int is_modified:1;
int is_writeable:1;
}
/* }}}*/
+/* {{{ 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
*/
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)
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)
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)
$file = "<?php
Phar::mapPhar('hio');
__HALT_COMPILER(); ?>";
-$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';
?>
// 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';
--- /dev/null
+--TEST--
+Phar with phar-level meta-data (read)
+--SKIPIF--
+<?php if (!extension_loaded("phar")) print "skip";?>
+--INI--
+phar.require_hash=0
+--FILE--
+<?php
+$fname = dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php';
+$pname = 'phar://' . $fname;
+$file = "<?php __HALT_COMPILER(); ?>";
+
+$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--
+<?php unlink(dirname(__FILE__) . '/' . basename(__FILE__, '.clean.php') . '.phar.php'); ?>
+--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===
--- /dev/null
+--TEST--
+Phar with phar meta-data (write)
+--SKIPIF--
+<?php if (!extension_loaded("phar")) print "skip";?>
+--INI--
+phar.require_hash=0
+--FILE--
+<?php
+$fname = dirname(__FILE__) . '/' . basename(__FILE__, '.php') . '.phar.php';
+$pname = 'phar://' . $fname;
+$file = "<?php __HALT_COMPILER(); ?>";
+
+$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--
+<?php unlink(dirname(__FILE__) . '/' . basename(__FILE__, '.clean.php') . '.phar.php'); ?>
+--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===
__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===
}
$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)