]> granicus.if.org Git - php/commitdiff
add Phar::extractTo(dest_directory[, mixed files[, bool overwrite]])
authorGreg Beaver <cellog@php.net>
Sat, 26 Apr 2008 05:31:00 +0000 (05:31 +0000)
committerGreg Beaver <cellog@php.net>
Sat, 26 Apr 2008 05:31:00 +0000 (05:31 +0000)
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]

ext/phar/phar_object.c
ext/phar/tests/phar_extract.phpt [new file with mode: 0644]

index 63931a0fee5c917c767801ae05b63bb4d4cec1e1..2c0ac441087366f97f3fade3acd466a4d3b279bd 100755 (executable)
@@ -274,6 +274,7 @@ static int phar_file_action(phar_entry_data *phar, char *mime_type, int code, ch
                        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 @@ static int phar_file_action(phar_entry_data *phar, char *mime_type, int code, ch
                                        PHAR_G(cwd) = NULL;
                                        PHAR_G(cwd_len) = 0;
                                }
+                               PHAR_G(cwd_init) = 0;
                                efree(name);
                                zend_bailout();
                        }
@@ -494,10 +496,12 @@ carry_on2:
                }
 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 @@ PHP_METHOD(Phar, delMetadata)
        }
 }
 /* }}} */
+#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
@@ -4128,6 +4364,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_emptydir, 0, 0, 0)
        ZEND_ARG_INFO(0, dirname)
 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)
@@ -4162,6 +4405,7 @@ zend_function_entry php_archive_methods[] = {
        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)
diff --git a/ext/phar/tests/phar_extract.phpt b/ext/phar/tests/phar_extract.phpt
new file mode 100644 (file)
index 0000000..ab71f04
--- /dev/null
@@ -0,0 +1,126 @@
+--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===
\ No newline at end of file