cellog Sat Apr 26 05:30:59 2008 UTC
Added files:
/pecl/phar/tests phar_extract.phpt
Modified files:
/pecl/phar phar_object.c
Log:
add Phar::extractTo(dest_directory[, mixed files[, bool overwrite]])
this is very similar to ext/zip's extractTo and is based on that code, with
the addition of the third parameter, which
is used to allow overwriting existing files (disallowed by default, unlike
ext/zip's implementation)
[DOC]
http://cvs.php.net/viewvc.cgi/pecl/phar/phar_object.c?r1=1.240&r2=1.241&diff_format=u
Index: pecl/phar/phar_object.c
diff -u pecl/phar/phar_object.c:1.240 pecl/phar/phar_object.c:1.241
--- pecl/phar/phar_object.c:1.240 Sat Apr 26 02:04:08 2008
+++ pecl/phar/phar_object.c Sat Apr 26 05:30:59 2008
@@ -17,7 +17,7 @@
+----------------------------------------------------------------------+
*/
-/* $Id: phar_object.c,v 1.240 2008/04/26 02:04:08 sfox Exp $ */
+/* $Id: phar_object.c,v 1.241 2008/04/26 05:30:59 cellog Exp $ */
#include "phar_internal.h"
#include "func_interceptors.h"
@@ -274,6 +274,7 @@
PHAR_G(cwd_len) = 0;
if (zend_hash_add(&EG(included_files),
file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy,
sizeof(int), NULL)==SUCCESS) {
if ((cwd = strrchr(entry, '/'))) {
+ PHAR_G(cwd_init) = 1;
if (entry == cwd) {
/* root directory */
PHAR_G(cwd_len) = 0;
@@ -319,6 +320,7 @@
PHAR_G(cwd) = NULL;
PHAR_G(cwd_len) = 0;
}
+ PHAR_G(cwd_init) = 0;
efree(name);
zend_bailout();
}
@@ -494,10 +496,12 @@
}
carry_on:
if (SUCCESS != phar_mount_entry(*pphar, actual, actual_len,
path, path_len TSRMLS_CC)) {
+ zend_throw_exception_ex(phar_ce_PharException, 0
TSRMLS_CC, "Mounting of %s to %s within phar %s failed", path, actual, arch);
if (path && path == entry) {
efree(entry);
}
- zend_throw_exception_ex(phar_ce_PharException, 0
TSRMLS_CC, "Mounting of %s to %s within phar %s failed", path, actual, arch);
+ efree(arch);
+ return;
}
if (path && path == entry) {
efree(entry);
@@ -3453,6 +3457,238 @@
}
}
/* }}} */
+#if (PHP_MAJOR_VERSION < 6)
+#define OPENBASEDIR_CHECKPATH(filename) \
+ (PG(safe_mode) && (!php_checkuid(filename, NULL,
CHECKUID_CHECK_FILE_AND_DIR))) || php_check_open_basedir(filename TSRMLS_CC)
+#else
+#define OPENBASEDIR_CHECKPATH(filename) \
+ php_check_open_basedir(filename TSRMLS_CC)
+#endif
+
+static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char
*dest, int dest_len, char **error TSRMLS_DC)
+{
+ php_stream_statbuf ssb;
+ int len;
+ php_stream *fp;
+ char *fullpath, *slash;
+
+ len = spprintf(&fullpath, 0, "%s/%s", dest, entry->filename);
+ if (len >= MAXPATHLEN) {
+ char *tmp;
+ /* truncate for error message */
+ fullpath[50] = '\0';
+ if (entry->filename_len > 50) {
+ tmp = estrndup(entry->filename, 50);
+ spprintf(error, 4096, "Cannot extract \"%s...\" to
\"%s...\", extracted filename is too long for filesystem", tmp, fullpath);
+ efree(tmp);
+ } else {
+ spprintf(error, 4096, "Cannot extract \"%s\" to
\"%s...\", extracted filename is too long for filesystem", entry->filename,
fullpath);
+ }
+ efree(fullpath);
+ return FAILURE;
+ }
+ if (!len) {
+ spprintf(error, 4096, "Cannot extract \"%s\", internal error",
entry->filename);
+ efree(fullpath);
+ return FAILURE;
+ }
+
+ if (OPENBASEDIR_CHECKPATH(fullpath)) {
+ spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\",
openbasedir/safe mode restrictions in effect", entry->filename, fullpath);
+ efree(fullpath);
+ return FAILURE;
+ }
+
+ /* let see if the path already exists */
+ if (!overwrite && SUCCESS == php_stream_stat_path(fullpath, &ssb)) {
+ spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", path
already exists", entry->filename, fullpath);
+ efree(fullpath);
+ return FAILURE;
+ }
+ /* perform dirname */
+ slash = strrchr(entry->filename, '/');
+ if (slash) {
+ fullpath[dest_len + (slash - entry->filename) + 1] = '\0';
+ } else {
+ fullpath[dest_len] = '\0';
+ }
+ if (FAILURE == php_stream_stat_path(fullpath, &ssb)) {
+ if (!php_stream_mkdir(fullpath, 0777,
PHP_STREAM_MKDIR_RECURSIVE, NULL)) {
+ spprintf(error, 4096, "Cannot extract \"%s\", could not
create directory \"%s\"", entry->filename, fullpath);
+ efree(fullpath);
+ return FAILURE;
+ }
+ }
+ if (slash) {
+ fullpath[dest_len + (slash - entry->filename) + 1] = '/';
+ } else {
+ fullpath[dest_len] = '/';
+ }
+
+ /* it is a standalone directory, job done */
+ if (entry->is_dir) {
+ efree(fullpath);
+ return SUCCESS;
+ }
+
+ fp = php_stream_open_wrapper(fullpath, "w+b",
REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL);
+ if (!fp) {
+ spprintf(error, 4096, "Cannot extract \"%s\", could not open
for writing \"%s\"", entry->filename, fullpath);
+ efree(fullpath);
+ return FAILURE;
+ }
+ if (!phar_get_efp(entry, 0 TSRMLS_CC)) {
+ if (FAILURE == phar_open_entry_fp(entry, error, 1 TSRMLS_CC)) {
+ if (error) {
+ spprintf(error, 4096, "Cannot extract \"%s\" to
\"%s\", unable to open internal file pointer: %s", entry->filename, fullpath,
*error);
+ } else {
+ spprintf(error, 4096, "Cannot extract \"%s\" to
\"%s\", unable to open internal file pointer", entry->filename, fullpath);
+ }
+ efree(fullpath);
+ php_stream_close(fp);
+ return FAILURE;
+ }
+ }
+ if (FAILURE == phar_seek_efp(entry, 0, SEEK_SET, 0, 0 TSRMLS_CC)) {
+ spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable
to seek internal file pointer", entry->filename, fullpath);
+ efree(fullpath);
+ php_stream_close(fp);
+ return FAILURE;
+ }
+ if (entry->uncompressed_filesize !=
php_stream_copy_to_stream(phar_get_efp(entry, 0 TSRMLS_CC), fp,
entry->uncompressed_filesize)) {
+ spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", copying
contents failed", entry->filename, fullpath);
+ efree(fullpath);
+ php_stream_close(fp);
+ return FAILURE;
+ }
+ efree(fullpath);
+ php_stream_close(fp);
+ return SUCCESS;
+}
+
+/* {{{ proto bool Phar::extractTo(string pathto[[, mixed files], bool
overwrite])
+ * Extract one or more file from a phar archive, optionally overwriting
existing files
+ */
+PHP_METHOD(Phar, extractTo)
+{
+ char *error = NULL;
+ php_stream_statbuf ssb;
+ phar_entry_info *entry;
+ char *pathto, *filename;
+ int pathto_len, filename_len;
+ int ret, i;
+ int nelems;
+ zval *zval_files = NULL;
+ zend_bool overwrite = 0;
+ PHAR_ARCHIVE_OBJECT();
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|zb", &pathto,
&pathto_len, &zval_files, &overwrite) == FAILURE) {
+ return;
+ }
+
+ if (pathto_len < 1) {
+ zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0
TSRMLS_CC,
+ "Invalid argument, extraction path must be non-zero
length");
+ return;
+ }
+ if (pathto_len >= MAXPATHLEN) {
+ char *tmp = estrndup(pathto, 50);
+ /* truncate for error message */
+ zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0
TSRMLS_CC, "Cannot extract to \"%s...\", destination directory is too long for
filesystem", tmp);
+ efree(tmp);
+ return;
+ }
+
+ if (php_stream_stat_path(pathto, &ssb) < 0) {
+ ret = php_stream_mkdir(pathto, 0777,
PHP_STREAM_MKDIR_RECURSIVE, NULL);
+ if (!ret) {
+ zend_throw_exception_ex(spl_ce_RuntimeException, 0
TSRMLS_CC,
+ "Unable to create path \"%s\" for extraction",
pathto);
+ return;
+ }
+ } else if (!(ssb.sb.st_mode & S_IFDIR)) {
+ zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC,
+ "Unable to use path \"%s\" for extraction, it is a
file, must be a directory", pathto);
+ return;
+ }
+
+ if (zval_files) {
+ switch (Z_TYPE_P(zval_files)) {
+ case IS_STRING:
+ filename = Z_STRVAL_P(zval_files);
+ filename_len = Z_STRLEN_P(zval_files);
+ break;
+ case IS_ARRAY:
+ nelems =
zend_hash_num_elements(Z_ARRVAL_P(zval_files));
+ if (nelems == 0 ) {
+ RETURN_FALSE;
+ }
+ for (i = 0; i < nelems; i++) {
+ zval **zval_file;
+ if
(zend_hash_index_find(Z_ARRVAL_P(zval_files), i, (void **) &zval_file) ==
SUCCESS) {
+ switch (Z_TYPE_PP(zval_file)) {
+ case IS_STRING:
+ break;
+ default:
+
zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC,
+
"Invalid argument, array of filenames to extract contains non-string value");
+ return;
+ }
+ if (FAILURE ==
zend_hash_find(&phar_obj->arc.archive->manifest, Z_STRVAL_PP(zval_file),
Z_STRLEN_PP(zval_file), (void **)&entry)) {
+
zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC,
+ "Phar Error:
attempted to extract non-existent file \"%s\" from phar \"%s\"",
Z_STRVAL_PP(zval_file), phar_obj->arc.archive->fname);
+ }
+ if (FAILURE ==
phar_extract_file(overwrite, entry, pathto, pathto_len, &error TSRMLS_CC)) {
+
zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC,
+ "Extraction
from phar \"%s\" failed: %s", phar_obj->arc.archive->fname, error);
+ efree(error);
+ return;
+ }
+ }
+ }
+ RETURN_TRUE;
+ default:
+
zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC,
+ "Invalid argument, expected a filename
(string) or array of filenames");
+ return;
+ }
+ if (FAILURE == zend_hash_find(&phar_obj->arc.archive->manifest,
filename, filename_len, (void **)&entry)) {
+ zend_throw_exception_ex(phar_ce_PharException, 0
TSRMLS_CC,
+ "Phar Error: attempted to extract non-existent
file \"%s\" from phar \"%s\"", filename, phar_obj->arc.archive->fname);
+ return;
+ }
+ if (FAILURE == phar_extract_file(overwrite, entry, pathto,
pathto_len, &error TSRMLS_CC)) {
+ zend_throw_exception_ex(phar_ce_PharException, 0
TSRMLS_CC,
+ "Extraction from phar \"%s\" failed: %s",
phar_obj->arc.archive->fname, error);
+ efree(error);
+ return;
+ }
+ } else {
+ phar_archive_data *phar = phar_obj->arc.archive;
+ /* Extract all files */
+ if (!zend_hash_num_elements(&(phar->manifest))) {
+ RETURN_TRUE;
+ }
+
+ for (zend_hash_internal_pointer_reset(&phar->manifest);
+ zend_hash_has_more_elements(&phar->manifest) == SUCCESS;
+ zend_hash_move_forward(&phar->manifest)) {
+ phar_entry_info *entry;
+ if (zend_hash_get_current_data(&phar->manifest, (void
**)&entry) == FAILURE) {
+ continue;
+ }
+ if (FAILURE == phar_extract_file(overwrite, entry,
pathto, pathto_len, &error TSRMLS_CC)) {
+ zend_throw_exception_ex(phar_ce_PharException,
0 TSRMLS_CC,
+ "Extraction from phar \"%s\" failed:
%s", phar->fname, error);
+ efree(error);
+ return;
+ }
+ }
+ }
+ RETURN_TRUE;
+}
+/* }}} */
+
/* {{{ proto void PharFileInfo::__construct(string entry)
* Construct a Phar entry object
@@ -4129,6 +4365,13 @@
ZEND_END_ARG_INFO();
static
+ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_extract, 0, 0, 1)
+ ZEND_ARG_INFO(0, pathto)
+ ZEND_ARG_INFO(0, files)
+ ZEND_ARG_INFO(0, overwrite)
+ZEND_END_ARG_INFO();
+
+static
ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_addfile, 0, 0, 1)
ZEND_ARG_INFO(0, filename)
ZEND_ARG_INFO(0, localname)
@@ -4162,6 +4405,7 @@
PHP_ME(Phar, count, NULL,
ZEND_ACC_PUBLIC)
PHP_ME(Phar, delete, arginfo_phar_delete,
ZEND_ACC_PUBLIC)
PHP_ME(Phar, delMetadata, NULL,
ZEND_ACC_PUBLIC)
+ PHP_ME(Phar, extractTo, arginfo_phar_extract,
ZEND_ACC_PUBLIC)
PHP_ME(Phar, getAlias, NULL,
ZEND_ACC_PUBLIC)
PHP_ME(Phar, getPath, NULL,
ZEND_ACC_PUBLIC)
PHP_ME(Phar, getMetadata, NULL,
ZEND_ACC_PUBLIC)
http://cvs.php.net/viewvc.cgi/pecl/phar/tests/phar_extract.phpt?view=markup&rev=1.1
Index: pecl/phar/tests/phar_extract.phpt
+++ pecl/phar/tests/phar_extract.phpt
--TEST--
Phar: Phar::extractTo()
--SKIPIF--
<?php if (!extension_loaded("phar")) die("skip"); ?>
--INI--
phar.readonly=0
--FILE--
<?php
$fname = dirname(__FILE__) . '/tempmanifest1.phar.php';
$pname = 'phar://' . $fname;
$a = new Phar($fname);
$a['file1.txt'] = 'hi';
$a['file2.txt'] = 'hi2';
$a['subdir/ectory/file.txt'] = 'hi3';
$a->addEmptyDir('one/level');
$a->extractTo(dirname(__FILE__) . '/extract');
$out = array();
foreach (new RecursiveIteratorIterator(new
RecursiveDirectoryIterator(dirname(__FILE__) . '/extract'),
RecursiveIteratorIterator::CHILD_FIRST) as $p => $b) {
$out[] = $p;
}
sort($out);
foreach ($out as $b) {
echo "$b\n";
}
$a->extractTo(dirname(__FILE__) . '/extract1', 'file1.txt');
var_dump(file_get_contents(dirname(__FILE__) . '/extract1/file1.txt'));
$a->extractTo(dirname(__FILE__) . '/extract1', 'subdir/ectory/file.txt');
var_dump(file_get_contents(dirname(__FILE__) .
'/extract1/subdir/ectory/file.txt'));
$a->extractTo(dirname(__FILE__) . '/extract2', array('file2.txt', 'one/level'));
var_dump(file_get_contents(dirname(__FILE__) . '/extract2/file2.txt'));
var_dump(is_dir(dirname(__FILE__) . '/extract2/one/level'));
try {
$a->extractTo('whatever', 134);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
$a->extractTo(array());
try {
$a->extractTo('');
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
file_put_contents(dirname(__FILE__) . '/oops', 'I is file');
try {
$a->extractTo(dirname(__FILE__) . '/oops', 'file1.txt');
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
try {
$a->extractTo(dirname(__FILE__) . '/oops1', array(array(), 'file1.txt'));
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
try {
$a->extractTo(dirname(__FILE__) . '/extract', 'file1.txt');
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
file_put_contents(dirname(__FILE__) . '/extract/file1.txt', 'first');
var_dump(file_get_contents(dirname(__FILE__) . '/extract/file1.txt'));
$a->extractTo(dirname(__FILE__) . '/extract', 'file1.txt', true);
var_dump(file_get_contents(dirname(__FILE__) . '/extract/file1.txt'));
try {
$a->extractTo(str_repeat('a', 20000), 'file1.txt');
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
$a[str_repeat('a', 20000)] = 'long';
try {
$a->extractTo(dirname(__FILE__) . '/extract', str_repeat('a', 20000));
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
?>
===DONE===
--CLEAN--
<?php
@unlink(dirname(__FILE__) . '/oops');
@unlink(dirname(__FILE__) . '/oops1');
@unlink(dirname(__FILE__) . '/tempmanifest1.phar.php');
$e = dirname(__FILE__) . '/extract/';
@unlink($e . 'file1.txt');
@unlink($e . 'file2.txt');
@unlink($e . 'subdir/ectory/file.txt');
@rmdir($e . 'subdir/ectory');
@rmdir($e . 'subdir');
@rmdir($e . 'one/level');
@rmdir($e . 'one');
@rmdir($e);
$e = dirname($e) . '/extract1/';
@unlink($e . 'file1.txt');
@unlink($e . 'subdir/ectory/file.txt');
@unlink($e);
$e = dirname($e) . '/extract2/';
@unlink($e . 'file2.txt');
@rmdir($e . 'one/level');
@rmdir($e . 'one');
@rmdir($e);
?>
--EXPECTF--
%sextract/file1.txt
%sextract%cfile2.txt
%sextract%cone
%sextract%csubdir
%sextract%csubdir%cectory
%sextract%csubdir%cectory%cfile.txt
string(2) "hi"
string(3) "hi3"
string(3) "hi2"
bool(false)
Invalid argument, expected a filename (string) or array of filenames
Warning: Phar::extractTo() expects parameter 1 to be string, array given in
%sphar_extract.php on line 34
Invalid argument, extraction path must be non-zero length
Unable to use path "%soops" for extraction, it is a file, must be a directory
Invalid argument, array of filenames to extract contains non-string value
Extraction from phar "%stempmanifest1.phar.php" failed: Cannot extract
"file1.txt" to "%sextract/file1.txt", path already exists
string(5) "first"
string(2) "hi"
Cannot extract to "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...",
destination directory is too long for filesystem
Extraction from phar "%stempmanifest1.phar.php" failed: Cannot extract
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..." to "%sextract...",
extracted filename is too long for filesystem
===DONE===