From: Greg Beaver Date: Mon, 28 Jan 2008 08:52:08 +0000 (+0000) Subject: remove ext/zip dependency entirely, write better native zip support X-Git-Tag: RELEASE_2_0_0a1~754 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6cdabede4c1ab363bb9dd3256e1ec9ce7fcfd84b;p=php remove ext/zip dependency entirely, write better native zip support re-organize, create util.c, move entry_info/archive_data/entry_data access methods to this file refactor entry->fp, now this is abstracted with phar_get_efp() and phar_seek_efp(), fixes all weird dependency issues permanently solve the "millions of file pointers" issue for read access. All compressed files are read into a single temporary stream, and their constraints are controlled by the entry->fp abstraction Improvements in this zip implementation over ext/zip: * full read/write support for bzip2 compressed files * much more efficient access for accessing only a few files within large zip files, as crc/header validation is done just-in-time * full stream support for opendir/rename/rmdir/mkdir as well as all of the other stream funcs * full support for setting file perms via Phar::chmod(), stored as zip-standard extra field * no problem with large zips and many open file pointers # TODO: add big-endian system support for tar/zip file format headers, otherwise the implementation is complete # TODO: test on windows and fix any windows-specific issues # TODO: verify zips created work with unzip/winzip/windows explorer and so on --- diff --git a/ext/phar/config.m4 b/ext/phar/config.m4 index 4aac00d497..d3e4d5a54f 100644 --- a/ext/phar/config.m4 +++ b/ext/phar/config.m4 @@ -5,31 +5,9 @@ PHP_ARG_ENABLE(phar, for phar archive support, [ --enable-phar Enable phar support]) if test "$PHP_PHAR" != "no"; then - AC_MSG_CHECKING([for ZIP includes]) - if test -f $abs_srcdir/include/php/ext/zip/lib/zip.h; then - zip_inc_path=$abs_srcdir/ext - AC_DEFINE(HAVE_PHAR_ZIP,1,[ ]) - AC_MSG_RESULT($zip_inc_path) - PHP_NEW_EXTENSION(phar, tar.c zip.c stream.c func_interceptors.c dirstream.c phar.c phar_object.c phar_path_check.c, $ext_shared,,-I$zip_inc_path $PHAR_DEFS) - elif test -f $abs_srcdir/ext/zip/lib/zip.h; then - zip_inc_path=$abs_srcdir/ext - AC_DEFINE(HAVE_PHAR_ZIP,1,[ ]) - AC_MSG_RESULT($zip_inc_path) - PHP_NEW_EXTENSION(phar, tar.c zip.c stream.c func_interceptors.c dirstream.c phar.c phar_object.c phar_path_check.c, $ext_shared,,-I$zip_inc_path $PHAR_DEFS) - elif test -f $prefix/include/php/ext/zip/lib/zip.h; then - zip_inc_path=$prefix/include/php/ext - AC_DEFINE(HAVE_PHAR_ZIP,1,[ ]) - AC_MSG_RESULT($zip_inc_path) - PHP_NEW_EXTENSION(phar, tar.c zip.c stream.c func_interceptors.c dirstream.c phar.c phar_object.c phar_path_check.c, $ext_shared,,-I$zip_inc_path $PHAR_DEFS) - else - zip_inc_path=/dev/null - AC_DEFINE(HAVE_PHAR_ZIP,0,[ ]) - AC_MSG_RESULT([not found, disabling ZIP-based phar support]) - PHP_NEW_EXTENSION(phar, tar.c zip.c stream.c func_interceptors.c dirstream.c phar.c phar_object.c phar_path_check.c, $ext_shared) - fi + PHP_NEW_EXTENSION(phar, util.c tar.c zip.c stream.c func_interceptors.c dirstream.c phar.c phar_object.c phar_path_check.c, $ext_shared) PHP_ADD_BUILD_DIR($ext_builddir/lib, 1) PHP_SUBST(PHAR_SHARED_LIBADD) - PHP_ADD_EXTENSION_DEP(phar, zip, true) PHP_ADD_EXTENSION_DEP(phar, zlib, true) PHP_ADD_EXTENSION_DEP(phar, bz2, true) PHP_ADD_EXTENSION_DEP(phar, spl, true) diff --git a/ext/phar/config.w32 b/ext/phar/config.w32 index 7d6bf11d34..7589a48c4e 100644 --- a/ext/phar/config.w32 +++ b/ext/phar/config.w32 @@ -4,21 +4,10 @@ ARG_ENABLE("phar", "enable phar support", "no"); if (PHP_PHAR != "no") { - EXTENSION("phar", "dirstream.c func_interceptors.c phar.c phar_object.c phar_path_check.c stream.c tar.c zip.c"); + EXTENSION("phar", "util.c dirstream.c func_interceptors.c phar.c phar_object.c phar_path_check.c stream.c tar.c zip.c"); if (PHP_PHAR_SHARED) { ADD_FLAG("CFLAGS_PHAR", "/D COMPILE_DL_PHAR "); } - if (PHP_ZIP_SHARED) { - if (PHP_PHAR_SHARED) { - ADD_FLAG("CFLAGS_PHAR", "/D COMPILE_DL_ZIP "); - } else { - ERROR("Phar cannot be built statically with shared Zip extension"); - } - } - if (PHP_ZIP != "no") { - ADD_EXTENSION_DEP('phar', 'zip', true); - AC_DEFINE('HAVE_PHAR_ZIP', 1); - } ADD_EXTENSION_DEP('phar', 'bz2', true); ADD_EXTENSION_DEP('phar', 'spl', true); ADD_EXTENSION_DEP('phar', 'zlib', true); diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index 0d43af991c..f52c1b2ef4 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -461,13 +461,9 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, char *url_from, int mode, in memset((void *) &entry, 0, sizeof(phar_entry_info)); /* strip leading "/" */ -#if HAVE_PHAR_ZIP if (phar->is_zip) { entry.is_zip = 1; - /* prevent attempts to check the CRC */ - entry.index = -1; } -#endif entry.filename = estrdup(resource->path + 1); if (phar->is_tar) { entry.is_tar = 1; diff --git a/ext/phar/phar.c b/ext/phar/phar.c index 3eeecf735b..1c454a1bf0 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -184,38 +184,36 @@ PHP_INI_END() * When all uses of a phar have been concluded, this frees the manifest * and the phar slot */ -static void phar_destroy_phar_data(phar_archive_data *data TSRMLS_DC) /* {{{ */ +static void phar_destroy_phar_data(phar_archive_data *phar TSRMLS_DC) /* {{{ */ { -#if HAVE_PHAR_ZIP - if (data->zip) { - _zip_free(data->zip); - data->zip = 0; + if (phar->alias && phar->alias != phar->fname) { + efree(phar->alias); + phar->alias = NULL; } -#endif - if (data->alias && data->alias != data->fname) { - efree(data->alias); - data->alias = NULL; + if (phar->fname) { + efree(phar->fname); + phar->fname = NULL; } - if (data->fname) { - efree(data->fname); - data->fname = NULL; + if (phar->signature) { + efree(phar->signature); } - if (data->signature) { - efree(data->signature); + if (phar->manifest.arBuckets) { + zend_hash_destroy(&phar->manifest); + phar->manifest.arBuckets = NULL; } - if (data->manifest.arBuckets) { - zend_hash_destroy(&data->manifest); - data->manifest.arBuckets = NULL; + if (phar->metadata) { + zval_ptr_dtor(&phar->metadata); + phar->metadata = 0; } - if (data->metadata) { - zval_ptr_dtor(&data->metadata); - data->metadata = 0; + if (phar->fp) { + php_stream_close(phar->fp); + phar->fp = 0; } - if (data->fp) { - php_stream_close(data->fp); - data->fp = 0; + if (phar->ufp) { + php_stream_close(phar->ufp); + phar->fp = 0; } - efree(data); + efree(phar); } /* }}}*/ @@ -290,9 +288,7 @@ void destroy_phar_manifest_entry(void *pDest) /* {{{ */ entry->cfp = 0; } if (entry->fp) { - if (entry->fp != entry->phar->fp) { - php_stream_close(entry->fp); - } + php_stream_close(entry->fp); entry->fp = 0; } if (entry->metadata) { @@ -304,12 +300,6 @@ void destroy_phar_manifest_entry(void *pDest) /* {{{ */ entry->metadata_str.c = 0; } efree(entry->filename); -#if HAVE_PHAR_ZIP - if (entry->zip) { - zip_fclose(entry->zip); - entry->zip = 0; - } -#endif if (entry->link) { efree(entry->link); entry->link = 0; @@ -317,373 +307,6 @@ void destroy_phar_manifest_entry(void *pDest) /* {{{ */ } /* }}} */ -/** - * Looks up a phar archive in the filename map, connecting it to the alias - * (if any) or returns null - */ -int phar_get_archive(phar_archive_data **archive, char *fname, int fname_len, char *alias, int alias_len, char **error TSRMLS_DC) /* {{{ */ -{ - phar_archive_data *fd, **fd_ptr; - char *my_realpath, *save; - int save_len; - - phar_request_initialize(TSRMLS_C); - - if (error) { - *error = NULL; - } - *archive = NULL; - if (alias && alias_len) { - if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void**)&fd_ptr)) { - if (fname && (fname_len != (*fd_ptr)->fname_len || strncmp(fname, (*fd_ptr)->fname, fname_len))) { - if (error) { - spprintf(error, 0, "alias \"%s\" is already used for archive \"%s\" cannot be overloaded with \"%s\"", alias, (*fd_ptr)->fname, fname); - } - return FAILURE; - } - *archive = *fd_ptr; - return SUCCESS; - } - } - my_realpath = NULL; - save = fname; - save_len = fname_len; - if (fname && fname_len) { - if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), fname, fname_len, (void**)&fd_ptr)) { - *archive = *fd_ptr; - fd = *fd_ptr; - if (alias && alias_len) { - zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&fd, sizeof(phar_archive_data*), NULL); - } - return SUCCESS; - } - if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), save, save_len, (void**)&fd_ptr)) { - *archive = *fd_ptr; - return SUCCESS; - } - - /* not found, try converting \ to / */ - my_realpath = expand_filepath(fname, my_realpath TSRMLS_CC); - if (my_realpath) { - fname_len = strlen(my_realpath); - fname = my_realpath; - } else { - return FAILURE; - } -#ifdef PHP_WIN32 - phar_unixify_path_separators(fname, fname_len); -#endif - if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), fname, fname_len, (void**)&fd_ptr)) { - *archive = *fd_ptr; - fd = *fd_ptr; - if (alias && alias_len) { - zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&fd, sizeof(phar_archive_data*), NULL); - } - efree(my_realpath); - return SUCCESS; - } - efree(my_realpath); - } - return FAILURE; -} -/* }}} */ - -/** - * retrieve information on a file contained within a phar, or null if it ain't there - */ -phar_entry_info *phar_get_entry_info(phar_archive_data *phar, char *path, int path_len, char **error TSRMLS_DC) /* {{{ */ -{ - return phar_get_entry_info_dir(phar, path, path_len, 0, error TSRMLS_CC); -} -/* }}} */ -/** - * retrieve information on a file or directory contained within a phar, or null if none found - * allow_dir is 0 for none, 1 for both empty directories in the phar and temp directories, and 2 for only - * valid pre-existing empty directory entries - */ -phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, int path_len, char dir, char **error TSRMLS_DC) /* {{{ */ -{ - const char *pcr_error; - phar_entry_info *entry; - char is_dir = (path[path_len - 1] == '/'); - - if (error) { - *error = NULL; - } - - if (!path_len && !dir) { - if (error) { - spprintf(error, 4096, "phar error: invalid path \"%s\" must not be empty", path); - } - return NULL; - } - if (phar_path_check(&path, &path_len, &pcr_error) > pcr_is_ok) { - if (error) { - spprintf(error, 4096, "phar error: invalid path \"%s\" contains %s", path, pcr_error); - } - return NULL; - } - - if (!&phar->manifest.arBuckets) { - return NULL; - } - if (is_dir) { - path_len--; - } - if (SUCCESS == zend_hash_find(&phar->manifest, path, path_len, (void**)&entry)) { - if (entry->is_deleted) { - /* entry is deleted, but has not been flushed to disk yet */ - return NULL; - } - if (entry->is_dir && !dir) { - if (error) { - spprintf(error, 4096, "phar error: path \"%s\" is a directory", path); - } - return NULL; - } - if (!entry->is_dir && is_dir) { - /* user requested a directory, we must return one */ - if (error) { - spprintf(error, 4096, "phar error: path \"%s\" exists and is a not a directory", path); - } - return NULL; - } - return entry; - } - if (dir == 1) { - /* try to find a directory */ - HashTable *manifest; - char *key; - uint keylen; - ulong unused; - - if (!path_len) { - path = "/"; - } - manifest = &phar->manifest; - zend_hash_internal_pointer_reset(manifest); - while (FAILURE != zend_hash_has_more_elements(manifest)) { - if (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key_ex(manifest, &key, &keylen, &unused, 0, NULL)) { - break; - } - if (0 != memcmp(key, path, path_len)) { - /* entry in directory not found */ - if (SUCCESS != zend_hash_move_forward(manifest)) { - break; - } - continue; - } else { - if (key[path_len] != '/') { - if (SUCCESS != zend_hash_move_forward(manifest)) { - break; - } - continue; - } - /* found a file in this path */ - entry = (phar_entry_info *) ecalloc(1, sizeof(phar_entry_info)); - /* this next line tells PharFileInfo->__destruct() to efree the filename */ - entry->is_temp_dir = entry->is_dir = 1; - entry->filename = (char *) estrndup(path, path_len + 1); - entry->filename_len = path_len; - return entry; - } - } - } - return NULL; -} -/* }}} */ - -#if defined(PHP_VERSION_ID) && PHP_VERSION_ID < 50202 -typedef struct { - char *data; - size_t fpos; - size_t fsize; - size_t smax; - int mode; - php_stream **owner_ptr; -} php_stream_memory_data; -#endif - -/* this is only called for non-zip/tar-based phars */ -static int phar_open_entry_file(phar_archive_data *phar, phar_entry_info *entry, char **error TSRMLS_DC) /* {{{ */ -{ - if (error) { - *error = NULL; - } - /* open a new temp file for writing */ - entry->fp = php_stream_fopen_tmpfile(); - if (!entry->fp) { - if (error) { - spprintf(error, 0, "phar error: unable to create temporary file"); - } - return FAILURE; - } - entry->old_flags = entry->flags; - entry->is_modified = 1; - phar->is_modified = 1; - /* reset file size */ - entry->uncompressed_filesize = 0; - entry->compressed_filesize = 0; - entry->crc32 = 0; - entry->flags = PHAR_ENT_PERM_DEF_FILE; - return SUCCESS; -} -/* }}} */ - -/** - * Retrieve a copy of the file information on a single file within a phar, or null. - * This also transfers the open file pointer, if any, to the entry. - * - * If the file does not already exist, this will fail. Pre-existing files can be - * appended, truncated, or read. For read, if the entry is marked unmodified, it is - * assumed that the file pointer, if present, is opened for reading - */ -int phar_get_entry_data(phar_entry_data **ret, char *fname, int fname_len, char *path, int path_len, char *mode, char allow_dir, char **error TSRMLS_DC) /* {{{ */ -{ - phar_archive_data *phar; - phar_entry_info *entry; - int for_write = mode[0] != 'r' || mode[1] == '+'; - int for_append = mode[0] == 'a'; - int for_create = mode[0] != 'r'; - int for_trunc = mode[0] == 'w'; - - if (!ret) { - return FAILURE; - } - *ret = NULL; - if (error) { - *error = NULL; - } - if (for_write && PHAR_G(readonly)) { - if (error) { - spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for writing, disabled by ini setting", path, fname); - } - return FAILURE; - } - if (FAILURE == phar_get_archive(&phar, fname, fname_len, NULL, 0, error TSRMLS_CC)) { - return FAILURE; - } - if (!path_len) { - if (error) { - spprintf(error, 4096, "phar error: file \"\" in phar \"%s\" cannot be empty", fname); - } - return FAILURE; - } - if (allow_dir) { - if ((entry = phar_get_entry_info_dir(phar, path, path_len, 2, for_create && !PHAR_G(readonly) ? NULL : error TSRMLS_CC)) == NULL) { - if (for_create && !PHAR_G(readonly)) { - return SUCCESS; - } - return FAILURE; - } - } else { - if ((entry = phar_get_entry_info(phar, path, path_len, for_create && !PHAR_G(readonly) ? NULL : error TSRMLS_CC)) == NULL) { - if (for_create && !PHAR_G(readonly)) { - return SUCCESS; - } - return FAILURE; - } - } - if (entry->is_modified && !for_write) { - if (error) { - spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for reading, writable file pointers are open", path, fname); - } - return FAILURE; - } - if (entry->fp_refcount && for_write) { - if (error) { - spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for writing, readable file pointers are open", path, fname); - } - return FAILURE; - } - if (entry->is_deleted) { - if (!for_create) { - return FAILURE; - } - entry->is_deleted = 0; - } - *ret = (phar_entry_data *) emalloc(sizeof(phar_entry_data)); - (*ret)->position = 0; - (*ret)->zero = 0; - (*ret)->phar = phar; - (*ret)->for_write = for_write; - (*ret)->internal_file = entry; - (*ret)->is_zip = entry->is_zip; - (*ret)->is_tar = entry->is_tar; - if (entry->is_dir) { - entry->phar->refcount++; - entry->fp_refcount++; - return SUCCESS; - } - if (entry->fp) { - /* make a copy */ - if (for_trunc) { - if (entry->fp == phar->fp) { - /* duplicate entry if we are writing and are recycling the phar fp */ - if (FAILURE == phar_open_entry_file(phar, entry, error TSRMLS_CC)) { - return FAILURE; - } - (*ret)->fp = entry->fp; - } -#if PHP_VERSION_ID >= 50202 - php_stream_truncate_set_size(entry->fp, 0); -#else - if (php_stream_is(entry->fp, PHP_STREAM_IS_TEMP)) { - if (php_stream_is(*(php_stream**)entry->fp->abstract, PHP_STREAM_IS_MEMORY)) { - php_stream *inner = *(php_stream**)entry->fp->abstract; - php_stream_memory_data *memfp = (php_stream_memory_data*)inner->abstract; - memfp->fpos = 0; - memfp->fsize = 0; - } else if (php_stream_is(*(php_stream**)entry->fp->abstract, PHP_STREAM_IS_STDIO)) { - php_stream_truncate_set_size(*(php_stream**)entry->fp->abstract, 0); - } else { - efree(*ret); - *ret = NULL; - if (error) { - spprintf(error, 0, "phar error: file \"%s\" cannot be opened for writing, no truncate support", fname); - } - return FAILURE; - } - } else if (php_stream_is(entry->fp, PHP_STREAM_IS_STDIO)) { - php_stream_truncate_set_size(entry->fp, 0); - } else { - efree(*ret); - *ret = NULL; - if (error) { - spprintf(error, 0, "phar error: file \"%s\" cannot be opened for writing, no truncate support", fname); - } - return FAILURE; - } -#endif - entry->old_flags = entry->flags; - entry->is_modified = 1; - phar->is_modified = 1; - /* reset file size */ - entry->uncompressed_filesize = 0; - entry->compressed_filesize = 0; - entry->crc32 = 0; - } else if (for_append) { - php_stream_seek(entry->fp, 0, SEEK_END); - } - (*ret)->fp = entry->fp; - if (entry->fp == phar->fp) { - (*ret)->zero = entry->offset_within_phar + phar->internal_file_start; - } - } else { - (*ret)->fp = 0; - if (for_write) { - if (FAILURE == phar_open_entry_file(phar, entry, error TSRMLS_CC)) { - return FAILURE; - } - (*ret)->fp = entry->fp; - } - } - entry->fp_refcount++; - entry->phar->refcount++; - return SUCCESS; -} -/* }}} */ - int phar_entry_delref(phar_entry_data *idata TSRMLS_DC) /* {{{ */ { int ret = 0; @@ -692,7 +315,7 @@ int phar_entry_delref(phar_entry_data *idata TSRMLS_DC) /* {{{ */ if (--idata->internal_file->fp_refcount < 0) { idata->internal_file->fp_refcount = 0; } - if (idata->fp && idata->fp != idata->internal_file->fp) { + if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) { php_stream_close(idata->fp); } } @@ -711,18 +334,9 @@ void phar_entry_remove(phar_entry_data *idata, char **error TSRMLS_DC) /* {{{ */ phar = idata->phar; if (idata->internal_file->fp_refcount < 2) { - if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->internal_file->fp) { + if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) { php_stream_close(idata->fp); } -#if HAVE_PHAR_ZIP - if (idata->internal_file->is_zip) { - if (idata->internal_file->zip) { - zip_fclose(idata->internal_file->zip); - idata->internal_file->zip = 0; - } - zip_delete(phar->zip, idata->internal_file->index); - } -#endif zend_hash_del(&idata->phar->manifest, idata->internal_file->filename, idata->internal_file->filename_len); idata->phar->refcount--; efree(idata); @@ -736,95 +350,6 @@ void phar_entry_remove(phar_entry_data *idata, char **error TSRMLS_DC) /* {{{ */ } /* }}} */ -/** - * Create a new dummy file slot within a writeable phar for a newly created file - */ -phar_entry_data *phar_get_or_create_entry_data(char *fname, int fname_len, char *path, int path_len, char *mode, char allow_dir, char **error TSRMLS_DC) /* {{{ */ -{ - phar_archive_data *phar; - phar_entry_info *entry, etemp; - phar_entry_data *ret; - const char *pcr_error; - char is_dir = path[path_len - 1] == '/'; - - if (FAILURE == phar_get_archive(&phar, fname, fname_len, NULL, 0, error TSRMLS_CC)) { - return NULL; - } - - if (FAILURE == phar_get_entry_data(&ret, fname, fname_len, path, path_len, mode, allow_dir, error TSRMLS_CC)) { - return NULL; - } else if (ret) { - return ret; - } - - if (phar_path_check(&path, &path_len, &pcr_error) > pcr_is_ok) { - if (error) { - spprintf(error, 0, "phar error: invalid path \"%s\" contains %s", path, pcr_error); - } - return NULL; - } - - /* create a new phar data holder */ - ret = (phar_entry_data *) emalloc(sizeof(phar_entry_data)); - - /* create an entry, this is a new file */ - memset(&etemp, 0, sizeof(phar_entry_info)); - etemp.filename_len = path_len; - etemp.fp = php_stream_fopen_tmpfile(); - if (!etemp.fp) { - if (error) { - spprintf(error, 0, "phar error: unable to create temporary file"); - } - return NULL; - } - if (is_dir) { - etemp.fp_refcount = 1; - etemp.is_dir = 1; - etemp.flags = PHAR_ENT_PERM_DEF_DIR; - etemp.old_flags = PHAR_ENT_PERM_DEF_DIR; - etemp.filename_len--; /* strip trailing / */ - path_len--; - } else { - etemp.fp_refcount = 1; - etemp.flags = PHAR_ENT_PERM_DEF_FILE; - etemp.old_flags = PHAR_ENT_PERM_DEF_FILE; - } - etemp.is_modified = 1; - etemp.timestamp = time(0); - etemp.offset_within_phar = (phar->is_zip ? 0 : -1); - etemp.is_crc_checked = 1; - etemp.phar = phar; -#if HAVE_PHAR_ZIP - if (phar->is_zip) { - etemp.is_zip = 1; - /* prevent attempts to check the CRC */ - etemp.is_crc_checked = 1; - etemp.index = -1; - } -#endif - etemp.filename = estrndup(path, path_len); - if (phar->is_tar) { - etemp.is_tar = phar->is_tar; - etemp.tar_type = TAR_FILE; - } - zend_hash_add(&phar->manifest, etemp.filename, path_len, (void*)&etemp, sizeof(phar_entry_info), (void **) &entry); - - if (!entry) { - return NULL; - } - - phar->refcount++; - ret->phar = phar; - ret->fp = entry->fp; - ret->position = 0; - ret->for_write = 1; - ret->is_zip = entry->is_zip; - ret->is_tar = entry->is_tar; - ret->internal_file = entry; - return ret; -} -/* }}} */ - #define MAPPHAR_ALLOC_FAIL(msg) \ php_stream_close(fp);\ if (error) {\ @@ -1318,12 +843,15 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int /* set up our manifest */ zend_hash_init(&mydata->manifest, sizeof(phar_entry_info), zend_get_hash_value, destroy_phar_manifest_entry, 0); - offset = 0; + offset = halt_offset + manifest_len + 4; + memset(&entry, 0, sizeof(phar_entry_info)); + entry.phar = mydata; + entry.fp_type = PHAR_FP; + for (manifest_index = 0; manifest_index < manifest_count; manifest_index++) { if (buffer + 4 > endbuffer) { MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)") } - memset(&entry, 0, sizeof(phar_entry_info)); PHAR_GET_32(buffer, entry.filename_len); if (entry.filename_len == 0) { MAPPHAR_FAIL("zero-length filename encountered in phar \"%s\""); @@ -1333,16 +861,14 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int } if ((manifest_ver & PHAR_API_VER_MASK) >= PHAR_API_MIN_DIR && buffer[entry.filename_len - 1] == '/') { entry.is_dir = 1; - entry.filename_len--; - entry.flags |= PHAR_ENT_PERM_DEF_DIR; } else { entry.is_dir = 0; } entry.filename = estrndup(buffer, entry.filename_len); - buffer += entry.filename_len + (entry.is_dir ? 1 : 0); + buffer += entry.filename_len; PHAR_GET_32(buffer, entry.uncompressed_filesize); PHAR_GET_32(buffer, entry.timestamp); - if (offset == 0) { + if (offset == halt_offset + manifest_len + 4) { mydata->min_timestamp = entry.timestamp; mydata->max_timestamp = entry.timestamp; } else { @@ -1355,11 +881,15 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int PHAR_GET_32(buffer, entry.compressed_filesize); PHAR_GET_32(buffer, entry.crc32); PHAR_GET_32(buffer, entry.flags); + if (entry.is_dir) { + entry.filename_len--; + entry.flags |= PHAR_ENT_PERM_DEF_DIR; + } if (phar_parse_metadata(&buffer, &entry.metadata, 0 TSRMLS_CC) == FAILURE) { efree(entry.filename); MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\""); } - entry.offset_within_phar = offset; + entry.offset = entry.offset_abs = offset; offset += entry.compressed_filesize; switch (entry.flags & PHAR_ENT_COMPRESSION_MASK) { case PHAR_ENT_COMPRESSED_GZ: @@ -1393,8 +923,6 @@ int phar_open_file(php_stream *fp, char *fname, int fname_len, char *alias, int manifest_flags |= (entry.flags & PHAR_ENT_COMPRESSION_MASK); /* if signature matched, no need to check CRC32 for each file */ entry.is_crc_checked = (manifest_flags & PHAR_HDR_SIGNATURE ? 1 : 0); - entry.fp = NULL; - entry.phar = mydata; zend_hash_add(&mydata->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL); } @@ -1468,6 +996,7 @@ int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int a phar_archive_data *mydata; int register_alias; php_stream *fp; + char *actual = NULL; if (!pphar) { pphar = &mydata; @@ -1483,20 +1012,32 @@ int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int a } /* first open readonly so it won't be created if not present */ - fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, NULL); + fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, &actual); + if (actual) { + fname = actual; + fname_len = strlen(actual); + } if (fp) { if (phar_open_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error TSRMLS_CC) == SUCCESS) { if (!PHAR_G(readonly)) { (*pphar)->is_writeable = 1; } + if (actual) { + efree(actual); + } return SUCCESS; } else { /* file exists, but is either corrupt or not a phar archive */ php_stream_close(fp); + if (actual) { + efree(actual); + } return FAILURE; } - php_stream_close(fp); + } + if (actual) { + efree(actual); } @@ -1512,8 +1053,16 @@ int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int a /* set up our manifest */ mydata = ecalloc(sizeof(phar_archive_data), 1); - /* re-open for writing */ - fp = php_stream_open_wrapper(fname, "r+b", IGNORE_URL|STREAM_MUST_SEEK|0, &mydata->fname); + /* re-open for writing (we only reach here if the file does not exist) */ + fp = php_stream_open_wrapper(fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|0, &mydata->fname); + if (!fp) { + if (options & REPORT_ERRORS) { + if (error) { + spprintf(error, 0, "creating archive \"%s\" failed", fname); + } + } + return FAILURE; + } if (mydata->fname) { fname = mydata->fname; #ifdef PHP_WIN32 @@ -1568,6 +1117,8 @@ int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int a int phar_open_filename(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */ { php_stream *fp; + char *actual; + int ret; if (error) { *error = NULL; @@ -1589,18 +1140,29 @@ int phar_open_filename(char *fname, int fname_len, char *alias, int alias_len, i return FAILURE; } - fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, NULL); - + fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, &actual); if (!fp) { if (options & REPORT_ERRORS) { if (error) { spprintf(error, 0, "unable to open phar for reading \"%s\"", fname); } } + if (actual) { + efree(actual); + } return FAILURE; } - return phar_open_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error TSRMLS_CC); + if (actual) { + fname = actual; + fname_len = strlen(actual); + } + + ret = phar_open_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error TSRMLS_CC); + if (actual) { + efree(actual); + } + return ret; } /* }}}*/ @@ -1727,8 +1289,8 @@ static int phar_open_fp(php_stream* fp, char *fname, int fname_len, char *alias, continue; } if (!memcmp(pos, zip_magic, 4)) { - php_stream_close(fp); - return phar_open_zipfile(fname, fname_len, alias, alias_len, pphar, error TSRMLS_CC); + php_stream_seek(fp, 0, SEEK_END); + return phar_open_zipfile(fp, fname, fname_len, alias, alias_len, pphar, error TSRMLS_CC); } if (got > 512) { if (phar_is_tar(pos)) { @@ -2059,243 +1621,56 @@ int phar_open_compiled_file(char *alias, int alias_len, char **error TSRMLS_DC) /** * Validate the CRC32 of a file opened from within the phar */ -int phar_postprocess_file(php_stream_wrapper *wrapper, int options, phar_entry_data *idata, php_uint32 crc32 TSRMLS_DC) /* {{{ */ +int phar_postprocess_file(php_stream_wrapper *wrapper, int options, phar_entry_data *idata, php_uint32 crc32, char **error TSRMLS_DC) /* {{{ */ { php_uint32 crc = ~0; int len = idata->internal_file->uncompressed_filesize; php_stream *fp = idata->fp; - - php_stream_seek(fp, 0 + idata->zero, SEEK_SET); - while (len--) { - CRC32(crc, php_stream_getc(fp)); - } - php_stream_seek(fp, 0 + idata->zero, SEEK_SET); - if (~crc == crc32) { - return SUCCESS; - } else { - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: internal corruption of phar \"%s\" (crc32 mismatch on file \"%s\")", idata->phar->fname, idata->internal_file->filename); - return FAILURE; - } -} -/* }}} */ - -/** - * Determine which stream compression filter (if any) we need to read this file - */ -static char * phar_compress_filter(phar_entry_info * entry, int return_unknown) /* {{{ */ -{ - switch (entry->flags & PHAR_ENT_COMPRESSION_MASK) { - case PHAR_ENT_COMPRESSED_GZ: - return "zlib.deflate"; - case PHAR_ENT_COMPRESSED_BZ2: - return "bzip2.compress"; - default: - return return_unknown ? "unknown" : NULL; - } -} -/* }}} */ - -/** - * Determine which stream decompression filter (if any) we need to read this file - */ -static char * phar_decompress_filter(phar_entry_info * entry, int return_unknown) /* {{{ */ -{ - php_uint32 flags; - - if (entry->is_modified) { - flags = entry->old_flags; - } else { - flags = entry->flags; - } - switch (flags & PHAR_ENT_COMPRESSION_MASK) { - case PHAR_ENT_COMPRESSED_GZ: - return "zlib.inflate"; - case PHAR_ENT_COMPRESSED_BZ2: - return "bzip2.decompress"; - default: - return return_unknown ? "unknown" : NULL; - } -} -/* }}} */ - -/** - * helper function to open an internal file's fp just-in-time - */ -phar_entry_info * phar_open_jit(phar_archive_data *phar, phar_entry_info *entry, php_stream *fp, - char **error, int for_write TSRMLS_DC) -{ - php_uint32 offset, flags; - php_stream_filter *filter/*, *consumed */; - char *filter_name; - char *buffer; + phar_entry_info *entry = idata->internal_file; if (error) { *error = NULL; } -#if HAVE_PHAR_ZIP if (entry->is_zip) { - char readbuf[8192]; - int got; - if (entry->fp) { - return entry; - } - if (!entry->zip) { - if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) { - char *filter_name; - php_stream_filter *filter; - /* we have to decompress this by hand */ - - if (!phar_has_bz2) { - if (error) { - spprintf(error, 4096, "phar error, cannot decompress bzip2-compressed entry"); - } - return NULL; - } - - /* first, read into a temp stream */ - fp = php_stream_temp_new(); - entry->zip = zip_fopen_index(phar->zip, entry->index, ZIP_FL_COMPRESSED); - if (!entry->zip) { - if (error) { - spprintf(error, 4096, "phar error: %s", zip_strerror(phar->zip)); - } - zip_error_clear(phar->zip); - return NULL; - } - do { - size_t check; - got = zip_fread(entry->zip, (void *)readbuf, 8192); - if (-1 == got) break; - check = got; - if (check != php_stream_write(fp, readbuf, got)) { - if (error) { - spprintf(error, 4096, "phar error: could not copy full zip file contents of entry \"%s\"", entry->filename); - } - php_stream_close(fp); - entry->fp = NULL; - zip_fclose(entry->zip); - entry->zip = NULL; - return NULL; - } - } while (got == 8192); - zip_fclose(entry->zip); - entry->zip = NULL; - php_stream_seek(fp, 0, SEEK_SET); - - /* now use a decompression filter to inflate into our temp file */ - if ((filter_name = phar_decompress_filter(entry, 0)) != NULL) { - filter = php_stream_filter_create(filter_name, NULL, php_stream_is_persistent(fp) TSRMLS_CC); - } else { - filter = NULL; - } - if (!filter) { - spprintf(error, 0, "phar error: unable to read phar \"%s\" (cannot create %s filter while decompressing file \"%s\")", entry->phar->fname, phar_decompress_filter(entry, 1), entry->filename); - return NULL; - } + /* verify local file header */ + phar_zip_file_header local; - /* now we can safely use proper decompression */ - entry->fp = php_stream_temp_new(); - php_stream_filter_append(&entry->fp->writefilters, filter); - if (php_stream_copy_to_stream(fp, entry->fp, entry->compressed_filesize) != entry->compressed_filesize || php_stream_tell(entry->fp) != (off_t) entry->uncompressed_filesize) { - spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - return NULL; - } - php_stream_filter_flush(filter, 1); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - php_stream_close(fp); - return entry; - } else { - /* uncompressed or zlib-compressed */ - entry->zip = zip_fopen_index(phar->zip, entry->index, 0); - if (!entry->zip) { - if (error) { - spprintf(error, 4096, "phar error: %s", zip_strerror(phar->zip)); - } - zip_error_clear(phar->zip); - return NULL; - } - } + if (SUCCESS != phar_open_archive_fp(idata->phar TSRMLS_CC)) { + spprintf(error, 0, "phar error: unable to open zip-based phar archive \"%s\" to verify local file header for file \"%s\"", idata->phar->fname, entry->filename); + return FAILURE; } + php_stream_seek(idata->phar->fp, entry->header_offset, SEEK_SET); - /* load contents of zip file to temp stream */ - entry->fp = php_stream_temp_new(); - do { - size_t check; - got = zip_fread(entry->zip, (void *)readbuf, 8192); - if (-1 == got) break; - check = got; - if (check != php_stream_write(entry->fp, readbuf, got)) { - if (error) { - spprintf(error, 4096, "phar error: could not copy full zip file contents of entry \"%s\"", entry->filename); - } - php_stream_close(entry->fp); - entry->fp = NULL; - zip_fclose(entry->zip); - entry->zip = NULL; - return NULL; - } - } while (got == 8192); - zip_fclose(entry->zip); - entry->zip = NULL; - return entry; - } -#endif /* #if HAVE_PHAR_ZIP */ - /* seek to start of internal file and read it */ - offset = phar->internal_file_start + entry->offset_within_phar; - if (-1 == php_stream_seek(fp, offset, SEEK_SET)) { - spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (cannot seek to start of file \"%s\" at offset \"%d\")", - phar->fname, entry->filename, offset); - return NULL; - } - - if (entry->is_modified) { - flags = entry->old_flags; - } else { - flags = entry->flags; - } + if (sizeof(local) != php_stream_read(idata->phar->fp, (char *) &local, sizeof(local))) { - if ((flags & PHAR_ENT_COMPRESSION_MASK) != 0) { - if ((filter_name = phar_decompress_filter(entry, 0)) != NULL) { - filter = php_stream_filter_create(phar_decompress_filter(entry, 0), NULL, php_stream_is_persistent(fp) TSRMLS_CC); - } else { - filter = NULL; - } - if (!filter) { - spprintf(error, 0, "phar error: unable to read phar \"%s\" (cannot create %s filter while decompressing file \"%s\")", phar->fname, phar_decompress_filter(entry, 1), entry->filename); - return NULL; + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local file header for file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; } - /* now we can safely use proper decompression */ - entry->old_flags = entry->flags; - buffer = (char *) emalloc(8192); - - entry->fp = php_stream_temp_new(); - php_stream_filter_append(&entry->fp->writefilters, filter); - if (php_stream_copy_to_stream(fp, entry->fp, entry->compressed_filesize) != entry->compressed_filesize || php_stream_tell(entry->fp) != (off_t) entry->uncompressed_filesize) { - efree(buffer); - spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - return NULL; + /* fix up for big-endian systems */ + /* verify local header if not yet verified */ + if (entry->filename_len != local.filename_len || entry->crc32 != local.crc32 || entry->uncompressed_filesize != local.uncompsize || entry->compressed_filesize != local.compsize) { + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (local head of file \"%s\" does not match central directory)", idata->phar->fname, entry->filename); + return FAILURE; } - efree(buffer); - php_stream_filter_flush(filter, 1); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - php_stream_seek(fp, offset + entry->compressed_filesize, SEEK_SET); - } else { /* from here is for non-compressed */ - if (!for_write && !entry->is_modified) { - /* recycle the phar fp */ - entry->fp = phar->fp; - return entry; - } - /* bypass to temp stream */ - entry->fp = php_stream_temp_new(); - if (php_stream_copy_to_stream(fp, entry->fp, entry->uncompressed_filesize) != entry->uncompressed_filesize) { - spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename); - return NULL; + if (-1 == php_stream_seek(idata->phar->fp, local.filename_len + local.extra_len, SEEK_CUR)) { + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot seek to start of file data for file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; } } - return entry; + php_stream_seek(fp, idata->zero, SEEK_SET); + while (len--) { + CRC32(crc, php_stream_getc(fp)); + } + php_stream_seek(fp, idata->zero, SEEK_SET); + if (~crc == crc32) { + entry->is_crc_checked = 1; + return SUCCESS; + } else { + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: internal corruption of phar \"%s\" (crc32 mismatch on file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; + } } +/* }}} */ static inline void phar_set_32(char *buffer, int var) /* {{{ */ { @@ -2396,7 +1771,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error char *newstub; phar_entry_info *entry, *newentry; int halt_offset, restore_alias_len, global_flags = 0, closeoldfile; - char *buf, *pos, has_dirs = 0; + char *pos, has_dirs = 0; char manifest[18], entry_buffer[24]; off_t manifest_ftell; long offset; @@ -2417,11 +1792,9 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error return EOF; } -#if HAVE_PHAR_ZIP if (phar->is_zip) { return phar_zip_flush(phar, user_stub, len, error TSRMLS_CC); } -#endif if (phar->is_tar) { return phar_tar_flush(phar, user_stub, len, error TSRMLS_CC); } @@ -2558,7 +1931,6 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error } new_manifest_count = 0; offset = 0; - buf = (char *) emalloc(8192); for (zend_hash_internal_pointer_reset(&phar->manifest); zend_hash_has_more_elements(&phar->manifest) == SUCCESS; zend_hash_move_forward(&phar->manifest)) { @@ -2591,6 +1963,9 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash TSRMLS_CC); PHP_VAR_SERIALIZE_DESTROY(metadata_hash); } else { + if (entry->metadata_str.c) { + smart_str_free(&entry->metadata_str); + } entry->metadata_str.c = 0; entry->metadata_str.len = 0; } @@ -2602,7 +1977,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error if (oldfile && !entry->is_modified) { continue; } - if (!entry->fp || (entry->is_modified && entry->fp == phar->fp)) { + if (!phar_get_efp(entry)) { /* re-open internal file pointer just-in-time */ newentry = phar_open_jit(phar, entry, oldfile, error, 0 TSRMLS_CC); if (!newentry) { @@ -2613,20 +1988,16 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error } entry = newentry; } - file = entry->fp; - if (file == phar->fp) { - if (-1 == php_stream_seek(file, entry->offset_within_phar + phar->internal_file_start, SEEK_SET)) { - if (closeoldfile) { - php_stream_close(oldfile); - } - php_stream_close(newfile); - if (error) { - spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); - } - return EOF; + file = phar_get_efp(entry); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + if (closeoldfile) { + php_stream_close(oldfile); } - } else { - php_stream_rewind(file); + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; } newcrc32 = ~0; mytime = entry->uncompressed_filesize; @@ -2655,7 +2026,6 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname); } } - efree(buf); return EOF; } @@ -2671,36 +2041,41 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error php_stream_close(oldfile); } php_stream_close(newfile); - efree(buf); return EOF; } php_stream_flush(file); - if (file == phar->fp) { - if (-1 == php_stream_seek(file, entry->offset_within_phar + phar->internal_file_start, SEEK_SET)) { - if (closeoldfile) { - php_stream_close(oldfile); - } - php_stream_close(newfile); - if (error) { - spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); - } - return EOF; + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + if (closeoldfile) { + php_stream_close(oldfile); } - } else { - php_stream_rewind(file); + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + php_stream_filter_append((&entry->cfp->writefilters), filter); + if (entry->uncompressed_filesize != php_stream_copy_to_stream(file, entry->cfp, entry->uncompressed_filesize)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; } - php_stream_filter_append((&file->readfilters), filter); - entry->compressed_filesize = (php_uint32) php_stream_copy_to_stream(file, entry->cfp, entry->uncompressed_filesize+8192); php_stream_filter_flush(filter, 1); - entry->compressed_filesize += (php_uint32) php_stream_copy_to_stream(file, entry->cfp, entry->uncompressed_filesize+8192); + php_stream_flush(entry->cfp); php_stream_filter_remove(filter, 1 TSRMLS_CC); + php_stream_seek(entry->cfp, 0, SEEK_END); + entry->compressed_filesize = (php_uint32) php_stream_tell(entry->cfp); /* generate crc on compressed file */ php_stream_rewind(entry->cfp); entry->old_flags = entry->flags; entry->is_modified = 1; global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK); } - efree(buf); global_flags |= PHAR_HDR_SIGNATURE; /* write out manifest pre-header */ @@ -2849,7 +2224,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error } /* now copy the actual file data to the new phar */ - offset = 0; + offset = php_stream_tell(newfile); for (zend_hash_internal_pointer_reset(&phar->manifest); zend_hash_has_more_elements(&phar->manifest) == SUCCESS; zend_hash_move_forward(&phar->manifest)) { @@ -2862,24 +2237,9 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error if (entry->cfp) { file = entry->cfp; php_stream_rewind(file); - } else if (entry->fp && (entry->is_modified || !oldfile)) { - file = entry->fp; - if (file == phar->fp) { - if (-1 == php_stream_seek(file, entry->offset_within_phar + phar->internal_file_start, SEEK_SET)) { - if (closeoldfile) { - php_stream_close(oldfile); - } - php_stream_close(newfile); - if (error) { - spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); - } - return EOF; - } - } else { - php_stream_rewind(file); - } } else { - if (!oldfile) { + file = phar_get_efp(entry); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { if (closeoldfile) { php_stream_close(oldfile); } @@ -2889,21 +2249,20 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error } return EOF; } - if (-1 == php_stream_seek(oldfile, entry->offset_within_phar + phar->internal_file_start, SEEK_SET)) { - if (closeoldfile) { - php_stream_close(oldfile); - } - php_stream_close(newfile); - if (error) { - spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); - } - return EOF; + } + if (!file) { + if (closeoldfile) { + php_stream_close(oldfile); } - file = oldfile; + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; } /* this will have changed for all files that have either changed compression or been modified */ - entry->offset_within_phar = offset; + entry->offset = entry->offset_abs = offset; offset += entry->compressed_filesize; wrote = php_stream_copy_to_stream(file, newfile, entry->compressed_filesize); if (entry->compressed_filesize != wrote) { @@ -2919,13 +2278,18 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error entry->is_modified = 0; if (entry->cfp) { php_stream_close(entry->cfp); - entry->cfp = 0; + entry->cfp = NULL; } - if (entry->fp && entry->fp_refcount == 0) { - if (entry->fp != phar->fp) { + if (entry->fp_type == PHAR_MOD) { + /* this fp is in use by a phar_entry_data returned by phar_get_entry_data, it will be closed + when the phar_entry_data is phar_entry_delref'ed */ + if (entry->fp_refcount == 0 && entry->fp != phar->fp && entry->fp != phar->ufp) { php_stream_close(entry->fp); } - entry->fp = 0; + entry->fp = NULL; + entry->fp_type = PHAR_FP; + } else if (entry->fp_type == PHAR_UFP) { + entry->fp_type = PHAR_FP; } } @@ -3027,11 +2391,16 @@ int phar_flush(phar_archive_data *phar, char *user_stub, long len, char **error if (phar->fp) { php_stream_close(phar->fp); } + if (phar->ufp) { + php_stream_close(phar->ufp); + phar->ufp = NULL; + } if (closeoldfile) { php_stream_close(oldfile); } phar->internal_file_start = halt_offset + manifest_len + 4; + phar->halt_offset = halt_offset; phar->is_brandnew = 0; php_stream_rewind(newfile); @@ -3252,24 +2621,11 @@ skip_phar: PHP_MINIT_FUNCTION(phar) /* {{{ */ { - zend_module_entry *test; - ZEND_INIT_MODULE_GLOBALS(phar, php_phar_init_globals_module, NULL); REGISTER_INI_ENTRIES(); phar_has_bz2 = zend_hash_exists(&module_registry, "bz2", sizeof("bz2")); phar_has_zlib = zend_hash_exists(&module_registry, "zlib", sizeof("zlib")); - if (SUCCESS == zend_hash_find(&module_registry, "zip", sizeof("zip"), (void **) &test)) { - if (php_version_compare((const char *) test->version, "1.8.11") != -1) { - phar_has_zip = 1; - } else { - phar_has_zip = 0; - } - phar_zip_ver = (char *) test->version; - } else { - phar_has_zip = 0; - phar_zip_ver = NULL; - } phar_orig_compile_file = zend_compile_file; zend_compile_file = phar_compile_file; @@ -3349,22 +2705,7 @@ PHP_MINFO_FUNCTION(phar) /* {{{ */ php_info_print_table_row(2, "CVS revision", "$Revision$"); php_info_print_table_row(2, "Phar-based phar archives", "enabled"); php_info_print_table_row(2, "Tar-based phar archives", "enabled"); -#if HAVE_PHAR_ZIP - if (phar_has_zip) { - php_info_print_table_row(2, "ZIP-based phar archives", "enabled"); - } else { - if (phar_zip_ver) { - char *tmp; - spprintf(&tmp, 0, "disabled (pecl/zip version %s installed, we need zip >= 1.8.11)", phar_zip_ver); - php_info_print_table_row(2, "ZIP-based phar archives", tmp); - efree(tmp); - } else { - php_info_print_table_row(2, "ZIP-based phar archives", "disabled (install pecl/zip)"); - } - } -#else - php_info_print_table_row(2, "ZIP-based phar archives", "unavailable"); -#endif + php_info_print_table_row(2, "ZIP-based phar archives", "enabled"); if (phar_has_zlib) { php_info_print_table_row(2, "gzip compression", "enabled"); } else { @@ -3392,9 +2733,6 @@ PHP_MINFO_FUNCTION(phar) /* {{{ */ /* {{{ phar_module_entry */ static zend_module_dep phar_deps[] = { -#if HAVE_PHAR_ZIP - ZEND_MOD_OPTIONAL("zip") -#endif ZEND_MOD_OPTIONAL("zlib") ZEND_MOD_OPTIONAL("bz2") #if HAVE_SPL diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h index 82be02c6a7..540c8df1c7 100755 --- a/ext/phar/phar_internal.h +++ b/ext/phar/phar_internal.h @@ -25,10 +25,6 @@ #include #include "php.h" -#if HAVE_PHAR_ZIP -#include "ext/zip/lib/zip.h" -#include "ext/zip/lib/zipint.h" -#endif #include "tar.h" #include "php_ini.h" #include "zend_constants.h" @@ -174,8 +170,6 @@ ZEND_EXTERN_MODULE_GLOBALS(phar) int phar_has_bz2; int phar_has_zlib; -int phar_has_zip; -char *phar_zip_ver; #ifdef ZTS # include "TSRM.h" @@ -193,12 +187,25 @@ char *phar_zip_ver; # define php_uint16 uint16_t # endif #endif +#include "pharzip.h" #if HAVE_SPL typedef union _phar_archive_object phar_archive_object; typedef union _phar_entry_object phar_entry_object; #endif +/* + * used in phar_entry_info->fp_type to + */ +enum phar_fp_type { + /* regular file pointer phar_archive_data->fp */ + PHAR_FP, + /* uncompressed file pointer phar_archive_data->uncompressed_fp */ + PHAR_UFP, + /* modified file pointer phar_entry_info->fp */ + PHAR_MOD +}; + typedef struct _phar_archive_data phar_archive_data; /* entry for one file in a phar file */ typedef struct _phar_entry_info { @@ -214,7 +221,13 @@ typedef struct _phar_entry_info { zval *metadata; php_uint32 filename_len; char *filename; - long offset_within_phar; + enum phar_fp_type fp_type; + /* offset within original phar file of the file contents */ + long offset_abs; + /* offset within fp of the file contents */ + long offset; + /* offset within original phar file of the file header (for zip-based/tar-based) */ + long header_offset; php_stream *fp; php_stream *cfp; int fp_refcount; @@ -232,10 +245,6 @@ typedef struct _phar_entry_info { char tar_type; /* zip-based phar file stuff */ int is_zip:1; -#if HAVE_PHAR_ZIP - int index; - struct zip_file *zip; -#endif } phar_entry_info; /* information about a phar file (the archive itself) */ @@ -252,6 +261,8 @@ struct _phar_archive_data { php_uint32 min_timestamp; php_uint32 max_timestamp; php_stream *fp; + /* decompressed file contents are stored here */ + php_stream *ufp; int refcount; php_uint32 sig_flags; int sig_len; @@ -267,9 +278,6 @@ struct _phar_archive_data { int is_zip:1; /* tar-based phar variables */ int is_tar:1; -#if HAVE_PHAR_ZIP - struct zip *zip; -#endif }; #define PHAR_MIME_PHP '\0' @@ -286,8 +294,8 @@ typedef struct _phar_mime_type { /* stream access data for one file entry in a phar file */ typedef struct _phar_entry_data { phar_archive_data *phar; - /* stream position proxy, allows multiple open streams referring to the same fp */ php_stream *fp; + /* stream position proxy, allows multiple open streams referring to the same fp */ off_t position; /* for copies of the phar fp, defines where 0 is */ off_t zero; @@ -330,19 +338,31 @@ void phar_request_initialize(TSRMLS_D); void phar_object_init(TSRMLS_D); +int phar_open_entry_file(phar_archive_data *phar, phar_entry_info *entry, char **error TSRMLS_DC); int phar_open_filename(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); int phar_open_or_create_filename(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); int phar_open_compiled_file(char *alias, int alias_len, char **error TSRMLS_DC); int phar_get_archive(phar_archive_data **archive, char *fname, int fname_len, char *alias, int alias_len, char **error TSRMLS_DC); int phar_open_loaded(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); + +/* utility functions */ char *phar_create_default_stub(const char *index_php, const char *web_index, size_t *len, char **error TSRMLS_DC); +char * phar_decompress_filter(phar_entry_info * entry, int return_unknown); +char * phar_compress_filter(phar_entry_info * entry, int return_unknown); char *phar_fix_filepath(char *path, int *new_len, int use_cwd TSRMLS_DC); phar_entry_info * phar_open_jit(phar_archive_data *phar, phar_entry_info *entry, php_stream *fp, char **error, int for_write TSRMLS_DC); int phar_parse_metadata(char **buffer, zval **metadata, int is_zip TSRMLS_DC); void destroy_phar_manifest_entry(void *pDest); +int phar_seek_efp(phar_entry_info *entry, off_t offset, int whence, off_t position TSRMLS_DC); +php_stream *phar_get_efp(phar_entry_info *entry); +int phar_copy_entry_fp(phar_entry_info *source, phar_entry_info *dest, char **error TSRMLS_DC); +int phar_open_entry_fp(phar_entry_info *entry, char **error TSRMLS_DC); +int phar_create_writeable_entry(phar_archive_data *phar, phar_entry_info *entry, char **error TSRMLS_DC); +int phar_separate_entry_fp(phar_entry_info *entry, char **error TSRMLS_DC); +int phar_open_archive_fp(phar_archive_data *phar TSRMLS_DC); /* tar functions in tar.c */ int phar_is_tar(char *buf); @@ -352,11 +372,9 @@ int phar_open_or_create_tar(char *fname, int fname_len, char *alias, int alias_l int phar_tar_flush(phar_archive_data *phar, char *user_stub, long len, char **error TSRMLS_DC); /* zip functions in zip.c */ -int phar_open_zipfile(char *fname, int fname_len, char *alias, int alias_len, phar_archive_data** pphar, char **error TSRMLS_DC); +int phar_open_zipfile(php_stream *fp, char *fname, int fname_len, char *alias, int alias_len, phar_archive_data** pphar, char **error TSRMLS_DC); int phar_open_or_create_zip(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); -#if HAVE_PHAR_ZIP int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char **error TSRMLS_DC); -#endif #ifdef PHAR_MAIN static int phar_open_fp(php_stream* fp, char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC); diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index de54d0588a..63b479ea74 100755 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -243,7 +243,7 @@ static int phar_file_action(phar_entry_data *phar, char *mime_type, int code, ch } /* prepare to output */ - if (!phar->fp) { + if (!phar_get_efp(phar->internal_file)) { char *error; if (!phar_open_jit(phar->phar, phar->internal_file, phar->phar->fp, &error, 0 TSRMLS_CC)) { if (error) { @@ -252,29 +252,16 @@ static int phar_file_action(phar_entry_data *phar, char *mime_type, int code, ch } return -1; } - phar->fp = phar->internal_file->fp; - if (phar->internal_file->fp == phar->phar->fp) { - phar->zero = phar->internal_file->offset_within_phar; - if (!phar->is_tar && !phar->is_zip) { - phar->zero += phar->phar->internal_file_start; - } - } + phar->fp = phar_get_efp(phar->internal_file); + phar->zero = phar->internal_file->offset; } - php_stream_seek(phar->fp, phar->zero, SEEK_SET); + phar_seek_efp(phar->internal_file, 0, SEEK_SET, 0 TSRMLS_CC); do { - if (!phar->zero) { - got = php_stream_read(phar->fp, buf, 8192); - PHPWRITE(buf, got); - if (phar->fp->eof) { - break; - } - } else { - got = php_stream_read(phar->fp, buf, MIN(8192, phar->internal_file->uncompressed_filesize - phar->position)); - PHPWRITE(buf, got); - phar->position = php_stream_tell(phar->fp) - phar->zero; - if (phar->position == (off_t) phar->internal_file->uncompressed_filesize) { - break; - } + got = php_stream_read(phar->fp, buf, MIN(8192, phar->internal_file->uncompressed_filesize - phar->position)); + PHPWRITE(buf, got); + phar->position = php_stream_tell(phar->fp) - phar->zero; + if (phar->position == (off_t) phar->internal_file->uncompressed_filesize) { + break; } } while (1); @@ -1313,44 +1300,65 @@ PHP_METHOD(Phar, isPhar) static int phar_copy_file_contents(phar_entry_info *entry, php_stream *fp TSRMLS_DC) /* {{{ */ { - /* if not available, open the file for reading */ - if (!entry->fp) { - char *error; - if (!phar_open_jit(entry->phar, entry, entry->phar->fp, &error, 0 TSRMLS_CC)) { - if (error) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents: %s", entry->phar->fname, entry->filename, error); - efree(error); - } else { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents", entry->phar->fname, entry->filename); - } - return FAILURE; + char *error; + off_t offset; + + if (FAILURE == phar_open_entry_fp(entry, &error TSRMLS_CC)) { + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents: %s", entry->phar->fname, entry->filename, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents", entry->phar->fname, entry->filename); } + return FAILURE; } /* copy old contents in entirety */ - php_stream_seek(entry->fp, entry->phar->internal_file_start + entry->offset_within_phar, SEEK_SET); - if (entry->uncompressed_filesize != php_stream_copy_to_stream(entry->fp, fp, entry->uncompressed_filesize)) { + phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC); + offset = php_stream_tell(fp); + if (entry->uncompressed_filesize != php_stream_copy_to_stream(phar_get_efp(entry), fp, entry->uncompressed_filesize)) { zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Cannot convert phar archive \"%s\", unable to copy entry \"%s\" contents", entry->phar->fname, entry->filename); return FAILURE; } - if (entry->fp != entry->phar->fp) { - php_stream_close(entry->fp); + if (entry->fp_type == PHAR_MOD) { + /* save for potential restore on error */ + entry->cfp = entry->fp; entry->fp = NULL; } + /* set new location of file contents */ + entry->fp_type = PHAR_FP; + entry->offset = offset; return SUCCESS; } /* }}} */ +static int phar_restore_apply(void *data TSRMLS_DC) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)data; + + if (entry->cfp) { + entry->fp_type = PHAR_MOD; + entry->fp = entry->cfp; + entry->cfp = NULL; + entry->offset = 0; + } else { + entry->fp_type = PHAR_FP; + entry->fp = NULL; + entry->offset = entry->offset_abs; + } + return ZEND_HASH_APPLY_KEEP; +} + static void phar_convert_to_other(phar_archive_data *source, int convert, php_uint32 flags TSRMLS_DC) /* {{{ */ { phar_archive_data phar = {0}; - long offset = 0; - char *error, *opened_path = NULL; -#if HAVE_PHAR_ZIP - int fd, ziperror; -#endif + char *error; + zval *userz; + long tmp; + php_stream *fp; + phar_entry_info *entry, newentry; /* set whole-archive compression from parameter */ phar.flags = flags; @@ -1360,39 +1368,6 @@ static void phar_convert_to_other(phar_archive_data *source, int convert, php_ui break; case 2 : phar.is_zip = 1; -#if HAVE_PHAR_ZIP - if (!((fd = php_open_temporary_fd(NULL, "pharzip", &opened_path TSRMLS_CC)) >= 0)) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open temporary zip archive", source->fname); - return; - } - close(fd); - unlink(opened_path); - phar.zip = zip_open(opened_path, ZIP_CREATE, &ziperror); - if (!phar.zip) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - - efree(opened_path); - tmp_len = zip_error_to_str(NULL, 0, ziperror, ziperror); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open temporary zip archive", source->fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, ziperror, ziperror)) { - efree(tmp); - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open temporary zip archive", source->fname); - } else { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\", unable to open temporary zip archive: %s", source->fname, tmp); - efree(tmp); - } - } - return; - } -#endif break; } @@ -1403,67 +1378,51 @@ static void phar_convert_to_other(phar_archive_data *source, int convert, php_ui phar.fname = source->fname; /* first copy each file's uncompressed contents to a temporary file and set per-file flags */ for (zend_hash_internal_pointer_reset(&source->manifest); SUCCESS == zend_hash_has_more_elements(&source->manifest); zend_hash_move_forward(&source->manifest)) { - phar_entry_info *entry, newentry; + if (FAILURE == zend_hash_get_current_data(&source->manifest, (void **) &entry)) { zend_hash_destroy(&(phar.manifest)); php_stream_close(phar.fp); zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Cannot convert phar archive \"%s\"", source->fname); - if (opened_path) { - efree(opened_path); + zend_hash_apply(&source->manifest, phar_restore_apply TSRMLS_CC); + return; + } + if (!convert) { + /* converting to Phar */ + if (entry->filename_len == sizeof(".phar/stub.php")-1 && !strncmp(entry->filename, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { + /* do not copy stub file */ + continue; } -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); + if (entry->filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry->filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + /* do not copy alias file */ + continue; } -#endif - return; } if (entry->fp_refcount > 0) { zend_hash_destroy(&(phar.manifest)); php_stream_close(phar.fp); zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Cannot convert phar archive \"%s\", open file pointers for entry \"%s\"", source->fname, entry->filename); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } + zend_hash_apply(&source->manifest, phar_restore_apply TSRMLS_CC); return; } newentry = *entry; - if (FAILURE == phar_copy_file_contents(entry, phar.fp TSRMLS_CC)) { + if (FAILURE == phar_copy_file_contents(&newentry, phar.fp TSRMLS_CC)) { zend_hash_destroy(&(phar.manifest)); php_stream_close(phar.fp); /* exception already thrown */ -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } + zend_hash_apply(&source->manifest, phar_restore_apply TSRMLS_CC); return; } newentry.filename = estrndup(newentry.filename, newentry.filename_len); if (newentry.metadata) { SEPARATE_ZVAL(&(newentry.metadata)); } - newentry.offset_within_phar = offset; - offset += newentry.uncompressed_filesize; newentry.is_zip = phar.is_zip; newentry.is_tar = phar.is_tar; if (newentry.is_tar) { newentry.tar_type = (entry->is_dir ? TAR_DIR : TAR_FILE); } - newentry.fp = phar.fp; -#if HAVE_PHAR_ZIP - newentry.index = -1; -#endif newentry.is_modified = 1; newentry.phar = &phar; newentry.old_flags = newentry.flags & ~PHAR_ENT_COMPRESSION_MASK; /* remove compression from old_flags */ @@ -1471,90 +1430,28 @@ static void phar_convert_to_other(phar_archive_data *source, int convert, php_ui } /* next copy the stub and flush */ - if (phar.is_zip) { - phar.fname = opened_path; - } - - if (source->is_zip) { - phar_entry_info *entry; - long tmp = 0; - zval *userz; + if (source->is_tar || source->is_zip) { if (FAILURE == (zend_hash_find(&source->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1, (void **)&entry))) { /* use default stub - the existing one in the manifest will be used if present */ phar_flush(&phar, NULL, 0, &error TSRMLS_CC); goto finalize; } - if (!entry->fp && !phar_open_jit(source, entry, NULL, &error, 0 TSRMLS_CC)) { - if (error) { - efree(error); - } - php_stream_close(phar.fp); - zend_hash_destroy(&(phar.manifest)); - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\": unable to retrieve stub", source->fname, error); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } - return; - } - /* copy the other phar's stub */ - php_stream_seek(entry->fp, 0, SEEK_SET); - ALLOC_ZVAL(userz); - php_stream_to_zval(entry->fp, userz); - tmp = entry->uncompressed_filesize; - phar_flush(&phar, (char *) &userz, -tmp, &error TSRMLS_CC); - efree(userz); - php_stream_close(entry->fp); - if (error) { - zend_hash_destroy(&(phar.manifest)); - php_stream_close(phar.fp); - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "Cannot convert phar archive \"%s\": %s", source->fname, error); - efree(error); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } - return; - } + fp = php_stream_open_wrapper(source->fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, NULL); + php_stream_seek(fp, entry->offset, SEEK_SET); + /* use this unused value to set the stub size */ + source->internal_file_start = entry->uncompressed_filesize; } else { - zval *userz; - long tmp; - - if (source->is_tar) { - phar_entry_info *entry; - - if (FAILURE == (zend_hash_find(&source->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1, (void **)&entry))) { - /* use default stub - the existing one in the manifest will be used if present */ - phar_flush(&phar, NULL, 0, &error TSRMLS_CC); - goto finalize; - } - php_stream_seek(source->fp, entry->offset_within_phar, SEEK_SET); - /* use this unused value to set the stub size */ - source->internal_file_start = entry->uncompressed_filesize; - } else { - php_stream_seek(source->fp, 0, SEEK_SET); - } - /* copy the other phar's stub */ - ALLOC_ZVAL(userz); - php_stream_to_zval(source->fp, userz); - tmp = source->internal_file_start; - phar_flush(&phar, (char *) &userz, -tmp, &error TSRMLS_CC); - efree(userz); - if (source->is_tar) { - source->internal_file_start = 0; - } + fp = php_stream_open_wrapper(source->fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, NULL); } + /* copy the other phar's stub */ + ALLOC_ZVAL(userz); + php_stream_to_zval(fp, userz); + tmp = source->internal_file_start; + source->internal_file_start = 0; + phar_flush(&phar, (char *) &userz, -tmp, &error TSRMLS_CC); + php_stream_close(fp); + efree(userz); if (error) { zend_hash_destroy(&(phar.manifest)); @@ -1562,14 +1459,6 @@ static void phar_convert_to_other(phar_archive_data *source, int convert, php_ui "Cannot convert phar archive \"%s\": %s", source->fname, error); efree(error); php_stream_close(phar.fp); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } return; } @@ -1582,14 +1471,6 @@ finalize: php_stream_close(phar.fp); zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Cannot convert phar archive \"%s\"", source->fname); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } /* we can't throw an exception or bad crap will happen */ zend_error(E_ERROR, "Error: could not convert phar archive \"%s\", phar is in unstable state, shutting down", source->fname); return; @@ -1600,6 +1481,7 @@ finalize: /* a little hack to prevent destruction of data */ dtor_func_t save; + entry->phar = source; save = phar.manifest.pDestructor; phar.manifest.pDestructor = NULL; zend_hash_add(&source->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1, (void*)entry, sizeof(phar_entry_info), NULL); @@ -1611,6 +1493,7 @@ finalize: /* a little hack to prevent destruction of data */ dtor_func_t save; + entry->phar = source; save = phar.manifest.pDestructor; phar.manifest.pDestructor = NULL; zend_hash_add(&source->manifest, ".phar/alias.txt", sizeof(".phar/alias.txt")-1, (void*)entry, sizeof(phar_entry_info), NULL); @@ -1621,14 +1504,6 @@ finalize: } zend_hash_destroy(&(phar.manifest)); php_stream_close(phar.fp); -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - } -#endif - if (opened_path) { - efree(opened_path); - } /* we can't throw an exception or bad crap will happen */ zend_error(E_ERROR, "Error: could not convert phar archive \"%s\", phar is in unstable state, shutting down", source->fname); return; @@ -1639,53 +1514,32 @@ finalize: zval_dtor(mine->metadata); efree(mine->metadata); } + if (mine->fp_type == PHAR_MOD && mine->fp != source->fp && mine->fp != source->ufp) { + php_stream_close(mine->fp); + } *mine = *entry; mine->phar = source; } + if (source->fp && source->refcount == 1) { + php_stream_close(source->fp); + } source->fp = phar.fp; /* don't free stuff */ phar.manifest.pDestructor = NULL; zend_hash_destroy(&(phar.manifest)); source->is_zip = phar.is_zip; + if (phar.is_zip || phar.is_tar) { + source->internal_file_start = source->halt_offset = 0; + } source->is_tar = phar.is_tar; + if (source->signature) { + efree(source->signature); + } if (phar.signature) { - if (source->signature) { - efree(source->signature); - } source->signature = phar.signature; + } else { + source->signature = 0; } -#if HAVE_PHAR_ZIP - if (phar.is_zip) { - _zip_free(phar.zip); - _zip_free(source->zip); - if (FAILURE == php_copy_file(opened_path, source->fname TSRMLS_CC)) { - /* we can't throw an exception or bad crap will happen */ - zend_error(E_ERROR, "Error: could not copy newly created zip to \"%s\", phar is in unstable state, shutting down", source->fname); - } - source->zip = zip_open(source->fname, 0, &ziperror); - if (!source->zip) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - - efree(opened_path); - tmp_len = zip_error_to_str(NULL, 0, ziperror, ziperror); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - zend_error(E_ERROR, "Error: could not re-open newly created zip \"%s\", phar is in unstable state, shutting down", source->fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, ziperror, ziperror)) { - efree(tmp); - zend_error(E_ERROR, "Error: could not re-open newly created zip \"%s\", phar is in unstable state, shutting down", source->fname); - } else { - zend_error(E_ERROR, "Error: could not re-open newly created zip \"%s\" (%s), phar is in unstable state, shutting down", source->fname, tmp); - efree(tmp); - } - } - return; - } - efree(opened_path); - } -#endif } /* {{{ proto bool Phar::convertToTar([int compression]) @@ -1781,44 +1635,6 @@ PHP_METHOD(Phar, convertToZip) return; } -#if HAVE_PHAR_ZIP - if (!phar_has_zip) { - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot convert phar archive to zip format, zip-based phar archives are disabled (enable ext/zip in php.ini)"); - return; - } - - if (!zend_hash_num_elements(&phar_obj->arc.archive->manifest)) { - int ziperror; - /* nothing need be done specially, this has no files in it */ - phar_obj->arc.archive->is_zip = 1; - phar_obj->arc.archive->internal_file_start = 0; - phar_obj->arc.archive->is_modified = 1; - phar_obj->arc.archive->zip = zip_open(phar_obj->arc.archive->fname, ZIP_CREATE, &ziperror); - if (!phar_obj->arc.archive->zip) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - tmp_len = zip_error_to_str(NULL, 0, ziperror, ziperror); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot convert phar archive to zip format, unable to open", phar_obj->arc.archive->fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, ziperror, ziperror)) { - efree(tmp); - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot convert phar archive to zip format, unable to open", phar_obj->arc.archive->fname); - } else { - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot convert phar archive to zip format, unable to open: %s", phar_obj->arc.archive->fname, tmp); - efree(tmp); - } - } - return; - } - RETURN_TRUE; - } - if (phar_obj->arc.archive->donotflush) { zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Cannot convert phar archive to zip format, call stopBuffering() first"); @@ -1833,11 +1649,6 @@ PHP_METHOD(Phar, convertToZip) } phar_convert_to_other(phar_obj->arc.archive, 2, 0 TSRMLS_CC); RETURN_TRUE; -#else - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot convert phar archive to zip format, zip-based phar archives are unavailable"); - return; -#endif } /* }}} */ @@ -2359,12 +2170,6 @@ PHP_METHOD(Phar, compressAllFilesBZIP2) char *error; PHAR_ARCHIVE_OBJECT(); - if (phar_obj->arc.archive->is_zip) { - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, - "Cannot compress all files as Bzip2, not possible with zip-based phar archives"); - return; - } - if (PHAR_G(readonly)) { zend_throw_exception_ex(spl_ce_BadMethodCallException, 0 TSRMLS_CC, "Phar is readonly, cannot change compression"); @@ -2438,7 +2243,6 @@ PHP_METHOD(Phar, copy) const char *pcr_error; int oldfile_len, newfile_len; phar_entry_info *oldentry, newentry = {0}, *temp; - php_stream *fp; PHAR_ARCHIVE_OBJECT(); if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { @@ -2477,67 +2281,28 @@ PHP_METHOD(Phar, copy) RETURN_FALSE; } - fp = oldentry->fp; - if (fp && fp != phar_obj->arc.archive->fp) { - /* new file */ - newentry.fp = php_stream_temp_new(); - fp = newentry.fp; - php_stream_seek(oldentry->fp, 0, SEEK_SET); - if (oldentry->uncompressed_filesize != php_stream_copy_to_stream(oldentry->fp, fp, oldentry->uncompressed_filesize)) { - php_stream_close(fp); - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "file \"%s\" could not be copied to file \"%s\" in %s, copy failed", oldfile, newfile, phar_obj->arc.archive->fname); - return; - } - } else { - newentry.fp = NULL; - } - memcpy((void *) &newentry, oldentry, sizeof(phar_entry_info)); if (newentry.metadata) { SEPARATE_ZVAL(&(newentry.metadata)); newentry.metadata_str.c = NULL; newentry.metadata_str.len = 0; } -#if HAVE_PHAR_ZIP - if (oldentry->is_zip) { - int zindex; - /* for new files, start with an empty string */ - struct zip_source *s; - if (!newentry.fp) { - if (!phar_open_jit(phar_obj->arc.archive, oldentry, NULL, &error, 0 TSRMLS_CC)) { - php_stream_close(fp); - if (error) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "file \"%s\" could not be copied to file \"%s\" in %s, open of source file failed: %s", oldfile, newfile, phar_obj->arc.archive->fname, error); - efree(error); - } else { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "file \"%s\" could not be copied to file \"%s\" in %s, open of source file failed", oldfile, newfile, phar_obj->arc.archive->fname); - } - return; - } - fp = oldentry->fp; - oldentry->fp = NULL; - } - s = zip_source_buffer(phar_obj->arc.archive->zip, (void *)"", 0, 0); - if (-1 == (zindex = zip_add(phar_obj->arc.archive->zip, newfile, s))) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, - "file \"%s\" could not be copied to file \"%s\" in %s, creation of destination file failed: %s", oldfile, newfile, phar_obj->arc.archive->fname, zip_strerror(phar_obj->arc.archive->zip)); - zip_error_clear(phar_obj->arc.archive->zip); - return; - } - newentry.index = zindex; - newentry.zip = NULL; - } -#endif - newentry.fp = fp; newentry.filename = estrndup(newfile, newfile_len); newentry.filename_len = newfile_len; - newentry.is_modified = 1; newentry.fp_refcount = 0; + + if (oldentry->fp_type != PHAR_FP) { + if (FAILURE == phar_copy_entry_fp(oldentry, &newentry, &error TSRMLS_CC)) { + efree(newentry.filename); + php_stream_close(newentry.fp); + zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC, error); + efree(error); + return; + } + } + + zend_hash_add(&oldentry->phar->manifest, newfile, newfile_len, (void*)&newentry, sizeof(phar_entry_info), NULL); phar_obj->arc.archive->is_modified = 1; - zend_hash_add(&phar_obj->arc.archive->manifest, newfile, newfile_len, (void*)&newentry, sizeof(phar_entry_info), NULL); phar_flush(phar_obj->arc.archive, 0, 0, &error TSRMLS_CC); if (error) { @@ -2691,15 +2456,6 @@ PHP_METHOD(Phar, offsetUnset) } entry->is_modified = 0; entry->is_deleted = 1; -#if HAVE_PHAR_ZIP - if (entry->is_zip) { - if (entry->zip) { - zip_fclose(entry->zip); - entry->zip = 0; - } - zip_delete(phar_obj->arc.archive->zip, entry->index); - } -#endif /* we need to "flush" the stream to save the newly deleted file on disk */ phar_flush(phar_obj->arc.archive, 0, 0, &error TSRMLS_CC); if (error) { @@ -2719,49 +2475,34 @@ PHP_METHOD(Phar, offsetUnset) */ PHP_METHOD(Phar, getStub) { - char *buf; size_t len; + char *buf; php_stream *fp; + php_stream_filter *filter = NULL; + phar_entry_info *stub; PHAR_ARCHIVE_OBJECT(); -#if HAVE_PHAR_ZIP - if (phar_obj->arc.archive->is_zip) { - struct zip_stat zs; - struct zip_file *zf; - int index; - - if (-1 == zip_stat(phar_obj->arc.archive->zip, ".phar/stub.php", 0, &zs)) { - zip_error_clear(phar_obj->arc.archive->zip); - RETURN_STRINGL("", 0, 1); - } - index = zs.index; - len = zs.size; - zf = zip_fopen_index(phar_obj->arc.archive->zip, index, 0); - if (!zf) { - zip_error_clear(phar_obj->arc.archive->zip); - RETURN_STRINGL("", 0, 1); - } - buf = safe_emalloc(len, 1, 1); - if (len != zip_fread(zf, buf, len)) { - zip_fclose(zf); - efree(buf); - zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, - "Unable to read stub"); - return; - } - buf[len] = '\0'; - - RETURN_STRINGL(buf, len, 0); - } -#endif - if (phar_obj->arc.archive->is_tar) { - phar_entry_info *stub; + if (phar_obj->arc.archive->is_tar || phar_obj->arc.archive->is_zip) { if (SUCCESS == zend_hash_find(&(phar_obj->arc.archive->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1, (void **)&stub)) { - if (phar_obj->arc.archive->fp && !phar_obj->arc.archive->is_brandnew) { + if (phar_obj->arc.archive->fp && !phar_obj->arc.archive->is_brandnew && !(stub->flags & PHAR_ENT_COMPRESSION_MASK)) { fp = phar_obj->arc.archive->fp; } else { fp = php_stream_open_wrapper(phar_obj->arc.archive->fname, "rb", 0, NULL); + if (stub->flags & PHAR_ENT_COMPRESSION_MASK) { + char *filter_name; + + if ((filter_name = phar_decompress_filter(stub, 0)) != NULL) { + filter = php_stream_filter_create(phar_decompress_filter(stub, 0), NULL, php_stream_is_persistent(fp) TSRMLS_CC); + } else { + filter = NULL; + } + if (!filter) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "phar error: unable to read stub of phar \"%s\" (cannot create %s filter)", phar_obj->arc.archive->fname, phar_decompress_filter(stub, 1)); + return; + } + php_stream_filter_append(&fp->readfilters, filter); + } } if (!fp) { @@ -2770,7 +2511,7 @@ PHP_METHOD(Phar, getStub) return; } - php_stream_seek(fp, stub->offset_within_phar, SEEK_SET); + php_stream_seek(fp, stub->offset_abs, SEEK_SET); len = stub->uncompressed_filesize; goto carry_on; } else { @@ -2803,6 +2544,10 @@ carry_on: efree(buf); return; } + if (filter) { + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1 TSRMLS_CC); + } if (fp != phar_obj->arc.archive->fp) { php_stream_close(fp); } @@ -3101,12 +2846,6 @@ PHP_METHOD(PharFileInfo, chmod) zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC, "Cannot modify permissions for file \"%s\" in phar \"%s\", write operations are prohibited", entry_obj->ent.entry->filename, entry_obj->ent.entry->phar->fname); return; } -#if HAVE_PHAR_ZIP - if (entry_obj->ent.entry->is_zip) { - zend_throw_exception_ex(phar_ce_PharException, 0 TSRMLS_CC, "Cannot modify permissions for file \"%s\" in phar \"%s\", not supported for zip-based phars", entry_obj->ent.entry->filename, entry_obj->ent.entry->phar->fname); - return; - } -#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &perms) == FAILURE) { return; } diff --git a/ext/phar/pharzip.c b/ext/phar/pharzip.c deleted file mode 100644 index ed21c1d022..0000000000 --- a/ext/phar/pharzip.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | phar php single-file executable PHP extension, zip implementation | - +----------------------------------------------------------------------+ - | Copyright (c) 2005-2008 The PHP Group | - +----------------------------------------------------------------------+ - | This source file is subject to version 3.01 of the PHP license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | http://www.php.net/license/3_01.txt. | - | If you did not receive a copy of the PHP license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Authors: Gregory Beaver | - | Marcus Boerger | - +----------------------------------------------------------------------+ -*/ - -/* $Id$ */ - -#include "phar_internal.h" - -#ifdef PHP_WIN32 -static inline void phar_unixify_path_separators(char *path, int path_len) /* {{{ */ -{ - char *s; - - /* unixify win paths */ - for (s = path; s - path < path_len; s++) { - if (*s == '\\') { - *s = '/'; - } - } -} -/* }}} */ -#endif - -static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, php_uint16 len TSRMLS_DC) -{ - php_uint16 left = len; - union { - phar_zip_extra_field_header header; - phar_zip_unix3 unix3; - } h; - - do { - if (sizeof(h.header) != php_stream_read(fp, (char *) &h.header, sizeof(h.header))) { - return FAILURE; - } - /* clean up header for big-endian systems */ - if (h.header.tag != "\x75\x6e") { - /* skip to next header */ - php_stream_seek(fp, h.header.size, SEEK_CUR); - len -= h.header.size; - continue; - } - /* unix3 header found */ - /* clean up header for big-endian systems */ - if (sizeof(h.unix3) != php_stream_read(fp, (char *) &h.unix3, sizeof(h.unix3))) { - if (h.unix3.size > sizeof(h.unix3) - 4) { - /* skip symlink filename - we may add this support in later */ - php_stream_seek(fp, h.unix3.size - sizeof(h.unix3.size), SEEK_CUR); - } - /* set permissions */ - entry->flags &= PHAR_ENT_COMPRESSION_MASK; - if (entry->is_dir) { - entry->flags |= h.unix3.perms & PHAR_ENT_PERM_DEF_DIR; - } else { - entry->flags |= h.unix3.perms & PHAR_ENT_PERM_DEF_FILE; - } - } - } while (len); - return SUCCESS; -} - -/* - extracted from libzip - zip_dirent.c -- read directory entry (local or central), clean dirent - Copyright (C) 1999, 2003, 2004, 2005 Dieter Baron and Thomas Klausner - - This function is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - 3. The names of the authors may not be used to endorse or promote - products derived from this software without specific prior - written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN - IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -static time_t phar_zip_d2u_time(int dtime, int ddate) -{ - struct tm *tm, tmbuf; - time_t now; - - now = time(NULL); - tm = php_localtime_r(&now, &tmbuf); - - tm->tm_year = ((ddate>>9)&127) + 1980 - 1900; - tm->tm_mon = ((ddate>>5)&15) - 1; - tm->tm_mday = ddate&31; - - tm->tm_hour = (dtime>>11)&31; - tm->tm_min = (dtime>>5)&63; - tm->tm_sec = (dtime<<1)&62; - - return mktime(tm); -} - -int phar_zip_parse(char *fname, int fname_len, char **error TSRMLS_DC) -{ - php_stream *fp; - char buf[8192], *metadata; - phar_zip_dir_end locator; - long size; - size_t read; - php_uint16 i; - phar_archive_data *mydata = NULL; - phar_entry_info entry = {0}; - - fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, NULL); - - php_stream_seek(fp, 0, SEEK_END); - size = php_stream_tell(fp); - if (size > sizeof(locator) + 65536) { - /* seek to max comment length + end of central directory record */ - size = sizeof(locator) + 65536; - if (FAILURE == php_stream_seek(fp, -size, SEEK_END)) { - php_stream_close(fp); - return FAILURE; - } - } else { - php_stream_seek(fp, 0, SEEK_SET); - } - do { - char *p = buf; - if (!(read = php_stream_read(fp, buf, 8192))) { - php_stream_close(fp); - return FAILURE; - } - while ((p=(char *) memchr(p + 1, 'P', (size_t)(buf - (p+1) + 8192 - 4 + 1))) != NULL) { - if (!memcmp(p + 1, "K\5\6", 3)) { - if (p - buf < sizeof(locator)) { - /* didn't read in the whole thing, back up */ - php_stream_seek(fp, 8192 - (p - buf), SEEK_CUR); - if (sizeof(locator) != php_stream_read(fp, (char *) &locator, sizeof(locator))) { - php_stream_close(fp); - return FAILURE; - } - } else { - memcpy((void *)&locator, (void *) p, sizeof(locator)); - } - goto foundit; - } - } - } while (read == 8192); - php_stream_close(fp); - return FAILURE; -foundit: - if (locator.centraldisk != 0 || locator.disknumber != 0) { - /* split archives not handled */ - php_stream_close(fp); - return FAILURE; - } - mydata = ecalloc(sizeof(phar_archive_data), 1); - mydata->fname = estrndup(fname, fname_len); -#ifdef PHP_WIN32 - phar_unixify_path_separators(mydata->fname, fname_len); -#endif - mydata->is_zip = 1; - mydata->fname_len = fname_len; - /* clean up on big-endian systems */ - /* read in archive comment, if any */ - if (locator.comment_len) { - metadata = (char *) emalloc(locator.comment_len); - if (locator.comment_len != php_stream_read(fp, metadata, locator.comment_len)) { - php_stream_close(fp); - efree(mydata->fname); - efree(mydata); - return FAILURE; - } - if (phar_parse_metadata(&metadata, &mydata->metadata, locator.comment_len TSRMLS_CC) == FAILURE) { - /* if not valid serialized data, it is a regular string */ - ALLOC_INIT_ZVAL(mydata->metadata); - Z_STRVAL_P(mydata->metadata) = metadata; - Z_STRLEN_P(mydata->metadata) = locator.comment_len; - Z_TYPE_P(mydata->metadata) = IS_STRING; - } - } else { - mydata->metadata = NULL; - } - /* seek to central directory */ - php_stream_seek(fp, locator.cdir_offset, SEEK_SET); - /* read in central directory */ - zend_hash_init(&mydata->manifest, sizeof(phar_entry_info), - zend_get_hash_value, destroy_phar_manifest_entry, 0); - entry.phar = mydata; - entry.is_zip = 1; -#define PHAR_ZIP_FAIL \ - zend_hash_destroy(&mydata->manifest); \ - php_stream_close(fp); \ - efree(mydata->fname); \ - if (mydata->metadata) { \ - zval_dtor(mydata->metadata); \ - } \ - efree(mydata); \ - return FAILURE - - /* add each central directory item to the manifest */ - for (i = 0; i < locator.cdir_size; i++) { - phar_zip_central_dir_file zipentry; - - /* sizeof(zipentry) reports size of contents + 2, no idea why */ - if (sizeof(zipentry)-2 != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry)-2)) { - PHAR_ZIP_FAIL; - } - /* clean up for bigendian systems */ - if (memcmp("PK\1\2", zipentry.signature, 4)) { - /* corrupted entry */ - PHAR_ZIP_FAIL; - } - entry.compressed_filesize = zipentry.compsize; - entry.uncompressed_filesize = zipentry.uncompsize; - entry.crc32 = zipentry.crc32; - entry.timestamp = phar_zip_d2u_time(zipentry.timestamp, zipentry.datestamp); - entry.flags = PHAR_ENT_PERM_DEF_FILE; - if (zipentry.flags & PHAR_ZIP_FLAG_ENCRYPTED) { - PHAR_ZIP_FAIL; - } - if (!zipentry.filename_len) { - PHAR_ZIP_FAIL; - } - entry.filename_len = zipentry.filename_len; - entry.filename = (char *) emalloc(zipentry.filename_len+1); - if (entry.filename_len != php_stream_read(fp, entry.filename, entry.filename_len)) { - efree(entry.filename); - PHAR_ZIP_FAIL; - } - entry.filename[entry.filename_len] = '\0'; - if (entry.filename[entry.filename_len - 1] == '/') { - entry.is_dir = 1; - entry.filename_len--; - entry.flags |= PHAR_ENT_PERM_DEF_DIR; - } else { - entry.is_dir = 0; - } - if (zipentry.extra_len) { - if (FAILURE == phar_zip_process_extra(fp, &entry, zipentry.extra_len TSRMLS_CC)) { - PHAR_ZIP_FAIL; - } - } - switch (zipentry.compressed) { - case PHAR_ZIP_COMP_NONE : - /* compression flag already set */ - break; - case PHAR_ZIP_COMP_DEFLATE : - entry.flags |= PHAR_ENT_COMPRESSED_GZ; - if (!phar_has_zlib) { - if (error) { - spprintf(error, 0, "zlib extension is required for zlib compressed zip-based .phar file \"%s\"", fname); - } - PHAR_ZIP_FAIL; - } - break; - case PHAR_ZIP_COMP_BZIP2 : - entry.flags |= PHAR_ENT_COMPRESSED_BZ2; - if (!phar_has_bz2) { - if (error) { - spprintf(error, 0, "bz2 extension is required for Bzip2 compressed zip-based .phar file \"%s\"", fname); - } - PHAR_ZIP_FAIL; - } - break; - default : - PHAR_ZIP_FAIL; - } - /* get file metadata */ - if (zipentry.comment_len) { - metadata = (char *) emalloc(zipentry.comment_len); - if (zipentry.comment_len != php_stream_read(fp, metadata, zipentry.comment_len)) { - PHAR_ZIP_FAIL; - } - if (phar_parse_metadata(&metadata, &(entry.metadata), zipentry.comment_len TSRMLS_CC) == FAILURE) { - /* if not valid serialized data, it is a regular string */ - ALLOC_INIT_ZVAL(entry.metadata); - Z_STRVAL_P(entry.metadata) = metadata; - Z_STRLEN_P(entry.metadata) = zipentry.comment_len; - Z_TYPE_P(entry.metadata) = IS_STRING; - } else { - efree(metadata); - } - } else { - entry.metadata = NULL; - } - if (entry.filename_len == sizeof(".phar/alias.txt")-1 && strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { - size_t remember = php_stream_tell(fp); - php_stream_filter *filter; - /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */ - zval filterparams; - - /* archive alias found, seek to file contents, do not validate local header. Potentially risky, but - not very. */ - php_stream_seek(fp, zipentry.offset + sizeof(phar_zip_file_header) + entry.filename_len + zipentry.extra_len, SEEK_SET); - if (entry.flags & PHAR_ENT_COMPRESSED_GZ) { -/* this is defined in zlib's zconf.h */ -#ifndef MAX_WBITS -#define MAX_WBITS 15 -#endif - array_init(&filterparams); - add_assoc_long(&filterparams, "window", MAX_WBITS + 32); - filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp) TSRMLS_CC); - if (!filter) { - add_assoc_long(&filterparams, "window", MAX_WBITS); - filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp) TSRMLS_CC); - if (!filter) { - PHAR_ZIP_FAIL; - } - } - zval_dtor(&filterparams); - php_stream_filter_append(&fp->readfilters, filter); - if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &(mydata->alias), entry.uncompressed_filesize, 0)) || !mydata->alias) { - PHAR_ZIP_FAIL; - } - php_stream_filter_flush(filter, 1); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - } else if (entry.flags & PHAR_ENT_COMPRESSED_BZ2) { - php_stream_filter *filter; - filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp) TSRMLS_CC); - if (!filter) { - PHAR_ZIP_FAIL; - } - php_stream_filter_append(&fp->readfilters, filter); - php_stream_filter_append(&fp->readfilters, filter); - if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &(mydata->alias), entry.uncompressed_filesize, 0)) || !mydata->alias) { - PHAR_ZIP_FAIL; - } - php_stream_filter_flush(filter, 1); - php_stream_filter_remove(filter, 1 TSRMLS_CC); - } - - mydata->is_explicit_alias = 1; - mydata->alias_len = zipentry.uncompsize; - zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), mydata->alias, mydata->alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); - } - zend_hash_add(&mydata->manifest, entry.filename, entry.filename_len, (void *)&entry,sizeof(phar_entry_info), NULL); - } - zend_hash_add(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); - if (pphar) { - *pphar = mydata; - } - return SUCCESS; -} diff --git a/ext/phar/pharzip.h b/ext/phar/pharzip.h index 066c167cbf..e74b5ba47f 100644 --- a/ext/phar/pharzip.h +++ b/ext/phar/pharzip.h @@ -19,8 +19,14 @@ /* $Id$ */ +#ifdef PHP_WIN32 +#pragma pack(1) +# define PHAR_ZIP_PACK +#else +# define PHAR_ZIP_PACK __attribute__((__packed__)) +#endif typedef struct _phar_zip_file_header { - char magic[4]; /* local file header signature 4 bytes (0x04034b50) */ + char signature[4]; /* local file header signature 4 bytes (0x04034b50) */ char zipversion[2]; /* version needed to extract 2 bytes */ php_uint16 flags; /* general purpose bit flag 2 bytes */ php_uint16 compressed; /* compression method 2 bytes */ @@ -33,13 +39,13 @@ typedef struct _phar_zip_file_header { php_uint16 extra_len; /* extra field length 2 bytes */ /* file name (variable size) */ /* extra field (variable size) */ -} phar_zip_file_header; +} PHAR_ZIP_PACK phar_zip_file_header; typedef struct _phar_zip_file_datadesc { php_uint32 crc32; /* crc-32 4 bytes */ php_uint32 compsize; /* compressed size 4 bytes */ php_uint32 uncompsize; /* uncompressed size 4 bytes */ -} phar_zip_data_desc; +} PHAR_ZIP_PACK phar_zip_data_desc; typedef struct _phar_zip_file_datadesc_zip64 { php_uint32 crc32; /* crc-32 4 bytes */ @@ -47,13 +53,13 @@ typedef struct _phar_zip_file_datadesc_zip64 { php_uint32 compsize2; php_uint32 uncompsize; /* uncompressed size 8 bytes */ php_uint32 uncompsize2; -} phar_zip_data_desc_zip64; +} PHAR_ZIP_PACK phar_zip_data_desc_zip64; typedef struct _phar_zip_archive_extra_data_record { char signature[4]; /* archive extra data signature 4 bytes (0x08064b50) */ php_uint32 len; /* extra field length 4 bytes */ /* extra field data (variable size) */ -} phar_zip_archive_extra_data_record; +} PHAR_ZIP_PACK phar_zip_archive_extra_data_record; /* madeby/extractneeded value if bzip2 compression is used */ #define PHAR_ZIP_BZIP2 "46" @@ -132,18 +138,18 @@ typedef struct _phar_zip_archive_extra_data_record { typedef struct _phar_zip_extra_field_header { char tag[2]; php_uint16 size; -} phar_zip_extra_field_header; +} PHAR_ZIP_PACK phar_zip_extra_field_header; typedef struct _phar_zip_unix3 { char tag[2]; /* 0x756e Short tag for this extra block type ("nu") */ php_uint16 size; /* TSize Short total data size for this block */ php_uint32 crc32; /* CRC Long CRC-32 of the remaining data */ php_uint16 perms; /* Mode Short file permissions */ - php_uint32 smylinksize; /* SizDev Long symlink'd size OR major/minor dev num */ + php_uint32 symlinksize; /* SizDev Long symlink'd size OR major/minor dev num */ php_uint16 uid; /* UID Short user ID */ php_uint16 gid; /* GID Short group ID */ /* (var.) variable symbolic link filename */ -} phar_zip_unix3; +} PHAR_ZIP_PACK phar_zip_unix3; typedef struct _phar_zip_central_dir_file { char signature[4]; /* central file header signature 4 bytes (0x02014b50) */ @@ -167,12 +173,12 @@ typedef struct _phar_zip_central_dir_file { /* file name (variable size) */ /* extra field (variable size) */ /* file comment (variable size) */ -} phar_zip_central_dir_file; +} PHAR_ZIP_PACK phar_zip_central_dir_file; typedef struct _phar_zip_dir_signature { char signature[4]; /* header signature 4 bytes (0x05054b50) */ php_uint16 size; /* size of data 2 bytes */ -} phar_zip_dir_signature; +} PHAR_ZIP_PACK phar_zip_dir_signature; typedef struct _phar_zip64_dir_end { char signature[4]; /* zip64 end of central dir @@ -198,7 +204,7 @@ typedef struct _phar_zip64_dir_end { the starting disk number 8 bytes */ php_uint32 offset2; /* zip64 extensible data sector (variable size) */ -} phar_zip64_dir_end; +} PHAR_ZIP_PACK phar_zip64_dir_end; typedef struct _phar_zip64_dir_locator { char signature[4]; /* zip64 end of central dir locator @@ -210,7 +216,7 @@ typedef struct _phar_zip64_dir_locator { end of central directory record 8 bytes */ php_uint32 diroffset2; php_uint32 totaldisks; /* total number of disks 4 bytes */ -} phar_zip64_dir_locator; +} PHAR_ZIP_PACK phar_zip64_dir_locator; typedef struct _phar_zip_dir_end { char signature[4]; /* end of central dir signature 4 bytes (0x06054b50) */ @@ -227,11 +233,10 @@ typedef struct _phar_zip_dir_end { the starting disk number 4 bytes */ php_uint16 comment_len; /* .ZIP file comment length 2 bytes */ /* .ZIP file comment (variable size) */ -} phar_zip_dir_end; - -BEGIN_EXTERN_C() -int phar_zip_parse(char *fname, int fname_len, char **error TSRMLS_DC); -END_EXTERN_C() +} PHAR_ZIP_PACK phar_zip_dir_end; +#ifdef PHP_WIN32 +#pragma pack() +#endif /* * Local variables: * tab-width: 4 diff --git a/ext/phar/stream.c b/ext/phar/stream.c index cf24b5045d..61ec675b79 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -241,8 +241,9 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, char *pat if (error) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, error); efree(error); + } else { + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host); } - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host); efree(internal_file); php_url_free(resource); return NULL; @@ -260,56 +261,13 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, char *pat fprintf(stderr, "Cached: %s\n", idata->internal_file->filedata ? "yes" : "no"); #endif - /* do we have the data already? */ - if (idata->fp) { - fpf = php_stream_alloc(&phar_ops, idata, NULL, mode); - efree(internal_file); - return fpf; - } - -#if PHP_MAJOR_VERSION < 6 - if (PG(safe_mode) && (!php_checkuid(idata->phar->fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { - phar_entry_delref(idata TSRMLS_CC); - efree(internal_file); - return NULL; - } -#endif - - if (php_check_open_basedir(idata->phar->fname TSRMLS_CC)) { - phar_entry_delref(idata TSRMLS_CC); - efree(internal_file); - return NULL; - } - - fp = idata->phar->fp; - - if (!idata->phar->is_zip && !fp) { - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: cannot open phar \"%s\"", idata->phar->fname); - phar_entry_delref(idata TSRMLS_CC); - efree(internal_file); - return NULL; - } - - if (!phar_open_jit(idata->phar, idata->internal_file, fp, &error, idata->for_write TSRMLS_CC)) { - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, error); - efree(error); - phar_entry_delref(idata TSRMLS_CC); - efree(internal_file); - return NULL; - } - idata->fp = idata->internal_file->fp; - if (idata->fp == idata->phar->fp) { - idata->zero = idata->internal_file->offset_within_phar + idata->phar->internal_file_start; - } - /* check length, crc32 */ - if (!idata->internal_file->is_crc_checked && phar_postprocess_file(wrapper, options, idata, idata->internal_file->crc32 TSRMLS_CC) != SUCCESS) { + if (!idata->internal_file->is_crc_checked && phar_postprocess_file(wrapper, options, idata, idata->internal_file->crc32, &error TSRMLS_CC) != SUCCESS) { /* already issued the error */ phar_entry_delref(idata TSRMLS_CC); efree(internal_file); return NULL; } - idata->internal_file->is_crc_checked = 1; fpf = php_stream_alloc(&phar_ops, idata, NULL, mode); efree(internal_file); @@ -344,18 +302,9 @@ static size_t phar_stream_read(php_stream *stream, char *buf, size_t count TSRML /* use our proxy position */ php_stream_seek(data->fp, data->position + data->zero, SEEK_SET); - if (!data->zero) { - got = php_stream_read(data->fp, buf, count); - if (data->fp->eof) { - stream->eof = 1; - } - /* note the position, and restore the stream for the next fp */ - data->position = php_stream_tell(data->fp); - } else { - got = php_stream_read(data->fp, buf, MIN(count, data->internal_file->uncompressed_filesize - data->position)); - data->position = php_stream_tell(data->fp) - data->zero; - stream->eof = (data->position == (off_t) data->internal_file->uncompressed_filesize); - } + got = php_stream_read(data->fp, buf, MIN(count, data->internal_file->uncompressed_filesize - data->position)); + data->position = php_stream_tell(data->fp) - data->zero; + stream->eof = (data->position == (off_t) data->internal_file->uncompressed_filesize); return got; @@ -370,39 +319,28 @@ static int phar_stream_seek(php_stream *stream, off_t offset, int whence, off_t phar_entry_data *data = (phar_entry_data *)stream->abstract; int res; - if (data->zero) { - off_t temp; - switch (whence) { - case SEEK_END : - temp = data->zero + data->internal_file->uncompressed_filesize + offset; - break; - case SEEK_CUR : - temp = data->zero + data->position + offset; - break; - case SEEK_SET : - temp = data->zero + offset; - break; - } - if (temp > data->zero + (off_t) data->internal_file->uncompressed_filesize) { - *newoffset = -1; - return -1; - } - if (temp < data->zero) { - *newoffset = -1; - return -1; - } - res = php_stream_seek(data->fp, temp, SEEK_SET); - *newoffset = php_stream_tell(data->fp) - data->zero; - data->position = *newoffset; - return res; - } - if (whence != SEEK_SET) { - /* use our proxy position, so the relative stuff works */ - php_stream_seek(data->fp, data->position, SEEK_SET); - } - /* now do the actual seek */ - res = php_stream_seek(data->fp, offset, whence); - *newoffset = php_stream_tell(data->fp); + off_t temp; + switch (whence) { + case SEEK_END : + temp = data->zero + data->internal_file->uncompressed_filesize + offset; + break; + case SEEK_CUR : + temp = data->zero + data->position + offset; + break; + case SEEK_SET : + temp = data->zero + offset; + break; + } + if (temp > data->zero + (off_t) data->internal_file->uncompressed_filesize) { + *newoffset = -1; + return -1; + } + if (temp < data->zero) { + *newoffset = -1; + return -1; + } + res = php_stream_seek(data->fp, temp, SEEK_SET); + *newoffset = php_stream_tell(data->fp) - data->zero; data->position = *newoffset; return res; } @@ -817,7 +755,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char } if (SUCCESS == zend_hash_find(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1, (void **)&entry)) { - phar_entry_info new; + phar_entry_info new, *source; /* perform rename magic */ if (entry->is_deleted) { @@ -833,23 +771,21 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char entry->fp = NULL; entry->metadata = 0; entry->link = NULL; -#if HAVE_PHAR_ZIP - entry->zip = NULL; -#endif + source = entry; + /* add to the manifest, and then store the pointer to the new guy in entry */ zend_hash_add(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info), (void **) &entry); - if (!entry->is_modified) { - /* copy file contents into a new temp stream */ - if (!phar_open_jit(phar, entry, phar->fp, &error, 1 TSRMLS_CC)) { - php_url_free(resource_from); - php_url_free(resource_to); - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); - efree(error); - return 0; - } + + entry->filename = estrdup(resource_to->path+1); + if (FAILURE == phar_copy_entry_fp(source, entry, &error TSRMLS_CC)) { + php_url_free(resource_from); + php_url_free(resource_to); + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); + efree(error); + zend_hash_del(&(phar->manifest), entry->filename, strlen(entry->filename)); + return 0; } entry->is_modified = 1; - entry->filename = estrdup(resource_to->path+1); entry->filename_len = strlen(entry->filename); phar_flush(phar, 0, 0, &error TSRMLS_CC); if (error) { @@ -857,6 +793,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char php_url_free(resource_to); php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); efree(error); + zend_hash_del(&(phar->manifest), entry->filename, strlen(entry->filename)); return 0; } } diff --git a/ext/phar/stream.h b/ext/phar/stream.h index 7eb39fa3bf..f273acb0aa 100644 --- a/ext/phar/stream.h +++ b/ext/phar/stream.h @@ -20,7 +20,7 @@ /* $Id$ */ BEGIN_EXTERN_C() -int phar_postprocess_file(php_stream_wrapper *wrapper, int options, phar_entry_data *idata, php_uint32 crc32 TSRMLS_DC); +int phar_postprocess_file(php_stream_wrapper *wrapper, int options, phar_entry_data *idata, php_uint32 crc32, char **error TSRMLS_DC); php_url* phar_open_url(php_stream_wrapper *wrapper, char *filename, char *mode, int options TSRMLS_DC); void phar_entry_remove(phar_entry_data *idata, char **error TSRMLS_DC); diff --git a/ext/phar/tar.c b/ext/phar/tar.c index e1b371bc9a..8c395761e1 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -235,7 +235,8 @@ int phar_open_tarfile(php_stream* fp, char *fname, int fname_len, char *alias, i } entry.tar_type = ((old & (hdr->typeflag == 0))?'0':hdr->typeflag); - entry.offset_within_phar = pos; + entry.offset = entry.offset_abs = pos; /* header_offset unused in tar */ + entry.fp_type = PHAR_FP; entry.flags = phar_tar_number(hdr->mode, sizeof(hdr->mode)) & PHAR_ENT_PERM_MASK; entry.timestamp = phar_tar_number(hdr->mtime, sizeof(hdr->mtime)); @@ -337,7 +338,6 @@ int phar_tar_writeheaders(void *pDest, void *argument TSRMLS_DC) size_t pos; phar_entry_info *entry = (phar_entry_info *) pDest; struct _phar_pass_tar_info *fp = (struct _phar_pass_tar_info *)argument; - php_stream *file; char padding[512]; if (entry->is_deleted) { @@ -387,6 +387,7 @@ int phar_tar_writeheaders(void *pDest, void *argument TSRMLS_DC) } /* write header */ + entry->header_offset = php_stream_tell(fp->new); if (sizeof(header) != php_stream_write(fp->new, (char *) &header, sizeof(header))) { if (fp->error) { spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, header for file \"%s\" could not be written", entry->phar->fname, entry->filename); @@ -396,19 +397,16 @@ int phar_tar_writeheaders(void *pDest, void *argument TSRMLS_DC) pos = php_stream_tell(fp->new); /* save start of file within tar */ /* write contents */ - if (entry->fp) { - /* new file */ - file = entry->fp; - if (file == entry->phar->fp) { - php_stream_seek(file, entry->offset_within_phar, SEEK_SET); - } else { - php_stream_seek(file, 0, SEEK_SET); + if (FAILURE == phar_open_entry_fp(entry, fp->error TSRMLS_CC)) { + return ZEND_HASH_APPLY_STOP; + } + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + if (fp->error) { + spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, contents of file \"%s\" could not be written, seek failed", entry->phar->fname, entry->filename); } - } else { - file = fp->old; - php_stream_seek(file, entry->offset_within_phar, SEEK_SET); + return ZEND_HASH_APPLY_STOP; } - if (entry->uncompressed_filesize != php_stream_copy_to_stream(file, fp->new, entry->uncompressed_filesize)) { + if (entry->uncompressed_filesize != php_stream_copy_to_stream(phar_get_efp(entry), fp->new, entry->uncompressed_filesize)) { if (fp->error) { spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, contents of file \"%s\" could not be written", entry->phar->fname, entry->filename); } @@ -419,15 +417,14 @@ int phar_tar_writeheaders(void *pDest, void *argument TSRMLS_DC) php_stream_write(fp->new, padding, ((entry->uncompressed_filesize +511)&~511) - entry->uncompressed_filesize); entry->is_modified = 0; - if (entry->fp && entry->fp_refcount == 0) { - if (entry->fp != entry->phar->fp) { - php_stream_close(entry->fp); - } + if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp) { + php_stream_close(entry->fp); entry->fp = NULL; } + entry->fp_type = PHAR_FP; /* note new location within tar */ - entry->offset_within_phar = pos; + entry->offset = entry->offset_abs = pos; return ZEND_HASH_APPLY_KEEP; } @@ -447,6 +444,7 @@ int phar_tar_flush(phar_archive_data *phar, char *user_stub, long len, char **er entry.is_tar = 1; entry.tar_type = '0'; entry.phar = phar; + entry.fp_type = PHAR_MOD; /* set alias */ if (phar->is_explicit_alias) { entry.filename = estrndup(".phar/alias.txt", sizeof(".phar/alias.txt")-1); @@ -574,6 +572,10 @@ int phar_tar_flush(phar_archive_data *phar, char *user_stub, long len, char **er if (phar->fp) { php_stream_close(phar->fp); } + if (phar->ufp) { + php_stream_close(phar->ufp); + phar->ufp = NULL; + } phar->is_brandnew = 0; diff --git a/ext/phar/tests/033.phpt b/ext/phar/tests/033.phpt index 9620fdaaa3..c57e4dbead 100644 --- a/ext/phar/tests/033.phpt +++ b/ext/phar/tests/033.phpt @@ -35,7 +35,7 @@ var_dump($a['dir']->isReadable()); ---EXPECTF-- +--EXPECT-- bool(false) bool(true) bool(false) diff --git a/ext/phar/tests/phar_setalias2.phpt b/ext/phar/tests/phar_setalias2.phpt index 631a8e016c..3cca420203 100644 --- a/ext/phar/tests/phar_setalias2.phpt +++ b/ext/phar/tests/phar_setalias2.phpt @@ -34,6 +34,7 @@ try { --EXPECTF-- diff --git a/ext/phar/tests/zip/033.phpt b/ext/phar/tests/zip/033.phpt index 3d0e584e64..b2855368bd 100644 --- a/ext/phar/tests/zip/033.phpt +++ b/ext/phar/tests/zip/033.phpt @@ -42,7 +42,13 @@ try { ---EXPECTF-- +--EXPECT-- bool(false) -Cannot modify permissions for file "a.php" in phar "%s033.1.phar.php", not supported for zip-based phars +bool(true) +bool(false) +test dir +bool(true) +bool(true) +bool(false) +bool(true) ===DONE=== diff --git a/ext/phar/tests/zip/phar_commitwrite.phpt b/ext/phar/tests/zip/phar_commitwrite.phpt index 9d560e4383..9a371eaac5 100644 --- a/ext/phar/tests/zip/phar_commitwrite.phpt +++ b/ext/phar/tests/zip/phar_commitwrite.phpt @@ -1,8 +1,7 @@ --TEST-- -Phar::setStub()/stopBuffering() tar-based +Phar::setStub()/stopBuffering() zip-based --SKIPIF-- - --INI-- phar.require_hash=0 phar.readonly=0 diff --git a/ext/phar/tests/zip/phar_convert_phar.phpt b/ext/phar/tests/zip/phar_convert_phar.phpt index f0dede60c9..337682e7d8 100644 --- a/ext/phar/tests/zip/phar_convert_phar.phpt +++ b/ext/phar/tests/zip/phar_convert_phar.phpt @@ -2,7 +2,6 @@ Phar::convertToPhar() from zip --SKIPIF-- - --INI-- phar.require_hash=0 phar.readonly=0 diff --git a/ext/phar/tests/zip/phar_copy.phpt b/ext/phar/tests/zip/phar_copy.phpt index 3053c8bbe2..199abc8305 100644 --- a/ext/phar/tests/zip/phar_copy.phpt +++ b/ext/phar/tests/zip/phar_copy.phpt @@ -3,7 +3,6 @@ Phar: copy() zip-based --SKIPIF-- - --INI-- phar.readonly=0 phar.require_hash=1 diff --git a/ext/phar/util.c b/ext/phar/util.c new file mode 100644 index 0000000000..4205ae23ab --- /dev/null +++ b/ext/phar/util.c @@ -0,0 +1,724 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + | utility functions | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2008 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gregory Beaver | + | Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "phar_internal.h" + +/* retrieve a phar_entry_info's current file pointer for reading contents */ +php_stream *phar_get_efp(phar_entry_info *entry) +{ + if (entry->fp_type == PHAR_FP) { + return entry->phar->fp; + } else if (entry->fp_type == PHAR_UFP) { + return entry->phar->ufp; + } else { + return entry->fp; + } +} + +int phar_seek_efp(phar_entry_info *entry, off_t offset, int whence, off_t position TSRMLS_DC) +{ + php_stream *fp = phar_get_efp(entry); + off_t temp; + + switch (whence) { + case SEEK_END : + temp = entry->offset + entry->uncompressed_filesize + offset; + break; + case SEEK_CUR : + temp = entry->offset + position + offset; + break; + case SEEK_SET : + temp = entry->offset + offset; + break; + } + if (temp > entry->offset + (off_t) entry->uncompressed_filesize) { + return -1; + } + if (temp < entry->offset) { + return -1; + } + return php_stream_seek(fp, temp, SEEK_SET); +} + +/** + * Retrieve a copy of the file information on a single file within a phar, or null. + * This also transfers the open file pointer, if any, to the entry. + * + * If the file does not already exist, this will fail. Pre-existing files can be + * appended, truncated, or read. For read, if the entry is marked unmodified, it is + * assumed that the file pointer, if present, is opened for reading + */ +int phar_get_entry_data(phar_entry_data **ret, char *fname, int fname_len, char *path, int path_len, char *mode, char allow_dir, char **error TSRMLS_DC) /* {{{ */ +{ + phar_archive_data *phar; + phar_entry_info *entry; + int for_write = mode[0] != 'r' || mode[1] == '+'; + int for_append = mode[0] == 'a'; + int for_create = mode[0] != 'r'; + int for_trunc = mode[0] == 'w'; + + if (!ret) { + return FAILURE; + } + *ret = NULL; + if (error) { + *error = NULL; + } + if (for_write && PHAR_G(readonly)) { + if (error) { + spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for writing, disabled by ini setting", path, fname); + } + return FAILURE; + } + if (FAILURE == phar_get_archive(&phar, fname, fname_len, NULL, 0, error TSRMLS_CC)) { + return FAILURE; + } + if (!path_len) { + if (error) { + spprintf(error, 4096, "phar error: file \"\" in phar \"%s\" cannot be empty", fname); + } + return FAILURE; + } + if (allow_dir) { + if ((entry = phar_get_entry_info_dir(phar, path, path_len, 2, for_create && !PHAR_G(readonly) ? NULL : error TSRMLS_CC)) == NULL) { + if (for_create && !PHAR_G(readonly)) { + return SUCCESS; + } + return FAILURE; + } + } else { + if ((entry = phar_get_entry_info(phar, path, path_len, for_create && !PHAR_G(readonly) ? NULL : error TSRMLS_CC)) == NULL) { + if (for_create && !PHAR_G(readonly)) { + return SUCCESS; + } + return FAILURE; + } + } + if (entry->is_modified && !for_write) { + if (error) { + spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for reading, writable file pointers are open", path, fname); + } + return FAILURE; + } + if (entry->fp_refcount && for_write) { + if (error) { + spprintf(error, 4096, "phar error: file \"%s\" in phar \"%s\" cannot be opened for writing, readable file pointers are open", path, fname); + } + return FAILURE; + } + if (entry->is_deleted) { + if (!for_create) { + return FAILURE; + } + entry->is_deleted = 0; + } + if (entry->is_dir) { + *ret = (phar_entry_data *) emalloc(sizeof(phar_entry_data)); + (*ret)->position = 0; + (*ret)->phar = phar; + (*ret)->for_write = for_write; + (*ret)->internal_file = entry; + (*ret)->is_zip = entry->is_zip; + (*ret)->is_tar = entry->is_tar; + entry->phar->refcount++; + entry->fp_refcount++; + return SUCCESS; + } + if (entry->fp_type == PHAR_MOD) { + if (for_trunc) { + if (FAILURE == phar_create_writeable_entry(phar, entry, error TSRMLS_CC)) { + return FAILURE; + } + } else if (for_append) { + phar_seek_efp(entry, 0, SEEK_END, 0 TSRMLS_CC); + } + } else { + if (for_write) { + if (for_trunc) { + if (FAILURE == phar_create_writeable_entry(phar, entry, error TSRMLS_CC)) { + return FAILURE; + } + } else { + if (FAILURE == phar_separate_entry_fp(entry, error TSRMLS_CC)) { + return FAILURE; + } + } + } else { + if (FAILURE == phar_open_entry_fp(entry, error TSRMLS_CC)) { + return FAILURE; + } + } + } + *ret = (phar_entry_data *) emalloc(sizeof(phar_entry_data)); + (*ret)->position = 0; + (*ret)->phar = phar; + (*ret)->for_write = for_write; + (*ret)->internal_file = entry; + (*ret)->is_zip = entry->is_zip; + (*ret)->is_tar = entry->is_tar; + (*ret)->fp = phar_get_efp(entry); + (*ret)->zero = entry->offset; + entry->fp_refcount++; + entry->phar->refcount++; + return SUCCESS; +} +/* }}} */ + +/** + * Create a new dummy file slot within a writeable phar for a newly created file + */ +phar_entry_data *phar_get_or_create_entry_data(char *fname, int fname_len, char *path, int path_len, char *mode, char allow_dir, char **error TSRMLS_DC) /* {{{ */ +{ + phar_archive_data *phar; + phar_entry_info *entry, etemp; + phar_entry_data *ret; + const char *pcr_error; + char is_dir = path[path_len - 1] == '/'; + + if (FAILURE == phar_get_archive(&phar, fname, fname_len, NULL, 0, error TSRMLS_CC)) { + return NULL; + } + + if (FAILURE == phar_get_entry_data(&ret, fname, fname_len, path, path_len, mode, allow_dir, error TSRMLS_CC)) { + return NULL; + } else if (ret) { + return ret; + } + + if (phar_path_check(&path, &path_len, &pcr_error) > pcr_is_ok) { + if (error) { + spprintf(error, 0, "phar error: invalid path \"%s\" contains %s", path, pcr_error); + } + return NULL; + } + + /* create a new phar data holder */ + ret = (phar_entry_data *) emalloc(sizeof(phar_entry_data)); + + /* create an entry, this is a new file */ + memset(&etemp, 0, sizeof(phar_entry_info)); + etemp.filename_len = path_len; + etemp.fp_type = PHAR_MOD; + etemp.fp = php_stream_fopen_tmpfile(); + if (!etemp.fp) { + if (error) { + spprintf(error, 0, "phar error: unable to create temporary file"); + } + return NULL; + } + etemp.fp_refcount = 1; + + if (is_dir) { + etemp.is_dir = 1; + etemp.flags = etemp.old_flags = PHAR_ENT_PERM_DEF_DIR; + etemp.filename_len--; /* strip trailing / */ + path_len--; + } else { + etemp.flags = etemp.old_flags = PHAR_ENT_PERM_DEF_FILE; + } + etemp.is_modified = 1; + etemp.timestamp = time(0); + etemp.is_crc_checked = 1; + etemp.phar = phar; + etemp.filename = estrndup(path, path_len); + etemp.is_zip = phar->is_zip; + if (phar->is_tar) { + etemp.is_tar = phar->is_tar; + etemp.tar_type = TAR_FILE; + } + zend_hash_add(&phar->manifest, etemp.filename, path_len, (void*)&etemp, sizeof(phar_entry_info), (void **) &entry); + + if (!entry) { + php_stream_close(etemp.fp); + efree(etemp.filename); + return NULL; + } + + phar->refcount++; + ret->phar = phar; + ret->fp = entry->fp; + ret->position = ret->zero = 0; + ret->for_write = 1; + ret->is_zip = entry->is_zip; + ret->is_tar = entry->is_tar; + ret->internal_file = entry; + return ret; +} +/* }}} */ + +/* initialize a phar_archive_data's read-only fp for existing phar data */ +int phar_open_archive_fp(phar_archive_data *phar TSRMLS_DC) +{ + if (phar->fp) { + return SUCCESS; + } + +#if PHP_MAJOR_VERSION < 6 + if (PG(safe_mode) && (!php_checkuid(phar->fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + return FAILURE; + } +#endif + + if (php_check_open_basedir(phar->fname TSRMLS_CC)) { + return FAILURE; + } + + phar->fp = php_stream_open_wrapper(phar->fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, NULL); + if (!phar->fp) { + return FAILURE; + } + return SUCCESS; +} + +/* copy file data from an existing to a new phar_entry_info that is not in the manifest */ +int phar_copy_entry_fp(phar_entry_info *source, phar_entry_info *dest, char **error TSRMLS_DC) +{ + if (FAILURE == phar_open_entry_fp(source, error TSRMLS_CC)) { + return FAILURE; + } + dest->fp_type = PHAR_MOD; + dest->offset = 0; + dest->is_modified = 1; + dest->fp = php_stream_fopen_tmpfile(); + + phar_seek_efp(source, 0, SEEK_SET, 0 TSRMLS_CC); + if (source->uncompressed_filesize != php_stream_copy_to_stream(phar_get_efp(source), dest->fp, source->uncompressed_filesize)) { + php_stream_close(dest->fp); + dest->fp_type = PHAR_FP; + if (error) { + spprintf(error, 4096, "phar error: unable to copy contents of file \"%s\" to \"%s\" in phar archive \"%s\"", source->filename, dest->filename, source->phar->fname); + } + return FAILURE; + } + return SUCCESS; +} + +/* open and decompress a compressed phar entry + */ +int phar_open_entry_fp(phar_entry_info *entry, char **error TSRMLS_DC) +{ + php_stream_filter *filter; + phar_archive_data *phar = entry->phar; + char *filtername; + off_t loc; + + if (entry->fp_type != PHAR_FP) { + /* either newly created or already modified */ + return SUCCESS; + } + if (!phar->fp) { + if (FAILURE == phar_open_archive_fp(phar TSRMLS_CC)) { + spprintf(error, 4096, "phar error: Cannot open phar archive \"%s\" for reading", phar->fname); + return FAILURE; + } + } + if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) { + return SUCCESS; + } + if (!phar->ufp) { + phar->ufp = php_stream_fopen_tmpfile(); + if (!phar->ufp) { + spprintf(error, 4096, "phar error: Cannot open temporary file for decompressing phar archive \"%s\" file \"%s\"", phar->fname, entry->filename); + return FAILURE; + } + } + + if ((filtername = phar_decompress_filter(entry, 0)) != NULL) { + filter = php_stream_filter_create(filtername, NULL, php_stream_is_persistent(phar->ufp) TSRMLS_CC); + } else { + filter = NULL; + } + if (!filter) { + spprintf(error, 4096, "phar error: unable to read phar \"%s\" (cannot create %s filter while decompressing file \"%s\")", phar->fname, phar_decompress_filter(entry, 1), entry->filename); + return FAILURE; + } + /* now we can safely use proper decompression */ + /* save the new offset location within ufp */ + php_stream_seek(phar->ufp, 0, SEEK_END); + loc = php_stream_tell(phar->ufp); + php_stream_filter_append(&phar->ufp->writefilters, filter); + php_stream_seek(phar->fp, entry->offset, SEEK_SET); + if (php_stream_copy_to_stream(phar->fp, phar->ufp, entry->compressed_filesize) != entry->compressed_filesize) { + spprintf(error, 4096, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename); + php_stream_filter_remove(filter, 1 TSRMLS_CC); + return FAILURE; + } + php_stream_filter_flush(filter, 1); + php_stream_flush(phar->ufp); + php_stream_filter_remove(filter, 1 TSRMLS_CC); + if (php_stream_tell(phar->ufp) - loc != (off_t) entry->uncompressed_filesize) { + spprintf(error, 4096, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename); + return FAILURE; + } + + entry->old_flags = entry->flags; + entry->fp_type = PHAR_UFP; + /* this is now the new location of the file contents within this fp */ + entry->offset = loc; + + return SUCCESS; +} + +#if defined(PHP_VERSION_ID) && PHP_VERSION_ID < 50202 +typedef struct { + char *data; + size_t fpos; + size_t fsize; + size_t smax; + int mode; + php_stream **owner_ptr; +} php_stream_memory_data; +#endif + +int phar_create_writeable_entry(phar_archive_data *phar, phar_entry_info *entry, char **error TSRMLS_DC) /* {{{ */ +{ + if (entry->fp_type == PHAR_MOD) { + /* already newly created, truncate */ +#if PHP_VERSION_ID >= 50202 + php_stream_truncate_set_size(entry->fp, 0); +#else + if (php_stream_is(entry->fp, PHP_STREAM_IS_TEMP)) { + if (php_stream_is(*(php_stream**)entry->fp->abstract, PHP_STREAM_IS_MEMORY)) { + php_stream *inner = *(php_stream**)entry->fp->abstract; + php_stream_memory_data *memfp = (php_stream_memory_data*)inner->abstract; + memfp->fpos = 0; + memfp->fsize = 0; + } else if (php_stream_is(*(php_stream**)entry->fp->abstract, PHP_STREAM_IS_STDIO)) { + php_stream_truncate_set_size(*(php_stream**)entry->fp->abstract, 0); + } else { + efree(*ret); + *ret = NULL; + if (error) { + spprintf(error, 0, "phar error: file \"%s\" cannot be opened for writing, no truncate support", fname); + } + return FAILURE; + } + } else if (php_stream_is(entry->fp, PHP_STREAM_IS_STDIO)) { + php_stream_truncate_set_size(entry->fp, 0); + } else { + efree(*ret); + *ret = NULL; + if (error) { + spprintf(error, 0, "phar error: file \"%s\" cannot be opened for writing, no truncate support", fname); + } + return FAILURE; + } +#endif + entry->old_flags = entry->flags; + entry->is_modified = 1; + phar->is_modified = 1; + /* reset file size */ + entry->uncompressed_filesize = 0; + entry->compressed_filesize = 0; + entry->crc32 = 0; + entry->flags = PHAR_ENT_PERM_DEF_FILE; + entry->fp_type = PHAR_MOD; + entry->offset = 0; + return SUCCESS; + } + if (error) { + *error = NULL; + } + /* open a new temp file for writing */ + entry->fp = php_stream_fopen_tmpfile(); + if (!entry->fp) { + if (error) { + spprintf(error, 0, "phar error: unable to create temporary file"); + } + return FAILURE; + } + entry->old_flags = entry->flags; + entry->is_modified = 1; + phar->is_modified = 1; + /* reset file size */ + entry->uncompressed_filesize = 0; + entry->compressed_filesize = 0; + entry->crc32 = 0; + entry->flags = PHAR_ENT_PERM_DEF_FILE; + entry->fp_type = PHAR_MOD; + entry->offset = 0; + return SUCCESS; +} +/* }}} */ + +int phar_separate_entry_fp(phar_entry_info *entry, char **error TSRMLS_DC) +{ + php_stream *fp; + + if (FAILURE == phar_open_entry_fp(entry, error TSRMLS_CC)) { + return FAILURE; + } + + if (entry->fp_type == PHAR_MOD) { + return SUCCESS; + } + + fp = php_stream_fopen_tmpfile(); + phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC); + if (entry->uncompressed_filesize != php_stream_copy_to_stream(phar_get_efp(entry), fp, entry->uncompressed_filesize)) { + if (error) { + spprintf(error, 4096, "phar error: cannot separate entry file \"%s\" contents in phar archive \"%s\" for write access", entry->filename, entry->phar->fname); + } + return FAILURE; + } + + entry->offset = 0; + entry->fp = fp; + entry->fp_type = PHAR_MOD; + entry->is_modified = 1; + return SUCCESS; +} + +/** + * helper function to open an internal file's fp just-in-time + */ +phar_entry_info * phar_open_jit(phar_archive_data *phar, phar_entry_info *entry, php_stream *fp, + char **error, int for_write TSRMLS_DC) +{ + if (error) { + *error = NULL; + } + /* seek to start of internal file and read it */ + if (FAILURE == phar_open_entry_fp(entry, error TSRMLS_CC)) { + return NULL; + } + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + spprintf(error, 4096, "phar error: cannot seek to start of file \"%s\" in phar \"%s\"", entry->filename, phar->fname); + return NULL; + } + return entry; +} + +/** + * Looks up a phar archive in the filename map, connecting it to the alias + * (if any) or returns null + */ +int phar_get_archive(phar_archive_data **archive, char *fname, int fname_len, char *alias, int alias_len, char **error TSRMLS_DC) /* {{{ */ +{ + phar_archive_data *fd, **fd_ptr; + char *my_realpath, *save; + int save_len; + + phar_request_initialize(TSRMLS_C); + + if (error) { + *error = NULL; + } + *archive = NULL; + if (alias && alias_len) { + if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void**)&fd_ptr)) { + if (fname && (fname_len != (*fd_ptr)->fname_len || strncmp(fname, (*fd_ptr)->fname, fname_len))) { + if (error) { + spprintf(error, 0, "alias \"%s\" is already used for archive \"%s\" cannot be overloaded with \"%s\"", alias, (*fd_ptr)->fname, fname); + } + return FAILURE; + } + *archive = *fd_ptr; + return SUCCESS; + } + } + my_realpath = NULL; + save = fname; + save_len = fname_len; + if (fname && fname_len) { + if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), fname, fname_len, (void**)&fd_ptr)) { + *archive = *fd_ptr; + fd = *fd_ptr; + if (alias && alias_len) { + zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&fd, sizeof(phar_archive_data*), NULL); + } + return SUCCESS; + } + if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), save, save_len, (void**)&fd_ptr)) { + *archive = *fd_ptr; + return SUCCESS; + } + + /* not found, try converting \ to / */ + my_realpath = expand_filepath(fname, my_realpath TSRMLS_CC); + if (my_realpath) { + fname_len = strlen(my_realpath); + fname = my_realpath; + } else { + return FAILURE; + } +#ifdef PHP_WIN32 + phar_unixify_path_separators(fname, fname_len); +#endif + if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), fname, fname_len, (void**)&fd_ptr)) { + *archive = *fd_ptr; + fd = *fd_ptr; + if (alias && alias_len) { + zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&fd, sizeof(phar_archive_data*), NULL); + } + efree(my_realpath); + return SUCCESS; + } + efree(my_realpath); + } + return FAILURE; +} +/* }}} */ + +/** + * Determine which stream compression filter (if any) we need to read this file + */ +char * phar_compress_filter(phar_entry_info * entry, int return_unknown) /* {{{ */ +{ + switch (entry->flags & PHAR_ENT_COMPRESSION_MASK) { + case PHAR_ENT_COMPRESSED_GZ: + return "zlib.deflate"; + case PHAR_ENT_COMPRESSED_BZ2: + return "bzip2.compress"; + default: + return return_unknown ? "unknown" : NULL; + } +} +/* }}} */ + +/** + * Determine which stream decompression filter (if any) we need to read this file + */ +char * phar_decompress_filter(phar_entry_info * entry, int return_unknown) /* {{{ */ +{ + php_uint32 flags; + + if (entry->is_modified) { + flags = entry->old_flags; + } else { + flags = entry->flags; + } + switch (flags & PHAR_ENT_COMPRESSION_MASK) { + case PHAR_ENT_COMPRESSED_GZ: + return "zlib.inflate"; + case PHAR_ENT_COMPRESSED_BZ2: + return "bzip2.decompress"; + default: + return return_unknown ? "unknown" : NULL; + } +} +/* }}} */ + +/** + * retrieve information on a file contained within a phar, or null if it ain't there + */ +phar_entry_info *phar_get_entry_info(phar_archive_data *phar, char *path, int path_len, char **error TSRMLS_DC) /* {{{ */ +{ + return phar_get_entry_info_dir(phar, path, path_len, 0, error TSRMLS_CC); +} +/* }}} */ +/** + * retrieve information on a file or directory contained within a phar, or null if none found + * allow_dir is 0 for none, 1 for both empty directories in the phar and temp directories, and 2 for only + * valid pre-existing empty directory entries + */ +phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, int path_len, char dir, char **error TSRMLS_DC) /* {{{ */ +{ + const char *pcr_error; + phar_entry_info *entry; + char is_dir = (path[path_len - 1] == '/'); + + if (error) { + *error = NULL; + } + + if (!path_len && !dir) { + if (error) { + spprintf(error, 4096, "phar error: invalid path \"%s\" must not be empty", path); + } + return NULL; + } + if (phar_path_check(&path, &path_len, &pcr_error) > pcr_is_ok) { + if (error) { + spprintf(error, 4096, "phar error: invalid path \"%s\" contains %s", path, pcr_error); + } + return NULL; + } + + if (!&phar->manifest.arBuckets) { + return NULL; + } + if (is_dir) { + path_len--; + } + if (SUCCESS == zend_hash_find(&phar->manifest, path, path_len, (void**)&entry)) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + return NULL; + } + if (entry->is_dir && !dir) { + if (error) { + spprintf(error, 4096, "phar error: path \"%s\" is a directory", path); + } + return NULL; + } + if (!entry->is_dir && is_dir) { + /* user requested a directory, we must return one */ + if (error) { + spprintf(error, 4096, "phar error: path \"%s\" exists and is a not a directory", path); + } + return NULL; + } + return entry; + } + if (dir == 1) { + /* try to find a directory */ + HashTable *manifest; + char *key; + uint keylen; + ulong unused; + + if (!path_len) { + path = "/"; + } + manifest = &phar->manifest; + zend_hash_internal_pointer_reset(manifest); + while (FAILURE != zend_hash_has_more_elements(manifest)) { + if (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key_ex(manifest, &key, &keylen, &unused, 0, NULL)) { + break; + } + if (0 != memcmp(key, path, path_len)) { + /* entry in directory not found */ + if (SUCCESS != zend_hash_move_forward(manifest)) { + break; + } + continue; + } else { + if (key[path_len] != '/') { + if (SUCCESS != zend_hash_move_forward(manifest)) { + break; + } + continue; + } + /* found a file in this path */ + entry = (phar_entry_info *) ecalloc(1, sizeof(phar_entry_info)); + /* this next line tells PharFileInfo->__destruct() to efree the filename */ + entry->is_temp_dir = entry->is_dir = 1; + entry->filename = (char *) estrndup(path, path_len + 1); + entry->filename_len = path_len; + return entry; + } + } + } + return NULL; +} +/* }}} */ diff --git a/ext/phar/zip.c b/ext/phar/zip.c index 0f7881c6ae..b7c35acdcd 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -17,7 +17,6 @@ */ #include "phar_internal.h" -#include "php_stream_unlink.h" #ifdef PHP_WIN32 static inline void phar_unixify_path_separators(char *path, int path_len) /* {{{ */ @@ -34,6 +33,104 @@ static inline void phar_unixify_path_separators(char *path, int path_len) /* {{{ /* }}} */ #endif +static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, php_uint16 len TSRMLS_DC) +{ + union { + phar_zip_extra_field_header header; + phar_zip_unix3 unix3; + } h; + + do { + if (sizeof(h.header) != php_stream_read(fp, (char *) &h.header, sizeof(h.header))) { + return FAILURE; + } + /* clean up header for big-endian systems */ + if (h.header.tag != "nu") { + /* skip to next header */ + php_stream_seek(fp, h.header.size, SEEK_CUR); + len -= h.header.size + 4; + continue; + } + /* unix3 header found */ + /* clean up header for big-endian systems */ + if (sizeof(h.unix3) != php_stream_read(fp, (char *) &h.unix3, sizeof(h.unix3))) { + if (h.unix3.size > sizeof(h.unix3) - 4) { + /* skip symlink filename - we may add this support in later */ + php_stream_seek(fp, h.unix3.size - sizeof(h.unix3.size), SEEK_CUR); + } + /* set permissions */ + entry->flags &= PHAR_ENT_COMPRESSION_MASK; + if (entry->is_dir) { + entry->flags |= h.unix3.perms & PHAR_ENT_PERM_DEF_DIR; + } else { + entry->flags |= h.unix3.perms & PHAR_ENT_PERM_DEF_FILE; + } + } + } while (len); + return SUCCESS; +} + +/* + extracted from libzip + zip_dirent.c -- read directory entry (local or central), clean dirent + Copyright (C) 1999, 2003, 2004, 2005 Dieter Baron and Thomas Klausner + + This function is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +static time_t phar_zip_d2u_time(int dtime, int ddate) +{ + struct tm *tm, tmbuf; + time_t now; + + now = time(NULL); + tm = php_localtime_r(&now, &tmbuf); + + tm->tm_year = ((ddate>>9)&127) + 1980 - 1900; + tm->tm_mon = ((ddate>>5)&15) - 1; + tm->tm_mday = ddate&31; + + tm->tm_hour = (dtime>>11)&31; + tm->tm_min = (dtime>>5)&63; + tm->tm_sec = (dtime<<1)&62; + + return mktime(tm); +} + +static void phar_zip_u2d_time(time_t time, php_uint16 *dtime, php_uint16 *ddate) +{ + struct tm *tm, tmbuf; + + tm = php_localtime_r(&time, &tmbuf); + *ddate = ((tm->tm_year+1900-1980)<<9) + ((tm->tm_mon+1)<<5) + tm->tm_mday; + *dtime = ((tm->tm_hour)<<11) + ((tm->tm_min)<<5) + ((tm->tm_sec)>>1); +} + /** * Does not check for a previously opened phar in the cache. * @@ -43,44 +140,55 @@ static inline void phar_unixify_path_separators(char *path, int path_len) /* {{{ * This is used by phar_open_fp to process a zip-based phar, but can be called * directly. */ -int phar_open_zipfile(char *fname, int fname_len, char *alias, int alias_len, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */ +int phar_open_zipfile(php_stream *fp, char *fname, int fname_len, char *alias, int alias_len, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */ { -#if HAVE_PHAR_ZIP - struct zip *zip; - int ziperror, i, phar_alias_index, register_alias, metadata_len; - phar_entry_info entry = {0}; - struct zip_stat zs; - char tmp_buf[1024], *metadata; + char buf[8192], *metadata; + phar_zip_dir_end locator; + long size; + size_t read; + php_uint16 i; phar_archive_data *mydata = NULL; + phar_entry_info entry = {0}; - if (error) { - *error = NULL; - } - - if (!phar_has_zip) { - spprintf(error, 4096, "phar zip error: cannot open zip-based phar \"%s\", ext/zip is not enabled", fname); - return FAILURE; + size = php_stream_tell(fp); + if (size > sizeof(locator) + 65536) { + /* seek to max comment length + end of central directory record */ + size = sizeof(locator) + 65536; + if (FAILURE == php_stream_seek(fp, -size, SEEK_END)) { + php_stream_close(fp); + return FAILURE; + } + } else { + php_stream_seek(fp, 0, SEEK_SET); } - - zip = zip_open(fname, 0, &ziperror); - if (!zip) { - if (error) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - tmp_len = zip_error_to_str(NULL, 0, ziperror, ziperror); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - spprintf(error, 4096, "phar zip error: cannot open zip-based phar \"%s\"", fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, ziperror, ziperror)) { - efree(tmp); - spprintf(error, 4096, "phar zip error: cannot open zip-based phar \"%s\"", fname); + do { + char *p = buf; + if (!(read = php_stream_read(fp, buf, 8192))) { + php_stream_close(fp); + return FAILURE; + } + while ((p=(char *) memchr(p + 1, 'P', (size_t)(buf - (p+1) + 8192 - 4 + 1))) != NULL) { + if (!memcmp(p + 1, "K\5\6", 3)) { + if (p - buf < sizeof(locator)) { + /* didn't read in the whole thing, back up */ + php_stream_seek(fp, 8192 - (p - buf), SEEK_CUR); + if (sizeof(locator) != php_stream_read(fp, (char *) &locator, sizeof(locator))) { + php_stream_close(fp); + return FAILURE; + } } else { - spprintf(error, 4096, "phar zip error: cannot open zip-based phar \"%s\": %s", fname, tmp); - efree(tmp); + memcpy((void *)&locator, (void *) p, sizeof(locator)); } + goto foundit; } } + } while (read == 8192); + php_stream_close(fp); + return FAILURE; +foundit: + if (locator.centraldisk != 0 || locator.disknumber != 0) { + /* split archives not handled */ + php_stream_close(fp); return FAILURE; } mydata = ecalloc(sizeof(phar_archive_data), 1); @@ -88,139 +196,188 @@ int phar_open_zipfile(char *fname, int fname_len, char *alias, int alias_len, ph #ifdef PHP_WIN32 phar_unixify_path_separators(mydata->fname, fname_len); #endif + mydata->is_zip = 1; mydata->fname_len = fname_len; - if (-1 != (phar_alias_index = zip_name_locate(zip, ".phar/alias.txt", 0))) { - struct zip_file *zf = zip_fopen_index(zip, phar_alias_index, 0); - int tmp_len; - - tmp_len = zip_fread(zf, tmp_buf, 1024); - zip_fclose(zf); - /* if the alias is stored we enforce it (implicit overrides explicit) */ - if (tmp_len != -1 && alias && alias_len && (alias_len != tmp_len || strncmp(alias, tmp_buf, tmp_len))) - { - if (error) { - spprintf(error, 0, "cannot load phar \"%s\" with implicit alias \"%s\" under different alias \"%s\"", fname, tmp_buf, alias); - } + /* clean up on big-endian systems */ + /* read in archive comment, if any */ + if (locator.comment_len) { + metadata = (char *) emalloc(locator.comment_len); + if (locator.comment_len != php_stream_read(fp, metadata, locator.comment_len)) { + php_stream_close(fp); + efree(mydata->fname); + efree(mydata); return FAILURE; } - if (tmp_len != -1) { - /* use implicit alias */ - alias = tmp_buf; - alias_len = tmp_len; - register_alias = 0; - } - } else { - register_alias = alias ? 1 : 0; - } - mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, alias_len); - mydata->alias_len = alias ? alias_len : fname_len; - mydata->is_zip = 1; - mydata->zip = zip; - - phar_request_initialize(TSRMLS_C); - - /* read in phar metadata (zip file comment) */ - metadata = (char *) zip_get_archive_comment(zip, &metadata_len, 0); - if (metadata) { - if (phar_parse_metadata(&metadata, &mydata->metadata, metadata_len TSRMLS_CC) == FAILURE) { + if (phar_parse_metadata(&metadata, &mydata->metadata, locator.comment_len TSRMLS_CC) == FAILURE) { /* if not valid serialized data, it is a regular string */ ALLOC_INIT_ZVAL(mydata->metadata); - Z_STRVAL_P(mydata->metadata) = estrndup(metadata, metadata_len); - Z_STRLEN_P(mydata->metadata) = metadata_len; + Z_STRVAL_P(mydata->metadata) = metadata; + Z_STRLEN_P(mydata->metadata) = locator.comment_len; Z_TYPE_P(mydata->metadata) = IS_STRING; } } else { mydata->metadata = NULL; } - /* set up our manifest */ + /* seek to central directory */ + php_stream_seek(fp, locator.cdir_offset, SEEK_SET); + /* read in central directory */ zend_hash_init(&mydata->manifest, sizeof(phar_entry_info), zend_get_hash_value, destroy_phar_manifest_entry, 0); entry.phar = mydata; entry.is_zip = 1; - /* prevent CRC checking */ - entry.is_crc_checked = 1; - for (i = 0; i < zip_get_num_files(zip); i++) { - char *name; - name = (char *) zip_get_name(zip, i, 0); - if (name) { - /* get file stat */ - if (-1 != zip_stat_index(zip, i, 0, &zs)) { - entry.compressed_filesize = zs.comp_size; - entry.uncompressed_filesize = zs.size; - entry.crc32 = zs.crc; - entry.timestamp = (php_uint32) zs.mtime; - entry.flags = PHAR_ENT_PERM_DEF_FILE; - switch (zs.comp_method) { - case ZIP_CM_DEFLATE : - /* if we have zip, we have zlib decompression */ - entry.flags |= PHAR_ENT_COMPRESSED_GZ; - break; - case ZIP_CM_BZIP2 : - if (!phar_has_bz2) { - if (mydata->metadata) { - zval_dtor(mydata->metadata); - } - efree(mydata->fname); - if (mydata->alias) { - efree(mydata->alias); - } - zip_close(zip); - zend_hash_destroy(&(mydata->manifest)); - mydata->manifest.arBuckets = NULL; - efree(mydata); - if (error) { - spprintf(error, 0, "bz2 extension is required for Bzip2 compressed zip-based .phar file \"%s\"", fname); - } - return FAILURE; - } - entry.flags |= PHAR_ENT_COMPRESSED_BZ2; - break; + entry.fp_type = PHAR_FP; +#define PHAR_ZIP_FAIL(errmsg) \ + zend_hash_destroy(&mydata->manifest); \ + php_stream_close(fp); \ + if (mydata->metadata) { \ + zval_dtor(mydata->metadata); \ + } \ + if (error) { \ + spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \ + } \ + efree(mydata->fname); \ + if (mydata->alias) { \ + efree(mydata->alias); \ + } \ + efree(mydata); \ + return FAILURE + + /* add each central directory item to the manifest */ + for (i = 0; i < locator.count; i++) { + phar_zip_central_dir_file zipentry; + + if (sizeof(zipentry) != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry))) { + PHAR_ZIP_FAIL("unable to read central directory entry, truncated"); + } + /* clean up for bigendian systems */ + if (memcmp("PK\1\2", zipentry.signature, 4)) { + /* corrupted entry */ + PHAR_ZIP_FAIL("corrupted central directory entry, no magic signature"); + } + entry.compressed_filesize = zipentry.compsize; + entry.uncompressed_filesize = zipentry.uncompsize; + entry.crc32 = zipentry.crc32; + entry.timestamp = phar_zip_d2u_time(zipentry.timestamp, zipentry.datestamp); + entry.flags = PHAR_ENT_PERM_DEF_FILE; + entry.header_offset = zipentry.offset; + entry.offset = entry.offset_abs = zipentry.offset + sizeof(phar_zip_file_header) + zipentry.filename_len + + zipentry.extra_len; + if (zipentry.flags & PHAR_ZIP_FLAG_ENCRYPTED) { + PHAR_ZIP_FAIL("Cannot process encrypted zip files"); + } + if (!zipentry.filename_len) { + PHAR_ZIP_FAIL("Cannot process zips created from stdin (zero-length filename)"); + } + entry.filename_len = zipentry.filename_len; + entry.filename = (char *) emalloc(zipentry.filename_len+1); + if (entry.filename_len != php_stream_read(fp, entry.filename, entry.filename_len)) { + efree(entry.filename); + PHAR_ZIP_FAIL("unable to read in filename from central directory, truncated"); + } + entry.filename[entry.filename_len] = '\0'; + if (entry.filename[entry.filename_len - 1] == '/') { + entry.is_dir = 1; + entry.filename_len--; + entry.flags |= PHAR_ENT_PERM_DEF_DIR; + } else { + entry.is_dir = 0; + } + if (zipentry.extra_len) { + off_t loc = php_stream_tell(fp); + if (FAILURE == phar_zip_process_extra(fp, &entry, zipentry.extra_len TSRMLS_CC)) { + PHAR_ZIP_FAIL("Unable to process extra field header for file in central directory"); + } + php_stream_seek(fp, loc + zipentry.extra_len, SEEK_SET); + } + switch (zipentry.compressed) { + case PHAR_ZIP_COMP_NONE : + /* compression flag already set */ + break; + case PHAR_ZIP_COMP_DEFLATE : + entry.flags |= PHAR_ENT_COMPRESSED_GZ; + if (!phar_has_zlib) { + PHAR_ZIP_FAIL("zlib extension is required"); + } + break; + case PHAR_ZIP_COMP_BZIP2 : + entry.flags |= PHAR_ENT_COMPRESSED_BZ2; + if (!phar_has_bz2) { + PHAR_ZIP_FAIL("bzip2 extension is required"); } + break; + default : + PHAR_ZIP_FAIL("unsupported compression method used in this zip"); + } + /* get file metadata */ + if (zipentry.comment_len) { + metadata = (char *) emalloc(zipentry.comment_len); + if (zipentry.comment_len != php_stream_read(fp, metadata, zipentry.comment_len)) { + PHAR_ZIP_FAIL("unable to read in file comment, truncated"); } - entry.index = i; - entry.filename_len = strlen(name); - if (name[entry.filename_len - 1] == '/') { - entry.is_dir = 1; - entry.filename_len--; - entry.flags |= PHAR_ENT_PERM_DEF_DIR; + if (phar_parse_metadata(&metadata, &(entry.metadata), zipentry.comment_len TSRMLS_CC) == FAILURE) { + /* if not valid serialized data, it is a regular string */ + ALLOC_INIT_ZVAL(entry.metadata); + Z_STRVAL_P(entry.metadata) = metadata; + Z_STRLEN_P(entry.metadata) = zipentry.comment_len; + Z_TYPE_P(entry.metadata) = IS_STRING; } else { - entry.is_dir = 0; + efree(metadata); } - entry.filename = estrndup(name, entry.filename_len); - /* get file metadata */ - metadata = (char *) zip_get_file_comment(zip, i, &metadata_len, 0); - if (metadata) { - if (phar_parse_metadata(&metadata, &(entry.metadata), metadata_len TSRMLS_CC) == FAILURE) { - /* if not valid serialized data, it is a regular string */ - ALLOC_INIT_ZVAL(entry.metadata); - Z_STRVAL_P(entry.metadata) = estrndup(metadata, metadata_len); - Z_STRLEN_P(entry.metadata) = metadata_len; - Z_TYPE_P(entry.metadata) = IS_STRING; + } else { + entry.metadata = NULL; + } + if (entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + php_stream_filter *filter; + off_t saveloc; + + /* archive alias found, seek to file contents, do not validate local header. Potentially risky, but + not very. */ + saveloc = php_stream_tell(fp); + php_stream_seek(fp, zipentry.offset + sizeof(phar_zip_file_header) + entry.filename_len + zipentry.extra_len, SEEK_SET); + if (entry.flags & PHAR_ENT_COMPRESSED_GZ) { + filter = php_stream_filter_create("zlib.inflate", NULL, php_stream_is_persistent(fp) TSRMLS_CC); + if (!filter) { + PHAR_ZIP_FAIL("unable to decompress alias, zlib filter creation failed"); } + php_stream_filter_append(&fp->readfilters, filter); + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &(mydata->alias), entry.uncompressed_filesize, 0)) || !mydata->alias) { + PHAR_ZIP_FAIL("unable to read in alias, truncated"); + } + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1 TSRMLS_CC); + } else if (entry.flags & PHAR_ENT_COMPRESSED_BZ2) { + php_stream_filter *filter; + filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp) TSRMLS_CC); + if (!filter) { + PHAR_ZIP_FAIL("unable to read in alias, bzip2 filter creation failed"); + } + php_stream_filter_append(&fp->readfilters, filter); + php_stream_filter_append(&fp->readfilters, filter); + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &(mydata->alias), entry.uncompressed_filesize, 0)) || !mydata->alias) { + PHAR_ZIP_FAIL("unable to read in alias, truncated"); + } + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1 TSRMLS_CC); } else { - entry.metadata = NULL; + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &(mydata->alias), entry.uncompressed_filesize, 0)) || !mydata->alias) { + PHAR_ZIP_FAIL("unable to read in alias, truncated"); + } } - zend_hash_add(&mydata->manifest, name, entry.filename_len, (void *)&entry,sizeof(phar_entry_info), NULL); - } - } - /* ignore all errors in loading up manifest */ - zip_error_clear(zip); - zend_hash_add(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); - if (register_alias) { - mydata->is_explicit_alias = 1; - zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); - } else { - mydata->is_explicit_alias = 0; + mydata->is_explicit_alias = 1; + mydata->alias_len = zipentry.uncompsize; + zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), mydata->alias, mydata->alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); + /* return to central directory parsing */ + php_stream_seek(fp, saveloc, SEEK_SET); + } + zend_hash_add(&mydata->manifest, entry.filename, entry.filename_len, (void *)&entry,sizeof(phar_entry_info), NULL); } + mydata->fp = fp; + zend_hash_add(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len, (void*)&mydata, sizeof(phar_archive_data*), NULL); if (pphar) { *pphar = mydata; } return SUCCESS; -#else - spprintf(error, 4096, "phar zip error: Cannot open zip-based phar \"%s\", phar not compiled with zip enabled", fname); - return FAILURE; -#endif } /* }}} */ @@ -229,7 +386,6 @@ int phar_open_zipfile(char *fname, int fname_len, char *alias, int alias_len, ph */ int phar_open_or_create_zip(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */ { -#if HAVE_PHAR_ZIP phar_archive_data *phar; int ret = phar_create_or_parse_filename(fname, fname_len, alias, alias_len, options, &phar, error TSRMLS_CC); @@ -243,49 +399,10 @@ int phar_open_or_create_zip(char *fname, int fname_len, char *alias, int alias_l return ret; } - if (!phar_has_zip) { - if (error) { - spprintf(error, 4096, "phar zip error: phar \"%s\" cannot be created as zip-based phar, zip-based phars are disabled (enable ext/zip)", fname); - } - return FAILURE; - } if (phar->is_brandnew) { - int *errorp = NULL; + phar->internal_file_start = 0; phar->is_zip = 1; - if (phar->fp) { - php_stream_close(phar->fp); - phar->fp = NULL; - php_stream_unlink(phar->fname, 0, NULL); - } - phar->zip = zip_open(fname, 0 | ZIP_CREATE | ZIP_EXCL, errorp); - if (NULL != phar->zip) { - return SUCCESS; - } - /* fail - free newly created manifest entry */ - zend_hash_del(&(PHAR_GLOBALS->phar_alias_map), phar->alias, phar->alias_len); - zend_hash_del(&(PHAR_GLOBALS->phar_fname_map), fname, fname_len); - efree(phar->fname); - efree(phar->alias); - efree(phar); - *pphar = NULL; - - if (error) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - tmp_len = zip_error_to_str(NULL, 0, *errorp, *errorp); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - spprintf(error, 4096, "phar zip error: cannot create zip-based phar \"%s\"", fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, *errorp, *errorp)) { - spprintf(error, 4096, "phar zip error: cannot create zip-based phar \"%s\"", fname); - } else { - spprintf(error, 4096, "phar zip error: cannot create zip-based phar \"%s\": %s", fname, tmp); - efree(tmp); - } - } - } - return FAILURE; + return SUCCESS; } /* we've reached here - the phar exists and is a regular phar */ @@ -293,56 +410,29 @@ int phar_open_or_create_zip(char *fname, int fname_len, char *alias, int alias_l spprintf(error, 4096, "phar zip error: phar \"%s\" already exists as a regular phar and must be deleted from disk prior to creating as a zip-based phar", fname); } return FAILURE; -#else - if (error) { - spprintf(error, 4096, "phar zip error: phar \"%s\" cannot be created as zip-based phar, zip-based phars are disabled and cannot be enabled", fname); - } - return FAILURE; -#endif /* #if HAVE_PHAR_ZIP */ } /* }}} */ -#if HAVE_PHAR_ZIP -static ssize_t phar_zip_source(void *state, void *data, size_t len, enum zip_source_cmd cmd) -{ - char *buf = (char *) data; - phar_entry_info *entry = (phar_entry_info *) state; - size_t read; - struct zip_stat *sb = (struct zip_stat *) data; - TSRMLS_FETCH(); - - switch (cmd) { - case ZIP_SOURCE_OPEN : - /* offset_within_phar is only non-zero when converting from tar/phar-based to zip-based */ - php_stream_seek(entry->fp, entry->offset_within_phar, SEEK_SET); - return 0; - case ZIP_SOURCE_READ : - read = php_stream_read(entry->fp, buf, len); - if (read < 0) return 0; - return read; - case ZIP_SOURCE_STAT : - zip_stat_init(sb); - sb->mtime = time(NULL); - sb->size = entry->uncompressed_filesize; - return sizeof(struct zip_stat); - case ZIP_SOURCE_FREE: - entry->is_modified = 0; - /* phar->fp is set only if we're converting from a tar/phar-based archive */ - if (entry->fp && entry->fp_refcount == 0 && entry->fp != entry->phar->fp) { - php_stream_close(entry->fp); - entry->fp = NULL; - } - default: - return len; - } -} - -/* reconstruct the zip index of each manifest entry */ -static int phar_zip_reconstruct_apply(void *data TSRMLS_DC) /* {{{ */ +struct _phar_zip_pass { + php_stream *filefp; + php_stream *centralfp; + php_stream *old; + char **error; +}; +/* perform final modification of zip contents for each file in the manifest before saving */ +static int phar_zip_changed_apply(void *data, void *arg TSRMLS_DC) /* {{{ */ { - phar_entry_info *entry = (phar_entry_info *)data; + phar_entry_info *entry; + phar_zip_file_header local; + phar_zip_unix3 perms; + phar_zip_central_dir_file central; + struct _phar_zip_pass *p; + php_uint32 newcrc32; + off_t offset; + + entry = (phar_entry_info *)data; + p = (struct _phar_zip_pass*) arg; if (entry->is_deleted) { - entry->index = -1; if (entry->fp_refcount <= 0) { return ZEND_HASH_APPLY_REMOVE; } else { @@ -350,137 +440,251 @@ static int phar_zip_reconstruct_apply(void *data TSRMLS_DC) /* {{{ */ return ZEND_HASH_APPLY_KEEP; } } - if (entry->is_dir) { - char *myname = estrndup(entry->filename, entry->filename_len+2); - myname[entry->filename_len] = '/'; - myname[entry->filename_len+1] = '\0'; - entry->index = zip_name_locate(entry->phar->zip, entry->filename, ZIP_FL_UNCHANGED); - efree(myname); - } else { - entry->index = zip_name_locate(entry->phar->zip, entry->filename, ZIP_FL_UNCHANGED); + memset(&local, 0, sizeof(local)); + memset(¢ral, 0, sizeof(central)); + memset(&perms, 0, sizeof(perms)); + strncpy(local.signature, "PK\3\4", 4); + strncpy(central.signature, "PK\1\2", 4); + central.extra_len = local.extra_len = sizeof(perms); + perms.tag[0] = 'n'; + perms.tag[1] = 'u'; + perms.size = sizeof(perms) - 4; + perms.perms = entry->flags & PHAR_ENT_PERM_MASK; + perms.crc32 = ~0; + CRC32(perms.crc32, (char)perms.perms & 0xFF); + CRC32(perms.crc32, (char)perms.perms & 0xFF00 >> 8); + perms.crc32 = ~(perms.crc32); + if (entry->flags & PHAR_ENT_COMPRESSED_GZ) { + local.compressed = central.compressed = PHAR_ZIP_COMP_DEFLATE; } - return ZEND_HASH_APPLY_KEEP; -} - -/* perform final modification of zip contents for each file in the manifest before saving */ -static int phar_zip_changed_apply(void *data TSRMLS_DC) /* {{{ */ -{ - phar_entry_info *entry = (phar_entry_info *)data; - - if (entry->is_deleted) { - if (entry->fp_refcount <= 0) { - return ZEND_HASH_APPLY_REMOVE; - } else { - /* we can't delete this in-memory until it is closed */ - return ZEND_HASH_APPLY_KEEP; - } + if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) { + local.compressed = central.compressed = PHAR_ZIP_COMP_BZIP2; } + phar_zip_u2d_time(entry->timestamp, &local.timestamp, &local.datestamp); + central.timestamp = local.timestamp; + central.datestamp = local.datestamp; + central.filename_len = local.filename_len = entry->filename_len; + central.offset = php_stream_tell(p->filefp); + /* do extra field for perms later */ if (entry->is_modified) { - ssize_t (*cb)(void *state, void *data, size_t len, enum zip_source_cmd cmd) = phar_zip_source; - if (entry->fp) { - php_stream *fp = entry->fp; - struct zip_source *s = zip_source_function(entry->phar->zip, cb, entry); - - /* we have to prevent free of this fp by mistake */ - entry->fp = NULL; - if (-1 == (entry->index = _zip_replace(entry->phar->zip, entry->index, (const char *) entry->filename, s))) { - zip_error_clear(entry->phar->zip); - return ZEND_HASH_APPLY_REMOVE; + php_uint32 loc; + php_stream_filter *filter; + php_stream *efp; + + if (entry->is_dir) { + entry->is_modified = 0; + if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp) { + php_stream_close(entry->fp); + entry->fp = NULL; + entry->fp_type = PHAR_FP; } - /* now restore fp and is_modified */ - entry->fp = fp; - entry->is_modified = 1; + goto continue_dir; + } + if (FAILURE == phar_open_entry_fp(entry, p->error TSRMLS_CC)) { + spprintf(p->error, 0, "unable to open file contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; } + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + efp = phar_get_efp(entry); - /* set file metadata */ - if (entry->metadata) { - php_serialize_data_t metadata_hash; - if (entry->metadata_str.c) { - smart_str_free(&entry->metadata_str); + newcrc32 = ~0; + for (loc = 0;loc < entry->uncompressed_filesize; loc++) { + CRC32(newcrc32, php_stream_getc(efp)); + } + entry->crc32 = ~newcrc32; + central.uncompsize = local.uncompsize = entry->uncompressed_filesize; + if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) { + /* not compressed */ + entry->compressed_filesize = entry->uncompressed_filesize; + central.compsize = local.compsize = entry->compressed_filesize; + goto not_compressed; + } + filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0 TSRMLS_CC); + if (!filter) { + if (entry->flags & PHAR_ENT_COMPRESSED_GZ) { + spprintf(p->error, 0, "unable to gzip compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + } else { + spprintf(p->error, 0, "unable to bzip2 compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); } - entry->metadata_str.c = 0; - entry->metadata_str.len = 0; - PHP_VAR_SERIALIZE_INIT(metadata_hash); - php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash TSRMLS_CC); - PHP_VAR_SERIALIZE_DESTROY(metadata_hash); - if (-1 == zip_set_file_comment(entry->phar->zip, entry->index, entry->metadata_str.c, entry->metadata_str.len)) { + return ZEND_HASH_APPLY_STOP; + } + + /* create new file that holds the compressed version */ + /* work around inability to specify freedom in write and strictness + in read count */ + entry->cfp = php_stream_fopen_tmpfile(); + if (!entry->cfp) { + spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + php_stream_flush(efp); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC)) { + spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + php_stream_filter_append((&entry->cfp->writefilters), filter); + if (entry->uncompressed_filesize != php_stream_copy_to_stream(efp, entry->cfp, entry->uncompressed_filesize)) { + spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + php_stream_filter_flush(filter, 1); + php_stream_flush(entry->cfp); + php_stream_filter_remove(filter, 1 TSRMLS_CC); + php_stream_seek(entry->cfp, 0, SEEK_END); + entry->compressed_filesize = (php_uint32) php_stream_tell(entry->cfp); + /* generate crc on compressed file */ + php_stream_rewind(entry->cfp); + entry->old_flags = entry->flags; + entry->is_modified = 1; + } else { + central.uncompsize = local.uncompsize = entry->uncompressed_filesize; + central.compsize = local.compsize = entry->compressed_filesize; + if (-1 == php_stream_seek(p->old, entry->offset_abs, SEEK_SET)) { + spprintf(p->error, 0, "unable to seek to start of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + } +not_compressed: + central.crc32 = local.crc32 = entry->crc32; +continue_dir: + /* set file metadata */ + if (entry->metadata) { + php_serialize_data_t metadata_hash; + if (entry->metadata_str.c) { + smart_str_free(&entry->metadata_str); + } + entry->metadata_str.c = 0; + entry->metadata_str.len = 0; + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash TSRMLS_CC); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + central.comment_len = entry->metadata_str.len; + } + entry->header_offset = php_stream_tell(p->filefp); + offset = entry->header_offset + sizeof(local) + entry->filename_len + sizeof(perms); + if (sizeof(local) != php_stream_write(p->filefp, (char *)&local, sizeof(local))) { + spprintf(p->error, 0, "unable to write local file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (sizeof(central) != php_stream_write(p->centralfp, (char *)¢ral, sizeof(central))) { + spprintf(p->error, 0, "unable to write central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) { + spprintf(p->error, 0, "unable to write filename to local directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) { + spprintf(p->error, 0, "unable to write filename to central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (sizeof(perms) != php_stream_write(p->filefp, (char *)&perms, sizeof(perms))) { + spprintf(p->error, 0, "unable to write local extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (sizeof(perms) != php_stream_write(p->centralfp, (char *)&perms, sizeof(perms))) { + spprintf(p->error, 0, "unable to write central extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + if (entry->is_modified) { + if (entry->cfp) { + if (entry->compressed_filesize != php_stream_copy_to_stream(entry->cfp, p->filefp, entry->compressed_filesize)) { + spprintf(p->error, 0, "unable to write compressed contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } + php_stream_close(entry->cfp); + entry->cfp = NULL; } else { - zip_set_file_comment(entry->phar->zip, entry->index, NULL, 0); + if (FAILURE == phar_open_entry_fp(entry, p->error TSRMLS_CC)) { + return ZEND_HASH_APPLY_STOP; + } + phar_seek_efp(entry, 0, SEEK_SET, 0 TSRMLS_CC); + if (entry->uncompressed_filesize != php_stream_copy_to_stream(phar_get_efp(entry), p->filefp, entry->uncompressed_filesize)) { + spprintf(p->error, 0, "unable to write contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; + } + } + if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp && entry->fp_refcount == 0) { + php_stream_close(entry->fp); + } + entry->is_modified = 0; + } else { + if (!entry->is_dir && entry->compressed_filesize && entry->compressed_filesize != php_stream_copy_to_stream(p->old, p->filefp, entry->compressed_filesize)) { + spprintf(p->error, 0, "unable to copy contents of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + return ZEND_HASH_APPLY_STOP; } } + entry->fp = NULL; + entry->offset = entry->offset_abs = offset; + entry->fp_type = PHAR_FP; + if (entry->metadata_str.c) { + if (entry->metadata_str.len != php_stream_write(p->centralfp, entry->metadata_str.c, entry->metadata_str.len)) { + spprintf(p->error, 0, "unable to write metadata as file comment for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); + smart_str_free(&entry->metadata_str); + return ZEND_HASH_APPLY_STOP; + } + smart_str_free(&entry->metadata_str); + } return ZEND_HASH_APPLY_KEEP; } /* }}} */ -int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char **error TSRMLS_DC) /* {{{ */ +int phar_zip_flush(phar_archive_data *phar, char *user_stub, long len, char **error TSRMLS_DC) /* {{{ */ { char *pos; smart_str main_metadata_str = {0}; - int ziperrint; static const char newstub[] = "metadata) { - PHP_VAR_SERIALIZE_INIT(metadata_hash); - php_var_serialize(&main_metadata_str, &archive->metadata, &metadata_hash TSRMLS_CC); - PHP_VAR_SERIALIZE_DESTROY(metadata_hash); - zip_set_archive_comment(archive->zip, main_metadata_str.c, main_metadata_str.len); - smart_str_free(&main_metadata_str); - } else { - zip_set_archive_comment(archive->zip, NULL, 0); - } + entry.phar = phar; + entry.fp_type = PHAR_MOD; /* set alias */ - if (archive->is_explicit_alias) { - phar_alias_index = zip_name_locate(archive->zip, ".phar/alias.txt", 0); + if (phar->is_explicit_alias) { entry.fp = php_stream_fopen_tmpfile(); - if (archive->alias_len != php_stream_write(entry.fp, archive->alias, archive->alias_len)) { + if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) { if (error) { - spprintf(error, 0, "unable to set alias in new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to set alias in new zip-based phar \"%s\"", phar->fname); } return EOF; } - entry.uncompressed_filesize = sizeof(newstub) - 1; + entry.uncompressed_filesize = entry.compressed_filesize = phar->alias_len; entry.filename = estrndup(".phar/alias.txt", sizeof(".phar/alias.txt")-1); entry.filename_len = sizeof(".phar/alias.txt")-1; entry.is_modified = 1; - entry.index = phar_alias_index; - if (SUCCESS != zend_hash_update(&archive->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { + if (SUCCESS != zend_hash_update(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { if (error) { - spprintf(error, 0, "unable to set alias in new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to set alias in new zip-based phar \"%s\"", phar->fname); } return EOF; } } /* register alias */ - if (archive->alias_len) { - phar_get_archive(&archive, archive->fname, archive->fname_len, archive->alias, archive->alias_len, NULL TSRMLS_CC); + if (phar->alias_len) { + phar_get_archive(&phar, phar->fname, phar->fname_len, phar->alias, phar->alias_len, NULL TSRMLS_CC); } /* set stub */ - phar_stub_index = zip_name_locate(archive->zip, ".phar/stub.php", 0); - zip_error_clear(archive->zip); if (user_stub) { if (len < 0) { /* resource passed in */ if (!(php_stream_from_zval_no_verify(stubfile, (zval **)user_stub))) { if (error) { - spprintf(error, 0, "unable to access resource to copy stub to new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to access resource to copy stub to new zip-based phar \"%s\"", phar->fname); } return EOF; } @@ -492,7 +696,7 @@ int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char * user_stub = 0; if (!(len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)) || !user_stub) { if (error) { - spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", phar->fname); } return EOF; } @@ -503,7 +707,7 @@ int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char * if ((pos = strstr(user_stub, "__HALT_COMPILER();")) == NULL) { if (error) { - spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", phar->fname); } if (free_user_stub) { efree(user_stub); @@ -517,7 +721,7 @@ int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char * if ((size_t)len != php_stream_write(entry.fp, user_stub, len) || 5 != php_stream_write(entry.fp, " ?>\r\n", 5)) { if (error) { - spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", phar->fname); } if (free_user_stub) { efree(user_stub); @@ -528,13 +732,12 @@ int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char * entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1); entry.filename_len = sizeof(".phar/stub.php")-1; entry.is_modified = 1; - entry.index = phar_stub_index; - if (SUCCESS != zend_hash_update(&archive->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { + if (SUCCESS != zend_hash_update(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { if (free_user_stub) { efree(user_stub); } if (error) { - spprintf(error, 0, "unable to set stub in zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to set stub in zip-based phar \"%s\"", phar->fname); } return EOF; } @@ -542,73 +745,156 @@ int phar_zip_flush(phar_archive_data *archive, char *user_stub, long len, char * efree(user_stub); } } else { - if (-1 == phar_stub_index) { + if (!zend_hash_exists(&phar->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { /* this is a brand new phar, add the stub */ entry.fp = php_stream_fopen_tmpfile(); if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) { if (error) { - spprintf(error, 0, "unable to create stub in new zip-based phar \"%s\"", archive->fname); + spprintf(error, 0, "unable to create stub in new zip-based phar \"%s\"", phar->fname); } return EOF; } - entry.uncompressed_filesize = sizeof(newstub) - 1; + entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1; entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1); entry.filename_len = sizeof(".phar/stub.php")-1; entry.is_modified = 1; - entry.index = -1; - if (SUCCESS != zend_hash_add(&archive->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { + if (SUCCESS != zend_hash_add(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL)) { php_stream_close(entry.fp); efree(entry.filename); if (error) { - spprintf(error, 0, "unable to create stub in new zip-based phar \"%s\": %s", archive->fname, zip_strerror(archive->zip)); + spprintf(error, 0, "unable to create stub in new zip-based phar \"%s\"", phar->fname); } return EOF; } } } + if (phar->fp && !phar->is_brandnew) { + oldfile = phar->fp; + closeoldfile = 0; + php_stream_rewind(oldfile); + } else { + oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL); + closeoldfile = oldfile != NULL; + } + /* save modified files to the zip */ - zend_hash_apply(&archive->manifest, phar_zip_changed_apply TSRMLS_CC); - if (archive->zip->error.str) { + pass.old = oldfile; + pass.filefp = php_stream_fopen_tmpfile(); + if (!pass.filefp) { + if (closeoldfile) { + php_stream_close(oldfile); + } if (error) { - spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", archive->fname, zip_strerror(archive->zip)); + spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname); } return EOF; } + pass.centralfp = php_stream_fopen_tmpfile(); + if (!pass.centralfp) { + if (closeoldfile) { + php_stream_close(oldfile); + } + if (error) { + spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname); + } + return EOF; + } + memset(&eocd, 0, sizeof(eocd)); + + strncpy(eocd.signature, "PK\5\6", 4); + eocd.counthere = eocd.count = zend_hash_num_elements(&phar->manifest); + zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass TSRMLS_CC); + if (temperr) { + php_stream_close(pass.filefp); + php_stream_close(pass.centralfp); + if (closeoldfile) { + php_stream_close(oldfile); + } + if (error) { + spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr); + } + efree(temperr); + return EOF; + } /* save zip */ - if (-1 == zip_close(archive->zip)) { + eocd.cdir_size = php_stream_tell(pass.centralfp); + eocd.cdir_offset = php_stream_tell(pass.filefp); + /* implement metadata here */ + php_stream_seek(pass.centralfp, 0, SEEK_SET); + if (eocd.cdir_size != php_stream_copy_to_stream(pass.centralfp, pass.filefp, PHP_STREAM_COPY_ALL)) { + php_stream_close(pass.filefp); + php_stream_close(pass.centralfp); if (error) { - spprintf(error, 4096, "saving of zip-based phar \"%s\" failed: %s", archive->fname, zip_strerror(archive->zip)); + spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write central-directory", phar->fname); + } + if (closeoldfile) { + php_stream_close(oldfile); } return EOF; } - - /* re-open */ - archive->zip = zip_open(archive->fname, 0, &ziperrint); - if (!archive->zip) { + php_stream_close(pass.centralfp); + if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) { + php_stream_close(pass.filefp); if (error) { - /* now for the stupid hoops libzip forces... */ - char *tmp; - int tmp_len; - tmp_len = zip_error_to_str(NULL, 0, ziperrint, ziperrint); - if (!(tmp = emalloc((tmp_len + 1) * sizeof(char)))) { - spprintf(error, 4096, "phar zip error: cannot re-open zip-based phar \"%s\"", archive->fname); - } else { - if (!zip_error_to_str(tmp, tmp_len + 1, ziperrint, ziperrint)) { - spprintf(error, 4096, "phar zip error: cannot re-open zip-based phar \"%s\"", archive->fname); - } else { - spprintf(error, 4096, "phar zip error: cannot re-open zip-based phar \"%s\": %s", archive->fname, tmp); - efree(tmp); - } - } + spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname); + } + if (closeoldfile) { + php_stream_close(oldfile); } return EOF; } + if (phar->metadata) { + /* set phar metadata */ + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash TSRMLS_CC); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + if (main_metadata_str.len != php_stream_write(pass.filefp, main_metadata_str.c, main_metadata_str.len)) { + php_stream_close(pass.filefp); + if (error) { + spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write metadata to zip comment", phar->fname); + } + if (closeoldfile) { + php_stream_close(oldfile); + } + return EOF; + } + smart_str_free(&main_metadata_str); + } + if (phar->fp) { + php_stream_close(phar->fp); + } + if (phar->ufp) { + php_stream_close(phar->ufp); + phar->ufp = NULL; + } + /* re-open */ + phar->is_brandnew = 0; + if (phar->donotflush) { + /* deferred flush */ + phar->fp = pass.filefp; + } else { + phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL); + if (!phar->fp) { + if (closeoldfile) { + php_stream_close(oldfile); + } + phar->fp = pass.filefp; + if (error) { + spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname); + } + return EOF; + } + php_stream_rewind(pass.filefp); + php_stream_copy_to_stream(pass.filefp, phar->fp, PHP_STREAM_COPY_ALL); + /* we could also reopen the file in "rb" mode but there is no need for that */ + php_stream_close(pass.filefp); + } - /* reconstruct manifest zip index map */ - zend_hash_apply(&archive->manifest, phar_zip_reconstruct_apply TSRMLS_CC); + if (closeoldfile) { + php_stream_close(oldfile); + } return EOF; } /* }}} */ -#endif /* if HAVE_PHAR_ZIP */