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===