}
/* }}} */
+static int extract_helper(phar_archive_data *archive, zend_string *search, char *pathto, size_t pathto_len, zend_bool overwrite, char **error) { /* {{{ */
+ int extracted = 0;
+ phar_entry_info *entry;
+
+ if (!search) {
+ /* nothing to match -- extract all files */
+ ZEND_HASH_FOREACH_PTR(&archive->manifest, entry) {
+ if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, error)) return -1;
+ extracted++;
+ } ZEND_HASH_FOREACH_END();
+ } else if ('/' == ZSTR_VAL(search)[ZSTR_LEN(search) - 1]) {
+ /* ends in "/" -- extract all entries having that prefix */
+ ZEND_HASH_FOREACH_PTR(&archive->manifest, entry) {
+ if (0 != strncmp(ZSTR_VAL(search), entry->filename, ZSTR_LEN(search))) continue;
+ if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, error)) return -1;
+ extracted++;
+ } ZEND_HASH_FOREACH_END();
+ } else {
+ /* otherwise, looking for an exact match */
+ entry = zend_hash_find_ptr(&archive->manifest, search);
+ if (NULL == entry) return 0;
+ if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, error)) return -1;
+ return 1;
+ }
+
+ return extracted;
+}
+/* }}} */
+
/* {{{ 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 *fp;
php_stream_statbuf ssb;
- phar_entry_info *entry;
- char *pathto, *filename;
- size_t pathto_len, filename_len;
+ char *pathto;
+ zend_string *filename;
+ size_t pathto_len;
int ret, i;
int nelems;
+ zval *zval_file;
zval *zval_files = NULL;
zend_bool overwrite = 0;
+ char *error = NULL;
PHAR_ARCHIVE_OBJECT();
if (zval_files) {
switch (Z_TYPE_P(zval_files)) {
case IS_NULL:
- goto all_files;
+ filename = NULL;
+ break;
case IS_STRING:
- filename = Z_STRVAL_P(zval_files);
- filename_len = Z_STRLEN_P(zval_files);
+ filename = Z_STR_P(zval_files);
break;
case IS_ARRAY:
nelems = zend_hash_num_elements(Z_ARRVAL_P(zval_files));
RETURN_FALSE;
}
for (i = 0; i < nelems; i++) {
- zval *zval_file;
if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(zval_files), i)) != NULL) {
- switch (Z_TYPE_P(zval_file)) {
- case IS_STRING:
- break;
- default:
- zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0,
- "Invalid argument, array of filenames to extract contains non-string value");
- return;
- }
- if (NULL == (entry = zend_hash_find_ptr(&phar_obj->archive->manifest, Z_STR_P(zval_file)))) {
- zend_throw_exception_ex(phar_ce_PharException, 0,
- "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", Z_STRVAL_P(zval_file), phar_obj->archive->fname);
- }
- if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) {
- zend_throw_exception_ex(phar_ce_PharException, 0,
- "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error);
- efree(error);
+ if (IS_STRING != Z_TYPE_P(zval_file)) {
+ zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0,
+ "Invalid argument, array of filenames to extract contains non-string value");
return;
}
+ switch (extract_helper(phar_obj->archive, Z_STR_P(zval_file), pathto, pathto_len, overwrite, &error)) {
+ case -1:
+ zend_throw_exception_ex(phar_ce_PharException, 0, "Extraction from phar \"%s\" failed: %s",
+ phar_obj->archive->fname, error);
+ efree(error);
+ return;
+ case 0:
+ zend_throw_exception_ex(phar_ce_PharException, 0,
+ "Phar Error: attempted to extract non-existent file or directory \"%s\" from phar \"%s\"",
+ ZSTR_VAL(Z_STR_P(zval_file)), phar_obj->archive->fname);
+ return;
+ }
}
}
RETURN_TRUE;
"Invalid argument, expected a filename (string) or array of filenames");
return;
}
-
- if (NULL == (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, filename, filename_len))) {
- zend_throw_exception_ex(phar_ce_PharException, 0,
- "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", filename, phar_obj->archive->fname);
- return;
- }
-
- if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) {
- zend_throw_exception_ex(phar_ce_PharException, 0,
- "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error);
- efree(error);
- return;
- }
} else {
- phar_archive_data *phar;
-all_files:
- phar = phar_obj->archive;
- /* Extract all files */
- if (!zend_hash_num_elements(&(phar->manifest))) {
- RETURN_TRUE;
- }
+ filename = NULL;
+ }
- ZEND_HASH_FOREACH_PTR(&phar->manifest, entry) {
- if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) {
- zend_throw_exception_ex(phar_ce_PharException, 0,
- "Extraction from phar \"%s\" failed: %s", phar->fname, error);
- efree(error);
- return;
- }
- } ZEND_HASH_FOREACH_END();
+ ret = extract_helper(phar_obj->archive, filename, pathto, pathto_len, overwrite, &error);
+ if (-1 == ret) {
+ zend_throw_exception_ex(phar_ce_PharException, 0, "Extraction from phar \"%s\" failed: %s",
+ phar_obj->archive->fname, error);
+ efree(error);
+ } else if (0 == ret && NULL != filename) {
+ zend_throw_exception_ex(phar_ce_PharException, 0,
+ "Phar Error: attempted to extract non-existent file or directory \"%s\" from phar \"%s\"",
+ ZSTR_VAL(filename), phar_obj->archive->fname);
+ } else {
+ RETURN_TRUE;
}
- RETURN_TRUE;
}
/* }}} */
--- /dev/null
+--TEST--
+Bug #54289 Phar::extractTo() does not accept specific directories to be extracted
+--SKIPIF--
+<?php if (!extension_loaded("phar")) die("skip"); ?>
+--INI--
+phar.readonly = 0
+--FILE--
+<?php
+// put our test fixtures into a far
+$base = __DIR__.DIRECTORY_SEPARATOR.'bug54289'.DIRECTORY_SEPARATOR;
+$inDir = $base.'in';
+$phar = $base.'test.phar';
+$pharA = new Phar($phar);
+$pharA->buildFromDirectory($inDir);
+
+// we should be able to pull out a directory that's there, but none that share
+// the same prefix
+$outDir = $base.'out';
+$pharB = new Phar($phar);
+$pharB->extractTo($outDir, 'dirA/', true);
+var_dump(file_exists($outDir.DIRECTORY_SEPARATOR.'dirA'.DIRECTORY_SEPARATOR.'fileA'));
+var_dump(file_exists($outDir.DIRECTORY_SEPARATOR.'dirA'.DIRECTORY_SEPARATOR.'fileB'));
+var_dump(is_dir($outDir.DIRECTORY_SEPARATOR.'dirAB'));
+
+// should also not be able to pull out non-existent ones
+try {
+ $pharB->extractTo($outDir, 'dirX/', true);
+ echo 'failed to throw expected exception';
+} catch (PharException $ex) {
+}
+
+// should also not be able to pull out /, because paths are not "rooted" that way
+try {
+ $pharB->extractTo($outDir, '/', true);
+ echo 'failed to throw expected exception';
+} catch (PharException $ex) {
+}
+
+// should be able to do by array, too
+$pharB = new Phar($phar);
+$pharB->extractTo($outDir, [ 'dirA/', 'dirAB/' ], true);
+
+// but not an array with a bad member in it
+try {
+ $pharB = new Phar($phar);
+ $pharB->extractTo($outDir, [ 'dirA/', 'dirX/' ], true);
+ echo 'failed to throw expected exception';
+} catch (PharException $ex) {
+}
+
+echo 'done';
+?>
+--CLEAN--
+<?php
+$base = __DIR__.DIRECTORY_SEPARATOR.'bug54289'.DIRECTORY_SEPARATOR;
+$phar = $base.'test.phar';
+$outDir = $base.'out';
+unlink($phar);
+$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
+ $outDir, \FilesystemIterator::SKIP_DOTS|\FilesystemIterator::UNIX_PATHS),
+ \RecursiveIteratorIterator::CHILD_FIRST);
+foreach ($iter as $value) {
+ $value->isFile() ? unlink($value) : rmdir($value);
+}
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+done