summaryrefslogtreecommitdiff
path: root/ext/phar/phar.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/phar/phar.c')
-rw-r--r--ext/phar/phar.c3749
1 files changed, 3749 insertions, 0 deletions
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
new file mode 100644
index 0000000..f9db05c
--- /dev/null
+++ b/ext/phar/phar.c
@@ -0,0 +1,3749 @@
+/*
+ +----------------------------------------------------------------------+
+ | phar php single-file executable PHP extension |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 2005-2013 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 <cellog@php.net> |
+ | Marcus Boerger <helly@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id: c5042cc34acebcc0926625b57dff03deebbe6472 $ */
+
+#define PHAR_MAIN 1
+#include "phar_internal.h"
+#include "SAPI.h"
+#include "func_interceptors.h"
+
+static void destroy_phar_data(void *pDest);
+
+ZEND_DECLARE_MODULE_GLOBALS(phar)
+#if PHP_VERSION_ID >= 50300
+char *(*phar_save_resolve_path)(const char *filename, int filename_len TSRMLS_DC);
+#endif
+
+/**
+ * set's phar->is_writeable based on the current INI value
+ */
+static int phar_set_writeable_bit(void *pDest, void *argument TSRMLS_DC) /* {{{ */
+{
+ zend_bool keep = *(zend_bool *)argument;
+ phar_archive_data *phar = *(phar_archive_data **)pDest;
+
+ if (!phar->is_data) {
+ phar->is_writeable = !keep;
+ }
+
+ return ZEND_HASH_APPLY_KEEP;
+}
+/* }}} */
+
+/* if the original value is 0 (disabled), then allow setting/unsetting at will. Otherwise only allow 1 (enabled), and error on disabling */
+ZEND_INI_MH(phar_ini_modify_handler) /* {{{ */
+{
+ zend_bool old, ini;
+
+ if (entry->name_length == 14) {
+ old = PHAR_G(readonly_orig);
+ } else {
+ old = PHAR_G(require_hash_orig);
+ }
+
+ if (new_value_length == 2 && !strcasecmp("on", new_value)) {
+ ini = (zend_bool) 1;
+ }
+ else if (new_value_length == 3 && !strcasecmp("yes", new_value)) {
+ ini = (zend_bool) 1;
+ }
+ else if (new_value_length == 4 && !strcasecmp("true", new_value)) {
+ ini = (zend_bool) 1;
+ }
+ else {
+ ini = (zend_bool) atoi(new_value);
+ }
+
+ /* do not allow unsetting in runtime */
+ if (stage == ZEND_INI_STAGE_STARTUP) {
+ if (entry->name_length == 14) {
+ PHAR_G(readonly_orig) = ini;
+ } else {
+ PHAR_G(require_hash_orig) = ini;
+ }
+ } else if (old && !ini) {
+ return FAILURE;
+ }
+
+ if (entry->name_length == 14) {
+ PHAR_G(readonly) = ini;
+ if (PHAR_GLOBALS->request_init && PHAR_GLOBALS->phar_fname_map.arBuckets) {
+ zend_hash_apply_with_argument(&(PHAR_GLOBALS->phar_fname_map), phar_set_writeable_bit, (void *)&ini TSRMLS_CC);
+ }
+ } else {
+ PHAR_G(require_hash) = ini;
+ }
+
+ return SUCCESS;
+}
+/* }}}*/
+
+/* this global stores the global cached pre-parsed manifests */
+HashTable cached_phars;
+HashTable cached_alias;
+
+static void phar_split_cache_list(TSRMLS_D) /* {{{ */
+{
+ char *tmp;
+ char *key, *lasts, *end;
+ char ds[2];
+ phar_archive_data *phar;
+ uint i = 0;
+
+ if (!PHAR_GLOBALS->cache_list || !(PHAR_GLOBALS->cache_list[0])) {
+ return;
+ }
+
+ ds[0] = DEFAULT_DIR_SEPARATOR;
+ ds[1] = '\0';
+ tmp = estrdup(PHAR_GLOBALS->cache_list);
+
+ /* fake request startup */
+ PHAR_GLOBALS->request_init = 1;
+ if (zend_hash_init(&EG(regular_list), 0, NULL, NULL, 0) == SUCCESS) {
+ EG(regular_list).nNextFreeElement=1; /* we don't want resource id 0 */
+ }
+
+ PHAR_G(has_bz2) = zend_hash_exists(&module_registry, "bz2", sizeof("bz2"));
+ PHAR_G(has_zlib) = zend_hash_exists(&module_registry, "zlib", sizeof("zlib"));
+ /* these two are dummies and will be destroyed later */
+ zend_hash_init(&cached_phars, sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1);
+ zend_hash_init(&cached_alias, sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1);
+ /* these two are real and will be copied over cached_phars/cached_alias later */
+ zend_hash_init(&(PHAR_GLOBALS->phar_fname_map), sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1);
+ zend_hash_init(&(PHAR_GLOBALS->phar_alias_map), sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1);
+ PHAR_GLOBALS->manifest_cached = 1;
+ PHAR_GLOBALS->persist = 1;
+
+ for (key = php_strtok_r(tmp, ds, &lasts);
+ key;
+ key = php_strtok_r(NULL, ds, &lasts)) {
+ end = strchr(key, DEFAULT_DIR_SEPARATOR);
+
+ if (end) {
+ if (SUCCESS == phar_open_from_filename(key, end - key, NULL, 0, 0, &phar, NULL TSRMLS_CC)) {
+finish_up:
+ phar->phar_pos = i++;
+ php_stream_close(phar->fp);
+ phar->fp = NULL;
+ } else {
+finish_error:
+ PHAR_GLOBALS->persist = 0;
+ PHAR_GLOBALS->manifest_cached = 0;
+ efree(tmp);
+ zend_hash_destroy(&(PHAR_G(phar_fname_map)));
+ PHAR_GLOBALS->phar_fname_map.arBuckets = 0;
+ zend_hash_destroy(&(PHAR_G(phar_alias_map)));
+ PHAR_GLOBALS->phar_alias_map.arBuckets = 0;
+ zend_hash_destroy(&cached_phars);
+ zend_hash_destroy(&cached_alias);
+ zend_hash_graceful_reverse_destroy(&EG(regular_list));
+ memset(&EG(regular_list), 0, sizeof(HashTable));
+ /* free cached manifests */
+ PHAR_GLOBALS->request_init = 0;
+ return;
+ }
+ } else {
+ if (SUCCESS == phar_open_from_filename(key, strlen(key), NULL, 0, 0, &phar, NULL TSRMLS_CC)) {
+ goto finish_up;
+ } else {
+ goto finish_error;
+ }
+ }
+ }
+
+ PHAR_GLOBALS->persist = 0;
+ PHAR_GLOBALS->request_init = 0;
+ /* destroy dummy values from before */
+ zend_hash_destroy(&cached_phars);
+ zend_hash_destroy(&cached_alias);
+ cached_phars = PHAR_GLOBALS->phar_fname_map;
+ cached_alias = PHAR_GLOBALS->phar_alias_map;
+ PHAR_GLOBALS->phar_fname_map.arBuckets = 0;
+ PHAR_GLOBALS->phar_alias_map.arBuckets = 0;
+ zend_hash_graceful_reverse_destroy(&EG(regular_list));
+ memset(&EG(regular_list), 0, sizeof(HashTable));
+ efree(tmp);
+}
+/* }}} */
+
+ZEND_INI_MH(phar_ini_cache_list) /* {{{ */
+{
+ PHAR_G(cache_list) = new_value;
+
+ if (stage == ZEND_INI_STAGE_STARTUP) {
+ phar_split_cache_list(TSRMLS_C);
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+PHP_INI_BEGIN()
+ STD_PHP_INI_BOOLEAN( "phar.readonly", "1", PHP_INI_ALL, phar_ini_modify_handler, readonly, zend_phar_globals, phar_globals)
+ STD_PHP_INI_BOOLEAN( "phar.require_hash", "1", PHP_INI_ALL, phar_ini_modify_handler, require_hash, zend_phar_globals, phar_globals)
+ STD_PHP_INI_ENTRY("phar.cache_list", "", PHP_INI_SYSTEM, phar_ini_cache_list, cache_list, zend_phar_globals, phar_globals)
+PHP_INI_END()
+
+/**
+ * When all uses of a phar have been concluded, this frees the manifest
+ * and the phar slot
+ */
+void phar_destroy_phar_data(phar_archive_data *phar TSRMLS_DC) /* {{{ */
+{
+ if (phar->alias && phar->alias != phar->fname) {
+ pefree(phar->alias, phar->is_persistent);
+ phar->alias = NULL;
+ }
+
+ if (phar->fname) {
+ pefree(phar->fname, phar->is_persistent);
+ phar->fname = NULL;
+ }
+
+ if (phar->signature) {
+ pefree(phar->signature, phar->is_persistent);
+ phar->signature = NULL;
+ }
+
+ if (phar->manifest.arBuckets) {
+ zend_hash_destroy(&phar->manifest);
+ phar->manifest.arBuckets = NULL;
+ }
+
+ if (phar->mounted_dirs.arBuckets) {
+ zend_hash_destroy(&phar->mounted_dirs);
+ phar->mounted_dirs.arBuckets = NULL;
+ }
+
+ if (phar->virtual_dirs.arBuckets) {
+ zend_hash_destroy(&phar->virtual_dirs);
+ phar->virtual_dirs.arBuckets = NULL;
+ }
+
+ if (phar->metadata) {
+ if (phar->is_persistent) {
+ if (phar->metadata_len) {
+ /* for zip comments that are strings */
+ free(phar->metadata);
+ } else {
+ zval_internal_ptr_dtor(&phar->metadata);
+ }
+ } else {
+ zval_ptr_dtor(&phar->metadata);
+ }
+ phar->metadata_len = 0;
+ phar->metadata = 0;
+ }
+
+ if (phar->fp) {
+ php_stream_close(phar->fp);
+ phar->fp = 0;
+ }
+
+ if (phar->ufp) {
+ php_stream_close(phar->ufp);
+ phar->ufp = 0;
+ }
+
+ pefree(phar, phar->is_persistent);
+}
+/* }}}*/
+
+/**
+ * Delete refcount and destruct if needed. On destruct return 1 else 0.
+ */
+int phar_archive_delref(phar_archive_data *phar TSRMLS_DC) /* {{{ */
+{
+ if (phar->is_persistent) {
+ return 0;
+ }
+
+ if (--phar->refcount < 0) {
+ if (PHAR_GLOBALS->request_done
+ || zend_hash_del(&(PHAR_GLOBALS->phar_fname_map), phar->fname, phar->fname_len) != SUCCESS) {
+ phar_destroy_phar_data(phar TSRMLS_CC);
+ }
+ return 1;
+ } else if (!phar->refcount) {
+ /* invalidate phar cache */
+ PHAR_G(last_phar) = NULL;
+ PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL;
+
+ if (phar->fp && !(phar->flags & PHAR_FILE_COMPRESSION_MASK)) {
+ /* close open file handle - allows removal or rename of
+ the file on windows, which has greedy locking
+ only close if the archive was not already compressed. If it
+ was compressed, then the fp does not refer to the original file */
+ php_stream_close(phar->fp);
+ phar->fp = NULL;
+ }
+
+ if (!zend_hash_num_elements(&phar->manifest)) {
+ /* this is a new phar that has perhaps had an alias/metadata set, but has never
+ been flushed */
+ if (zend_hash_del(&(PHAR_GLOBALS->phar_fname_map), phar->fname, phar->fname_len) != SUCCESS) {
+ phar_destroy_phar_data(phar TSRMLS_CC);
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+/* }}}*/
+
+/**
+ * Destroy phar's in shutdown, here we don't care about aliases
+ */
+static void destroy_phar_data_only(void *pDest) /* {{{ */
+{
+ phar_archive_data *phar_data = *(phar_archive_data **) pDest;
+ TSRMLS_FETCH();
+
+ if (EG(exception) || --phar_data->refcount < 0) {
+ phar_destroy_phar_data(phar_data TSRMLS_CC);
+ }
+}
+/* }}}*/
+
+/**
+ * Delete aliases to phar's that got kicked out of the global table
+ */
+static int phar_unalias_apply(void *pDest, void *argument TSRMLS_DC) /* {{{ */
+{
+ return *(void**)pDest == argument ? ZEND_HASH_APPLY_REMOVE : ZEND_HASH_APPLY_KEEP;
+}
+/* }}} */
+
+/**
+ * Delete aliases to phar's that got kicked out of the global table
+ */
+static int phar_tmpclose_apply(void *pDest TSRMLS_DC) /* {{{ */
+{
+ phar_entry_info *entry = (phar_entry_info *) pDest;
+
+ if (entry->fp_type != PHAR_TMP) {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+
+ if (entry->fp && !entry->fp_refcount) {
+ php_stream_close(entry->fp);
+ entry->fp = NULL;
+ }
+
+ return ZEND_HASH_APPLY_KEEP;
+}
+/* }}} */
+
+/**
+ * Filename map destructor
+ */
+static void destroy_phar_data(void *pDest) /* {{{ */
+{
+ phar_archive_data *phar_data = *(phar_archive_data **) pDest;
+ TSRMLS_FETCH();
+
+ if (PHAR_GLOBALS->request_ends) {
+ /* first, iterate over the manifest and close all PHAR_TMP entry fp handles,
+ this prevents unnecessary unfreed stream resources */
+ zend_hash_apply(&(phar_data->manifest), phar_tmpclose_apply TSRMLS_CC);
+ destroy_phar_data_only(pDest);
+ return;
+ }
+
+ zend_hash_apply_with_argument(&(PHAR_GLOBALS->phar_alias_map), phar_unalias_apply, phar_data TSRMLS_CC);
+
+ if (--phar_data->refcount < 0) {
+ phar_destroy_phar_data(phar_data TSRMLS_CC);
+ }
+}
+/* }}}*/
+
+/**
+ * destructor for the manifest hash, frees each file's entry
+ */
+void destroy_phar_manifest_entry(void *pDest) /* {{{ */
+{
+ phar_entry_info *entry = (phar_entry_info *)pDest;
+ TSRMLS_FETCH();
+
+ if (entry->cfp) {
+ php_stream_close(entry->cfp);
+ entry->cfp = 0;
+ }
+
+ if (entry->fp) {
+ php_stream_close(entry->fp);
+ entry->fp = 0;
+ }
+
+ if (entry->metadata) {
+ if (entry->is_persistent) {
+ if (entry->metadata_len) {
+ /* for zip comments that are strings */
+ free(entry->metadata);
+ } else {
+ zval_internal_ptr_dtor(&entry->metadata);
+ }
+ } else {
+ zval_ptr_dtor(&entry->metadata);
+ }
+ entry->metadata_len = 0;
+ entry->metadata = 0;
+ }
+
+ if (entry->metadata_str.c) {
+ smart_str_free(&entry->metadata_str);
+ entry->metadata_str.c = 0;
+ }
+
+ pefree(entry->filename, entry->is_persistent);
+
+ if (entry->link) {
+ pefree(entry->link, entry->is_persistent);
+ entry->link = 0;
+ }
+
+ if (entry->tmp) {
+ pefree(entry->tmp, entry->is_persistent);
+ entry->tmp = 0;
+ }
+}
+/* }}} */
+
+int phar_entry_delref(phar_entry_data *idata TSRMLS_DC) /* {{{ */
+{
+ int ret = 0;
+
+ if (idata->internal_file && !idata->internal_file->is_persistent) {
+ if (--idata->internal_file->fp_refcount < 0) {
+ idata->internal_file->fp_refcount = 0;
+ }
+
+ 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 phar_get_or_create_entry_data returns a sub-directory, we have to free it */
+ if (idata->internal_file->is_temp_dir) {
+ destroy_phar_manifest_entry((void *)idata->internal_file);
+ efree(idata->internal_file);
+ }
+ }
+
+ phar_archive_delref(idata->phar TSRMLS_CC);
+ efree(idata);
+ return ret;
+}
+/* }}} */
+
+/**
+ * Removes an entry, either by actually removing it or by marking it.
+ */
+void phar_entry_remove(phar_entry_data *idata, char **error TSRMLS_DC) /* {{{ */
+{
+ phar_archive_data *phar;
+
+ phar = idata->phar;
+
+ if (idata->internal_file->fp_refcount < 2) {
+ if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) {
+ php_stream_close(idata->fp);
+ }
+ zend_hash_del(&idata->phar->manifest, idata->internal_file->filename, idata->internal_file->filename_len);
+ idata->phar->refcount--;
+ efree(idata);
+ } else {
+ idata->internal_file->is_deleted = 1;
+ phar_entry_delref(idata TSRMLS_CC);
+ }
+
+ if (!phar->donotflush) {
+ phar_flush(phar, 0, 0, 0, error TSRMLS_CC);
+ }
+}
+/* }}} */
+
+#define MAPPHAR_ALLOC_FAIL(msg) \
+ if (fp) {\
+ php_stream_close(fp);\
+ }\
+ if (error) {\
+ spprintf(error, 0, msg, fname);\
+ }\
+ return FAILURE;
+
+#define MAPPHAR_FAIL(msg) \
+ efree(savebuf);\
+ if (mydata) {\
+ phar_destroy_phar_data(mydata TSRMLS_CC);\
+ }\
+ if (signature) {\
+ pefree(signature, PHAR_G(persist));\
+ }\
+ MAPPHAR_ALLOC_FAIL(msg)
+
+#ifdef WORDS_BIGENDIAN
+# define PHAR_GET_32(buffer, var) \
+ var = ((((unsigned char*)(buffer))[3]) << 24) \
+ | ((((unsigned char*)(buffer))[2]) << 16) \
+ | ((((unsigned char*)(buffer))[1]) << 8) \
+ | (((unsigned char*)(buffer))[0]); \
+ (buffer) += 4
+# define PHAR_GET_16(buffer, var) \
+ var = ((((unsigned char*)(buffer))[1]) << 8) \
+ | (((unsigned char*)(buffer))[0]); \
+ (buffer) += 2
+#else
+# define PHAR_GET_32(buffer, var) \
+ memcpy(&var, buffer, sizeof(var)); \
+ buffer += 4
+# define PHAR_GET_16(buffer, var) \
+ var = *(php_uint16*)(buffer); \
+ buffer += 2
+#endif
+#define PHAR_ZIP_16(var) ((php_uint16)((((php_uint16)var[0]) & 0xff) | \
+ (((php_uint16)var[1]) & 0xff) << 8))
+#define PHAR_ZIP_32(var) ((php_uint32)((((php_uint32)var[0]) & 0xff) | \
+ (((php_uint32)var[1]) & 0xff) << 8 | \
+ (((php_uint32)var[2]) & 0xff) << 16 | \
+ (((php_uint32)var[3]) & 0xff) << 24))
+
+/**
+ * Open an already loaded phar
+ */
+int phar_open_parsed_phar(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */
+{
+ phar_archive_data *phar;
+#ifdef PHP_WIN32
+ char *unixfname;
+#endif
+
+ if (error) {
+ *error = NULL;
+ }
+#ifdef PHP_WIN32
+ unixfname = estrndup(fname, fname_len);
+ phar_unixify_path_separators(unixfname, fname_len);
+
+ if (SUCCESS == phar_get_archive(&phar, unixfname, fname_len, alias, alias_len, error TSRMLS_CC)
+ && ((alias && fname_len == phar->fname_len
+ && !strncmp(unixfname, phar->fname, fname_len)) || !alias)
+ ) {
+ phar_entry_info *stub;
+ efree(unixfname);
+#else
+ if (SUCCESS == phar_get_archive(&phar, fname, fname_len, alias, alias_len, error TSRMLS_CC)
+ && ((alias && fname_len == phar->fname_len
+ && !strncmp(fname, phar->fname, fname_len)) || !alias)
+ ) {
+ phar_entry_info *stub;
+#endif
+ /* logic above is as follows:
+ If an explicit alias was requested, ensure the filename passed in
+ matches the phar's filename.
+ If no alias was passed in, then it can match either and be valid
+ */
+
+ if (!is_data) {
+ /* prevent any ".phar" without a stub getting through */
+ if (!phar->halt_offset && !phar->is_brandnew && (phar->is_tar || phar->is_zip)) {
+ if (PHAR_G(readonly) && FAILURE == zend_hash_find(&(phar->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1, (void **)&stub)) {
+ if (error) {
+ spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname);
+ }
+ return FAILURE;
+ }
+ }
+ }
+
+ if (pphar) {
+ *pphar = phar;
+ }
+
+ return SUCCESS;
+ } else {
+#ifdef PHP_WIN32
+ efree(unixfname);
+#endif
+ if (pphar) {
+ *pphar = NULL;
+ }
+
+ if (phar && error && !(options & REPORT_ERRORS)) {
+ efree(error);
+ }
+
+ return FAILURE;
+ }
+}
+/* }}}*/
+
+/**
+ * Parse out metadata from the manifest for a single file
+ *
+ * Meta-data is in this format:
+ * [len32][data...]
+ *
+ * data is the serialized zval
+ */
+int phar_parse_metadata(char **buffer, zval **metadata, int zip_metadata_len TSRMLS_DC) /* {{{ */
+{
+ const unsigned char *p;
+ php_uint32 buf_len;
+ php_unserialize_data_t var_hash;
+
+ if (!zip_metadata_len) {
+ PHAR_GET_32(*buffer, buf_len);
+ } else {
+ buf_len = zip_metadata_len;
+ }
+
+ if (buf_len) {
+ ALLOC_ZVAL(*metadata);
+ INIT_ZVAL(**metadata);
+ p = (const unsigned char*) *buffer;
+ PHP_VAR_UNSERIALIZE_INIT(var_hash);
+
+ if (!php_var_unserialize(metadata, &p, p + buf_len, &var_hash TSRMLS_CC)) {
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+ zval_ptr_dtor(metadata);
+ *metadata = NULL;
+ return FAILURE;
+ }
+
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+
+ if (PHAR_G(persist)) {
+ /* lazy init metadata */
+ zval_ptr_dtor(metadata);
+ *metadata = (zval *) pemalloc(buf_len, 1);
+ memcpy(*metadata, *buffer, buf_len);
+ *buffer += buf_len;
+ return SUCCESS;
+ }
+ } else {
+ *metadata = NULL;
+ }
+
+ if (!zip_metadata_len) {
+ *buffer += buf_len;
+ }
+
+ return SUCCESS;
+}
+/* }}}*/
+
+/**
+ * Does not check for a previously opened phar in the cache.
+ *
+ * Parse a new one and add it to the cache, returning either SUCCESS or
+ * FAILURE, and setting pphar to the pointer to the manifest entry
+ *
+ * This is used by phar_open_from_filename to process the manifest, but can be called
+ * directly.
+ */
+static int phar_parse_pharfile(php_stream *fp, char *fname, int fname_len, char *alias, int alias_len, long halt_offset, phar_archive_data** pphar, php_uint32 compression, char **error TSRMLS_DC) /* {{{ */
+{
+ char b32[4], *buffer, *endbuffer, *savebuf;
+ phar_archive_data *mydata = NULL;
+ phar_entry_info entry;
+ php_uint32 manifest_len, manifest_count, manifest_flags, manifest_index, tmp_len, sig_flags;
+ php_uint16 manifest_ver;
+ long offset;
+ int sig_len, register_alias = 0, temp_alias = 0;
+ char *signature = NULL;
+
+ if (pphar) {
+ *pphar = NULL;
+ }
+
+ if (error) {
+ *error = NULL;
+ }
+
+ /* check for ?>\n and increment accordingly */
+ if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) {
+ MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"")
+ }
+
+ buffer = b32;
+
+ if (3 != php_stream_read(fp, buffer, 3)) {
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
+ }
+
+ if ((*buffer == ' ' || *buffer == '\n') && *(buffer + 1) == '?' && *(buffer + 2) == '>') {
+ int nextchar;
+ halt_offset += 3;
+ if (EOF == (nextchar = php_stream_getc(fp))) {
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
+ }
+
+ if ((char) nextchar == '\r') {
+ /* if we have an \r we require an \n as well */
+ if (EOF == (nextchar = php_stream_getc(fp)) || (char)nextchar != '\n') {
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
+ }
+ ++halt_offset;
+ }
+
+ if ((char) nextchar == '\n') {
+ ++halt_offset;
+ }
+ }
+
+ /* make sure we are at the right location to read the manifest */
+ if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) {
+ MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"")
+ }
+
+ /* read in manifest */
+ buffer = b32;
+
+ if (4 != php_stream_read(fp, buffer, 4)) {
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at manifest length)")
+ }
+
+ PHAR_GET_32(buffer, manifest_len);
+
+ if (manifest_len > 1048576 * 100) {
+ /* prevent serious memory issues by limiting manifest to at most 100 MB in length */
+ MAPPHAR_ALLOC_FAIL("manifest cannot be larger than 100 MB in phar \"%s\"")
+ }
+
+ buffer = (char *)emalloc(manifest_len);
+ savebuf = buffer;
+ endbuffer = buffer + manifest_len;
+
+ if (manifest_len < 10 || manifest_len != php_stream_read(fp, buffer, manifest_len)) {
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
+ }
+
+ /* extract the number of entries */
+ PHAR_GET_32(buffer, manifest_count);
+
+ if (manifest_count == 0) {
+ MAPPHAR_FAIL("in phar \"%s\", manifest claims to have zero entries. Phars must have at least 1 entry");
+ }
+
+ /* extract API version, lowest nibble currently unused */
+ manifest_ver = (((unsigned char)buffer[0]) << 8)
+ + ((unsigned char)buffer[1]);
+ buffer += 2;
+
+ if ((manifest_ver & PHAR_API_VER_MASK) < PHAR_API_MIN_READ) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" is API version %1.u.%1.u.%1.u, and cannot be processed", fname, manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0x0F);
+ }
+ return FAILURE;
+ }
+
+ PHAR_GET_32(buffer, manifest_flags);
+
+ manifest_flags &= ~PHAR_HDR_COMPRESSION_MASK;
+ manifest_flags &= ~PHAR_FILE_COMPRESSION_MASK;
+ /* remember whether this entire phar was compressed with gz/bzip2 */
+ manifest_flags |= compression;
+
+ /* The lowest nibble contains the phar wide flags. The compression flags can */
+ /* be ignored on reading because it is being generated anyways. */
+ if (manifest_flags & PHAR_HDR_SIGNATURE) {
+ char sig_buf[8], *sig_ptr = sig_buf;
+ off_t read_len;
+ size_t end_of_phar;
+
+ if (-1 == php_stream_seek(fp, -8, SEEK_END)
+ || (read_len = php_stream_tell(fp)) < 20
+ || 8 != php_stream_read(fp, sig_buf, 8)
+ || memcmp(sig_buf+4, "GBMB", 4)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
+ }
+ return FAILURE;
+ }
+
+ PHAR_GET_32(sig_ptr, sig_flags);
+
+ switch(sig_flags) {
+ case PHAR_SIG_OPENSSL: {
+ php_uint32 signature_len;
+ char *sig;
+ off_t whence;
+
+ /* we store the signature followed by the signature length */
+ if (-1 == php_stream_seek(fp, -12, SEEK_CUR)
+ || 4 != php_stream_read(fp, sig_buf, 4)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" openssl signature length could not be read", fname);
+ }
+ return FAILURE;
+ }
+
+ sig_ptr = sig_buf;
+ PHAR_GET_32(sig_ptr, signature_len);
+ sig = (char *) emalloc(signature_len);
+ whence = signature_len + 4;
+ whence = -whence;
+
+ if (-1 == php_stream_seek(fp, whence, SEEK_CUR)
+ || !(end_of_phar = php_stream_tell(fp))
+ || signature_len != php_stream_read(fp, sig, signature_len)) {
+ efree(savebuf);
+ efree(sig);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" openssl signature could not be read", fname);
+ }
+ return FAILURE;
+ }
+
+ if (FAILURE == phar_verify_signature(fp, end_of_phar, PHAR_SIG_OPENSSL, sig, signature_len, fname, &signature, &sig_len, error TSRMLS_CC)) {
+ efree(savebuf);
+ efree(sig);
+ php_stream_close(fp);
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar \"%s\" openssl signature could not be verified: %s", fname, *error);
+ efree(save);
+ }
+ return FAILURE;
+ }
+ efree(sig);
+ }
+ break;
+#if PHAR_HASH_OK
+ case PHAR_SIG_SHA512: {
+ unsigned char digest[64];
+
+ php_stream_seek(fp, -(8 + 64), SEEK_END);
+ read_len = php_stream_tell(fp);
+
+ if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
+ }
+ return FAILURE;
+ }
+
+ if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA512, (char *)digest, 64, fname, &signature, &sig_len, error TSRMLS_CC)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar \"%s\" SHA512 signature could not be verified: %s", fname, *error);
+ efree(save);
+ }
+ return FAILURE;
+ }
+ break;
+ }
+ case PHAR_SIG_SHA256: {
+ unsigned char digest[32];
+
+ php_stream_seek(fp, -(8 + 32), SEEK_END);
+ read_len = php_stream_tell(fp);
+
+ if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
+ }
+ return FAILURE;
+ }
+
+ if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA256, (char *)digest, 32, fname, &signature, &sig_len, error TSRMLS_CC)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar \"%s\" SHA256 signature could not be verified: %s", fname, *error);
+ efree(save);
+ }
+ return FAILURE;
+ }
+ break;
+ }
+#else
+ case PHAR_SIG_SHA512:
+ case PHAR_SIG_SHA256:
+ efree(savebuf);
+ php_stream_close(fp);
+
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a unsupported signature", fname);
+ }
+ return FAILURE;
+#endif
+ case PHAR_SIG_SHA1: {
+ unsigned char digest[20];
+
+ php_stream_seek(fp, -(8 + 20), SEEK_END);
+ read_len = php_stream_tell(fp);
+
+ if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
+ }
+ return FAILURE;
+ }
+
+ if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA1, (char *)digest, 20, fname, &signature, &sig_len, error TSRMLS_CC)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar \"%s\" SHA1 signature could not be verified: %s", fname, *error);
+ efree(save);
+ }
+ return FAILURE;
+ }
+ break;
+ }
+ case PHAR_SIG_MD5: {
+ unsigned char digest[16];
+
+ php_stream_seek(fp, -(8 + 16), SEEK_END);
+ read_len = php_stream_tell(fp);
+
+ if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
+ }
+ return FAILURE;
+ }
+
+ if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_MD5, (char *)digest, 16, fname, &signature, &sig_len, error TSRMLS_CC)) {
+ efree(savebuf);
+ php_stream_close(fp);
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar \"%s\" MD5 signature could not be verified: %s", fname, *error);
+ efree(save);
+ }
+ return FAILURE;
+ }
+ break;
+ }
+ default:
+ efree(savebuf);
+ php_stream_close(fp);
+
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" has a broken or unsupported signature", fname);
+ }
+ return FAILURE;
+ }
+ } else if (PHAR_G(require_hash)) {
+ efree(savebuf);
+ php_stream_close(fp);
+
+ if (error) {
+ spprintf(error, 0, "phar \"%s\" does not have a signature", fname);
+ }
+ return FAILURE;
+ } else {
+ sig_flags = 0;
+ sig_len = 0;
+ }
+
+ /* extract alias */
+ PHAR_GET_32(buffer, tmp_len);
+
+ if (buffer + tmp_len > endbuffer) {
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (buffer overrun)");
+ }
+
+ if (manifest_len < 10 + tmp_len) {
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
+ }
+
+ /* tmp_len = 0 says alias length is 0, which means the alias is not stored in the phar */
+ if (tmp_len) {
+ /* if the alias is stored we enforce it (implicit overrides explicit) */
+ if (alias && alias_len && (alias_len != (int)tmp_len || strncmp(alias, buffer, tmp_len)))
+ {
+ buffer[tmp_len] = '\0';
+ php_stream_close(fp);
+
+ if (signature) {
+ efree(signature);
+ }
+
+ if (error) {
+ spprintf(error, 0, "cannot load phar \"%s\" with implicit alias \"%s\" under different alias \"%s\"", fname, buffer, alias);
+ }
+
+ efree(savebuf);
+ return FAILURE;
+ }
+
+ alias_len = tmp_len;
+ alias = buffer;
+ buffer += tmp_len;
+ register_alias = 1;
+ } else if (!alias_len || !alias) {
+ /* if we neither have an explicit nor an implicit alias, we use the filename */
+ alias = NULL;
+ alias_len = 0;
+ register_alias = 0;
+ } else if (alias_len) {
+ register_alias = 1;
+ temp_alias = 1;
+ }
+
+ /* we have 5 32-bit items plus 1 byte at least */
+ if (manifest_count > ((manifest_len - 10 - tmp_len) / (5 * 4 + 1))) {
+ /* prevent serious memory issues */
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)")
+ }
+
+ mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist));
+ mydata->is_persistent = PHAR_G(persist);
+
+ /* check whether we have meta data, zero check works regardless of byte order */
+ if (mydata->is_persistent) {
+ PHAR_GET_32(buffer, mydata->metadata_len);
+ if (phar_parse_metadata(&buffer, &mydata->metadata, mydata->metadata_len TSRMLS_CC) == FAILURE) {
+ MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\"");
+ }
+ } else {
+ if (phar_parse_metadata(&buffer, &mydata->metadata, 0 TSRMLS_CC) == FAILURE) {
+ MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\"");
+ }
+ }
+
+ /* set up our manifest */
+ zend_hash_init(&mydata->manifest, manifest_count,
+ zend_get_hash_value, destroy_phar_manifest_entry, (zend_bool)mydata->is_persistent);
+ zend_hash_init(&mydata->mounted_dirs, 5,
+ zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent);
+ zend_hash_init(&mydata->virtual_dirs, manifest_count * 2,
+ zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent);
+ mydata->fname = pestrndup(fname, fname_len, mydata->is_persistent);
+#ifdef PHP_WIN32
+ phar_unixify_path_separators(mydata->fname, fname_len);
+#endif
+ mydata->fname_len = fname_len;
+ offset = halt_offset + manifest_len + 4;
+ memset(&entry, 0, sizeof(phar_entry_info));
+ entry.phar = mydata;
+ entry.fp_type = PHAR_FP;
+ entry.is_persistent = mydata->is_persistent;
+
+ for (manifest_index = 0; manifest_index < manifest_count; ++manifest_index) {
+ if (buffer + 4 > endbuffer) {
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)")
+ }
+
+ PHAR_GET_32(buffer, entry.filename_len);
+
+ if (entry.filename_len == 0) {
+ MAPPHAR_FAIL("zero-length filename encountered in phar \"%s\"");
+ }
+
+ if (entry.is_persistent) {
+ entry.manifest_pos = manifest_index;
+ }
+
+ if (buffer + entry.filename_len + 20 > endbuffer) {
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)");
+ }
+
+ if ((manifest_ver & PHAR_API_VER_MASK) >= PHAR_API_MIN_DIR && buffer[entry.filename_len - 1] == '/') {
+ entry.is_dir = 1;
+ } else {
+ entry.is_dir = 0;
+ }
+
+ phar_add_virtual_dirs(mydata, buffer, entry.filename_len TSRMLS_CC);
+ entry.filename = pestrndup(buffer, entry.filename_len, entry.is_persistent);
+ buffer += entry.filename_len;
+ PHAR_GET_32(buffer, entry.uncompressed_filesize);
+ PHAR_GET_32(buffer, entry.timestamp);
+
+ if (offset == halt_offset + (int)manifest_len + 4) {
+ mydata->min_timestamp = entry.timestamp;
+ mydata->max_timestamp = entry.timestamp;
+ } else {
+ if (mydata->min_timestamp > entry.timestamp) {
+ mydata->min_timestamp = entry.timestamp;
+ } else if (mydata->max_timestamp < entry.timestamp) {
+ mydata->max_timestamp = entry.timestamp;
+ }
+ }
+
+ 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 (entry.is_persistent) {
+ PHAR_GET_32(buffer, entry.metadata_len);
+ if (!entry.metadata_len) buffer -= 4;
+ if (phar_parse_metadata(&buffer, &entry.metadata, entry.metadata_len TSRMLS_CC) == FAILURE) {
+ pefree(entry.filename, entry.is_persistent);
+ MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\"");
+ }
+ } else {
+ if (phar_parse_metadata(&buffer, &entry.metadata, 0 TSRMLS_CC) == FAILURE) {
+ pefree(entry.filename, entry.is_persistent);
+ MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\"");
+ }
+ }
+
+ entry.offset = entry.offset_abs = offset;
+ offset += entry.compressed_filesize;
+
+ switch (entry.flags & PHAR_ENT_COMPRESSION_MASK) {
+ case PHAR_ENT_COMPRESSED_GZ:
+ if (!PHAR_G(has_zlib)) {
+ if (entry.metadata) {
+ if (entry.is_persistent) {
+ free(entry.metadata);
+ } else {
+ zval_ptr_dtor(&entry.metadata);
+ }
+ }
+ pefree(entry.filename, entry.is_persistent);
+ MAPPHAR_FAIL("zlib extension is required for gz compressed .phar file \"%s\"");
+ }
+ break;
+ case PHAR_ENT_COMPRESSED_BZ2:
+ if (!PHAR_G(has_bz2)) {
+ if (entry.metadata) {
+ if (entry.is_persistent) {
+ free(entry.metadata);
+ } else {
+ zval_ptr_dtor(&entry.metadata);
+ }
+ }
+ pefree(entry.filename, entry.is_persistent);
+ MAPPHAR_FAIL("bz2 extension is required for bzip2 compressed .phar file \"%s\"");
+ }
+ break;
+ default:
+ if (entry.uncompressed_filesize != entry.compressed_filesize) {
+ if (entry.metadata) {
+ if (entry.is_persistent) {
+ free(entry.metadata);
+ } else {
+ zval_ptr_dtor(&entry.metadata);
+ }
+ }
+ pefree(entry.filename, entry.is_persistent);
+ MAPPHAR_FAIL("internal corruption of phar \"%s\" (compressed and uncompressed size does not match for uncompressed entry)");
+ }
+ break;
+ }
+
+ 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);
+ phar_set_inode(&entry TSRMLS_CC);
+ zend_hash_add(&mydata->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info), NULL);
+ }
+
+ snprintf(mydata->version, sizeof(mydata->version), "%u.%u.%u", manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0xF);
+ mydata->internal_file_start = halt_offset + manifest_len + 4;
+ mydata->halt_offset = halt_offset;
+ mydata->flags = manifest_flags;
+ endbuffer = strrchr(mydata->fname, '/');
+
+ if (endbuffer) {
+ mydata->ext = memchr(endbuffer, '.', (mydata->fname + fname_len) - endbuffer);
+ if (mydata->ext == endbuffer) {
+ mydata->ext = memchr(endbuffer + 1, '.', (mydata->fname + fname_len) - endbuffer - 1);
+ }
+ if (mydata->ext) {
+ mydata->ext_len = (mydata->fname + mydata->fname_len) - mydata->ext;
+ }
+ }
+
+ mydata->alias = alias ?
+ pestrndup(alias, alias_len, mydata->is_persistent) :
+ pestrndup(mydata->fname, fname_len, mydata->is_persistent);
+ mydata->alias_len = alias ? alias_len : fname_len;
+ mydata->sig_flags = sig_flags;
+ mydata->fp = fp;
+ mydata->sig_len = sig_len;
+ mydata->signature = signature;
+ phar_request_initialize(TSRMLS_C);
+
+ if (register_alias) {
+ phar_archive_data **fd_ptr;
+
+ mydata->is_temporary_alias = temp_alias;
+
+ if (!phar_validate_alias(mydata->alias, mydata->alias_len)) {
+ signature = NULL;
+ fp = NULL;
+ MAPPHAR_FAIL("Cannot open archive \"%s\", invalid alias");
+ }
+
+ if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void **)&fd_ptr)) {
+ if (SUCCESS != phar_free_alias(*fd_ptr, alias, alias_len TSRMLS_CC)) {
+ signature = NULL;
+ fp = NULL;
+ MAPPHAR_FAIL("Cannot open archive \"%s\", alias is already in use by existing archive");
+ }
+ }
+
+ zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL);
+ } else {
+ mydata->is_temporary_alias = 1;
+ }
+
+ zend_hash_add(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len, (void*)&mydata, sizeof(phar_archive_data*), NULL);
+ efree(savebuf);
+
+ if (pphar) {
+ *pphar = mydata;
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+/**
+ * Create or open a phar for writing
+ */
+int phar_open_or_create_filename(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */
+{
+ const char *ext_str, *z;
+ char *my_error;
+ int ext_len;
+ phar_archive_data **test, *unused = NULL;
+
+ test = &unused;
+
+ if (error) {
+ *error = NULL;
+ }
+
+ /* first try to open an existing file */
+ if (phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 0, 1 TSRMLS_CC) == SUCCESS) {
+ goto check_file;
+ }
+
+ /* next try to create a new file */
+ if (FAILURE == phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 1, 1 TSRMLS_CC)) {
+ if (error) {
+ if (ext_len == -2) {
+ spprintf(error, 0, "Cannot create a phar archive from a URL like \"%s\". Phar objects can only be created from local files", fname);
+ } else {
+ spprintf(error, 0, "Cannot create phar '%s', file extension (or combination) not recognised or the directory does not exist", fname);
+ }
+ }
+ return FAILURE;
+ }
+check_file:
+ if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, test, &my_error TSRMLS_CC) == SUCCESS) {
+ if (pphar) {
+ *pphar = *test;
+ }
+
+ if ((*test)->is_data && !(*test)->is_tar && !(*test)->is_zip) {
+ if (error) {
+ spprintf(error, 0, "Cannot open '%s' as a PharData object. Use Phar::__construct() for executable archives", fname);
+ }
+ return FAILURE;
+ }
+
+ if (PHAR_G(readonly) && !(*test)->is_data && ((*test)->is_tar || (*test)->is_zip)) {
+ phar_entry_info *stub;
+ if (FAILURE == zend_hash_find(&((*test)->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1, (void **)&stub)) {
+ spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname);
+ return FAILURE;
+ }
+ }
+
+ if (!PHAR_G(readonly) || (*test)->is_data) {
+ (*test)->is_writeable = 1;
+ }
+ return SUCCESS;
+ } else if (my_error) {
+ if (error) {
+ *error = my_error;
+ } else {
+ efree(my_error);
+ }
+ return FAILURE;
+ }
+
+ if (ext_len > 3 && (z = memchr(ext_str, 'z', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ip", 2)) {
+ /* assume zip-based phar */
+ return phar_open_or_create_zip(fname, fname_len, alias, alias_len, is_data, options, pphar, error TSRMLS_CC);
+ }
+
+ if (ext_len > 3 && (z = memchr(ext_str, 't', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ar", 2)) {
+ /* assume tar-based phar */
+ return phar_open_or_create_tar(fname, fname_len, alias, alias_len, is_data, options, pphar, error TSRMLS_CC);
+ }
+
+ return phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, pphar, error TSRMLS_CC);
+}
+/* }}} */
+
+int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error TSRMLS_DC) /* {{{ */
+{
+ phar_archive_data *mydata;
+ php_stream *fp;
+ char *actual = NULL, *p;
+
+ if (!pphar) {
+ pphar = &mydata;
+ }
+#if PHP_API_VERSION < 20100412
+ if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) {
+ return FAILURE;
+ }
+#endif
+ if (php_check_open_basedir(fname TSRMLS_CC)) {
+ return FAILURE;
+ }
+
+ /* 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, &actual);
+
+ if (actual) {
+ fname = actual;
+ fname_len = strlen(actual);
+ }
+
+ if (fp) {
+ if (phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error TSRMLS_CC) == SUCCESS) {
+ if ((*pphar)->is_data || !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 */
+ if (actual) {
+ efree(actual);
+ }
+ return FAILURE;
+ }
+ }
+
+ if (actual) {
+ efree(actual);
+ }
+
+ if (PHAR_G(readonly) && !is_data) {
+ if (options & REPORT_ERRORS) {
+ if (error) {
+ spprintf(error, 0, "creating archive \"%s\" disabled by the php.ini setting phar.readonly", fname);
+ }
+ }
+ return FAILURE;
+ }
+
+ /* set up our manifest */
+ mydata = ecalloc(1, sizeof(phar_archive_data));
+ mydata->fname = expand_filepath(fname, NULL TSRMLS_CC);
+ fname_len = strlen(mydata->fname);
+#ifdef PHP_WIN32
+ phar_unixify_path_separators(mydata->fname, fname_len);
+#endif
+ p = strrchr(mydata->fname, '/');
+
+ if (p) {
+ mydata->ext = memchr(p, '.', (mydata->fname + fname_len) - p);
+ if (mydata->ext == p) {
+ mydata->ext = memchr(p + 1, '.', (mydata->fname + fname_len) - p - 1);
+ }
+ if (mydata->ext) {
+ mydata->ext_len = (mydata->fname + fname_len) - mydata->ext;
+ }
+ }
+
+ if (pphar) {
+ *pphar = mydata;
+ }
+
+ zend_hash_init(&mydata->manifest, sizeof(phar_entry_info),
+ zend_get_hash_value, destroy_phar_manifest_entry, 0);
+ zend_hash_init(&mydata->mounted_dirs, sizeof(char *),
+ zend_get_hash_value, NULL, 0);
+ zend_hash_init(&mydata->virtual_dirs, sizeof(char *),
+ zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent);
+ mydata->fname_len = fname_len;
+ snprintf(mydata->version, sizeof(mydata->version), "%s", PHP_PHAR_API_VERSION);
+ mydata->is_temporary_alias = alias ? 0 : 1;
+ mydata->internal_file_start = -1;
+ mydata->fp = NULL;
+ mydata->is_writeable = 1;
+ mydata->is_brandnew = 1;
+ phar_request_initialize(TSRMLS_C);
+ zend_hash_add(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len, (void*)&mydata, sizeof(phar_archive_data*), NULL);
+
+ if (is_data) {
+ alias = NULL;
+ alias_len = 0;
+ mydata->is_data = 1;
+ /* assume tar format, PharData can specify other */
+ mydata->is_tar = 1;
+ } else {
+ phar_archive_data **fd_ptr;
+
+ if (alias && SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void **)&fd_ptr)) {
+ if (SUCCESS != phar_free_alias(*fd_ptr, alias, alias_len TSRMLS_CC)) {
+ if (error) {
+ spprintf(error, 4096, "phar error: phar \"%s\" cannot set alias \"%s\", already in use by another phar archive", mydata->fname, alias);
+ }
+
+ zend_hash_del(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len);
+
+ if (pphar) {
+ *pphar = NULL;
+ }
+
+ return FAILURE;
+ }
+ }
+
+ mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len);
+ mydata->alias_len = alias ? alias_len : fname_len;
+ }
+
+ if (alias_len && alias) {
+ if (FAILURE == zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), alias, alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL)) {
+ if (options & REPORT_ERRORS) {
+ if (error) {
+ spprintf(error, 0, "archive \"%s\" cannot be associated with alias \"%s\", already in use", fname, alias);
+ }
+ }
+
+ zend_hash_del(&(PHAR_GLOBALS->phar_fname_map), mydata->fname, fname_len);
+
+ if (pphar) {
+ *pphar = NULL;
+ }
+
+ return FAILURE;
+ }
+ }
+
+ return SUCCESS;
+}
+/* }}}*/
+
+/**
+ * Return an already opened filename.
+ *
+ * Or scan a phar file for the required __HALT_COMPILER(); ?> token and verify
+ * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
+ * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
+ */
+int phar_open_from_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, is_data = 0;
+
+ if (error) {
+ *error = NULL;
+ }
+
+ if (!strstr(fname, ".phar")) {
+ is_data = 1;
+ }
+
+ if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, pphar, error TSRMLS_CC) == SUCCESS) {
+ return SUCCESS;
+ } else if (error && *error) {
+ return FAILURE;
+ }
+#if PHP_API_VERSION < 20100412
+ if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) {
+ return FAILURE;
+ }
+#endif
+ if (php_check_open_basedir(fname TSRMLS_CC)) {
+ return FAILURE;
+ }
+
+ 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;
+ }
+
+ if (actual) {
+ fname = actual;
+ fname_len = strlen(actual);
+ }
+
+ ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error TSRMLS_CC);
+
+ if (actual) {
+ efree(actual);
+ }
+
+ return ret;
+}
+/* }}}*/
+
+static inline char *phar_strnstr(const char *buf, int buf_len, const char *search, int search_len) /* {{{ */
+{
+ const char *c;
+ int so_far = 0;
+
+ if (buf_len < search_len) {
+ return NULL;
+ }
+
+ c = buf - 1;
+
+ do {
+ if (!(c = memchr(c + 1, search[0], buf_len - search_len - so_far))) {
+ return (char *) NULL;
+ }
+
+ so_far = c - buf;
+
+ if (so_far >= (buf_len - search_len)) {
+ return (char *) NULL;
+ }
+
+ if (!memcmp(c, search, search_len)) {
+ return (char *) c;
+ }
+ } while (1);
+}
+/* }}} */
+
+/**
+ * Scan an open fp for the required __HALT_COMPILER(); ?> token and verify
+ * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
+ * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
+ */
+static int phar_open_from_fp(php_stream* fp, char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, int is_data, char **error TSRMLS_DC) /* {{{ */
+{
+ const char token[] = "__HALT_COMPILER();";
+ const char zip_magic[] = "PK\x03\x04";
+ const char gz_magic[] = "\x1f\x8b\x08";
+ const char bz_magic[] = "BZh";
+ char *pos, test = '\0';
+ const int window_size = 1024;
+ char buffer[1024 + sizeof(token)]; /* a 1024 byte window + the size of the halt_compiler token (moving window) */
+ const long readsize = sizeof(buffer) - sizeof(token);
+ const long tokenlen = sizeof(token) - 1;
+ long halt_offset;
+ size_t got;
+ php_uint32 compression = PHAR_FILE_COMPRESSED_NONE;
+
+ if (error) {
+ *error = NULL;
+ }
+
+ if (-1 == php_stream_rewind(fp)) {
+ MAPPHAR_ALLOC_FAIL("cannot rewind phar \"%s\"")
+ }
+
+ buffer[sizeof(buffer)-1] = '\0';
+ memset(buffer, 32, sizeof(token));
+ halt_offset = 0;
+
+ /* Maybe it's better to compile the file instead of just searching, */
+ /* but we only want the offset. So we want a .re scanner to find it. */
+ while(!php_stream_eof(fp)) {
+ if ((got = php_stream_read(fp, buffer+tokenlen, readsize)) < (size_t) tokenlen) {
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)")
+ }
+
+ if (!test) {
+ test = '\1';
+ pos = buffer+tokenlen;
+ if (!memcmp(pos, gz_magic, 3)) {
+ char err = 0;
+ php_stream_filter *filter;
+ php_stream *temp;
+ /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */
+ zval filterparams;
+
+ if (!PHAR_G(has_zlib)) {
+ MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file, enable zlib extension in php.ini")
+ }
+ array_init(&filterparams);
+/* this is defined in zlib's zconf.h */
+#ifndef MAX_WBITS
+#define MAX_WBITS 15
+#endif
+ add_assoc_long(&filterparams, "window", MAX_WBITS + 32);
+
+ /* entire file is gzip-compressed, uncompress to temporary file */
+ if (!(temp = php_stream_fopen_tmpfile())) {
+ MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of gzipped phar archive \"%s\"")
+ }
+
+ php_stream_rewind(fp);
+ filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp) TSRMLS_CC);
+
+ if (!filter) {
+ err = 1;
+ add_assoc_long(&filterparams, "window", MAX_WBITS);
+ filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp) TSRMLS_CC);
+ zval_dtor(&filterparams);
+
+ if (!filter) {
+ php_stream_close(temp);
+ MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
+ }
+ } else {
+ zval_dtor(&filterparams);
+ }
+
+ php_stream_filter_append(&temp->writefilters, filter);
+
+ if (SUCCESS != phar_stream_copy_to_stream(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
+ if (err) {
+ php_stream_close(temp);
+ MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
+ }
+ php_stream_close(temp);
+ MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file")
+ }
+
+ php_stream_filter_flush(filter, 1);
+ php_stream_filter_remove(filter, 1 TSRMLS_CC);
+ php_stream_close(fp);
+ fp = temp;
+ php_stream_rewind(fp);
+ compression = PHAR_FILE_COMPRESSED_GZ;
+
+ /* now, start over */
+ test = '\0';
+ continue;
+ } else if (!memcmp(pos, bz_magic, 3)) {
+ php_stream_filter *filter;
+ php_stream *temp;
+
+ if (!PHAR_G(has_bz2)) {
+ MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file, enable bz2 extension in php.ini")
+ }
+
+ /* entire file is bzip-compressed, uncompress to temporary file */
+ if (!(temp = php_stream_fopen_tmpfile())) {
+ MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of bzipped phar archive \"%s\"")
+ }
+
+ php_stream_rewind(fp);
+ filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp) TSRMLS_CC);
+
+ if (!filter) {
+ php_stream_close(temp);
+ MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\", filter creation failed")
+ }
+
+ php_stream_filter_append(&temp->writefilters, filter);
+
+ if (SUCCESS != phar_stream_copy_to_stream(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
+ php_stream_close(temp);
+ MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file")
+ }
+
+ php_stream_filter_flush(filter, 1);
+ php_stream_filter_remove(filter, 1 TSRMLS_CC);
+ php_stream_close(fp);
+ fp = temp;
+ php_stream_rewind(fp);
+ compression = PHAR_FILE_COMPRESSED_BZ2;
+
+ /* now, start over */
+ test = '\0';
+ continue;
+ }
+
+ if (!memcmp(pos, zip_magic, 4)) {
+ php_stream_seek(fp, 0, SEEK_END);
+ return phar_parse_zipfile(fp, fname, fname_len, alias, alias_len, pphar, error TSRMLS_CC);
+ }
+
+ if (got > 512) {
+ if (phar_is_tar(pos, fname)) {
+ php_stream_rewind(fp);
+ return phar_parse_tarfile(fp, fname, fname_len, alias, alias_len, pphar, is_data, compression, error TSRMLS_CC);
+ }
+ }
+ }
+
+ if (got > 0 && (pos = phar_strnstr(buffer, got + sizeof(token), token, sizeof(token)-1)) != NULL) {
+ halt_offset += (pos - buffer); /* no -tokenlen+tokenlen here */
+ return phar_parse_pharfile(fp, fname, fname_len, alias, alias_len, halt_offset, pphar, compression, error TSRMLS_CC);
+ }
+
+ halt_offset += got;
+ memmove(buffer, buffer + window_size, tokenlen); /* move the memory buffer by the size of the window */
+ }
+
+ MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (__HALT_COMPILER(); not found)")
+}
+/* }}} */
+
+/*
+ * given the location of the file extension and the start of the file path,
+ * determine the end of the portion of the path (i.e. /path/to/file.ext/blah
+ * grabs "/path/to/file.ext" as does the straight /path/to/file.ext),
+ * stat it to determine if it exists.
+ * if so, check to see if it is a directory and fail if so
+ * if not, check to see if its dirname() exists (i.e. "/path/to") and is a directory
+ * succeed if we are creating the file, otherwise fail.
+ */
+static int phar_analyze_path(const char *fname, const char *ext, int ext_len, int for_create TSRMLS_DC) /* {{{ */
+{
+ php_stream_statbuf ssb;
+ char *realpath;
+ char *filename = estrndup(fname, (ext - fname) + ext_len);
+
+ if ((realpath = expand_filepath(filename, NULL TSRMLS_CC))) {
+#ifdef PHP_WIN32
+ phar_unixify_path_separators(realpath, strlen(realpath));
+#endif
+ if (zend_hash_exists(&(PHAR_GLOBALS->phar_fname_map), realpath, strlen(realpath))) {
+ efree(realpath);
+ efree(filename);
+ return SUCCESS;
+ }
+
+ if (PHAR_G(manifest_cached) && zend_hash_exists(&cached_phars, realpath, strlen(realpath))) {
+ efree(realpath);
+ efree(filename);
+ return SUCCESS;
+ }
+ efree(realpath);
+ }
+
+ if (SUCCESS == php_stream_stat_path((char *) filename, &ssb)) {
+
+ efree(filename);
+
+ if (ssb.sb.st_mode & S_IFDIR) {
+ return FAILURE;
+ }
+
+ if (for_create == 1) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+ } else {
+ char *slash;
+
+ if (!for_create) {
+ efree(filename);
+ return FAILURE;
+ }
+
+ slash = (char *) strrchr(filename, '/');
+
+ if (slash) {
+ *slash = '\0';
+ }
+
+ if (SUCCESS != php_stream_stat_path((char *) filename, &ssb)) {
+ if (!slash) {
+ if (!(realpath = expand_filepath(filename, NULL TSRMLS_CC))) {
+ efree(filename);
+ return FAILURE;
+ }
+#ifdef PHP_WIN32
+ phar_unixify_path_separators(realpath, strlen(realpath));
+#endif
+ slash = strstr(realpath, filename) + ((ext - fname) + ext_len);
+ *slash = '\0';
+ slash = strrchr(realpath, '/');
+
+ if (slash) {
+ *slash = '\0';
+ } else {
+ efree(realpath);
+ efree(filename);
+ return FAILURE;
+ }
+
+ if (SUCCESS != php_stream_stat_path(realpath, &ssb)) {
+ efree(realpath);
+ efree(filename);
+ return FAILURE;
+ }
+
+ efree(realpath);
+
+ if (ssb.sb.st_mode & S_IFDIR) {
+ efree(filename);
+ return SUCCESS;
+ }
+ }
+
+ efree(filename);
+ return FAILURE;
+ }
+
+ efree(filename);
+
+ if (ssb.sb.st_mode & S_IFDIR) {
+ return SUCCESS;
+ }
+
+ return FAILURE;
+ }
+}
+/* }}} */
+
+/* check for ".phar" in extension */
+static int phar_check_str(const char *fname, const char *ext_str, int ext_len, int executable, int for_create TSRMLS_DC) /* {{{ */
+{
+ char test[51];
+ const char *pos;
+
+ if (ext_len >= 50) {
+ return FAILURE;
+ }
+
+ if (executable == 1) {
+ /* copy "." as well */
+ memcpy(test, ext_str - 1, ext_len + 1);
+ test[ext_len + 1] = '\0';
+ /* executable phars must contain ".phar" as a valid extension (phar://.pharmy/oops is invalid) */
+ /* (phar://hi/there/.phar/oops is also invalid) */
+ pos = strstr(test, ".phar");
+
+ if (pos && (*(pos - 1) != '/')
+ && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) {
+ return phar_analyze_path(fname, ext_str, ext_len, for_create TSRMLS_CC);
+ } else {
+ return FAILURE;
+ }
+ }
+
+ /* data phars need only contain a single non-"." to be valid */
+ if (!executable) {
+ pos = strstr(ext_str, ".phar");
+ if (!(pos && (*(pos - 1) != '/')
+ && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) && *(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
+ return phar_analyze_path(fname, ext_str, ext_len, for_create TSRMLS_CC);
+ }
+ } else {
+ if (*(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
+ return phar_analyze_path(fname, ext_str, ext_len, for_create TSRMLS_CC);
+ }
+ }
+
+ return FAILURE;
+}
+/* }}} */
+
+/*
+ * if executable is 1, only returns SUCCESS if the extension is one of the tar/zip .phar extensions
+ * if executable is 0, it returns SUCCESS only if the filename does *not* contain ".phar" anywhere, and treats
+ * the first extension as the filename extension
+ *
+ * if an extension is found, it sets ext_str to the location of the file extension in filename,
+ * and ext_len to the length of the extension.
+ * for urls like "phar://alias/oops" it instead sets ext_len to -1 and returns FAILURE, which tells
+ * the calling function to use "alias" as the phar alias
+ *
+ * the last parameter should be set to tell the thing to assume that filename is the full path, and only to check the
+ * extension rules, not to iterate.
+ */
+int phar_detect_phar_fname_ext(const char *filename, int filename_len, const char **ext_str, int *ext_len, int executable, int for_create, int is_complete TSRMLS_DC) /* {{{ */
+{
+ const char *pos, *slash;
+
+ *ext_str = NULL;
+ *ext_len = 0;
+
+ if (!filename_len || filename_len == 1) {
+ return FAILURE;
+ }
+
+ phar_request_initialize(TSRMLS_C);
+ /* first check for alias in first segment */
+ pos = memchr(filename, '/', filename_len);
+
+ if (pos && pos != filename) {
+ /* check for url like http:// or phar:// */
+ if (*(pos - 1) == ':' && (pos - filename) < filename_len - 1 && *(pos + 1) == '/') {
+ *ext_len = -2;
+ *ext_str = NULL;
+ return FAILURE;
+ }
+ if (zend_hash_exists(&(PHAR_GLOBALS->phar_alias_map), (char *) filename, pos - filename)) {
+ *ext_str = pos;
+ *ext_len = -1;
+ return FAILURE;
+ }
+
+ if (PHAR_G(manifest_cached) && zend_hash_exists(&cached_alias, (char *) filename, pos - filename)) {
+ *ext_str = pos;
+ *ext_len = -1;
+ return FAILURE;
+ }
+ }
+
+ if (zend_hash_num_elements(&(PHAR_GLOBALS->phar_fname_map)) || PHAR_G(manifest_cached)) {
+ phar_archive_data **pphar;
+
+ if (is_complete) {
+ if (SUCCESS == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), (char *) filename, filename_len, (void **)&pphar)) {
+ *ext_str = filename + (filename_len - (*pphar)->ext_len);
+woohoo:
+ *ext_len = (*pphar)->ext_len;
+
+ if (executable == 2) {
+ return SUCCESS;
+ }
+
+ if (executable == 1 && !(*pphar)->is_data) {
+ return SUCCESS;
+ }
+
+ if (!executable && (*pphar)->is_data) {
+ return SUCCESS;
+ }
+
+ return FAILURE;
+ }
+
+ if (PHAR_G(manifest_cached) && SUCCESS == zend_hash_find(&cached_phars, (char *) filename, filename_len, (void **)&pphar)) {
+ *ext_str = filename + (filename_len - (*pphar)->ext_len);
+ goto woohoo;
+ }
+ } else {
+ phar_zstr key;
+ char *str_key;
+ uint keylen;
+ ulong unused;
+
+ zend_hash_internal_pointer_reset(&(PHAR_GLOBALS->phar_fname_map));
+
+ while (FAILURE != zend_hash_has_more_elements(&(PHAR_GLOBALS->phar_fname_map))) {
+ if (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key_ex(&(PHAR_GLOBALS->phar_fname_map), &key, &keylen, &unused, 0, NULL)) {
+ break;
+ }
+
+ PHAR_STR(key, str_key);
+
+ if (keylen > (uint) filename_len) {
+ zend_hash_move_forward(&(PHAR_GLOBALS->phar_fname_map));
+ PHAR_STR_FREE(str_key);
+ continue;
+ }
+
+ if (!memcmp(filename, str_key, keylen) && ((uint)filename_len == keylen
+ || filename[keylen] == '/' || filename[keylen] == '\0')) {
+ PHAR_STR_FREE(str_key);
+ if (FAILURE == zend_hash_get_current_data(&(PHAR_GLOBALS->phar_fname_map), (void **) &pphar)) {
+ break;
+ }
+ *ext_str = filename + (keylen - (*pphar)->ext_len);
+ goto woohoo;
+ }
+
+ PHAR_STR_FREE(str_key);
+ zend_hash_move_forward(&(PHAR_GLOBALS->phar_fname_map));
+ }
+
+ if (PHAR_G(manifest_cached)) {
+ zend_hash_internal_pointer_reset(&cached_phars);
+
+ while (FAILURE != zend_hash_has_more_elements(&cached_phars)) {
+ if (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key_ex(&cached_phars, &key, &keylen, &unused, 0, NULL)) {
+ break;
+ }
+
+ PHAR_STR(key, str_key);
+
+ if (keylen > (uint) filename_len) {
+ zend_hash_move_forward(&cached_phars);
+ PHAR_STR_FREE(str_key);
+ continue;
+ }
+
+ if (!memcmp(filename, str_key, keylen) && ((uint)filename_len == keylen
+ || filename[keylen] == '/' || filename[keylen] == '\0')) {
+ PHAR_STR_FREE(str_key);
+ if (FAILURE == zend_hash_get_current_data(&cached_phars, (void **) &pphar)) {
+ break;
+ }
+ *ext_str = filename + (keylen - (*pphar)->ext_len);
+ goto woohoo;
+ }
+ PHAR_STR_FREE(str_key);
+ zend_hash_move_forward(&cached_phars);
+ }
+ }
+ }
+ }
+
+ pos = memchr(filename + 1, '.', filename_len);
+next_extension:
+ if (!pos) {
+ return FAILURE;
+ }
+
+ while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) {
+ pos = memchr(pos + 1, '.', filename_len - (pos - filename) + 1);
+ if (!pos) {
+ return FAILURE;
+ }
+ }
+
+ slash = memchr(pos, '/', filename_len - (pos - filename));
+
+ if (!slash) {
+ /* this is a url like "phar://blah.phar" with no directory */
+ *ext_str = pos;
+ *ext_len = strlen(pos);
+
+ /* file extension must contain "phar" */
+ switch (phar_check_str(filename, *ext_str, *ext_len, executable, for_create TSRMLS_CC)) {
+ case SUCCESS:
+ return SUCCESS;
+ case FAILURE:
+ /* we are at the end of the string, so we fail */
+ return FAILURE;
+ }
+ }
+
+ /* we've found an extension that ends at a directory separator */
+ *ext_str = pos;
+ *ext_len = slash - pos;
+
+ switch (phar_check_str(filename, *ext_str, *ext_len, executable, for_create TSRMLS_CC)) {
+ case SUCCESS:
+ return SUCCESS;
+ case FAILURE:
+ /* look for more extensions */
+ pos = strchr(pos + 1, '.');
+ if (pos) {
+ *ext_str = NULL;
+ *ext_len = 0;
+ }
+ goto next_extension;
+ }
+
+ return FAILURE;
+}
+/* }}} */
+
+static int php_check_dots(const char *element, int n) /* {{{ */
+{
+ for(n--; n >= 0; --n) {
+ if (element[n] != '.') {
+ return 1;
+ }
+ }
+ return 0;
+}
+/* }}} */
+
+#define IS_DIRECTORY_UP(element, len) \
+ (len >= 2 && !php_check_dots(element, len))
+
+#define IS_DIRECTORY_CURRENT(element, len) \
+ (len == 1 && element[0] == '.')
+
+#define IS_BACKSLASH(c) ((c) == '/')
+
+#ifdef COMPILE_DL_PHAR
+/* stupid-ass non-extern declaration in tsrm_strtok.h breaks dumbass MS compiler */
+static inline int in_character_class(char ch, const char *delim) /* {{{ */
+{
+ while (*delim) {
+ if (*delim == ch) {
+ return 1;
+ }
+ ++delim;
+ }
+ return 0;
+}
+/* }}} */
+
+char *tsrm_strtok_r(char *s, const char *delim, char **last) /* {{{ */
+{
+ char *token;
+
+ if (s == NULL) {
+ s = *last;
+ }
+
+ while (*s && in_character_class(*s, delim)) {
+ ++s;
+ }
+
+ if (!*s) {
+ return NULL;
+ }
+
+ token = s;
+
+ while (*s && !in_character_class(*s, delim)) {
+ ++s;
+ }
+
+ if (!*s) {
+ *last = s;
+ } else {
+ *s = '\0';
+ *last = s + 1;
+ }
+
+ return token;
+}
+/* }}} */
+#endif
+
+/**
+ * Remove .. and . references within a phar filename
+ */
+char *phar_fix_filepath(char *path, int *new_len, int use_cwd TSRMLS_DC) /* {{{ */
+{
+ char newpath[MAXPATHLEN];
+ int newpath_len;
+ char *ptr;
+ char *tok;
+ int ptr_length, path_length = *new_len;
+
+ if (PHAR_G(cwd_len) && use_cwd && path_length > 2 && path[0] == '.' && path[1] == '/') {
+ newpath_len = PHAR_G(cwd_len);
+ memcpy(newpath, PHAR_G(cwd), newpath_len);
+ } else {
+ newpath[0] = '/';
+ newpath_len = 1;
+ }
+
+ ptr = path;
+
+ if (*ptr == '/') {
+ ++ptr;
+ }
+
+ tok = ptr;
+
+ do {
+ ptr = memchr(ptr, '/', path_length - (ptr - path));
+ } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
+
+ if (!ptr && (path_length - (tok - path))) {
+ switch (path_length - (tok - path)) {
+ case 1:
+ if (*tok == '.') {
+ efree(path);
+ *new_len = 1;
+ return estrndup("/", 1);
+ }
+ break;
+ case 2:
+ if (tok[0] == '.' && tok[1] == '.') {
+ efree(path);
+ *new_len = 1;
+ return estrndup("/", 1);
+ }
+ }
+ return path;
+ }
+
+ while (ptr) {
+ ptr_length = ptr - tok;
+last_time:
+ if (IS_DIRECTORY_UP(tok, ptr_length)) {
+#define PREVIOUS newpath[newpath_len - 1]
+
+ while (newpath_len > 1 && !IS_BACKSLASH(PREVIOUS)) {
+ newpath_len--;
+ }
+
+ if (newpath[0] != '/') {
+ newpath[newpath_len] = '\0';
+ } else if (newpath_len > 1) {
+ --newpath_len;
+ }
+ } else if (!IS_DIRECTORY_CURRENT(tok, ptr_length)) {
+ if (newpath_len > 1) {
+ newpath[newpath_len++] = '/';
+ memcpy(newpath + newpath_len, tok, ptr_length+1);
+ } else {
+ memcpy(newpath + newpath_len, tok, ptr_length+1);
+ }
+
+ newpath_len += ptr_length;
+ }
+
+ if (ptr == path + path_length) {
+ break;
+ }
+
+ tok = ++ptr;
+
+ do {
+ ptr = memchr(ptr, '/', path_length - (ptr - path));
+ } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
+
+ if (!ptr && (path_length - (tok - path))) {
+ ptr_length = path_length - (tok - path);
+ ptr = path + path_length;
+ goto last_time;
+ }
+ }
+
+ efree(path);
+ *new_len = newpath_len;
+ return estrndup(newpath, newpath_len);
+}
+/* }}} */
+
+/**
+ * Process a phar stream name, ensuring we can handle any of:
+ *
+ * - whatever.phar
+ * - whatever.phar.gz
+ * - whatever.phar.bz2
+ * - whatever.phar.php
+ *
+ * Optionally the name might start with 'phar://'
+ *
+ * This is used by phar_parse_url()
+ */
+int phar_split_fname(char *filename, int filename_len, char **arch, int *arch_len, char **entry, int *entry_len, int executable, int for_create TSRMLS_DC) /* {{{ */
+{
+ const char *ext_str;
+#ifdef PHP_WIN32
+ char *save;
+#endif
+ int ext_len, free_filename = 0;
+
+ if (!strncasecmp(filename, "phar://", 7)) {
+ filename += 7;
+ filename_len -= 7;
+ }
+
+ ext_len = 0;
+#ifdef PHP_WIN32
+ free_filename = 1;
+ save = filename;
+ filename = estrndup(filename, filename_len);
+ phar_unixify_path_separators(filename, filename_len);
+#endif
+ if (phar_detect_phar_fname_ext(filename, filename_len, &ext_str, &ext_len, executable, for_create, 0 TSRMLS_CC) == FAILURE) {
+ if (ext_len != -1) {
+ if (!ext_str) {
+ /* no / detected, restore arch for error message */
+#ifdef PHP_WIN32
+ *arch = save;
+#else
+ *arch = filename;
+#endif
+ }
+
+ if (free_filename) {
+ efree(filename);
+ }
+
+ return FAILURE;
+ }
+
+ ext_len = 0;
+ /* no extension detected - instead we are dealing with an alias */
+ }
+
+ *arch_len = ext_str - filename + ext_len;
+ *arch = estrndup(filename, *arch_len);
+
+ if (ext_str[ext_len]) {
+ *entry_len = filename_len - *arch_len;
+ *entry = estrndup(ext_str+ext_len, *entry_len);
+#ifdef PHP_WIN32
+ phar_unixify_path_separators(*entry, *entry_len);
+#endif
+ *entry = phar_fix_filepath(*entry, entry_len, 0 TSRMLS_CC);
+ } else {
+ *entry_len = 1;
+ *entry = estrndup("/", 1);
+ }
+
+ if (free_filename) {
+ efree(filename);
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+/**
+ * Invoked when a user calls Phar::mapPhar() from within an executing .phar
+ * to set up its manifest directly
+ */
+int phar_open_executed_filename(char *alias, int alias_len, char **error TSRMLS_DC) /* {{{ */
+{
+ char *fname;
+ zval *halt_constant;
+ php_stream *fp;
+ int fname_len;
+ char *actual = NULL;
+ int ret;
+
+ if (error) {
+ *error = NULL;
+ }
+
+ fname = (char*)zend_get_executed_filename(TSRMLS_C);
+ fname_len = strlen(fname);
+
+ if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, 0, REPORT_ERRORS, NULL, 0 TSRMLS_CC) == SUCCESS) {
+ return SUCCESS;
+ }
+
+ if (!strcmp(fname, "[no active file]")) {
+ if (error) {
+ spprintf(error, 0, "cannot initialize a phar outside of PHP execution");
+ }
+ return FAILURE;
+ }
+
+ MAKE_STD_ZVAL(halt_constant);
+
+ if (0 == zend_get_constant("__COMPILER_HALT_OFFSET__", 24, halt_constant TSRMLS_CC)) {
+ FREE_ZVAL(halt_constant);
+ if (error) {
+ spprintf(error, 0, "__HALT_COMPILER(); must be declared in a phar");
+ }
+ return FAILURE;
+ }
+
+ FREE_ZVAL(halt_constant);
+
+#if PHP_API_VERSION < 20100412
+ if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) {
+ return FAILURE;
+ }
+#endif
+
+ if (php_check_open_basedir(fname TSRMLS_CC)) {
+ return FAILURE;
+ }
+
+ fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, &actual);
+
+ if (!fp) {
+ if (error) {
+ spprintf(error, 0, "unable to open phar for reading \"%s\"", fname);
+ }
+ if (actual) {
+ efree(actual);
+ }
+ return FAILURE;
+ }
+
+ if (actual) {
+ fname = actual;
+ fname_len = strlen(actual);
+ }
+
+ ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, REPORT_ERRORS, NULL, 0, error TSRMLS_CC);
+
+ if (actual) {
+ efree(actual);
+ }
+
+ return ret;
+}
+/* }}} */
+
+/**
+ * Validate the CRC32 of a file opened from within the phar
+ */
+int phar_postprocess_file(phar_entry_data *idata, php_uint32 crc32, char **error, int process_zip TSRMLS_DC) /* {{{ */
+{
+ php_uint32 crc = ~0;
+ int len = idata->internal_file->uncompressed_filesize;
+ php_stream *fp = idata->fp;
+ phar_entry_info *entry = idata->internal_file;
+
+ if (error) {
+ *error = NULL;
+ }
+
+ if (entry->is_zip && process_zip > 0) {
+ /* verify local file header */
+ phar_zip_file_header local;
+ phar_zip_data_desc desc;
+
+ 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(phar_get_entrypfp(idata->internal_file TSRMLS_CC), entry->header_offset, SEEK_SET);
+
+ if (sizeof(local) != php_stream_read(phar_get_entrypfp(idata->internal_file TSRMLS_CC), (char *) &local, sizeof(local))) {
+
+ 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;
+ }
+
+ /* check for data descriptor */
+ if (((PHAR_ZIP_16(local.flags)) & 0x8) == 0x8) {
+ php_stream_seek(phar_get_entrypfp(idata->internal_file TSRMLS_CC),
+ entry->header_offset + sizeof(local) +
+ PHAR_ZIP_16(local.filename_len) +
+ PHAR_ZIP_16(local.extra_len) +
+ entry->compressed_filesize, SEEK_SET);
+ if (sizeof(desc) != php_stream_read(phar_get_entrypfp(idata->internal_file TSRMLS_CC),
+ (char *) &desc, sizeof(desc))) {
+ spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local data descriptor for file \"%s\")", idata->phar->fname, entry->filename);
+ return FAILURE;
+ }
+ if (desc.signature[0] == 'P' && desc.signature[1] == 'K') {
+ memcpy(&(local.crc32), &(desc.crc32), 12);
+ } else {
+ /* old data descriptors have no signature */
+ memcpy(&(local.crc32), &desc, 12);
+ }
+ }
+ /* verify local header */
+ if (entry->filename_len != PHAR_ZIP_16(local.filename_len) || entry->crc32 != PHAR_ZIP_32(local.crc32) || entry->uncompressed_filesize != PHAR_ZIP_32(local.uncompsize) || entry->compressed_filesize != PHAR_ZIP_32(local.compsize)) {
+ spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (local header of file \"%s\" does not match central directory)", idata->phar->fname, entry->filename);
+ return FAILURE;
+ }
+
+ /* construct actual offset to file start - local extra_len can be different from central extra_len */
+ entry->offset = entry->offset_abs =
+ sizeof(local) + entry->header_offset + PHAR_ZIP_16(local.filename_len) + PHAR_ZIP_16(local.extra_len);
+
+ if (idata->zero && idata->zero != entry->offset_abs) {
+ idata->zero = entry->offset_abs;
+ }
+ }
+
+ if (process_zip == 1) {
+ return SUCCESS;
+ }
+
+ 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 {
+ spprintf(error, 0, "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) /* {{{ */
+{
+#ifdef WORDS_BIGENDIAN
+ *((buffer) + 3) = (unsigned char) (((var) >> 24) & 0xFF);
+ *((buffer) + 2) = (unsigned char) (((var) >> 16) & 0xFF);
+ *((buffer) + 1) = (unsigned char) (((var) >> 8) & 0xFF);
+ *((buffer) + 0) = (unsigned char) ((var) & 0xFF);
+#else
+ memcpy(buffer, &var, sizeof(var));
+#endif
+} /* }}} */
+
+static int phar_flush_clean_deleted_apply(void *data TSRMLS_DC) /* {{{ */
+{
+ phar_entry_info *entry = (phar_entry_info *)data;
+
+ if (entry->fp_refcount <= 0 && entry->is_deleted) {
+ return ZEND_HASH_APPLY_REMOVE;
+ } else {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+}
+/* }}} */
+
+#include "stub.h"
+
+char *phar_create_default_stub(const char *index_php, const char *web_index, size_t *len, char **error TSRMLS_DC) /* {{{ */
+{
+ char *stub = NULL;
+ int index_len, web_len;
+ size_t dummy;
+
+ if (!len) {
+ len = &dummy;
+ }
+
+ if (error) {
+ *error = NULL;
+ }
+
+ if (!index_php) {
+ index_php = "index.php";
+ }
+
+ if (!web_index) {
+ web_index = "index.php";
+ }
+
+ index_len = strlen(index_php);
+ web_len = strlen(web_index);
+
+ if (index_len > 400) {
+ /* ridiculous size not allowed for index.php startup filename */
+ if (error) {
+ spprintf(error, 0, "Illegal filename passed in for stub creation, was %d characters long, and only 400 or less is allowed", index_len);
+ return NULL;
+ }
+ }
+
+ if (web_len > 400) {
+ /* ridiculous size not allowed for index.php startup filename */
+ if (error) {
+ spprintf(error, 0, "Illegal web filename passed in for stub creation, was %d characters long, and only 400 or less is allowed", web_len);
+ return NULL;
+ }
+ }
+
+ phar_get_stub(index_php, web_index, len, &stub, index_len+1, web_len+1 TSRMLS_CC);
+ return stub;
+}
+/* }}} */
+
+/**
+ * Save phar contents to disk
+ *
+ * user_stub contains either a string, or a resource pointer, if len is a negative length.
+ * user_stub and len should be both 0 if the default or existing stub should be used
+ */
+int phar_flush(phar_archive_data *phar, char *user_stub, long len, int convert, char **error TSRMLS_DC) /* {{{ */
+{
+ char halt_stub[] = "__HALT_COMPILER();";
+ char *newstub, *tmp;
+ phar_entry_info *entry, *newentry;
+ int halt_offset, restore_alias_len, global_flags = 0, closeoldfile;
+ char *pos, has_dirs = 0;
+ char manifest[18], entry_buffer[24];
+ off_t manifest_ftell;
+ long offset;
+ size_t wrote;
+ php_uint32 manifest_len, mytime, loc, new_manifest_count;
+ php_uint32 newcrc32;
+ php_stream *file, *oldfile, *newfile, *stubfile;
+ php_stream_filter *filter;
+ php_serialize_data_t metadata_hash;
+ smart_str main_metadata_str = {0};
+ int free_user_stub, free_fp = 1, free_ufp = 1;
+
+ if (phar->is_persistent) {
+ if (error) {
+ spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
+ }
+ return EOF;
+ }
+
+ if (error) {
+ *error = NULL;
+ }
+
+ if (!zend_hash_num_elements(&phar->manifest) && !user_stub) {
+ return EOF;
+ }
+
+ zend_hash_clean(&phar->virtual_dirs);
+
+ if (phar->is_zip) {
+ return phar_zip_flush(phar, user_stub, len, convert, error TSRMLS_CC);
+ }
+
+ if (phar->is_tar) {
+ return phar_tar_flush(phar, user_stub, len, convert, error TSRMLS_CC);
+ }
+
+ if (PHAR_G(readonly)) {
+ 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;
+ }
+ newfile = php_stream_fopen_tmpfile();
+ if (!newfile) {
+ if (error) {
+ spprintf(error, 0, "unable to create temporary file");
+ }
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ return EOF;
+ }
+
+ if (user_stub) {
+ if (len < 0) {
+ /* resource passed in */
+ if (!(php_stream_from_zval_no_verify(stubfile, (zval **)user_stub))) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ spprintf(error, 0, "unable to access resource to copy stub to new phar \"%s\"", phar->fname);
+ }
+ return EOF;
+ }
+ if (len == -1) {
+ len = PHP_STREAM_COPY_ALL;
+ } else {
+ len = -len;
+ }
+ user_stub = 0;
+
+ if (!(len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)) || !user_stub) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ spprintf(error, 0, "unable to read resource to copy stub to new phar \"%s\"", phar->fname);
+ }
+ return EOF;
+ }
+ free_user_stub = 1;
+ } else {
+ free_user_stub = 0;
+ }
+ tmp = estrndup(user_stub, len);
+ if ((pos = php_stristr(tmp, halt_stub, len, sizeof(halt_stub) - 1)) == NULL) {
+ efree(tmp);
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ spprintf(error, 0, "illegal stub for phar \"%s\"", phar->fname);
+ }
+ if (free_user_stub) {
+ efree(user_stub);
+ }
+ return EOF;
+ }
+ pos = user_stub + (pos - tmp);
+ efree(tmp);
+ len = pos - user_stub + 18;
+ if ((size_t)len != php_stream_write(newfile, user_stub, len)
+ || 5 != php_stream_write(newfile, " ?>\r\n", 5)) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ spprintf(error, 0, "unable to create stub from string in new phar \"%s\"", phar->fname);
+ }
+ if (free_user_stub) {
+ efree(user_stub);
+ }
+ return EOF;
+ }
+ phar->halt_offset = len + 5;
+ if (free_user_stub) {
+ efree(user_stub);
+ }
+ } else {
+ size_t written;
+
+ if (!user_stub && phar->halt_offset && oldfile && !phar->is_brandnew) {
+ phar_stream_copy_to_stream(oldfile, newfile, phar->halt_offset, &written);
+ newstub = NULL;
+ } else {
+ /* this is either a brand new phar or a default stub overwrite */
+ newstub = phar_create_default_stub(NULL, NULL, &(phar->halt_offset), NULL TSRMLS_CC);
+ written = php_stream_write(newfile, newstub, phar->halt_offset);
+ }
+ if (phar->halt_offset != written) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ if (newstub) {
+ spprintf(error, 0, "unable to create stub in new phar \"%s\"", phar->fname);
+ } else {
+ spprintf(error, 0, "unable to copy stub of old phar to new phar \"%s\"", phar->fname);
+ }
+ }
+ if (newstub) {
+ efree(newstub);
+ }
+ return EOF;
+ }
+ if (newstub) {
+ efree(newstub);
+ }
+ }
+ manifest_ftell = php_stream_tell(newfile);
+ halt_offset = manifest_ftell;
+
+ /* Check whether we can get rid of some of the deleted entries which are
+ * unused. However some might still be in use so even after this clean-up
+ * we need to skip entries marked is_deleted. */
+ zend_hash_apply(&phar->manifest, phar_flush_clean_deleted_apply TSRMLS_CC);
+
+ /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */
+ main_metadata_str.c = 0;
+ if (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);
+ } else {
+ main_metadata_str.len = 0;
+ }
+ new_manifest_count = 0;
+ offset = 0;
+ for (zend_hash_internal_pointer_reset(&phar->manifest);
+ zend_hash_has_more_elements(&phar->manifest) == SUCCESS;
+ zend_hash_move_forward(&phar->manifest)) {
+ if (zend_hash_get_current_data(&phar->manifest, (void **)&entry) == FAILURE) {
+ continue;
+ }
+ if (entry->cfp) {
+ /* did we forget to get rid of cfp last time? */
+ php_stream_close(entry->cfp);
+ entry->cfp = 0;
+ }
+ if (entry->is_deleted || entry->is_mounted) {
+ /* remove this from the new phar */
+ continue;
+ }
+ if (!entry->is_modified && entry->fp_refcount) {
+ /* open file pointers refer to this fp, do not free the stream */
+ switch (entry->fp_type) {
+ case PHAR_FP:
+ free_fp = 0;
+ break;
+ case PHAR_UFP:
+ free_ufp = 0;
+ default:
+ break;
+ }
+ }
+ /* after excluding deleted files, calculate manifest size in bytes and number of entries */
+ ++new_manifest_count;
+ phar_add_virtual_dirs(phar, entry->filename, entry->filename_len TSRMLS_CC);
+
+ if (entry->is_dir) {
+ /* we use this to calculate API version, 1.1.1 is used for phars with directories */
+ has_dirs = 1;
+ }
+ if (entry->metadata) {
+ 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);
+ } else {
+ if (entry->metadata_str.c) {
+ smart_str_free(&entry->metadata_str);
+ }
+ entry->metadata_str.c = 0;
+ entry->metadata_str.len = 0;
+ }
+
+ /* 32 bits for filename length, length of filename, manifest + metadata, and add 1 for trailing / if a directory */
+ offset += 4 + entry->filename_len + sizeof(entry_buffer) + entry->metadata_str.len + (entry->is_dir ? 1 : 0);
+
+ /* compress and rehash as necessary */
+ if ((oldfile && !entry->is_modified) || entry->is_dir) {
+ if (entry->fp_type == PHAR_UFP) {
+ /* reset so we can copy the compressed data over */
+ entry->fp_type = PHAR_FP;
+ }
+ continue;
+ }
+ if (!phar_get_efp(entry, 0 TSRMLS_CC)) {
+ /* re-open internal file pointer just-in-time */
+ newentry = phar_open_jit(phar, entry, error TSRMLS_CC);
+ if (!newentry) {
+ /* major problem re-opening, so we ignore this file and the error */
+ efree(*error);
+ *error = NULL;
+ continue;
+ }
+ entry = newentry;
+ }
+ file = phar_get_efp(entry, 0 TSRMLS_CC);
+ if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 1 TSRMLS_CC)) {
+ 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;
+ }
+ newcrc32 = ~0;
+ mytime = entry->uncompressed_filesize;
+ for (loc = 0;loc < mytime; ++loc) {
+ CRC32(newcrc32, php_stream_getc(file));
+ }
+ entry->crc32 = ~newcrc32;
+ entry->is_crc_checked = 1;
+ if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
+ /* not compressed */
+ entry->compressed_filesize = entry->uncompressed_filesize;
+ continue;
+ }
+ filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0 TSRMLS_CC);
+ if (!filter) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
+ if (error) {
+ spprintf(error, 0, "unable to gzip compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
+ }
+ } else {
+ if (error) {
+ spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
+ }
+ }
+ return EOF;
+ }
+
+ /* 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) {
+ if (error) {
+ spprintf(error, 0, "unable to create temporary file");
+ }
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ return EOF;
+ }
+ php_stream_flush(file);
+ if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0 TSRMLS_CC)) {
+ 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;
+ }
+ php_stream_filter_append((&entry->cfp->writefilters), filter);
+ if (SUCCESS != phar_stream_copy_to_stream(file, entry->cfp, entry->uncompressed_filesize, NULL)) {
+ 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_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;
+ global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK);
+ }
+ global_flags |= PHAR_HDR_SIGNATURE;
+
+ /* write out manifest pre-header */
+ /* 4: manifest length
+ * 4: manifest entry count
+ * 2: phar version
+ * 4: phar global flags
+ * 4: alias length
+ * ?: the alias itself
+ * 4: phar metadata length
+ * ?: phar metadata
+ */
+ restore_alias_len = phar->alias_len;
+ if (phar->is_temporary_alias) {
+ phar->alias_len = 0;
+ }
+
+ manifest_len = offset + phar->alias_len + sizeof(manifest) + main_metadata_str.len;
+ phar_set_32(manifest, manifest_len);
+ phar_set_32(manifest+4, new_manifest_count);
+ if (has_dirs) {
+ *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION) >> 8) & 0xFF);
+ *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION) & 0xF0));
+ } else {
+ *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION_NODIR) >> 8) & 0xFF);
+ *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION_NODIR) & 0xF0));
+ }
+ phar_set_32(manifest+10, global_flags);
+ phar_set_32(manifest+14, phar->alias_len);
+
+ /* write the manifest header */
+ if (sizeof(manifest) != php_stream_write(newfile, manifest, sizeof(manifest))
+ || (size_t)phar->alias_len != php_stream_write(newfile, phar->alias, phar->alias_len)) {
+
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+
+ php_stream_close(newfile);
+ phar->alias_len = restore_alias_len;
+
+ if (error) {
+ spprintf(error, 0, "unable to write manifest header of new phar \"%s\"", phar->fname);
+ }
+
+ return EOF;
+ }
+
+ phar->alias_len = restore_alias_len;
+
+ phar_set_32(manifest, main_metadata_str.len);
+ if (4 != php_stream_write(newfile, manifest, 4) || (main_metadata_str.len
+ && main_metadata_str.len != php_stream_write(newfile, main_metadata_str.c, main_metadata_str.len))) {
+ smart_str_free(&main_metadata_str);
+
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+
+ php_stream_close(newfile);
+ phar->alias_len = restore_alias_len;
+
+ if (error) {
+ spprintf(error, 0, "unable to write manifest meta-data of new phar \"%s\"", phar->fname);
+ }
+
+ return EOF;
+ }
+ smart_str_free(&main_metadata_str);
+
+ /* re-calculate the manifest location to simplify later code */
+ manifest_ftell = php_stream_tell(newfile);
+
+ /* now write the manifest */
+ for (zend_hash_internal_pointer_reset(&phar->manifest);
+ zend_hash_has_more_elements(&phar->manifest) == SUCCESS;
+ zend_hash_move_forward(&phar->manifest)) {
+
+ if (zend_hash_get_current_data(&phar->manifest, (void **)&entry) == FAILURE) {
+ continue;
+ }
+
+ if (entry->is_deleted || entry->is_mounted) {
+ /* remove this from the new phar if deleted, ignore if mounted */
+ continue;
+ }
+
+ if (entry->is_dir) {
+ /* add 1 for trailing slash */
+ phar_set_32(entry_buffer, entry->filename_len + 1);
+ } else {
+ phar_set_32(entry_buffer, entry->filename_len);
+ }
+
+ if (4 != php_stream_write(newfile, entry_buffer, 4)
+ || entry->filename_len != php_stream_write(newfile, entry->filename, entry->filename_len)
+ || (entry->is_dir && 1 != php_stream_write(newfile, "/", 1))) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ if (entry->is_dir) {
+ spprintf(error, 0, "unable to write filename of directory \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
+ } else {
+ spprintf(error, 0, "unable to write filename of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
+ }
+ }
+ return EOF;
+ }
+
+ /* set the manifest meta-data:
+ 4: uncompressed filesize
+ 4: creation timestamp
+ 4: compressed filesize
+ 4: crc32
+ 4: flags
+ 4: metadata-len
+ +: metadata
+ */
+ mytime = time(NULL);
+ phar_set_32(entry_buffer, entry->uncompressed_filesize);
+ phar_set_32(entry_buffer+4, mytime);
+ phar_set_32(entry_buffer+8, entry->compressed_filesize);
+ phar_set_32(entry_buffer+12, entry->crc32);
+ phar_set_32(entry_buffer+16, entry->flags);
+ phar_set_32(entry_buffer+20, entry->metadata_str.len);
+
+ if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer))
+ || entry->metadata_str.len != php_stream_write(newfile, entry->metadata_str.c, entry->metadata_str.len)) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+
+ php_stream_close(newfile);
+
+ if (error) {
+ spprintf(error, 0, "unable to write temporary manifest of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
+ }
+
+ return EOF;
+ }
+ }
+
+ /* now copy the actual file data to the new phar */
+ 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)) {
+
+ if (zend_hash_get_current_data(&phar->manifest, (void **)&entry) == FAILURE) {
+ continue;
+ }
+
+ if (entry->is_deleted || entry->is_dir || entry->is_mounted) {
+ continue;
+ }
+
+ if (entry->cfp) {
+ file = entry->cfp;
+ php_stream_rewind(file);
+ } else {
+ file = phar_get_efp(entry, 0 TSRMLS_CC);
+ if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0 TSRMLS_CC)) {
+ 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);
+ }
+ 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 = entry->offset_abs = offset;
+ offset += entry->compressed_filesize;
+ if (phar_stream_copy_to_stream(file, newfile, entry->compressed_filesize, &wrote) == FAILURE) {
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+
+ php_stream_close(newfile);
+
+ if (error) {
+ spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
+ }
+
+ return EOF;
+ }
+
+ entry->is_modified = 0;
+
+ if (entry->cfp) {
+ php_stream_close(entry->cfp);
+ entry->cfp = NULL;
+ }
+
+ 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 = NULL;
+ entry->fp_type = PHAR_FP;
+ } else if (entry->fp_type == PHAR_UFP) {
+ entry->fp_type = PHAR_FP;
+ }
+ }
+
+ /* append signature */
+ if (global_flags & PHAR_HDR_SIGNATURE) {
+ char sig_buf[4];
+
+ php_stream_rewind(newfile);
+
+ if (phar->signature) {
+ efree(phar->signature);
+ phar->signature = NULL;
+ }
+
+ switch(phar->sig_flags) {
+#ifndef PHAR_HASH_OK
+ case PHAR_SIG_SHA512:
+ case PHAR_SIG_SHA256:
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ if (error) {
+ spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\" with requested hash type", entry->filename, phar->fname);
+ }
+ return EOF;
+#endif
+ default: {
+ char *digest = NULL;
+ int digest_len;
+
+ if (FAILURE == phar_create_signature(phar, newfile, &digest, &digest_len, error TSRMLS_CC)) {
+ if (error) {
+ char *save = *error;
+ spprintf(error, 0, "phar error: unable to write signature: %s", save);
+ efree(save);
+ }
+ if (digest) {
+ efree(digest);
+ }
+ if (closeoldfile) {
+ php_stream_close(oldfile);
+ }
+ php_stream_close(newfile);
+ return EOF;
+ }
+
+ php_stream_write(newfile, digest, digest_len);
+ efree(digest);
+ if (phar->sig_flags == PHAR_SIG_OPENSSL) {
+ phar_set_32(sig_buf, digest_len);
+ php_stream_write(newfile, sig_buf, 4);
+ }
+ break;
+ }
+ }
+ phar_set_32(sig_buf, phar->sig_flags);
+ php_stream_write(newfile, sig_buf, 4);
+ php_stream_write(newfile, "GBMB", 4);
+ }
+
+ /* finally, close the temp file, rename the original phar,
+ move the temp to the old phar, unlink the old phar, and reload it into memory
+ */
+ if (phar->fp && free_fp) {
+ php_stream_close(phar->fp);
+ }
+
+ if (phar->ufp) {
+ if (free_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);
+
+ if (phar->donotflush) {
+ /* deferred flush */
+ phar->fp = newfile;
+ } else {
+ phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
+ if (!phar->fp) {
+ phar->fp = newfile;
+ if (error) {
+ spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
+ }
+ return EOF;
+ }
+
+ if (phar->flags & PHAR_FILE_COMPRESSED_GZ) {
+ /* to properly compress, we have to tell zlib to add a zlib header */
+ zval filterparams;
+
+ array_init(&filterparams);
+ add_assoc_long(&filterparams, "window", MAX_WBITS+16);
+ filter = php_stream_filter_create("zlib.deflate", &filterparams, php_stream_is_persistent(phar->fp) TSRMLS_CC);
+ zval_dtor(&filterparams);
+
+ if (!filter) {
+ if (error) {
+ spprintf(error, 4096, "unable to compress all contents of phar \"%s\" using zlib, PHP versions older than 5.2.6 have a buggy zlib", phar->fname);
+ }
+ return EOF;
+ }
+
+ php_stream_filter_append(&phar->fp->writefilters, filter);
+ phar_stream_copy_to_stream(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
+ php_stream_filter_flush(filter, 1);
+ php_stream_filter_remove(filter, 1 TSRMLS_CC);
+ php_stream_close(phar->fp);
+ /* use the temp stream as our base */
+ phar->fp = newfile;
+ } else if (phar->flags & PHAR_FILE_COMPRESSED_BZ2) {
+ filter = php_stream_filter_create("bzip2.compress", NULL, php_stream_is_persistent(phar->fp) TSRMLS_CC);
+ php_stream_filter_append(&phar->fp->writefilters, filter);
+ phar_stream_copy_to_stream(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
+ php_stream_filter_flush(filter, 1);
+ php_stream_filter_remove(filter, 1 TSRMLS_CC);
+ php_stream_close(phar->fp);
+ /* use the temp stream as our base */
+ phar->fp = newfile;
+ } else {
+ phar_stream_copy_to_stream(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
+ /* we could also reopen the file in "rb" mode but there is no need for that */
+ php_stream_close(newfile);
+ }
+ }
+
+ if (-1 == php_stream_seek(phar->fp, phar->halt_offset, SEEK_SET)) {
+ if (error) {
+ spprintf(error, 0, "unable to seek to __HALT_COMPILER(); in new phar \"%s\"", phar->fname);
+ }
+ return EOF;
+ }
+
+ return EOF;
+}
+/* }}} */
+
+#ifdef COMPILE_DL_PHAR
+ZEND_GET_MODULE(phar)
+#endif
+
+/* {{{ phar_functions[]
+ *
+ * Every user visible function must have an entry in phar_functions[].
+ */
+zend_function_entry phar_functions[] = {
+ PHP_FE_END
+};
+/* }}}*/
+
+static size_t phar_zend_stream_reader(void *handle, char *buf, size_t len TSRMLS_DC) /* {{{ */
+{
+ return php_stream_read(phar_get_pharfp((phar_archive_data*)handle TSRMLS_CC), buf, len);
+}
+/* }}} */
+
+#if PHP_VERSION_ID >= 50300
+static size_t phar_zend_stream_fsizer(void *handle TSRMLS_DC) /* {{{ */
+{
+ return ((phar_archive_data*)handle)->halt_offset + 32;
+} /* }}} */
+
+#else /* PHP_VERSION_ID */
+
+static long phar_stream_fteller_for_zend(void *handle TSRMLS_DC) /* {{{ */
+{
+ return (long)php_stream_tell(phar_get_pharfp((phar_archive_data*)handle TSRMLS_CC));
+}
+/* }}} */
+#endif
+
+zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);
+#if PHP_VERSION_ID >= 50300
+#define phar_orig_zend_open zend_stream_open_function
+static char *phar_resolve_path(const char *filename, int filename_len TSRMLS_DC)
+{
+ return phar_find_in_include_path((char *) filename, filename_len, NULL TSRMLS_CC);
+}
+#else
+int (*phar_orig_zend_open)(const char *filename, zend_file_handle *handle TSRMLS_DC);
+#endif
+
+static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) /* {{{ */
+{
+ zend_op_array *res;
+ char *name = NULL;
+ int failed;
+ phar_archive_data *phar;
+
+ if (!file_handle || !file_handle->filename) {
+ return phar_orig_compile_file(file_handle, type TSRMLS_CC);
+ }
+ if (strstr(file_handle->filename, ".phar") && !strstr(file_handle->filename, "://")) {
+ if (SUCCESS == phar_open_from_filename((char*)file_handle->filename, strlen(file_handle->filename), NULL, 0, 0, &phar, NULL TSRMLS_CC)) {
+ if (phar->is_zip || phar->is_tar) {
+ zend_file_handle f = *file_handle;
+
+ /* zip or tar-based phar */
+ spprintf(&name, 4096, "phar://%s/%s", file_handle->filename, ".phar/stub.php");
+ if (SUCCESS == phar_orig_zend_open((const char *)name, file_handle TSRMLS_CC)) {
+ efree(name);
+ name = NULL;
+ file_handle->filename = f.filename;
+ if (file_handle->opened_path) {
+ efree(file_handle->opened_path);
+ }
+ file_handle->opened_path = f.opened_path;
+ file_handle->free_filename = f.free_filename;
+ } else {
+ *file_handle = f;
+ }
+ } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) {
+ /* compressed phar */
+#if PHP_VERSION_ID >= 50300
+ file_handle->type = ZEND_HANDLE_STREAM;
+ /* we do our own reading directly from the phar, don't change the next line */
+ file_handle->handle.stream.handle = phar;
+ file_handle->handle.stream.reader = phar_zend_stream_reader;
+ file_handle->handle.stream.closer = NULL;
+ file_handle->handle.stream.fsizer = phar_zend_stream_fsizer;
+ file_handle->handle.stream.isatty = 0;
+ phar->is_persistent ?
+ php_stream_rewind(PHAR_GLOBALS->cached_fp[phar->phar_pos].fp) :
+ php_stream_rewind(phar->fp);
+ memset(&file_handle->handle.stream.mmap, 0, sizeof(file_handle->handle.stream.mmap));
+#else /* PHP_VERSION_ID */
+ file_handle->type = ZEND_HANDLE_STREAM;
+ /* we do our own reading directly from the phar, don't change the next line */
+ file_handle->handle.stream.handle = phar;
+ file_handle->handle.stream.reader = phar_zend_stream_reader;
+ file_handle->handle.stream.closer = NULL; /* don't close - let phar handle this one */
+ file_handle->handle.stream.fteller = phar_stream_fteller_for_zend;
+ file_handle->handle.stream.interactive = 0;
+ phar->is_persistent ?
+ php_stream_rewind(PHAR_GLOBALS->cached_fp[phar->phar_pos].fp) :
+ php_stream_rewind(phar->fp);
+#endif
+ }
+ }
+ }
+
+ zend_try {
+ failed = 0;
+ res = phar_orig_compile_file(file_handle, type TSRMLS_CC);
+ } zend_catch {
+ failed = 1;
+ res = NULL;
+ } zend_end_try();
+
+ if (name) {
+ efree(name);
+ }
+
+ if (failed) {
+ zend_bailout();
+ }
+
+ return res;
+}
+/* }}} */
+
+#if PHP_VERSION_ID < 50300
+int phar_zend_open(const char *filename, zend_file_handle *handle TSRMLS_DC) /* {{{ */
+{
+ char *arch, *entry;
+ int arch_len, entry_len;
+
+ /* this code is obsoleted in php 5.3 */
+ entry = (char *) filename;
+ if (!IS_ABSOLUTE_PATH(entry, strlen(entry)) && !strstr(entry, "://")) {
+ phar_archive_data **pphar = NULL;
+ char *fname;
+ int fname_len;
+
+ fname = (char*)zend_get_executed_filename(TSRMLS_C);
+ fname_len = strlen(fname);
+
+ if (fname_len > 7 && !strncasecmp(fname, "phar://", 7)) {
+ if (SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 1, 0 TSRMLS_CC)) {
+ zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), arch, arch_len, (void **) &pphar);
+ if (!pphar && PHAR_G(manifest_cached)) {
+ zend_hash_find(&cached_phars, arch, arch_len, (void **) &pphar);
+ }
+ efree(arch);
+ efree(entry);
+ }
+ }
+
+ /* retrieving an include within the current directory, so use this if possible */
+ if (!(entry = phar_find_in_include_path((char *) filename, strlen(filename), NULL TSRMLS_CC))) {
+ /* this file is not in the phar, use the original path */
+ goto skip_phar;
+ }
+
+ if (SUCCESS == phar_orig_zend_open(entry, handle TSRMLS_CC)) {
+ if (!handle->opened_path) {
+ handle->opened_path = entry;
+ }
+ if (entry != filename) {
+ handle->free_filename = 1;
+ }
+ return SUCCESS;
+ }
+
+ if (entry != filename) {
+ efree(entry);
+ }
+
+ return FAILURE;
+ }
+skip_phar:
+ return phar_orig_zend_open(filename, handle TSRMLS_CC);
+}
+/* }}} */
+#endif
+typedef zend_op_array* (zend_compile_t)(zend_file_handle*, int TSRMLS_DC);
+typedef zend_compile_t* (compile_hook)(zend_compile_t *ptr);
+
+PHP_GINIT_FUNCTION(phar) /* {{{ */
+{
+ phar_mime_type mime;
+
+ memset(phar_globals, 0, sizeof(zend_phar_globals));
+ phar_globals->readonly = 1;
+
+ zend_hash_init(&phar_globals->mime_types, 0, NULL, NULL, 1);
+
+#define PHAR_SET_MIME(mimetype, ret, fileext) \
+ mime.mime = mimetype; \
+ mime.len = sizeof((mimetype))+1; \
+ mime.type = ret; \
+ zend_hash_add(&phar_globals->mime_types, fileext, sizeof(fileext)-1, (void *)&mime, sizeof(phar_mime_type), NULL); \
+
+ PHAR_SET_MIME("text/html", PHAR_MIME_PHPS, "phps")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cc")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cpp")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c++")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "dtd")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "h")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "log")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "rng")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "txt")
+ PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "xsd")
+ PHAR_SET_MIME("", PHAR_MIME_PHP, "php")
+ PHAR_SET_MIME("", PHAR_MIME_PHP, "inc")
+ PHAR_SET_MIME("video/avi", PHAR_MIME_OTHER, "avi")
+ PHAR_SET_MIME("image/bmp", PHAR_MIME_OTHER, "bmp")
+ PHAR_SET_MIME("text/css", PHAR_MIME_OTHER, "css")
+ PHAR_SET_MIME("image/gif", PHAR_MIME_OTHER, "gif")
+ PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htm")
+ PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "html")
+ PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htmls")
+ PHAR_SET_MIME("image/x-ico", PHAR_MIME_OTHER, "ico")
+ PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpe")
+ PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpg")
+ PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpeg")
+ PHAR_SET_MIME("application/x-javascript", PHAR_MIME_OTHER, "js")
+ PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "midi")
+ PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "mid")
+ PHAR_SET_MIME("audio/mod", PHAR_MIME_OTHER, "mod")
+ PHAR_SET_MIME("movie/quicktime", PHAR_MIME_OTHER, "mov")
+ PHAR_SET_MIME("audio/mp3", PHAR_MIME_OTHER, "mp3")
+ PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpg")
+ PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpeg")
+ PHAR_SET_MIME("application/pdf", PHAR_MIME_OTHER, "pdf")
+ PHAR_SET_MIME("image/png", PHAR_MIME_OTHER, "png")
+ PHAR_SET_MIME("application/shockwave-flash", PHAR_MIME_OTHER, "swf")
+ PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tif")
+ PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tiff")
+ PHAR_SET_MIME("audio/wav", PHAR_MIME_OTHER, "wav")
+ PHAR_SET_MIME("image/xbm", PHAR_MIME_OTHER, "xbm")
+ PHAR_SET_MIME("text/xml", PHAR_MIME_OTHER, "xml")
+
+ phar_restore_orig_functions(TSRMLS_C);
+}
+/* }}} */
+
+PHP_GSHUTDOWN_FUNCTION(phar) /* {{{ */
+{
+ zend_hash_destroy(&phar_globals->mime_types);
+}
+/* }}} */
+
+PHP_MINIT_FUNCTION(phar) /* {{{ */
+{
+ REGISTER_INI_ENTRIES();
+
+ phar_orig_compile_file = zend_compile_file;
+ zend_compile_file = phar_compile_file;
+
+#if PHP_VERSION_ID >= 50300
+ phar_save_resolve_path = zend_resolve_path;
+ zend_resolve_path = phar_resolve_path;
+#else
+ phar_orig_zend_open = zend_stream_open_function;
+ zend_stream_open_function = phar_zend_open;
+#endif
+
+ phar_object_init(TSRMLS_C);
+
+ phar_intercept_functions_init(TSRMLS_C);
+ phar_save_orig_functions(TSRMLS_C);
+
+ return php_register_url_stream_wrapper("phar", &php_stream_phar_wrapper TSRMLS_CC);
+}
+/* }}} */
+
+PHP_MSHUTDOWN_FUNCTION(phar) /* {{{ */
+{
+ php_unregister_url_stream_wrapper("phar" TSRMLS_CC);
+
+ phar_intercept_functions_shutdown(TSRMLS_C);
+
+ if (zend_compile_file == phar_compile_file) {
+ zend_compile_file = phar_orig_compile_file;
+ }
+
+#if PHP_VERSION_ID < 50300
+ if (zend_stream_open_function == phar_zend_open) {
+ zend_stream_open_function = phar_orig_zend_open;
+ }
+#endif
+ if (PHAR_G(manifest_cached)) {
+ zend_hash_destroy(&(cached_phars));
+ zend_hash_destroy(&(cached_alias));
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+void phar_request_initialize(TSRMLS_D) /* {{{ */
+{
+ if (!PHAR_GLOBALS->request_init)
+ {
+ PHAR_G(last_phar) = NULL;
+ PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL;
+ PHAR_G(has_bz2) = zend_hash_exists(&module_registry, "bz2", sizeof("bz2"));
+ PHAR_G(has_zlib) = zend_hash_exists(&module_registry, "zlib", sizeof("zlib"));
+ PHAR_GLOBALS->request_init = 1;
+ PHAR_GLOBALS->request_ends = 0;
+ PHAR_GLOBALS->request_done = 0;
+ zend_hash_init(&(PHAR_GLOBALS->phar_fname_map), 5, zend_get_hash_value, destroy_phar_data, 0);
+ zend_hash_init(&(PHAR_GLOBALS->phar_persist_map), 5, zend_get_hash_value, NULL, 0);
+ zend_hash_init(&(PHAR_GLOBALS->phar_alias_map), 5, zend_get_hash_value, NULL, 0);
+
+ if (PHAR_G(manifest_cached)) {
+ phar_archive_data **pphar;
+ phar_entry_fp *stuff = (phar_entry_fp *) ecalloc(zend_hash_num_elements(&cached_phars), sizeof(phar_entry_fp));
+
+ for (zend_hash_internal_pointer_reset(&cached_phars);
+ zend_hash_get_current_data(&cached_phars, (void **)&pphar) == SUCCESS;
+ zend_hash_move_forward(&cached_phars)) {
+ stuff[pphar[0]->phar_pos].manifest = (phar_entry_fp_info *) ecalloc( zend_hash_num_elements(&(pphar[0]->manifest)), sizeof(phar_entry_fp_info));
+ }
+
+ PHAR_GLOBALS->cached_fp = stuff;
+ }
+
+ PHAR_GLOBALS->phar_SERVER_mung_list = 0;
+ PHAR_G(cwd) = NULL;
+ PHAR_G(cwd_len) = 0;
+ PHAR_G(cwd_init) = 0;
+ }
+}
+/* }}} */
+
+PHP_RSHUTDOWN_FUNCTION(phar) /* {{{ */
+{
+ int i;
+
+ PHAR_GLOBALS->request_ends = 1;
+
+ if (PHAR_GLOBALS->request_init)
+ {
+ phar_release_functions(TSRMLS_C);
+ zend_hash_destroy(&(PHAR_GLOBALS->phar_alias_map));
+ PHAR_GLOBALS->phar_alias_map.arBuckets = NULL;
+ zend_hash_destroy(&(PHAR_GLOBALS->phar_fname_map));
+ PHAR_GLOBALS->phar_fname_map.arBuckets = NULL;
+ zend_hash_destroy(&(PHAR_GLOBALS->phar_persist_map));
+ PHAR_GLOBALS->phar_persist_map.arBuckets = NULL;
+ PHAR_GLOBALS->phar_SERVER_mung_list = 0;
+
+ if (PHAR_GLOBALS->cached_fp) {
+ for (i = 0; i < zend_hash_num_elements(&cached_phars); ++i) {
+ if (PHAR_GLOBALS->cached_fp[i].fp) {
+ php_stream_close(PHAR_GLOBALS->cached_fp[i].fp);
+ }
+ if (PHAR_GLOBALS->cached_fp[i].ufp) {
+ php_stream_close(PHAR_GLOBALS->cached_fp[i].ufp);
+ }
+ efree(PHAR_GLOBALS->cached_fp[i].manifest);
+ }
+ efree(PHAR_GLOBALS->cached_fp);
+ PHAR_GLOBALS->cached_fp = 0;
+ }
+
+ PHAR_GLOBALS->request_init = 0;
+
+ if (PHAR_G(cwd)) {
+ efree(PHAR_G(cwd));
+ }
+
+ PHAR_G(cwd) = NULL;
+ PHAR_G(cwd_len) = 0;
+ PHAR_G(cwd_init) = 0;
+ }
+
+ PHAR_GLOBALS->request_done = 1;
+ return SUCCESS;
+}
+/* }}} */
+
+PHP_MINFO_FUNCTION(phar) /* {{{ */
+{
+ phar_request_initialize(TSRMLS_C);
+ php_info_print_table_start();
+ php_info_print_table_header(2, "Phar: PHP Archive support", "enabled");
+ php_info_print_table_row(2, "Phar EXT version", PHP_PHAR_VERSION);
+ php_info_print_table_row(2, "Phar API version", PHP_PHAR_API_VERSION);
+ php_info_print_table_row(2, "SVN revision", "$Id: c5042cc34acebcc0926625b57dff03deebbe6472 $");
+ php_info_print_table_row(2, "Phar-based phar archives", "enabled");
+ php_info_print_table_row(2, "Tar-based phar archives", "enabled");
+ php_info_print_table_row(2, "ZIP-based phar archives", "enabled");
+
+ if (PHAR_G(has_zlib)) {
+ php_info_print_table_row(2, "gzip compression", "enabled");
+ } else {
+ php_info_print_table_row(2, "gzip compression", "disabled (install ext/zlib)");
+ }
+
+ if (PHAR_G(has_bz2)) {
+ php_info_print_table_row(2, "bzip2 compression", "enabled");
+ } else {
+ php_info_print_table_row(2, "bzip2 compression", "disabled (install pecl/bz2)");
+ }
+#ifdef PHAR_HAVE_OPENSSL
+ php_info_print_table_row(2, "Native OpenSSL support", "enabled");
+#else
+ if (zend_hash_exists(&module_registry, "openssl", sizeof("openssl"))) {
+ php_info_print_table_row(2, "OpenSSL support", "enabled");
+ } else {
+ php_info_print_table_row(2, "OpenSSL support", "disabled (install ext/openssl)");
+ }
+#endif
+ php_info_print_table_end();
+
+ php_info_print_box_start(0);
+ PUTS("Phar based on pear/PHP_Archive, original concept by Davey Shafik.");
+ PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
+ PUTS("Phar fully realized by Gregory Beaver and Marcus Boerger.");
+ PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
+ PUTS("Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle.");
+ php_info_print_box_end();
+
+ DISPLAY_INI_ENTRIES();
+}
+/* }}} */
+
+/* {{{ phar_module_entry
+ */
+static const zend_module_dep phar_deps[] = {
+ ZEND_MOD_OPTIONAL("apc")
+ ZEND_MOD_OPTIONAL("bz2")
+ ZEND_MOD_OPTIONAL("openssl")
+ ZEND_MOD_OPTIONAL("zlib")
+ ZEND_MOD_OPTIONAL("standard")
+#if defined(HAVE_HASH) && !defined(COMPILE_DL_HASH)
+ ZEND_MOD_REQUIRED("hash")
+#endif
+#if HAVE_SPL
+ ZEND_MOD_REQUIRED("spl")
+#endif
+ ZEND_MOD_END
+};
+
+zend_module_entry phar_module_entry = {
+ STANDARD_MODULE_HEADER_EX, NULL,
+ phar_deps,
+ "Phar",
+ phar_functions,
+ PHP_MINIT(phar),
+ PHP_MSHUTDOWN(phar),
+ NULL,
+ PHP_RSHUTDOWN(phar),
+ PHP_MINFO(phar),
+ PHP_PHAR_VERSION,
+ PHP_MODULE_GLOBALS(phar), /* globals descriptor */
+ PHP_GINIT(phar), /* globals ctor */
+ PHP_GSHUTDOWN(phar), /* globals dtor */
+ NULL, /* post deactivate */
+ STANDARD_MODULE_PROPERTIES_EX
+};
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */