From fa586cee3ec4aa646b524737e1199fe9789c067d Mon Sep 17 00:00:00 2001 From: Bishop Bettini Date: Sat, 6 Jan 2018 02:21:30 -0500 Subject: [PATCH] Fixed bug #54289 If a directory is passed to Phar::extractTo(), loop over all entries and extract all files with the given prefix. --- NEWS | 6 +- ext/phar/phar_object.c | 118 ++++++++++++++----------- ext/phar/tests/bug54289.phpt | 70 +++++++++++++++ ext/phar/tests/bug54289/in/dirA/fileA | 0 ext/phar/tests/bug54289/in/dirA/fileB | 0 ext/phar/tests/bug54289/in/dirAB/file1 | 0 ext/phar/tests/bug54289/in/dirAB/file2 | 0 ext/phar/tests/bug54289/in/dirAB/file3 | 0 8 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 ext/phar/tests/bug54289.phpt create mode 100644 ext/phar/tests/bug54289/in/dirA/fileA create mode 100644 ext/phar/tests/bug54289/in/dirA/fileB create mode 100644 ext/phar/tests/bug54289/in/dirAB/file1 create mode 100644 ext/phar/tests/bug54289/in/dirAB/file2 create mode 100644 ext/phar/tests/bug54289/in/dirAB/file3 diff --git a/NEWS b/NEWS index 3a32a18339..28ed594748 100644 --- a/NEWS +++ b/NEWS @@ -6,7 +6,11 @@ PHP NEWS . Fixed bug #75864 ("stream_isatty" returns wrong value on s390x). (Sam Ding) - PGSQL: - . Fixed #75838 (Memory leak in pg_escape_bytea()). (ard_1 at mail dot ru) + . Fixed bug #75838 (Memory leak in pg_escape_bytea()). (ard_1 at mail dot ru) + +- Phar: + . Fixed bug #54289 (Phar::extractTo() does not accept specific directories to + be extracted). (bishop) - ODBC: . Fixed bug #73725 (Unable to retrieve value of varchar(max) type). (Anatol) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 59707d3638..5a85bf7ad9 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -4346,21 +4346,51 @@ static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char * } /* }}} */ +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(); @@ -4408,10 +4438,10 @@ PHP_METHOD(Phar, extractTo) 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)); @@ -4419,26 +4449,24 @@ PHP_METHOD(Phar, extractTo) 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; @@ -4447,38 +4475,22 @@ PHP_METHOD(Phar, extractTo) "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; } /* }}} */ diff --git a/ext/phar/tests/bug54289.phpt b/ext/phar/tests/bug54289.phpt new file mode 100644 index 0000000000..729c4e0e09 --- /dev/null +++ b/ext/phar/tests/bug54289.phpt @@ -0,0 +1,70 @@ +--TEST-- +Bug #54289 Phar::extractTo() does not accept specific directories to be extracted +--SKIPIF-- + +--INI-- +phar.readonly = 0 +--FILE-- +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-- +isFile() ? unlink($value) : rmdir($value); +} +?> +--EXPECT-- +bool(true) +bool(true) +bool(false) +done diff --git a/ext/phar/tests/bug54289/in/dirA/fileA b/ext/phar/tests/bug54289/in/dirA/fileA new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/phar/tests/bug54289/in/dirA/fileB b/ext/phar/tests/bug54289/in/dirA/fileB new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/phar/tests/bug54289/in/dirAB/file1 b/ext/phar/tests/bug54289/in/dirAB/file1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/phar/tests/bug54289/in/dirAB/file2 b/ext/phar/tests/bug54289/in/dirAB/file2 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/phar/tests/bug54289/in/dirAB/file3 b/ext/phar/tests/bug54289/in/dirAB/file3 new file mode 100644 index 0000000000..e69de29bb2 -- 2.40.0