From a1541a7638c44bb07331904bda7a58632b2f2aaf Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:36 +0000 Subject: [PATCH 01/25] commit patch 17679522 --- ext/standard/tests/streams/parseip-001.phpt | 37 +++++++++++++++++++++ main/streams/xp_socket.c | 29 ++++++++++------ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 ext/standard/tests/streams/parseip-001.phpt diff --git a/ext/standard/tests/streams/parseip-001.phpt b/ext/standard/tests/streams/parseip-001.phpt new file mode 100644 index 0000000000000..594756db6b7cd --- /dev/null +++ b/ext/standard/tests/streams/parseip-001.phpt @@ -0,0 +1,37 @@ +--TEST-- +Use of double-port in fsockopen() +--FILE-- + 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ - p = memchr(str + 1, ']', str_len - 2); + char *p = memchr(str + 1, ']', str_len - 2), *e = NULL; if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); } return NULL; } - *portno = atoi(p + 2); + *portno = strtol(p + 2, &e, 10); + if (e && *e) { + if (get_err) { + *err = strpprintf(0, "Failed to parse address \"%s\"", str); + } + return NULL; + } return estrndup(str + 1, p - str - 1); } #endif + if (str_len) { colon = memchr(str, ':', str_len - 1); } else { colon = NULL; } + if (colon) { - *portno = atoi(colon + 1); - host = estrndup(str, colon - str); - } else { - if (get_err) { - *err = strpprintf(0, "Failed to parse address \"%s\"", str); + char *e = NULL; + *portno = strtol(colon + 1, &e, 10); + if (!e || !*e) { + return estrndup(str, colon - str); } - return NULL; } - return host; + if (get_err) { + *err = strpprintf(0, "Failed to parse address \"%s\"", str); + } + return NULL; } static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) From 79e19cb2957e7c232959bd4219f8fe4be1024ae3 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:38 +0000 Subject: [PATCH 02/25] commit patch 21778259 --- sapi/apache2handler/sapi_apache2.c | 1 + sapi/apache2handler/sapi_apache2.c.orig | 756 ++++++++++++++++++++++++ 2 files changed, 757 insertions(+) create mode 100644 sapi/apache2handler/sapi_apache2.c.orig diff --git a/sapi/apache2handler/sapi_apache2.c b/sapi/apache2handler/sapi_apache2.c index 0ee4fcbddf087..d1faa2fd90dfc 100644 --- a/sapi/apache2handler/sapi_apache2.c +++ b/sapi/apache2handler/sapi_apache2.c @@ -712,6 +712,7 @@ zend_first_try { if (!parent_req) { php_apache_request_dtor(r); ctx->request_processed = 1; + apr_brigade_cleanup(brigade); bucket = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade, bucket); diff --git a/sapi/apache2handler/sapi_apache2.c.orig b/sapi/apache2handler/sapi_apache2.c.orig new file mode 100644 index 0000000000000..0ee4fcbddf087 --- /dev/null +++ b/sapi/apache2handler/sapi_apache2.c.orig @@ -0,0 +1,756 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Sascha Schumann | + | Parts based on Apache 1.3 SAPI module by | + | Rasmus Lerdorf and Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_main.h" +#include "php_ini.h" +#include "php_variables.h" +#include "SAPI.h" + +#include + +#include "zend_smart_str.h" +#include "ext/standard/php_standard.h" + +#include "apr_strings.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" +#include "ap_mpm.h" + +#include "php_apache.h" + +/* UnixWare define shutdown to _shutdown, which causes problems later + * on when using a structure member named shutdown. Since this source + * file does not use the system call shutdown, it is safe to #undef it. + */ +#undef shutdown + +#define PHP_MAGIC_TYPE "application/x-httpd-php" +#define PHP_SOURCE_MAGIC_TYPE "application/x-httpd-php-source" +#define PHP_SCRIPT "php7-script" + +/* A way to specify the location of the php.ini dir in an apache directive */ +char *apache2_php_ini_path_override = NULL; +#if defined(PHP_WIN32) && defined(ZTS) +ZEND_TSRMLS_CACHE_DEFINE() +#endif + +static size_t +php_apache_sapi_ub_write(const char *str, size_t str_length) +{ + request_rec *r; + php_struct *ctx; + + ctx = SG(server_context); + r = ctx->r; + + if (ap_rwrite(str, str_length, r) < 0) { + php_handle_aborted_connection(); + } + + return str_length; /* we always consume all the data passed to us. */ +} + +static int +php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) +{ + php_struct *ctx; + char *val, *ptr; + + ctx = SG(server_context); + + switch (op) { + case SAPI_HEADER_DELETE: + apr_table_unset(ctx->r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_DELETE_ALL: + apr_table_clear(ctx->r->headers_out); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + val = strchr(sapi_header->header, ':'); + + if (!val) { + return 0; + } + ptr = val; + + *val = '\0'; + + do { + val++; + } while (*val == ' '); + + if (!strcasecmp(sapi_header->header, "content-type")) { + if (ctx->content_type) { + efree(ctx->content_type); + } + ctx->content_type = estrdup(val); + } else if (!strcasecmp(sapi_header->header, "content-length")) { + apr_off_t clen = 0; + + if (APR_SUCCESS != apr_strtoff(&clen, val, (char **) NULL, 10)) { + /* We'll fall back to strtol, since that's what we used to + * do anyway. */ + clen = (apr_off_t) strtol(val, (char **) NULL, 10); + } + + ap_set_content_length(ctx->r, clen); + } else if (op == SAPI_HEADER_REPLACE) { + apr_table_set(ctx->r->headers_out, sapi_header->header, val); + } else { + apr_table_add(ctx->r->headers_out, sapi_header->header, val); + } + + *ptr = ':'; + + return SAPI_HEADER_ADD; + + default: + return 0; + } +} + +static int +php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers) +{ + php_struct *ctx = SG(server_context); + const char *sline = SG(sapi_headers).http_status_line; + + ctx->r->status = SG(sapi_headers).http_response_code; + + /* httpd requires that r->status_line is set to the first digit of + * the status-code: */ + if (sline && strlen(sline) > 12 && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') { + ctx->r->status_line = apr_pstrdup(ctx->r->pool, sline + 9); + ctx->r->proto_num = 1000 + (sline[7]-'0'); + if ((sline[7]-'0') == 0) { + apr_table_set(ctx->r->subprocess_env, "force-response-1.0", "true"); + } + } + + /* call ap_set_content_type only once, else each time we call it, + configured output filters for that content type will be added */ + if (!ctx->content_type) { + ctx->content_type = sapi_get_default_content_type(); + } + ap_set_content_type(ctx->r, apr_pstrdup(ctx->r->pool, ctx->content_type)); + efree(ctx->content_type); + ctx->content_type = NULL; + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static apr_size_t +php_apache_sapi_read_post(char *buf, size_t count_bytes) +{ + apr_size_t len, tlen=0; + php_struct *ctx = SG(server_context); + request_rec *r; + apr_bucket_brigade *brigade; + + r = ctx->r; + brigade = ctx->brigade; + len = count_bytes; + + /* + * This loop is needed because ap_get_brigade() can return us partial data + * which would cause premature termination of request read. Therefor we + * need to make sure that if data is available we fill the buffer completely. + */ + + while (ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES, APR_BLOCK_READ, len) == APR_SUCCESS) { + apr_brigade_flatten(brigade, buf, &len); + apr_brigade_cleanup(brigade); + tlen += len; + if (tlen == count_bytes || !len) { + break; + } + buf += len; + len = count_bytes - tlen; + } + + return tlen; +} + +static zend_stat_t* +php_apache_sapi_get_stat(void) +{ + php_struct *ctx = SG(server_context); + +#ifdef PHP_WIN32 + ctx->finfo.st_uid = 0; + ctx->finfo.st_gid = 0; +#else + ctx->finfo.st_uid = ctx->r->finfo.user; + ctx->finfo.st_gid = ctx->r->finfo.group; +#endif + ctx->finfo.st_dev = ctx->r->finfo.device; + ctx->finfo.st_ino = ctx->r->finfo.inode; + ctx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime); + ctx->finfo.st_size = ctx->r->finfo.size; + ctx->finfo.st_nlink = ctx->r->finfo.nlink; + + return &ctx->finfo; +} + +static char * +php_apache_sapi_read_cookies(void) +{ + php_struct *ctx = SG(server_context); + const char *http_cookie; + + http_cookie = apr_table_get(ctx->r->headers_in, "cookie"); + + /* The SAPI interface should use 'const char *' */ + return (char *) http_cookie; +} + +static char * +php_apache_sapi_getenv(char *name, size_t name_len) +{ + php_struct *ctx = SG(server_context); + const char *env_var; + + if (ctx == NULL) { + return NULL; + } + + env_var = apr_table_get(ctx->r->subprocess_env, name); + + return (char *) env_var; +} + +static void +php_apache_sapi_register_variables(zval *track_vars_array) +{ + php_struct *ctx = SG(server_context); + const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env); + char *key, *val; + size_t new_val_len; + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len)) { + php_register_variable_safe(key, val, new_val_len, track_vars_array); + } + APR_ARRAY_FOREACH_CLOSE() + + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len)) { + php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array); + } +} + +static void +php_apache_sapi_flush(void *server_context) +{ + php_struct *ctx; + request_rec *r; + + ctx = server_context; + + /* If we haven't registered a server_context yet, + * then don't bother flushing. */ + if (!server_context) { + return; + } + + r = ctx->r; + + sapi_send_headers(); + + r->status = SG(sapi_headers).http_response_code; + SG(headers_sent) = 1; + + if (ap_rflush(r) < 0 || r->connection->aborted) { + php_handle_aborted_connection(); + } +} + +static void php_apache_sapi_log_message(char *msg, int syslog_type_int) +{ + php_struct *ctx; + int aplog_type = APLOG_ERR; + + ctx = SG(server_context); + + switch (syslog_type_int) { +#if LOG_EMERG != LOG_CRIT + case LOG_EMERG: + aplog_type = APLOG_EMERG; + break; +#endif +#if LOG_ALERT != LOG_CRIT + case LOG_ALERT: + aplog_type = APLOG_ALERT; + break; +#endif + case LOG_CRIT: + aplog_type = APLOG_CRIT; + break; + case LOG_ERR: + aplog_type = APLOG_ERR; + break; + case LOG_WARNING: + aplog_type = APLOG_WARNING; + break; + case LOG_NOTICE: + aplog_type = APLOG_NOTICE; + break; +#if LOG_INFO != LOG_NOTICE + case LOG_INFO: + aplog_type = APLOG_INFO; + break; +#endif +#if LOG_NOTICE != LOG_DEBUG + case LOG_DEBUG: + aplog_type = APLOG_DEBUG; + break; +#endif + } + + if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg); + } else { + ap_log_rerror(APLOG_MARK, aplog_type, 0, ctx->r, "%s", msg); + } +} + +static void php_apache_sapi_log_message_ex(char *msg, request_rec *r) +{ + if (r) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, msg, r->filename); + } else { + php_apache_sapi_log_message(msg, -1); + } +} + +static double php_apache_sapi_get_request_time(void) +{ + php_struct *ctx = SG(server_context); + return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0; +} + +extern zend_module_entry php_apache_module; + +static int php_apache2_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +static sapi_module_struct apache2_sapi_module = { + "apache2handler", + "Apache 2.0 Handler", + + php_apache2_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_apache_sapi_ub_write, /* unbuffered write */ + php_apache_sapi_flush, /* flush */ + php_apache_sapi_get_stat, /* get uid */ + php_apache_sapi_getenv, /* getenv */ + + php_error, /* error handler */ + + php_apache_sapi_header_handler, /* header handler */ + php_apache_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_apache_sapi_read_post, /* read POST data */ + php_apache_sapi_read_cookies, /* read Cookies */ + + php_apache_sapi_register_variables, + php_apache_sapi_log_message, /* Log message */ + php_apache_sapi_get_request_time, /* Request Time */ + NULL, /* Child Terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static apr_status_t php_apache_server_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static apr_status_t php_apache_child_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); +#if defined(ZTS) && !defined(PHP_WIN32) + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static void php_apache_add_version(apr_pool_t *p) +{ + if (PG(expose_php)) { + ap_add_version_component(p, "PHP/" PHP_VERSION); + } +} + +static int php_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ +#ifndef ZTS + int threaded_mpm; + + ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm); + if(threaded_mpm) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, 0, "Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP."); + return DONE; + } +#endif + /* When this is NULL, apache won't override the hard-coded default + * php.ini path setting. */ + apache2_php_ini_path_override = NULL; + return OK; +} + +static int +php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + void *data = NULL; + const char *userdata_key = "apache2hook_post_config"; + + /* Apache will load, unload and then reload a DSO module. This + * prevents us from starting PHP until the second load. */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + if (data == NULL) { + /* We must use set() here and *not* setn(), otherwise the + * static string pointed to by userdata_key will be mapped + * to a different location when the DSO is reloaded and the + * pointers won't match, causing get() to return NULL when + * we expected it to return non-NULL. */ + apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); + return OK; + } + + /* Set up our overridden path. */ + if (apache2_php_ini_path_override) { + apache2_sapi_module.php_ini_path_override = apache2_php_ini_path_override; + } +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + (void)ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + zend_signal_startup(); + + sapi_startup(&apache2_sapi_module); + apache2_sapi_module.startup(&apache2_sapi_module); + apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null); + php_apache_add_version(pconf); + + return OK; +} + +static apr_status_t php_server_context_cleanup(void *data_) +{ + void **data = data_; + *data = NULL; + return APR_SUCCESS; +} + +static int php_apache_request_ctor(request_rec *r, php_struct *ctx) +{ + char *content_length; + const char *auth; + + SG(sapi_headers).http_response_code = !r->status ? HTTP_OK : r->status; + SG(request_info).content_type = apr_table_get(r->headers_in, "Content-Type"); + SG(request_info).query_string = apr_pstrdup(r->pool, r->args); + SG(request_info).request_method = r->method; + SG(request_info).proto_num = r->proto_num; + SG(request_info).request_uri = apr_pstrdup(r->pool, r->uri); + SG(request_info).path_translated = apr_pstrdup(r->pool, r->filename); + r->no_local_copy = 1; + + content_length = (char *) apr_table_get(r->headers_in, "Content-Length"); + if (content_length) { + ZEND_ATOL(SG(request_info).content_length, content_length); + } else { + SG(request_info).content_length = 0; + } + + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Last-Modified"); + apr_table_unset(r->headers_out, "Expires"); + apr_table_unset(r->headers_out, "ETag"); + + auth = apr_table_get(r->headers_in, "Authorization"); + php_handle_auth_data(auth); + + if (SG(request_info).auth_user == NULL && r->user) { + SG(request_info).auth_user = estrdup(r->user); + } + + ctx->r->user = apr_pstrdup(ctx->r->pool, SG(request_info).auth_user); + + return php_request_startup(); +} + +static void php_apache_request_dtor(request_rec *r) +{ + php_request_shutdown(NULL); +} + +static void php_apache_ini_dtor(request_rec *r, request_rec *p) +{ + if (strcmp(r->protocol, "INCLUDED")) { + zend_try { zend_ini_deactivate(); } zend_end_try(); + } else { +typedef struct { + HashTable config; +} php_conf_rec; + zend_string *str; + php_conf_rec *c = ap_get_module_config(r->per_dir_config, &php7_module); + + ZEND_HASH_FOREACH_STR_KEY(&c->config, str) { + zend_restore_ini_entry(str, ZEND_INI_STAGE_SHUTDOWN); + } ZEND_HASH_FOREACH_END(); + } + if (p) { + ((php_struct *)SG(server_context))->r = p; + } else { + apr_pool_cleanup_run(r->pool, (void *)&SG(server_context), php_server_context_cleanup); + } +} + +static int php_handler(request_rec *r) +{ + php_struct * volatile ctx; + void *conf; + apr_bucket_brigade * volatile brigade; + apr_bucket *bucket; + apr_status_t rv; + request_rec * volatile parent_req = NULL; +#ifdef ZTS + /* initial resource fetch */ + (void)ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + +#define PHPAP_INI_OFF php_apache_ini_dtor(r, parent_req); + + conf = ap_get_module_config(r->per_dir_config, &php7_module); + + /* apply_config() needs r in some cases, so allocate server_context early */ + ctx = SG(server_context); + if (ctx == NULL || (ctx && ctx->request_processed && !strcmp(r->protocol, "INCLUDED"))) { +normal: + ctx = SG(server_context) = apr_pcalloc(r->pool, sizeof(*ctx)); + /* register a cleanup so we clear out the SG(server_context) + * after each request. Note: We pass in the pointer to the + * server_context in case this is handled by a different thread. + */ + apr_pool_cleanup_register(r->pool, (void *)&SG(server_context), php_server_context_cleanup, apr_pool_cleanup_null); + ctx->r = r; + ctx = NULL; /* May look weird to null it here, but it is to catch the right case in the first_try later on */ + } else { + parent_req = ctx->r; + ctx->r = r; + } + apply_config(conf); + + if (strcmp(r->handler, PHP_MAGIC_TYPE) && strcmp(r->handler, PHP_SOURCE_MAGIC_TYPE) && strcmp(r->handler, PHP_SCRIPT)) { + /* Check for xbithack in this case. */ + if (!AP2(xbithack) || strcmp(r->handler, "text/html") || !(r->finfo.protection & APR_UEXECUTE)) { + PHPAP_INI_OFF; + return DECLINED; + } + } + + /* Give a 404 if PATH_INFO is used but is explicitly disabled in + * the configuration; default behaviour is to accept. */ + if (r->used_path_info == AP_REQ_REJECT_PATH_INFO + && r->path_info && r->path_info[0]) { + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + + /* handle situations where user turns the engine off */ + if (!AP2(engine)) { + PHPAP_INI_OFF; + return DECLINED; + } + + if (r->finfo.filetype == 0) { + php_apache_sapi_log_message_ex("script '%s' not found or unable to stat", r); + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + if (r->finfo.filetype == APR_DIR) { + php_apache_sapi_log_message_ex("attempt to invoke directory '%s' as script", r); + PHPAP_INI_OFF; + return HTTP_FORBIDDEN; + } + + /* Setup the CGI variables if this is the main request */ + if (r->main == NULL || + /* .. or if the sub-request environment differs from the main-request. */ + r->subprocess_env != r->main->subprocess_env + ) { + /* setup standard CGI variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + } + +zend_first_try { + + if (ctx == NULL) { + brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ctx = SG(server_context); + ctx->brigade = brigade; + + if (php_apache_request_ctor(r, ctx)!=SUCCESS) { + zend_bailout(); + } + } else { + if (!parent_req) { + parent_req = ctx->r; + } + if (parent_req && parent_req->handler && + strcmp(parent_req->handler, PHP_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SOURCE_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SCRIPT)) { + if (php_apache_request_ctor(r, ctx)!=SUCCESS) { + zend_bailout(); + } + } + + /* + * check if coming due to ErrorDocument + * We make a special exception of 413 (Invalid POST request) as the invalidity of the request occurs + * during processing of the request by PHP during POST processing. Therefor we need to re-use the exiting + * PHP instance to handle the request rather then creating a new one. + */ + if (parent_req && parent_req->status != HTTP_OK && parent_req->status != 413 && strcmp(r->protocol, "INCLUDED")) { + parent_req = NULL; + goto normal; + } + ctx->r = r; + brigade = ctx->brigade; + } + + if (AP2(last_modified)) { + ap_update_mtime(r, r->finfo.mtime); + ap_set_last_modified(r); + } + + /* Determine if we need to parse the file or show the source */ + if (strncmp(r->handler, PHP_SOURCE_MAGIC_TYPE, sizeof(PHP_SOURCE_MAGIC_TYPE) - 1) == 0) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file((char *)r->filename, &syntax_highlighter_ini); + } else { + zend_file_handle zfd; + + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = (char *) r->filename; + zfd.free_filename = 0; + zfd.opened_path = NULL; + + if (!parent_req) { + php_execute_script(&zfd); + } else { + zend_execute_scripts(ZEND_INCLUDE, NULL, 1, &zfd); + } + + apr_table_set(r->notes, "mod_php_memory_usage", + apr_psprintf(ctx->r->pool, "%" APR_SIZE_T_FMT, zend_memory_peak_usage(1))); + } + +} zend_end_try(); + + if (!parent_req) { + php_apache_request_dtor(r); + ctx->request_processed = 1; + bucket = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(brigade, bucket); + + rv = ap_pass_brigade(r->output_filters, brigade); + if (rv != APR_SUCCESS || r->connection->aborted) { +zend_first_try { + php_handle_aborted_connection(); +} zend_end_try(); + } + apr_brigade_cleanup(brigade); + apr_pool_cleanup_run(r->pool, (void *)&SG(server_context), php_server_context_cleanup); + } else { + ctx->r = parent_req; + } + + return OK; +} + +static void php_apache_child_init(apr_pool_t *pchild, server_rec *s) +{ + apr_pool_cleanup_register(pchild, NULL, php_apache_child_shutdown, apr_pool_cleanup_null); +} + +void php_ap2_register_hook(apr_pool_t *p) +{ + ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE); +#ifdef ZEND_SIGNALS + ap_hook_child_init(zend_signal_init, NULL, NULL, APR_HOOK_MIDDLE); +#endif + ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ From 018031a99298f15952d25002639469def3c3f243 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:40 +0000 Subject: [PATCH 03/25] commit patch 27675034 --- ext/imap/php_imap.c | 1 - ext/imap/php_imap.c.orig | 5102 ++++++++++++++++++++++++++++++++++ ext/imap/tests/bug77020.phpt | 15 + 3 files changed, 5117 insertions(+), 1 deletion(-) create mode 100644 ext/imap/php_imap.c.orig create mode 100644 ext/imap/tests/bug77020.phpt diff --git a/ext/imap/php_imap.c b/ext/imap/php_imap.c index 17456e3df7952..f98f4a71dda82 100644 --- a/ext/imap/php_imap.c +++ b/ext/imap/php_imap.c @@ -4106,7 +4106,6 @@ PHP_FUNCTION(imap_mail) if (!ZSTR_LEN(message)) { /* this is not really an error, so it is allowed. */ php_error_docref(NULL, E_WARNING, "No message string in mail command"); - message = NULL; } if (_php_imap_mail(ZSTR_VAL(to), ZSTR_VAL(subject), ZSTR_VAL(message), headers?ZSTR_VAL(headers):NULL, cc?ZSTR_VAL(cc):NULL, diff --git a/ext/imap/php_imap.c.orig b/ext/imap/php_imap.c.orig new file mode 100644 index 0000000000000..17456e3df7952 --- /dev/null +++ b/ext/imap/php_imap.c.orig @@ -0,0 +1,5102 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Rex Logan | + | Mark Musone | + | Brian Wang | + | Kaj-Michael Lang | + | Antoni Pamies Olive | + | Rasmus Lerdorf | + | Chuck Hagenbuch | + | Andrew Skalski | + | Hartmut Holzgraefe | + | Jani Taskinen | + | Daniel R. Kalowsky | + | PHP 4.0 updates: Zeev Suraski | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#define IMAP41 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "php_streams.h" +#include "ext/standard/php_string.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "zend_smart_str.h" +#include "ext/pcre/php_pcre.h" + +#ifdef ERROR +#undef ERROR +#endif +#include "php_imap.h" + +#include +#include +#include +#include + +#ifdef PHP_WIN32 +#include +#include +#include "win32/sendmail.h" +MAILSTREAM DEFAULTPROTO; +#endif + +#define CRLF "\015\012" +#define CRLF_LEN sizeof("\015\012") - 1 +#define PHP_EXPUNGE 32768 +#define PHP_IMAP_ADDRESS_SIZE_BUF 10 +#ifndef SENDBUFLEN +#define SENDBUFLEN 16385 +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_IMAP_EXPORT __attribute__ ((visibility("default"))) +#else +# define PHP_IMAP_EXPORT +#endif + +static void _php_make_header_object(zval *myzvalue, ENVELOPE *en); +static void _php_imap_add_body(zval *arg, BODY *body); +static zend_string* _php_imap_parse_address(ADDRESS *addresslist, zval *paddress); +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist); + +/* the gets we use */ +static char *php_mail_gets(readfn_t f, void *stream, unsigned long size, GETS_DATA *md); + +/* These function declarations are missing from the IMAP header files... */ +void rfc822_date(char *date); +char *cpystr(const char *str); +char *cpytxt(SIZEDTEXT *dst, char *text, unsigned long size); +#ifndef HAVE_NEW_MIME2TEXT +long utf8_mime2text(SIZEDTEXT *src, SIZEDTEXT *dst); +#else +long utf8_mime2text (SIZEDTEXT *src, SIZEDTEXT *dst, long flags); +#endif +unsigned long find_rightmost_bit(unsigned long *valptr); +void fs_give(void **block); +void *fs_get(size_t size); + +ZEND_DECLARE_MODULE_GLOBALS(imap) +static PHP_GINIT_FUNCTION(imap); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_open, 0, 0, 3) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, user) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, n_retries) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_reopen, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, n_retries) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_append, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, folder) + ZEND_ARG_INFO(0, message) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, date) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_num_msg, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_ping, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_num_recent, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_get_quota, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, qroot) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_get_quotaroot, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_set_quota, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, qroot) + ZEND_ARG_INFO(0, mailbox_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_setacl, 0, 0, 4) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, id) + ZEND_ARG_INFO(0, rights) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getacl, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_expunge, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_gc, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_close, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_headers, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_body, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_copy, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msglist) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_move, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_createmailbox, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_renamemailbox, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, old_name) + ZEND_ARG_INFO(0, new_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_deletemailbox, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_list, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getmailboxes, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_listscan, 0, 0, 4) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, content) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_check, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_delete, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_undelete, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_headerinfo, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, from_length) + ZEND_ARG_INFO(0, subject_length) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_parse_headers, 0, 0, 1) + ZEND_ARG_INFO(0, headers) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_lsub, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getsubscribed, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_subscribe, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_unsubscribe, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchstructure, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchbody, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_savebody, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, file) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_base64, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_qprint, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_8bit, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_binary, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mailboxmsginfo, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_write_address, 0, 0, 3) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, host) + ZEND_ARG_INFO(0, personal) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_parse_adrlist, 0, 0, 2) + ZEND_ARG_INFO(0, address_string) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf8, 0, 0, 1) + ZEND_ARG_INFO(0, mime_encoded_text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf7_decode, 0, 0, 1) + ZEND_ARG_INFO(0, buf) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf7_encode, 0, 0, 1) + ZEND_ARG_INFO(0, buf) +ZEND_END_ARG_INFO() + +#ifdef HAVE_IMAP_MUTF7 +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf8_to_mutf7, 0, 0, 1) + ZEND_ARG_INFO(0, in) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mutf7_to_utf8, 0, 0, 1) + ZEND_ARG_INFO(0, in) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_setflag_full, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, flag) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_clearflag_full, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, flag) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_sort, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, criteria) + ZEND_ARG_INFO(0, reverse) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, search_criteria) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchheader, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_uid, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_msgno, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, unique_msg_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_status, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_bodystruct, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetch_overview, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_compose, 0, 0, 2) + ZEND_ARG_INFO(0, envelope) + ZEND_ARG_INFO(0, body) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail, 0, 0, 3) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, subject) + ZEND_ARG_INFO(0, message) + ZEND_ARG_INFO(0, additional_headers) + ZEND_ARG_INFO(0, cc) + ZEND_ARG_INFO(0, bcc) + ZEND_ARG_INFO(0, rpath) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_search, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, criteria) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_alerts, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_last_error, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mime_header_decode, 0, 0, 1) + ZEND_ARG_INFO(0, str) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_thread, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_timeout, 0, 0, 1) + ZEND_ARG_INFO(0, timeout_type) + ZEND_ARG_INFO(0, timeout) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ imap_functions[] + */ +const zend_function_entry imap_functions[] = { + PHP_FE(imap_open, arginfo_imap_open) + PHP_FE(imap_reopen, arginfo_imap_reopen) + PHP_FE(imap_close, arginfo_imap_close) + PHP_FE(imap_num_msg, arginfo_imap_num_msg) + PHP_FE(imap_num_recent, arginfo_imap_num_recent) + PHP_FE(imap_headers, arginfo_imap_headers) + PHP_FE(imap_headerinfo, arginfo_imap_headerinfo) + PHP_FE(imap_rfc822_parse_headers, arginfo_imap_rfc822_parse_headers) + PHP_FE(imap_rfc822_write_address, arginfo_imap_rfc822_write_address) + PHP_FE(imap_rfc822_parse_adrlist, arginfo_imap_rfc822_parse_adrlist) + PHP_FE(imap_body, arginfo_imap_body) + PHP_FE(imap_bodystruct, arginfo_imap_bodystruct) + PHP_FE(imap_fetchbody, arginfo_imap_fetchbody) + PHP_FE(imap_fetchmime, arginfo_imap_fetchbody) + PHP_FE(imap_savebody, arginfo_imap_savebody) + PHP_FE(imap_fetchheader, arginfo_imap_fetchheader) + PHP_FE(imap_fetchstructure, arginfo_imap_fetchstructure) + PHP_FE(imap_gc, arginfo_imap_gc) + PHP_FE(imap_expunge, arginfo_imap_expunge) + PHP_FE(imap_delete, arginfo_imap_delete) + PHP_FE(imap_undelete, arginfo_imap_undelete) + PHP_FE(imap_check, arginfo_imap_check) + PHP_FE(imap_listscan, arginfo_imap_listscan) + PHP_FE(imap_mail_copy, arginfo_imap_mail_copy) + PHP_FE(imap_mail_move, arginfo_imap_mail_move) + PHP_FE(imap_mail_compose, arginfo_imap_mail_compose) + PHP_FE(imap_createmailbox, arginfo_imap_createmailbox) + PHP_FE(imap_renamemailbox, arginfo_imap_renamemailbox) + PHP_FE(imap_deletemailbox, arginfo_imap_deletemailbox) + PHP_FE(imap_subscribe, arginfo_imap_subscribe) + PHP_FE(imap_unsubscribe, arginfo_imap_unsubscribe) + PHP_FE(imap_append, arginfo_imap_append) + PHP_FE(imap_ping, arginfo_imap_ping) + PHP_FE(imap_base64, arginfo_imap_base64) + PHP_FE(imap_qprint, arginfo_imap_qprint) + PHP_FE(imap_8bit, arginfo_imap_8bit) + PHP_FE(imap_binary, arginfo_imap_binary) + PHP_FE(imap_utf8, arginfo_imap_utf8) + PHP_FE(imap_status, arginfo_imap_status) + PHP_FE(imap_mailboxmsginfo, arginfo_imap_mailboxmsginfo) + PHP_FE(imap_setflag_full, arginfo_imap_setflag_full) + PHP_FE(imap_clearflag_full, arginfo_imap_clearflag_full) + PHP_FE(imap_sort, arginfo_imap_sort) + PHP_FE(imap_uid, arginfo_imap_uid) + PHP_FE(imap_msgno, arginfo_imap_msgno) + PHP_FE(imap_list, arginfo_imap_list) + PHP_FE(imap_lsub, arginfo_imap_lsub) + PHP_FE(imap_fetch_overview, arginfo_imap_fetch_overview) + PHP_FE(imap_alerts, arginfo_imap_alerts) + PHP_FE(imap_errors, arginfo_imap_errors) + PHP_FE(imap_last_error, arginfo_imap_last_error) + PHP_FE(imap_search, arginfo_imap_search) + PHP_FE(imap_utf7_decode, arginfo_imap_utf7_decode) + PHP_FE(imap_utf7_encode, arginfo_imap_utf7_encode) +#ifdef HAVE_IMAP_MUTF7 + PHP_FE(imap_utf8_to_mutf7, arginfo_imap_utf8_to_mutf7) + PHP_FE(imap_mutf7_to_utf8, arginfo_imap_mutf7_to_utf8) +#endif + PHP_FE(imap_mime_header_decode, arginfo_imap_mime_header_decode) + PHP_FE(imap_thread, arginfo_imap_thread) + PHP_FE(imap_timeout, arginfo_imap_timeout) + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + PHP_FE(imap_get_quota, arginfo_imap_get_quota) + PHP_FE(imap_get_quotaroot, arginfo_imap_get_quotaroot) + PHP_FE(imap_set_quota, arginfo_imap_set_quota) + PHP_FE(imap_setacl, arginfo_imap_setacl) + PHP_FE(imap_getacl, arginfo_imap_getacl) +#endif + + PHP_FE(imap_mail, arginfo_imap_mail) + + PHP_FALIAS(imap_header, imap_headerinfo, arginfo_imap_headerinfo) + PHP_FALIAS(imap_listmailbox, imap_list, arginfo_imap_list) + PHP_FALIAS(imap_getmailboxes, imap_list_full, arginfo_imap_getmailboxes) + PHP_FALIAS(imap_scanmailbox, imap_listscan, arginfo_imap_listscan) + PHP_FALIAS(imap_listsubscribed, imap_lsub, arginfo_imap_lsub) + PHP_FALIAS(imap_getsubscribed, imap_lsub_full, arginfo_imap_getsubscribed) + PHP_FALIAS(imap_fetchtext, imap_body, arginfo_imap_body) + PHP_FALIAS(imap_scan, imap_listscan, arginfo_imap_listscan) + PHP_FALIAS(imap_create, imap_createmailbox, arginfo_imap_createmailbox) + PHP_FALIAS(imap_rename, imap_renamemailbox, arginfo_imap_renamemailbox) + PHP_FE_END +}; +/* }}} */ + +/* {{{ imap dependencies */ +static const zend_module_dep imap_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_END +}; +/* }}} */ + +/* {{{ imap_module_entry + */ +zend_module_entry imap_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + imap_deps, + "imap", + imap_functions, + PHP_MINIT(imap), + NULL, + PHP_RINIT(imap), + PHP_RSHUTDOWN(imap), + PHP_MINFO(imap), + PHP_IMAP_VERSION, + PHP_MODULE_GLOBALS(imap), + PHP_GINIT(imap), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_IMAP +ZEND_GET_MODULE(imap) +#endif + +/* True globals, no need for thread safety */ +static int le_imap; + +#define PHP_IMAP_CHECK_MSGNO(msgindex) \ + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { \ + php_error_docref(NULL, E_WARNING, "Bad message number"); \ + RETURN_FALSE; \ + } \ + +/* {{{ mail_close_it + */ +static void mail_close_it(zend_resource *rsrc) +{ + pils *imap_le_struct = (pils *)rsrc->ptr; + + /* Do not try to close prototype streams */ + if (!(imap_le_struct->flags & OP_PROTOTYPE)) { + mail_close_full(imap_le_struct->imap_stream, imap_le_struct->flags); + } + + if (IMAPG(imap_user)) { + efree(IMAPG(imap_user)); + IMAPG(imap_user) = 0; + } + if (IMAPG(imap_password)) { + efree(IMAPG(imap_password)); + IMAPG(imap_password) = 0; + } + + efree(imap_le_struct); +} +/* }}} */ + +/* {{{ add_assoc_object + */ +static zval *add_assoc_object(zval *arg, char *key, zval *tmp) +{ + HashTable *symtable; + + if (Z_TYPE_P(arg) == IS_OBJECT) { + symtable = Z_OBJPROP_P(arg); + } else { + symtable = Z_ARRVAL_P(arg); + } + return zend_hash_str_update(symtable, key, strlen(key), tmp); +} +/* }}} */ + +/* {{{ add_next_index_object + */ +static inline zval *add_next_index_object(zval *arg, zval *tmp) +{ + HashTable *symtable; + + if (Z_TYPE_P(arg) == IS_OBJECT) { + symtable = Z_OBJPROP_P(arg); + } else { + symtable = Z_ARRVAL_P(arg); + } + + return zend_hash_next_index_insert(symtable, tmp); +} +/* }}} */ + +/* {{{ mail_newfolderobjectlist + * + * Mail instantiate FOBJECTLIST + * Returns: new FOBJECTLIST list + * Author: CJH + */ +FOBJECTLIST *mail_newfolderobjectlist(void) +{ + return (FOBJECTLIST *) memset(fs_get(sizeof(FOBJECTLIST)), 0, sizeof(FOBJECTLIST)); +} +/* }}} */ + +/* {{{ mail_free_foblist + * + * Mail garbage collect FOBJECTLIST + * Accepts: pointer to FOBJECTLIST pointer + * Author: CJH + */ +void mail_free_foblist(FOBJECTLIST **foblist, FOBJECTLIST **tail) +{ + FOBJECTLIST *cur, *next; + + for (cur=*foblist, next=cur->next; cur; cur=next) { + next = cur->next; + + if(cur->text.data) + fs_give((void **)&(cur->text.data)); + + fs_give((void **)&cur); + } + + *tail = NIL; + *foblist = NIL; +} +/* }}} */ + +/* {{{ mail_newerrorlist + * + * Mail instantiate ERRORLIST + * Returns: new ERRORLIST list + * Author: CJH + */ +ERRORLIST *mail_newerrorlist(void) +{ + return (ERRORLIST *) memset(fs_get(sizeof(ERRORLIST)), 0, sizeof(ERRORLIST)); +} +/* }}} */ + +/* {{{ mail_free_errorlist + * + * Mail garbage collect FOBJECTLIST + * Accepts: pointer to FOBJECTLIST pointer + * Author: CJH + */ +void mail_free_errorlist(ERRORLIST **errlist) +{ + if (*errlist) { /* only free if exists */ + if ((*errlist)->text.data) { + fs_give((void **) &(*errlist)->text.data); + } + mail_free_errorlist (&(*errlist)->next); + fs_give((void **) errlist); /* return string to free storage */ + } +} +/* }}} */ + +/* {{{ mail_newmessagelist + * + * Mail instantiate MESSAGELIST + * Returns: new MESSAGELIST list + * Author: CJH + */ +MESSAGELIST *mail_newmessagelist(void) +{ + return (MESSAGELIST *) memset(fs_get(sizeof(MESSAGELIST)), 0, sizeof(MESSAGELIST)); +} +/* }}} */ + +/* {{{ mail_free_messagelist + * + * Mail garbage collect MESSAGELIST + * Accepts: pointer to MESSAGELIST pointer + * Author: CJH + */ +void mail_free_messagelist(MESSAGELIST **msglist, MESSAGELIST **tail) +{ + MESSAGELIST *cur, *next; + + for (cur = *msglist, next = cur->next; cur; cur = next) { + next = cur->next; + fs_give((void **)&cur); + } + + *tail = NIL; + *msglist = NIL; +} +/* }}} */ + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +/* {{{ mail_getquota + * + * Mail GET_QUOTA callback + * Called via the mail_parameter function in c-client:src/c-client/mail.c + * Author DRK + */ + +void mail_getquota(MAILSTREAM *stream, char *qroot, QUOTALIST *qlist) +{ + zval t_map, *return_value; + + return_value = *IMAPG(quota_return); + +/* put parsing code here */ + for(; qlist; qlist = qlist->next) { + array_init(&t_map); + if (strncmp(qlist->name, "STORAGE", 7) == 0) + { + /* this is to add backwards compatibility */ + add_assoc_long_ex(return_value, "usage", sizeof("usage") - 1, qlist->usage); + add_assoc_long_ex(return_value, "limit", sizeof("limit") - 1, qlist->limit); + } + + add_assoc_long_ex(&t_map, "usage", sizeof("usage") - 1, qlist->usage); + add_assoc_long_ex(&t_map, "limit", sizeof("limit") - 1, qlist->limit); + add_assoc_zval_ex(return_value, qlist->name, strlen(qlist->name), &t_map); + } +} +/* }}} */ + +/* {{{ mail_getquota + * + * Mail GET_ACL callback + * Called via the mail_parameter function in c-client:src/c-client/mail.c + */ +void mail_getacl(MAILSTREAM *stream, char *mailbox, ACLLIST *alist) +{ + + /* walk through the ACLLIST */ + for(; alist; alist = alist->next) { + add_assoc_stringl(IMAPG(imap_acl_list), alist->identifier, alist->rights, strlen(alist->rights)); + } +} +/* }}} */ +#endif + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(imap) +{ + imap_globals->imap_user = NIL; + imap_globals->imap_password = NIL; + + imap_globals->imap_alertstack = NIL; + imap_globals->imap_errorstack = NIL; + + imap_globals->imap_folders = NIL; + imap_globals->imap_folders_tail = NIL; + imap_globals->imap_sfolders = NIL; + imap_globals->imap_sfolders_tail = NIL; + imap_globals->imap_messages = NIL; + imap_globals->imap_messages_tail = NIL; + imap_globals->imap_folder_objects = NIL; + imap_globals->imap_folder_objects_tail = NIL; + imap_globals->imap_sfolder_objects = NIL; + imap_globals->imap_sfolder_objects_tail = NIL; + + imap_globals->folderlist_style = FLIST_ARRAY; +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + imap_globals->quota_return = NIL; + imap_globals->imap_acl_list = NIL; +#endif + imap_globals->gets_stream = NIL; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(imap) +{ + unsigned long sa_all = SA_MESSAGES | SA_RECENT | SA_UNSEEN | SA_UIDNEXT | SA_UIDVALIDITY; + +#ifndef PHP_WIN32 + mail_link(&unixdriver); /* link in the unix driver */ + mail_link(&mhdriver); /* link in the mh driver */ + /* mail_link(&mxdriver); */ /* According to c-client docs (internal.txt) this shouldn't be used. */ + mail_link(&mmdfdriver); /* link in the mmdf driver */ + mail_link(&newsdriver); /* link in the news driver */ + mail_link(&philedriver); /* link in the phile driver */ +#endif + mail_link(&imapdriver); /* link in the imap driver */ + mail_link(&nntpdriver); /* link in the nntp driver */ + mail_link(&pop3driver); /* link in the pop3 driver */ + mail_link(&mbxdriver); /* link in the mbx driver */ + mail_link(&tenexdriver); /* link in the tenex driver */ + mail_link(&mtxdriver); /* link in the mtx driver */ + mail_link(&dummydriver); /* link in the dummy driver */ + +#ifndef PHP_WIN32 + auth_link(&auth_log); /* link in the log authenticator */ + auth_link(&auth_md5); /* link in the cram-md5 authenticator */ +#if HAVE_IMAP_KRB && defined(HAVE_IMAP_AUTH_GSS) + auth_link(&auth_gss); /* link in the gss authenticator */ +#endif + auth_link(&auth_pla); /* link in the plain authenticator */ +#endif + +#ifdef HAVE_IMAP_SSL + ssl_onceonlyinit (); +#endif + + /* lets allow NIL */ + REGISTER_LONG_CONSTANT("NIL", NIL, CONST_PERSISTENT | CONST_CS); + + /* plug in our gets */ + mail_parameters(NIL, SET_GETS, (void *) NIL); + + /* set default timeout values */ + mail_parameters(NIL, SET_OPENTIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_READTIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_WRITETIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_CLOSETIMEOUT, (void *) FG(default_socket_timeout)); + + /* timeout constants */ + REGISTER_LONG_CONSTANT("IMAP_OPENTIMEOUT", 1, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_READTIMEOUT", 2, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_WRITETIMEOUT", 3, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_CLOSETIMEOUT", 4, CONST_PERSISTENT | CONST_CS); + + /* Open Options */ + + REGISTER_LONG_CONSTANT("OP_DEBUG", OP_DEBUG, CONST_PERSISTENT | CONST_CS); + /* debug protocol negotiations */ + REGISTER_LONG_CONSTANT("OP_READONLY", OP_READONLY, CONST_PERSISTENT | CONST_CS); + /* read-only open */ + REGISTER_LONG_CONSTANT("OP_ANONYMOUS", OP_ANONYMOUS, CONST_PERSISTENT | CONST_CS); + /* anonymous open of newsgroup */ + REGISTER_LONG_CONSTANT("OP_SHORTCACHE", OP_SHORTCACHE, CONST_PERSISTENT | CONST_CS); + /* short (elt-only) caching */ + REGISTER_LONG_CONSTANT("OP_SILENT", OP_SILENT, CONST_PERSISTENT | CONST_CS); + /* don't pass up events (internal use) */ + REGISTER_LONG_CONSTANT("OP_PROTOTYPE", OP_PROTOTYPE, CONST_PERSISTENT | CONST_CS); + /* return driver prototype */ + REGISTER_LONG_CONSTANT("OP_HALFOPEN", OP_HALFOPEN, CONST_PERSISTENT | CONST_CS); + /* half-open (IMAP connect but no select) */ + REGISTER_LONG_CONSTANT("OP_EXPUNGE", OP_EXPUNGE, CONST_PERSISTENT | CONST_CS); + /* silently expunge recycle stream */ + REGISTER_LONG_CONSTANT("OP_SECURE", OP_SECURE, CONST_PERSISTENT | CONST_CS); + /* don't do non-secure authentication */ + + /* + PHP re-assigns CL_EXPUNGE a custom value that can be used as part of the imap_open() bitfield + because it seems like a good idea to be able to indicate that the mailbox should be + automatically expunged during imap_open in case the script get interrupted and it doesn't get + to the imap_close() where this option is normally placed. If the c-client library adds other + options and the value for this one conflicts, simply make PHP_EXPUNGE higher at the top of + this file + */ + REGISTER_LONG_CONSTANT("CL_EXPUNGE", PHP_EXPUNGE, CONST_PERSISTENT | CONST_CS); + /* expunge silently */ + + /* Fetch options */ + + REGISTER_LONG_CONSTANT("FT_UID", FT_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID */ + REGISTER_LONG_CONSTANT("FT_PEEK", FT_PEEK, CONST_PERSISTENT | CONST_CS); + /* peek at data */ + REGISTER_LONG_CONSTANT("FT_NOT", FT_NOT, CONST_PERSISTENT | CONST_CS); + /* NOT flag for header lines fetch */ + REGISTER_LONG_CONSTANT("FT_INTERNAL", FT_INTERNAL, CONST_PERSISTENT | CONST_CS); + /* text can be internal strings */ + REGISTER_LONG_CONSTANT("FT_PREFETCHTEXT", FT_PREFETCHTEXT, CONST_PERSISTENT | CONST_CS); + /* IMAP prefetch text when fetching header */ + + /* Flagging options */ + + REGISTER_LONG_CONSTANT("ST_UID", ST_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID sequence */ + REGISTER_LONG_CONSTANT("ST_SILENT", ST_SILENT, CONST_PERSISTENT | CONST_CS); + /* don't return results */ + REGISTER_LONG_CONSTANT("ST_SET", ST_SET, CONST_PERSISTENT | CONST_CS); + /* set vs. clear */ + + /* Copy options */ + + REGISTER_LONG_CONSTANT("CP_UID", CP_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID sequence */ + REGISTER_LONG_CONSTANT("CP_MOVE", CP_MOVE, CONST_PERSISTENT | CONST_CS); + /* delete from source after copying */ + + /* Search/sort options */ + + REGISTER_LONG_CONSTANT("SE_UID", SE_UID, CONST_PERSISTENT | CONST_CS); + /* return UID */ + REGISTER_LONG_CONSTANT("SE_FREE", SE_FREE, CONST_PERSISTENT | CONST_CS); + /* free search program after finished */ + REGISTER_LONG_CONSTANT("SE_NOPREFETCH", SE_NOPREFETCH, CONST_PERSISTENT | CONST_CS); + /* no search prefetching */ + REGISTER_LONG_CONSTANT("SO_FREE", SO_FREE, CONST_PERSISTENT | CONST_CS); + /* free sort program after finished */ + REGISTER_LONG_CONSTANT("SO_NOSERVER", SO_NOSERVER, CONST_PERSISTENT | CONST_CS); + /* don't do server-based sort */ + + /* Status options */ + + REGISTER_LONG_CONSTANT("SA_MESSAGES", SA_MESSAGES , CONST_PERSISTENT | CONST_CS); + /* number of messages */ + REGISTER_LONG_CONSTANT("SA_RECENT", SA_RECENT, CONST_PERSISTENT | CONST_CS); + /* number of recent messages */ + REGISTER_LONG_CONSTANT("SA_UNSEEN", SA_UNSEEN , CONST_PERSISTENT | CONST_CS); + /* number of unseen messages */ + REGISTER_LONG_CONSTANT("SA_UIDNEXT", SA_UIDNEXT, CONST_PERSISTENT | CONST_CS); + /* next UID to be assigned */ + REGISTER_LONG_CONSTANT("SA_UIDVALIDITY", SA_UIDVALIDITY , CONST_PERSISTENT | CONST_CS); + /* UID validity value */ + REGISTER_LONG_CONSTANT("SA_ALL", sa_all, CONST_PERSISTENT | CONST_CS); + /* get all status information */ + + /* Bits for mm_list() and mm_lsub() */ + + REGISTER_LONG_CONSTANT("LATT_NOINFERIORS", LATT_NOINFERIORS , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_NOSELECT", LATT_NOSELECT, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_MARKED", LATT_MARKED, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_UNMARKED", LATT_UNMARKED , CONST_PERSISTENT | CONST_CS); + +#ifdef LATT_REFERRAL + REGISTER_LONG_CONSTANT("LATT_REFERRAL", LATT_REFERRAL, CONST_PERSISTENT | CONST_CS); +#endif + +#ifdef LATT_HASCHILDREN + REGISTER_LONG_CONSTANT("LATT_HASCHILDREN", LATT_HASCHILDREN, CONST_PERSISTENT | CONST_CS); +#endif + +#ifdef LATT_HASNOCHILDREN + REGISTER_LONG_CONSTANT("LATT_HASNOCHILDREN", LATT_HASNOCHILDREN, CONST_PERSISTENT | CONST_CS); +#endif + + /* Sort functions */ + + REGISTER_LONG_CONSTANT("SORTDATE", SORTDATE , CONST_PERSISTENT | CONST_CS); + /* date */ + REGISTER_LONG_CONSTANT("SORTARRIVAL", SORTARRIVAL , CONST_PERSISTENT | CONST_CS); + /* arrival date */ + REGISTER_LONG_CONSTANT("SORTFROM", SORTFROM , CONST_PERSISTENT | CONST_CS); + /* from */ + REGISTER_LONG_CONSTANT("SORTSUBJECT", SORTSUBJECT , CONST_PERSISTENT | CONST_CS); + /* subject */ + REGISTER_LONG_CONSTANT("SORTTO", SORTTO , CONST_PERSISTENT | CONST_CS); + /* to */ + REGISTER_LONG_CONSTANT("SORTCC", SORTCC , CONST_PERSISTENT | CONST_CS); + /* cc */ + REGISTER_LONG_CONSTANT("SORTSIZE", SORTSIZE , CONST_PERSISTENT | CONST_CS); + /* size */ + + REGISTER_LONG_CONSTANT("TYPETEXT", TYPETEXT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMULTIPART", TYPEMULTIPART , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMESSAGE", TYPEMESSAGE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEAPPLICATION", TYPEAPPLICATION , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEAUDIO", TYPEAUDIO , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEIMAGE", TYPEIMAGE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEVIDEO", TYPEVIDEO , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMODEL", TYPEMODEL , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEOTHER", TYPEOTHER , CONST_PERSISTENT | CONST_CS); + /* + TYPETEXT unformatted text + TYPEMULTIPART multiple part + TYPEMESSAGE encapsulated message + TYPEAPPLICATION application data + TYPEAUDIO audio + TYPEIMAGE static image (GIF, JPEG, etc.) + TYPEVIDEO video + TYPEMODEL model + TYPEOTHER unknown + */ + + REGISTER_LONG_CONSTANT("ENC7BIT", ENC7BIT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENC8BIT", ENC8BIT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCBINARY", ENCBINARY , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCBASE64", ENCBASE64, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCQUOTEDPRINTABLE", ENCQUOTEDPRINTABLE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCOTHER", ENCOTHER , CONST_PERSISTENT | CONST_CS); + /* + ENC7BIT 7 bit SMTP semantic data + ENC8BIT 8 bit SMTP semantic data + ENCBINARY 8 bit binary data + ENCBASE64 base-64 encoded data + ENCQUOTEDPRINTABLE human-readable 8-as-7 bit data + ENCOTHER unknown + */ + + REGISTER_LONG_CONSTANT("IMAP_GC_ELT", GC_ELT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_GC_ENV", GC_ENV , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_GC_TEXTS", GC_TEXTS , CONST_PERSISTENT | CONST_CS); + /* + GC_ELT message cache elements + GC_ENV ENVELOPEs and BODYs + GC_TEXTS texts + */ + + le_imap = zend_register_list_destructors_ex(mail_close_it, NULL, "imap", module_number); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(imap) +{ + IMAPG(imap_errorstack) = NIL; + IMAPG(imap_alertstack) = NIL; + IMAPG(gets_stream) = NIL; + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(imap) +{ + ERRORLIST *ecur = NIL; + STRINGLIST *acur = NIL; + + if (IMAPG(imap_errorstack) != NIL) { + /* output any remaining errors at their original error level */ + if (EG(error_reporting) & E_NOTICE) { + ecur = IMAPG(imap_errorstack); + while (ecur != NIL) { + php_error_docref(NULL, E_NOTICE, "%s (errflg=%ld)", ecur->LTEXT, ecur->errflg); + ecur = ecur->next; + } + } + mail_free_errorlist(&IMAPG(imap_errorstack)); + } + + if (IMAPG(imap_alertstack) != NIL) { + /* output any remaining alerts at E_NOTICE level */ + if (EG(error_reporting) & E_NOTICE) { + acur = IMAPG(imap_alertstack); + while (acur != NIL) { + php_error_docref(NULL, E_NOTICE, "%s", acur->LTEXT); + acur = acur->next; + } + } + mail_free_stringlist(&IMAPG(imap_alertstack)); + IMAPG(imap_alertstack) = NIL; + } + return SUCCESS; +} +/* }}} */ + +#if !defined(CCLIENTVERSION) +#if HAVE_IMAP2007e +#define CCLIENTVERSION "2007e" +#elif HAVE_IMAP2007d +#define CCLIENTVERSION "2007d" +#elif HAVE_IMAP2007b +#define CCLIENTVERSION "2007b" +#elif HAVE_IMAP2007a +#define CCLIENTVERSION "2007a" +#elif HAVE_IMAP2004 +#define CCLIENTVERSION "2004" +#elif HAVE_IMAP2001 +#define CCLIENTVERSION "2001" +#elif HAVE_IMAP2000 +#define CCLIENTVERSION "2000" +#elif defined(IMAP41) +#define CCLIENTVERSION "4.1" +#else +#define CCLIENTVERSION "4.0" +#endif +#endif + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(imap) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "IMAP c-Client Version", CCLIENTVERSION); +#if HAVE_IMAP_SSL + php_info_print_table_row(2, "SSL Support", "enabled"); +#endif +#if HAVE_IMAP_KRB && HAVE_IMAP_AUTH_GSS + php_info_print_table_row(2, "Kerberos Support", "enabled"); +#endif + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ imap_do_open + */ +static void php_imap_do_open(INTERNAL_FUNCTION_PARAMETERS, int persistent) +{ + zend_string *mailbox, *user, *passwd; + zend_long retries = 0, flags = NIL, cl_flags = NIL; + MAILSTREAM *imap_stream; + pils *imap_le_struct; + zval *params = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "PSS|lla", &mailbox, &user, + &passwd, &flags, &retries, ¶ms) == FAILURE) { + return; + } + + if (argc >= 4) { + if (flags & PHP_EXPUNGE) { + cl_flags = CL_EXPUNGE; + flags ^= PHP_EXPUNGE; + } + if (flags & OP_PROTOTYPE) { + cl_flags |= OP_PROTOTYPE; + } + } + + if (params) { + zval *disabled_auth_method; + + if ((disabled_auth_method = zend_hash_str_find(Z_ARRVAL_P(params), "DISABLE_AUTHENTICATOR", sizeof("DISABLE_AUTHENTICATOR") - 1)) != NULL) { + switch (Z_TYPE_P(disabled_auth_method)) { + case IS_STRING: + if (Z_STRLEN_P(disabled_auth_method) > 1) { + mail_parameters(NIL, DISABLE_AUTHENTICATOR, (void *)Z_STRVAL_P(disabled_auth_method)); + } + break; + case IS_ARRAY: + { + zval *z_auth_method; + int i; + int nelems = zend_hash_num_elements(Z_ARRVAL_P(disabled_auth_method)); + + if (nelems == 0 ) { + break; + } + for (i = 0; i < nelems; i++) { + if ((z_auth_method = zend_hash_index_find(Z_ARRVAL_P(disabled_auth_method), i)) != NULL) { + if (Z_TYPE_P(z_auth_method) == IS_STRING) { + if (Z_STRLEN_P(z_auth_method) > 1) { + mail_parameters(NIL, DISABLE_AUTHENTICATOR, (void *)Z_STRVAL_P(z_auth_method)); + } + } else { + php_error_docref(NULL, E_WARNING, "Invalid argument, expect string or array of strings"); + } + } + } + } + break; + case IS_LONG: + default: + php_error_docref(NULL, E_WARNING, "Invalid argument, expect string or array of strings"); + break; + } + } + } + + if (IMAPG(imap_user)) { + efree(IMAPG(imap_user)); + IMAPG(imap_user) = 0; + } + + if (IMAPG(imap_password)) { + efree(IMAPG(imap_password)); + IMAPG(imap_password) = 0; + } + + /* local filename, need to perform open_basedir check */ + if (ZSTR_VAL(mailbox)[0] != '{' && php_check_open_basedir(ZSTR_VAL(mailbox))) { + RETURN_FALSE; + } + + IMAPG(imap_user) = estrndup(ZSTR_VAL(user), ZSTR_LEN(user)); + IMAPG(imap_password) = estrndup(ZSTR_VAL(passwd), ZSTR_LEN(passwd)); + +#ifdef SET_MAXLOGINTRIALS + if (argc >= 5) { + if (retries < 0) { + php_error_docref(NULL, E_WARNING ,"Retries must be greater or equal to 0"); + } else { + mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) retries); + } + } +#endif + + imap_stream = mail_open(NIL, ZSTR_VAL(mailbox), flags); + + if (imap_stream == NIL) { + php_error_docref(NULL, E_WARNING, "Couldn't open stream %s", ZSTR_VAL(mailbox)); + efree(IMAPG(imap_user)); IMAPG(imap_user) = 0; + efree(IMAPG(imap_password)); IMAPG(imap_password) = 0; + RETURN_FALSE; + } + + imap_le_struct = emalloc(sizeof(pils)); + imap_le_struct->imap_stream = imap_stream; + imap_le_struct->flags = cl_flags; + + RETURN_RES(zend_register_resource(imap_le_struct, le_imap)); +} +/* }}} */ + +/* {{{ proto resource imap_open(string mailbox, string user, string password [, int options [, int n_retries]]) + Open an IMAP stream to a mailbox */ +PHP_FUNCTION(imap_open) +{ + php_imap_do_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imap_reopen(resource stream_id, string mailbox [, int options [, int n_retries]]) + Reopen an IMAP stream to a new mailbox */ +PHP_FUNCTION(imap_reopen) +{ + zval *streamind; + zend_string *mailbox; + zend_long options = 0, retries = 0; + pils *imap_le_struct; + long flags=NIL; + long cl_flags=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS|ll", &streamind, &mailbox, &options, &retries) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (options) { + flags = options; + if (flags & PHP_EXPUNGE) { + cl_flags = CL_EXPUNGE; + flags ^= PHP_EXPUNGE; + } + imap_le_struct->flags = cl_flags; + } +#ifdef SET_MAXLOGINTRIALS + if (retries) { + mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) retries); + } +#endif + /* local filename, need to perform open_basedir check */ + if (ZSTR_VAL(mailbox)[0] != '{' && php_check_open_basedir(ZSTR_VAL(mailbox))) { + RETURN_FALSE; + } + + imap_le_struct->imap_stream = mail_open(imap_le_struct->imap_stream, ZSTR_VAL(mailbox), flags); + if (imap_le_struct->imap_stream == NIL) { + zend_list_delete(Z_RES_P(streamind)); + php_error_docref(NULL, E_WARNING, "Couldn't re-open stream"); + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_append(resource stream_id, string folder, string message [, string options [, string internal_date]]) + Append a new message to a specified mailbox */ +PHP_FUNCTION(imap_append) +{ + zval *streamind; + zend_string *folder, *message, *internal_date = NULL, *flags = NULL; + pils *imap_le_struct; + STRING st; + zend_string* regex; + pcre_cache_entry *pce; /* Compiled regex */ + zval *subpats = NULL; /* Parts (not used) */ + int global = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS|SS", &streamind, &folder, &message, &flags, &internal_date) == FAILURE) { + return; + } + + regex = zend_string_init("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/", sizeof("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/") - 1, 0); + + if (internal_date) { + /* Make sure the given internal_date string matches the RFC specifiedformat */ + if ((pce = pcre_get_compiled_regex_cache(regex))== NULL) { + zend_string_free(regex); + RETURN_FALSE; + } + + zend_string_free(regex); + php_pcre_match_impl(pce, ZSTR_VAL(internal_date), ZSTR_LEN(internal_date), return_value, subpats, global, + 0, Z_L(0), Z_L(0)); + + if (!Z_LVAL_P(return_value)) { + php_error_docref(NULL, E_WARNING, "internal date not correctly formatted"); + internal_date = NULL; + } + } + + zend_string_free(regex); + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + INIT (&st, mail_string, (void *) ZSTR_VAL(message), ZSTR_LEN(message)); + + if (mail_append_full(imap_le_struct->imap_stream, ZSTR_VAL(folder), (flags ? ZSTR_VAL(flags) : NIL), (internal_date ? ZSTR_VAL(internal_date) : NIL), &st)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imap_num_msg(resource stream_id) + Gives the number of messages in the current mailbox */ +PHP_FUNCTION(imap_num_msg) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->nmsgs); +} +/* }}} */ + +/* {{{ proto bool imap_ping(resource stream_id) + Check if the IMAP stream is still active */ +PHP_FUNCTION(imap_ping) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(mail_ping(imap_le_struct->imap_stream)); +} +/* }}} */ + +/* {{{ proto int imap_num_recent(resource stream_id) + Gives the number of recent messages in current mailbox */ +PHP_FUNCTION(imap_num_recent) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->recent); +} +/* }}} */ + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +/* {{{ proto array imap_get_quota(resource stream_id, string qroot) + Returns the quota set to the mailbox account qroot */ +PHP_FUNCTION(imap_get_quota) +{ + zval *streamind; + zend_string *qroot; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &qroot) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTA function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquota failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_get_quotaroot(resource stream_id, string mbox) + Returns the quota set to the mailbox account mbox */ +PHP_FUNCTION(imap_get_quotaroot) +{ + zval *streamind; + zend_string *mbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTAROOT function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquotaroot(imap_le_struct->imap_stream, ZSTR_VAL(mbox))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquotaroot failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_set_quota(resource stream_id, string qroot, int mailbox_size) + Will set the quota for qroot mailbox */ +PHP_FUNCTION(imap_set_quota) +{ + zval *streamind; + zend_string *qroot; + zend_long mailbox_size; + pils *imap_le_struct; + STRINGLIST limits; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &streamind, &qroot, &mailbox_size) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + limits.text.data = (unsigned char*)"STORAGE"; + limits.text.size = mailbox_size; + limits.next = NIL; + + RETURN_BOOL(imap_setquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot), &limits)); +} +/* }}} */ + +/* {{{ proto bool imap_setacl(resource stream_id, string mailbox, string id, string rights) + Sets the ACL for a given mailbox */ +PHP_FUNCTION(imap_setacl) +{ + zval *streamind; + zend_string *mailbox, *id, *rights; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &mailbox, &id, &rights) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(imap_setacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox), ZSTR_VAL(id), ZSTR_VAL(rights))); +} +/* }}} */ + +/* {{{ proto array imap_getacl(resource stream_id, string mailbox) + Gets the ACL for a given mailbox */ +PHP_FUNCTION(imap_getacl) +{ + zval *streamind; + zend_string *mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* initializing the special array for the return values */ + array_init(return_value); + + IMAPG(imap_acl_list) = return_value; + + /* set the callback for the GET_ACL function */ + mail_parameters(NIL, SET_ACL, (void *) mail_getacl); + if (!imap_getacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox))) { + php_error(E_WARNING, "c-client imap_getacl failed"); + zval_dtor(return_value); + RETURN_FALSE; + } + + IMAPG(imap_acl_list) = NIL; +} +/* }}} */ +#endif /* HAVE_IMAP2000 || HAVE_IMAP2001 */ + +/* {{{ proto bool imap_expunge(resource stream_id) + Permanently delete all messages marked for deletion */ +PHP_FUNCTION(imap_expunge) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_expunge (imap_le_struct->imap_stream); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_gc(resource stream_id, int flags) + This function garbage collects (purges) the cache of entries of a specific type. */ +PHP_FUNCTION(imap_gc) +{ + zval *streamind; + pils *imap_le_struct; + zend_long flags; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(GC_TEXTS | GC_ELT | GC_ENV)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_gc(imap_le_struct->imap_stream, flags); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_close(resource stream_id [, int options]) + Close an IMAP stream */ +PHP_FUNCTION(imap_close) +{ + zval *streamind; + pils *imap_le_struct=NULL; + zend_long options = 0, flags = NIL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &streamind, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc == 2) { + flags = options; + + /* Check that flags is exactly equal to PHP_EXPUNGE or zero */ + if (flags && ((flags & ~PHP_EXPUNGE) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + /* Do the translation from PHP's internal PHP_EXPUNGE define to c-client's CL_EXPUNGE */ + if (flags & PHP_EXPUNGE) { + flags ^= PHP_EXPUNGE; + flags |= CL_EXPUNGE; + } + imap_le_struct->flags = flags; + } + + zend_list_close(Z_RES_P(streamind)); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imap_headers(resource stream_id) + Returns headers for all messages in a mailbox */ +PHP_FUNCTION(imap_headers) +{ + zval *streamind; + pils *imap_le_struct; + unsigned long i; + char *t; + unsigned int msgno; + char tmp[MAILTMPLEN]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* Initialize return array */ + array_init(return_value); + + for (msgno = 1; msgno <= imap_le_struct->imap_stream->nmsgs; msgno++) { + MESSAGECACHE * cache = mail_elt (imap_le_struct->imap_stream, msgno); + mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL); + tmp[0] = cache->recent ? (cache->seen ? 'R': 'N') : ' '; + tmp[1] = (cache->recent | cache->seen) ? ' ' : 'U'; + tmp[2] = cache->flagged ? 'F' : ' '; + tmp[3] = cache->answered ? 'A' : ' '; + tmp[4] = cache->deleted ? 'D' : ' '; + tmp[5] = cache->draft ? 'X' : ' '; + snprintf(tmp + 6, sizeof(tmp) - 6, "%4ld) ", cache->msgno); + mail_date(tmp+11, cache); + tmp[22] = ' '; + tmp[23] = '\0'; + mail_fetchfrom(tmp+23, imap_le_struct->imap_stream, msgno, (long)20); + strcat(tmp, " "); + if ((i = cache->user_flags)) { + strcat(tmp, "{"); + while (i) { + strlcat(tmp, imap_le_struct->imap_stream->user_flags[find_rightmost_bit (&i)], sizeof(tmp)); + if (i) strlcat(tmp, " ", sizeof(tmp)); + } + strlcat(tmp, "} ", sizeof(tmp)); + } + mail_fetchsubject(t = tmp + strlen(tmp), imap_le_struct->imap_stream, msgno, (long)25); + snprintf(t += strlen(t), sizeof(tmp) - strlen(tmp), " (%ld chars)", cache->rfc822_size); + add_next_index_string(return_value, tmp); + } +} +/* }}} */ + +/* {{{ proto string imap_body(resource stream_id, int msg_no [, int options]) + Read the message body */ +PHP_FUNCTION(imap_body) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + int msgindex, argc = ZEND_NUM_ARGS(); + char *body; + unsigned long body_len = 0; + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + body = mail_fetchtext_full (imap_le_struct->imap_stream, msgno, &body_len, (argc == 3 ? flags : NIL)); + if (body_len == 0) { + RETVAL_EMPTY_STRING(); + } else { + RETVAL_STRINGL(body, body_len); + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_copy(resource stream_id, string msglist, string mailbox [, int options]) + Copy specified message to a mailbox */ +PHP_FUNCTION(imap_mail_copy) +{ + zval *streamind; + zend_long options = 0; + zend_string *seq, *folder; + int argc = ZEND_NUM_ARGS(); + pils *imap_le_struct; + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? options : NIL)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_move(resource stream_id, string sequence, string mailbox [, int options]) + Move specified message to a mailbox */ +PHP_FUNCTION(imap_mail_move) +{ + zval *streamind; + zend_string *seq, *folder; + zend_long options = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? (options | CP_MOVE) : CP_MOVE)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_createmailbox(resource stream_id, string mailbox) + Create a new mailbox */ +PHP_FUNCTION(imap_createmailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_create(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_renamemailbox(resource stream_id, string old_name, string new_name) + Rename a mailbox */ +PHP_FUNCTION(imap_renamemailbox) +{ + zval *streamind; + zend_string *old_mailbox, *new_mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &old_mailbox, &new_mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_rename(imap_le_struct->imap_stream, ZSTR_VAL(old_mailbox), ZSTR_VAL(new_mailbox)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_deletemailbox(resource stream_id, string mailbox) + Delete a mailbox */ +PHP_FUNCTION(imap_deletemailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_delete(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_list(resource stream_id, string ref, string pattern) + Read the list of mailboxes */ +PHP_FUNCTION(imap_list) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto array imap_getmailboxes(resource stream_id, string ref, string pattern) + Reads the list of mailboxes and returns a full array of objects containing name, attributes, and delimiter */ +/* Author: CJH */ +PHP_FUNCTION(imap_list_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects mailbox list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_folder_objects) = IMAPG(imap_folder_objects_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_folder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist(&IMAPG(imap_folder_objects), &IMAPG(imap_folder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto array imap_listscan(resource stream_id, string ref, string pattern, string content) + Read list of mailboxes containing a certain string */ +PHP_FUNCTION(imap_listscan) +{ + zval *streamind; + zend_string *ref, *pat, *content; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &ref, &pat, &content) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + IMAPG(imap_folders) = NIL; + mail_scan(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat), ZSTR_VAL(content)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto object imap_check(resource stream_id) + Get mailbox properties */ +PHP_FUNCTION(imap_check) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_ping (imap_le_struct->imap_stream) == NIL) { + RETURN_FALSE; + } + + if (imap_le_struct->imap_stream && imap_le_struct->imap_stream->mailbox) { + rfc822_date(date); + object_init(return_value); + add_property_string(return_value, "Date", date); + add_property_string(return_value, "Driver", imap_le_struct->imap_stream->dtb->name); + add_property_string(return_value, "Mailbox", imap_le_struct->imap_stream->mailbox); + add_property_long(return_value, "Nmsgs", imap_le_struct->imap_stream->nmsgs); + add_property_long(return_value, "Recent", imap_le_struct->imap_stream->recent); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_delete(resource stream_id, int msg_no [, int options]) + Mark a message for deletion */ +PHP_FUNCTION(imap_delete) +{ + zval *streamind, *sequence; + pils *imap_le_struct; + zend_long flags = 0; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_setflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_undelete(resource stream_id, int msg_no [, int flags]) + Remove the delete flag from a message */ +PHP_FUNCTION(imap_undelete) +{ + zval *streamind, *sequence; + zend_long flags = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_clearflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto object imap_headerinfo(resource stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]]) + Read the headers of the message */ +PHP_FUNCTION(imap_headerinfo) +{ + zval *streamind; + zend_string *defaulthost = NULL; + int argc = ZEND_NUM_ARGS(); + zend_long msgno, fromlength, subjectlength; + pils *imap_le_struct; + MESSAGECACHE *cache; + ENVELOPE *en; + char dummy[2000], fulladdress[MAILTMPLEN + 1]; + + if (zend_parse_parameters(argc, "rl|llS", &streamind, &msgno, &fromlength, &subjectlength, &defaulthost) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc >= 3) { + if (fromlength < 0 || fromlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "From length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + fromlength = 0x00; + } + if (argc >= 4) { + if (subjectlength < 0 || subjectlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "Subject length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + subjectlength = 0x00; + } + + PHP_IMAP_CHECK_MSGNO(msgno); + + if (mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL)) { + cache = mail_elt(imap_le_struct->imap_stream, msgno); + } else { + RETURN_FALSE; + } + + en = mail_fetchenvelope(imap_le_struct->imap_stream, msgno); + + /* call a function to parse all the text, so that we can use the + same function to parse text from other sources */ + _php_make_header_object(return_value, en); + + /* now run through properties that are only going to be returned + from a server, not text headers */ + add_property_string(return_value, "Recent", cache->recent ? (cache->seen ? "R": "N") : " "); + add_property_string(return_value, "Unseen", (cache->recent | cache->seen) ? " " : "U"); + add_property_string(return_value, "Flagged", cache->flagged ? "F" : " "); + add_property_string(return_value, "Answered", cache->answered ? "A" : " "); + add_property_string(return_value, "Deleted", cache->deleted ? "D" : " "); + add_property_string(return_value, "Draft", cache->draft ? "X" : " "); + + snprintf(dummy, sizeof(dummy), "%4ld", cache->msgno); + add_property_string(return_value, "Msgno", dummy); + + mail_date(dummy, cache); + add_property_string(return_value, "MailDate", dummy); + + snprintf(dummy, sizeof(dummy), "%ld", cache->rfc822_size); + add_property_string(return_value, "Size", dummy); + + add_property_long(return_value, "udate", mail_longdate(cache)); + + if (en->from && fromlength) { + fulladdress[0] = 0x00; + mail_fetchfrom(fulladdress, imap_le_struct->imap_stream, msgno, fromlength); + add_property_string(return_value, "fetchfrom", fulladdress); + } + if (en->subject && subjectlength) { + fulladdress[0] = 0x00; + mail_fetchsubject(fulladdress, imap_le_struct->imap_stream, msgno, subjectlength); + add_property_string(return_value, "fetchsubject", fulladdress); + } +} +/* }}} */ + +/* {{{ proto object imap_rfc822_parse_headers(string headers [, string default_host]) + Parse a set of mail headers contained in a string, and return an object similar to imap_headerinfo() */ +PHP_FUNCTION(imap_rfc822_parse_headers) +{ + zend_string *headers, *defaulthost = NULL; + ENVELOPE *en; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "S|S", &headers, &defaulthost) == FAILURE) { + return; + } + + if (argc == 2) { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, ZSTR_VAL(defaulthost), NIL); + } else { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, "UNKNOWN", NIL); + } + + /* call a function to parse all the text, so that we can use the + same function no matter where the headers are from */ + _php_make_header_object(return_value, en); + mail_free_envelope(&en); +} +/* }}} */ + +/* KMLANG */ +/* {{{ proto array imap_lsub(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes */ +PHP_FUNCTION(imap_lsub) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_sfolders) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_sfolders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_sfolders)); + IMAPG(imap_sfolders) = IMAPG(imap_sfolders_tail) = NIL; +} +/* }}} */ + +/* {{{ proto array imap_getsubscribed(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes, in the same format as imap_getmailboxes() */ +/* Author: CJH */ +PHP_FUNCTION(imap_lsub_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_sfolder_objects) = IMAPG(imap_sfolder_objects_tail) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_sfolder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist (&IMAPG(imap_sfolder_objects), &IMAPG(imap_sfolder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto bool imap_subscribe(resource stream_id, string mailbox) + Subscribe to a mailbox */ +PHP_FUNCTION(imap_subscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_subscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_unsubscribe(resource stream_id, string mailbox) + Unsubscribe from a mailbox */ +PHP_FUNCTION(imap_unsubscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_unsubscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object imap_fetchstructure(resource stream_id, int msg_no [, int options]) + Read the full structure of a message */ +PHP_FUNCTION(imap_fetchstructure) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + BODY *body; + int msgindex, argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~FT_UID) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (msgno < 1) { + RETURN_FALSE; + } + + object_init(return_value); + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + PHP_IMAP_CHECK_MSGNO(msgindex); + + mail_fetchstructure_full(imap_le_struct->imap_stream, msgno, &body , (argc == 3 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + + _php_imap_add_body(return_value, body); +} +/* }}} */ + +/* {{{ proto string imap_fetchbody(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section */ +PHP_FUNCTION(imap_fetchbody) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetchbody_full(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + + +/* {{{ proto string imap_fetchmime(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section's MIME headers */ +PHP_FUNCTION(imap_fetchmime) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetch_mime(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body MIME information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + +/* {{{ proto bool imap_savebody(resource stream_id, string|resource file, int msg_no[, string section = ""[, int options = 0]]) + Save a specific body section to a file */ +PHP_FUNCTION(imap_savebody) +{ + zval *stream, *out; + pils *imap_ptr = NULL; + php_stream *writer = NULL; + zend_string *section = NULL; + int close_stream = 1; + zend_long msgno, flags = 0; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "rzl|Sl", &stream, &out, &msgno, §ion, &flags)) { + RETURN_FALSE; + } + + if ((imap_ptr = (pils *)zend_fetch_resource(Z_RES_P(stream), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (!imap_ptr) { + RETURN_FALSE; + } + + switch (Z_TYPE_P(out)) + { + case IS_LONG: + case IS_RESOURCE: + close_stream = 0; + php_stream_from_zval(writer, out); + break; + + default: + convert_to_string_ex(out); + writer = php_stream_open_wrapper(Z_STRVAL_P(out), "wb", REPORT_ERRORS, NULL); + break; + } + + if (!writer) { + RETURN_FALSE; + } + + IMAPG(gets_stream) = writer; + mail_parameters(NIL, SET_GETS, (void *) php_mail_gets); + mail_fetchbody_full(imap_ptr->imap_stream, msgno, section?ZSTR_VAL(section):"", NULL, flags); + mail_parameters(NIL, SET_GETS, (void *) NULL); + IMAPG(gets_stream) = NULL; + + if (close_stream) { + php_stream_close(writer); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string imap_base64(string text) + Decode BASE64 encoded text */ +PHP_FUNCTION(imap_base64) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_base64((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_qprint(string text) + Convert a quoted-printable string to an 8-bit string */ +PHP_FUNCTION(imap_qprint) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_qprint((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_8bit(string text) + Convert an 8-bit string to a quoted-printable string */ +PHP_FUNCTION(imap_8bit) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_8bit((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_binary(string text) + Convert an 8bit string to a base64 string */ +PHP_FUNCTION(imap_binary) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char*)rfc822_binary(ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto object imap_mailboxmsginfo(resource stream_id) + Returns info about the current mailbox */ +PHP_FUNCTION(imap_mailboxmsginfo) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + unsigned long msgno; + zend_ulong unreadmsg = 0, deletedmsg = 0, msize = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* Initialize return object */ + object_init(return_value); + + for (msgno = 1; msgno <= imap_le_struct->imap_stream->nmsgs; msgno++) { + MESSAGECACHE * cache = mail_elt (imap_le_struct->imap_stream, msgno); + mail_fetchstructure (imap_le_struct->imap_stream, msgno, NIL); + + if (!cache->seen || cache->recent) { + unreadmsg++; + } + + if (cache->deleted) { + deletedmsg++; + } + msize = msize + cache->rfc822_size; + } + add_property_long(return_value, "Unread", unreadmsg); + add_property_long(return_value, "Deleted", deletedmsg); + add_property_long(return_value, "Nmsgs", imap_le_struct->imap_stream->nmsgs); + add_property_long(return_value, "Size", msize); + rfc822_date(date); + add_property_string(return_value, "Date", date); + add_property_string(return_value, "Driver", imap_le_struct->imap_stream->dtb->name); + add_property_string(return_value, "Mailbox", imap_le_struct->imap_stream->mailbox); + add_property_long(return_value, "Recent", imap_le_struct->imap_stream->recent); +} +/* }}} */ + +/* {{{ proto string imap_rfc822_write_address(string mailbox, string host, string personal) + Returns a properly formatted email address given the mailbox, host, and personal info */ +PHP_FUNCTION(imap_rfc822_write_address) +{ + zend_string *mailbox, *host, *personal; + ADDRESS *addr; + zend_string *string; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSS", &mailbox, &host, &personal) == FAILURE) { + return; + } + + addr=mail_newaddr(); + + if (mailbox) { + addr->mailbox = cpystr(ZSTR_VAL(mailbox)); + } + + if (host) { + addr->host = cpystr(ZSTR_VAL(host)); + } + + if (personal) { + addr->personal = cpystr(ZSTR_VAL(personal)); + } + + addr->next=NIL; + addr->error=NIL; + addr->adl=NIL; + + string = _php_rfc822_write_address(addr); + if (string) { + RETVAL_STR(string); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_rfc822_parse_adrlist(string address_string, string default_host) + Parses an address string */ +PHP_FUNCTION(imap_rfc822_parse_adrlist) +{ + zval tovals; + zend_string *str, *defaulthost; + char *str_copy; + ADDRESS *addresstmp; + ENVELOPE *env; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &str, &defaulthost) == FAILURE) { + return; + } + + env = mail_newenvelope(); + + /* rfc822_parse_adrlist() modifies passed string. Copy it. */ + str_copy = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); + rfc822_parse_adrlist(&env->to, str_copy, ZSTR_VAL(defaulthost)); + efree(str_copy); + + array_init(return_value); + + addresstmp = env->to; + + if (addresstmp) do { + object_init(&tovals); + if (addresstmp->mailbox) { + add_property_string(&tovals, "mailbox", addresstmp->mailbox); + } + if (addresstmp->host) { + add_property_string(&tovals, "host", addresstmp->host); + } + if (addresstmp->personal) { + add_property_string(&tovals, "personal", addresstmp->personal); + } + if (addresstmp->adl) { + add_property_string(&tovals, "adl", addresstmp->adl); + } + add_next_index_object(return_value, &tovals); + } while ((addresstmp = addresstmp->next)); + + mail_free_envelope(&env); +} +/* }}} */ + +/* {{{ proto string imap_utf8(string mime_encoded_text) + Convert a mime-encoded text to UTF-8 */ +PHP_FUNCTION(imap_utf8) +{ + zend_string *str; + SIZEDTEXT src, dest; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + src.data = NULL; + src.size = 0; + dest.data = NULL; + dest.size = 0; + + cpytxt(&src, ZSTR_VAL(str), ZSTR_LEN(str)); + +#ifndef HAVE_NEW_MIME2TEXT + utf8_mime2text(&src, &dest); +#else + utf8_mime2text(&src, &dest, U8T_DECOMPOSE); +#endif + RETVAL_STRINGL((char*)dest.data, dest.size); + if (dest.data) { + free(dest.data); + } + if (src.data && src.data != dest.data) { + free(src.data); + } +} +/* }}} */ + +/* {{{ macros for the modified utf7 conversion functions + * + * author: Andrew Skalski + */ + +/* tests `c' and returns true if it is a special character */ +#define SPECIAL(c) ((c) <= 0x1f || (c) >= 0x7f) + +/* validate a modified-base64 character */ +#define B64CHAR(c) (isalnum(c) || (c) == '+' || (c) == ',') + +/* map the low 64 bits of `n' to the modified-base64 characters */ +#define B64(n) ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefghijklmnopqrstuvwxyz0123456789+,"[(n) & 0x3f]) + +/* map the modified-base64 character `c' to its 64 bit value */ +#define UNB64(c) ((c) == '+' ? 62 : (c) == ',' ? 63 : (c) >= 'a' ? \ + (c) - 71 : (c) >= 'A' ? (c) - 65 : (c) + 4) +/* }}} */ + +/* {{{ proto string imap_utf7_decode(string buf) + Decode a modified UTF-7 string */ +PHP_FUNCTION(imap_utf7_decode) +{ + /* author: Andrew Skalski */ + zend_string *arg; + const unsigned char *in, *inp, *endp; + unsigned char *out, *outp; + unsigned char c; + int inlen, outlen; + enum { + ST_NORMAL, /* printable text */ + ST_DECODE0, /* encoded text rotation... */ + ST_DECODE1, + ST_DECODE2, + ST_DECODE3 + } state; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + in = (const unsigned char *) ZSTR_VAL(arg); + inlen = ZSTR_LEN(arg); + + /* validate and compute length of output string */ + outlen = 0; + state = ST_NORMAL; + for (endp = (inp = in) + inlen; inp < endp; inp++) { + if (state == ST_NORMAL) { + /* process printable character */ + if (SPECIAL(*inp)) { + php_error_docref(NULL, E_WARNING, "Invalid modified UTF-7 character: `%c'", *inp); + RETURN_FALSE; + } else if (*inp != '&') { + outlen++; + } else if (inp + 1 == endp) { + php_error_docref(NULL, E_WARNING, "Unexpected end of string"); + RETURN_FALSE; + } else if (inp[1] != '-') { + state = ST_DECODE0; + } else { + outlen++; + inp++; + } + } else if (*inp == '-') { + /* return to NORMAL mode */ + if (state == ST_DECODE1) { + php_error_docref(NULL, E_WARNING, "Stray modified base64 character: `%c'", *--inp); + RETURN_FALSE; + } + state = ST_NORMAL; + } else if (!B64CHAR(*inp)) { + php_error_docref(NULL, E_WARNING, "Invalid modified base64 character: `%c'", *inp); + RETURN_FALSE; + } else { + switch (state) { + case ST_DECODE3: + outlen++; + state = ST_DECODE0; + break; + case ST_DECODE2: + case ST_DECODE1: + outlen++; + case ST_DECODE0: + state++; + case ST_NORMAL: + break; + } + } + } + + /* enforce end state */ + if (state != ST_NORMAL) { + php_error_docref(NULL, E_WARNING, "Unexpected end of string"); + RETURN_FALSE; + } + + /* allocate output buffer */ + out = emalloc(outlen + 1); + + /* decode input string */ + outp = out; + state = ST_NORMAL; + for (endp = (inp = in) + inlen; inp < endp; inp++) { + if (state == ST_NORMAL) { + if (*inp == '&' && inp[1] != '-') { + state = ST_DECODE0; + } + else if ((*outp++ = *inp) == '&') { + inp++; + } + } + else if (*inp == '-') { + state = ST_NORMAL; + } + else { + /* decode input character */ + switch (state) { + case ST_DECODE0: + *outp = UNB64(*inp) << 2; + state = ST_DECODE1; + break; + case ST_DECODE1: + outp[1] = UNB64(*inp); + c = outp[1] >> 4; + *outp++ |= c; + *outp <<= 4; + state = ST_DECODE2; + break; + case ST_DECODE2: + outp[1] = UNB64(*inp); + c = outp[1] >> 2; + *outp++ |= c; + *outp <<= 6; + state = ST_DECODE3; + break; + case ST_DECODE3: + *outp++ |= UNB64(*inp); + state = ST_DECODE0; + case ST_NORMAL: + break; + } + } + } + + *outp = 0; + +#if PHP_DEBUG + /* warn if we computed outlen incorrectly */ + if (outp - out != outlen) { + php_error_docref(NULL, E_WARNING, "outp - out [%zd] != outlen [%d]", outp - out, outlen); + } +#endif + + RETURN_STRINGL((char*)out, outlen); +} +/* }}} */ + +/* {{{ proto string imap_utf7_encode(string buf) + Encode a string in modified UTF-7 */ +PHP_FUNCTION(imap_utf7_encode) +{ + /* author: Andrew Skalski */ + zend_string *arg; + const unsigned char *in, *inp, *endp; + zend_string *out; + unsigned char *outp; + unsigned char c; + int inlen, outlen; + enum { + ST_NORMAL, /* printable text */ + ST_ENCODE0, /* encoded text rotation... */ + ST_ENCODE1, + ST_ENCODE2 + } state; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + in = (const unsigned char *) ZSTR_VAL(arg); + inlen = ZSTR_LEN(arg); + + /* compute the length of the result string */ + outlen = 0; + state = ST_NORMAL; + endp = (inp = in) + inlen; + while (inp < endp) { + if (state == ST_NORMAL) { + if (SPECIAL(*inp)) { + state = ST_ENCODE0; + outlen++; + } else if (*inp++ == '&') { + outlen++; + } + outlen++; + } else if (!SPECIAL(*inp)) { + state = ST_NORMAL; + } else { + /* ST_ENCODE0 -> ST_ENCODE1 - two chars + * ST_ENCODE1 -> ST_ENCODE2 - one char + * ST_ENCODE2 -> ST_ENCODE0 - one char + */ + if (state == ST_ENCODE2) { + state = ST_ENCODE0; + } + else if (state++ == ST_ENCODE0) { + outlen++; + } + outlen++; + inp++; + } + } + + /* allocate output buffer */ + out = zend_string_safe_alloc(1, outlen, 0, 0); + + /* encode input string */ + outp = (unsigned char*)ZSTR_VAL(out); + state = ST_NORMAL; + endp = (inp = in) + inlen; + while (inp < endp || state != ST_NORMAL) { + if (state == ST_NORMAL) { + if (SPECIAL(*inp)) { + /* begin encoding */ + *outp++ = '&'; + state = ST_ENCODE0; + } else if ((*outp++ = *inp++) == '&') { + *outp++ = '-'; + } + } else if (inp == endp || !SPECIAL(*inp)) { + /* flush overflow and terminate region */ + if (state != ST_ENCODE0) { + c = B64(*outp); + *outp++ = c; + } + *outp++ = '-'; + state = ST_NORMAL; + } else { + /* encode input character */ + switch (state) { + case ST_ENCODE0: + *outp++ = B64(*inp >> 2); + *outp = *inp++ << 4; + state = ST_ENCODE1; + break; + case ST_ENCODE1: + c = B64(*outp | *inp >> 4); + *outp++ = c; + *outp = *inp++ << 2; + state = ST_ENCODE2; + break; + case ST_ENCODE2: + c = B64(*outp | *inp >> 6); + *outp++ = c; + *outp++ = B64(*inp++); + state = ST_ENCODE0; + case ST_NORMAL: + break; + } + } + } + + *outp = 0; + + RETURN_STR(out); +} +/* }}} */ + +#undef SPECIAL +#undef B64CHAR +#undef B64 +#undef UNB64 + +#ifdef HAVE_IMAP_MUTF7 +static void php_imap_mutf7(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */ +{ + zend_string *in; + unsigned char *out; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &in) == FAILURE) { + return; + } + + if (ZSTR_LEN(in) < 1) { + RETURN_EMPTY_STRING(); + } + + if (mode == 0) { + out = utf8_to_mutf7((unsigned char *) ZSTR_VAL(in)); + } else { + out = utf8_from_mutf7((unsigned char *) ZSTR_VAL(in)); + } + + if (out == NIL) { + RETURN_FALSE; + } else { + RETURN_STRING((char *)out); + } +} +/* }}} */ + +/* {{{ proto string imap_utf8_to_mutf7(string in) + Encode a UTF-8 string to modified UTF-7 */ +PHP_FUNCTION(imap_utf8_to_mutf7) +{ + php_imap_mutf7(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto string imap_mutf7_to_utf8(string in) + Decode a modified UTF-7 string to UTF-8 */ +PHP_FUNCTION(imap_mutf7_to_utf8) +{ + php_imap_mutf7(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ +#endif + +/* {{{ proto bool imap_setflag_full(resource stream_id, string sequence, string flag [, int options]) + Sets flags on messages */ +PHP_FUNCTION(imap_setflag_full) +{ + zval *streamind; + zend_string *sequence, *flag; + zend_long flags = 0; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS|l", &streamind, &sequence, &flag, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_setflag_full(imap_le_struct->imap_stream, ZSTR_VAL(sequence), ZSTR_VAL(flag), (flags ? flags : NIL)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_clearflag_full(resource stream_id, string sequence, string flag [, int options]) + Clears flags on messages */ +PHP_FUNCTION(imap_clearflag_full) +{ + zval *streamind; + zend_string *sequence, *flag; + zend_long flags = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &sequence, &flag, &flags) ==FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_clearflag_full(imap_le_struct->imap_stream, ZSTR_VAL(sequence), ZSTR_VAL(flag), (argc == 4 ? flags : NIL)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imap_sort(resource stream_id, int criteria, int reverse [, int options [, string search_criteria [, string charset]]]) + Sort an array of message headers, optionally including only messages that meet specified criteria. */ +PHP_FUNCTION(imap_sort) +{ + zval *streamind; + zend_string *criteria = NULL, *charset = NULL; + zend_long pgm, rev, flags = 0; + pils *imap_le_struct; + unsigned long *slst, *sl; + char *search_criteria; + SORTPGM *mypgm=NIL; + SEARCHPGM *spg=NIL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rll|lSS", &streamind, &pgm, &rev, &flags, &criteria, &charset) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (pgm > SORTSIZE) { + php_error_docref(NULL, E_WARNING, "Unrecognized sort criteria"); + RETURN_FALSE; + } + if (argc >= 4) { + if (flags < 0) { + php_error_docref(NULL, E_WARNING, "Search options parameter has to be greater than or equal to 0"); + RETURN_FALSE; + } + } + if (argc >= 5) { + search_criteria = estrndup(ZSTR_VAL(criteria), ZSTR_LEN(criteria)); + spg = mail_criteria(search_criteria); + efree(search_criteria); + } else { + spg = mail_newsearchpgm(); + } + + mypgm = mail_newsortpgm(); + mypgm->reverse = rev; + mypgm->function = (short) pgm; + mypgm->next = NIL; + + slst = mail_sort(imap_le_struct->imap_stream, (argc == 6 ? ZSTR_VAL(charset) : NIL), spg, mypgm, (argc >= 4 ? flags : NIL)); + + if (spg && !(flags & SE_FREE)) { + mail_free_searchpgm(&spg); + } + + array_init(return_value); + if (slst != NIL && slst != 0) { + for (sl = slst; *sl; sl++) { + add_next_index_long(return_value, *sl); + } + fs_give ((void **) &slst); + } +} +/* }}} */ + +/* {{{ proto string imap_fetchheader(resource stream_id, int msg_no [, int options]) + Get the full unfiltered header for a message */ +PHP_FUNCTION(imap_fetchheader) +{ + zval *streamind; + zend_long msgno, flags = 0L; + pils *imap_le_struct; + int msgindex, argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_INTERNAL|FT_PREFETCHTEXT)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making sure + we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + + PHP_IMAP_CHECK_MSGNO(msgindex); + + RETVAL_STRING(mail_fetchheader_full(imap_le_struct->imap_stream, msgno, NIL, NIL, (argc == 3 ? flags : NIL))); +} +/* }}} */ + +/* {{{ proto int imap_uid(resource stream_id, int msg_no) + Get the unique message id associated with a standard sequential message number */ +PHP_FUNCTION(imap_uid) +{ + zval *streamind; + zend_long msgno; + pils *imap_le_struct; + int msgindex; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &msgno) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + msgindex = msgno; + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + RETURN_LONG(mail_uid(imap_le_struct->imap_stream, msgno)); +} +/* }}} */ + +/* {{{ proto int imap_msgno(resource stream_id, int unique_msg_id) + Get the sequence number associated with a UID */ +PHP_FUNCTION(imap_msgno) +{ + zval *streamind; + zend_long msgno; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &msgno) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(mail_msgno(imap_le_struct->imap_stream, msgno)); +} +/* }}} */ + +/* {{{ proto object imap_status(resource stream_id, string mailbox, int options) + Get status info from a mailbox */ +PHP_FUNCTION(imap_status) +{ + zval *streamind; + zend_string *mbx; + zend_long flags; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &streamind, &mbx, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + object_init(return_value); + + if (mail_status(imap_le_struct->imap_stream, ZSTR_VAL(mbx), flags)) { + add_property_long(return_value, "flags", IMAPG(status_flags)); + if (IMAPG(status_flags) & SA_MESSAGES) { + add_property_long(return_value, "messages", IMAPG(status_messages)); + } + if (IMAPG(status_flags) & SA_RECENT) { + add_property_long(return_value, "recent", IMAPG(status_recent)); + } + if (IMAPG(status_flags) & SA_UNSEEN) { + add_property_long(return_value, "unseen", IMAPG(status_unseen)); + } + if (IMAPG(status_flags) & SA_UIDNEXT) { + add_property_long(return_value, "uidnext", IMAPG(status_uidnext)); + } + if (IMAPG(status_flags) & SA_UIDVALIDITY) { + add_property_long(return_value, "uidvalidity", IMAPG(status_uidvalidity)); + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object imap_bodystruct(resource stream_id, int msg_no, string section) + Read the structure of a specified body section of a specific message */ +PHP_FUNCTION(imap_bodystruct) +{ + zval *streamind; + zend_long msg; + zend_string *section; + pils *imap_le_struct; + zval parametres, param, dparametres, dparam; + PARAMETER *par, *dpar; + BODY *body; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlS", &streamind, &msg, §ion) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (!msg || msg < 1 || (unsigned) msg > imap_le_struct->imap_stream->nmsgs) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + object_init(return_value); + + body=mail_body(imap_le_struct->imap_stream, msg, (unsigned char*)ZSTR_VAL(section)); + if (body == NULL) { + zval_dtor(return_value); + RETURN_FALSE; + } + if (body->type <= TYPEMAX) { + add_property_long(return_value, "type", body->type); + } + if (body->encoding <= ENCMAX) { + add_property_long(return_value, "encoding", body->encoding); + } + + if (body->subtype) { + add_property_long(return_value, "ifsubtype", 1); + add_property_string(return_value, "subtype", body->subtype); + } else { + add_property_long(return_value, "ifsubtype", 0); + } + + if (body->description) { + add_property_long(return_value, "ifdescription", 1); + add_property_string(return_value, "description", body->description); + } else { + add_property_long(return_value, "ifdescription", 0); + } + if (body->id) { + add_property_long(return_value, "ifid", 1); + add_property_string(return_value, "id", body->id); + } else { + add_property_long(return_value, "ifid", 0); + } + + if (body->size.lines) { + add_property_long(return_value, "lines", body->size.lines); + } + if (body->size.bytes) { + add_property_long(return_value, "bytes", body->size.bytes); + } +#ifdef IMAP41 + if (body->disposition.type) { + add_property_long(return_value, "ifdisposition", 1); + add_property_string(return_value, "disposition", body->disposition.type); + } else { + add_property_long(return_value, "ifdisposition", 0); + } + + if (body->disposition.parameter) { + dpar = body->disposition.parameter; + add_property_long(return_value, "ifdparameters", 1); + array_init(&dparametres); + do { + object_init(&dparam); + add_property_string(&dparam, "attribute", dpar->attribute); + add_property_string(&dparam, "value", dpar->value); + add_next_index_object(&dparametres, &dparam); + } while ((dpar = dpar->next)); + add_assoc_object(return_value, "dparameters", &dparametres); + } else { + add_property_long(return_value, "ifdparameters", 0); + } +#endif + + if ((par = body->parameter)) { + add_property_long(return_value, "ifparameters", 1); + + array_init(¶metres); + do { + object_init(¶m); + if (par->attribute) { + add_property_string(¶m, "attribute", par->attribute); + } + if (par->value) { + add_property_string(¶m, "value", par->value); + } + + add_next_index_object(¶metres, ¶m); + } while ((par = par->next)); + } else { + object_init(¶metres); + add_property_long(return_value, "ifparameters", 0); + } + add_assoc_object(return_value, "parameters", ¶metres); +} + +/* }}} */ + +/* {{{ proto array imap_fetch_overview(resource stream_id, string sequence [, int options]) + Read an overview of the information in the headers of the given message sequence */ +PHP_FUNCTION(imap_fetch_overview) +{ + zval *streamind; + zend_string *sequence; + pils *imap_le_struct; + zval myoverview; + zend_string *address; + zend_long status, flags = 0L; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rS|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~FT_UID) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + + status = (flags & FT_UID) + ? mail_uid_sequence(imap_le_struct->imap_stream, (unsigned char*)ZSTR_VAL(sequence)) + : mail_sequence(imap_le_struct->imap_stream, (unsigned char*)ZSTR_VAL(sequence)); + + if (status) { + MESSAGECACHE *elt; + ENVELOPE *env; + unsigned long i; + + for (i = 1; i <= imap_le_struct->imap_stream->nmsgs; i++) { + if (((elt = mail_elt (imap_le_struct->imap_stream, i))->sequence) && + (env = mail_fetch_structure (imap_le_struct->imap_stream, i, NIL, NIL))) { + object_init(&myoverview); + if (env->subject) { + add_property_string(&myoverview, "subject", env->subject); + } + if (env->from) { + env->from->next=NULL; + address =_php_rfc822_write_address(env->from); + if (address) { + add_property_str(&myoverview, "from", address); + } + } + if (env->to) { + env->to->next = NULL; + address = _php_rfc822_write_address(env->to); + if (address) { + add_property_str(&myoverview, "to", address); + } + } + if (env->date) { + add_property_string(&myoverview, "date", (char*)env->date); + } + if (env->message_id) { + add_property_string(&myoverview, "message_id", env->message_id); + } + if (env->references) { + add_property_string(&myoverview, "references", env->references); + } + if (env->in_reply_to) { + add_property_string(&myoverview, "in_reply_to", env->in_reply_to); + } + add_property_long(&myoverview, "size", elt->rfc822_size); + add_property_long(&myoverview, "uid", mail_uid(imap_le_struct->imap_stream, i)); + add_property_long(&myoverview, "msgno", i); + add_property_long(&myoverview, "recent", elt->recent); + add_property_long(&myoverview, "flagged", elt->flagged); + add_property_long(&myoverview, "answered", elt->answered); + add_property_long(&myoverview, "deleted", elt->deleted); + add_property_long(&myoverview, "seen", elt->seen); + add_property_long(&myoverview, "draft", elt->draft); + add_property_long(&myoverview, "udate", mail_longdate(elt)); + add_next_index_object(return_value, &myoverview); + } + } + } +} +/* }}} */ + +/* {{{ proto string imap_mail_compose(array envelope, array body) + Create a MIME message based on given envelope and body sections */ +PHP_FUNCTION(imap_mail_compose) +{ + zval *envelope, *body; + zend_string *key; + zval *data, *pvalue, *disp_data, *env_data; + char *cookie = NIL; + ENVELOPE *env; + BODY *bod=NULL, *topbod=NULL; + PART *mypart=NULL, *part; + PARAMETER *param, *disp_param = NULL, *custom_headers_param = NULL, *tmp_param = NULL; + char *tmp=NULL, *mystring=NULL, *t=NULL, *tempstring=NULL, *str_copy = NULL; + int toppart = 0; + int first; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &envelope, &body) == FAILURE) { + return; + } + +#define PHP_RFC822_PARSE_ADRLIST(target, value) \ + str_copy = estrndup(Z_STRVAL_P(value), Z_STRLEN_P(value)); \ + rfc822_parse_adrlist(target, str_copy, "NO HOST"); \ + efree(str_copy); + + env = mail_newenvelope(); + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "remail", sizeof("remail") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->remail = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "return_path", sizeof("return_path") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->return_path, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "date", sizeof("date") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->date = (unsigned char*)cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "from", sizeof("from") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->from, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "reply_to", sizeof("reply_to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->reply_to, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "in_reply_to", sizeof("in_reply_to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->in_reply_to = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "subject", sizeof("subject") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->subject = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "to", sizeof("to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->to, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "cc", sizeof("cc") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->cc, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "bcc", sizeof("bcc") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->bcc, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "message_id", sizeof("message_id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->message_id=cpystr(Z_STRVAL_P(pvalue)); + } + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "custom_headers", sizeof("custom_headers") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + custom_headers_param = tmp_param = NULL; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pvalue), env_data) { + custom_headers_param = mail_newbody_parameter(); + convert_to_string_ex(env_data); + custom_headers_param->value = (char *) fs_get(Z_STRLEN_P(env_data) + 1); + custom_headers_param->attribute = NULL; + memcpy(custom_headers_param->value, Z_STRVAL_P(env_data), Z_STRLEN_P(env_data) + 1); + custom_headers_param->next = tmp_param; + tmp_param = custom_headers_param; + } ZEND_HASH_FOREACH_END(); + } + } + + first = 1; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(body), data) { + if (first) { + first = 0; + + if (Z_TYPE_P(data) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "body parameter must be a non-empty array"); + RETURN_FALSE; + } + + bod = mail_newbody(); + topbod = bod; + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type", sizeof("type") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->type = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "encoding", sizeof("encoding") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->encoding = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) { + convert_to_string_ex(pvalue); + tmp_param = mail_newbody_parameter(); + tmp_param->value = cpystr(Z_STRVAL_P(pvalue)); + tmp_param->attribute = cpystr("CHARSET"); + tmp_param->next = bod->parameter; + bod->parameter = tmp_param; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type.parameters", sizeof("type.parameters") - 1)) != NULL) { + if(Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->subtype = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->id = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->description = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition", sizeof("disposition") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->disposition.parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "contents.data", sizeof("contents.data") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->contents.text.data = fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->contents.text.data, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + bod->contents.text.size = Z_STRLEN_P(pvalue); + } else { + bod->contents.text.data = fs_get(1); + memcpy(bod->contents.text.data, "", 1); + bod->contents.text.size = 0; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "lines", sizeof("lines") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.lines = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "bytes", sizeof("bytes") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.bytes = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->md5 = cpystr(Z_STRVAL_P(pvalue)); + } + } else if (Z_TYPE_P(data) == IS_ARRAY) { + short type = -1; + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type", sizeof("type") - 1)) != NULL) { + convert_to_long_ex(pvalue); + type = (short) Z_LVAL_P(pvalue); + } + + if (!toppart) { + bod->nested.part = mail_newbody_part(); + mypart = bod->nested.part; + toppart = 1; + } else { + mypart->next = mail_newbody_part(); + mypart = mypart->next; + } + + bod = &mypart->body; + + if (type != TYPEMULTIPART) { + bod->type = type; + } + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "encoding", sizeof("encoding") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->encoding = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) { + convert_to_string_ex(pvalue); + tmp_param = mail_newbody_parameter(); + tmp_param->value = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(tmp_param->value, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1); + tmp_param->attribute = cpystr("CHARSET"); + tmp_param->next = bod->parameter; + bod->parameter = tmp_param; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type.parameters", sizeof("type.parameters") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *)fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->subtype = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->id = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->description = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition", sizeof("disposition") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->disposition.parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "contents.data", sizeof("contents.data") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->contents.text.data = fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->contents.text.data, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1); + bod->contents.text.size = Z_STRLEN_P(pvalue); + } else { + bod->contents.text.data = fs_get(1); + memcpy(bod->contents.text.data, "", 1); + bod->contents.text.size = 0; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "lines", sizeof("lines") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.lines = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "bytes", sizeof("bytes") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.bytes = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->md5 = cpystr(Z_STRVAL_P(pvalue)); + } + } + } ZEND_HASH_FOREACH_END(); + + if (first) { + php_error_docref(NULL, E_WARNING, "body parameter must be a non-empty array"); + RETURN_FALSE; + } + + if (bod && bod->type == TYPEMULTIPART && (!bod->nested.part || !bod->nested.part->next)) { + php_error_docref(NULL, E_WARNING, "cannot generate multipart e-mail without components."); + RETVAL_FALSE; + goto done; + } + + rfc822_encode_body_7bit(env, topbod); + + tmp = emalloc(SENDBUFLEN + 1); + + rfc822_header(tmp, env, topbod); + + /* add custom envelope headers */ + if (custom_headers_param) { + int l = strlen(tmp) - 2, l2; + PARAMETER *tp = custom_headers_param; + + /* remove last CRLF from tmp */ + tmp[l] = '\0'; + tempstring = emalloc(l); + memcpy(tempstring, tmp, l); + + do { + l2 = strlen(custom_headers_param->value); + tempstring = erealloc(tempstring, l + l2 + CRLF_LEN + 1); + memcpy(tempstring + l, custom_headers_param->value, l2); + memcpy(tempstring + l + l2, CRLF, CRLF_LEN); + l += l2 + CRLF_LEN; + } while ((custom_headers_param = custom_headers_param->next)); + + mail_free_body_parameter(&tp); + + mystring = emalloc(l + CRLF_LEN + 1); + memcpy(mystring, tempstring, l); + memcpy(mystring + l , CRLF, CRLF_LEN); + mystring[l + CRLF_LEN] = '\0'; + + efree(tempstring); + } else { + mystring = estrdup(tmp); + } + + bod = topbod; + + if (bod && bod->type == TYPEMULTIPART) { + + /* first body part */ + part = bod->nested.part; + + /* find cookie */ + for (param = bod->parameter; param && !cookie; param = param->next) { + if (!strcmp (param->attribute, "BOUNDARY")) { + cookie = param->value; + } + } + + /* yucky default */ + if (!cookie) { + cookie = "-"; + } else if (strlen(cookie) > (SENDBUFLEN - 2 - 2 - 2)) { /* validate cookie length -- + CRLF * 2 */ + php_error_docref(NULL, E_WARNING, "The boundary should be no longer than 4kb"); + RETVAL_FALSE; + goto done; + } + + /* for each part */ + do { + t = tmp; + + /* append mini-header */ + *t = '\0'; + rfc822_write_body_header(&t, &part->body); + + /* output cookie, mini-header, and contents */ + spprintf(&tempstring, 0, "%s--%s%s%s%s", mystring, cookie, CRLF, tmp, CRLF); + efree(mystring); + mystring=tempstring; + + bod=&part->body; + + spprintf(&tempstring, 0, "%s%s%s", mystring, bod->contents.text.data, CRLF); + efree(mystring); + mystring=tempstring; + } while ((part = part->next)); /* until done */ + + /* output trailing cookie */ + spprintf(&tempstring, 0, "%s--%s--%s", mystring, cookie, CRLF); + efree(mystring); + mystring=tempstring; + } else if (bod) { + spprintf(&tempstring, 0, "%s%s%s", mystring, bod->contents.text.data, CRLF); + efree(mystring); + mystring=tempstring; + } else { + efree(mystring); + RETVAL_FALSE; + goto done; + } + + RETVAL_STRING(tempstring); + efree(tempstring); +done: + if (tmp) { + efree(tmp); + } + mail_free_body(&topbod); + mail_free_envelope(&env); +} +/* }}} */ + +/* {{{ _php_imap_mail + */ +int _php_imap_mail(char *to, char *subject, char *message, char *headers, char *cc, char *bcc, char* rpath) +{ +#ifdef PHP_WIN32 + int tsm_err; +#else + FILE *sendmail; + int ret; +#endif + +#ifdef PHP_WIN32 + char *tempMailTo; + char *tsm_errmsg = NULL; + ADDRESS *addr; + char *bufferTo = NULL, *bufferCc = NULL, *bufferBcc = NULL, *bufferHeader = NULL; + size_t offset, bufferLen = 0; + size_t bt_len; + + if (headers) { + bufferLen += strlen(headers); + } + if (to) { + bufferLen += strlen(to) + 6; + } + if (cc) { + bufferLen += strlen(cc) + 6; + } + +#define PHP_IMAP_CLEAN if (bufferTo) efree(bufferTo); if (bufferCc) efree(bufferCc); if (bufferBcc) efree(bufferBcc); if (bufferHeader) efree(bufferHeader); +#define PHP_IMAP_BAD_DEST PHP_IMAP_CLEAN; efree(tempMailTo); return (BAD_MSG_DESTINATION); + + bufferHeader = (char *)safe_emalloc(bufferLen, 1, 1); + memset(bufferHeader, 0, bufferLen); + if (to && *to) { + strlcat(bufferHeader, "To: ", bufferLen + 1); + strlcat(bufferHeader, to, bufferLen + 1); + strlcat(bufferHeader, "\r\n", bufferLen + 1); + tempMailTo = estrdup(to); + bt_len = strlen(to); + bufferTo = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferTo = safe_erealloc(bufferTo, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferTo = safe_erealloc(bufferTo, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferTo + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferTo[offset-1] = 0; + } + } + + if (cc && *cc) { + strlcat(bufferHeader, "Cc: ", bufferLen + 1); + strlcat(bufferHeader, cc, bufferLen + 1); + strlcat(bufferHeader, "\r\n", bufferLen + 1); + tempMailTo = estrdup(cc); + bt_len = strlen(cc); + bufferCc = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferCc = safe_erealloc(bufferCc, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferCc = safe_erealloc(bufferCc, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferCc + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferCc[offset-1] = 0; + } + } + + if (bcc && *bcc) { + tempMailTo = estrdup(bcc); + bt_len = strlen(bcc); + bufferBcc = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferBcc = safe_erealloc(bufferBcc, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferBcc = safe_erealloc(bufferBcc, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferBcc + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferBcc[offset-1] = 0; + } + } + + if (headers && *headers) { + strlcat(bufferHeader, headers, bufferLen + 1); + } + + if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, bufferHeader, subject, bufferTo, message, bufferCc, bufferBcc, rpath) != SUCCESS) { + if (tsm_errmsg) { + php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg); + efree(tsm_errmsg); + } else { + php_error_docref(NULL, E_WARNING, "%s", GetSMErrorText(tsm_err)); + } + PHP_IMAP_CLEAN; + return 0; + } + PHP_IMAP_CLEAN; +#else + if (!INI_STR("sendmail_path")) { + return 0; + } + sendmail = popen(INI_STR("sendmail_path"), "w"); + if (sendmail) { + if (rpath && rpath[0]) fprintf(sendmail, "From: %s\n", rpath); + fprintf(sendmail, "To: %s\n", to); + if (cc && cc[0]) fprintf(sendmail, "Cc: %s\n", cc); + if (bcc && bcc[0]) fprintf(sendmail, "Bcc: %s\n", bcc); + fprintf(sendmail, "Subject: %s\n", subject); + if (headers != NULL) { + fprintf(sendmail, "%s\n", headers); + } + fprintf(sendmail, "\n%s\n", message); + ret = pclose(sendmail); + if (ret == -1) { + return 0; + } else { + return 1; + } + } else { + php_error_docref(NULL, E_WARNING, "Could not execute mail delivery program"); + return 0; + } +#endif + return 1; +} +/* }}} */ + +/* {{{ proto bool imap_mail(string to, string subject, string message [, string additional_headers [, string cc [, string bcc [, string rpath]]]]) + Send an email message */ +PHP_FUNCTION(imap_mail) +{ + zend_string *to=NULL, *message=NULL, *headers=NULL, *subject=NULL, *cc=NULL, *bcc=NULL, *rpath=NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "SSS|SSSS", &to, &subject, &message, + &headers, &cc, &bcc, &rpath) == FAILURE) { + return; + } + + /* To: */ + if (!ZSTR_LEN(to)) { + php_error_docref(NULL, E_WARNING, "No to field in mail command"); + RETURN_FALSE; + } + + /* Subject: */ + if (!ZSTR_LEN(subject)) { + php_error_docref(NULL, E_WARNING, "No subject field in mail command"); + RETURN_FALSE; + } + + /* message body */ + if (!ZSTR_LEN(message)) { + /* this is not really an error, so it is allowed. */ + php_error_docref(NULL, E_WARNING, "No message string in mail command"); + message = NULL; + } + + if (_php_imap_mail(ZSTR_VAL(to), ZSTR_VAL(subject), ZSTR_VAL(message), headers?ZSTR_VAL(headers):NULL, cc?ZSTR_VAL(cc):NULL, + bcc?ZSTR_VAL(bcc):NULL, rpath?ZSTR_VAL(rpath):NULL)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_search(resource stream_id, string criteria [, int options [, string charset]]) + Return a list of messages matching the given criteria */ +PHP_FUNCTION(imap_search) +{ + zval *streamind; + zend_string *criteria, *charset = NULL; + zend_long flags = SE_FREE; + pils *imap_le_struct; + char *search_criteria; + MESSAGELIST *cur; + int argc = ZEND_NUM_ARGS(); + SEARCHPGM *pgm = NIL; + + if (zend_parse_parameters(argc, "rS|lS", &streamind, &criteria, &flags, &charset) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + search_criteria = estrndup(ZSTR_VAL(criteria), ZSTR_LEN(criteria)); + + IMAPG(imap_messages) = IMAPG(imap_messages_tail) = NIL; + pgm = mail_criteria(search_criteria); + + mail_search_full(imap_le_struct->imap_stream, (argc == 4 ? ZSTR_VAL(charset) : NIL), pgm, flags); + + if (pgm && !(flags & SE_FREE)) { + mail_free_searchpgm(&pgm); + } + + if (IMAPG(imap_messages) == NIL) { + efree(search_criteria); + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_messages); + while (cur != NIL) { + add_next_index_long(return_value, cur->msgid); + cur = cur->next; + } + mail_free_messagelist(&IMAPG(imap_messages), &IMAPG(imap_messages_tail)); + efree(search_criteria); +} +/* }}} */ + +/* {{{ proto array imap_alerts(void) + Returns an array of all IMAP alerts that have been generated since the last page load or since the last imap_alerts() call, whichever came last. The alert stack is cleared after imap_alerts() is called. */ +/* Author: CJH */ +PHP_FUNCTION(imap_alerts) +{ + STRINGLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_alertstack) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_alertstack); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur = cur->next; + } + mail_free_stringlist(&IMAPG(imap_alertstack)); + IMAPG(imap_alertstack) = NIL; +} +/* }}} */ + +/* {{{ proto array imap_errors(void) + Returns an array of all IMAP errors generated since the last page load, or since the last imap_errors() call, whichever came last. The error stack is cleared after imap_errors() is called. */ +/* Author: CJH */ +PHP_FUNCTION(imap_errors) +{ + ERRORLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_errorstack) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_errorstack); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur = cur->next; + } + mail_free_errorlist(&IMAPG(imap_errorstack)); + IMAPG(imap_errorstack) = NIL; +} +/* }}} */ + +/* {{{ proto string imap_last_error(void) + Returns the last error that was generated by an IMAP function. The error stack is NOT cleared after this call. */ +/* Author: CJH */ +PHP_FUNCTION(imap_last_error) +{ + ERRORLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_errorstack) == NIL) { + RETURN_FALSE; + } + + cur = IMAPG(imap_errorstack); + while (cur != NIL) { + if (cur->next == NIL) { + RETURN_STRING((char*)cur->LTEXT); + } + cur = cur->next; + } +} +/* }}} */ + +/* {{{ proto array imap_mime_header_decode(string str) + Decode mime header element in accordance with RFC 2047 and return array of objects containing 'charset' encoding and decoded 'text' */ +PHP_FUNCTION(imap_mime_header_decode) +{ + /* Author: Ted Parnefors */ + zval myobject; + zend_string *str; + char *string, *charset, encoding, *text, *decode; + zend_long charset_token, encoding_token, end_token, end, offset=0, i; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + array_init(return_value); + + string = ZSTR_VAL(str); + end = ZSTR_LEN(str); + + charset = (char *) safe_emalloc((end + 1), 2, 0); + text = &charset[end + 1]; + while (offset < end) { /* Reached end of the string? */ + if ((charset_token = (zend_long)php_memnstr(&string[offset], "=?", 2, string + end))) { /* Is there anything encoded in the string? */ + charset_token -= (zend_long)string; + if (offset != charset_token) { /* Is there anything before the encoded data? */ + /* Retrieve unencoded data that is found before encoded data */ + memcpy(text, &string[offset], charset_token-offset); + text[charset_token - offset] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + } + if ((encoding_token = (zend_long)php_memnstr(&string[charset_token+2], "?", 1, string+end))) { /* Find token for encoding */ + encoding_token -= (zend_long)string; + if ((end_token = (zend_long)php_memnstr(&string[encoding_token+3], "?=", 2, string+end))) { /* Find token for end of encoded data */ + end_token -= (zend_long)string; + memcpy(charset, &string[charset_token + 2], encoding_token - (charset_token + 2)); /* Extract charset encoding */ + charset[encoding_token-(charset_token + 2)] = 0x00; + encoding=string[encoding_token + 1]; /* Extract encoding from string */ + memcpy(text, &string[encoding_token + 3], end_token - (encoding_token + 3)); /* Extract text */ + text[end_token - (encoding_token + 3)] = 0x00; + decode = text; + if (encoding == 'q' || encoding == 'Q') { /* Decode 'q' encoded data */ + for(i=0; text[i] != 0x00; i++) if (text[i] == '_') text[i] = ' '; /* Replace all *_' with space. */ + decode = (char *)rfc822_qprint((unsigned char *) text, strlen(text), &newlength); + } else if (encoding == 'b' || encoding == 'B') { + decode = (char *)rfc822_base64((unsigned char *) text, strlen(text), &newlength); /* Decode 'B' encoded data */ + } + if (decode == NULL) { + efree(charset); + zval_dtor(return_value); + RETURN_FALSE; + } + object_init(&myobject); + add_property_string(&myobject, "charset", charset); + add_property_string(&myobject, "text", decode); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + /* only free decode if it was allocated by rfc822_qprint or rfc822_base64 */ + if (decode != text) { + fs_give((void**)&decode); + } + + offset = end_token+2; + for (i = 0; (string[offset + i] == ' ') || (string[offset + i] == 0x0a) || (string[offset + i] == 0x0d) || (string[offset + i] == '\t'); i++); + if ((string[offset + i] == '=') && (string[offset + i + 1] == '?') && (offset + i < end)) { + offset += i; + } + continue; /*/ Iterate the loop again please. */ + } + } + } else { + /* Just some tweaking to optimize the code, and get the end statements work in a general manner. + * If we end up here we didn't find a position for "charset_token", + * so we need to set it to the start of the yet unextracted data. + */ + charset_token = offset; + } + /* Return the rest of the data as unencoded, as it was either unencoded or was missing separators + which rendered the remainder of the string impossible for us to decode. */ + memcpy(text, &string[charset_token], end - charset_token); /* Extract unencoded text from string */ + text[end - charset_token] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + offset = end; /* We have reached the end of the string. */ + } + efree(charset); +} +/* }}} */ + +/* Support Functions */ + +#ifdef HAVE_RFC822_OUTPUT_ADDRESS_LIST +/* {{{ _php_rfc822_soutr + */ +static long _php_rfc822_soutr (void *stream, char *string) +{ + smart_str *ret = (smart_str*)stream; + int len = strlen(string); + + smart_str_appendl(ret, string, len); + return LONGT; +} +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[MAILTMPLEN]; + smart_str ret = {0}; + RFC822BUFFER buf; + + buf.beg = address; + buf.cur = buf.beg; + buf.end = buf.beg + sizeof(address) - 1; + buf.s = &ret; + buf.f = _php_rfc822_soutr; + rfc822_output_address_list(&buf, addresslist, 0, NULL); + rfc822_output_flush(&buf); + smart_str_0(&ret); + return ret.s; +} +/* }}} */ + +#else + +/* {{{ _php_rfc822_len + * Calculate string length based on imap's rfc822_cat function. + */ +static int _php_rfc822_len(char *str) +{ + int len; + char *p; + + if (!str || !*str) { + return 0; + } + + /* strings with special characters will need to be quoted, as a safety measure we + * add 2 bytes for the quotes just in case. + */ + len = strlen(str) + 2; + p = str; + /* rfc822_cat() will escape all " and \ characters, therefor we need to increase + * our buffer length to account for these characters. + */ + while ((p = strpbrk(p, "\\\""))) { + p++; + len++; + } + + return len; +} +/* }}} */ + +/* {{{ _php_imap_get_address_size + */ +static int _php_imap_address_size (ADDRESS *addresslist) +{ + ADDRESS *tmp; + int ret=0, num_ent=0; + + tmp = addresslist; + + if (tmp) do { + ret += _php_rfc822_len(tmp->personal); + ret += _php_rfc822_len(tmp->adl); + ret += _php_rfc822_len(tmp->mailbox); + ret += _php_rfc822_len(tmp->host); + num_ent++; + } while ((tmp = tmp->next)); + + /* + * rfc822_write_address_full() needs some extra space for '<>,', etc. + * for this perpouse we allocate additional PHP_IMAP_ADDRESS_SIZE_BUF bytes + * by default this buffer is 10 bytes long + */ + ret += (ret) ? num_ent*PHP_IMAP_ADDRESS_SIZE_BUF : 0; + + return ret; +} + +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[SENDBUFLEN]; + + if (_php_imap_address_size(addresslist) >= SENDBUFLEN) { + zend_throw_error(NULL, "Address buffer overflow"); + return NULL; + } + address[0] = 0; + rfc822_write_address(address, addresslist); + return zend_string_init(address, strlen(address), 0); +} +/* }}} */ +#endif +/* {{{ _php_imap_parse_address + */ +static zend_string* _php_imap_parse_address (ADDRESS *addresslist, zval *paddress) +{ + zend_string *fulladdress; + ADDRESS *addresstmp; + zval tmpvals; + + addresstmp = addresslist; + + fulladdress = _php_rfc822_write_address(addresstmp); + + addresstmp = addresslist; + do { + object_init(&tmpvals); + if (addresstmp->personal) add_property_string(&tmpvals, "personal", addresstmp->personal); + if (addresstmp->adl) add_property_string(&tmpvals, "adl", addresstmp->adl); + if (addresstmp->mailbox) add_property_string(&tmpvals, "mailbox", addresstmp->mailbox); + if (addresstmp->host) add_property_string(&tmpvals, "host", addresstmp->host); + add_next_index_object(paddress, &tmpvals); + } while ((addresstmp = addresstmp->next)); + return fulladdress; +} +/* }}} */ + +/* {{{ _php_make_header_object + */ +static void _php_make_header_object(zval *myzvalue, ENVELOPE *en) +{ + zval paddress; + zend_string *fulladdress=NULL; + + object_init(myzvalue); + + if (en->remail) add_property_string(myzvalue, "remail", en->remail); + if (en->date) add_property_string(myzvalue, "date", (char*)en->date); + if (en->date) add_property_string(myzvalue, "Date", (char*)en->date); + if (en->subject) add_property_string(myzvalue, "subject", en->subject); + if (en->subject) add_property_string(myzvalue, "Subject", en->subject); + if (en->in_reply_to) add_property_string(myzvalue, "in_reply_to", en->in_reply_to); + if (en->message_id) add_property_string(myzvalue, "message_id", en->message_id); + if (en->newsgroups) add_property_string(myzvalue, "newsgroups", en->newsgroups); + if (en->followup_to) add_property_string(myzvalue, "followup_to", en->followup_to); + if (en->references) add_property_string(myzvalue, "references", en->references); + + if (en->to) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->to, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "toaddress", fulladdress); + } + add_assoc_object(myzvalue, "to", &paddress); + } + + if (en->from) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->from, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "fromaddress", fulladdress); + } + add_assoc_object(myzvalue, "from", &paddress); + } + + if (en->cc) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->cc, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "ccaddress", fulladdress); + } + add_assoc_object(myzvalue, "cc", &paddress); + } + + if (en->bcc) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->bcc, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "bccaddress", fulladdress); + } + add_assoc_object(myzvalue, "bcc", &paddress); + } + + if (en->reply_to) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->reply_to, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "reply_toaddress", fulladdress); + } + add_assoc_object(myzvalue, "reply_to", &paddress); + } + + if (en->sender) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->sender, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "senderaddress", fulladdress); + } + add_assoc_object(myzvalue, "sender", &paddress); + } + + if (en->return_path) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->return_path, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "return_pathaddress", fulladdress); + } + add_assoc_object(myzvalue, "return_path", &paddress); + } +} +/* }}} */ + +/* {{{ _php_imap_add_body + */ +void _php_imap_add_body(zval *arg, BODY *body) +{ + zval parametres, param, dparametres, dparam; + PARAMETER *par, *dpar; + PART *part; + + if (body->type <= TYPEMAX) { + add_property_long(arg, "type", body->type); + } + + if (body->encoding <= ENCMAX) { + add_property_long(arg, "encoding", body->encoding); + } + + if (body->subtype) { + add_property_long(arg, "ifsubtype", 1); + add_property_string(arg, "subtype", body->subtype); + } else { + add_property_long(arg, "ifsubtype", 0); + } + + if (body->description) { + add_property_long(arg, "ifdescription", 1); + add_property_string(arg, "description", body->description); + } else { + add_property_long(arg, "ifdescription", 0); + } + + if (body->id) { + add_property_long(arg, "ifid", 1); + add_property_string(arg, "id", body->id); + } else { + add_property_long(arg, "ifid", 0); + } + + if (body->size.lines) { + add_property_long(arg, "lines", body->size.lines); + } + + if (body->size.bytes) { + add_property_long(arg, "bytes", body->size.bytes); + } + +#ifdef IMAP41 + if (body->disposition.type) { + add_property_long(arg, "ifdisposition", 1); + add_property_string(arg, "disposition", body->disposition.type); + } else { + add_property_long(arg, "ifdisposition", 0); + } + + if (body->disposition.parameter) { + dpar = body->disposition.parameter; + add_property_long(arg, "ifdparameters", 1); + array_init(&dparametres); + do { + object_init(&dparam); + add_property_string(&dparam, "attribute", dpar->attribute); + add_property_string(&dparam, "value", dpar->value); + add_next_index_object(&dparametres, &dparam); + } while ((dpar = dpar->next)); + add_assoc_object(arg, "dparameters", &dparametres); + } else { + add_property_long(arg, "ifdparameters", 0); + } +#endif + + if ((par = body->parameter)) { + add_property_long(arg, "ifparameters", 1); + + array_init(¶metres); + do { + object_init(¶m); + if (par->attribute) { + add_property_string(¶m, "attribute", par->attribute); + } + if (par->value) { + add_property_string(¶m, "value", par->value); + } + + add_next_index_object(¶metres, ¶m); + } while ((par = par->next)); + } else { + object_init(¶metres); + add_property_long(arg, "ifparameters", 0); + } + add_assoc_object(arg, "parameters", ¶metres); + + /* multipart message ? */ + if (body->type == TYPEMULTIPART) { + array_init(¶metres); + for (part = body->CONTENT_PART; part; part = part->next) { + object_init(¶m); + _php_imap_add_body(¶m, &part->body); + add_next_index_object(¶metres, ¶m); + } + add_assoc_object(arg, "parts", ¶metres); + } + + /* encapsulated message ? */ + if ((body->type == TYPEMESSAGE) && (!strcasecmp(body->subtype, "rfc822"))) { + body = body->CONTENT_MSG_BODY; + array_init(¶metres); + object_init(¶m); + _php_imap_add_body(¶m, body); + add_next_index_object(¶metres, ¶m); + add_assoc_object(arg, "parts", ¶metres); + } +} +/* }}} */ + +/* imap_thread, stealing this from header cclient -rjs3 */ +/* {{{ build_thread_tree_helper + */ +static void build_thread_tree_helper(THREADNODE *cur, zval *tree, long *numNodes, char *buf) +{ + unsigned long thisNode = *numNodes; + + /* define "#.num" */ + snprintf(buf, 25, "%ld.num", thisNode); + + add_assoc_long(tree, buf, cur->num); + + snprintf(buf, 25, "%ld.next", thisNode); + if(cur->next) { + (*numNodes)++; + add_assoc_long(tree, buf, *numNodes); + build_thread_tree_helper(cur->next, tree, numNodes, buf); + } else { /* "null pointer" */ + add_assoc_long(tree, buf, 0); + } + + snprintf(buf, 25, "%ld.branch", thisNode); + if(cur->branch) { + (*numNodes)++; + add_assoc_long(tree, buf, *numNodes); + build_thread_tree_helper(cur->branch, tree, numNodes, buf); + } else { /* "null pointer" */ + add_assoc_long(tree, buf, 0); + } +} +/* }}} */ + +/* {{{ build_thread_tree + */ +static int build_thread_tree(THREADNODE *top, zval **tree) +{ + long numNodes = 0; + char buf[25]; + + array_init(*tree); + + build_thread_tree_helper(top, *tree, &numNodes, buf); + + return SUCCESS; +} +/* }}} */ + +/* {{{ proto array imap_thread(resource stream_id [, int options]) + Return threaded by REFERENCES tree */ +PHP_FUNCTION(imap_thread) +{ + zval *streamind; + pils *imap_le_struct; + zend_long flags = SE_FREE; + char criteria[] = "ALL"; + THREADNODE *top; + int argc = ZEND_NUM_ARGS(); + SEARCHPGM *pgm = NIL; + + if (zend_parse_parameters(argc, "r|l", &streamind, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + pgm = mail_criteria(criteria); + top = mail_thread(imap_le_struct->imap_stream, "REFERENCES", NIL, pgm, flags); + if (pgm && !(flags & SE_FREE)) { + mail_free_searchpgm(&pgm); + } + + if(top == NIL) { + php_error_docref(NULL, E_WARNING, "Function returned an empty tree"); + RETURN_FALSE; + } + + /* Populate our return value data structure here. */ + if(build_thread_tree(top, &return_value) == FAILURE) { + mail_free_threadnode(&top); + RETURN_FALSE; + } + mail_free_threadnode(&top); +} +/* }}} */ + +/* {{{ proto mixed imap_timeout(int timeout_type [, int timeout]) + Set or fetch imap timeout */ +PHP_FUNCTION(imap_timeout) +{ + zend_long ttype, timeout=-1; + int timeout_type; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &ttype, &timeout) == FAILURE) { + RETURN_FALSE; + } + + if (timeout == -1) { + switch (ttype) { + case 1: + timeout_type = GET_OPENTIMEOUT; + break; + case 2: + timeout_type = GET_READTIMEOUT; + break; + case 3: + timeout_type = GET_WRITETIMEOUT; + break; + case 4: + timeout_type = GET_CLOSETIMEOUT; + break; + default: + RETURN_FALSE; + break; + } + + timeout = (zend_long) mail_parameters(NIL, timeout_type, NIL); + RETURN_LONG(timeout); + } else if (timeout >= 0) { + switch (ttype) { + case 1: + timeout_type = SET_OPENTIMEOUT; + break; + case 2: + timeout_type = SET_READTIMEOUT; + break; + case 3: + timeout_type = SET_WRITETIMEOUT; + break; + case 4: + timeout_type = SET_CLOSETIMEOUT; + break; + default: + RETURN_FALSE; + break; + } + + timeout = (zend_long) mail_parameters(NIL, timeout_type, (void *) timeout); + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +#define GETS_FETCH_SIZE 8196LU +static char *php_mail_gets(readfn_t f, void *stream, unsigned long size, GETS_DATA *md) /* {{{ */ +{ + + /* write to the gets stream if it is set, + otherwise forward to c-clients gets */ + if (IMAPG(gets_stream)) { + char buf[GETS_FETCH_SIZE]; + + while (size) { + unsigned long read; + + if (size > GETS_FETCH_SIZE) { + read = GETS_FETCH_SIZE; + size -=GETS_FETCH_SIZE; + } else { + read = size; + size = 0; + } + + if (!f(stream, read, buf)) { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + break; + } else if (read != php_stream_write(IMAPG(gets_stream), buf, read)) { + php_error_docref(NULL, E_WARNING, "Failed to write to stream"); + break; + } + } + return NULL; + } else { + char *buf = pemalloc(size + 1, 1); + + if (f(stream, size, buf)) { + buf[size] = '\0'; + } else { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + free(buf); + buf = NULL; + } + return buf; + } +} +/* }}} */ + +/* {{{ Interfaces to C-client + */ +PHP_IMAP_EXPORT void mm_searched(MAILSTREAM *stream, unsigned long number) +{ + MESSAGELIST *cur = NIL; + + if (IMAPG(imap_messages) == NIL) { + IMAPG(imap_messages) = mail_newmessagelist(); + IMAPG(imap_messages)->msgid = number; + IMAPG(imap_messages)->next = NIL; + IMAPG(imap_messages_tail) = IMAPG(imap_messages); + } else { + cur = IMAPG(imap_messages_tail); + cur->next = mail_newmessagelist(); + cur = cur->next; + cur->msgid = number; + cur->next = NIL; + IMAPG(imap_messages_tail) = cur; + } +} + +PHP_IMAP_EXPORT void mm_exists(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_expunged(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_flags(MAILSTREAM *stream, unsigned long number) +{ +} + +/* Author: CJH */ +PHP_IMAP_EXPORT void mm_notify(MAILSTREAM *stream, char *str, long errflg) +{ + STRINGLIST *cur = NIL; + + if (strncmp(str, "[ALERT] ", 8) == 0) { + if (IMAPG(imap_alertstack) == NIL) { + IMAPG(imap_alertstack) = mail_newstringlist(); + IMAPG(imap_alertstack)->LSIZE = strlen((char*)(IMAPG(imap_alertstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_alertstack)->next = NIL; + } else { + cur = IMAPG(imap_alertstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newstringlist (); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_list(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build up a the new array of objects */ + /* Author: CJH */ + if (IMAPG(imap_folder_objects) == NIL) { + IMAPG(imap_folder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_folder_objects)->LSIZE=strlen((char*)(IMAPG(imap_folder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folder_objects)->delimiter = delimiter; + IMAPG(imap_folder_objects)->attributes = attributes; + IMAPG(imap_folder_objects)->next = NIL; + IMAPG(imap_folder_objects_tail) = IMAPG(imap_folder_objects); + } else { + ocur=IMAPG(imap_folder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE = strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_folder_objects_tail) = ocur; + } + + } else { + /* build the old IMAPG(imap_folders) variable to allow old imap_listmailbox() to work */ + if (!(attributes & LATT_NOSELECT)) { + if (IMAPG(imap_folders) == NIL) { + IMAPG(imap_folders)=mail_newstringlist(); + IMAPG(imap_folders)->LSIZE=strlen((char*)(IMAPG(imap_folders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folders)->next=NIL; + IMAPG(imap_folders_tail) = IMAPG(imap_folders); + } else { + cur=IMAPG(imap_folders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_folders_tail) = cur; + } + } + } +} + +PHP_IMAP_EXPORT void mm_lsub(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build the array of objects */ + /* Author: CJH */ + if (IMAPG(imap_sfolder_objects) == NIL) { + IMAPG(imap_sfolder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_sfolder_objects)->LSIZE = strlen((char*)(IMAPG(imap_sfolder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolder_objects)->delimiter = delimiter; + IMAPG(imap_sfolder_objects)->attributes = attributes; + IMAPG(imap_sfolder_objects)->next = NIL; + IMAPG(imap_sfolder_objects_tail) = IMAPG(imap_sfolder_objects); + } else { + ocur=IMAPG(imap_sfolder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE=strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_sfolder_objects_tail) = ocur; + } + } else { + /* build the old simple array for imap_listsubscribed() */ + if (IMAPG(imap_sfolders) == NIL) { + IMAPG(imap_sfolders)=mail_newstringlist(); + IMAPG(imap_sfolders)->LSIZE=strlen((char*)(IMAPG(imap_sfolders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolders)->next=NIL; + IMAPG(imap_sfolders_tail) = IMAPG(imap_sfolders); + } else { + cur=IMAPG(imap_sfolders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_sfolders_tail) = cur; + } + } +} + +PHP_IMAP_EXPORT void mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status) +{ + + IMAPG(status_flags)=status->flags; + if (IMAPG(status_flags) & SA_MESSAGES) { + IMAPG(status_messages)=status->messages; + } + if (IMAPG(status_flags) & SA_RECENT) { + IMAPG(status_recent)=status->recent; + } + if (IMAPG(status_flags) & SA_UNSEEN) { + IMAPG(status_unseen)=status->unseen; + } + if (IMAPG(status_flags) & SA_UIDNEXT) { + IMAPG(status_uidnext)=status->uidnext; + } + if (IMAPG(status_flags) & SA_UIDVALIDITY) { + IMAPG(status_uidvalidity)=status->uidvalidity; + } +} + +PHP_IMAP_EXPORT void mm_log(char *str, long errflg) +{ + ERRORLIST *cur = NIL; + + /* Author: CJH */ + if (errflg != NIL) { /* CJH: maybe put these into a more comprehensive log for debugging purposes? */ + if (IMAPG(imap_errorstack) == NIL) { + IMAPG(imap_errorstack) = mail_newerrorlist(); + IMAPG(imap_errorstack)->LSIZE = strlen((char*)(IMAPG(imap_errorstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_errorstack)->errflg = errflg; + IMAPG(imap_errorstack)->next = NIL; + } else { + cur = IMAPG(imap_errorstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newerrorlist(); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->errflg = errflg; + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_dlog(char *str) +{ + /* CJH: this is for debugging; it might be useful to allow setting + the stream to debug mode and capturing this somewhere - syslog? + php debugger? */ +} + +PHP_IMAP_EXPORT void mm_login(NETMBX *mb, char *user, char *pwd, long trial) +{ + + if (*mb->user) { + strlcpy (user, mb->user, MAILTMPLEN); + } else { + strlcpy (user, IMAPG(imap_user), MAILTMPLEN); + } + strlcpy (pwd, IMAPG(imap_password), MAILTMPLEN); +} + +PHP_IMAP_EXPORT void mm_critical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT void mm_nocritical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT long mm_diskerror(MAILSTREAM *stream, long errcode, long serious) +{ + return 1; +} + +PHP_IMAP_EXPORT void mm_fatal(char *str) +{ +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/imap/tests/bug77020.phpt b/ext/imap/tests/bug77020.phpt new file mode 100644 index 0000000000000..8a65232eec6d3 --- /dev/null +++ b/ext/imap/tests/bug77020.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #77020 (null pointer dereference in imap_mail) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECTF-- +Warning: imap_mail(): No message string in mail command in %s on line %d +%s +===DONE=== From 9e1d2e7d73fec5e716058228829934dbbe9e0b9a Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:42 +0000 Subject: [PATCH 04/25] commit patch 26003849 --- NEWS | 4 + NEWS.orig | 1902 ++++++++++++++++++++++++++++++++++ UPGRADING | 7 + ext/imap/php_imap.c | 17 + ext/imap/php_imap.h | 1 + ext/imap/tests/bug77153.phpt | 24 + 6 files changed, 1955 insertions(+) create mode 100644 NEWS.orig create mode 100644 ext/imap/tests/bug77153.phpt diff --git a/NEWS b/NEWS index 60c259a6e6df3..80e3074f7f28e 100644 --- a/NEWS +++ b/NEWS @@ -1572,6 +1572,10 @@ PHP NEWS . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + - ODBC: . Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes). (Anatol) diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..60c259a6e6df3 --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,1902 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +?? ??? ????, PHP 7.2.1 + + +- Core: + . Fixed bug #75573 (Segmentation fault in 7.1.12 and 7.0.26). (Laruence) + . Fixed bug #75384 (PHP seems incompatible with OneDrive files on demand). + (Anatol) + . Fixed bug #75525 (Access Violation in vcruntime140.dll). (Anatol) + . Fixed bug #74862 (Unable to clone instance when private __clone defined). + (Daniel Ciochiu) + . Fixed bug #75074 (php-process crash when is_file() is used with strings + longer 260 chars). (Anatol) + +- CLI server: + . Fixed bug #73830 (Directory does not exist). (Anatol) + +- FPM: + . Fixed bug #64938 (libxml_disable_entity_loader setting is shared between + requests). (Remi) + +- GD: + . Fixed bug #75571 (Potential infinite loop in gdImageCreateFromGifCtx). + (Christoph) + +- Opcache: + . Fixed bug #75608 ("Narrowing occurred during type inference" error). + (Laruence, Dmitry) + . Fixed bug #75579 (Interned strings buffer overflow may cause crash). + (Dmitry) + . Fixed bug #75570 ("Narrowing occurred during type inference" error). + (Dmitry) + . Fixed bug #75556 (Invalid opcode 138/1/1). (Laruence) + +- PCRE: + . Fixed bug #74183 (preg_last_error not returning error code after error). + (Andrew Nester) + +- Phar: + . Fixed bug #74782 (remove file name from output to avoid XSS). (stas) + +- Standard: + . Fixed bug #75511 (fread not free unused buffer). (Laruence) + . Fixed bug #75514 (mt_rand returns value outside [$min,$max]+ on 32-bit) + (Remi) + . Fixed bug #75535 (Inappropriately parsing HTTP response leads to PHP + segment fault). (Nikita) + . Fixed bug #75409 (accept EFAULT in addition to ENOSYS as indicator + that getrandom() is missing). (sarciszewski) + . Fixed bug #73124 (php_ini_scanned_files() not reporting correctly). + (John Stevenson) + . Fixed bug #75574 (putenv does not work properly if parameter contains + non-ASCII unicode character). (Anatol) + +- Zip: + . Fixed bug #75540 (Segfault with libzip 1.3.1). (Remi) + +30 Nov 2017, PHP 7.2.0 + +- BCMath: + . Fixed bug #46564 (bcmod truncates fractionals). (liborm85) + +- CLI: + . Fixed bug #74849 (Process is started as interactive shell in PhpStorm). + (Anatol) + . Fixed bug #74979 (Interactive shell opening instead of script execution + with -f flag). (Anatol) + +- CLI server: + . Fixed bug #60471 (Random "Invalid request (unexpected EOF)" using a router + script). (SammyK) + +- Core: + . Added ZEND_COUNT, ZEND_GET_CLASS, ZEND_GET_CALLED_CLASS, ZEND_GET_TYPE, + ZEND_FUNC_NUM_ARGS, ZEND_FUNC_GET_ARGS instructions, to implement + corresponding builtin functions. (Dmitry) + . "Countable" interface is moved from SPL to Core. (Dmitry) + . Added ZEND_IN_ARRAY instruction, implementing optimized in_array() builtin + function, through hash lookup in flipped array. (Dmitry) + . Removed IS_TYPE_IMMUTABLE (it's the same as COPYABLE & !REFCOUNTED). (Dmitry) + . Removed the sql.safe_mode directive. (Kalle) + . Removed support for Netware. (Kalle) + . Renamed ReflectionClass::isIterateable() to ReflectionClass::isIterable() + (alias original name for BC). (Sara) + . Fixed bug #54535 (WSA cleanup executes before MSHUTDOWN). (Kalle) + . Implemented FR #69791 (Disallow mail header injections by extra headers) + (Yasuo) + . Implemented FR #49806 (proc_nice() for Windows). (Kalle) + . Fix pthreads detection when cross-compiling (ffontaine) + . Fixed memory leaks caused by exceptions thrown from destructors. (Bob, + Dmitry). + . Fixed bug #73215 (uniqid() should use better random source). (Yasuo) + . Implemented FR #72768 (Add ENABLE_VIRTUAL_TERMINAL_PROCESSING flag for + php.exe). (Michele Locati) + . Implemented "Convert numeric keys in object/array casts" RFC, fixes + bugs #53838, #61655, #66173, #70925, #72254, etc. (Andrea) + . Implemented "Deprecate and Remove Bareword (Unquoted) Strings" RFC. + (Rowan Collins) + . Raised minimum supported Windows versions to Windows 7/Server 2008 R2. + (Anatol) + . Implemented minor optimization in array_keys/array_values(). (Sara) + . Added PHP_OS_FAMILY constant to determine on which OS we are. (Jan Altensen) + . Fixed bug #73987 (Method compatibility check looks to original + definition and not parent). (pmmaga) + . Fixed bug #73991 (JSON_OBJECT_AS_ARRAY not respected). (Sara) + . Fixed bug #74053 (Corrupted class entries on shutdown when a destructor + spawns another object). (jim at commercebyte dot com) + . Fixed bug #73971 (Filename got limited to MAX_PATH on Win32 when scan + directory). (Anatol) + . Fixed bug #72359, bug #72451, bug #73706, bug #71115 and others related + to interned strings handling in TS builds. (Anatol, Dmitry) + . Implemented "Trailing Commas In List Syntax" RFC for group use lists only. + (Sammy Kaye Powers) + . Fixed bug #74269 (It's possible to override trait property with different + loosely-equal value). (pmmaga) + . Fixed bug #61970 (Restraining __construct() access level in subclass gives + a fatal error). (pmmaga) + . Fixed bug #63384 (Cannot override an abstract method with an abstract + method). (pmmaga, wes) + . Fixed bug #74607 (Traits enforce different inheritance rules). (pmmaga) + . Fixed misparsing of abstract unix domain socket names. (Sara) + . Change PHP_OS_FAMILY value from "OSX" to "Darwin". (Sebastian, Kalle) + . Allow loading PHP/Zend extensions by name in ini files (extension=). + (francois at tekwire dot net) + . Added object type annotation. (brzuchal) + . Fixed bug #74815 (crash with a combination of INI entries at startup). + (Anatol) + . Fixed bug #74836 (isset on zero-prefixed numeric indexes in array broken). + (Dmitry) + . Added new VM instuctions ISSET_ISEMPTY_CV and UNSET_CV. Previously they + were implemented as ISSET_ISEMPTY_VAR and UNSET_VAR variants with + ZEND_QUICK_SET flag. (Nikita, Dmitry) + . Fixed bug #49649 (unserialize() doesn't handle changes in property + visibility). (pmmaga) + . Fixed #74866 (extension_dir = "./ext" now use current directory for base). + (Francois Laupretre) + . Implemented FR #74963 (Improved error message on fetching property of + non-object). (Laruence) + . Fixed Bug #75142 (buildcheck.sh check for autoconf version needs to be updated + for v2.64). (zizzy at zizzy dot net, Remi) + . Fixed bug #74878 (Data race in ZTS builds). (Nikita, Dmitry) + . Fixed bug #75515 ("stream_copy_to_stream" doesn't stream anymore). (Sara) + +- cURL: + . Fixed bug #75093 (OpenSSL support not detected). (Remi) + . Better fix for #74125 (use pkg-config instead of curl-config). (Remi) + +- Date: + . Fixed bug #55407 (Impossible to prototype DateTime::createFromFormat). + (kelunik) + . Implemented FR #71520 (Adding the DateTime constants to the + DateTimeInterface interface). (Majkl578) + . Fixed bug #75149 (redefinition of typedefs ttinfo and t1info). (Remi) + . Fixed bug #75222 (DateInterval microseconds property always 0). (jhdxr) + +- Dba: + . Fixed bug #72885 (flatfile: dba_fetch() fails to read replaced entry). + (Anatol) + +- DOM: + . Implement #74837 (Implement Countable for DomNodeList and DOMNamedNodeMap). + (Andreas Treichel) + +- EXIF: + . Added support for vendor specific tags for the following formats: + Samsung, DJI, Panasonic, Sony, Pentax, Minolta, Sigma/Foveon, AGFA, + Kyocera, Ricoh & Epson. (Kalle) + . Fixed bug #72682 (exif_read_data() fails to read all data for some + images). (Kalle) + . Fixed bug #71534 (Type confusion in exif_read_data() leading to heap + overflow in debug mode). (hlt99 at blinkenshell dot org, Kalle) + . Fixed bug #68547 (Exif Header component value check error). + (sjh21a at gmail dot com, Kalle) + . Fixed bug #66443 (Corrupt EXIF header: maximum directory nesting level + reached for some cameras). (Kalle) + . Fixed Redhat bug #1362571 (PHP not returning full results for + exif_read_data function). (Kalle) + . Implemented #65187 (exif_read_data/thumbnail: add support for stream + resource). (Kalle) + . Deprecated the read_exif_data() alias. (Kalle) + . Fixed bug #74428 (exif_read_data(): "Illegal IFD size" warning occurs with + correct exif format). (bradpiccho at gmail dot com, Kalle) + . Fixed bug #72819 (EXIF thumbnails not read anymore). (Kalle) + . Fixed bug #62523 (php crashes with segfault when exif_read_data called). + (Kalle) + . Fixed bug #50660 (exif_read_data(): Illegal IFD offset (works fine with + other exif readers). (skinny dot bravo at gmail dot com, Kalle) + +- Fileinfo: + . Upgrade bundled libmagic to 5.31. (Anatol) + +- FPM: + . Configuration to limit fpm slow log trace callers. (Sannis) + . Fixed bug #75212 (php_value acts like php_admin_value). (Remi) + +- FTP: + . Implement MLSD for structured listing of directories. (blar) + . Added ftp_append() function. (blar) + +- GD: + . Implemented imageresolution as getter and setter (Christoph) + . Fixed bug #74744 (gd.h: stdarg.h include missing for va_list use in + gdErrorMethod). (rainer dot jung at kippdata dot de, cmb) + . Fixed bug #75111 (Memory disclosure or DoS via crafted .bmp image). (cmb) + +- GMP: + . Fixed bug #70896 (gmp_fact() silently ignores non-integer input). (Sara) + +- Hash: + . Changed HashContext from resource to object. (Rouven Weßling, Sara) + . Disallowed usage of non-cryptographic hash functions with HMAC and PBKDF2. + (Andrey Andreev, Nikita) + . Fixed Bug #75284 (sha3 is not supported on bigendian machine). (Remi) + +- IMAP: + . Fixed bug #72324 (imap_mailboxmsginfo() return wrong size). + (ronaldpoon at udomain dot com dot hk, Kalle) + +- Intl: + . Fixed bug #63790 (test using Spoofchecker which may be unavailable). (Sara) + . Fixed bug #75378 ([REGRESSION] IntlDateFormatter::parse() does not change + $position argument). (Laruence) + +- JSON: + . Add JSON_INVALID_UTF8_IGNORE and JSON_INVALID_UTF8_SUBSTITUTE options for + json_encode and json_decode to ignore or replace invalid UTF-8 byte + sequences - it addresses request #65082. (Jakub Zelenka) + . Fixed bug #75185 (Buffer overflow in json_decode() with + JSON_INVALID_UTF8_IGNORE or JSON_INVALID). (Jakub Zelenka) + . Fixed bug #68567 (JSON_PARTIAL_OUTPUT_ON_ERROR can result in JSON with null + key). (Jakub Zelenka) + +- LDAP: + . Implemented FR #69445 (Support for LDAP EXOP operations) + . Fixed support for LDAP_OPT_SERVER_CONTROLS and LDAP_OPT_CLIENT_CONTROLS in ldap_get_option + . Fixed passing an empty array to ldap_set_option for client or server controls. + +- Mbstring: + . Implemented request #66024 (mb_chr() and mb_ord()). (Masakielastic, Yasuo) + . Implemented request #65081 (mb_scrub()). (Masakielastic, Yasuo) + . Implemented request #69086 (enhancement for mb_convert_encoding() that + handles multibyte replacement char nicely). (Masakielastic, Yasuo) + . Added array input support to mb_convert_encoding(). (Yasuo) + . Added array input support to mb_check_encoding(). (Yasuo) + . Fixed bug #69079 (enhancement for mb_substitute_character). (masakielastic) + . Update to oniguruma version 6.3.0. (Remi) + . Fixed bug #69267 (mb_strtolower fails on titlecase characters). (Nikita) + +- Mcrypt: + . The deprecated mcrypt extension has been moved to PECL. (leigh) + +- Opcache: + . Added global optimisation passes based on data flow analysis using Single + Static Assignment (SSA) form: Sparse Conditional Constant Propagation (SCCP), + Dead Code Elimination (DCE), and removal of unused local variables + (Nikita, Dmitry) + . Fixed incorect constant conditional jump elimination. (Dmitry) + . Fixed bug #75230 (Invalid opcode 49/1/8 using opcache). (Laruence) + . Fixed bug (assertion fails with extended info generated). (Laruence) + . Fixed bug (Phi sources removel). (Laruence) + . Fixed bug #75370 (Webserver hangs on valid PHP text). (Laruence) + . Fixed bug #75357 (segfault loading WordPress wp-admin). (Laruence) + +- OpenSSL: + . Use TLS_ANY for default ssl:// and tls:// negotiation. (kelunik) + . Fix leak in openssl_spki_new(). (jelle at vdwaa dot nl) + . Added openssl_pkcs7_read() and pk7 parameter to openssl_pkcs7_verify(). + (jelle at vdwaa dot nl) + . Add ssl security_level stream option to support OpenSSL security levels. + (Jakub Zelenka). + . Allow setting SNI cert and private key in separate files. (Jakub Zelenka) + . Fixed bug #74903 (openssl_pkcs7_encrypt() uses different EOL than before). + (Anatol) + . Automatically load OpenSSL configuration file. (Jakub Zelenka) + +- PCRE: + . Added support for PCRE JIT fast path API. (dmitry) + . Fixed bug #61780 (Inconsistent PCRE captures in match results). (cmb) + . Fixed bug #74873 (Minor BC break: PCRE_JIT changes output of preg_match()). + (Dmitry) + . Fixed bug #75089 (preg_grep() is not reporting PREG_BAD_UTF8_ERROR after + first input string). (Dmitry) + . Fixed bug #75223 (PCRE JIT broken in 7.2). (Dmitry) + . Fixed bug #75285 (Broken build when system libpcre don't have jit support). + (Remi) + +- phar: + . Fixed bug #74196 (phar does not correctly handle names containing dots). + (mhagstrand) + +- PDO: + . Add "Sent SQL" to debug dump for emulated prepares. (Adam Baratz) + . Add parameter types for national character set strings. (Adam Baratz) + +- PDO_DBlib: + . Fixed bug #73234 (Emulated statements let value dictate parameter type). + (Adam Baratz) + . Fixed bug #73396 (bigint columns are returned as strings). (Adam Baratz) + . Expose DB-Library version as \PDO::DBLIB_ATTR_VERSION attribute on \PDO + instance. (Adam Baratz) + . Add test coverage for bug #72969. (Jeff Farr) + +- PDO_OCI: + . Fixed Bug #74537 (Align --with-pdo-oci configure option with --with-oci8 syntax). + (Tianfang Yang) + +- PDO_Sqlite + . Switch to sqlite3_prepare_v2() and sqlite3_close_v2() functions (rasmus) + +- PHPDBG + . Added extended_value to opcode dump output. (Sara) + +- Session: + . Fixed bug #73461 (Prohibit session save handler recursion). (Yasuo) + . PR #2233 Removed register_globals related code and "!" can be used as $_SESSION key name. (Yasuo) + . Improved bug #73100 fix. 'user' save handler can only be set by session_set_save_handler() + . Fixed bug #74514 (5 session functions incorrectly warn when calling in + read-only/getter mode). (Yasuo) + . Fixed bug #74936 (session_cache_expire/cache_limiter/save_path() trigger a + warning in read mode). (morozov) + . Fixed bug #74941 (session fails to start after having headers sent). + (morozov) + +- Sodium: + . New cryptographic extension + . Added missing bindings for libsodium > 1.0.13. (Frank) + +- SPL: + . Fixed bug #71412 (Incorrect arginfo for ArrayIterator::__construct). + (tysonandre775 at hotmail dot com) + . Added spl_object_id(). (Tyson Andre) + +- SQLite3: + . Implement writing to blobs. (bohwaz at github dot com) + . Update to Sqlite 3.20.1. (cmb) + +- Standard: + . Fixed bug #69442 (closing of fd incorrect when PTS enabled). (jaytaph) + . Fixed bug #74300 (unserialize accepts two plus/minus signs for float number exponent part). + (xKerman) + . Compatibility with libargon2 versions 20161029 and 20160821. + (charlesportwoodii at erianna dot com) + . Fixed Bug #74737 (mysqli_get_client_info reflection info). + (mhagstrand at gmail dot com) + . Add support for extension name as argument to dl(). + (francois at tekwire dot net) + . Fixed bug #74851 (uniqid() without more_entropy performs badly). + (Emmanuel Dreyfus) + . Fixed bug #74103 (heap-use-after-free when unserializing invalid array + size). (Nikita) + . Fixed bug #75054 (A Denial of Service Vulnerability was found when + performing deserialization). (Nikita) + . Fixed bug #75170 (mt_rand() bias on 64-bit machines). (Nikita) + . Fixed bug #75221 (Argon2i always throws NUL at the end). (cmb) + +- Streams: + . Default ssl/single_dh_use and ssl/honor_cipher_order to true. (kelunik) + +- XML: + . Moved utf8_encode() and utf8_decode() to the Standard extension. (Andrea) + +- XMLRPC: + . Use Zend MM for allocation in bundled libxmlrpc (Joe) + +- ZIP: + . Add support for encrypted archives. (Remi) + . Use of bundled libzip is deprecated, --with-libzip option is recommended. (Remi) + . Fixed Bug #73803 (Reflection of ZipArchive does not show public properties). (Remi) + . ZipArchive implements countable, added ZipArchive::count() method. (Remi) + . Fix segfault in php_stream_context_get_option call. (Remi) + . Fixed bug #75143 (new method setEncryptionName() seems not to exist + in ZipArchive). (Anatol) + +- zlib: + . Expose inflate_get_status() and inflate_get_read_len() functions. + (Matthew Trescott) + +23 Nov 2017, PHP 7.1.12 + +- Core: + . Fixed bug #75420 (Crash when modifing property name in __isset for + BP_VAR_IS). (Laruence) + . Fixed bug #75368 (mmap/munmap trashing on unlucky allocations). (Nikita, + Dmitry) + +- CLI: + . Fixed bug #75287 (Builtin webserver crash after chdir in a shutdown + function). (Laruence) + +- Enchant: + . Fixed bug #53070 (enchant_broker_get_path crashes if no path is set). (jelle + van der Waa, cmb) + . Fixed bug #75365 (Enchant still reports version 1.1.0). (cmb) + +- Exif: + . Fixed bug #75301 (Exif extension has built in revision version). (Peter + Kokot) + +- GD: + . Fixed bug #65148 (imagerotate may alter image dimensions). (cmb) + . Fixed bug #75437 (Wrong reflection on imagewebp). (Fabien Villepinte) + +- intl: + . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead + of destination). (andrewnester) + +- interbase: + . Fixed bug #75453 (Incorrect reflection for ibase_[p]connect). (villfa) + +- Mysqli: + . Fixed bug #75434 (Wrong reflection for mysqli_fetch_all function). (Fabien + Villepinte) + +- OCI8: + . Fixed valgrind issue. (Tianfang Yang) + +- OpenSSL: + . Fixed bug #75363 (openssl_x509_parse leaks memory). (Bob, Jakub Zelenka) + . Fixed bug #75307 (Wrong reflection for openssl_open function). (villfa) + +- Opcache: + . Fixed bug #75373 (Warning Internal error: wrong size calculation). (Laruence, Dmitry) + +- PGSQL: + . Fixed bug #75419 (Default link incorrectly cleared/linked by pg_close()). (Sara) + +- SOAP: + . Fixed bug #75464 (Wrong reflection on SoapClient::__setSoapHeaders). (villfa) + +- Zlib: + . Fixed bug #75299 (Wrong reflection on inflate_init and inflate_add). (Fabien + Villepinte) + +26 Oct 2017, PHP 7.1.11 + +- Core: + . Fixed bug #75241 (Null pointer dereference in zend_mm_alloc_small()). + (Laruence) + . Fixed bug #75236 (infinite loop when printing an error-message). (Andrea) + . Fixed bug #75252 (Incorrect token formatting on two parse errors in one + request). (Nikita) + . Fixed bug #75220 (Segfault when calling is_callable on parent). + (andrewnester) + . Fixed bug #75290 (debug info of Closures of internal functions contain + garbage argument names). (Andrea) + +- Apache2Handler: + . Fixed bug #75311 (error: 'zend_hash_key' has no member named 'arKey' in + apache2handler). (mcarbonneaux) + +- Date: + . Fixed bug #75055 (Out-Of-Bounds Read in timelib_meridian()). (Derick) + +- Hash: + . Fixed bug #75303 (sha3 hangs on bigendian). (Remi) + +- Intl: + . Fixed bug #75318 (The parameter of UConverter::getAliases() is not + optional). (cmb) + +- litespeed: + . Fixed bug #75248 (Binary directory doesn't get created when building + only litespeed SAPI). (petk) + . Fixed bug #75251 (Missing program prefix and suffix). (petk) + +- mcrypt: + . Fixed bug #72535 (arcfour encryption stream filter crashes php). (Leigh) + +- MySQLi: + . Fixed bug #75018 (Data corruption when reading fields of bit type). (Anatol) + +- OCI8: + . Fixed incorrect reference counting. (Dmitry, Tianfang Yang) + +- Opcache + . Fixed bug #75255 (Request hangs and not finish). (Dmitry) + +- PCRE: + . Fixed bug #75207 (applied upstream patch for CVE-2016-1283). (Anatol) + +- PDO_mysql: + . Fixed bug #75177 (Type 'bit' is fetched as unexpected string). (Anatol) + +- SPL: + . Fixed bug #73629 (SplDoublyLinkedList::setIteratorMode masks intern flags). + (J. Jeising, cmb) + +28 Sep 2017, PHP 7.1.10 + +- Core: + . Fixed bug #75042 (run-tests.php issues with EXTENSION block). (John Boehr) + +- BCMath: + . Fixed bug #44995 (bcpowmod() fails if scale != 0). (cmb) + . Fixed bug #46781 (BC math handles minus zero incorrectly). (cmb) + . Fixed bug #54598 (bcpowmod() may return 1 if modulus is 1). (okano1220, cmb) + . Fixed bug #75178 (bcpowmod() misbehaves for non-integer base or modulus). (cmb) + +- CLI server: + . Fixed bug #70470 (Built-in server truncates headers spanning over TCP + packets). (bouk) + +- CURL: + . Fixed bug #75093 (OpenSSL support not detected). (Remi) + +- GD: + . Fixed bug #75124 (gdImageGrayScale() may produce colors). (cmb) + . Fixed bug #75139 (libgd/gd_interpolation.c:1786: suspicious if ?). (cmb) + +- Gettext: + . Fixed bug #73730 (textdomain(null) throws in strict mode). (cmb) + +- Intl: + . Fixed bug #75090 (IntlGregorianCalendar doesn't have constants from parent + class). (tpunt) + . Fixed bug #75193 (segfault in collator_convert_object_to_string). (Remi) + +- PDO_OCI: + . Fixed bug #74631 (PDO_PCO with PHP-FPM: OCI environment initialized + before PHP-FPM sets it up). (Ingmar Runge) + +- SPL: + . Fixed bug #75155 (AppendIterator::append() is broken when appending another + AppendIterator). (Nikita) + . Fixed bug #75173 (incorrect behavior of AppendIterator::append in foreach loop). + (jhdxr) + +- Standard: + . Fixed bug #75152 (signed integer overflow in parse_iv). (Laruence) + . Fixed bug #75097 (gethostname fails if your host name is 64 chars long). (Andrea) + +31 Aug 2017, PHP 7.1.9 + +- Core: + . Fixed bug #74947 (Segfault in scanner on INF number). (Laruence) + . Fixed bug #74954 (null deref and segfault in zend_generator_resume()). (Bob) + . Fixed bug #74725 (html_errors=1 breaks unhandled exceptions). (Andrea) + . Fixed bug #75063 (Main CWD initialized with wrong codepage). (Anatol) + . Fixed bug #75349 (NAN comparison). (Sara) + +- cURL: + . Fixed bug #74125 (Fixed finding CURL on systems with multiarch support). + (cebe) + +- Date: + . Fixed bug #75002 (Null Pointer Dereference in timelib_time_clone). (Derick) + +- Intl: + . Fixed bug #74993 (Wrong reflection on some locale_* functions). (Sara) + +- Mbstring: + . Fixed bug #71606 (Segmentation fault mb_strcut with HTML-ENTITIES encoding). + (cmb) + . Fixed bug #62934 (mb_convert_kana() does not convert iteration marks). + (Nikita) + . Fixed bug #75001 (Wrong reflection on mb_eregi_replace). (Fabien + Villepinte) + +- MySQLi: + . Fixed bug #74968 (PHP crashes when calling mysqli_result::fetch_object with + an abstract class). (Anatol) + +- OCI8: + . Expose oci_unregister_taf_callback() (Tianfang Yang) + +- Opcache: + . Fixed bug #74980 (Narrowing occurred during type inference). (Laruence) + +- phar: + . Fixed bug #74991 (include_path has a 4096 char limit in some cases). + (bwbroersma) + +- Reflection: + . Fixed bug #74949 (null pointer dereference in _function_string). (Laruence) + +- Session: + . Fixed bug #74892 (Url Rewriting (trans_sid) not working on urls that start + with "#"). (Andrew Nester) + . Fixed bug #74833 (SID constant created with wrong module number). (Anatol) + +- SimpleXML: + . Fixed bug #74950 (nullpointer deref in simplexml_element_getDocNamespaces). + (Laruence) + +- SPL: + . Fixed bug #75049 (spl_autoload_unregister can't handle + spl_autoload_functions results). (Laruence) + . Fixed bug #74669 (Unserialize ArrayIterator broken). (Andrew Nester) + . Fixed bug #74977 (Appending AppendIterator leads to segfault). + (Andrew Nester) + . Fixed bug #75015 (Crash in recursive iterator destructors). (Julien) + +- Standard: + . Fixed bug #75075 (unpack with X* causes infinity loop). (Laruence) + . Fixed bug #74103 (heap-use-after-free when unserializing invalid array + size). (Nikita) + . Fixed bug #75054 (A Denial of Service Vulnerability was found when + performing deserialization). (Nikita) + +- WDDX: + . Fixed bug #73793 (WDDX uses wrong decimal seperator). (cmb) + +- XMLRPC: + . Fixed bug #74975 (Incorrect xmlrpc serialization for classes with declared + properties). (blar) + +03 Aug 2017, PHP 7.1.8 + +- Core: + . Fixed bug #74832 (Loading PHP extension with already registered function + name leads to a crash). (jpauli) + . Fixed bug #74780 (parse_url() broken when query string contains colon). + (jhdxr) + . Fixed bug #74761 (Unary operator expected error on some systems). (petk) + . Fixed bug #73900 (Use After Free in unserialize() SplFixedArray). (nikic) + . Fixed bug #74923 (Crash when crawling through network share). (Anatol) + . Fixed bug #74913 (fixed incorrect poll.h include). (petk) + . Fixed bug #74906 (fixed incorrect errno.h include). (petk) + +- Date: + . Fixed bug #74852 (property_exists returns true on unknown DateInterval + property). (jhdxr) + +- OCI8: + . Fixed bug #74625 (Integer overflow in oci_bind_array_by_name). (Ingmar Runge) + +- Opcache: + . Fixed bug #74623 (Infinite loop in type inference when using HTMLPurifier). + (nikic) + +- OpenSSL: + . Fixed bug #74798 (pkcs7_en/decrypt does not work if \x0a is used in content). + (Anatol) + . Added OPENSSL_DONT_ZERO_PAD_KEY constant to prevent key padding and fix bug + #71917 (openssl_open() returns junk on envelope < 16 bytes) and bug #72362 + (OpenSSL Blowfish encryption is incorrect for short keys). (Jakub Zelenka) + +- PDO: + . Fixed bug #69356 (PDOStatement::debugDumpParams() truncates query). (Adam + Baratz) + +- SPL: + . Fixed bug #73471 (PHP freezes with AppendIterator). (jhdxr) + +- SQLite3: + . Fixed bug #74883 (SQLite3::__construct() produces "out of memory" exception + with invalid flags). (Anatol) + +- Wddx: + . Fixed bug #73173 (huge memleak when wddx_unserialize). + (tloi at fortinet dot com) + +- zlib: + . Fixed bug #73944 (dictionary option of inflate_init() does not work). + (wapmorgan) + +06 Jul 2017, PHP 7.1.7 + +- Core: + . Fixed bug #74738 (Multiple [PATH=] and [HOST=] sections not properly + parsed). (Manuel Mausz) + . Fixed bug #74658 (Undefined constants in array properties result in broken + properties). (Laruence) + . Fixed misparsing of abstract unix domain socket names. (Sara) + . Fixed bug #74603 (PHP INI Parsing Stack Buffer Overflow Vulnerability). + (Stas) + . Fixed bug #74101, bug #74614 (Unserialize Heap Use-After-Free (READ: 1) in + zval_get_type). (Nikita) + . Fixed bug #74111 (Heap buffer overread (READ: 1) finish_nested_data from + unserialize). (Nikita) + . Fixed bug #74819 (wddx_deserialize() heap out-of-bound read via + php_parse_date()). (Derick) + +- Date: + . Fixed bug #74639 (implement clone for DatePeriod and DateInterval). + (andrewnester) + +- DOM: + . Fixed bug #69373 (References to deleted XPath query results). (ttoohey) + +- GD: + . Fixed bug #74435 (Buffer over-read into uninitialized memory). (cmb) + +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + . Fixed bug #74705 (Wrong reflection on Collator::getSortKey and + collator_get_sort_key). (Tyson Andre, Remi) + +- Mbstring: + . Add oniguruma upstream fix (CVE-2017-9224, CVE-2017-9226, CVE-2017-9227, + CVE-2017-9228, CVE-2017-9229) (Remi, Mamoru TASAKA) + +- OCI8: + . Add TAF callback (PR #2459). (KoenigsKind) + +- Opcache: + . Fixed bug #74663 (Segfault with opcache.memory_protect and + validate_timestamp). (Laruence) + . Revert opcache.enable_cli to default disabled. (Nikita) + +- OpenSSL: + . Fixed bug #74720 (pkcs7_en/decrypt does not work if \x1a is used in + content). (Anatol) + . Fixed bug #74651 (negative-size-param (-1) in memcpy in zif_openssl_seal()). + (Stas) + +- PDO_OCI: + . Support Instant Client 12.2 in --with-pdo-oci configure option. + (Tianfang Yang) + +- Reflection: + . Fixed bug #74673 (Segfault when cast Reflection object to string with + undefined constant). (Laruence) + +- SPL: + . Fixed bug #74478 (null coalescing operator failing with SplFixedArray). + (jhdxr) + +- FTP: + . Fixed bug #74598 (ftp:// wrapper ignores context arg). (Sara) + +- PHAR: + . Fixed bug #74386 (Phar::__construct reflection incorrect). (villfa) + +- SOAP + . Fixed bug #74679 (Incorrect conversion array with WSDL_CACHE_MEMORY). + (Dmitry) + +- Streams: + . Fixed bug #74556 (stream_socket_get_name() returns '\0'). (Sara) + +8 Jun 2017, PHP 7.1.6 + +- Core: + . Fixed bug #74600 (crash (SIGSEGV) in _zend_hash_add_or_update_i). + (Laruence) + . Fixed bug #74546 (SIGILL in ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_CONST). + (Laruence) + . Fixed bug #74589 (__DIR__ wrong for unicode character). (Anatol) + +- intl: + . Fixed bug #74468 (wrong reflection on Collator::sortWithSortKeys). (villfa) + +- MySQLi: + . Fixed bug #74547 (mysqli::change_user() doesn't accept null as $database + argument w/strict_types). (Anatol) + +- Opcache: + . Fixed bug #74596 (SIGSEGV with opcache.revalidate_path enabled). (Laruence) + +- phar: + . Fixed bug #51918 (Phar::webPhar() does not handle requests sent through PUT + and DELETE method). (Christian Weiske) + +- Readline: + . Fixed bug #74490 (readline() moves the cursor to the beginning of the line). + (Anatol) + +- Standard: + . Fixed bug #74510 (win32/sendmail.c anchors CC header but not BCC). + (Damian Wadley, Anatol) + +- xmlreader: + . Fixed bug #74457 (Wrong reflection on XMLReader::expand). (villfa) + +11 May 2017, PHP 7.1.5 + +- Core: + . Fixed bug #74408 (Endless loop bypassing execution time limit). (Laruence) + . Fixed bug #74353 (Segfault when killing within bash script trap code). + (Laruence) + . Fixed bug #74340 (Magic function __get has different behavior in php 7.1.x). + (Nikita) + . Fixed bug #74188 (Null coalescing operator fails for undeclared static + class properties). (tpunt) + . Fixed bug #74444 (multiple catch freezes in some cases). (David MatÄ›jka) + . Fixed bug #74410 (stream_select() is broken on Windows Nanoserver). + (Matt Ficken) + . Fixed bug #74337 (php-cgi.exe crash on facebook callback). + (Anton Serbulov) + . Patch for bug #74216 was reverted. (Anatol) + +- Date: + . Fixed bug #74404 (Wrong reflection on DateTimeZone::getTransitions). + (krakjoe) + . Fixed bug #74080 (add constant for RFC7231 format datetime). (duncan3dc) + +- DOM: + . Fixed bug #74416 (Wrong reflection on DOMNode::cloneNode). + (Remi, Fabien Villepinte) + +- Fileinfo: + . Fixed bug #74379 (syntax error compile error in libmagic/apprentice.c). + (Laruence) + +- GD: + . Fixed bug #74343 (compile fails on solaris 11 with system gd2 library). + (krakjoe) + +- MySQLi: + . Fixed bug #74432 (mysqli_connect adding ":3306" to $host if $port parameter + not given). (Anatol) + +- MySQLnd: + . Fixed bug #74376 (Invalid free of persistent results on error/connection + loss). (Yussuf Khalil) + +- Intl: + . Fixed bug #65683 (Intl does not support DateTimeImmutable). (Ben Scholzen) + . Fixed bug #74298 (IntlDateFormatter->format() doesn't return + microseconds/fractions). (Andrew Nester) + . Fixed bug #74433 (wrong reflection for Normalizer methods). (villfa) + . Fixed bug #74439 (wrong reflection for Locale methods). (villfa) + +- Opcache: + . Fixed bug #74456 (Segmentation error while running a script in CLI mode). + (Laruence) + . Fixed bug #74431 (foreach infinite loop). (Nikita) + . Fixed bug #74442 (Opcached version produces a nested array). (Nikita) + +- OpenSSL: + . Fixed bug #73833 (null character not allowed in openssl_pkey_get_private). + (Jakub Zelenka) + . Fixed bug #73711 (Segfault in openssl_pkey_new when generating DSA or DH + key). (Jakub Zelenka) + . Fixed bug #74341 (openssl_x509_parse fails to parse ASN.1 UTCTime without + seconds). (Moritz Fain) + . Fixed bug #73808 (iv length warning too restrictive for aes-128-ccm). + (Jakub Zelenka) + +- phar: + . Fixed bug #74383 (phar method parameters reflection correction). + (mhagstrand) + +- Readline: + . Fixed bug #74489 (readline() immediately returns false in interactive + console mode). (Anatol) + +- Standard: + . Fixed bug #72071 (setcookie allows max-age to be negative). (Craig Duncan) + . Fixed bug #74361 (Compaction in array_rand() violates COW). (Nikita) + +- Streams: + . Fixed bug #74429 (Remote socket URI with unique persistence identifier + broken). (Sara) + +13 Apr 2017, PHP 7.1.4 + +- Core: + . Fixed bug #74149 (static embed SAPI linkage error). (krakjoe) + . Fixed bug #73370 (falsely exits with "Out of Memory" when using + USE_ZEND_ALLOC=0). (Nikita) + . Fixed bug #73960 (Leak with instance method calling static method with + referenced return). (Nikita) + . Fixed bug #69676 (Resolution of self::FOO in class constants not correct). + (Nikita) + . Fixed bug #74265 (Build problems after 7.0.17 release: undefined reference + to `isfinite'). (Nikita) + . Fixed bug #74302 (yield fromLABEL is over-greedy). (Sara) + +- Apache: + . Reverted patch for bug #61471, fixes bug #74318. (Anatol) + +- Date: + . Fixed bug #72096 (Swatch time value incorrect for dates before 1970). (mcq8) + +- DOM: + . Fixed bug #74004 (LIBXML_NOWARNING flag ingnored on loadHTML*). + (somedaysummer) + +- iconv: + . Fixed bug #74230 (iconv fails to fail on surrogates). (Anatol) + +- OCI8: + . Fixed uninitialized data causing random crash. (Dmitry) + +- Opcache: + . Fixed bug #74250 (OPcache compilation performance regression in PHP 5.6/7 + with huge classes). (Nikita) + +- OpenSSL: + . Fixed bug #72333 (fwrite() on non-blocking SSL sockets doesn't work). + (Jakub Zelenka) + +- PDO MySQL: + . Fixed bug #71003 (Expose MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT to PDO + interface). (Thomas Orozco) + +- SPL: + . Fixed bug #74058 (ArrayObject can not notice changes). (Andrew Nester) + +- Sqlite: + . Implemented FR #74217 (Allow creation of deterministic sqlite functions). + (Andrew Nester) + +- Streams: + . Fixed bug #74216 (Correctly fail on invalid IP address ports). (Sara) + +- Zlib: + . Fixed bug #74240 (deflate_add can allocate too much memory). (Matt Bonneau) + +16 Mar 2017, PHP 7.1.3 + +- Core: + . Fixed bug #74157 (Segfault with nested generators). (Laruence) + . Fixed bug #74164 (PHP hangs when an invalid value is dynamically passed to + typehinted by-ref arg). (Laruence) + . Fixed bug #74093 (Maximum execution time of n+2 seconds exceed not written + in error_log). (Laruence) + . Fixed bug #73989 (PHP 7.1 Segfaults within Symfony test suite). + (Dmitry, Laruence) + . Fixed bug #74084 (Out of bound read - zend_mm_alloc_small). (Laruence) + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + . Fixed bug #73998 (array_key_exists fails on arrays created by + get_object_vars). (mhagstrand) + . Fixed bug #73954 (NAN check fails on Alpine Linux with musl). (Andrea) + . Fixed bug #73677 (Generating phar.phar core dump with gcc ASAN enabled + build). (ondrej) + +- Apache: + . Fixed bug #61471 (Incomplete POST does not timeout but is passed to PHP). + (Zheng Shao) + +- Date: + . Fixed bug #73837 ("new DateTime()" sometimes returns 1 second ago value). + (Derick) + +- FPM: + . Fixed bug #69860 (php-fpm process accounting is broken with keepalive). + (Denis Yeldandi) + +- Hash: + . Fixed bug #73127 (gost-crypto hash incorrect if input data contains long + 0xFF sequence). (Grundik) + +- GD: + . Fixed bug #74031 (ReflectionFunction for imagepng is missing last two + parameters). (finwe) + +- Mysqlnd: + . Fixed bug #74021 (fetch_array broken data. Data more then MEDIUMBLOB). + (Andrew Nester, Nikita) + +- Opcache: + . Fixed bug #74152 (if statement says true to a null variable). (Laruence) + . Fixed bug #74019 (Segfault with list). (Laruence) + +- OpenSSL: + . Fixed bug #74022 (PHP Fast CGI crashes when reading from a pfx file). + (Anatol) + . Fixed bug #74099 (Memory leak with openssl_encrypt()). (Andrew Nester) + . Fixed bug #74159 (Writing a large buffer to a non-blocking encrypted stream + fails with "bad write retry"). (trowski) + +- PDO_OCI: + . Fixed bug #54379 (PDO_OCI: UTF-8 output gets truncated). (gureedo / Oracle) + +- SQLite3: + . Fixed bug #74413 (incorrect reflection for SQLite3::enableExceptions). + (krakjoe) + +- Standard: + . Fixed bug #74005 (mail.add_x_header causes RFC-breaking lone line feed). + (Anatol) + . Fixed bug #74041 (substr_count with length=0 broken). (Nikita) + . Fixed bug #73118 (is_callable callable name reports misleading value for + anonymous classes). (Adam Saponara) + . Fixed bug #74105 (PHP on Linux should use /dev/urandom when getrandom is + not available). (Benjamin Robin) + . Fixed bug #74708 (Invalid Reflection signatures for random_bytes and + random_int). (Tyson Andre, Remi) + +- Streams: + . Fixed bug #73496 (Invalid memory access in zend_inline_hash_func). + (Laruence) + . Fixed bug #74090 (stream_get_contents maxlength>-1 returns empty string). + (Anatol) + +16 Feb 2017, PHP 7.1.2 + +- Core: + . Improved GENERATOR_CREATE opcode handler. (Bob, Dmitry) + . Fixed bug #73877 (readlink() returns garbage for UTF-8 paths). (Anatol) + . Fixed bug #73876 (Crash when exporting **= in expansion of assign op). + (Sara) + . Fixed bug #73962 (bug with symlink related to cyrillic directory). (Anatol) + . Fixed bug #73969 (segfault in debug_print_backtrace). (andrewnester) + . Fixed bug #73994 (arginfo incorrect for unpack). (krakjoe) + . Fixed bug #73973 (assertion error in debug_zval_dump). (andrewnester) + +- DOM: + . Fixed bug #54382 (getAttributeNodeNS doesn't get xmlns* attributes). + (aboks) + +- DTrace: + . Fixed bug #73965 (DTrace reported as enabled when disabled). (Remi) + +- FCGI: + . Fixed bug #73904 (php-cgi fails to load -c specified php.ini file). (Anatol) + . Fixed bug #72898 (PHP_FCGI_CHILDREN is not included in phpinfo()). (Anatol) + +- FPM: + . Fixed bug #69865 (php-fpm does not close stderr when using syslog). + (m6w6) + +- GD: + . Fixed bug #73968 (Premature failing of XBM reading). (cmb) + +- GMP: + . Fixed bug #69993 (test for gmp.h needs to test machine includes). + (Jordan Gigov) + +- Hash: + . Added hash_hkdf() function. (Andrey Andreev) + . Fixed bug #73961 (environmental build dependency in hash sha3 source). + (krakjoe) + +- Intl: + . Fix bug #73956 (Link use CC instead of CXX). (Remi) + +- LDAP: + . Fixed bug #73933 (error/segfault with ldap_mod_replace and opcache). + (Laruence) + +- MySQLi: + . Fixed bug #73949 (leak in mysqli_fetch_object). (krakjoe) + +- Mysqlnd: + . Fixed bug #69899 (segfault on close() after free_result() with mysqlnd). + (Richard Fussenegger) + +- Opcache: + . Fixed bug #73983 (crash on finish work with phar in cli + opcache). + (Anatol) + +- OpenSSL: + . Fixed bug #71519 (add serial hex to return value array). (xrobau) + . Fixed bug #73692 (Compile ext/openssl with openssl 1.1.0 on Win). (Anatol) + . Fixed bug #73978 (openssl_decrypt triggers bug in PDO). (Jakub Zelenka) + +- PDO_Firebird: + . Implemented FR #72583 (All data are fetched as strings). (Dorin Marcoci) + +- PDO_PgSQL: + . Fixed bug #73959 (lastInsertId fails to throw an exception for wrong + sequence name). (andrewnester) + +- Phar: + . Fixed bug #70417 (PharData::compress() doesn't close temp file). (cmb) + +- posix: + . Fixed bug #71219 (configure script incorrectly checks for ttyname_r). (atoh) + +- Session: + . Fixed bug #69582 (session not readable by root in CLI). (EvgeniySpinov) + +- SPL: + . Fixed bug #73896 (spl_autoload() crashes when calls magic _call()). (Dmitry) + +- Standard: + . Fixed bug #69442 (closing of fd incorrect when PTS enabled). (jaytaph) + . Fixed bug #47021 (SoapClient stumbles over WSDL delivered with + "Transfer-Encoding: chunked"). (Rowan Collins) + . Fixed bug #72974 (imap is undefined service on AIX). (matthieu.sarter) + . Fixed bug #72979 (money_format stores wrong length AIX). (matthieu.sarter) + . Fixed bug #73374 (intval() with base 0 should detect binary). (Leigh) + . Fixed bug #69061 (mail.log = syslog contains double information). + (Tom Sommer) + +- ZIP: + . Fixed bug #70103 (ZipArchive::addGlob ignores remove_all_path option). (cmb, + Mitch Hagstrand) + +19 Jan 2017, PHP 7.1.1 + +- Core: + . Fixed bug #73792 (invalid foreach loop hangs script). (Dmitry) + . Fixed bug #73686 (Adding settype()ed values to ArrayObject results in + references). (Nikita, Laruence) + . Fixed bug #73663 ("Invalid opcode 65/16/8" occurs with a variable created + with list()). (Laruence) + . Fixed bug #73727 (ZEND_MM_BITSET_LEN is "undefined symbol" in + zend_bitset.h). (Nikita) + . Fixed bug #73753 (unserialized array pointer not advancing). (David Walker) + . Fixed bug #73783 (SIG_IGN doesn't work when Zend Signals is enabled). + (David Walker) + +- CLI: + . Fixed bug #72555 (CLI output(japanese) on Windows). (Anatol) + +- COM: + . Fixed bug #73679 (DOTNET read access violation using invalid codepage). + (Anatol) + +- DOM: + . Fixed bug #67474 (getElementsByTagNameNS filter on default ns). (aboks) + +- Mbstring: + . Fixed bug #73646 (mb_ereg_search_init null pointer dereference). + (Laruence) + +- Mysqli: + . Fixed bug #73462 (Persistent connections don't set $connect_errno). + (darkain) + +- Mysqlnd: + . Optimized handling of BIT fields - less memory copies and lower memory + usage. (Andrey) + . Fixed bug #73800 (sporadic segfault with MYSQLI_OPT_INT_AND_FLOAT_NATIVE). + (vanviegen) + +- Opcache: + . Fixed bug #73789 (Strange behavior of class constants in switch/case block). + (Laruence) + . Fixed bug #73746 (Method that returns string returns UNKNOWN:0 instead). + (Laruence) + . Fixed bug #73654 (Segmentation fault in zend_call_function). (Nikita) + . Fixed bug #73668 ("SIGFPE Arithmetic exception" in opcache when divide by + minus 1). (Nikita) + . Fixed bug #73847 (Recursion when a variable is redefined as array). (Nikita) + +- PDO_Firebird: + . Fixed bug #72931 (PDO_FIREBIRD with Firebird 3.0 not work on returning + statement). (Dorin Marcoci) + +- phpdbg: + . Fixed bug #73794 (Crash (out of memory) when using run and # command + separator). (Bob) + . Fixed bug #73704 (phpdbg shows the wrong line in files with shebang). (Bob) + +- SQLite3: + . Reverted fix for bug #73530 (Unsetting result set may reset other result + set). (cmb) + +- Standard: + . Fixed bug #73594 (dns_get_record does not populate $additional out + parameter). (Bruce Weirdan) + . Fixed bug #70213 (Unserialize context shared on double class lookup). + (Taoguang Chen) + . Fixed bug #73154 (serialize object with __sleep function crash). (Nikita) + . Fixed bug #70490 (get_browser function is very slow). (Nikita) + . Fixed bug #73265 (Loading browscap.ini at startup causes high memory usage). + (Nikita) + . Add subject to mail log. (tomsommer) + . Fixed bug #31875 (get_defined_functions additional param to exclude + disabled functions). (willianveiga) + +- Zlib + . Fixed bug #73373 (deflate_add does not verify that output was not truncated). + (Matt Bonneau) + +01 Dec 2016, PHP 7.1.0 + +- Core: + . Added nullable types. (Levi, Dmitry) + . Added DFA optimization framework based on e-SSA form. (Dmitry, Nikita) + . Added specialized opcode handlers (e.g. ZEND_ADD_LONG_NO_OVERFLOW). + (Dmitry) + . Added [] = as alternative construct to list() =. (Bob) + . Added void return type. (Andrea) + . Added support for negative string offsets in string offset syntax and + various string functions. (Francois) + . Added a form of the list() construct where keys can be specified. (Andrea) + . Implemented safe execution timeout handling, that prevents random crashes + after "Maximum execution time exceeded" error. (Dmitry) + . Implemented the RFC `Support Class Constant Visibility`. (Sean DuBois, + Reeze Xia, Dmitry) + . Implemented the RFC `Catching multiple exception types`. (Bronislaw Bialek, + Pierrick) + . Implemented logging to syslog with dynamic error levels. (Jani Ollikainen) + . Implemented FR #72614 (Support "nmake test" on building extensions by + phpize). (Yuji Uchiyama) + . Implemented RFC: Iterable. (Aaron Piotrowski) + . Implemented RFC: Closure::fromCallable (Danack) + . Implemented RFC: Replace "Missing argument" warning with "\ArgumentCountError" + exception. (Dmitry, Davey) + . Implemented RFC: Fix inconsistent behavior of $this variable. (Dmitry) + . Fixed bug #73585 (Logging of "Internal Zend error - Missing class + information" missing class name). (Laruence) + . Fixed memory leak(null coalescing operator with Spl hash). (Tyson Andre) + . Fixed bug #72736 (Slow performance when fetching large dataset with mysqli + / PDO). (Dmitry) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (cmb) + . Fixed bug #72696 (imagefilltoborder stackoverflow on truecolor images). + (cmb) + . Fixed bug #73350 (Exception::__toString() cause circular references). + (Laruence) + . Fixed bug #73329 ((Float)"Nano" == NAN). (Anatol) + . Fixed bug #73288 (Segfault in __clone > Exception.toString > __get). + (Laruence) + . Fixed for #73240 (Write out of bounds at number_format). (Stas) + . Fix pthreads detection when cross-compiling (ffontaine) + . Fixed bug #73337 (try/catch not working with two exceptions inside a same + operation). (Dmitry) + . Fixed bug #73156 (segfault on undefined function). (Dmitry) + . Fixed bug #73163 (PHP hangs if error handler throws while accessing undef + const in default value). (Nikita) + . Fixed bug #73172 (parse error: Invalid numeric literal). (Nikita, Anatol) + . Fixed bug #73181 (parse_str() without a second argument leads to crash). + (Nikita) + . Fixed bug #73025 (Heap Buffer Overflow in virtual_popen of + zend_virtual_cwd.c). (cmb) + . Fixed bug #73058 (crypt broken when salt is 'too' long). (Anatol) + . Fixed bug #72944 (Null pointer deref in zval_delref_p). (Dmitry) + . Fixed bug #72943 (assign_dim on string doesn't reset hval). (Laruence) + . Fixed bug #72598 (Reference is lost after array_slice()) (Nikita) + . Fixed bug #72703 (Out of bounds global memory read in BF_crypt triggered by + password_verify). (Anatol) + . Fixed bug #72813 (Segfault with __get returned by ref). (Laruence) + . Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator). + (Nikita) + . TypeError messages for arg_info type checks will now say "must be ... + or null" where the parameter or return type accepts null. (Andrea) + . Fixed bug #72857 (stream_socket_recvfrom read access violation). (Anatol) + . Fixed bug #72663 (Create an Unexpected Object and Don't Invoke + __wakeup() in Deserialization). (Stas) + . Fixed bug #72681 (PHP Session Data Injection Vulnerability). (Stas) + . Fixed bug #72742 (memory allocator fails to realloc small block to large + one). (Stas) + . Fixed URL rewriter. It would not rewrite '//example.com/' URL + unconditionally. URL rewrite target hosts whitelist is implemented. (Yasuo) + . Fixed bug #72641 (phpize (on Windows) ignores PHP_PREFIX). + (Yuji Uchiyama) + . Fixed bug #72683 (getmxrr broken). (Anatol) + . Fixed bug #72629 (Caught exception assignment to variables ignores + references). (Laruence) + . Fixed bug #72594 (Calling an earlier instance of an included anonymous + class fatals). (Laruence) + . Fixed bug #72581 (previous property undefined in Exception after + deserialization). (Laruence) + . Fixed bug #72543 (Different references behavior comparing to PHP 5) + (Laruence, Dmitry, Nikita) + . Fixed bug #72347 (VERIFY_RETURN type casts visible in finally). (Dmitry) + . Fixed bug #72216 (Return by reference with finally is not memory safe). + (Dmitry) + . Fixed bug #72215 (Wrong return value if var modified in finally). (Dmitry) + . Fixed bug #71818 (Memory leak when array altered in destructor). (Dmitry) + . Fixed bug #71539 (Memory error on $arr[$a] =& $arr[$b] if RHS rehashes) + (Dmitry, Nikita) + . Added new constant PHP_FD_SETSIZE. (cmb) + . Added optind parameter to getopt(). (as) + . Added PHP to SAPI error severity mapping for logs. (Martin Vobruba) + . Fixed bug #71911 (Unable to set --enable-debug on building extensions by + phpize on Windows). (Yuji Uchiyama) + . Fixed bug #29368 (The destructor is called when an exception is thrown from + the constructor). (Dmitry) + . Implemented RFC: RNG Fixes. (Leigh) + . Implemented email validation as per RFC 6531. (Leo Feyer, Anatol) + . Fixed bug #72513 (Stack-based buffer overflow vulnerability in + virtual_file_ex). (Stas) + . Fixed bug #72573 (HTTP_PROXY is improperly trusted by some PHP libraries + and applications). (Stas) + . Fixed bug #72523 (dtrace issue with reflection (failed test)). (Laruence) + . Fixed bug #72508 (strange references after recursive function call and + "switch" statement). (Laruence) + . Fixed bug #72441 (Segmentation fault: RFC list_keys). (Laruence) + . Fixed bug #72395 (list() regression). (Laruence) + . Fixed bug #72373 (TypeError after Generator function w/declared return type + finishes). (Nikita) + . Fixed bug #69489 (tempnam() should raise notice if falling back to temp dir). + (Laruence, Anatol) + . Fixed UTF-8 and long path support on Windows. (Anatol) + . Fixed bug #53432 (Assignment via string index access on an empty string + converts to array). (Nikita) + . Fixed bug #62210 (Exceptions can leak temporary variables). (Dmitry, Bob) + . Fixed bug #62814 (It is possible to stiffen child class members visibility). + (Nikita) + . Fixed bug #69989 (Generators don't participate in cycle GC). (Nikita) + . Fixed bug #70228 (Memleak if return in finally block). (Dmitry) + . Fixed bug #71266 (Missing separation of properties HT in foreach etc). + (Dmitry) + . Fixed bug #71604 (Aborted Generators continue after nested finally). + (Nikita) + . Fixed bug #71572 (String offset assignment from an empty string inserts + null byte). (Francois) + . Fixed bug #71897 (ASCII 0x7F Delete control character permitted in + identifiers). (Andrea) + . Fixed bug #72188 (Nested try/finally blocks losing return value). (Dmitry) + . Fixed bug #72213 (Finally leaks on nested exceptions). (Dmitry, Nikita) + . Fixed bug #47517 (php-cgi.exe missing UAC manifest). + (maxdax15801 at users noreply github com) + . Change statement and fcall extension handlers to accept frame. (Joe) + . Number operators taking numeric strings now emit E_NOTICEs or E_WARNINGs + when given malformed numeric strings. (Andrea) + . (int), intval() where $base is 10 or unspecified, settype(), decbin(), + decoct(), dechex(), integer operators and other conversions now always + respect scientific notation in numeric strings. (Andrea) + . Raise a compile-time warning on octal escape sequence overflow. (Sara) + +- Apache2handler: + . Enable per-module logging in Apache 2.4+. (Martin Vobruba) + +- BCmath: + . Fix bug #73190 (memcpy negative parameter _bc_new_num_ex). (Stas) + +- Bz2: + . Fixed bug #72837 (integer overflow in bzdecompress caused heap + corruption). (Stas) + . Fixed bug #72613 (Inadequate error handling in bzread()). (Stas) + +- Calendar: + . Fix integer overflows (Joshua Rogers) + . Fixed bug #67976 (cal_days_month() fails for final month of the French + calendar). (cmb) + . Fixed bug #71894 (AddressSanitizer: global-buffer-overflow in + zif_cal_from_jd). (cmb) + +- CLI Server: + . Fixed bug #73360 (Unable to work in root with unicode chars). (Anatol) + . Fixed bug #71276 (Built-in webserver does not send Date header). + (see at seos fr) + +- COM: + . Fixed bug #73126 (Cannot pass parameter 1 by reference). (Anatol) + . Fixed bug #69579 (Invalid free in extension trait). (John Boehr) + . Fixed bug #72922 (COM called from PHP does not return out parameters). + (Anatol) + . Fixed bug #72569 (DOTNET/COM array parameters broke in PHP7). (Anatol) + . Fixed bug #72498 (variant_date_from_timestamp null dereference). (Anatol) + +- Curl + . Implement support for handling HTTP/2 Server Push. (Davey) + . Add curl_multi_errno(), curl_share_errno() and curl_share_strerror() + functions. (Pierrick) + . Fixed bug #72674 (Heap overflow in curl_escape). (Stas) + . Fixed bug #72541 (size_t overflow lead to heap corruption). (Stas). + . Fixed bug #71709 (curl_setopt segfault with empty CURLOPT_HTTPHEADER). + (Pierrick) + . Fixed bug #71929 (CURLINFO_CERTINFO data parsing error). (Pierrick) + +- Date: + . Fixed bug #69587 (DateInterval properties and isset). (jhdxr) + . Fixed bug #73426 (createFromFormat with 'z' format char results in + incorrect time). (Derick) + . Fixed bug #45554 (Inconsistent behavior of the u format char). (Derick) + . Fixed bug #48225 (DateTime parser doesn't set microseconds for "now"). + (Derick) + . Fixed bug #52514 (microseconds are missing in DateTime class). (Derick) + . Fixed bug #52519 (microseconds in DateInterval are missing). (Derick) + . Fixed bug #60089 (DateTime::createFromFormat() U after u nukes microtime). + (Derick) + . Fixed bug #64887 (Allow DateTime modification with subsecond items). + (Derick) + . Fixed bug #68506 (General DateTime improvments needed for microseconds to + become useful). (Derick) + . Fixed bug #73109 (timelib_meridian doesn't parse dots correctly). (Derick) + . Fixed bug #73247 (DateTime constructor does not initialise microseconds + property). (Derick) + . Fixed bug #73147 (Use After Free in PHP7 unserialize()). (Stas) + . Fixed bug #73189 (Memcpy negative size parameter php_resolve_path). (Stas) + . Fixed bug #66836 (DateTime::createFromFormat 'U' with pre 1970 dates fails + parsing). (derick) + . Invalid serialization data for a DateTime or DatePeriod object will now + throw an instance of Error from __wakeup() or __set_state() instead of + resulting in a fatal error. (Aaron Piotrowski) + . Timezone initialization failure from serialized data will now throw an + instance of Error from __wakeup() or __set_state() instead of resulting in + a fatal error. (Aaron Piotrowski) + . Export date_get_interface_ce() for extension use. (Jeremy Mikola) + . Fixed bug #63740 (strtotime seems to use both sunday and monday as start of + week). (Derick) + +- Dba: + . Fixed bug #70825 (Cannot fetch multiple values with group in ini file). + (cmb) + . Data modification functions (e.g.: dba_insert()) now throw an instance of + Error instead of triggering a catchable fatal error if the key is does not + contain exactly two elements. (Aaron Piotrowski) + +- DOM: + . Fixed bug #73150 (missing NULL check in dom_document_save_html). (Stas) + . Fixed bug #66502 (DOM document dangling reference). (Sean Heelan, cmb) + . Invalid schema or RelaxNG validation contexts will throw an instance of + Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Attempting to register a node class that does not extend the appropriate + base class will now throw an instance of Error instead of resulting in a + fatal error. (Aaron Piotrowski) + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. (Aaron + Piotrowski) + +- DTrace: + . Disabled PHP call tracing by default (it makes significant overhead). + This may be enabled again using envirionment variable USE_ZEND_DTRACE=1. + (Dmitry) + +- EXIF: + . Fixed bug #72735 (Samsung picture thumb not read (zero size)). (Kalle, Remi) + . Fixed bug #72627 (Memory Leakage In exif_process_IFD_in_TIFF). (Stas) + . Fixed bug #72603 (Out of bound read in exif_process_IFD_in_MAKERNOTE). + (Stas) + . Fixed bug #72618 (NULL Pointer Dereference in exif_process_user_comment). + (Stas) + +- Filter: + . Fixed bug #72972 (Bad filter for the flags FILTER_FLAG_NO_RES_RANGE and + FILTER_FLAG_NO_PRIV_RANGE). (julien) + . Fixed bug #73054 (default option ignored when object passed to int filter). + (cmb) + . Fixed bug #71745 (FILTER_FLAG_NO_RES_RANGE does not cover whole 127.0.0.0/8 + range). (bugs dot php dot net at majkl578 dot cz) + +- FPM: + . Fixed bug #72575 (using --allow-to-run-as-root should ignore missing user). + (gooh) + +- FTP: + . Fixed bug #70195 (Cannot upload file using ftp_put to FTPES with + require_ssl_reuse). (Benedict Singer) + . Implemented FR #55651 (Option to ignore the returned FTP PASV address). + (abrender at elitehosts dot com) + +- GD: + . Fixed bug #73213 (Integer overflow in imageline() with antialiasing). (cmb) + . Fixed bug #73272 (imagescale() is not affected by, but affects + imagesetinterpolation()). (cmb) + . Fixed bug #73279 (Integer overflow in gdImageScaleBilinearPalette()). (cmb) + . Fixed bug #73280 (Stack Buffer Overflow in GD dynamicGetbuf). (cmb) + . Fixed bug #50194 (imagettftext broken on transparent background w/o + alphablending). (cmb) + . Fixed bug #73003 (Integer Overflow in gdImageWebpCtx of gd_webp.c). (trylab, + cmb) + . Fixed bug #53504 (imagettfbbox gives incorrect values for bounding box). + (Mark Plomer, cmb) + . Fixed bug #73157 (imagegd2() ignores 3rd param if 4 are given). (cmb) + . Fixed bug #73155 (imagegd2() writes wrong chunk sizes on boundaries). (cmb) + . Fixed bug #73159 (imagegd2(): unrecognized formats may result in corrupted + files). (cmb) + . Fixed bug #73161 (imagecreatefromgd2() may leak memory). (cmb) + . Fixed bug #67325 (imagetruecolortopalette: white is duplicated in palette). + (cmb) + . Fixed bug #66005 (imagecopy does not support 1bit transparency on truecolor + images). (cmb) + . Fixed bug #72913 (imagecopy() loses single-color transparency on palette + images). (cmb) + . Fixed bug #68716 (possible resource leaks in _php_image_convert()). (cmb) + . Fixed bug #72709 (imagesetstyle() causes OOB read for empty $styles). (cmb) + . Fixed bug #72697 (select_colors write out-of-bounds). (Stas) + . Fixed bug #72730 (imagegammacorrect allows arbitrary write access). (Stas) + . Fixed bug #72596 (imagetypes function won't advertise WEBP support). (cmb) + . Fixed bug #72604 (imagearc() ignores thickness for full arcs). (cmb) + . Fixed bug #70315 (500 Server Error but page is fully rendered). (cmb) + . Fixed bug #43828 (broken transparency of imagearc for truecolor in + blendingmode). (cmb) + . Fixed bug #72512 (gdImageTrueColorToPaletteBody allows arbitrary write/read + access). (Pierre) + . Fixed bug #72519 (imagegif/output out-of-bounds access). (Pierre) + . Fixed bug #72558 (Integer overflow error within _gdContributionsAlloc()). + (Pierre) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (Pierre) + . Fixed bug #72494 (imagecropauto out-of-bounds access). (Fernando, Pierre, + cmb) + . Fixed bug #72404 (imagecreatefromjpeg fails on selfie). (cmb) + . Fixed bug #43475 (Thick styled lines have scrambled patterns). (cmb) + . Fixed bug #53640 (XBM images require width to be multiple of 8). (cmb) + . Fixed bug #64641 (imagefilledpolygon doesn't draw horizontal line). (cmb) + +- Hash: + . Added SHA3 fixed mode algorithms (224, 256, 384, and 512 bit). (Sara) + . Added SHA512/256 and SHA512/224 algorithms. (Sara) + +- iconv: + . Fixed bug #72320 (iconv_substr returns false for empty strings). (cmb) + +- IMAP: + . Fixed bug #73418 (Integer Overflow in "_php_imap_mail" leads to crash). + (Anatol) + . An email address longer than 16385 bytes will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- Interbase: + . Fixed bug #73512 (Fails to find firebird headers as don't use fb_config + output). (Remi) + +- Intl: + . Fixed bug #73007 (add locale length check). (Stas) + . Fixed bug #73218 (add mitigation for ICU int overflow). (Stas) + . Fixed bug #65732 (grapheme_*() is not Unicode compliant on CR LF + sequence). (cmb) + . Fixed bug #73007 (add locale length check). (Stas) + . Fixed bug #72639 (Segfault when instantiating class that extends + IntlCalendar and adds a property). (Laruence) + . Fixed bug #72658 (Locale::lookup() / locale_lookup() hangs if no match + found). (Anatol) + . Partially fixed #72506 (idn_to_ascii for UTS #46 incorrect for long domain + names). (cmb) + . Fixed bug #72533 (locale_accept_from_http out-of-bounds access). (Stas) + . Failure to call the parent constructor in a class extending Collator + before invoking the parent methods will throw an instance of Error + instead of resulting in a recoverable fatal error. (Aaron Piotrowski) + . Cloning a Transliterator object may will now throw an instance of Error + instead of resulting in a fatal error if cloning the internal + transliterator fails. (Aaron Piotrowski) + . Added IntlTimeZone::getWindowsID() and + IntlTimeZone::getIDForWindowsID(). (Sara) + . Fixed bug #69374 (IntlDateFormatter formatObject returns wrong utf8 value). + (lenhatanh86 at gmail com) + . Fixed bug #69398 (IntlDateFormatter formatObject returns wrong value when + time style is NONE). (lenhatanh86 at gmail com) + +- JSON: + . Introduced encoder struct instead of global which fixes bugs #66025 and + #73254 related to pretty print indentation. (Jakub Zelenka) + . Fixed bug #73113 (Segfault with throwing JsonSerializable). (julien) + . Implemented earlier return when json_encode fails, fixes bugs #68992 + (Stacking exceptions thrown by JsonSerializable) and #70275 (On recursion + error, json_encode can eat up all system memory). (Jakub Zelenka) + . Implemented FR #46600 ("_empty_" key in objects). (Jakub Zelenka) + . Exported JSON parser API including json_parser_method that can be used + for implementing custom logic when parsing JSON. (Jakub Zelenka) + . Escaped U+2028 and U+2029 when JSON_UNESCAPED_UNICODE is supplied as + json_encode options and added JSON_UNESCAPED_LINE_TERMINATORS to restore + the previous behaviour. (Eddie Kohler) + +- LDAP: + . Providing an unknown modification type to ldap_batch_modify() will now + throw an instance of Error instead of resulting in a fatal error. + (Aaron Piotrowski) + +- Mbstring: + . Fixed bug #73532 (Null pointer dereference in mb_eregi). (Laruence) + . Fixed bug #66964 (mb_convert_variables() cannot detect recursion) (Yasuo) + . Fixed bug #72992 (mbstring.internal_encoding doesn't inherit default_charset). + (Yasuo) + . Fixed bug #66797 (mb_substr only takes 32-bit signed integer). (cmb) + . Fixed bug #72711 (`mb_ereg` does not clear the `$regs` parameter on + failure). (ju1ius) + . Fixed bug #72691 (mb_ereg_search raises a warning if a match zero-width). + (cmb) + . Fixed bug #72693 (mb_ereg_search increments search position when a match + zero-width). (cmb) + . Fixed bug #72694 (mb_ereg_search_setpos does not accept a string's last + position). (cmb) + . Fixed bug #72710 (`mb_ereg` causes buffer overflow on regexp compile error). + (ju1ius) + . Deprecated mb_ereg_replace() eval option. (Rouven Weßling, cmb) + . Fixed bug #69151 (mb_ereg should reject ill-formed byte sequence). + (Masaki Kagaya) + . Fixed bug #72405 (mb_ereg_replace - mbc_to_code (oniguruma) - + oob read access). (Laruence) + . Fixed bug #72399 (Use-After-Free in MBString (search_re)). (Laruence) + . mb_ereg() and mb_eregi() will now throw an instance of ParseError if an + invalid PHP expression is provided and the 'e' option is used. (Aaron + Piotrowski) + +- Mcrypt: + . Deprecated ext/mcrypt. (Scott Arciszewski, cmb) + . Fixed bug #72782 (Heap Overflow due to integer overflows). (Stas) + . Fixed bug #72551, bug #72552 (In correct casting from size_t to int lead to + heap overflow in mdecrypt_generic). (Stas) + . mcrypt_encrypt() and mcrypt_decrypt() will throw an instance of Error + instead of resulting in a fatal error if mcrypt cannot be initialized. + (Aaron Piotrowski) + +- Mysqli: + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. (Aaron + Piotrowski) + +- Mysqlnd: + . Fixed bug #64526 (Add missing mysqlnd.* parameters to php.ini-*). (cmb) + . Fixed bug #71863 (Segfault when EXPLAIN with "Unknown column" error when + using MariaDB). (Andrey) + . Fixed bug #72701 (mysqli_get_host_info() wrong output). (Anatol) + +- OCI8 + . Fixed bug #71148 (Bind reference overwritten on PHP 7). (Oracle Corp.) + . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) + . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) + +- ODBC: + . Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes). + (Anatol) + +- Opcache: + . Fixed bug #73583 (Segfaults when conditionally declared class and function + have the same name). (Laruence) + . Fixed bug #69090 (check cached files permissions) + . Fixed bug #72982 (Memory leak in zend_accel_blacklist_update_regexp() + function). (Laruence) + . Fixed bug #72949 (Typo in opcache error message). (cmb) + . Fixed bug #72762 (Infinite loop while parsing a file with opcache enabled). + (Nikita) + . Fixed bug #72590 (Opcache restart with kill_all_lockers does not work). + (Keyur) + +- OpenSSL: + . Fixed bug #73478 (openssl_pkey_new() generates wrong pub/priv keys with + Diffie Hellman). (Jakub Zelenka) + . Fixed bug #73276 (crash in openssl_random_pseudo_bytes function). (Stas) + . Fixed bug #73072 (Invalid path SNI_server_certs causes segfault). + (Jakub Zelenka) + . Fixed bug #72360 (ext/openssl build failure with OpenSSL 1.1.0). + (Jakub Zelenka) + . Bumped a minimal version to 1.0.1. (Jakub Zelenka) + . Dropped support for SSL2. (Remi) + . Implemented FR #61204 (Add elliptic curve support for OpenSSL). + (Dominic Luechinger) + . Implemented FR #67304 (Added AEAD support [CCM and GCM modes] to + openssl_encrypt and openssl_decrypt). (Jakub Zelenka) + . Implemented error storing to the global queue and cleaning up the OpenSSL + error queue (resolves bugs #68276 and #69882). (Jakub Zelenka) + +- Pcntl + . Implemented asynchronous signal handling without TICKS. (Dmitry) + . Added pcntl_signal_get_handler() that returns the current signal handler + for a particular signal. Addresses FR #72409. (David Walker) + . Add signinfo to pcntl_signal() handler args (Bishop Bettini, David Walker) + +- PCRE: + . Fixed bug #73483 (Segmentation fault on pcre_replace_callback). (Laruence) + . Fixed bug #73612 (preg_*() may leak memory). (cmb) + . Fixed bug #73392 (A use-after-free in zend allocator management). + (Laruence) + . Fixed bug #73121 (Bundled PCRE doesn't compile because JIT isn't supported + on s390). (Anatol) + . Fixed bug #72688 (preg_match missing group names in matches). (cmb) + . Downgraded to PCRE 8.38. (Anatol) + . Fixed bug #72476 (Memleak in jit_stack). (Laruence) + . Fixed bug #72463 (mail fails with invalid argument). (Anatol) + . Upgraded to PCRE 8.39. (Anatol) + +- PDO: + . Fixed bug #72788 (Invalid memory access when using persistent PDO + connection). (Keyur) + . Fixed bug #72791 (Memory leak in PDO persistent connection handling). (Keyur) + . Fixed bug #60665 (call to empty() on NULL result using PDO::FETCH_LAZY + returns false). (cmb) + +- PDO_DBlib: + . Fixed bug #72414 (Never quote values as raw binary data). (Adam Baratz) + . Allow \PDO::setAttribute() to set query timeouts. (Adam Baratz) + . Handle SQLDECIMAL/SQLNUMERIC types, which are used by later TDS versions. + (Adam Baratz) + . Add common PDO test suite. (Adam Baratz) + . Free error and message strings when cleaning up PDO instances. + (Adam Baratz) + . Fixed bug #67130 (\PDOStatement::nextRowset() should succeed when all rows + in current rowset haven't been fetched). (Peter LeBrun) + . Ignore potentially misleading dberr values. (Chris Kings-Lynne) + . Implemented stringify 'uniqueidentifier' fields. + (Alexander Zhuravlev, Adam Baratz) + +- PDO_Firebird: + . Fixed bug #73087, #61183, #71494 (Memory corruption in bindParam). + (Dorin Marcoci) + . Fixed bug #60052 (Integer returned as a 64bit integer on X86_64). (Mariuz) + +- PDO_pgsql: + . Fixed bug #70313 (PDO statement fails to throw exception). (Matteo) + . Fixed bug #72570 (Segmentation fault when binding parameters on a query + without placeholders). (Matteo) + . Implemented FR #72633 (Postgres PDO lastInsertId() should work without + specifying a sequence). (Pablo Santiago Sánchez, Matteo) + +- Phar: + . Fixed bug #72928 (Out of bound when verify signature of zip phar in + phar_parse_zipfile). (Stas) + . Fixed bug #73035 (Out of bound when verify signature of tar phar in + phar_parse_tarfile). (Stas) + +- phpdbg: + . Added generator command for inspection of currently alive generators. (Bob) + +- Postgres: + . Fixed bug #73498 (Incorrect SQL generated for pg_copy_to()). (Craig Duncan) + . Implemented FR #31021 (pg_last_notice() is needed to get all notice + messages). (Yasuo) + . Implemented FR #48532 (Allow pg_fetch_all() to index numerically). (Yasuo) + +- Readline: + . Fixed bug #72538 (readline_redisplay crashes php). (Laruence) + +- Reflection + . Undo backwards compatiblity break in ReflectionType->__toString() and + deprecate via documentation instead. (Nikita) + . Reverted prepending \ for class names. (Trowski) + . Implemented request #38992 (invoke() and invokeArgs() static method calls + should match). (cmb). + . Add ReflectionNamedType::getName(). This method should be used instead of + ReflectionType::__toString() + . Prepend \ for class names and ? for nullable types returned from + ReflectionType::__toString(). (Trowski) + . Fixed bug #72661 (ReflectionType::__toString crashes with iterable). + (Laruence) + . Fixed bug #72222 (ReflectionClass::export doesn't handle array constants). + (Nikita Nefedov) + . Failure to retrieve a reflection object or retrieve an object property + will now throw an instance of Error instead of resulting in a fatal error. + (Aaron Piotrowski) + . Fix #72209 (ReflectionProperty::getValue() doesn't fail if object doesn't match type). (Joe) + +- Session: + . Fixed bug #73273 (session_unset() empties values from all variables in which + is $_session stored). (Nikita) + . Fixed bug #73100 (session_destroy null dereference in ps_files_path_create). + (cmb) + . Fixed bug #68015 (Session does not report invalid uid for files save handler). + (Yasuo) + . Fixed bug #72940 (SID always return "name=ID", even if session + cookie exist). (Yasuo) + . Implemented session_gc() (Yasuo) + https://wiki.php.net/rfc/session-create-id + . Implemented session_create_id() (Yasuo) + https://wiki.php.net/rfc/session-gc + . Implemented RFC: Session ID without hashing. (Yasuo) + https://wiki.php.net/rfc/session-id-without-hashing + . Fixed bug #72531 (ps_files_cleanup_dir Buffer overflow). (Laruence) + . Custom session handlers that do not return strings for session IDs will + now throw an instance of Error instead of resulting in a fatal error + when a function is called that must generate a session ID. + (Aaron Piotrowski) + . An invalid setting for session.hash_function will throw an instance of + Error instead of resulting in a fatal error when a session ID is created. + (Aaron Piotrowski) + . Fixed bug #72562 (Use After Free in unserialize() with Unexpected Session + Deserialization). (Stas) + . Improved fix for bug #68063 (Empty session IDs do still start sessions). + (Yasuo) + . Fixed bug #71038 (session_start() returns TRUE on failure). + Session save handlers must return 'string' always for successful read. + i.e. Non-existing session read must return empty string. PHP 7.0 is made + not to tolerate buggy return value. (Yasuo) + . Fixed bug #71394 (session_regenerate_id() must close opened session on + errors). (Yasuo) + +- SimpleXML: + . Fixed bug #73293 (NULL pointer dereference in SimpleXMLElement::asXML()). + (Stas) + . Fixed bug #72971 (SimpleXML isset/unset do not respect namespace). (Nikita) + . Fixed bug #72957 (Null coalescing operator doesn't behave as expected with + SimpleXMLElement). (Nikita) + . Fixed bug #72588 (Using global var doesn't work while accessing SimpleXML + element). (Laruence) + . Creating an unnamed or duplicate attribute will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- SNMP: + . Fixed bug #72708 (php_snmp_parse_oid integer overflow in memory + allocation). (djodjo at gmail dot com) + . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and + unserialize()). (Stas) + +- Soap: + . Fixed bug #73538 (SoapClient::__setSoapHeaders doesn't overwrite SOAP + headers). (duncan3dc) + . Fixed bug #73452 (Segfault (Regression for #69152)). (Dmitry) + . Fixed bug #73037 (SoapServer reports Bad Request when gzipped). (Anatol) + . Fixed bug #73237 (Nested object in "any" element overwrites other fields). + (Keith Smiley) + . Fixed bug #69137 (Peer verification fails when using a proxy with SoapClient) + (Keith Smiley) + . Fixed bug #71711 (Soap Server Member variables reference bug). (Nikita) + . Fixed bug #71996 (Using references in arrays doesn't work like expected). + (Nikita) + +- SPL: + . Fixed bug #73423 (Reproducible crash with GDB backtrace). (Laruence) + . Fixed bug #72888 (Segfault on clone on splFileObject). (Laruence) + . Fixed bug #73029 (Missing type check when unserializing SplArray). (Stas) + . Fixed bug #72646 (SplFileObject::getCsvControl does not return the escape + character). (cmb) + . Fixed bug #72684 (AppendIterator segfault with closed generator). (Pierrick) + . Attempting to clone an SplDirectory object will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + . Calling ArrayIterator::append() when iterating over an object will throw an + instance of Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Fixed bug #55701 (GlobIterator throws LogicException). (Valentin VÄ‚LCIU) + +- SQLite3: + . Update to SQLite 3.15.1. (cmb) + . Fixed bug #73530 (Unsetting result set may reset other result set). (cmb) + . Fixed bug #73333 (2147483647 is fetched as string). (cmb) + . Fixed bug #72668 (Spurious warning when exception is thrown in user defined + function). (Laruence) + . Implemented FR #72653 (SQLite should allow opening with empty filename). + (cmb) + . Fixed bug #70628 (Clearing bindings on an SQLite3 statement doesn't work). + (cmb) + . Implemented FR #71159 (Upgraded bundled SQLite lib to 3.9.2). (Laruence) + +- Standard: + . Fixed bug #73297 (HTTP stream wrapper should ignore HTTP 100 Continue). + (rowan dot collins at gmail dot com) + . Fixed bug #73303 (Scope not inherited by eval in assert()). (nikic) + . Fixed bug #73192 (parse_url return wrong hostname). (Nikita) + . Fixed bug #73203 (passing additional_parameters causes mail to fail). (cmb) + . Fixed bug #73203 (passing additional_parameters causes mail to fail). (cmb) + . Fixed bug #72920 (Accessing a private constant using constant() creates + an exception AND warning). (Laruence) + . Fixed bug #65550 (get_browser() incorrectly parses entries with "+" sign). + (cmb) + . Fixed bug #71882 (Negative ftruncate() on php://memory exhausts memory). + (cmb) + . Fixed bug #55451 (substr_compare NULL length interpreted as 0). (Lauri + Kenttä) + . Fixed bug #72278 (getimagesize returning FALSE on valid jpg). (cmb) + . Fixed bug #61967 (unset array item in array_walk_recursive cause + inconsistent array). (Nikita) + . Fixed bug #62607 (array_walk_recursive move internal pointer). (Nikita) + . Fixed bug #69068 (Exchanging array during array_walk -> memory errors). + (Nikita) + . Fixed bug #70713 (Use After Free Vulnerability in array_walk()/ + array_walk_recursive()). (Nikita) + . Fixed bug #72622 (array_walk + array_replace_recursive create references + from nothing). (Laruence) + . Fixed bug #72330 (CSV fields incorrectly split if escape char followed by + UTF chars). (cmb) + . Implemented RFC: More precise float values. (Jakub Zelenka, Yasuo) + . array_multisort now uses zend_sort instead zend_qsort. (Laruence) + . Fixed bug #72505 (readfile() mangles files larger than 2G). (Cschneid) + . assert() will throw a ParseError when evaluating a string given as the first + argument if the PHP code is invalid instead of resulting in a catchable + fatal error. (Aaron Piotrowski) + . Calling forward_static_call() outside of a class scope will now throw an + instance of Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Added is_iterable() function. (Aaron Piotrowski) + . Fixed bug #72306 (Heap overflow through proc_open and $env parameter). + (Laruence) + . Fixed bug #71100 (long2ip() doesn't accept integers in strict mode). + (Laruence) + . Implemented FR #55716 (Add an option to pass a custom stream context to + get_headers()). (Ferenc) + . Additional validation for parse_url() for login/pass components). + (Ilia) (Julien) + . Implemented FR #69359 (Provide a way to fetch the current environment + variables). (Ferenc) + . unpack() function accepts an additional optional argument $offset. (Dmitry) + . Implemented #51879 stream context socket option tcp_nodelay (Joe) + +- Streams: + . Fixed bug #73586 (php_user_filter::$stream is not set to the stream the + filter is working on). (Dmitry) + . Fixed bug #72853 (stream_set_blocking doesn't work). (Laruence) + . Fixed bug #72743 (Out-of-bound read in php_stream_filter_create). + (Loianhtuan) + . Implemented FR #27814 (Multiple small packets send for HTTP request). + (vhuk) + . Fixed bug #72764 (ftps:// opendir wrapper data channel encryption fails + with IIS FTP 7.5, 8.5). (vhuk) + . Fixed bug #72810 (Missing SKIP_ONLINE_TESTS checks). (vhuk) + . Fixed bug #41021 (Problems with the ftps wrapper). (vhuk) + . Fixed bug #54431 (opendir() does not work with ftps:// wrapper). (vhuk) + . Fixed bug #72667 (opendir() with ftp:// attempts to open data stream for + non-existent directories). (vhuk) + . Fixed bug #72771 (ftps:// wrapper is vulnerable to protocol downgrade + attack). (Stas) + . Fixed bug #72534 (stream_socket_get_name crashes). (Anatol) + . Fixed bug #72439 (Stream socket with remote address leads to a segmentation + fault). (Laruence) + +- sysvshm: + . Fixed bug #72858 (shm_attach null dereference). (Anatol) + +- Tidy: + . Implemented support for libtidy 5.0.0 and above. (Michael Orlitzky, Anatol) + . Creating a tidyNode manually will now throw an instance of Error instead of + resulting in a fatal error. (Aaron Piotrowski) + +- Wddx: + . Fixed bug #73331 (NULL Pointer Dereference in WDDX Packet Deserialization + with PDORow). (Stas) + . Fixed bug #72142 (WDDX Packet Injection Vulnerability in + wddx_serialize_value()). (Taoguang Chen) + . Fixed bug #72749 (wddx_deserialize allows illegal memory access) (Stas) + . Fixed bug #72750 (wddx_deserialize null dereference). (Stas) + . Fixed bug #72790 (wddx_deserialize null dereference with invalid xml). + (Stas) + . Fixed bug #72799 (wddx_deserialize null dereference in + php_wddx_pop_element). (Stas) + . Fixed bug #72860 (wddx_deserialize use-after-free). (Stas) + . Fixed bug #73065 (Out-Of-Bounds Read in php_wddx_push_element). (Stas) + . Fixed bug #72564 (boolean always deserialized as "true") (Remi) + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- XML: + . Fixed bug #72135 (malformed XML causes fault) (edgarsandi) + . Fixed bug #72714 (_xml_startElementHandler() segmentation fault). (cmb) + . Fixed bug #72085 (SEGV on unknown address zif_xml_parse). (cmb) + +- XMLRPC: + . Fixed bug #72647 (xmlrpc_encode() unexpected output after referencing + array elements). (Laruence) + . Fixed bug #72606 (heap-buffer-overflow (write) simplestring_addn + simplestring.c). (Stas) + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- Zip: + . Fixed bug #68302 (impossible to compile php with zip support). (cmb) + . Fixed bug #72660 (NULL Pointer dereference in zend_virtual_cwd). + (Laruence) + . Fixed bug #72520 (Stack-based buffer overflow vulnerability in + php_stream_zip_opener). (Stas) + . ZipArchive::addGlob() will throw an instance of Error instead of resulting + in a fatal error if glob support is not available. (Aaron Piotrowski) + diff --git a/UPGRADING b/UPGRADING index 3e2be23323ac8..c9807e793995e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -99,6 +99,13 @@ PHP 7.2 UPGRADE NOTES from PHP 7.1 on 64-bit machines. This change was necessary to resolve a modulo bias bug in the implementation. +- IMAP: + Starting with 7.2.13, rsh/ssh logins are disabled by default. Use + imap.enable_insecure_rsh if you want to enable them. Note that the IMAP + library does not filter mailbox names before passing them to rsh/ssh + command, thus passing untrusted data to this function with rsh/ssh enabled + is insecure. + ======================================== 2. New Features ======================================== diff --git a/ext/imap/php_imap.c b/ext/imap/php_imap.c index f98f4a71dda82..b8df680a35542 100644 --- a/ext/imap/php_imap.c +++ b/ext/imap/php_imap.c @@ -562,6 +562,15 @@ static const zend_module_dep imap_deps[] = { }; /* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() +STD_PHP_INI_BOOLEAN("imap.enable_insecure_rsh", "0", PHP_INI_SYSTEM, OnUpdateBool, enable_rsh, zend_imap_globals, imap_globals) +PHP_INI_END() +/* }}} */ + + /* {{{ imap_module_entry */ zend_module_entry imap_module_entry = { @@ -832,6 +841,8 @@ PHP_MINIT_FUNCTION(imap) { unsigned long sa_all = SA_MESSAGES | SA_RECENT | SA_UNSEEN | SA_UIDNEXT | SA_UIDVALIDITY; + REGISTER_INI_ENTRIES(); + #ifndef PHP_WIN32 mail_link(&unixdriver); /* link in the unix driver */ mail_link(&mhdriver); /* link in the mh driver */ @@ -1049,6 +1060,12 @@ PHP_MINIT_FUNCTION(imap) GC_TEXTS texts */ + if (!IMAPG(enable_rsh)) { + /* disable SSH and RSH, see https://bugs.php.net/bug.php?id=77153 */ + mail_parameters (NIL, SET_RSHTIMEOUT, 0); + mail_parameters (NIL, SET_SSHTIMEOUT, 0); + } + le_imap = zend_register_list_destructors_ex(mail_close_it, NULL, "imap", module_number); return SUCCESS; } diff --git a/ext/imap/php_imap.h b/ext/imap/php_imap.h index c8e85de19b81e..0dcedefd0f4f3 100644 --- a/ext/imap/php_imap.h +++ b/ext/imap/php_imap.h @@ -231,6 +231,7 @@ ZEND_BEGIN_MODULE_GLOBALS(imap) #endif /* php_stream for php_mail_gets() */ php_stream *gets_stream; + zend_bool enable_rsh; ZEND_END_MODULE_GLOBALS(imap) #ifdef ZTS diff --git a/ext/imap/tests/bug77153.phpt b/ext/imap/tests/bug77153.phpt new file mode 100644 index 0000000000000..63590aee1dde4 --- /dev/null +++ b/ext/imap/tests/bug77153.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #77153 (imap_open allows to run arbitrary shell commands via mailbox parameter) +--SKIPIF-- + +--FILE-- + " . __DIR__ . '/__bug'; +$payloadb64 = base64_encode($payload); +$server = "x -oProxyCommand=echo\t$payloadb64|base64\t-d|sh}"; +@imap_open('{'.$server.':143/imap}INBOX', '', ''); +// clean +imap_errors(); +var_dump(file_exists(__DIR__ . '/__bug')); +?> +--EXPECT-- +bool(false) +--CLEAN-- + \ No newline at end of file From ca3e79e13cf42513800a34401d0067b884962150 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:44 +0000 Subject: [PATCH 05/25] commit patch 27315381 --- ext/xmlrpc/libxmlrpc/xml_element.c | 3 + ext/xmlrpc/libxmlrpc/xml_element.c.orig | 764 ++++++++++++++++++++++++ ext/xmlrpc/tests/bug77242.phpt | 10 + 3 files changed, 777 insertions(+) create mode 100644 ext/xmlrpc/libxmlrpc/xml_element.c.orig create mode 100644 ext/xmlrpc/tests/bug77242.phpt diff --git a/ext/xmlrpc/libxmlrpc/xml_element.c b/ext/xmlrpc/libxmlrpc/xml_element.c index 070680d4a7800..86aad6108a3c6 100644 --- a/ext/xmlrpc/libxmlrpc/xml_element.c +++ b/ext/xmlrpc/libxmlrpc/xml_element.c @@ -720,6 +720,9 @@ xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTI long byte_idx = XML_GetCurrentByteIndex(parser); /* int byte_total = XML_GetCurrentByteCount(parser); */ const char * error_str = XML_ErrorString(err_code); + if(byte_idx > len) { + byte_idx = len; + } if(byte_idx >= 0) { snprintf(buf, sizeof(buf), diff --git a/ext/xmlrpc/libxmlrpc/xml_element.c.orig b/ext/xmlrpc/libxmlrpc/xml_element.c.orig new file mode 100644 index 0000000000000..070680d4a7800 --- /dev/null +++ b/ext/xmlrpc/libxmlrpc/xml_element.c.orig @@ -0,0 +1,764 @@ +/* + This file is part of libXMLRPC - a C library for xml-encoded function calls. + + Author: Dan Libby (dan@libby.com) + Epinions.com may be contacted at feedback@epinions-inc.com +*/ + +/* + Copyright 2000 Epinions, Inc. + + Subject to the following 3 conditions, Epinions, Inc. permits you, free + of charge, to (a) use, copy, distribute, modify, perform and display this + software and associated documentation files (the "Software"), and (b) + permit others to whom the Software is furnished to do so as well. + + 1) The above copyright notice and this permission notice shall be included + without modification in all copies or substantial portions of the + Software. + + 2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF + ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY + IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE OR NONINFRINGEMENT. + + 3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, + SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT + OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING + NEGLIGENCE), EVEN IF EPINIONS, INC. IS AWARE OF THE POSSIBILITY OF SUCH + DAMAGES. + +*/ + + +static const char rcsid[] = "#(@) $Id$"; + + + +/****h* ABOUT/xml_element + * NAME + * xml_element + * AUTHOR + * Dan Libby, aka danda (dan@libby.com) + * CREATION DATE + * 06/2000 + * HISTORY + * $Log$ + * Revision 1.9.4.1.2.1 2008/12/09 17:22:12 iliaa + * + * MFH: Fixed bug #46746 (xmlrpc_decode_request outputs non-suppressable error + * when given bad data). + * + * Revision 1.9.4.1 2006/07/30 11:34:02 tony2001 + * MFH: fix compile warnings (#38257) + * + * Revision 1.9 2005/04/22 11:06:53 jorton + * Fixed bug #32797 (invalid C code in xmlrpc extension). + * + * Revision 1.8 2005/03/28 00:07:24 edink + * Reshufle includes to make it compile on windows + * + * Revision 1.7 2005/03/26 03:13:58 sniper + * - Made it possible to build ext/xmlrpc with libxml2 + * + * Revision 1.6 2004/06/01 20:16:06 iliaa + * Fixed bug #28597 (xmlrpc_encode_request() incorrectly encodes chars in + * 200-210 range). + * Patch by: fernando dot nemec at folha dot com dot br + * + * Revision 1.5 2003/12/16 21:00:21 sniper + * Fix some compile warnings (patch by Joe Orton) + * + * Revision 1.4 2002/11/26 23:01:16 fmk + * removing unused variables + * + * Revision 1.3 2002/07/05 04:43:53 danda + * merged in updates from SF project. bring php repository up to date with xmlrpc-epi version 0.51 + * + * Revision 1.9 2002/07/03 20:54:30 danda + * root element should not have a parent. patch from anon SF user + * + * Revision 1.8 2002/05/23 17:46:51 danda + * patch from mukund - fix non utf-8 encoding conversions + * + * Revision 1.7 2002/02/13 20:58:50 danda + * patch to make source more windows friendly, contributed by Jeff Lawson + * + * Revision 1.6 2002/01/08 01:06:55 danda + * enable format for parsers that are very picky. + * + * Revision 1.5 2001/09/29 21:58:05 danda + * adding cvs log to history section + * + * 10/15/2000 -- danda -- adding robodoc documentation + * TODO + * Nicer external API. Get rid of macros. Make opaque types, etc. + * PORTABILITY + * Coded on RedHat Linux 6.2. Builds on Solaris x86. Should build on just + * about anything with minor mods. + * NOTES + * This code incorporates ideas from expat-ensor from http://xml.ensor.org. + * + * It was coded primarily to act as a go-between for expat and xmlrpc. To this + * end, it stores xml elements, their sub-elements, and their attributes in an + * in-memory tree. When expat is done parsing, the tree can be walked, thus + * retrieving the values. The code can also be used to build a tree via API then + * write out the tree to a buffer, thus "serializing" the xml. + * + * It turns out this is useful for other purposes, such as parsing config files. + * YMMV. + * + * Some Features: + * - output option for xml escaping data. Choices include no escaping, entity escaping, + * or CDATA sections. + * - output option for character encoding. Defaults to (none) utf-8. + * - output option for verbosity/readability. ultra-compact, newlines, pretty/level indented. + * + * BUGS + * there must be some. + ******/ + +#include "ext/xml/expat_compat.h" +#include +#include +#include + +#include "xml_element.h" +#include "queue.h" +#include "encodings.h" + +#define my_free(thing) if(thing) {efree(thing); thing = NULL;} + +#define XML_DECL_START "" +#define XML_DECL_END_LEN sizeof(XML_DECL_END) - 1 +#define START_TOKEN_BEGIN "<" +#define START_TOKEN_BEGIN_LEN sizeof(START_TOKEN_BEGIN) - 1 +#define START_TOKEN_END ">" +#define START_TOKEN_END_LEN sizeof(START_TOKEN_END) - 1 +#define EMPTY_START_TOKEN_END "/>" +#define EMPTY_START_TOKEN_END_LEN sizeof(EMPTY_START_TOKEN_END) - 1 +#define END_TOKEN_BEGIN "" +#define END_TOKEN_END_LEN sizeof(END_TOKEN_END) - 1 +#define ATTR_DELIMITER "\"" +#define ATTR_DELIMITER_LEN sizeof(ATTR_DELIMITER) - 1 +#define CDATA_BEGIN "" +#define CDATA_END_LEN sizeof(CDATA_END) - 1 +#define EQUALS "=" +#define EQUALS_LEN sizeof(EQUALS) - 1 +#define WHITESPACE " " +#define WHITESPACE_LEN sizeof(WHITESPACE) - 1 +#define NEWLINE "\n" +#define NEWLINE_LEN sizeof(NEWLINE) - 1 +#define MAX_VAL_BUF 144 +#define SCALAR_STR "SCALAR" +#define SCALAR_STR_LEN sizeof(SCALAR_STR) - 1 +#define VECTOR_STR "VECTOR" +#define VECTOR_STR_LEN sizeof(VECTOR_STR) - 1 +#define RESPONSE_STR "RESPONSE" +#define RESPONSE_STR_LEN sizeof(RESPONSE_STR) - 1 + + +/*----------------------------- +- Begin xml_element Functions - +-----------------------------*/ + +/****f* xml_element/xml_elem_free_non_recurse + * NAME + * xml_elem_free_non_recurse + * SYNOPSIS + * void xml_elem_free_non_recurse(xml_element* root) + * FUNCTION + * free a single xml element. child elements will not be freed. + * INPUTS + * root - the element to free + * RESULT + * void + * NOTES + * SEE ALSO + * xml_elem_free () + * xml_elem_new () + * SOURCE + */ +void xml_elem_free_non_recurse(xml_element* root) { + if(root) { + xml_element_attr* attrs = Q_Head(&root->attrs); + while(attrs) { + my_free(attrs->key); + my_free(attrs->val); + my_free(attrs); + attrs = Q_Next(&root->attrs); + } + + Q_Destroy(&root->children); + Q_Destroy(&root->attrs); + if(root->name) { + efree((char *)root->name); + root->name = NULL; + } + simplestring_free(&root->text); + my_free(root); + } +} +/******/ + +/****f* xml_element/xml_elem_free + * NAME + * xml_elem_free + * SYNOPSIS + * void xml_elem_free(xml_element* root) + * FUNCTION + * free an xml element and all of its child elements + * INPUTS + * root - the root of an xml tree you would like to free + * RESULT + * void + * NOTES + * SEE ALSO + * xml_elem_free_non_recurse () + * xml_elem_new () + * SOURCE + */ +void xml_elem_free(xml_element* root) { + if(root) { + xml_element* kids = Q_Head(&root->children); + while(kids) { + xml_elem_free(kids); + kids = Q_Next(&root->children); + } + xml_elem_free_non_recurse(root); + } +} +/******/ + +/****f* xml_element/xml_elem_new + * NAME + * xml_elem_new + * SYNOPSIS + * xml_element* xml_elem_new() + * FUNCTION + * allocates and initializes a new xml_element + * INPUTS + * none + * RESULT + * xml_element* or NULL. NULL indicates an out-of-memory condition. + * NOTES + * SEE ALSO + * xml_elem_free () + * xml_elem_free_non_recurse () + * SOURCE + */ +xml_element* xml_elem_new() { + xml_element* elem = ecalloc(1, sizeof(xml_element)); + if(elem) { + Q_Init(&elem->children); + Q_Init(&elem->attrs); + simplestring_init(&elem->text); + + /* init empty string in case we don't find any char data */ + simplestring_addn(&elem->text, "", 0); + } + return elem; +} +/******/ + +static int xml_elem_writefunc(int (*fptr)(void *data, const char *text, int size), const char *text, void *data, int len) +{ + return fptr && text ? fptr(data, text, len ? len : strlen(text)) : 0; +} + + + +static int create_xml_escape(char *pString, unsigned char c) +{ + int counter = 0; + + pString[counter++] = '&'; + pString[counter++] = '#'; + if(c >= 100) { + pString[counter++] = c / 100 + '0'; + c = c % 100; + } + pString[counter++] = c / 10 + '0'; + c = c % 10; + + pString[counter++] = c + '0'; + pString[counter++] = ';'; + return counter; +} + +#define non_ascii(c) (c > 127) +#define non_print(c) (!isprint(c)) +#define markup(c) (c == '&' || c == '\"' || c == '>' || c == '<') +#define entity_length(c) ( (c >= 100) ? 3 : ((c >= 10) ? 2 : 1) ) + 3; /* "&#" + c + ";" */ + +/* + * xml_elem_entity_escape + * + * Purpose: + * escape reserved xml chars and non utf-8 chars as xml entities + * Comments: + * The return value may be a new string, or null if no + * conversion was performed. In the latter case, *newlen will + * be 0. + * Flags (to escape) + * xml_elem_no_escaping = 0x000, + * xml_elem_entity_escaping = 0x002, // escape xml special chars as entities + * xml_elem_non_ascii_escaping = 0x008, // escape chars above 127 + * xml_elem_cdata_escaping = 0x010, // wrap in cdata + */ +static char* xml_elem_entity_escape(const char* buf, int old_len, int *newlen, XML_ELEM_ESCAPING flags) { + char *pRetval = 0; + int iNewBufLen=0; + +#define should_escape(c, flag) ( ((flag & xml_elem_markup_escaping) && markup(c)) || \ + ((flag & xml_elem_non_ascii_escaping) && non_ascii(c)) || \ + ((flag & xml_elem_non_print_escaping) && non_print(c)) ) + + if(buf && *buf) { + const unsigned char *bufcopy; + char *NewBuffer; + int ToBeXmlEscaped=0; + int iLength; + bufcopy = buf; + iLength= old_len ? old_len : strlen(buf); + while(*bufcopy) { + if( should_escape(*bufcopy, flags) ) { + /* the length will increase by length of xml escape - the character length */ + iLength += entity_length(*bufcopy); + ToBeXmlEscaped=1; + } + bufcopy++; + } + + if(ToBeXmlEscaped) { + + NewBuffer= emalloc(iLength+1); + if(NewBuffer) { + bufcopy=buf; + while(*bufcopy) { + if(should_escape(*bufcopy, flags)) { + iNewBufLen += create_xml_escape(NewBuffer+iNewBufLen,*bufcopy); + } + else { + NewBuffer[iNewBufLen++]=*bufcopy; + } + bufcopy++; + } + NewBuffer[iNewBufLen] = 0; + pRetval = NewBuffer; + } + } + } + + if(newlen) { + *newlen = iNewBufLen; + } + + return pRetval; +} + + +static void xml_element_serialize(xml_element *el, int (*fptr)(void *data, const char *text, int size), void *data, XML_ELEM_OUTPUT_OPTIONS options, int depth) +{ + int i; + static STRUCT_XML_ELEM_OUTPUT_OPTIONS default_opts = {xml_elem_pretty, xml_elem_markup_escaping | xml_elem_non_print_escaping, XML_DECL_ENCODING_DEFAULT}; + static char whitespace[] = " " + " " + " "; + depth++; + + if(!el) { + /* fprintf(stderr, "Nothing to write\n"); */ + return; + } + if(!options) { + options = &default_opts; + } + + /* print xml declaration if at root level */ + if(depth == 1) { + xml_elem_writefunc(fptr, XML_DECL_START, data, XML_DECL_START_LEN); + xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN); + xml_elem_writefunc(fptr, XML_DECL_VERSION, data, XML_DECL_VERSION_LEN); + if(options->encoding && *options->encoding) { + xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN); + xml_elem_writefunc(fptr, XML_DECL_ENCODING_ATTR, data, XML_DECL_ENCODING_ATTR_LEN); + xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN); + xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN); + xml_elem_writefunc(fptr, options->encoding, data, 0); + xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN); + } + xml_elem_writefunc(fptr, XML_DECL_END, data, XML_DECL_END_LEN); + if(options->verbosity != xml_elem_no_white_space) { + xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN); + } + } + + if(options->verbosity == xml_elem_pretty && depth > 2) { + xml_elem_writefunc(fptr, whitespace, data, depth - 2); + } + /* begin element */ + xml_elem_writefunc(fptr,START_TOKEN_BEGIN, data, START_TOKEN_BEGIN_LEN); + if(el->name) { + xml_elem_writefunc(fptr, el->name, data, 0); + + /* write attrs, if any */ + if(Q_Size(&el->attrs)) { + xml_element_attr* iter = Q_Head(&el->attrs); + while( iter ) { + xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN); + xml_elem_writefunc(fptr, iter->key, data, 0); + xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN); + xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN); + xml_elem_writefunc(fptr, iter->val, data, 0); + xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN); + + iter = Q_Next(&el->attrs); + } + } + } + else { + xml_elem_writefunc(fptr, "None", data, 0); + } + /* if no text and no children, use abbreviated form, eg: */ + if(!el->text.len && !Q_Size(&el->children)) { + xml_elem_writefunc(fptr, EMPTY_START_TOKEN_END, data, EMPTY_START_TOKEN_END_LEN); + } + /* otherwise, print element contents */ + else { + xml_elem_writefunc(fptr, START_TOKEN_END, data, START_TOKEN_END_LEN); + + /* print text, if any */ + if(el->text.len) { + char* escaped_str = el->text.str; + int buflen = el->text.len; + + if(options->escaping && options->escaping != xml_elem_cdata_escaping) { + escaped_str = xml_elem_entity_escape(el->text.str, buflen, &buflen, options->escaping ); + if(!escaped_str) { + escaped_str = el->text.str; + } + } + + if(options->escaping & xml_elem_cdata_escaping) { + xml_elem_writefunc(fptr, CDATA_BEGIN, data, CDATA_BEGIN_LEN); + } + + xml_elem_writefunc(fptr, escaped_str, data, buflen); + + if(escaped_str != el->text.str) { + my_free(escaped_str); + } + + if(options->escaping & xml_elem_cdata_escaping) { + xml_elem_writefunc(fptr, CDATA_END, data, CDATA_END_LEN); + } + } + /* no text, so print child elems */ + else { + xml_element *kids = Q_Head(&el->children); + i = 0; + while( kids ) { + if(i++ == 0) { + if(options->verbosity != xml_elem_no_white_space) { + xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN); + } + } + xml_element_serialize(kids, fptr, data, options, depth); + kids = Q_Next(&el->children); + } + if(i) { + if(options->verbosity == xml_elem_pretty && depth > 2) { + xml_elem_writefunc(fptr, whitespace, data, depth - 2); + } + } + } + + xml_elem_writefunc(fptr, END_TOKEN_BEGIN, data, END_TOKEN_BEGIN_LEN); + xml_elem_writefunc(fptr,el->name ? el->name : "None", data, 0); + xml_elem_writefunc(fptr, END_TOKEN_END, data, END_TOKEN_END_LEN); + } + if(options->verbosity != xml_elem_no_white_space) { + xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN); + } +} + +/* print buf to file */ +static int file_out_fptr(void *f, const char *text, int size) +{ + fputs(text, (FILE *)f); + return 0; +} + +/* print buf to simplestring */ +static int simplestring_out_fptr(void *f, const char *text, int size) +{ + simplestring* buf = (simplestring*)f; + if(buf) { + simplestring_addn(buf, text, size); + } + return 0; +} + +/****f* xml_element/xml_elem_serialize_to_string + * NAME + * xml_elem_serialize_to_string + * SYNOPSIS + * void xml_element_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len) + * FUNCTION + * writes element tree as XML into a newly allocated buffer + * INPUTS + * el - root element of tree + * options - options determining how output is written. see XML_ELEM_OUTPUT_OPTIONS + * buf_len - length of returned buffer, if not null. + * RESULT + * char* or NULL. Must be free'd by caller. + * NOTES + * SEE ALSO + * xml_elem_serialize_to_stream () + * xml_elem_parse_buf () + * SOURCE + */ +char* xml_elem_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len) +{ + simplestring buf; + simplestring_init(&buf); + + xml_element_serialize(el, simplestring_out_fptr, (void *)&buf, options, 0); + + if(buf_len) { + *buf_len = buf.len; + } + + return buf.str; +} +/******/ + +/****f* xml_element/xml_elem_serialize_to_stream + * NAME + * xml_elem_serialize_to_stream + * SYNOPSIS + * void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options) + * FUNCTION + * writes element tree as XML into a stream (typically an opened file) + * INPUTS + * el - root element of tree + * output - stream handle + * options - options determining how output is written. see XML_ELEM_OUTPUT_OPTIONS + * RESULT + * void + * NOTES + * SEE ALSO + * xml_elem_serialize_to_string () + * xml_elem_parse_buf () + * SOURCE + */ +void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options) +{ + xml_element_serialize(el, file_out_fptr, (void *)output, options, 0); +} +/******/ + +/*--------------------------* +* End xml_element Functions * +*--------------------------*/ + + +/*---------------------- +* Begin Expat Handlers * +*---------------------*/ + +typedef struct _xml_elem_data { + xml_element* root; + xml_element* current; + XML_ELEM_INPUT_OPTIONS input_options; + int needs_enc_conversion; +} xml_elem_data; + + +/* expat start of element handler */ +static void _xmlrpc_startElement(void *userData, const char *name, const char **attrs) +{ + xml_element *c; + xml_elem_data* mydata = (xml_elem_data*)userData; + const char** p = attrs; + + if(mydata) { + c = mydata->current; + + mydata->current = xml_elem_new(); + mydata->current->name = (char*)estrdup(name); + mydata->current->parent = c; + + /* init attrs */ + while(p && *p) { + xml_element_attr* attr = emalloc(sizeof(xml_element_attr)); + if(attr) { + attr->key = estrdup(*p); + attr->val = estrdup(*(p+1)); + Q_PushTail(&mydata->current->attrs, attr); + + p += 2; + } + } + } +} + +/* expat end of element handler */ +static void _xmlrpc_endElement(void *userData, const char *name) +{ + xml_elem_data* mydata = (xml_elem_data*)userData; + + if(mydata && mydata->current && mydata->current->parent) { + Q_PushTail(&mydata->current->parent->children, mydata->current); + + mydata->current = mydata->current->parent; + } +} + +/* expat char data handler */ +static void _xmlrpc_charHandler(void *userData, + const char *s, + int len) +{ + xml_elem_data* mydata = (xml_elem_data*)userData; + if(mydata && mydata->current) { + + /* Check if we need to decode utf-8 parser output to another encoding */ + if(mydata->needs_enc_conversion && mydata->input_options->encoding) { + int new_len = 0; + char* add_text = utf8_decode(s, len, &new_len, mydata->input_options->encoding); + if(add_text) { + len = new_len; + simplestring_addn(&mydata->current->text, add_text, len); + efree(add_text); + return; + } + } + simplestring_addn(&mydata->current->text, s, len); + } +} +/******/ + +/*-------------------* +* End Expat Handlers * +*-------------------*/ + +/*-------------------* +* xml_elem_parse_buf * +*-------------------*/ + +/****f* xml_element/xml_elem_parse_buf + * NAME + * xml_elem_parse_buf + * SYNOPSIS + * xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error) + * FUNCTION + * parse a buffer containing XML into an xml_element in-memory tree + * INPUTS + * in_buf - buffer containing XML document + * len - length of buffer + * options - input options. optional + * error - error result data. optional. check if result is null. + * RESULT + * void + * NOTES + * The returned data must be free'd by caller + * SEE ALSO + * xml_elem_serialize_to_string () + * xml_elem_free () + * SOURCE + */ +xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error) +{ + xml_element* xReturn = NULL; + char buf[100] = ""; + static STRUCT_XML_ELEM_INPUT_OPTIONS default_opts = {encoding_utf_8}; + + if(!options) { + options = &default_opts; + } + + if(in_buf) { + XML_Parser parser; + xml_elem_data mydata = {0}; + + parser = XML_ParserCreate(NULL); + + mydata.root = xml_elem_new(); + mydata.current = mydata.root; + mydata.input_options = options; + mydata.needs_enc_conversion = options->encoding && strcmp(options->encoding, encoding_utf_8); + + XML_SetElementHandler(parser, (XML_StartElementHandler)_xmlrpc_startElement, (XML_EndElementHandler)_xmlrpc_endElement); + XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler)_xmlrpc_charHandler); + + /* pass the xml_elem_data struct along */ + XML_SetUserData(parser, (void*)&mydata); + + if(!len) { + len = strlen(in_buf); + } + + /* parse the XML */ + if(XML_Parse(parser, in_buf, len, 1) == 0) { + enum XML_Error err_code = XML_GetErrorCode(parser); + int line_num = XML_GetCurrentLineNumber(parser); + int col_num = XML_GetCurrentColumnNumber(parser); + long byte_idx = XML_GetCurrentByteIndex(parser); +/* int byte_total = XML_GetCurrentByteCount(parser); */ + const char * error_str = XML_ErrorString(err_code); + if(byte_idx >= 0) { + snprintf(buf, + sizeof(buf), + "\n\tdata beginning %ld before byte index: %s\n", + byte_idx > 10 ? 10 : byte_idx, + in_buf + (byte_idx > 10 ? byte_idx - 10 : byte_idx)); + } +/* + fprintf(stderr, "expat reports error code %i\n" + "\tdescription: %s\n" + "\tline: %i\n" + "\tcolumn: %i\n" + "\tbyte index: %ld\n" + "\ttotal bytes: %i\n%s ", + err_code, error_str, line_num, + col_num, byte_idx, byte_total, buf); +*/ + + /* error condition */ + if(error) { + error->parser_code = (long)err_code; + error->line = line_num; + error->column = col_num; + error->byte_index = byte_idx; + error->parser_error = error_str; + } + } + else { + xReturn = (xml_element*)Q_Head(&mydata.root->children); + xReturn->parent = NULL; + } + + XML_ParserFree(parser); + + + xml_elem_free_non_recurse(mydata.root); + } + + return xReturn; +} + +/******/ diff --git a/ext/xmlrpc/tests/bug77242.phpt b/ext/xmlrpc/tests/bug77242.phpt new file mode 100644 index 0000000000000..542c06311f745 --- /dev/null +++ b/ext/xmlrpc/tests/bug77242.phpt @@ -0,0 +1,10 @@ +--TEST-- +Bug #77242 (heap out of bounds read in xmlrpc_decode()) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +NULL \ No newline at end of file From 6b587e6072ec278ab0b6ac03da51e9d1ea294101 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:46 +0000 Subject: [PATCH 06/25] commit patch 19685338 --- NEWS | 4 + NEWS.orig | 4 + ext/phar/phar.c | 2 +- ext/phar/phar.c.orig | 3623 ++++++++++++++++++++++++++++++++++ ext/phar/tests/bug77247.phpt | 14 + 5 files changed, 3646 insertions(+), 1 deletion(-) create mode 100644 ext/phar/phar.c.orig create mode 100644 ext/phar/tests/bug77247.phpt diff --git a/NEWS b/NEWS index 80e3074f7f28e..62bdef871c2a0 100644 --- a/NEWS +++ b/NEWS @@ -1749,6 +1749,10 @@ PHP NEWS . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and unserialize()). (Stas) +- Phar: + . Fixed bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext). + (Stas) + - Soap: . Fixed bug #73538 (SoapClient::__setSoapHeaders doesn't overwrite SOAP headers). (duncan3dc) diff --git a/NEWS.orig b/NEWS.orig index 60c259a6e6df3..80e3074f7f28e 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -1572,6 +1572,10 @@ PHP NEWS . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + - ODBC: . Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes). (Anatol) diff --git a/ext/phar/phar.c b/ext/phar/phar.c index 65ebce0f0856f..062838425c0eb 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -1999,7 +1999,7 @@ int phar_detect_phar_fname_ext(const char *filename, int filename_len, const cha } while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) { - pos = memchr(pos + 1, '.', filename_len - (pos - filename) + 1); + pos = memchr(pos + 1, '.', filename_len - (pos - filename) - 1); if (!pos) { return FAILURE; } diff --git a/ext/phar/phar.c.orig b/ext/phar/phar.c.orig new file mode 100644 index 0000000000000..11492aa8e7b4b --- /dev/null +++ b/ext/phar/phar.c.orig @@ -0,0 +1,3623 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gregory Beaver | + | Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id: 65ebce0f0856fc5a90a62d32dd0bb5c00627706f $ */ + +#define PHAR_MAIN 1 +#include "phar_internal.h" +#include "SAPI.h" +#include "func_interceptors.h" + +static void destroy_phar_data(zval *zv); + +ZEND_DECLARE_MODULE_GLOBALS(phar) +zend_string *(*phar_save_resolve_path)(const char *filename, int filename_len); + +/** + * set's phar->is_writeable based on the current INI value + */ +static int phar_set_writeable_bit(zval *zv, void *argument) /* {{{ */ +{ + zend_bool keep = *(zend_bool *)argument; + phar_archive_data *phar = (phar_archive_data *)Z_PTR_P(zv); + + 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 (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + old = PHAR_G(readonly_orig); + } else { + old = PHAR_G(require_hash_orig); + } + + if (ZSTR_LEN(new_value) == 2 && !strcasecmp("on", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else if (ZSTR_LEN(new_value) == 3 && !strcasecmp("yes", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else if (ZSTR_LEN(new_value) == 4 && !strcasecmp("true", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else { + ini = (zend_bool) atoi(ZSTR_VAL(new_value)); + } + + /* do not allow unsetting in runtime */ + if (stage == ZEND_INI_STAGE_STARTUP) { + if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + PHAR_G(readonly_orig) = ini; + } else { + PHAR_G(require_hash_orig) = ini; + } + } else if (old && !ini) { + return FAILURE; + } + + if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + PHAR_G(readonly) = ini; + if (PHAR_G(request_init) && PHAR_G(phar_fname_map.u.flags)) { + zend_hash_apply_with_argument(&(PHAR_G(phar_fname_map)), phar_set_writeable_bit, (void *)&ini); + } + } 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(void) /* {{{ */ +{ + char *tmp; + char *key, *lasts, *end; + char ds[2]; + phar_archive_data *phar; + uint32_t i = 0; + + if (!PHAR_G(cache_list) || !(PHAR_G(cache_list)[0])) { + return; + } + + ds[0] = DEFAULT_DIR_SEPARATOR; + ds[1] = '\0'; + tmp = estrdup(PHAR_G(cache_list)); + + /* fake request startup */ + PHAR_G(request_init) = 1; + zend_hash_init(&EG(regular_list), 0, NULL, NULL, 0); + EG(regular_list).nNextFreeElement=1; /* we don't want resource id 0 */ + + PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1); + PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1); + /* 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_G(phar_fname_map)), sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1); + zend_hash_init(&(PHAR_G(phar_alias_map)), sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1); + PHAR_G(manifest_cached) = 1; + PHAR_G(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)) { +finish_up: + phar->phar_pos = i++; + php_stream_close(phar->fp); + phar->fp = NULL; + } else { +finish_error: + PHAR_G(persist) = 0; + PHAR_G(manifest_cached) = 0; + efree(tmp); + zend_hash_destroy(&(PHAR_G(phar_fname_map))); + PHAR_G(phar_fname_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_alias_map))); + PHAR_G(phar_alias_map.u.flags) = 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_G(request_init) = 0; + return; + } + } else { + if (SUCCESS == phar_open_from_filename(key, strlen(key), NULL, 0, 0, &phar, NULL)) { + goto finish_up; + } else { + goto finish_error; + } + } + } + + PHAR_G(persist) = 0; + PHAR_G(request_init) = 0; + /* destroy dummy values from before */ + zend_hash_destroy(&cached_phars); + zend_hash_destroy(&cached_alias); + cached_phars = PHAR_G(phar_fname_map); + cached_alias = PHAR_G(phar_alias_map); + PHAR_G(phar_fname_map.u.flags) = 0; + PHAR_G(phar_alias_map.u.flags) = 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) = ZSTR_VAL(new_value); + + if (stage == ZEND_INI_STAGE_STARTUP) { + phar_split_cache_list(); + } + + 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) /* {{{ */ +{ + 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.u.flags) { + zend_hash_destroy(&phar->manifest); + phar->manifest.u.flags = 0; + } + + if (phar->mounted_dirs.u.flags) { + zend_hash_destroy(&phar->mounted_dirs); + phar->mounted_dirs.u.flags = 0; + } + + if (phar->virtual_dirs.u.flags) { + zend_hash_destroy(&phar->virtual_dirs); + phar->virtual_dirs.u.flags = 0; + } + + if (Z_TYPE(phar->metadata) != IS_UNDEF) { + if (phar->is_persistent) { + if (phar->metadata_len) { + /* for zip comments that are strings */ + free(Z_PTR(phar->metadata)); + } else { + zval_internal_ptr_dtor(&phar->metadata); + } + } else { + zval_ptr_dtor(&phar->metadata); + } + phar->metadata_len = 0; + ZVAL_UNDEF(&phar->metadata); + } + + 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) /* {{{ */ +{ + if (phar->is_persistent) { + return 0; + } + + if (--phar->refcount < 0) { + if (PHAR_G(request_done) + || zend_hash_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) { + phar_destroy_phar_data(phar); + } + 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) || !phar->alias)) { + /* 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. + We're also closing compressed files to save resources, + but only if the archive isn't aliased. */ + 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_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) { + phar_destroy_phar_data(phar); + } + return 1; + } + } + return 0; +} +/* }}}*/ + +/** + * Destroy phar's in shutdown, here we don't care about aliases + */ +static void destroy_phar_data_only(zval *zv) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *) Z_PTR_P(zv); + + if (EG(exception) || --phar_data->refcount < 0) { + phar_destroy_phar_data(phar_data); + } +} +/* }}}*/ + +/** + * Delete aliases to phar's that got kicked out of the global table + */ +static int phar_unalias_apply(zval *zv, void *argument) /* {{{ */ +{ + return Z_PTR_P(zv) == 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(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *) Z_PTR_P(zv); + + 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(zval *zv) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *)Z_PTR_P(zv); + + if (PHAR_G(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); + destroy_phar_data_only(zv); + return; + } + + zend_hash_apply_with_argument(&(PHAR_G(phar_alias_map)), phar_unalias_apply, phar_data); + + if (--phar_data->refcount < 0) { + phar_destroy_phar_data(phar_data); + } +} +/* }}}*/ + +/** + * destructor for the manifest hash, frees each file's entry + */ +void destroy_phar_manifest_entry_int(phar_entry_info *entry) /* {{{ */ +{ + + if (entry->cfp) { + php_stream_close(entry->cfp); + entry->cfp = 0; + } + + if (entry->fp) { + php_stream_close(entry->fp); + entry->fp = 0; + } + + if (Z_TYPE(entry->metadata) != IS_UNDEF) { + if (entry->is_persistent) { + if (entry->metadata_len) { + /* for zip comments that are strings */ + free(Z_PTR(entry->metadata)); + } else { + zval_internal_ptr_dtor(&entry->metadata); + } + } else { + zval_ptr_dtor(&entry->metadata); + } + entry->metadata_len = 0; + ZVAL_UNDEF(&entry->metadata); + } + + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + entry->metadata_str.s = NULL; + } + + 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; + } +} +/* }}} */ + +void destroy_phar_manifest_entry(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = Z_PTR_P(zv); + destroy_phar_manifest_entry_int(entry); + pefree(entry, entry->is_persistent); +} +/* }}} */ + +int phar_entry_delref(phar_entry_data *idata) /* {{{ */ +{ + 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_int(idata->internal_file); + efree(idata->internal_file); + } + } + + phar_archive_delref(idata->phar); + 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) /* {{{ */ +{ + 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_str_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); + } + + if (!phar->donotflush) { + phar_flush(phar, 0, 0, 0, error); + } +} +/* }}} */ + +#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);\ + }\ + 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 = *(uint16_t*)(buffer); \ + buffer += 2 +#endif +#define PHAR_ZIP_16(var) ((uint16_t)((((uint16_t)var[0]) & 0xff) | \ + (((uint16_t)var[1]) & 0xff) << 8)) +#define PHAR_ZIP_32(var) ((uint32_t)((((uint32_t)var[0]) & 0xff) | \ + (((uint32_t)var[1]) & 0xff) << 8 | \ + (((uint32_t)var[2]) & 0xff) << 16 | \ + (((uint32_t)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) /* {{{ */ +{ + 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) + && ((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) + && ((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) && NULL == (stub = zend_hash_str_find_ptr(&(phar->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + 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, uint32_t zip_metadata_len) /* {{{ */ +{ + php_unserialize_data_t var_hash; + + if (zip_metadata_len) { + const unsigned char *p; + unsigned char *p_buff = (unsigned char *)estrndup(*buffer, zip_metadata_len); + p = p_buff; + ZVAL_NULL(metadata); + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) { + efree(p_buff); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + zval_ptr_dtor(metadata); + ZVAL_UNDEF(metadata); + return FAILURE; + } + efree(p_buff); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + if (PHAR_G(persist)) { + /* lazy init metadata */ + zval_ptr_dtor(metadata); + Z_PTR_P(metadata) = pemalloc(zip_metadata_len, 1); + memcpy(Z_PTR_P(metadata), *buffer, zip_metadata_len); + return SUCCESS; + } + } else { + ZVAL_UNDEF(metadata); + } + + 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, zend_long halt_offset, phar_archive_data** pphar, uint32_t compression, char **error) /* {{{ */ +{ + char b32[4], *buffer, *endbuffer, *savebuf; + phar_archive_data *mydata = NULL; + phar_entry_info entry; + uint32_t manifest_len, manifest_count, manifest_flags, manifest_index, tmp_len, sig_flags; + uint16_t manifest_ver; + uint32_t len; + zend_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; + zend_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: { + uint32_t signature_len; + char *sig; + zend_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)) { + 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)) { + 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)) { + 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)) { + 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)) { + 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))) + { + 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, tmp_len, 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 */ + PHAR_GET_32(buffer, len); + if (mydata->is_persistent) { + mydata->metadata_len = len; + if(!len) { + /* FIXME: not sure why this is needed but removing it breaks tests */ + PHAR_GET_32(buffer, len); + } + } + if(len > (size_t)(endbuffer - buffer)) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (trying to read past buffer end)"); + } + if (phar_parse_metadata(&buffer, &mydata->metadata, len) == FAILURE) { + MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\""); + } + buffer += len; + + /* 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 + 28 > 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 (entry.filename_len > (size_t)(endbuffer - buffer - 24)) { + 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); + 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; + } + + PHAR_GET_32(buffer, len); + if (entry.is_persistent) { + entry.metadata_len = len; + } else { + entry.metadata_len = 0; + } + if (len > (size_t)(endbuffer - buffer)) { + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)"); + } + if (phar_parse_metadata(&buffer, &entry.metadata, len) == FAILURE) { + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\""); + } + buffer += len; + + 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 (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(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 (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(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 (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(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); + zend_hash_str_add_mem(&mydata->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info)); + } + + 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(); + + 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 (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) { + signature = NULL; + fp = NULL; + MAPPHAR_FAIL("Cannot open archive \"%s\", alias is already in use by existing archive"); + } + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata); + } else { + mydata->is_temporary_alias = 1; + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata); + 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) /* {{{ */ +{ + 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) == 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)) { + 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) == 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 (NULL == (stub = zend_hash_str_find_ptr(&((*test)->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + 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); + } + + 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); + } + + return phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, pphar, error); +} +/* }}} */ + +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) /* {{{ */ +{ + phar_archive_data *mydata; + php_stream *fp; + zend_string *actual = NULL; + char *p; + + if (!pphar) { + pphar = &mydata; + } + if (php_check_open_basedir(fname)) { + 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 = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + if (fp) { + if (phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error) == SUCCESS) { + if ((*pphar)->is_data || !PHAR_G(readonly)) { + (*pphar)->is_writeable = 1; + } + if (actual) { + zend_string_release(actual); + } + return SUCCESS; + } else { + /* file exists, but is either corrupt or not a phar archive */ + if (actual) { + zend_string_release(actual); + } + return FAILURE; + } + } + + if (actual) { + zend_string_release(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); + 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(); + zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata); + + 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 && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) { + 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_str_del(&(PHAR_G(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 (NULL == zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata)) { + if (options & REPORT_ERRORS) { + if (error) { + spprintf(error, 0, "archive \"%s\" cannot be associated with alias \"%s\", already in use", fname, alias); + } + } + + zend_hash_str_del(&(PHAR_G(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) /* {{{ */ +{ + php_stream *fp; + zend_string *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) == SUCCESS) { + return SUCCESS; + } else if (error && *error) { + return FAILURE; + } + if (php_check_open_basedir(fname)) { + 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) { + zend_string_release(actual); + } + return FAILURE; + } + + if (actual) { + fname = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error); + + if (actual) { + zend_string_release(actual); + } + + return ret; +} +/* }}}*/ + +static inline char *phar_strnstr(const char *buf, int buf_len, const char *search, int search_len) /* {{{ */ +{ + const char *c; + ptrdiff_t 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) /* {{{ */ +{ + 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 zend_long readsize = sizeof(buffer) - sizeof(token); + const zend_long tokenlen = sizeof(token) - 1; + zend_long halt_offset; + size_t got; + uint32_t 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_ex(&filterparams, "window", sizeof("window") - 1, 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)); + + if (!filter) { + err = 1; + add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS); + filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp)); + 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 != php_stream_copy_to_stream_ex(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); + 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)); + + 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 != php_stream_copy_to_stream_ex(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); + 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); + } + + 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); + } + } + } + + 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); + } + + 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) /* {{{ */ +{ + php_stream_statbuf ssb; + char *realpath; + char *filename = estrndup(fname, (ext - fname) + ext_len); + + if ((realpath = expand_filepath(filename, NULL))) { +#ifdef PHP_WIN32 + phar_unixify_path_separators(realpath, strlen(realpath)); +#endif + if (zend_hash_str_exists(&(PHAR_G(phar_fname_map)), realpath, strlen(realpath))) { + efree(realpath); + efree(filename); + return SUCCESS; + } + + if (PHAR_G(manifest_cached) && zend_hash_str_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))) { + efree(filename); + return FAILURE; + } +#ifdef PHP_WIN32 + phar_unixify_path_separators(realpath, strlen(realpath)); +#endif + slash = strstr(realpath, filename); + if (slash) { + slash += ((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) /* {{{ */ +{ + 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); + } 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); + } + } else { + if (*(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') { + return phar_analyze_path(fname, ext_str, ext_len, for_create); + } + } + + 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) /* {{{ */ +{ + const char *pos, *slash; + + *ext_str = NULL; + *ext_len = 0; + + if (!filename_len || filename_len == 1) { + return FAILURE; + } + + phar_request_initialize(); + /* 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_str_exists(&(PHAR_G(phar_alias_map)), (char *) filename, pos - filename)) { + *ext_str = pos; + *ext_len = -1; + return FAILURE; + } + + if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_alias, (char *) filename, pos - filename)) { + *ext_str = pos; + *ext_len = -1; + return FAILURE; + } + } + + if (zend_hash_num_elements(&(PHAR_G(phar_fname_map))) || PHAR_G(manifest_cached)) { + phar_archive_data *pphar; + + if (is_complete) { + if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), (char *) filename, filename_len))) { + *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) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, (char *) filename, filename_len))) { + *ext_str = filename + (filename_len - pphar->ext_len); + goto woohoo; + } + } else { + zend_string *str_key; + zend_ulong unused; + + for (zend_hash_internal_pointer_reset(&(PHAR_G(phar_fname_map))); + HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&(PHAR_G(phar_fname_map)), &str_key, &unused); + zend_hash_move_forward(&(PHAR_G(phar_fname_map))) + ) { + if (ZSTR_LEN(str_key) > (uint32_t) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint32_t)filename_len == ZSTR_LEN(str_key) + || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) { + if (NULL == (pphar = zend_hash_get_current_data_ptr(&(PHAR_G(phar_fname_map))))) { + break; + } + *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len); + goto woohoo; + } + } + + if (PHAR_G(manifest_cached)) { + for (zend_hash_internal_pointer_reset(&cached_phars); + HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&cached_phars, &str_key, &unused); + zend_hash_move_forward(&cached_phars) + ) { + if (ZSTR_LEN(str_key) > (uint32_t) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint32_t)filename_len == ZSTR_LEN(str_key) + || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) { + if (NULL == (pphar = zend_hash_get_current_data_ptr(&cached_phars))) { + break; + } + *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len); + goto woohoo; + } + } + } + } + } + + 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)) { + 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)) { + 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) == '/') + +/** + * Remove .. and . references within a phar filename + */ +char *phar_fix_filepath(char *path, int *new_len, int use_cwd) /* {{{ */ +{ + char *newpath; + 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); + newpath = emalloc(strlen(path) + newpath_len + 1); + memcpy(newpath, PHAR_G(cwd), newpath_len); + } else { + newpath = emalloc(strlen(path) + 2); + 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; + efree(newpath); + return estrndup("/", 1); + } + break; + case 2: + if (tok[0] == '.' && tok[1] == '.') { + efree(path); + *new_len = 1; + efree(newpath); + return estrndup("/", 1); + } + } + efree(newpath); + 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; + newpath[newpath_len] = '\0'; + return erealloc(newpath, newpath_len + 1); +} +/* }}} */ + +/** + * 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(const char *filename, int filename_len, char **arch, int *arch_len, char **entry, int *entry_len, int executable, int for_create) /* {{{ */ +{ + const char *ext_str; +#ifdef PHP_WIN32 + char *save; +#endif + int ext_len; + + if (CHECK_NULL_PATH(filename, filename_len)) { + return FAILURE; + } + + if (CHECK_NULL_PATH(filename, filename_len)) { + return FAILURE; + } + + if (!strncasecmp(filename, "phar://", 7)) { + filename += 7; + filename_len -= 7; + } + + ext_len = 0; +#ifdef PHP_WIN32 + 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) == FAILURE) { + if (ext_len != -1) { + if (!ext_str) { + /* no / detected, restore arch for error message */ +#ifdef PHP_WIN32 + *arch = save; +#else + *arch = (char*)filename; +#endif + } + +#ifdef PHP_WIN32 + efree(filename); +#endif + 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); + } else { + *entry_len = 1; + *entry = estrndup("/", 1); + } + +#ifdef PHP_WIN32 + efree(filename); +#endif + + 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) /* {{{ */ +{ + char *fname; + php_stream *fp; + int fname_len; + zend_string *actual = NULL; + int ret; + + if (error) { + *error = NULL; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = strlen(fname); + + if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, 0, REPORT_ERRORS, NULL, 0) == SUCCESS) { + return SUCCESS; + } + + if (!strcmp(fname, "[no active file]")) { + if (error) { + spprintf(error, 0, "cannot initialize a phar outside of PHP execution"); + } + return FAILURE; + } + + if (0 == zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) { + if (error) { + spprintf(error, 0, "__HALT_COMPILER(); must be declared in a phar"); + } + return FAILURE; + } + + if (php_check_open_basedir(fname)) { + 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) { + zend_string_release(actual); + } + return FAILURE; + } + + if (actual) { + fname = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, REPORT_ERRORS, NULL, 0, error); + + if (actual) { + zend_string_release(actual); + } + + return ret; +} +/* }}} */ + +/** + * Validate the CRC32 of a file opened from within the phar + */ +int phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip) /* {{{ */ +{ + uint32_t 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)) { + 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), entry->header_offset, SEEK_SET); + + if (sizeof(local) != php_stream_read(phar_get_entrypfp(idata->internal_file), (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), + 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), + (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(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + + if (entry->fp_refcount <= 0 && entry->is_deleted) { + return ZEND_HASH_APPLY_REMOVE; + } else { + return ZEND_HASH_APPLY_KEEP; + } +} +/* }}} */ + +#include "stub.h" + +zend_string *phar_create_default_stub(const char *index_php, const char *web_index, char **error) /* {{{ */ +{ + int index_len, web_len; + + 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; + } + } + + return phar_get_stub(index_php, web_index, index_len+1, web_len+1); +} +/* }}} */ + +/** + * 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, zend_long len, int convert, char **error) /* {{{ */ +{ + char halt_stub[] = "__HALT_COMPILER();"; + zend_string *newstub; + char *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]; + zend_off_t manifest_ftell; + zend_long offset; + size_t wrote; + uint32_t manifest_len, mytime, loc, new_manifest_count; + uint32_t 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; + int manifest_hack = 0; + + 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); + } + + if (phar->is_tar) { + return phar_tar_flush(phar, user_stub, len, convert, error); + } + + 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) { + zend_string *suser_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 (!(suser_stub = php_stream_copy_to_mem(stubfile, len, 0))) { + 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; + user_stub = ZSTR_VAL(suser_stub); + len = ZSTR_LEN(suser_stub); + } 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) { + zend_string_free(suser_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) { + zend_string_free(suser_stub); + } + return EOF; + } + phar->halt_offset = len + 5; + if (free_user_stub) { + zend_string_free(suser_stub); + } + } else { + size_t written; + + if (!user_stub && phar->halt_offset && oldfile && !phar->is_brandnew) { + php_stream_copy_to_stream_ex(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, NULL); + phar->halt_offset = ZSTR_LEN(newstub); + written = php_stream_write(newfile, ZSTR_VAL(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) { + zend_string_free(newstub); + } + return EOF; + } + if (newstub) { + zend_string_free(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); + + /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */ + main_metadata_str.s = NULL; + if (Z_TYPE(phar->metadata) != IS_UNDEF) { + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } + 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 ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + 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); + + if (entry->is_dir) { + /* we use this to calculate API version, 1.1.1 is used for phars with directories */ + has_dirs = 1; + } + if (Z_TYPE(entry->metadata) != IS_UNDEF) { + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + } + entry->metadata_str.s = NULL; + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } else { + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + } + entry->metadata_str.s = NULL; + } + + /* 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.s ? ZSTR_LEN(entry->metadata_str.s) : 0) + (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)) { + /* re-open internal file pointer just-in-time */ + newentry = phar_open_jit(phar, entry, error); + 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); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 1)) { + 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); + 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)) { + 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 != php_stream_copy_to_stream_ex(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); + php_stream_seek(entry->cfp, 0, SEEK_END); + entry->compressed_filesize = (uint32_t) 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.s ? ZSTR_LEN(main_metadata_str.s) : 0); + phar_set_32(manifest, manifest_len); + /* Hack - see bug #65028, add padding byte to the end of the manifest */ + if(manifest[0] == '\r' || manifest[0] == '\n') { + manifest_len++; + phar_set_32(manifest, manifest_len); + manifest_hack = 1; + } + 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.s ? ZSTR_LEN(main_metadata_str.s) : 0); + if (4 != php_stream_write(newfile, manifest, 4) || ((main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0) + && ZSTR_LEN(main_metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s)))) { + 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 ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + 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.s ? ZSTR_LEN(entry->metadata_str.s) : 0); + + if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer)) + || (entry->metadata_str.s && + ZSTR_LEN(entry->metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s)))) { + 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; + } + } + /* Hack - see bug #65028, add padding byte to the end of the manifest */ + if(manifest_hack) { + if(1 != php_stream_write(newfile, manifest, 1)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + + if (error) { + spprintf(error, 0, "unable to write manifest padding byte"); + } + + 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 ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + 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); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + 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 (php_stream_copy_to_stream_ex(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)) { + 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)); + 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); + php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL); + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + 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)); + php_stream_filter_append(&phar->fp->writefilters, filter); + php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL); + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + php_stream_close(phar->fp); + /* use the temp stream as our base */ + phar->fp = newfile; + } else { + php_stream_copy_to_stream_ex(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 +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +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) /* {{{ */ +{ + return php_stream_read(phar_get_pharfp((phar_archive_data*)handle), buf, len); +} +/* }}} */ + +static size_t phar_zend_stream_fsizer(void *handle) /* {{{ */ +{ + return ((phar_archive_data*)handle)->halt_offset + 32; +} /* }}} */ + +zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type); +#define phar_orig_zend_open zend_stream_open_function + +static zend_string *phar_resolve_path(const char *filename, int filename_len) +{ + return phar_find_in_include_path((char *) filename, filename_len, NULL); +} + +static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type) /* {{{ */ +{ + 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); + } + 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)) { + 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, &f)) { + + efree(name); + name = NULL; + + f.filename = file_handle->filename; + if (f.opened_path) { + efree(f.opened_path); + } + f.opened_path = file_handle->opened_path; + f.free_filename = file_handle->free_filename; + + switch (file_handle->type) { + case ZEND_HANDLE_STREAM: + case ZEND_HANDLE_MAPPED: + if (file_handle->handle.stream.closer && file_handle->handle.stream.handle) { + file_handle->handle.stream.closer(file_handle->handle.stream.handle); + } + file_handle->handle.stream.handle = NULL; + break; + default: + break; + } + *file_handle = f; + } + } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) { + zend_file_handle_dtor(file_handle); + /* compressed phar */ + 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_G(cached_fp)[phar->phar_pos].fp) : + php_stream_rewind(phar->fp); + memset(&file_handle->handle.stream.mmap, 0, sizeof(file_handle->handle.stream.mmap)); + } + } + } + + zend_try { + failed = 0; + CG(zend_lineno) = 0; + res = phar_orig_compile_file(file_handle, type); + } zend_catch { + failed = 1; + res = NULL; + } zend_end_try(); + + if (name) { + efree(name); + } + + if (failed) { + zend_bailout(); + } + + return res; +} +/* }}} */ + +typedef zend_op_array* (zend_compile_t)(zend_file_handle*, int); +typedef zend_compile_t* (compile_hook)(zend_compile_t *ptr); + +static void mime_type_dtor(zval *zv) +{ + free(Z_PTR_P(zv)); +} + +PHP_GINIT_FUNCTION(phar) /* {{{ */ +{ +#if defined(COMPILE_DL_PHAR) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + 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, mime_type_dtor, 1); + +#define PHAR_SET_MIME(mimetype, ret, fileext) \ + mime.mime = mimetype; \ + mime.len = sizeof((mimetype))+1; \ + mime.type = ret; \ + zend_hash_str_add_mem(&phar_globals->mime_types, fileext, sizeof(fileext)-1, (void *)&mime, sizeof(phar_mime_type)); \ + + 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(); +} +/* }}} */ + +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; + + phar_save_resolve_path = zend_resolve_path; + zend_resolve_path = phar_resolve_path; + + phar_object_init(); + + phar_intercept_functions_init(); + phar_save_orig_functions(); + + return php_register_url_stream_wrapper("phar", &php_stream_phar_wrapper); +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(phar) /* {{{ */ +{ + php_unregister_url_stream_wrapper("phar"); + + phar_intercept_functions_shutdown(); + + if (zend_compile_file == phar_compile_file) { + zend_compile_file = phar_orig_compile_file; + } + + if (PHAR_G(manifest_cached)) { + zend_hash_destroy(&(cached_phars)); + zend_hash_destroy(&(cached_alias)); + } + + return SUCCESS; +} +/* }}} */ + +void phar_request_initialize(void) /* {{{ */ +{ + if (!PHAR_G(request_init)) + { + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1); + PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1); + PHAR_G(request_init) = 1; + PHAR_G(request_ends) = 0; + PHAR_G(request_done) = 0; + zend_hash_init(&(PHAR_G(phar_fname_map)), 5, zend_get_hash_value, destroy_phar_data, 0); + zend_hash_init(&(PHAR_G(phar_persist_map)), 5, zend_get_hash_value, NULL, 0); + zend_hash_init(&(PHAR_G(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); + (pphar = zend_hash_get_current_data_ptr(&cached_phars)) != NULL; + zend_hash_move_forward(&cached_phars)) { + stuff[pphar->phar_pos].manifest = (phar_entry_fp_info *) ecalloc( zend_hash_num_elements(&(pphar->manifest)), sizeof(phar_entry_fp_info)); + } + + PHAR_G(cached_fp) = stuff; + } + + PHAR_G(phar_SERVER_mung_list) = 0; + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + PHAR_G(cwd_init) = 0; + } +} +/* }}} */ + +PHP_RSHUTDOWN_FUNCTION(phar) /* {{{ */ +{ + uint32_t i; + + PHAR_G(request_ends) = 1; + + if (PHAR_G(request_init)) + { + phar_release_functions(); + zend_hash_destroy(&(PHAR_G(phar_alias_map))); + PHAR_G(phar_alias_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_fname_map))); + PHAR_G(phar_fname_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_persist_map))); + PHAR_G(phar_persist_map.u.flags) = 0; + PHAR_G(phar_SERVER_mung_list) = 0; + + if (PHAR_G(cached_fp)) { + for (i = 0; i < zend_hash_num_elements(&cached_phars); ++i) { + if (PHAR_G(cached_fp)[i].fp) { + php_stream_close(PHAR_G(cached_fp)[i].fp); + } + if (PHAR_G(cached_fp)[i].ufp) { + php_stream_close(PHAR_G(cached_fp)[i].ufp); + } + efree(PHAR_G(cached_fp)[i].manifest); + } + efree(PHAR_G(cached_fp)); + PHAR_G(cached_fp) = 0; + } + + PHAR_G(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_G(request_done) = 1; + return SUCCESS; +} +/* }}} */ + +PHP_MINFO_FUNCTION(phar) /* {{{ */ +{ + phar_request_initialize(); + 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: 65ebce0f0856fc5a90a62d32dd0bb5c00627706f $"); + 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_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) { + 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?"
":"\n"); + PUTS("Phar fully realized by Gregory Beaver and Marcus Boerger."); + PUTS(!sapi_module.phpinfo_as_text?"
":"\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 + ZEND_MOD_REQUIRED("spl") + 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 + */ diff --git a/ext/phar/tests/bug77247.phpt b/ext/phar/tests/bug77247.phpt new file mode 100644 index 0000000000000..588975f9f2f88 --- /dev/null +++ b/ext/phar/tests/bug77247.phpt @@ -0,0 +1,14 @@ +--TEST-- +PHP bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +OK \ No newline at end of file From be790cf2b61dba0cf0f1d93c0870128ef2a3ec44 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:48 +0000 Subject: [PATCH 07/25] commit patch 21989338 --- ext/xmlrpc/libxmlrpc/base64.c | 4 ++-- ext/xmlrpc/tests/bug77380.phpt | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 ext/xmlrpc/tests/bug77380.phpt diff --git a/ext/xmlrpc/libxmlrpc/base64.c b/ext/xmlrpc/libxmlrpc/base64.c index dd60dc9cd6d3e..a6410145e237c 100644 --- a/ext/xmlrpc/libxmlrpc/base64.c +++ b/ext/xmlrpc/libxmlrpc/base64.c @@ -77,7 +77,7 @@ void base64_encode_xmlrpc(struct buffer_st *b, const char *source, int length) while (!hiteof) { unsigned char igroup[3], ogroup[4]; - int c, n; + int c, n; igroup[0] = igroup[1] = igroup[2] = 0; for (n = 0; n < 3; n++) { @@ -169,7 +169,7 @@ void base64_decode_xmlrpc(struct buffer_st *bfr, const char *source, int length) return; } - if (dtable[c] & 0x80) { + if (dtable[(unsigned char)c] & 0x80) { /* fprintf(stderr, "Offset %i length %i\n", offset, length); fprintf(stderr, "character '%c:%x:%c' in input file.\n", c, c, dtable[c]); diff --git a/ext/xmlrpc/tests/bug77380.phpt b/ext/xmlrpc/tests/bug77380.phpt new file mode 100644 index 0000000000000..8559c07a5aea6 --- /dev/null +++ b/ext/xmlrpc/tests/bug77380.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #77380 (Global out of bounds read in xmlrpc base64 code) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +object(stdClass)#1 (2) { + ["scalar"]=> + string(0) "" + ["xmlrpc_type"]=> + string(6) "base64" +} From 6583e9ea1a2a8a98ba11bf1525dc28433ff175a3 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:50 +0000 Subject: [PATCH 08/25] commit patch 20995179 --- ext/exif/exif.c | 14 +- ext/exif/exif.c.orig | 4741 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4748 insertions(+), 7 deletions(-) create mode 100644 ext/exif/exif.c.orig diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 306b94dbe2b30..602f43f6b79ac 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -3978,10 +3978,10 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse tag_table_type tag_table = exif_get_tag_table(section_index); if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { - return FALSE; - } + return FALSE; + } - if (ImageInfo->FileSize >= dir_offset+2) { + if (ImageInfo->FileSize >= 2 && ImageInfo->FileSize - 2 >= dir_offset) { sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); @@ -3989,8 +3989,8 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); - dir_size = 2/*num dir entries*/ +12/*length of entry*/*num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; - if (ImageInfo->FileSize >= dir_offset+dir_size) { + dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; + if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X), IFD entries(%d)", ImageInfo->FileSize, dir_offset+2, dir_size-2, num_entries); #endif @@ -4073,9 +4073,9 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse } } } - if (ImageInfo->FileSize >= dir_offset + ImageInfo->file.list[sn].size) { + if (ImageInfo->FileSize >= ImageInfo->file.list[sn].size && ImageInfo->FileSize - ImageInfo->file.list[sn].size >= dir_offset) { if (ifd_size > dir_size) { - if (dir_offset + ifd_size > ImageInfo->FileSize) { + if (ImageInfo->FileSize < ifd_size || dir_offset > ImageInfo->FileSize - ifd_size) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); return FALSE; } diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig new file mode 100644 index 0000000000000..8c54e1942cf63 --- /dev/null +++ b/ext/exif/exif.c.orig @@ -0,0 +1,4741 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Rasmus Lerdorf | + | Marcus Boerger | + +----------------------------------------------------------------------+ + */ + +/* $Id: 306b94dbe2b30552c836c631bd098f8bf5e924bb $ */ + +/* ToDos + * + * See if example images from http://www.exif.org have illegal + * thumbnail sizes or if code is corrupt. + * Create/Update exif headers. + * Create/Remove/Update image thumbnails. + */ + +/* Security + * + * At current time i do not see any security problems but a potential + * attacker could generate an image with recursive ifd pointers...(Marcus) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "ext/standard/file.h" + +#if HAVE_EXIF + +/* When EXIF_DEBUG is defined the module generates a lot of debug messages + * that help understanding what is going on. This can and should be used + * while extending the module as it shows if you are at the right position. + * You are always considered to have a copy of TIFF6.0 and EXIF2.10 standard. + */ +#undef EXIF_DEBUG + +#ifdef EXIF_DEBUG +#define EXIFERR_DC , const char *_file, size_t _line +#define EXIFERR_CC , __FILE__, __LINE__ +#else +#define EXIFERR_DC +#define EXIFERR_CC +#endif + +#undef EXIF_JPEG2000 + +#include "php_exif.h" +#include +#include "php_ini.h" +#include "ext/standard/php_string.h" +#include "ext/standard/php_image.h" +#include "ext/standard/info.h" + +/* needed for ssize_t definition */ +#include + +typedef unsigned char uchar; + +#ifndef safe_emalloc +# define safe_emalloc(a,b,c) emalloc((a)*(b)+(c)) +#endif +#ifndef safe_erealloc +# define safe_erealloc(p,a,b,c) erealloc(p, (a)*(b)+(c)) +#endif + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + +#ifndef max +# define max(a,b) ((a)>(b) ? (a) : (b)) +#endif + +#define EFREE_IF(ptr) if (ptr) efree(ptr) + +#define MAX_IFD_NESTING_LEVEL 150 + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_exif_tagname, 0) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_exif_read_data, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, sections_needed) + ZEND_ARG_INFO(0, sub_arrays) + ZEND_ARG_INFO(0, read_thumbnail) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_exif_thumbnail, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(1, width) + ZEND_ARG_INFO(1, height) + ZEND_ARG_INFO(1, imagetype) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_exif_imagetype, 0) + ZEND_ARG_INFO(0, imagefile) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ exif_functions[] + */ +const zend_function_entry exif_functions[] = { + PHP_FE(exif_read_data, arginfo_exif_read_data) + PHP_DEP_FALIAS(read_exif_data, exif_read_data, arginfo_exif_read_data) + PHP_FE(exif_tagname, arginfo_exif_tagname) + PHP_FE(exif_thumbnail, arginfo_exif_thumbnail) + PHP_FE(exif_imagetype, arginfo_exif_imagetype) + PHP_FE_END +}; +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(exif) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "EXIF Support", "enabled"); + php_info_print_table_row(2, "EXIF Version", PHP_EXIF_VERSION); + php_info_print_table_row(2, "Supported EXIF Version", "0220"); + php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF"); + + if (zend_hash_str_exists(&module_registry, "mbstring", sizeof("mbstring")-1)) { + php_info_print_table_row(2, "Multibyte decoding support using mbstring", "enabled"); + } else { + php_info_print_table_row(2, "Multibyte decoding support using mbstring", "disabled"); + } + + php_info_print_table_row(2, "Extended EXIF tag formats", "Canon, Casio, Fujifilm, Nikon, Olympus, Samsung, Panasonic, DJI, Sony, Pentax, Minolta, Sigma, Foveon, Kyocera, Ricoh, AGFA, Epson"); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +ZEND_BEGIN_MODULE_GLOBALS(exif) + char * encode_unicode; + char * decode_unicode_be; + char * decode_unicode_le; + char * encode_jis; + char * decode_jis_be; + char * decode_jis_le; +ZEND_END_MODULE_GLOBALS(exif) + +ZEND_DECLARE_MODULE_GLOBALS(exif) +#define EXIF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(exif, v) + +#if defined(ZTS) && defined(COMPILE_DL_EXIF) +ZEND_TSRMLS_CACHE_DEFINE() +#endif + +/* {{{ PHP_INI + */ + +ZEND_INI_MH(OnUpdateEncode) +{ + if (new_value && ZSTR_LEN(new_value)) { + const zend_encoding **return_list; + size_t return_size; + if (FAILURE == zend_multibyte_parse_encoding_list(ZSTR_VAL(new_value), ZSTR_LEN(new_value), + &return_list, &return_size, 0)) { + php_error_docref(NULL, E_WARNING, "Illegal encoding ignored: '%s'", ZSTR_VAL(new_value)); + return FAILURE; + } + pefree((void *) return_list, 0); + } + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} + +ZEND_INI_MH(OnUpdateDecode) +{ + if (new_value) { + const zend_encoding **return_list; + size_t return_size; + if (FAILURE == zend_multibyte_parse_encoding_list(ZSTR_VAL(new_value), ZSTR_LEN(new_value), + &return_list, &return_size, 0)) { + php_error_docref(NULL, E_WARNING, "Illegal encoding ignored: '%s'", ZSTR_VAL(new_value)); + return FAILURE; + } + pefree((void *) return_list, 0); + } + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("exif.encode_unicode", "ISO-8859-15", PHP_INI_ALL, OnUpdateEncode, encode_unicode, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_unicode_motorola", "UCS-2BE", PHP_INI_ALL, OnUpdateDecode, decode_unicode_be, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_unicode_intel", "UCS-2LE", PHP_INI_ALL, OnUpdateDecode, decode_unicode_le, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.encode_jis", "", PHP_INI_ALL, OnUpdateEncode, encode_jis, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_jis_motorola", "JIS", PHP_INI_ALL, OnUpdateDecode, decode_jis_be, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_jis_intel", "JIS", PHP_INI_ALL, OnUpdateDecode, decode_jis_le, zend_exif_globals, exif_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(exif) +{ +#if defined(COMPILE_DL_EXIF) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + exif_globals->encode_unicode = NULL; + exif_globals->decode_unicode_be = NULL; + exif_globals->decode_unicode_le = NULL; + exif_globals->encode_jis = NULL; + exif_globals->decode_jis_be = NULL; + exif_globals->decode_jis_le = NULL; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(exif) + */ +PHP_MINIT_FUNCTION(exif) +{ + REGISTER_INI_ENTRIES(); + if (zend_hash_str_exists(&module_registry, "mbstring", sizeof("mbstring")-1)) { + REGISTER_LONG_CONSTANT("EXIF_USE_MBSTRING", 1, CONST_CS | CONST_PERSISTENT); + } else { + REGISTER_LONG_CONSTANT("EXIF_USE_MBSTRING", 0, CONST_CS | CONST_PERSISTENT); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(exif) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ exif dependencies */ +static const zend_module_dep exif_module_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_OPTIONAL("mbstring") + ZEND_MOD_END +}; +/* }}} */ + +/* {{{ exif_module_entry + */ +zend_module_entry exif_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + exif_module_deps, + "exif", + exif_functions, + PHP_MINIT(exif), + PHP_MSHUTDOWN(exif), + NULL, NULL, + PHP_MINFO(exif), + PHP_EXIF_VERSION, + PHP_MODULE_GLOBALS(exif), + PHP_GINIT(exif), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_EXIF +ZEND_GET_MODULE(exif) +#endif + +/* {{{ php_strnlen + * get length of string if buffer if less than buffer size or buffer size */ +static size_t php_strnlen(char* str, size_t maxlen) { + size_t len = 0; + + if (str && maxlen && *str) { + do { + len++; + } while (--maxlen && *(++str)); + } + return len; +} +/* }}} */ + +/* {{{ error messages +*/ +static const char * EXIF_ERROR_FILEEOF = "Unexpected end of file reached"; +static const char * EXIF_ERROR_CORRUPT = "File structure corrupted"; +static const char * EXIF_ERROR_THUMBEOF = "Thumbnail goes IFD boundary or end of file reached"; +static const char * EXIF_ERROR_FSREALLOC = "Illegal reallocating of undefined file section"; + +#define EXIF_ERRLOG_FILEEOF(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_FILEEOF); +#define EXIF_ERRLOG_CORRUPT(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_CORRUPT); +#define EXIF_ERRLOG_THUMBEOF(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_THUMBEOF); +#define EXIF_ERRLOG_FSREALLOC(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_FSREALLOC); +/* }}} */ + +/* {{{ format description defines + Describes format descriptor +*/ +static int php_tiff_bytes_per_format[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1}; +#define NUM_FORMATS 13 + +#define TAG_FMT_BYTE 1 +#define TAG_FMT_STRING 2 +#define TAG_FMT_USHORT 3 +#define TAG_FMT_ULONG 4 +#define TAG_FMT_URATIONAL 5 +#define TAG_FMT_SBYTE 6 +#define TAG_FMT_UNDEFINED 7 +#define TAG_FMT_SSHORT 8 +#define TAG_FMT_SLONG 9 +#define TAG_FMT_SRATIONAL 10 +#define TAG_FMT_SINGLE 11 +#define TAG_FMT_DOUBLE 12 +#define TAG_FMT_IFD 13 + +#ifdef EXIF_DEBUG +static char *exif_get_tagformat(int format) +{ + switch(format) { + case TAG_FMT_BYTE: return "BYTE"; + case TAG_FMT_STRING: return "STRING"; + case TAG_FMT_USHORT: return "USHORT"; + case TAG_FMT_ULONG: return "ULONG"; + case TAG_FMT_URATIONAL: return "URATIONAL"; + case TAG_FMT_SBYTE: return "SBYTE"; + case TAG_FMT_UNDEFINED: return "UNDEFINED"; + case TAG_FMT_SSHORT: return "SSHORT"; + case TAG_FMT_SLONG: return "SLONG"; + case TAG_FMT_SRATIONAL: return "SRATIONAL"; + case TAG_FMT_SINGLE: return "SINGLE"; + case TAG_FMT_DOUBLE: return "DOUBLE"; + case TAG_FMT_IFD: return "IFD"; + } + return "*Illegal"; +} +#endif + +/* Describes tag values */ +#define TAG_GPS_VERSION_ID 0x0000 +#define TAG_GPS_LATITUDE_REF 0x0001 +#define TAG_GPS_LATITUDE 0x0002 +#define TAG_GPS_LONGITUDE_REF 0x0003 +#define TAG_GPS_LONGITUDE 0x0004 +#define TAG_GPS_ALTITUDE_REF 0x0005 +#define TAG_GPS_ALTITUDE 0x0006 +#define TAG_GPS_TIME_STAMP 0x0007 +#define TAG_GPS_SATELLITES 0x0008 +#define TAG_GPS_STATUS 0x0009 +#define TAG_GPS_MEASURE_MODE 0x000A +#define TAG_GPS_DOP 0x000B +#define TAG_GPS_SPEED_REF 0x000C +#define TAG_GPS_SPEED 0x000D +#define TAG_GPS_TRACK_REF 0x000E +#define TAG_GPS_TRACK 0x000F +#define TAG_GPS_IMG_DIRECTION_REF 0x0010 +#define TAG_GPS_IMG_DIRECTION 0x0011 +#define TAG_GPS_MAP_DATUM 0x0012 +#define TAG_GPS_DEST_LATITUDE_REF 0x0013 +#define TAG_GPS_DEST_LATITUDE 0x0014 +#define TAG_GPS_DEST_LONGITUDE_REF 0x0015 +#define TAG_GPS_DEST_LONGITUDE 0x0016 +#define TAG_GPS_DEST_BEARING_REF 0x0017 +#define TAG_GPS_DEST_BEARING 0x0018 +#define TAG_GPS_DEST_DISTANCE_REF 0x0019 +#define TAG_GPS_DEST_DISTANCE 0x001A +#define TAG_GPS_PROCESSING_METHOD 0x001B +#define TAG_GPS_AREA_INFORMATION 0x001C +#define TAG_GPS_DATE_STAMP 0x001D +#define TAG_GPS_DIFFERENTIAL 0x001E +#define TAG_TIFF_COMMENT 0x00FE /* SHOUDLNT HAPPEN */ +#define TAG_NEW_SUBFILE 0x00FE /* New version of subfile tag */ +#define TAG_SUBFILE_TYPE 0x00FF /* Old version of subfile tag */ +#define TAG_IMAGEWIDTH 0x0100 +#define TAG_IMAGEHEIGHT 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERPRETATION 0x0106 +#define TAG_TRESHHOLDING 0x0107 +#define TAG_CELL_WIDTH 0x0108 +#define TAG_CELL_HEIGHT 0x0109 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_STRIP_OFFSETS 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_MIN_SAMPPLE_VALUE 0x0118 +#define TAG_MAX_SAMPLE_VALUE 0x0119 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_PAGE_NAME 0x011D +#define TAG_X_POSITION 0x011E +#define TAG_Y_POSITION 0x011F +#define TAG_FREE_OFFSETS 0x0120 +#define TAG_FREE_BYTE_COUNTS 0x0121 +#define TAG_GRAY_RESPONSE_UNIT 0x0122 +#define TAG_GRAY_RESPONSE_CURVE 0x0123 +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_PAGE_NUMBER 0x0129 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_HOST_COMPUTER 0x013C +#define TAG_PREDICTOR 0x013D +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_COLOR_MAP 0x0140 +#define TAG_HALFTONE_HINTS 0x0141 +#define TAG_TILE_WIDTH 0x0142 +#define TAG_TILE_LENGTH 0x0143 +#define TAG_TILE_OFFSETS 0x0144 +#define TAG_TILE_BYTE_COUNTS 0x0145 +#define TAG_SUB_IFD 0x014A +#define TAG_INK_SETMPUTER 0x014C +#define TAG_INK_NAMES 0x014D +#define TAG_NUMBER_OF_INKS 0x014E +#define TAG_DOT_RANGE 0x0150 +#define TAG_TARGET_PRINTER 0x0151 +#define TAG_EXTRA_SAMPLE 0x0152 +#define TAG_SAMPLE_FORMAT 0x0153 +#define TAG_S_MIN_SAMPLE_VALUE 0x0154 +#define TAG_S_MAX_SAMPLE_VALUE 0x0155 +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_TABLES 0x015B +#define TAG_JPEG_PROC 0x0200 +#define TAG_JPEG_INTERCHANGE_FORMAT 0x0201 +#define TAG_JPEG_INTERCHANGE_FORMAT_LEN 0x0202 +#define TAG_JPEG_RESTART_INTERVAL 0x0203 +#define TAG_JPEG_LOSSLESS_PREDICTOR 0x0205 +#define TAG_JPEG_POINT_TRANSFORMS 0x0206 +#define TAG_JPEG_Q_TABLES 0x0207 +#define TAG_JPEG_DC_TABLES 0x0208 +#define TAG_JPEG_AC_TABLES 0x0209 +#define TAG_YCC_COEFFICIENTS 0x0211 +#define TAG_YCC_SUB_SAMPLING 0x0212 +#define TAG_YCC_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +/* 0x0301 - 0x0302 */ +/* 0x0320 */ +/* 0x0343 */ +/* 0x5001 - 0x501B */ +/* 0x5021 - 0x503B */ +/* 0x5090 - 0x5091 */ +/* 0x5100 - 0x5101 */ +/* 0x5110 - 0x5113 */ +/* 0x80E3 - 0x80E6 */ +/* 0x828d - 0x828F */ +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_EXIF_IFD_POINTER 0x8769 +#define TAG_ICC_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITY 0x8824 +#define TAG_GPS_IFD_POINTER 0x8825 +#define TAG_ISOSPEED 0x8827 +#define TAG_OPTOELECTRIC_CONVERSION_F 0x8828 +/* 0x8829 - 0x882b */ +#define TAG_EXIFVERSION 0x9000 +#define TAG_DATE_TIME_ORIGINAL 0x9003 +#define TAG_DATE_TIME_DIGITIZED 0x9004 +#define TAG_COMPONENT_CONFIG 0x9101 +#define TAG_COMPRESSED_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#define TAG_EXPOSURE_BIAS_VALUE 0x9204 +#define TAG_MAX_APERTURE 0x9205 +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_METRIC_MODULE 0x9207 +#define TAG_LIGHT_SOURCE 0x9208 +#define TAG_FLASH 0x9209 +#define TAG_FOCAL_LENGTH 0x920A +/* 0x920B - 0x920D */ +/* 0x9211 - 0x9216 */ +#define TAG_SUBJECT_AREA 0x9214 +#define TAG_MAKER_NOTE 0x927C +#define TAG_USERCOMMENT 0x9286 +#define TAG_SUB_SEC_TIME 0x9290 +#define TAG_SUB_SEC_TIME_ORIGINAL 0x9291 +#define TAG_SUB_SEC_TIME_DIGITIZED 0x9292 +/* 0x923F */ +/* 0x935C */ +#define TAG_XP_TITLE 0x9C9B +#define TAG_XP_COMMENTS 0x9C9C +#define TAG_XP_AUTHOR 0x9C9D +#define TAG_XP_KEYWORDS 0x9C9E +#define TAG_XP_SUBJECT 0x9C9F +#define TAG_FLASH_PIX_VERSION 0xA000 +#define TAG_COLOR_SPACE 0xA001 +#define TAG_COMP_IMAGE_WIDTH 0xA002 /* compressed images only */ +#define TAG_COMP_IMAGE_HEIGHT 0xA003 +#define TAG_RELATED_SOUND_FILE 0xA004 +#define TAG_INTEROP_IFD_POINTER 0xA005 /* IFD pointer */ +#define TAG_FLASH_ENERGY 0xA20B +#define TAG_SPATIAL_FREQUENCY_RESPONSE 0xA20C +#define TAG_FOCALPLANE_X_RES 0xA20E +#define TAG_FOCALPLANE_Y_RES 0xA20F +#define TAG_FOCALPLANE_RESOLUTION_UNIT 0xA210 +#define TAG_SUBJECT_LOCATION 0xA214 +#define TAG_EXPOSURE_INDEX 0xA215 +#define TAG_SENSING_METHOD 0xA217 +#define TAG_FILE_SOURCE 0xA300 +#define TAG_SCENE_TYPE 0xA301 +#define TAG_CFA_PATTERN 0xA302 +#define TAG_CUSTOM_RENDERED 0xA401 +#define TAG_EXPOSURE_MODE 0xA402 +#define TAG_WHITE_BALANCE 0xA403 +#define TAG_DIGITAL_ZOOM_RATIO 0xA404 +#define TAG_FOCAL_LENGTH_IN_35_MM_FILM 0xA405 +#define TAG_SCENE_CAPTURE_TYPE 0xA406 +#define TAG_GAIN_CONTROL 0xA407 +#define TAG_CONTRAST 0xA408 +#define TAG_SATURATION 0xA409 +#define TAG_SHARPNESS 0xA40A +#define TAG_DEVICE_SETTING_DESCRIPTION 0xA40B +#define TAG_SUBJECT_DISTANCE_RANGE 0xA40C +#define TAG_IMAGE_UNIQUE_ID 0xA420 + +/* Olympus specific tags */ +#define TAG_OLYMPUS_SPECIALMODE 0x0200 +#define TAG_OLYMPUS_JPEGQUAL 0x0201 +#define TAG_OLYMPUS_MACRO 0x0202 +#define TAG_OLYMPUS_DIGIZOOM 0x0204 +#define TAG_OLYMPUS_SOFTWARERELEASE 0x0207 +#define TAG_OLYMPUS_PICTINFO 0x0208 +#define TAG_OLYMPUS_CAMERAID 0x0209 +/* end Olympus specific tags */ + +/* Internal */ +#define TAG_NONE -1 /* note that -1 <> 0xFFFF */ +#define TAG_COMPUTED_VALUE -2 +#define TAG_END_OF_LIST 0xFFFD + +/* Values for TAG_PHOTOMETRIC_INTERPRETATION */ +#define PMI_BLACK_IS_ZERO 0 +#define PMI_WHITE_IS_ZERO 1 +#define PMI_RGB 2 +#define PMI_PALETTE_COLOR 3 +#define PMI_TRANSPARENCY_MASK 4 +#define PMI_SEPARATED 5 +#define PMI_YCBCR 6 +#define PMI_CIELAB 8 + +/* }}} */ + +/* {{{ TabTable[] + */ +typedef const struct { + unsigned short Tag; + char *Desc; +} tag_info_type; + +typedef tag_info_type tag_info_array[]; +typedef tag_info_type *tag_table_type; + +#define TAG_TABLE_END \ + {TAG_NONE, "No tag value"},\ + {TAG_COMPUTED_VALUE, "Computed value"},\ + {TAG_END_OF_LIST, ""} /* Important for exif_get_tagname() IF value != "" function result is != false */ + +static tag_info_array tag_table_IFD = { + { 0x000B, "ACDComment"}, + { 0x00FE, "NewSubFile"}, /* better name it 'ImageType' ? */ + { 0x00FF, "SubFile"}, + { 0x0100, "ImageWidth"}, + { 0x0101, "ImageLength"}, + { 0x0102, "BitsPerSample"}, + { 0x0103, "Compression"}, + { 0x0106, "PhotometricInterpretation"}, + { 0x010A, "FillOrder"}, + { 0x010D, "DocumentName"}, + { 0x010E, "ImageDescription"}, + { 0x010F, "Make"}, + { 0x0110, "Model"}, + { 0x0111, "StripOffsets"}, + { 0x0112, "Orientation"}, + { 0x0115, "SamplesPerPixel"}, + { 0x0116, "RowsPerStrip"}, + { 0x0117, "StripByteCounts"}, + { 0x0118, "MinSampleValue"}, + { 0x0119, "MaxSampleValue"}, + { 0x011A, "XResolution"}, + { 0x011B, "YResolution"}, + { 0x011C, "PlanarConfiguration"}, + { 0x011D, "PageName"}, + { 0x011E, "XPosition"}, + { 0x011F, "YPosition"}, + { 0x0120, "FreeOffsets"}, + { 0x0121, "FreeByteCounts"}, + { 0x0122, "GrayResponseUnit"}, + { 0x0123, "GrayResponseCurve"}, + { 0x0124, "T4Options"}, + { 0x0125, "T6Options"}, + { 0x0128, "ResolutionUnit"}, + { 0x0129, "PageNumber"}, + { 0x012D, "TransferFunction"}, + { 0x0131, "Software"}, + { 0x0132, "DateTime"}, + { 0x013B, "Artist"}, + { 0x013C, "HostComputer"}, + { 0x013D, "Predictor"}, + { 0x013E, "WhitePoint"}, + { 0x013F, "PrimaryChromaticities"}, + { 0x0140, "ColorMap"}, + { 0x0141, "HalfToneHints"}, + { 0x0142, "TileWidth"}, + { 0x0143, "TileLength"}, + { 0x0144, "TileOffsets"}, + { 0x0145, "TileByteCounts"}, + { 0x014A, "SubIFD"}, + { 0x014C, "InkSet"}, + { 0x014D, "InkNames"}, + { 0x014E, "NumberOfInks"}, + { 0x0150, "DotRange"}, + { 0x0151, "TargetPrinter"}, + { 0x0152, "ExtraSample"}, + { 0x0153, "SampleFormat"}, + { 0x0154, "SMinSampleValue"}, + { 0x0155, "SMaxSampleValue"}, + { 0x0156, "TransferRange"}, + { 0x0157, "ClipPath"}, + { 0x0158, "XClipPathUnits"}, + { 0x0159, "YClipPathUnits"}, + { 0x015A, "Indexed"}, + { 0x015B, "JPEGTables"}, + { 0x015F, "OPIProxy"}, + { 0x0200, "JPEGProc"}, + { 0x0201, "JPEGInterchangeFormat"}, + { 0x0202, "JPEGInterchangeFormatLength"}, + { 0x0203, "JPEGRestartInterval"}, + { 0x0205, "JPEGLosslessPredictors"}, + { 0x0206, "JPEGPointTransforms"}, + { 0x0207, "JPEGQTables"}, + { 0x0208, "JPEGDCTables"}, + { 0x0209, "JPEGACTables"}, + { 0x0211, "YCbCrCoefficients"}, + { 0x0212, "YCbCrSubSampling"}, + { 0x0213, "YCbCrPositioning"}, + { 0x0214, "ReferenceBlackWhite"}, + { 0x02BC, "ExtensibleMetadataPlatform"}, /* XAP: Extensible Authoring Publishing, obsoleted by XMP: Extensible Metadata Platform */ + { 0x0301, "Gamma"}, + { 0x0302, "ICCProfileDescriptor"}, + { 0x0303, "SRGBRenderingIntent"}, + { 0x0320, "ImageTitle"}, + { 0x5001, "ResolutionXUnit"}, + { 0x5002, "ResolutionYUnit"}, + { 0x5003, "ResolutionXLengthUnit"}, + { 0x5004, "ResolutionYLengthUnit"}, + { 0x5005, "PrintFlags"}, + { 0x5006, "PrintFlagsVersion"}, + { 0x5007, "PrintFlagsCrop"}, + { 0x5008, "PrintFlagsBleedWidth"}, + { 0x5009, "PrintFlagsBleedWidthScale"}, + { 0x500A, "HalftoneLPI"}, + { 0x500B, "HalftoneLPIUnit"}, + { 0x500C, "HalftoneDegree"}, + { 0x500D, "HalftoneShape"}, + { 0x500E, "HalftoneMisc"}, + { 0x500F, "HalftoneScreen"}, + { 0x5010, "JPEGQuality"}, + { 0x5011, "GridSize"}, + { 0x5012, "ThumbnailFormat"}, + { 0x5013, "ThumbnailWidth"}, + { 0x5014, "ThumbnailHeight"}, + { 0x5015, "ThumbnailColorDepth"}, + { 0x5016, "ThumbnailPlanes"}, + { 0x5017, "ThumbnailRawBytes"}, + { 0x5018, "ThumbnailSize"}, + { 0x5019, "ThumbnailCompressedSize"}, + { 0x501A, "ColorTransferFunction"}, + { 0x501B, "ThumbnailData"}, + { 0x5020, "ThumbnailImageWidth"}, + { 0x5021, "ThumbnailImageHeight"}, + { 0x5022, "ThumbnailBitsPerSample"}, + { 0x5023, "ThumbnailCompression"}, + { 0x5024, "ThumbnailPhotometricInterp"}, + { 0x5025, "ThumbnailImageDescription"}, + { 0x5026, "ThumbnailEquipMake"}, + { 0x5027, "ThumbnailEquipModel"}, + { 0x5028, "ThumbnailStripOffsets"}, + { 0x5029, "ThumbnailOrientation"}, + { 0x502A, "ThumbnailSamplesPerPixel"}, + { 0x502B, "ThumbnailRowsPerStrip"}, + { 0x502C, "ThumbnailStripBytesCount"}, + { 0x502D, "ThumbnailResolutionX"}, + { 0x502E, "ThumbnailResolutionY"}, + { 0x502F, "ThumbnailPlanarConfig"}, + { 0x5030, "ThumbnailResolutionUnit"}, + { 0x5031, "ThumbnailTransferFunction"}, + { 0x5032, "ThumbnailSoftwareUsed"}, + { 0x5033, "ThumbnailDateTime"}, + { 0x5034, "ThumbnailArtist"}, + { 0x5035, "ThumbnailWhitePoint"}, + { 0x5036, "ThumbnailPrimaryChromaticities"}, + { 0x5037, "ThumbnailYCbCrCoefficients"}, + { 0x5038, "ThumbnailYCbCrSubsampling"}, + { 0x5039, "ThumbnailYCbCrPositioning"}, + { 0x503A, "ThumbnailRefBlackWhite"}, + { 0x503B, "ThumbnailCopyRight"}, + { 0x5090, "LuminanceTable"}, + { 0x5091, "ChrominanceTable"}, + { 0x5100, "FrameDelay"}, + { 0x5101, "LoopCount"}, + { 0x5110, "PixelUnit"}, + { 0x5111, "PixelPerUnitX"}, + { 0x5112, "PixelPerUnitY"}, + { 0x5113, "PaletteHistogram"}, + { 0x1000, "RelatedImageFileFormat"}, + { 0x800D, "ImageID"}, + { 0x80E3, "Matteing"}, /* obsoleted by ExtraSamples */ + { 0x80E4, "DataType"}, /* obsoleted by SampleFormat */ + { 0x80E5, "ImageDepth"}, + { 0x80E6, "TileDepth"}, + { 0x828D, "CFARepeatPatternDim"}, + { 0x828E, "CFAPattern"}, + { 0x828F, "BatteryLevel"}, + { 0x8298, "Copyright"}, + { 0x829A, "ExposureTime"}, + { 0x829D, "FNumber"}, + { 0x83BB, "IPTC/NAA"}, + { 0x84E3, "IT8RasterPadding"}, + { 0x84E5, "IT8ColorTable"}, + { 0x8649, "ImageResourceInformation"}, /* PhotoShop */ + { 0x8769, "Exif_IFD_Pointer"}, + { 0x8773, "ICC_Profile"}, + { 0x8822, "ExposureProgram"}, + { 0x8824, "SpectralSensity"}, + { 0x8828, "OECF"}, + { 0x8825, "GPS_IFD_Pointer"}, + { 0x8827, "ISOSpeedRatings"}, + { 0x8828, "OECF"}, + { 0x9000, "ExifVersion"}, + { 0x9003, "DateTimeOriginal"}, + { 0x9004, "DateTimeDigitized"}, + { 0x9101, "ComponentsConfiguration"}, + { 0x9102, "CompressedBitsPerPixel"}, + { 0x9201, "ShutterSpeedValue"}, + { 0x9202, "ApertureValue"}, + { 0x9203, "BrightnessValue"}, + { 0x9204, "ExposureBiasValue"}, + { 0x9205, "MaxApertureValue"}, + { 0x9206, "SubjectDistance"}, + { 0x9207, "MeteringMode"}, + { 0x9208, "LightSource"}, + { 0x9209, "Flash"}, + { 0x920A, "FocalLength"}, + { 0x920B, "FlashEnergy"}, /* 0xA20B in JPEG */ + { 0x920C, "SpatialFrequencyResponse"}, /* 0xA20C - - */ + { 0x920D, "Noise"}, + { 0x920E, "FocalPlaneXResolution"}, /* 0xA20E - - */ + { 0x920F, "FocalPlaneYResolution"}, /* 0xA20F - - */ + { 0x9210, "FocalPlaneResolutionUnit"}, /* 0xA210 - - */ + { 0x9211, "ImageNumber"}, + { 0x9212, "SecurityClassification"}, + { 0x9213, "ImageHistory"}, + { 0x9214, "SubjectLocation"}, /* 0xA214 - - */ + { 0x9215, "ExposureIndex"}, /* 0xA215 - - */ + { 0x9216, "TIFF/EPStandardID"}, + { 0x9217, "SensingMethod"}, /* 0xA217 - - */ + { 0x923F, "StoNits"}, + { 0x927C, "MakerNote"}, + { 0x9286, "UserComment"}, + { 0x9290, "SubSecTime"}, + { 0x9291, "SubSecTimeOriginal"}, + { 0x9292, "SubSecTimeDigitized"}, + { 0x935C, "ImageSourceData"}, /* "Adobe Photoshop Document Data Block": 8BIM... */ + { 0x9c9b, "Title" }, /* Win XP specific, Unicode */ + { 0x9c9c, "Comments" }, /* Win XP specific, Unicode */ + { 0x9c9d, "Author" }, /* Win XP specific, Unicode */ + { 0x9c9e, "Keywords" }, /* Win XP specific, Unicode */ + { 0x9c9f, "Subject" }, /* Win XP specific, Unicode, not to be confused with SubjectDistance and SubjectLocation */ + { 0xA000, "FlashPixVersion"}, + { 0xA001, "ColorSpace"}, + { 0xA002, "ExifImageWidth"}, + { 0xA003, "ExifImageLength"}, + { 0xA004, "RelatedSoundFile"}, + { 0xA005, "InteroperabilityOffset"}, + { 0xA20B, "FlashEnergy"}, /* 0x920B in TIFF/EP */ + { 0xA20C, "SpatialFrequencyResponse"}, /* 0x920C - - */ + { 0xA20D, "Noise"}, + { 0xA20E, "FocalPlaneXResolution"}, /* 0x920E - - */ + { 0xA20F, "FocalPlaneYResolution"}, /* 0x920F - - */ + { 0xA210, "FocalPlaneResolutionUnit"}, /* 0x9210 - - */ + { 0xA211, "ImageNumber"}, + { 0xA212, "SecurityClassification"}, + { 0xA213, "ImageHistory"}, + { 0xA214, "SubjectLocation"}, /* 0x9214 - - */ + { 0xA215, "ExposureIndex"}, /* 0x9215 - - */ + { 0xA216, "TIFF/EPStandardID"}, + { 0xA217, "SensingMethod"}, /* 0x9217 - - */ + { 0xA300, "FileSource"}, + { 0xA301, "SceneType"}, + { 0xA302, "CFAPattern"}, + { 0xA401, "CustomRendered"}, + { 0xA402, "ExposureMode"}, + { 0xA403, "WhiteBalance"}, + { 0xA404, "DigitalZoomRatio"}, + { 0xA405, "FocalLengthIn35mmFilm"}, + { 0xA406, "SceneCaptureType"}, + { 0xA407, "GainControl"}, + { 0xA408, "Contrast"}, + { 0xA409, "Saturation"}, + { 0xA40A, "Sharpness"}, + { 0xA40B, "DeviceSettingDescription"}, + { 0xA40C, "SubjectDistanceRange"}, + { 0xA420, "ImageUniqueID"}, + TAG_TABLE_END +} ; + +static tag_info_array tag_table_GPS = { + { 0x0000, "GPSVersion"}, + { 0x0001, "GPSLatitudeRef"}, + { 0x0002, "GPSLatitude"}, + { 0x0003, "GPSLongitudeRef"}, + { 0x0004, "GPSLongitude"}, + { 0x0005, "GPSAltitudeRef"}, + { 0x0006, "GPSAltitude"}, + { 0x0007, "GPSTimeStamp"}, + { 0x0008, "GPSSatellites"}, + { 0x0009, "GPSStatus"}, + { 0x000A, "GPSMeasureMode"}, + { 0x000B, "GPSDOP"}, + { 0x000C, "GPSSpeedRef"}, + { 0x000D, "GPSSpeed"}, + { 0x000E, "GPSTrackRef"}, + { 0x000F, "GPSTrack"}, + { 0x0010, "GPSImgDirectionRef"}, + { 0x0011, "GPSImgDirection"}, + { 0x0012, "GPSMapDatum"}, + { 0x0013, "GPSDestLatitudeRef"}, + { 0x0014, "GPSDestLatitude"}, + { 0x0015, "GPSDestLongitudeRef"}, + { 0x0016, "GPSDestLongitude"}, + { 0x0017, "GPSDestBearingRef"}, + { 0x0018, "GPSDestBearing"}, + { 0x0019, "GPSDestDistanceRef"}, + { 0x001A, "GPSDestDistance"}, + { 0x001B, "GPSProcessingMode"}, + { 0x001C, "GPSAreaInformation"}, + { 0x001D, "GPSDateStamp"}, + { 0x001E, "GPSDifferential"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_IOP = { + { 0x0001, "InterOperabilityIndex"}, /* should be 'R98' or 'THM' */ + { 0x0002, "InterOperabilityVersion"}, + { 0x1000, "RelatedFileFormat"}, + { 0x1001, "RelatedImageWidth"}, + { 0x1002, "RelatedImageHeight"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_CANON = { + { 0x0001, "ModeArray"}, /* guess */ + { 0x0004, "ImageInfo"}, /* guess */ + { 0x0006, "ImageType"}, + { 0x0007, "FirmwareVersion"}, + { 0x0008, "ImageNumber"}, + { 0x0009, "OwnerName"}, + { 0x000C, "Camera"}, + { 0x000F, "CustomFunctions"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_CASIO = { + { 0x0001, "RecordingMode"}, + { 0x0002, "Quality"}, + { 0x0003, "FocusingMode"}, + { 0x0004, "FlashMode"}, + { 0x0005, "FlashIntensity"}, + { 0x0006, "ObjectDistance"}, + { 0x0007, "WhiteBalance"}, + { 0x000A, "DigitalZoom"}, + { 0x000B, "Sharpness"}, + { 0x000C, "Contrast"}, + { 0x000D, "Saturation"}, + { 0x0014, "CCDSensitivity"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_FUJI = { + { 0x0000, "Version"}, + { 0x1000, "Quality"}, + { 0x1001, "Sharpness"}, + { 0x1002, "WhiteBalance"}, + { 0x1003, "Color"}, + { 0x1004, "Tone"}, + { 0x1010, "FlashMode"}, + { 0x1011, "FlashStrength"}, + { 0x1020, "Macro"}, + { 0x1021, "FocusMode"}, + { 0x1030, "SlowSync"}, + { 0x1031, "PictureMode"}, + { 0x1100, "ContTake"}, + { 0x1300, "BlurWarning"}, + { 0x1301, "FocusWarning"}, + { 0x1302, "AEWarning "}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_NIKON = { + { 0x0003, "Quality"}, + { 0x0004, "ColorMode"}, + { 0x0005, "ImageAdjustment"}, + { 0x0006, "CCDSensitivity"}, + { 0x0007, "WhiteBalance"}, + { 0x0008, "Focus"}, + { 0x000a, "DigitalZoom"}, + { 0x000b, "Converter"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_NIKON_990 = { + { 0x0001, "Version"}, + { 0x0002, "ISOSetting"}, + { 0x0003, "ColorMode"}, + { 0x0004, "Quality"}, + { 0x0005, "WhiteBalance"}, + { 0x0006, "ImageSharpening"}, + { 0x0007, "FocusMode"}, + { 0x0008, "FlashSetting"}, + { 0x000F, "ISOSelection"}, + { 0x0080, "ImageAdjustment"}, + { 0x0082, "AuxiliaryLens"}, + { 0x0085, "ManualFocusDistance"}, + { 0x0086, "DigitalZoom"}, + { 0x0088, "AFFocusPosition"}, + { 0x0010, "DataDump"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_OLYMPUS = { + { 0x0200, "SpecialMode"}, + { 0x0201, "JPEGQuality"}, + { 0x0202, "Macro"}, + { 0x0204, "DigitalZoom"}, + { 0x0207, "SoftwareRelease"}, + { 0x0208, "PictureInfo"}, + { 0x0209, "CameraId"}, + { 0x0F00, "DataDump"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_SAMSUNG = { + { 0x0001, "Version"}, + { 0x0021, "PictureWizard"}, + { 0x0030, "LocalLocationName"}, + { 0x0031, "LocationName"}, + { 0x0035, "Preview"}, + { 0x0043, "CameraTemperature"}, + { 0xa001, "FirmwareName"}, + { 0xa003, "LensType"}, + { 0xa004, "LensFirmware"}, + { 0xa010, "SensorAreas"}, + { 0xa011, "ColorSpace"}, + { 0xa012, "SmartRange"}, + { 0xa013, "ExposureBiasValue"}, + { 0xa014, "ISO"}, + { 0xa018, "ExposureTime"}, + { 0xa019, "FNumber"}, + { 0xa01a, "FocalLengthIn35mmFormat"}, + { 0xa020, "EncryptionKey"}, + { 0xa021, "WB_RGGBLevelsUncorrected"}, + { 0xa022, "WB_RGGBLevelsAuto"}, + { 0xa023, "WB_RGGBLevelsIlluminator1"}, + { 0xa024, "WB_RGGBLevelsIlluminator2"}, + { 0xa028, "WB_RGGBLevelsBlack"}, + { 0xa030, "ColorMatrix"}, + { 0xa031, "ColorMatrixSRGB"}, + { 0xa032, "ColorMatrixAdobeRGB"}, + { 0xa040, "ToneCurve1"}, + { 0xa041, "ToneCurve2"}, + { 0xa042, "ToneCurve3"}, + { 0xa043, "ToneCurve4"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_PANASONIC = { + { 0x0001, "Quality"}, + { 0x0002, "FirmwareVersion"}, + { 0x0003, "WhiteBalance"}, + { 0x0004, "0x0004"}, + { 0x0007, "FocusMode"}, + { 0x000f, "AFMode"}, + { 0x001a, "ImageStabilization"}, + { 0x001c, "Macro"}, + { 0x001f, "ShootingMode"}, + { 0x0020, "Audio"}, + { 0x0021, "DataDump"}, + { 0x0022, "0x0022"}, + { 0x0023, "WhiteBalanceBias"}, + { 0x0024, "FlashBias"}, + { 0x0025, "InternalSerialNumber"}, + { 0x0026, "ExifVersion"}, + { 0x0027, "0x0027"}, + { 0x0028, "ColorEffect"}, + { 0x0029, "TimeSincePowerOn"}, + { 0x002a, "BurstMode"}, + { 0x002b, "SequenceNumber"}, + { 0x002c, "Contrast"}, + { 0x002d, "NoiseReduction"}, + { 0x002e, "SelfTimer"}, + { 0x002f, "0x002f"}, + { 0x0030, "Rotation"}, + { 0x0031, "AFAssistLamp"}, + { 0x0032, "ColorMode"}, + { 0x0033, "BabyAge1"}, + { 0x0034, "OpticalZoomMode"}, + { 0x0035, "ConversionLens"}, + { 0x0036, "TravelDay"}, + { 0x0039, "Contrast"}, + { 0x003a, "WorldTimeLocation"}, + { 0x003b, "TextStamp1"}, + { 0x003c, "ProgramISO"}, + { 0x003d, "AdvancedSceneType"}, + { 0x003e, "TextStamp2"}, + { 0x003f, "FacesDetected"}, + { 0x0040, "Saturation"}, + { 0x0041, "Sharpness"}, + { 0x0042, "FilmMode"}, + { 0x0044, "ColorTempKelvin"}, + { 0x0045, "BracketSettings"}, + { 0x0046, "WBAdjustAB"}, + { 0x0047, "WBAdjustGM"}, + { 0x0048, "FlashCurtain"}, + { 0x0049, "LongShutterNoiseReduction"}, + { 0x004b, "ImageWidth"}, + { 0x004c, "ImageHeight"}, + { 0x004d, "AFPointPosition"}, + { 0x004e, "FaceDetInfo"}, + { 0x0051, "LensType"}, + { 0x0052, "LensSerialNumber"}, + { 0x0053, "AccessoryType"}, + { 0x0054, "AccessorySerialNumber"}, + { 0x0059, "Transform1"}, + { 0x005d, "IntelligentExposure"}, + { 0x0060, "LensFirmwareVersion"}, + { 0x0061, "FaceRecInfo"}, + { 0x0062, "FlashWarning"}, + { 0x0065, "Title"}, + { 0x0066, "BabyName"}, + { 0x0067, "Location"}, + { 0x0069, "Country"}, + { 0x006b, "State"}, + { 0x006d, "City"}, + { 0x006f, "Landmark"}, + { 0x0070, "IntelligentResolution"}, + { 0x0077, "BurstSheed"}, + { 0x0079, "IntelligentDRange"}, + { 0x007c, "ClearRetouch"}, + { 0x0080, "City2"}, + { 0x0086, "ManometerPressure"}, + { 0x0089, "PhotoStyle"}, + { 0x008a, "ShadingCompensation"}, + { 0x008c, "AccelerometerZ"}, + { 0x008d, "AccelerometerX"}, + { 0x008e, "AccelerometerY"}, + { 0x008f, "CameraOrientation"}, + { 0x0090, "RollAngle"}, + { 0x0091, "PitchAngle"}, + { 0x0093, "SweepPanoramaDirection"}, + { 0x0094, "PanoramaFieldOfView"}, + { 0x0096, "TimerRecording"}, + { 0x009d, "InternalNDFilter"}, + { 0x009e, "HDR"}, + { 0x009f, "ShutterType"}, + { 0x00a3, "ClearRetouchValue"}, + { 0x00ab, "TouchAE"}, + { 0x0e00, "PrintIM"}, + { 0x4449, "0x4449"}, + { 0x8000, "MakerNoteVersion"}, + { 0x8001, "SceneMode"}, + { 0x8004, "WBRedLevel"}, + { 0x8005, "WBGreenLevel"}, + { 0x8006, "WBBlueLevel"}, + { 0x8007, "FlashFired"}, + { 0x8008, "TextStamp3"}, + { 0x8009, "TextStamp4"}, + { 0x8010, "BabyAge2"}, + { 0x8012, "Transform2"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_DJI = { + { 0x0001, "Make"}, + { 0x0003, "SpeedX"}, + { 0x0004, "SpeedY"}, + { 0x0005, "SpeedZ"}, + { 0x0006, "Pitch"}, + { 0x0007, "Yaw"}, + { 0x0008, "Roll"}, + { 0x0009, "CameraPitch"}, + { 0x000a, "CameraYaw"}, + { 0x000b, "CameraRoll"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_SONY = { + { 0x0102, "Quality"}, + { 0x0104, "FlashExposureComp"}, + { 0x0105, "Teleconverter"}, + { 0x0112, "WhiteBalanceFineTune"}, + { 0x0114, "CameraSettings"}, + { 0x0115, "WhiteBalance"}, + { 0x0116, "0x0116"}, + { 0x0e00, "PrintIM"}, + { 0x1000, "MultiBurstMode"}, + { 0x1001, "MultiBurstImageWidth"}, + { 0x1002, "MultiBurstImageHeight"}, + { 0x1003, "Panorama"}, + { 0x2000, "0x2000"}, + { 0x2001, "PreviewImage"}, + { 0x2002, "0x2002"}, + { 0x2003, "0x2003"}, + { 0x2004, "Contrast"}, + { 0x2005, "Saturation"}, + { 0x2006, "0x2006"}, + { 0x2007, "0x2007"}, + { 0x2008, "0x2008"}, + { 0x2009, "0x2009"}, + { 0x200a, "AutoHDR"}, + { 0x3000, "ShotInfo"}, + { 0xb000, "FileFormat"}, + { 0xb001, "SonyModelID"}, + { 0xb020, "ColorReproduction"}, + { 0xb021, "ColorTemperature"}, + { 0xb022, "ColorCompensationFilter"}, + { 0xb023, "SceneMode"}, + { 0xb024, "ZoneMatching"}, + { 0xb025, "DynamicRangeOptimizer"}, + { 0xb026, "ImageStabilization"}, + { 0xb027, "LensID"}, + { 0xb028, "MinoltaMakerNote"}, + { 0xb029, "ColorMode"}, + { 0xb02b, "FullImageSize"}, + { 0xb02c, "PreviewImageSize"}, + { 0xb040, "Macro"}, + { 0xb041, "ExposureMode"}, + { 0xb042, "FocusMode"}, + { 0xb043, "AFMode"}, + { 0xb044, "AFIlluminator"}, + { 0xb047, "JPEGQuality"}, + { 0xb048, "FlashLevel"}, + { 0xb049, "ReleaseMode"}, + { 0xb04a, "SequenceNumber"}, + { 0xb04b, "AntiBlur"}, + { 0xb04e, "LongExposureNoiseReduction"}, + { 0xb04f, "DynamicRangeOptimizer"}, + { 0xb052, "IntelligentAuto"}, + { 0xb054, "WhiteBalance2"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_PENTAX = { + { 0x0000, "Version"}, + { 0x0001, "Mode"}, + { 0x0002, "PreviewResolution"}, + { 0x0003, "PreviewLength"}, + { 0x0004, "PreviewOffset"}, + { 0x0005, "ModelID"}, + { 0x0006, "Date"}, + { 0x0007, "Time"}, + { 0x0008, "Quality"}, + { 0x0009, "Size"}, + { 0x000c, "Flash"}, + { 0x000d, "Focus"}, + { 0x000e, "AFPoint"}, + { 0x000f, "AFPointInFocus"}, + { 0x0012, "ExposureTime"}, + { 0x0013, "FNumber"}, + { 0x0014, "ISO"}, + { 0x0016, "ExposureCompensation"}, + { 0x0017, "MeteringMode"}, + { 0x0018, "AutoBracketing"}, + { 0x0019, "WhiteBalance"}, + { 0x001a, "WhiteBalanceMode"}, + { 0x001b, "BlueBalance"}, + { 0x001c, "RedBalance"}, + { 0x001d, "FocalLength"}, + { 0x001e, "DigitalZoom"}, + { 0x001f, "Saturation"}, + { 0x0020, "Contrast"}, + { 0x0021, "Sharpness"}, + { 0x0022, "Location"}, + { 0x0023, "Hometown"}, + { 0x0024, "Destination"}, + { 0x0025, "HometownDST"}, + { 0x0026, "DestinationDST"}, + { 0x0027, "DSPFirmwareVersion"}, + { 0x0028, "CPUFirmwareVersion"}, + { 0x0029, "FrameNumber"}, + { 0x002d, "EffectiveLV"}, + { 0x0032, "ImageProcessing"}, + { 0x0033, "PictureMode"}, + { 0x0034, "DriveMode"}, + { 0x0037, "ColorSpace"}, + { 0x0038, "ImageAreaOffset"}, + { 0x0039, "RawImageSize"}, + { 0x003e, "PreviewImageBorders"}, + { 0x003f, "LensType"}, + { 0x0040, "SensitivityAdjust"}, + { 0x0041, "DigitalFilter"}, + { 0x0047, "Temperature"}, + { 0x0048, "AELock"}, + { 0x0049, "NoiseReduction"}, + { 0x004d, "FlashExposureCompensation"}, + { 0x004f, "ImageTone"}, + { 0x0050, "ColorTemperature"}, + { 0x005c, "ShakeReduction"}, + { 0x005d, "ShutterCount"}, + { 0x0069, "DynamicRangeExpansion"}, + { 0x0071, "HighISONoiseReduction"}, + { 0x0072, "AFAdjustment"}, + { 0x0200, "BlackPoint"}, + { 0x0201, "WhitePoint"}, + { 0x0205, "ShotInfo"}, + { 0x0206, "AEInfo"}, + { 0x0207, "LensInfo"}, + { 0x0208, "FlashInfo"}, + { 0x0209, "AEMeteringSegments"}, + { 0x020a, "FlashADump"}, + { 0x020b, "FlashBDump"}, + { 0x020d, "WB_RGGBLevelsDaylight"}, + { 0x020e, "WB_RGGBLevelsShade"}, + { 0x020f, "WB_RGGBLevelsCloudy"}, + { 0x0210, "WB_RGGBLevelsTungsten"}, + { 0x0211, "WB_RGGBLevelsFluorescentD"}, + { 0x0212, "WB_RGGBLevelsFluorescentN"}, + { 0x0213, "WB_RGGBLevelsFluorescentW"}, + { 0x0214, "WB_RGGBLevelsFlash"}, + { 0x0215, "CameraInfo"}, + { 0x0216, "BatteryInfo"}, + { 0x021f, "AFInfo"}, + { 0x0222, "ColorInfo"}, + { 0x0229, "SerialNumber"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_MINOLTA = { + { 0x0000, "Version"}, + { 0x0001, "CameraSettingsStdOld"}, + { 0x0003, "CameraSettingsStdNew"}, + { 0x0004, "CameraSettings7D"}, + { 0x0018, "ImageStabilizationData"}, + { 0x0020, "WBInfoA100"}, + { 0x0040, "CompressedImageSize"}, + { 0x0081, "Thumbnail"}, + { 0x0088, "ThumbnailOffset"}, + { 0x0089, "ThumbnailLength"}, + { 0x0100, "SceneMode"}, + { 0x0101, "ColorMode"}, + { 0x0102, "Quality"}, + { 0x0103, "0x0103"}, + { 0x0104, "FlashExposureComp"}, + { 0x0105, "Teleconverter"}, + { 0x0107, "ImageStabilization"}, + { 0x0109, "RawAndJpgRecording"}, + { 0x010a, "ZoneMatching"}, + { 0x010b, "ColorTemperature"}, + { 0x010c, "LensID"}, + { 0x0111, "ColorCompensationFilter"}, + { 0x0112, "WhiteBalanceFineTune"}, + { 0x0113, "ImageStabilizationA100"}, + { 0x0114, "CameraSettings5D"}, + { 0x0115, "WhiteBalance"}, + { 0x0e00, "PrintIM"}, + { 0x0f00, "CameraSettingsZ1"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_SIGMA = { + { 0x0002, "SerialNumber"}, + { 0x0003, "DriveMode"}, + { 0x0004, "ResolutionMode"}, + { 0x0005, "AutofocusMode"}, + { 0x0006, "FocusSetting"}, + { 0x0007, "WhiteBalance"}, + { 0x0008, "ExposureMode"}, + { 0x0009, "MeteringMode"}, + { 0x000a, "LensRange"}, + { 0x000b, "ColorSpace"}, + { 0x000c, "Exposure"}, + { 0x000d, "Contrast"}, + { 0x000e, "Shadow"}, + { 0x000f, "Highlight"}, + { 0x0010, "Saturation"}, + { 0x0011, "Sharpness"}, + { 0x0012, "FillLight"}, + { 0x0014, "ColorAdjustment"}, + { 0x0015, "AdjustmentMode"}, + { 0x0016, "Quality"}, + { 0x0017, "Firmware"}, + { 0x0018, "Software"}, + { 0x0019, "AutoBracket"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_KYOCERA = { + { 0x0001, "FormatThumbnail"}, + { 0x0E00, "PrintImageMatchingInfo"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_RICOH = { + { 0x0001, "MakerNoteDataType"}, + { 0x0002, "Version"}, + { 0x0E00, "PrintImageMatchingInfo"}, + { 0x2001, "RicohCameraInfoMakerNoteSubIFD"}, + TAG_TABLE_END +}; + +typedef enum mn_byte_order_t { + MN_ORDER_INTEL = 0, + MN_ORDER_MOTOROLA = 1, + MN_ORDER_NORMAL +} mn_byte_order_t; + +typedef enum mn_offset_mode_t { + MN_OFFSET_NORMAL, + MN_OFFSET_MAKER +#ifdef KALLE_0 + , MN_OFFSET_GUESS +#endif +} mn_offset_mode_t; + +typedef struct { + tag_table_type tag_table; + char * make; + char * model; + char * id_string; + int id_string_len; + int offset; + mn_byte_order_t byte_order; + mn_offset_mode_t offset_mode; +} maker_note_type; + +/* Remember to update PHP_MINFO if updated */ +static const maker_note_type maker_note_array[] = { + { tag_table_VND_CANON, "Canon", NULL, NULL, 0, 0, MN_ORDER_INTEL, MN_OFFSET_NORMAL}, + { tag_table_VND_CASIO, "CASIO", NULL, NULL, 0, 0, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + { tag_table_VND_FUJI, "FUJIFILM", NULL, "FUJIFILM\x0C\x00\x00\x00", 12, 12, MN_ORDER_INTEL, MN_OFFSET_MAKER}, + { tag_table_VND_NIKON, "NIKON", NULL, "Nikon\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_NIKON_990, "NIKON", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_OLYMPUS, "OLYMPUS OPTICAL CO.,LTD", NULL, "OLYMP\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SAMSUNG, "SAMSUNG", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_PANASONIC, "Panasonic", NULL, "Panasonic\x00\x00\x00", 12, 12, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_DJI, "DJI", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SONY, "SONY", NULL, "SONY DSC \x00\x00\x00", 12, 12, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_PENTAX, "PENTAX", NULL, "AOC\x00", 6, 6, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_MINOLTA, "Minolta, KONICA MINOLTA", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SIGMA, "SIGMA, FOVEON", NULL, "SIGMA\x00\x00\x00", 10, 10, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SIGMA, "SIGMA, FOVEON", NULL, "FOVEON\x00\x00\x00", 10, 10, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_KYOCERA, "KYOCERA, CONTAX", NULL, "KYOCERA \x00\x00\x00", 22, 22, MN_ORDER_NORMAL, MN_OFFSET_MAKER}, + { tag_table_VND_RICOH, "RICOH", NULL, "Ricoh", 5, 5, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + { tag_table_VND_RICOH, "RICOH", NULL, "RICOH", 5, 5, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + + /* These re-uses existing formats */ + { tag_table_VND_OLYMPUS, "AGFA", NULL, "AGFA \x00\x01", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_OLYMPUS, "EPSON", NULL, "EPSON\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL} +}; +/* }}} */ + +/* {{{ exif_get_tagname + Get headername for tag_num or NULL if not defined */ +static char * exif_get_tagname(int tag_num, char *ret, int len, tag_table_type tag_table) +{ + int i, t; + char tmp[32]; + + for (i = 0; (t = tag_table[i].Tag) != TAG_END_OF_LIST; i++) { + if (t == tag_num) { + if (ret && len) { + strlcpy(ret, tag_table[i].Desc, abs(len)); + if (len < 0) { + memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1); + ret[-len - 1] = '\0'; + } + return ret; + } + return tag_table[i].Desc; + } + } + + if (ret && len) { + snprintf(tmp, sizeof(tmp), "UndefinedTag:0x%04X", tag_num); + strlcpy(ret, tmp, abs(len)); + if (len < 0) { + memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1); + ret[-len - 1] = '\0'; + } + return ret; + } + return ""; +} +/* }}} */ + +/* {{{ exif_char_dump + * Do not use! This is a debug function... */ +#ifdef EXIF_DEBUG +static unsigned char* exif_char_dump(unsigned char * addr, int len, int offset) +{ + static unsigned char buf[4096+1]; + static unsigned char tmp[20]; + int c, i, p=0, n = 5+31; + + p += slprintf(buf+p, sizeof(buf)-p, "\nDump Len: %08X (%d)", len, len); + if (len) { + for(i=0; i=32 ? c : '.'; + tmp[(i%16)+1] = '\0'; + } else { + p += slprintf(buf+p, sizeof(buf)-p, " "); + } + if (i%16==15) { + p += slprintf(buf+p, sizeof(buf)-p, " %s", tmp); + if (i>=len) { + break; + } + } + } + } + buf[sizeof(buf)-1] = '\0'; + return buf; +} +#endif +/* }}} */ + +/* {{{ php_jpg_get16 + Get 16 bits motorola order (always) for jpeg header stuff. +*/ +static int php_jpg_get16(void *value) +{ + return (((uchar *)value)[0] << 8) | ((uchar *)value)[1]; +} +/* }}} */ + +/* {{{ php_ifd_get16u + * Convert a 16 bit unsigned value from file's native byte order */ +static int php_ifd_get16u(void *value, int motorola_intel) +{ + if (motorola_intel) { + return (((uchar *)value)[0] << 8) | ((uchar *)value)[1]; + } else { + return (((uchar *)value)[1] << 8) | ((uchar *)value)[0]; + } +} +/* }}} */ + +/* {{{ php_ifd_get16s + * Convert a 16 bit signed value from file's native byte order */ +static signed short php_ifd_get16s(void *value, int motorola_intel) +{ + return (signed short)php_ifd_get16u(value, motorola_intel); +} +/* }}} */ + +/* {{{ php_ifd_get32s + * Convert a 32 bit signed value from file's native byte order */ +static int php_ifd_get32s(void *value, int motorola_intel) +{ + if (motorola_intel) { + return (((char *)value)[0] << 24) + | (((uchar *)value)[1] << 16) + | (((uchar *)value)[2] << 8 ) + | (((uchar *)value)[3] ); + } else { + return (((char *)value)[3] << 24) + | (((uchar *)value)[2] << 16) + | (((uchar *)value)[1] << 8 ) + | (((uchar *)value)[0] ); + } +} +/* }}} */ + +/* {{{ php_ifd_get32u + * Write 32 bit unsigned value to data */ +static unsigned php_ifd_get32u(void *value, int motorola_intel) +{ + return (unsigned)php_ifd_get32s(value, motorola_intel) & 0xffffffff; +} +/* }}} */ + +/* {{{ php_ifd_set16u + * Write 16 bit unsigned value to data */ +static void php_ifd_set16u(char *data, unsigned int value, int motorola_intel) +{ + if (motorola_intel) { + data[0] = (value & 0xFF00) >> 8; + data[1] = (value & 0x00FF); + } else { + data[1] = (value & 0xFF00) >> 8; + data[0] = (value & 0x00FF); + } +} +/* }}} */ + +/* {{{ php_ifd_set32u + * Convert a 32 bit unsigned value from file's native byte order */ +static void php_ifd_set32u(char *data, size_t value, int motorola_intel) +{ + if (motorola_intel) { + data[0] = (value & 0xFF000000) >> 24; + data[1] = (value & 0x00FF0000) >> 16; + data[2] = (value & 0x0000FF00) >> 8; + data[3] = (value & 0x000000FF); + } else { + data[3] = (value & 0xFF000000) >> 24; + data[2] = (value & 0x00FF0000) >> 16; + data[1] = (value & 0x0000FF00) >> 8; + data[0] = (value & 0x000000FF); + } +} +/* }}} */ + +#ifdef EXIF_DEBUG +char * exif_dump_data(int *dump_free, int format, int components, int length, int motorola_intel, char *value_ptr) /* {{{ */ +{ + char *dump; + int len; + + *dump_free = 0; + if (format == TAG_FMT_STRING) { + return value_ptr ? value_ptr : ""; + } + if (format == TAG_FMT_UNDEFINED) { + return ""; + } + if (format == TAG_FMT_IFD) { + return ""; + } + if (format == TAG_FMT_SINGLE || format == TAG_FMT_DOUBLE) { + return ""; + } + *dump_free = 1; + if (components > 1) { + len = spprintf(&dump, 0, "(%d,%d) {", components, length); + } else { + len = spprintf(&dump, 0, "{"); + } + while(components > 0) { + switch(format) { + case TAG_FMT_BYTE: + case TAG_FMT_UNDEFINED: + case TAG_FMT_STRING: + case TAG_FMT_SBYTE: + dump = erealloc(dump, len + 4 + 1); + snprintf(dump + len, 4 + 1, "0x%02X", *value_ptr); + len += 4; + value_ptr++; + break; + case TAG_FMT_USHORT: + case TAG_FMT_SSHORT: + dump = erealloc(dump, len + 6 + 1); + snprintf(dump + len, 6 + 1, "0x%04X", php_ifd_get16s(value_ptr, motorola_intel)); + len += 6; + value_ptr += 2; + break; + case TAG_FMT_ULONG: + case TAG_FMT_SLONG: + dump = erealloc(dump, len + 6 + 1); + snprintf(dump + len, 6 + 1, "0x%04X", php_ifd_get32s(value_ptr, motorola_intel)); + len += 6; + value_ptr += 4; + break; + case TAG_FMT_URATIONAL: + case TAG_FMT_SRATIONAL: + dump = erealloc(dump, len + 13 + 1); + snprintf(dump + len, 13 + 1, "0x%04X/0x%04X", php_ifd_get32s(value_ptr, motorola_intel), php_ifd_get32s(value_ptr+4, motorola_intel)); + len += 13; + value_ptr += 8; + break; + } + if (components > 0) { + dump = erealloc(dump, len + 2 + 1); + snprintf(dump + len, 2 + 1, ", "); + len += 2; + components--; + } else{ + break; + } + } + dump = erealloc(dump, len + 1 + 1); + snprintf(dump + len, 1 + 1, "}"); + return dump; +} +/* }}} */ +#endif + +/* {{{ exif_convert_any_format + * Evaluate number, be it int, rational, or float from directory. */ +static double exif_convert_any_format(void *value, int format, int motorola_intel) +{ + int s_den; + unsigned u_den; + + switch(format) { + case TAG_FMT_SBYTE: return *(signed char *)value; + case TAG_FMT_BYTE: return *(uchar *)value; + + case TAG_FMT_USHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_ULONG: return php_ifd_get32u(value, motorola_intel); + + case TAG_FMT_URATIONAL: + u_den = php_ifd_get32u(4+(char *)value, motorola_intel); + if (u_den == 0) { + return 0; + } else { + return (double)php_ifd_get32u(value, motorola_intel) / u_den; + } + + case TAG_FMT_SRATIONAL: + s_den = php_ifd_get32s(4+(char *)value, motorola_intel); + if (s_den == 0) { + return 0; + } else { + return (double)php_ifd_get32s(value, motorola_intel) / s_den; + } + + case TAG_FMT_SSHORT: return (signed short)php_ifd_get16u(value, motorola_intel); + case TAG_FMT_SLONG: return php_ifd_get32s(value, motorola_intel); + + /* Not sure if this is correct (never seen float used in Exif format) */ + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type single"); +#endif + return (double)*(float *)value; + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return *(double *)value; + } + return 0; +} +/* }}} */ + +/* {{{ exif_rewrite_tag_format_to_unsigned + * Rewrite format tag so that it specifies an unsigned type for a tag */ +static int exif_rewrite_tag_format_to_unsigned(int format) +{ + switch(format) { + case TAG_FMT_SBYTE: return TAG_FMT_BYTE; + case TAG_FMT_SRATIONAL: return TAG_FMT_URATIONAL; + case TAG_FMT_SSHORT: return TAG_FMT_USHORT; + case TAG_FMT_SLONG: return TAG_FMT_ULONG; + } + return format; +} +/* }}} */ + +/* {{{ exif_convert_any_to_int + * Evaluate number, be it int, rational, or float from directory. */ +static size_t exif_convert_any_to_int(void *value, int format, int motorola_intel) +{ + int s_den; + unsigned u_den; + + switch(format) { + case TAG_FMT_SBYTE: return *(signed char *)value; + case TAG_FMT_BYTE: return *(uchar *)value; + + case TAG_FMT_USHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_ULONG: return php_ifd_get32u(value, motorola_intel); + + case TAG_FMT_URATIONAL: + u_den = php_ifd_get32u(4+(char *)value, motorola_intel); + if (u_den == 0) { + return 0; + } else { + return php_ifd_get32u(value, motorola_intel) / u_den; + } + + case TAG_FMT_SRATIONAL: + s_den = php_ifd_get32s(4+(char *)value, motorola_intel); + if (s_den == 0) { + return 0; + } else { + return (size_t)((double)php_ifd_get32s(value, motorola_intel) / s_den); + } + + case TAG_FMT_SSHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_SLONG: return php_ifd_get32s(value, motorola_intel); + + /* Not sure if this is correct (never seen float used in Exif format) */ + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type single"); +#endif + return (size_t)*(float *)value; + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return (size_t)*(double *)value; + } + return 0; +} +/* }}} */ + +/* {{{ struct image_info_value, image_info_list +*/ +#ifndef WORD +#define WORD unsigned short +#endif +#ifndef DWORD +#define DWORD unsigned int +#endif + +typedef struct { + int num; + int den; +} signed_rational; + +typedef struct { + unsigned int num; + unsigned int den; +} unsigned_rational; + +typedef union _image_info_value { + char *s; + unsigned u; + int i; + float f; + double d; + signed_rational sr; + unsigned_rational ur; + union _image_info_value *list; +} image_info_value; + +typedef struct { + WORD tag; + WORD format; + DWORD length; + DWORD dummy; /* value ptr of tiff directory entry */ + char *name; + image_info_value value; +} image_info_data; + +typedef struct { + int count; + image_info_data *list; +} image_info_list; +/* }}} */ + +/* {{{ exif_get_sectionname + Returns the name of a section +*/ +#define SECTION_FILE 0 +#define SECTION_COMPUTED 1 +#define SECTION_ANY_TAG 2 +#define SECTION_IFD0 3 +#define SECTION_THUMBNAIL 4 +#define SECTION_COMMENT 5 +#define SECTION_APP0 6 +#define SECTION_EXIF 7 +#define SECTION_FPIX 8 +#define SECTION_GPS 9 +#define SECTION_INTEROP 10 +#define SECTION_APP12 11 +#define SECTION_WINXP 12 +#define SECTION_MAKERNOTE 13 +#define SECTION_COUNT 14 + +#define FOUND_FILE (1<2) + sections[len-2] = '\0'; + return sections; +} +/* }}} */ + +/* {{{ struct image_info_type + This structure stores Exif header image elements in a simple manner + Used to store camera data as extracted from the various ways that it can be + stored in a nexif header +*/ + +typedef struct { + int type; + size_t size; + uchar *data; +} file_section; + +typedef struct { + int count; + file_section *list; +} file_section_list; + +typedef struct { + image_filetype filetype; + size_t width, height; + size_t size; + size_t offset; + char *data; +} thumbnail_data; + +typedef struct { + char *value; + size_t size; + int tag; +} xp_field_type; + +typedef struct { + int count; + xp_field_type *list; +} xp_field_list; + +/* This structure is used to store a section of a Jpeg file. */ +typedef struct { + php_stream *infile; + char *FileName; + time_t FileDateTime; + size_t FileSize; + image_filetype FileType; + int Height, Width; + int IsColor; + + char *make; + char *model; + + float ApertureFNumber; + float ExposureTime; + double FocalplaneUnits; + float CCDWidth; + double FocalplaneXRes; + size_t ExifImageWidth; + float FocalLength; + float Distance; + + int motorola_intel; /* 1 Motorola; 0 Intel */ + + char *UserComment; + int UserCommentLength; + char *UserCommentEncoding; + char *encode_unicode; + char *decode_unicode_be; + char *decode_unicode_le; + char *encode_jis; + char *decode_jis_be; + char *decode_jis_le; + char *Copyright;/* EXIF standard defines Copyright as " [ '\0' ] ['\0']" */ + char *CopyrightPhotographer; + char *CopyrightEditor; + + xp_field_list xp_fields; + + thumbnail_data Thumbnail; + /* other */ + int sections_found; /* FOUND_ */ + image_info_list info_list[SECTION_COUNT]; + /* for parsing */ + int read_thumbnail; + int read_all; + int ifd_nesting_level; + /* internal */ + file_section_list file; +} image_info_type; +/* }}} */ + +/* {{{ exif_error_docref */ +static void exif_error_docref(const char *docref EXIFERR_DC, const image_info_type *ImageInfo, int type, const char *format, ...) +{ + va_list args; + + va_start(args, format); +#ifdef EXIF_DEBUG + { + char *buf; + + spprintf(&buf, 0, "%s(%d): %s", _file, _line, format); + php_verror(docref, ImageInfo && ImageInfo->FileName ? ImageInfo->FileName:"", type, buf, args); + efree(buf); + } +#else + php_verror(docref, ImageInfo && ImageInfo->FileName ? ImageInfo->FileName:"", type, format, args); +#endif + va_end(args); +} +/* }}} */ + +/* {{{ jpeg_sof_info + */ +typedef struct { + int bits_per_sample; + size_t width; + size_t height; + int num_components; +} jpeg_sof_info; +/* }}} */ + +/* {{{ exif_file_sections_add + Add a file_section to image_info + returns the used block or -1. if size>0 and data == NULL buffer of size is allocated +*/ +static int exif_file_sections_add(image_info_type *ImageInfo, int type, size_t size, uchar *data) +{ + file_section *tmp; + int count = ImageInfo->file.count; + + tmp = safe_erealloc(ImageInfo->file.list, (count+1), sizeof(file_section), 0); + ImageInfo->file.list = tmp; + ImageInfo->file.list[count].type = 0xFFFF; + ImageInfo->file.list[count].data = NULL; + ImageInfo->file.list[count].size = 0; + ImageInfo->file.count = count+1; + if (!size) { + data = NULL; + } else if (data == NULL) { + data = safe_emalloc(size, 1, 0); + } + ImageInfo->file.list[count].type = type; + ImageInfo->file.list[count].data = data; + ImageInfo->file.list[count].size = size; + return count; +} +/* }}} */ + +/* {{{ exif_file_sections_realloc + Reallocate a file section returns 0 on success and -1 on failure +*/ +static int exif_file_sections_realloc(image_info_type *ImageInfo, int section_index, size_t size) +{ + void *tmp; + + /* This is not a malloc/realloc check. It is a plausibility check for the + * function parameters (requirements engineering). + */ + if (section_index >= ImageInfo->file.count) { + EXIF_ERRLOG_FSREALLOC(ImageInfo) + return -1; + } + tmp = safe_erealloc(ImageInfo->file.list[section_index].data, 1, size, 0); + ImageInfo->file.list[section_index].data = tmp; + ImageInfo->file.list[section_index].size = size; + return 0; +} +/* }}} */ + +/* {{{ exif_file_section_free + Discard all file_sections in ImageInfo +*/ +static int exif_file_sections_free(image_info_type *ImageInfo) +{ + int i; + + if (ImageInfo->file.count) { + for (i=0; ifile.count; i++) { + EFREE_IF(ImageInfo->file.list[i].data); + } + } + EFREE_IF(ImageInfo->file.list); + ImageInfo->file.count = 0; + return TRUE; +} +/* }}} */ + +/* {{{ exif_iif_add_value + Add a value to image_info +*/ +static void exif_iif_add_value(image_info_type *image_info, int section_index, char *name, int tag, int format, int length, void* value, int motorola_intel) +{ + size_t idex; + void *vptr; + image_info_value *info_value; + image_info_data *info_data; + image_info_data *list; + + if (length < 0) { + return; + } + + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + memset(info_data, 0, sizeof(image_info_data)); + info_data->tag = tag; + info_data->format = format; + info_data->length = length; + info_data->name = estrdup(name); + info_value = &info_data->value; + + switch (format) { + case TAG_FMT_STRING: + if (value) { + length = php_strnlen(value, length); + info_value->s = estrndup(value, length); + info_data->length = length; + } else { + info_data->length = 0; + info_value->s = estrdup(""); + } + break; + + default: + /* Standard says more types possible but skip them... + * but allow users to handle data if they know how to + * So not return but use type UNDEFINED + * return; + */ + info_data->tag = TAG_FMT_UNDEFINED;/* otherwise not freed from memory */ + case TAG_FMT_SBYTE: + case TAG_FMT_BYTE: + /* in contrast to strings bytes do not need to allocate buffer for NULL if length==0 */ + if (!length) + break; + case TAG_FMT_UNDEFINED: + if (value) { + if (tag == TAG_MAKER_NOTE) { + length = MIN(length, (int) strlen(value)); + } + + /* do not recompute length here */ + info_value->s = estrndup(value, length); + info_data->length = length; + } else { + info_data->length = 0; + info_value->s = estrdup(""); + } + break; + + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + case TAG_FMT_URATIONAL: + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + case TAG_FMT_SRATIONAL: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + if (length==0) { + break; + } else + if (length>1) { + info_value->list = safe_emalloc(length, sizeof(image_info_value), 0); + } else { + info_value = &info_data->value; + } + for (idex=0,vptr=value; idex<(size_t)length; idex++,vptr=(char *) vptr + php_tiff_bytes_per_format[format]) { + if (length>1) { + info_value = &info_data->value.list[idex]; + } + switch (format) { + case TAG_FMT_USHORT: + info_value->u = php_ifd_get16u(vptr, motorola_intel); + break; + + case TAG_FMT_ULONG: + info_value->u = php_ifd_get32u(vptr, motorola_intel); + break; + + case TAG_FMT_URATIONAL: + info_value->ur.num = php_ifd_get32u(vptr, motorola_intel); + info_value->ur.den = php_ifd_get32u(4+(char *)vptr, motorola_intel); + break; + + case TAG_FMT_SSHORT: + info_value->i = php_ifd_get16s(vptr, motorola_intel); + break; + + case TAG_FMT_SLONG: + info_value->i = php_ifd_get32s(vptr, motorola_intel); + break; + + case TAG_FMT_SRATIONAL: + info_value->sr.num = php_ifd_get32u(vptr, motorola_intel); + info_value->sr.den = php_ifd_get32u(4+(char *)vptr, motorola_intel); + break; + + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_WARNING, "Found value of type single"); +#endif + info_value->f = *(float *)value; + + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_WARNING, "Found value of type double"); +#endif + info_value->d = *(double *)value; + break; + } + } + } + image_info->sections_found |= 1<info_list[section_index].count++; +} +/* }}} */ + +/* {{{ exif_iif_add_tag + Add a tag from IFD to image_info +*/ +static void exif_iif_add_tag(image_info_type *image_info, int section_index, char *name, int tag, int format, size_t length, void* value) +{ + exif_iif_add_value(image_info, section_index, name, tag, format, (int)length, value, image_info->motorola_intel); +} +/* }}} */ + +/* {{{ exif_iif_add_int + Add an int value to image_info +*/ +static void exif_iif_add_int(image_info_type *image_info, int section_index, char *name, int value) +{ + image_info_data *info_data; + image_info_data *list; + + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_SLONG; + info_data->length = 1; + info_data->name = estrdup(name); + info_data->value.i = value; + image_info->sections_found |= 1<info_list[section_index].count++; +} +/* }}} */ + +/* {{{ exif_iif_add_str + Add a string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_str(image_info_type *image_info, int section_index, char *name, char *value) +{ + image_info_data *info_data; + image_info_data *list; + + if (value) { + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_STRING; + info_data->length = 1; + info_data->name = estrdup(name); + info_data->value.s = estrdup(value); + image_info->sections_found |= 1<info_list[section_index].count++; + } +} +/* }}} */ + +/* {{{ exif_iif_add_fmt + Add a format string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_fmt(image_info_type *image_info, int section_index, char *name, char *value, ...) +{ + char *tmp; + va_list arglist; + + va_start(arglist, value); + if (value) { + vspprintf(&tmp, 0, value, arglist); + exif_iif_add_str(image_info, section_index, name, tmp); + efree(tmp); + } + va_end(arglist); +} +/* }}} */ + +/* {{{ exif_iif_add_str + Add a string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_buffer(image_info_type *image_info, int section_index, char *name, int length, char *value) +{ + image_info_data *info_data; + image_info_data *list; + + if (value) { + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_UNDEFINED; + info_data->length = length; + info_data->name = estrdup(name); + info_data->value.s = safe_emalloc(length, 1, 1); + memcpy(info_data->value.s, value, length); + info_data->value.s[length] = 0; + image_info->sections_found |= 1<info_list[section_index].count++; + } +} +/* }}} */ + +/* {{{ exif_iif_free + Free memory allocated for image_info +*/ +static void exif_iif_free(image_info_type *image_info, int section_index) { + int i; + void *f; /* faster */ + + if (image_info->info_list[section_index].count) { + for (i=0; i < image_info->info_list[section_index].count; i++) { + if ((f=image_info->info_list[section_index].list[i].name) != NULL) { + efree(f); + } + switch(image_info->info_list[section_index].list[i].format) { + case TAG_FMT_SBYTE: + case TAG_FMT_BYTE: + /* in contrast to strings bytes do not need to allocate buffer for NULL if length==0 */ + if (image_info->info_list[section_index].list[i].length<1) + break; + default: + case TAG_FMT_UNDEFINED: + case TAG_FMT_STRING: + if ((f=image_info->info_list[section_index].list[i].value.s) != NULL) { + efree(f); + } + break; + + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + case TAG_FMT_URATIONAL: + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + case TAG_FMT_SRATIONAL: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + /* nothing to do here */ + if (image_info->info_list[section_index].list[i].length > 1) { + if ((f=image_info->info_list[section_index].list[i].value.list) != NULL) { + efree(f); + } + } + break; + } + } + } + EFREE_IF(image_info->info_list[section_index].list); +} +/* }}} */ + +/* {{{ add_assoc_image_info + * Add image_info to associative array value. */ +static void add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index) +{ + char buffer[64], *val, *name, uname[64]; + int i, ap, l, b, idx=0, unknown=0; +#ifdef EXIF_DEBUG + int info_tag; +#endif + image_info_value *info_value; + image_info_data *info_data; + zval tmpi, array; + +#ifdef EXIF_DEBUG +/* php_error_docref(NULL, E_NOTICE, "Adding %d infos from section %s", image_info->info_list[section_index].count, exif_get_sectionname(section_index));*/ +#endif + if (image_info->info_list[section_index].count) { + if (sub_array) { + array_init(&tmpi); + } else { + ZVAL_COPY_VALUE(&tmpi, value); + } + + for(i=0; iinfo_list[section_index].count; i++) { + info_data = &image_info->info_list[section_index].list[i]; +#ifdef EXIF_DEBUG + info_tag = info_data->tag; /* conversion */ +#endif + info_value = &info_data->value; + if (!(name = info_data->name)) { + snprintf(uname, sizeof(uname), "%d", unknown++); + name = uname; + } +#ifdef EXIF_DEBUG +/* php_error_docref(NULL, E_NOTICE, "Adding infos: tag(0x%04X,%12s,L=0x%04X): %s", info_tag, exif_get_tagname(info_tag, buffer, -12, exif_get_tag_table(section_index)), info_data->length, info_data->format==TAG_FMT_STRING?(info_value&&info_value->s?info_value->s:""):exif_get_tagformat(info_data->format));*/ +#endif + if (info_data->length==0) { + add_assoc_null(&tmpi, name); + } else { + switch (info_data->format) { + default: + /* Standard says more types possible but skip them... + * but allow users to handle data if they know how to + * So not return but use type UNDEFINED + * return; + */ + case TAG_FMT_BYTE: + case TAG_FMT_SBYTE: + case TAG_FMT_UNDEFINED: + if (!info_value->s) { + add_assoc_stringl(&tmpi, name, "", 0); + } else { + add_assoc_stringl(&tmpi, name, info_value->s, info_data->length); + } + break; + + case TAG_FMT_STRING: + if (!(val = info_value->s)) { + val = ""; + } + if (section_index==SECTION_COMMENT) { + add_index_string(&tmpi, idx++, val); + } else { + add_assoc_string(&tmpi, name, val); + } + break; + + case TAG_FMT_URATIONAL: + case TAG_FMT_SRATIONAL: + /*case TAG_FMT_BYTE: + case TAG_FMT_SBYTE:*/ + case TAG_FMT_USHORT: + case TAG_FMT_SSHORT: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + case TAG_FMT_ULONG: + case TAG_FMT_SLONG: + /* now the rest, first see if it becomes an array */ + if ((l = info_data->length) > 1) { + array_init(&array); + } + for(ap=0; ap1) { + info_value = &info_data->value.list[ap]; + } + switch (info_data->format) { + case TAG_FMT_BYTE: + if (l>1) { + info_value = &info_data->value; + for (b=0;bs[b])); + } + break; + } + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + if (l==1) { + add_assoc_long(&tmpi, name, (int)info_value->u); + } else { + add_index_long(&array, ap, (int)info_value->u); + } + break; + + case TAG_FMT_URATIONAL: + snprintf(buffer, sizeof(buffer), "%i/%i", info_value->ur.num, info_value->ur.den); + if (l==1) { + add_assoc_string(&tmpi, name, buffer); + } else { + add_index_string(&array, ap, buffer); + } + break; + + case TAG_FMT_SBYTE: + if (l>1) { + info_value = &info_data->value; + for (b=0;bs[b]); + } + break; + } + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + if (l==1) { + add_assoc_long(&tmpi, name, info_value->i); + } else { + add_index_long(&array, ap, info_value->i); + } + break; + + case TAG_FMT_SRATIONAL: + snprintf(buffer, sizeof(buffer), "%i/%i", info_value->sr.num, info_value->sr.den); + if (l==1) { + add_assoc_string(&tmpi, name, buffer); + } else { + add_index_string(&array, ap, buffer); + } + break; + + case TAG_FMT_SINGLE: + if (l==1) { + add_assoc_double(&tmpi, name, info_value->f); + } else { + add_index_double(&array, ap, info_value->f); + } + break; + + case TAG_FMT_DOUBLE: + if (l==1) { + add_assoc_double(&tmpi, name, info_value->d); + } else { + add_index_double(&array, ap, info_value->d); + } + break; + } + info_value = &info_data->value.list[ap]; + } + if (l>1) { + add_assoc_zval(&tmpi, name, &array); + } + break; + } + } + } + if (sub_array) { + add_assoc_zval(value, exif_get_sectionname(section_index), &tmpi); + } + } +} +/* }}} */ + +/* {{{ Markers + JPEG markers consist of one or more 0xFF bytes, followed by a marker + code byte (which is not an FF). Here are the marker codes of interest + in this program. (See jdmarker.c for a more complete list.) +*/ + +#define M_TEM 0x01 /* temp for arithmetic coding */ +#define M_RES 0x02 /* reserved */ +#define M_SOF0 0xC0 /* Start Of Frame N */ +#define M_SOF1 0xC1 /* N indicates which compression process */ +#define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */ +#define M_SOF3 0xC3 +#define M_DHT 0xC4 +#define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */ +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_JPEG 0x08 /* reserved for extensions */ +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_DAC 0xCC /* arithmetic table */ +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_RST0 0xD0 /* restart segment */ +#define M_RST1 0xD1 +#define M_RST2 0xD2 +#define M_RST3 0xD3 +#define M_RST4 0xD4 +#define M_RST5 0xD5 +#define M_RST6 0xD6 +#define M_RST7 0xD7 +#define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */ +#define M_EOI 0xD9 /* End Of Image (end of datastream) */ +#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */ +#define M_DQT 0xDB +#define M_DNL 0xDC +#define M_DRI 0xDD +#define M_DHP 0xDE +#define M_EXP 0xDF +#define M_APP0 0xE0 /* JPEG: 'JFIFF' AND (additional 'JFXX') */ +#define M_EXIF 0xE1 /* Exif Attribute Information */ +#define M_APP2 0xE2 /* Flash Pix Extension Data? */ +#define M_APP3 0xE3 +#define M_APP4 0xE4 +#define M_APP5 0xE5 +#define M_APP6 0xE6 +#define M_APP7 0xE7 +#define M_APP8 0xE8 +#define M_APP9 0xE9 +#define M_APP10 0xEA +#define M_APP11 0xEB +#define M_APP12 0xEC +#define M_APP13 0xED /* IPTC International Press Telecommunications Council */ +#define M_APP14 0xEE /* Software, Copyright? */ +#define M_APP15 0xEF +#define M_JPG0 0xF0 +#define M_JPG1 0xF1 +#define M_JPG2 0xF2 +#define M_JPG3 0xF3 +#define M_JPG4 0xF4 +#define M_JPG5 0xF5 +#define M_JPG6 0xF6 +#define M_JPG7 0xF7 +#define M_JPG8 0xF8 +#define M_JPG9 0xF9 +#define M_JPG10 0xFA +#define M_JPG11 0xFB +#define M_JPG12 0xFC +#define M_JPG13 0xFD +#define M_COM 0xFE /* COMment */ + +#define M_PSEUDO 0x123 /* Extra value. */ + +/* }}} */ + +/* {{{ jpeg2000 markers + */ +/* Markers x30 - x3F do not have a segment */ +/* Markers x00, x01, xFE, xC0 - xDF ISO/IEC 10918-1 -> M_ */ +/* Markers xF0 - xF7 ISO/IEC 10918-3 */ +/* Markers xF7 - xF8 ISO/IEC 14495-1 */ +/* XY=Main/Tile-header:(R:required, N:not_allowed, O:optional, L:last_marker) */ +#define JC_SOC 0x4F /* NN, Start of codestream */ +#define JC_SIZ 0x51 /* RN, Image and tile size */ +#define JC_COD 0x52 /* RO, Codeing style defaulte */ +#define JC_COC 0x53 /* OO, Coding style component */ +#define JC_TLM 0x55 /* ON, Tile part length main header */ +#define JC_PLM 0x57 /* ON, Packet length main header */ +#define JC_PLT 0x58 /* NO, Packet length tile part header */ +#define JC_QCD 0x5C /* RO, Quantization default */ +#define JC_QCC 0x5D /* OO, Quantization component */ +#define JC_RGN 0x5E /* OO, Region of interest */ +#define JC_POD 0x5F /* OO, Progression order default */ +#define JC_PPM 0x60 /* ON, Packed packet headers main header */ +#define JC_PPT 0x61 /* NO, Packet packet headers tile part header */ +#define JC_CME 0x64 /* OO, Comment: "LL E " E=0:binary, E=1:ascii */ +#define JC_SOT 0x90 /* NR, Start of tile */ +#define JC_SOP 0x91 /* NO, Start of packeter default */ +#define JC_EPH 0x92 /* NO, End of packet header */ +#define JC_SOD 0x93 /* NL, Start of data */ +#define JC_EOC 0xD9 /* NN, End of codestream */ +/* }}} */ + +/* {{{ exif_process_COM + Process a COM marker. + We want to print out the marker contents as legible text; + we must guard against random junk and varying newline representations. +*/ +static void exif_process_COM (image_info_type *image_info, char *value, size_t length) +{ + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_STRING, length-2, value+2); +} +/* }}} */ + +/* {{{ exif_process_CME + Process a CME marker. + We want to print out the marker contents as legible text; + we must guard against random junk and varying newline representations. +*/ +#ifdef EXIF_JPEG2000 +static void exif_process_CME (image_info_type *image_info, char *value, size_t length) +{ + if (length>3) { + switch(value[2]) { + case 0: + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_UNDEFINED, length, value); + break; + case 1: + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_STRING, length, value); + break; + default: + php_error_docref(NULL, E_NOTICE, "Undefined JPEG2000 comment encoding"); + break; + } + } else { + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_UNDEFINED, 0, NULL); + php_error_docref(NULL, E_NOTICE, "JPEG2000 comment section too small"); + } +} +#endif +/* }}} */ + +/* {{{ exif_process_SOFn + * Process a SOFn marker. This is useful for the image dimensions */ +static void exif_process_SOFn (uchar *Data, int marker, jpeg_sof_info *result) +{ +/* 0xFF SOSn SectLen(2) Bits(1) Height(2) Width(2) Channels(1) 3*Channels (1) */ + result->bits_per_sample = Data[2]; + result->height = php_jpg_get16(Data+3); + result->width = php_jpg_get16(Data+5); + result->num_components = Data[7]; + +/* switch (marker) { + case M_SOF0: process = "Baseline"; break; + case M_SOF1: process = "Extended sequential"; break; + case M_SOF2: process = "Progressive"; break; + case M_SOF3: process = "Lossless"; break; + case M_SOF5: process = "Differential sequential"; break; + case M_SOF6: process = "Differential progressive"; break; + case M_SOF7: process = "Differential lossless"; break; + case M_SOF9: process = "Extended sequential, arithmetic coding"; break; + case M_SOF10: process = "Progressive, arithmetic coding"; break; + case M_SOF11: process = "Lossless, arithmetic coding"; break; + case M_SOF13: process = "Differential sequential, arithmetic coding"; break; + case M_SOF14: process = "Differential progressive, arithmetic coding"; break; + case M_SOF15: process = "Differential lossless, arithmetic coding"; break; + default: process = "Unknown"; break; + }*/ +} +/* }}} */ + +/* forward declarations */ +static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo, char *dir_start, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int tag); +static int exif_process_IFD_TAG( image_info_type *ImageInfo, char *dir_entry, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int ReadNextIFD, tag_table_type tag_table); + +/* {{{ exif_get_markername + Get name of marker */ +#ifdef EXIF_DEBUG +static char * exif_get_markername(int marker) +{ + switch(marker) { + case 0xC0: return "SOF0"; + case 0xC1: return "SOF1"; + case 0xC2: return "SOF2"; + case 0xC3: return "SOF3"; + case 0xC4: return "DHT"; + case 0xC5: return "SOF5"; + case 0xC6: return "SOF6"; + case 0xC7: return "SOF7"; + case 0xC9: return "SOF9"; + case 0xCA: return "SOF10"; + case 0xCB: return "SOF11"; + case 0xCD: return "SOF13"; + case 0xCE: return "SOF14"; + case 0xCF: return "SOF15"; + case 0xD8: return "SOI"; + case 0xD9: return "EOI"; + case 0xDA: return "SOS"; + case 0xDB: return "DQT"; + case 0xDC: return "DNL"; + case 0xDD: return "DRI"; + case 0xDE: return "DHP"; + case 0xDF: return "EXP"; + case 0xE0: return "APP0"; + case 0xE1: return "EXIF"; + case 0xE2: return "FPIX"; + case 0xE3: return "APP3"; + case 0xE4: return "APP4"; + case 0xE5: return "APP5"; + case 0xE6: return "APP6"; + case 0xE7: return "APP7"; + case 0xE8: return "APP8"; + case 0xE9: return "APP9"; + case 0xEA: return "APP10"; + case 0xEB: return "APP11"; + case 0xEC: return "APP12"; + case 0xED: return "APP13"; + case 0xEE: return "APP14"; + case 0xEF: return "APP15"; + case 0xF0: return "JPG0"; + case 0xFD: return "JPG13"; + case 0xFE: return "COM"; + case 0x01: return "TEM"; + } + return "Unknown"; +} +#endif +/* }}} */ + +/* {{{ proto string exif_tagname(int index) + Get headername for index or false if not defined */ +PHP_FUNCTION(exif_tagname) +{ + zend_long tag; + char *szTemp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &tag) == FAILURE) { + return; + } + + szTemp = exif_get_tagname(tag, NULL, 0, tag_table_IFD); + + if (tag < 0 || !szTemp || !szTemp[0]) { + RETURN_FALSE; + } + + RETURN_STRING(szTemp) +} +/* }}} */ + +/* {{{ exif_ifd_make_value + * Create a value for an ifd from an info_data pointer */ +static void* exif_ifd_make_value(image_info_data *info_data, int motorola_intel) { + size_t byte_count; + char *value_ptr, *data_ptr; + size_t i; + + image_info_value *info_value; + + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; + value_ptr = safe_emalloc(max(byte_count, 4), 1, 0); + memset(value_ptr, 0, 4); + if (!info_data->length) { + return value_ptr; + } + if (info_data->format == TAG_FMT_UNDEFINED || info_data->format == TAG_FMT_STRING + || (byte_count>1 && (info_data->format == TAG_FMT_BYTE || info_data->format == TAG_FMT_SBYTE)) + ) { + memmove(value_ptr, info_data->value.s, byte_count); + return value_ptr; + } else if (info_data->format == TAG_FMT_BYTE) { + *value_ptr = info_data->value.u; + return value_ptr; + } else if (info_data->format == TAG_FMT_SBYTE) { + *value_ptr = info_data->value.i; + return value_ptr; + } else { + data_ptr = value_ptr; + for(i=0; ilength; i++) { + if (info_data->length==1) { + info_value = &info_data->value; + } else { + info_value = &info_data->value.list[i]; + } + switch(info_data->format) { + case TAG_FMT_USHORT: + php_ifd_set16u(data_ptr, info_value->u, motorola_intel); + data_ptr += 2; + break; + case TAG_FMT_ULONG: + php_ifd_set32u(data_ptr, info_value->u, motorola_intel); + data_ptr += 4; + break; + case TAG_FMT_SSHORT: + php_ifd_set16u(data_ptr, info_value->i, motorola_intel); + data_ptr += 2; + break; + case TAG_FMT_SLONG: + php_ifd_set32u(data_ptr, info_value->i, motorola_intel); + data_ptr += 4; + break; + case TAG_FMT_URATIONAL: + php_ifd_set32u(data_ptr, info_value->sr.num, motorola_intel); + php_ifd_set32u(data_ptr+4, info_value->sr.den, motorola_intel); + data_ptr += 8; + break; + case TAG_FMT_SRATIONAL: + php_ifd_set32u(data_ptr, info_value->ur.num, motorola_intel); + php_ifd_set32u(data_ptr+4, info_value->ur.den, motorola_intel); + data_ptr += 8; + break; + case TAG_FMT_SINGLE: + memmove(data_ptr, &info_value->f, 4); + data_ptr += 4; + break; + case TAG_FMT_DOUBLE: + memmove(data_ptr, &info_value->d, 8); + data_ptr += 8; + break; + } + } + } + return value_ptr; +} +/* }}} */ + +/* {{{ exif_thumbnail_build + * Check and build thumbnail */ +static void exif_thumbnail_build(image_info_type *ImageInfo) { + size_t new_size, new_move, new_value; + char *new_data; + void *value_ptr; + int i, byte_count; + image_info_list *info_list; + image_info_data *info_data; +#ifdef EXIF_DEBUG + char tagname[64]; +#endif + + if (!ImageInfo->read_thumbnail || !ImageInfo->Thumbnail.offset || !ImageInfo->Thumbnail.size) { + return; /* ignore this call */ + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: filetype = %d", ImageInfo->Thumbnail.filetype); +#endif + switch(ImageInfo->Thumbnail.filetype) { + default: + case IMAGE_FILETYPE_JPEG: + /* done */ + break; + case IMAGE_FILETYPE_TIFF_II: + case IMAGE_FILETYPE_TIFF_MM: + info_list = &ImageInfo->info_list[SECTION_THUMBNAIL]; + new_size = 8 + 2 + info_list->count * 12 + 4; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: size of signature + directory(%d): 0x%02X", info_list->count, new_size); +#endif + new_value= new_size; /* offset for ifd values outside ifd directory */ + for (i=0; icount; i++) { + info_data = &info_list->list[i]; + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; + if (byte_count > 4) { + new_size += byte_count; + } + } + new_move = new_size; + new_data = safe_erealloc(ImageInfo->Thumbnail.data, 1, ImageInfo->Thumbnail.size, new_size); + ImageInfo->Thumbnail.data = new_data; + memmove(ImageInfo->Thumbnail.data + new_move, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + ImageInfo->Thumbnail.size += new_size; + /* fill in data */ + if (ImageInfo->motorola_intel) { + memmove(new_data, "MM\x00\x2a\x00\x00\x00\x08", 8); + } else { + memmove(new_data, "II\x2a\x00\x08\x00\x00\x00", 8); + } + new_data += 8; + php_ifd_set16u(new_data, info_list->count, ImageInfo->motorola_intel); + new_data += 2; + for (i=0; icount; i++) { + info_data = &info_list->list[i]; + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: process tag(x%04X=%s): %s%s (%d bytes)", info_data->tag, exif_get_tagname(info_data->tag, tagname, -12, tag_table_IFD), (info_data->length>1)&&info_data->format!=TAG_FMT_UNDEFINED&&info_data->format!=TAG_FMT_STRING?"ARRAY OF ":"", exif_get_tagformat(info_data->format), byte_count); +#endif + if (info_data->tag==TAG_STRIP_OFFSETS || info_data->tag==TAG_JPEG_INTERCHANGE_FORMAT) { + php_ifd_set16u(new_data + 0, info_data->tag, ImageInfo->motorola_intel); + php_ifd_set16u(new_data + 2, TAG_FMT_ULONG, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 4, 1, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 8, new_move, ImageInfo->motorola_intel); + } else { + php_ifd_set16u(new_data + 0, info_data->tag, ImageInfo->motorola_intel); + php_ifd_set16u(new_data + 2, info_data->format, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 4, info_data->length, ImageInfo->motorola_intel); + value_ptr = exif_ifd_make_value(info_data, ImageInfo->motorola_intel); + if (byte_count <= 4) { + memmove(new_data+8, value_ptr, 4); + } else { + php_ifd_set32u(new_data+8, new_value, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: writing with value offset: 0x%04X + 0x%02X", new_value, byte_count); +#endif + memmove(ImageInfo->Thumbnail.data+new_value, value_ptr, byte_count); + new_value += byte_count; + } + efree(value_ptr); + } + new_data += 12; + } + memset(new_data, 0, 4); /* next ifd pointer */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: created"); +#endif + break; + } +} +/* }}} */ + +/* {{{ exif_thumbnail_extract + * Grab the thumbnail, corrected */ +static void exif_thumbnail_extract(image_info_type *ImageInfo, char *offset, size_t length) { + if (ImageInfo->Thumbnail.data) { + exif_error_docref("exif_read_data#error_mult_thumb" EXIFERR_CC, ImageInfo, E_WARNING, "Multiple possible thumbnails"); + return; /* Should not happen */ + } + if (!ImageInfo->read_thumbnail) { + return; /* ignore this call */ + } + /* according to exif2.1, the thumbnail is not supposed to be greater than 64K */ + if (ImageInfo->Thumbnail.size >= 65536 + || ImageInfo->Thumbnail.size <= 0 + || ImageInfo->Thumbnail.offset <= 0 + ) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Illegal thumbnail size/offset"); + return; + } + /* Check to make sure we are not going to go past the ExifLength */ + if ((ImageInfo->Thumbnail.offset + ImageInfo->Thumbnail.size) > length) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + return; + } + ImageInfo->Thumbnail.data = estrndup(offset + ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); + exif_thumbnail_build(ImageInfo); +} +/* }}} */ + +/* {{{ exif_process_undefined + * Copy a string/buffer in Exif header to a character string and return length of allocated buffer if any. */ +static int exif_process_undefined(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we have to copy NUL + * chars up to byte_count, we also have to add a single NUL character to + * force end of string. + * estrndup does not return length + */ + if (byte_count) { + (*result) = estrndup(value, byte_count); /* NULL @ byte_count!!! */ + return byte_count+1; + } + return 0; +} +/* }}} */ + +/* {{{ exif_process_string_raw + * Copy a string in Exif header to a character string returns length of allocated buffer if any. */ +static int exif_process_string_raw(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we have to copy NUL + * chars up to byte_count, we also have to add a single NUL character to + * force end of string. + */ + if (byte_count) { + (*result) = safe_emalloc(byte_count, 1, 1); + memcpy(*result, value, byte_count); + (*result)[byte_count] = '\0'; + return byte_count+1; + } + return 0; +} +/* }}} */ + +/* {{{ exif_process_string + * Copy a string in Exif header to a character string and return length of allocated buffer if any. + * In contrast to exif_process_string this function does always return a string buffer */ +static int exif_process_string(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we cannot use strlen to + * determin length of string and we cannot use strlcpy with len=byte_count+1 + * because then we might get into an EXCEPTION if we exceed an allocated + * memory page...so we use php_strnlen in conjunction with memcpy and add the NUL + * char. + * estrdup would sometimes allocate more memory and does not return length + */ + if ((byte_count=php_strnlen(value, byte_count)) > 0) { + return exif_process_undefined(result, value, byte_count); + } + (*result) = estrndup("", 1); /* force empty string */ + return byte_count+1; +} +/* }}} */ + +/* {{{ exif_process_user_comment + * Process UserComment in IFD. */ +static int exif_process_user_comment(image_info_type *ImageInfo, char **pszInfoPtr, char **pszEncoding, char *szValuePtr, int ByteCount) +{ + int a; + char *decode; + size_t len; + + *pszEncoding = NULL; + /* Copy the comment */ + if (ByteCount>=8) { + const zend_encoding *from, *to; + if (!memcmp(szValuePtr, "UNICODE\0", 8)) { + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + /* First try to detect BOM: ZERO WIDTH NOBREAK SPACE (FEFF 16) + * since we have no encoding support for the BOM yet we skip that. + */ + if (!memcmp(szValuePtr, "\xFE\xFF", 2)) { + decode = "UCS-2BE"; + szValuePtr = szValuePtr+2; + ByteCount -= 2; + } else if (!memcmp(szValuePtr, "\xFF\xFE", 2)) { + decode = "UCS-2LE"; + szValuePtr = szValuePtr+2; + ByteCount -= 2; + } else if (ImageInfo->motorola_intel) { + decode = ImageInfo->decode_unicode_be; + } else { + decode = ImageInfo->decode_unicode_le; + } + to = zend_multibyte_fetch_encoding(ImageInfo->encode_unicode); + from = zend_multibyte_fetch_encoding(decode); + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + if (!to || !from || zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + to, + from) == (size_t)-1) { + len = exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount); + } + return len; + } else if (!memcmp(szValuePtr, "ASCII\0\0\0", 8)) { + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + } else if (!memcmp(szValuePtr, "JIS\0\0\0\0\0", 8)) { + /* JIS should be tanslated to MB or we leave it to the user - leave it to the user */ + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + to = zend_multibyte_fetch_encoding(ImageInfo->encode_jis); + from = zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_jis_be : ImageInfo->decode_jis_le); + if (!to || !from || zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + to, + from) == (size_t)-1) { + len = exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount); + } + return len; + } else if (!memcmp(szValuePtr, "\0\0\0\0\0\0\0\0", 8)) { + /* 8 NULL means undefined and should be ASCII... */ + *pszEncoding = estrdup("UNDEFINED"); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + } + } + + /* Olympus has this padded with trailing spaces. Remove these first. */ + if (ByteCount>0) { + for (a=ByteCount-1;a && szValuePtr[a]==' ';a--) { + (szValuePtr)[a] = '\0'; + } + } + + /* normal text without encoding */ + exif_process_string(pszInfoPtr, szValuePtr, ByteCount); + return strlen(*pszInfoPtr); +} +/* }}} */ + +/* {{{ exif_process_unicode + * Process unicode field in IFD. */ +static int exif_process_unicode(image_info_type *ImageInfo, xp_field_type *xp_field, int tag, char *szValuePtr, int ByteCount) +{ + xp_field->tag = tag; + xp_field->value = NULL; + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + if (zend_multibyte_encoding_converter( + (unsigned char**)&xp_field->value, + &xp_field->size, + (unsigned char*)szValuePtr, + ByteCount, + zend_multibyte_fetch_encoding(ImageInfo->encode_unicode), + zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_unicode_be : ImageInfo->decode_unicode_le) + ) == (size_t)-1) { + xp_field->size = exif_process_string_raw(&xp_field->value, szValuePtr, ByteCount); + } + return xp_field->size; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_MAKERNOTE + * Process nested IFDs directories in Maker Note. */ +static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * value_ptr, int value_len, char *offset_base, size_t IFDlength, size_t displacement) +{ + size_t i; + int de, section_index = SECTION_MAKERNOTE; + int NumDirEntries, old_motorola_intel; +#ifdef KALLE_0 + int offset_diff; +#endif + const maker_note_type *maker_note; + char *dir_start; + + for (i=0; i<=sizeof(maker_note_array)/sizeof(maker_note_type); i++) { + if (i==sizeof(maker_note_array)/sizeof(maker_note_type)) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "No maker note data found. Detected maker: %s (length = %d)", ImageInfo->make, strlen(ImageInfo->make)); +#endif + /* unknown manufacturer, not an error, use it as a string */ + return TRUE; + } + + maker_note = maker_note_array+i; + + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "check (%s,%s)", maker_note->make?maker_note->make:"", maker_note->model?maker_note->model:"");*/ + if (maker_note->make && (!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make))) + continue; + if (maker_note->model && (!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) + continue; + if (maker_note->id_string && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) + continue; + break; + } + + if (maker_note->offset >= value_len) { + /* Do not go past the value end */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X offset 0x%04X", value_len, maker_note->offset); + return FALSE; + } + + dir_start = value_ptr + maker_note->offset; + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process %s @x%04X + 0x%04X=%d: %s", exif_get_sectionname(section_index), (int)dir_start-(int)offset_base+maker_note->offset+displacement, value_len, value_len, exif_char_dump(value_ptr, value_len, (int)dir_start-(int)offset_base+maker_note->offset+displacement)); +#endif + + ImageInfo->sections_found |= FOUND_MAKERNOTE; + + old_motorola_intel = ImageInfo->motorola_intel; + switch (maker_note->byte_order) { + case MN_ORDER_INTEL: + ImageInfo->motorola_intel = 0; + break; + case MN_ORDER_MOTOROLA: + ImageInfo->motorola_intel = 1; + break; + default: + case MN_ORDER_NORMAL: + break; + } + + NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel); + + switch (maker_note->offset_mode) { + case MN_OFFSET_MAKER: + offset_base = value_ptr; + break; +#ifdef KALLE_0 + case MN_OFFSET_GUESS: + if (maker_note->offset + 10 + 4 >= value_len) { + /* Can not read dir_start+10 since it's beyond value end */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X", value_len); + return FALSE; + } + offset_diff = 2 + NumDirEntries*12 + 4 - php_ifd_get32u(dir_start+10, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Using automatic offset correction: 0x%04X", ((int)dir_start-(int)offset_base+maker_note->offset+displacement) + offset_diff); +#endif + if (offset_diff < 0 || offset_diff >= value_len ) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data bad offset: 0x%04X length 0x%04X", offset_diff, value_len); + return FALSE; + } + offset_base = value_ptr + offset_diff; + break; +#endif + default: + case MN_OFFSET_NORMAL: + break; + } + + if ((2+NumDirEntries*12) > value_len) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: 2 + 0x%04X*12 = 0x%04X > 0x%04X", NumDirEntries, 2+NumDirEntries*12, value_len); + return FALSE; + } + + for (de=0;detag_table)) { + return FALSE; + } + } + ImageInfo->motorola_intel = old_motorola_intel; +/* NextDirOffset (must be NULL) = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel);*/ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Subsection %s done", exif_get_sectionname(SECTION_MAKERNOTE)); +#endif + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_IFD_TAG + * Process one of the nested IFDs directories. */ +static int exif_process_IFD_TAG(image_info_type *ImageInfo, char *dir_entry, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int ReadNextIFD, tag_table_type tag_table) +{ + size_t length; + int tag, format, components; + char *value_ptr, tagname[64], cbuf[32], *outside=NULL; + size_t byte_count, offset_val, fpos, fgot; + int64_t byte_count_signed; + xp_field_type *tmp_xp; +#ifdef EXIF_DEBUG + char *dump_data; + int dump_free; +#endif /* EXIF_DEBUG */ + + /* Protect against corrupt headers */ + if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "corrupt EXIF header: maximum directory nesting level reached"); + return FALSE; + } + ImageInfo->ifd_nesting_level++; + + tag = php_ifd_get16u(dir_entry, ImageInfo->motorola_intel); + format = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + components = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel); + + if (!format || format > NUM_FORMATS) { + /* (-1) catches illegal zero case as unsigned underflows to positive large. */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal format code 0x%04X, suppose BYTE", tag, exif_get_tagname(tag, tagname, -12, tag_table), format); + format = TAG_FMT_BYTE; + /*return TRUE;*/ + } + + if (components <= 0) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal components(%d)", tag, exif_get_tagname(tag, tagname, -12, tag_table), components); + return FALSE; + } + + byte_count_signed = (int64_t)components * php_tiff_bytes_per_format[format]; + + if (byte_count_signed < 0 || (byte_count_signed > INT32_MAX)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal byte_count", tag, exif_get_tagname(tag, tagname, -12, tag_table)); + return FALSE; + } + + byte_count = (size_t)byte_count_signed; + + if (byte_count > 4) { + offset_val = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + /* If its bigger than 4 bytes, the dir entry contains an offset. */ + value_ptr = offset_base+offset_val; + /* + dir_entry is ImageInfo->file.list[sn].data+2+i*12 + offset_base is ImageInfo->file.list[sn].data-dir_offset + dir_entry - offset_base is dir_offset+2+i*12 + */ + if (byte_count > IFDlength || offset_val > IFDlength-byte_count || value_ptr < dir_entry || offset_val < (size_t)(dir_entry-offset_base)) { + /* It is important to check for IMAGE_FILETYPE_TIFF + * JPEG does not use absolute pointers instead its pointers are + * relative to the start of the TIFF header in APP1 section. */ + if (byte_count > ImageInfo->FileSize || offset_val>ImageInfo->FileSize-byte_count || (ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II && ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM && ImageInfo->FileType!=IMAGE_FILETYPE_JPEG)) { + if (value_ptr < dir_entry) { + /* we can read this if offset_val > 0 */ + /* some files have their values in other parts of the file */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04X < x%04X)", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val, dir_entry); + } else { + /* this is for sure not allowed */ + /* exception are IFD pointers */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04X + x%04X = x%04X > x%04X)", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val, byte_count, offset_val+byte_count, IFDlength); + } + return FALSE; + } + if (byte_count>sizeof(cbuf)) { + /* mark as outside range and get buffer */ + value_ptr = safe_emalloc(byte_count, 1, 0); + outside = value_ptr; + } else { + /* In most cases we only access a small range so + * it is faster to use a static buffer there + * BUT it offers also the possibility to have + * pointers read without the need to free them + * explicitley before returning. */ + memset(&cbuf, 0, sizeof(cbuf)); + value_ptr = cbuf; + } + + fpos = php_stream_tell(ImageInfo->infile); + php_stream_seek(ImageInfo->infile, displacement+offset_val, SEEK_SET); + fgot = php_stream_tell(ImageInfo->infile); + if (fgot!=displacement+offset_val) { + EFREE_IF(outside); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val); + return FALSE; + } + fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count); + php_stream_seek(ImageInfo->infile, fpos, SEEK_SET); + if (fgotsections_found |= FOUND_ANY_TAG; +#ifdef EXIF_DEBUG + dump_data = exif_dump_data(&dump_free, format, components, length, ImageInfo->motorola_intel, value_ptr); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process tag(x%04X=%s,@x%04X + x%04X(=%d)): %s%s %s", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val+displacement, byte_count, byte_count, (components>1)&&format!=TAG_FMT_UNDEFINED&&format!=TAG_FMT_STRING?"ARRAY OF ":"", exif_get_tagformat(format), dump_data); + if (dump_free) { + efree(dump_data); + } +#endif + + if (section_index==SECTION_THUMBNAIL) { + if (!ImageInfo->Thumbnail.data) { + switch(tag) { + case TAG_IMAGEWIDTH: + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->Thumbnail.width = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Thumbnail.height = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_STRIP_OFFSETS: + case TAG_JPEG_INTERCHANGE_FORMAT: + /* accept both formats */ + ImageInfo->Thumbnail.offset = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_STRIP_BYTE_COUNTS: + if (ImageInfo->FileType == IMAGE_FILETYPE_TIFF_II || ImageInfo->FileType == IMAGE_FILETYPE_TIFF_MM) { + ImageInfo->Thumbnail.filetype = ImageInfo->FileType; + } else { + /* motorola is easier to read */ + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_TIFF_MM; + } + ImageInfo->Thumbnail.size = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_JPEG_INTERCHANGE_FORMAT_LEN: + if (ImageInfo->Thumbnail.filetype == IMAGE_FILETYPE_UNKNOWN) { + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_JPEG; + ImageInfo->Thumbnail.size = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + } + break; + } + } + } else { + if (section_index==SECTION_IFD0 || section_index==SECTION_EXIF) + switch(tag) { + case TAG_COPYRIGHT: + /* check for " NUL NUL" */ + if (byte_count>1 && (length=php_strnlen(value_ptr, byte_count)) > 0) { + if (lengthCopyrightPhotographer = estrdup(value_ptr); + ImageInfo->CopyrightEditor = estrndup(value_ptr+length+1, byte_count-length-1); + spprintf(&ImageInfo->Copyright, 0, "%s, %s", ImageInfo->CopyrightPhotographer, ImageInfo->CopyrightEditor); + /* format = TAG_FMT_UNDEFINED; this musn't be ASCII */ + /* but we are not supposed to change this */ + /* keep in mind that image_info does not store editor value */ + } else { + ImageInfo->Copyright = estrndup(value_ptr, byte_count); + } + } + break; + + case TAG_USERCOMMENT: + ImageInfo->UserCommentLength = exif_process_user_comment(ImageInfo, &(ImageInfo->UserComment), &(ImageInfo->UserCommentEncoding), value_ptr, byte_count); + break; + + case TAG_XP_TITLE: + case TAG_XP_COMMENTS: + case TAG_XP_AUTHOR: + case TAG_XP_KEYWORDS: + case TAG_XP_SUBJECT: + tmp_xp = (xp_field_type*)safe_erealloc(ImageInfo->xp_fields.list, (ImageInfo->xp_fields.count+1), sizeof(xp_field_type), 0); + ImageInfo->sections_found |= FOUND_WINXP; + ImageInfo->xp_fields.list = tmp_xp; + ImageInfo->xp_fields.count++; + exif_process_unicode(ImageInfo, &(ImageInfo->xp_fields.list[ImageInfo->xp_fields.count-1]), tag, value_ptr, byte_count); + break; + + case TAG_FNUMBER: + /* Simplest way of expressing aperture, so I trust it the most. + (overwrite previously computed value if there is one) */ + ImageInfo->ApertureFNumber = (float)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_APERTURE: + case TAG_MAX_APERTURE: + /* More relevant info always comes earlier, so only use this field if we don't + have appropriate aperture information yet. */ + if (ImageInfo->ApertureFNumber == 0) { + ImageInfo->ApertureFNumber + = (float)exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2)*0.5); + } + break; + + case TAG_SHUTTERSPEED: + /* More complicated way of expressing exposure time, so only use + this value if we don't already have it from somewhere else. + SHUTTERSPEED comes after EXPOSURE TIME + */ + if (ImageInfo->ExposureTime == 0) { + ImageInfo->ExposureTime + = (float)(1/exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2))); + } + break; + case TAG_EXPOSURETIME: + ImageInfo->ExposureTime = -1; + break; + + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->ExifImageWidth = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_FOCALPLANE_X_RES: + ImageInfo->FocalplaneXRes = exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_SUBJECT_DISTANCE: + /* Inidcates the distacne the autofocus camera is focused to. + Tends to be less accurate as distance increases. */ + ImageInfo->Distance = (float)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_FOCALPLANE_RESOLUTION_UNIT: + switch((int)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)) { + case 1: ImageInfo->FocalplaneUnits = 25.4; break; /* inch */ + case 2: + /* According to the information I was using, 2 measn meters. + But looking at the Cannon powershot's files, inches is the only + sensible value. */ + ImageInfo->FocalplaneUnits = 25.4; + break; + + case 3: ImageInfo->FocalplaneUnits = 10; break; /* centimeter */ + case 4: ImageInfo->FocalplaneUnits = 1; break; /* milimeter */ + case 5: ImageInfo->FocalplaneUnits = .001; break; /* micrometer */ + } + break; + + case TAG_SUB_IFD: + if (format==TAG_FMT_IFD) { + /* If this is called we are either in a TIFFs thumbnail or a JPEG where we cannot handle it */ + /* TIFF thumbnail: our data structure cannot store a thumbnail of a thumbnail */ + /* JPEG do we have the data area and what to do with it */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Skip SUB IFD"); + } + break; + + case TAG_MAKE: + ImageInfo->make = estrndup(value_ptr, byte_count); + break; + case TAG_MODEL: + ImageInfo->model = estrndup(value_ptr, byte_count); + break; + + case TAG_MAKER_NOTE: + if (!exif_process_IFD_in_MAKERNOTE(ImageInfo, value_ptr, byte_count, offset_base, IFDlength, displacement)) { + EFREE_IF(outside); + return FALSE; + } + break; + + case TAG_EXIF_IFD_POINTER: + case TAG_GPS_IFD_POINTER: + case TAG_INTEROP_IFD_POINTER: + if (ReadNextIFD) { + char *Subdir_start; + int sub_section_index = 0; + switch(tag) { + case TAG_EXIF_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found EXIF"); +#endif + ImageInfo->sections_found |= FOUND_EXIF; + sub_section_index = SECTION_EXIF; + break; + case TAG_GPS_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found GPS"); +#endif + ImageInfo->sections_found |= FOUND_GPS; + sub_section_index = SECTION_GPS; + break; + case TAG_INTEROP_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found INTEROPERABILITY"); +#endif + ImageInfo->sections_found |= FOUND_INTEROP; + sub_section_index = SECTION_INTEROP; + break; + } + Subdir_start = offset_base + php_ifd_get32u(value_ptr, ImageInfo->motorola_intel); + if (Subdir_start < offset_base || Subdir_start > offset_base+IFDlength) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD Pointer"); + return FALSE; + } + if (!exif_process_IFD_in_JPEG(ImageInfo, Subdir_start, offset_base, IFDlength, displacement, sub_section_index, tag)) { + return FALSE; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Subsection %s done", exif_get_sectionname(sub_section_index)); +#endif + } + } + } + exif_iif_add_tag(ImageInfo, section_index, exif_get_tagname(tag, tagname, sizeof(tagname), tag_table), tag, format, components, value_ptr); + EFREE_IF(outside); + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_JPEG + * Process one of the nested IFDs directories. */ +static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo, char *dir_start, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int tag) +{ + int de; + int NumDirEntries; + int NextDirOffset = 0; + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process %s (x%04X(=%d))", exif_get_sectionname(section_index), IFDlength, IFDlength); +#endif + + ImageInfo->sections_found |= FOUND_IFD0; + + if ((dir_start + 2) > (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + + NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel); + + if ((dir_start+2+NumDirEntries*12) > (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: x%04X + 2 + x%04X*12 = x%04X > x%04X", (int)((size_t)dir_start+2-(size_t)offset_base), NumDirEntries, (int)((size_t)dir_start+2+NumDirEntries*12-(size_t)offset_base), IFDlength); + return FALSE; + } + + for (de=0;de (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + + if (tag != TAG_EXIF_IFD_POINTER && tag != TAG_GPS_IFD_POINTER) { + NextDirOffset = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel); + } + + if (NextDirOffset) { + /* the next line seems false but here IFDlength means length of all IFDs */ + if (offset_base + NextDirOffset < offset_base || offset_base + NextDirOffset > offset_base+IFDlength) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD offset"); + return FALSE; + } + /* That is the IFD for the first thumbnail */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Expect next IFD to be thumbnail"); +#endif + if (exif_process_IFD_in_JPEG(ImageInfo, offset_base + NextDirOffset, offset_base, IFDlength, displacement, SECTION_THUMBNAIL, 0)) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail size: 0x%04X", ImageInfo->Thumbnail.size); +#endif + if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN + && ImageInfo->Thumbnail.size + && ImageInfo->Thumbnail.offset + && ImageInfo->read_thumbnail + ) { + exif_thumbnail_extract(ImageInfo, offset_base, IFDlength); + } + return TRUE; + } else { + return FALSE; + } + } + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_TIFF_in_JPEG + Process a TIFF header in a JPEG file +*/ +static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement) +{ + unsigned exif_value_2a, offset_of_ifd; + + /* set the thumbnail stuff to nothing so we can test to see if they get set up */ + if (memcmp(CharBuf, "II", 2) == 0) { + ImageInfo->motorola_intel = 0; + } else if (memcmp(CharBuf, "MM", 2) == 0) { + ImageInfo->motorola_intel = 1; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF alignment marker"); + return; + } + + /* Check the next two values for correctness. */ + if (length < 8) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + exif_value_2a = php_ifd_get16u(CharBuf+2, ImageInfo->motorola_intel); + offset_of_ifd = php_ifd_get32u(CharBuf+4, ImageInfo->motorola_intel); + if (exif_value_2a != 0x2a || offset_of_ifd < 0x08) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + if (offset_of_ifd > length) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid IFD start"); + return; + } + + ImageInfo->sections_found |= FOUND_IFD0; + /* First directory starts at offset 8. Offsets starts at 0. */ + exif_process_IFD_in_JPEG(ImageInfo, CharBuf+offset_of_ifd, CharBuf, length/*-14*/, displacement, SECTION_IFD0, 0); + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process TIFF in JPEG done"); +#endif + + /* Compute the CCD width, in milimeters. */ + if (ImageInfo->FocalplaneXRes != 0) { + ImageInfo->CCDWidth = (float)(ImageInfo->ExifImageWidth * ImageInfo->FocalplaneUnits / ImageInfo->FocalplaneXRes); + } +} +/* }}} */ + +/* {{{ exif_process_APP1 + Process an JPEG APP1 block marker + Describes all the drivel that most digital cameras include... +*/ +static void exif_process_APP1(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement) +{ + /* Check the APP1 for Exif Identifier Code */ + static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + if (length <= 8 || memcmp(CharBuf+2, ExifHeader, 6)) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Incorrect APP1 Exif Identifier Code"); + return; + } + exif_process_TIFF_in_JPEG(ImageInfo, CharBuf + 8, length - 8, displacement+8); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process APP1/EXIF done"); +#endif +} +/* }}} */ + +/* {{{ exif_process_APP12 + Process an JPEG APP12 block marker used by OLYMPUS +*/ +static void exif_process_APP12(image_info_type *ImageInfo, char *buffer, size_t length) +{ + size_t l1, l2=0; + + if ((l1 = php_strnlen(buffer+2, length-2)) > 0) { + exif_iif_add_tag(ImageInfo, SECTION_APP12, "Company", TAG_NONE, TAG_FMT_STRING, l1, buffer+2); + if (length > 2+l1+1) { + l2 = php_strnlen(buffer+2+l1+1, length-2-l1-1); + exif_iif_add_tag(ImageInfo, SECTION_APP12, "Info", TAG_NONE, TAG_FMT_STRING, l2, buffer+2+l1+1); + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process section APP12 with l1=%d, l2=%d done", l1, l2); +#endif +} +/* }}} */ + +/* {{{ exif_scan_JPEG_header + * Parse the marker stream until SOS or EOI is seen; */ +static int exif_scan_JPEG_header(image_info_type *ImageInfo) +{ + int section, sn; + int marker = 0, last_marker = M_PSEUDO, comment_correction=1; + unsigned int ll, lh; + uchar *Data; + size_t fpos, size, got, itemlen; + jpeg_sof_info sof_info; + + for(section=0;;section++) { +#ifdef EXIF_DEBUG + fpos = php_stream_tell(ImageInfo->infile); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Needing section %d @ 0x%08X", ImageInfo->file.count, fpos); +#endif + + /* get marker byte, swallowing possible padding */ + /* some software does not count the length bytes of COM section */ + /* one company doing so is very much envolved in JPEG... so we accept too */ + if (last_marker==M_COM && comment_correction) { + comment_correction = 2; + } + do { + if ((marker = php_stream_getc(ImageInfo->infile)) == EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + if (last_marker==M_COM && comment_correction>0) { + if (marker!=0xFF) { + marker = 0xff; + comment_correction--; + } else { + last_marker = M_PSEUDO; /* stop skipping 0 for M_COM */ + } + } + } while (marker == 0xff); + if (last_marker==M_COM && !comment_correction) { + exif_error_docref("exif_read_data#error_mcom" EXIFERR_CC, ImageInfo, E_NOTICE, "Image has corrupt COM section: some software set wrong length information"); + } + if (last_marker==M_COM && comment_correction) + return M_EOI; /* ah illegal: char after COM section not 0xFF */ + + fpos = php_stream_tell(ImageInfo->infile); + + if (marker == 0xff) { + /* 0xff is legal padding, but if we get that many, something's wrong. */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "To many padding bytes"); + return FALSE; + } + + /* Read the length of the section. */ + if ((lh = php_stream_getc(ImageInfo->infile)) == (unsigned int)EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + if ((ll = php_stream_getc(ImageInfo->infile)) == (unsigned int)EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + + itemlen = (lh << 8) | ll; + + if (itemlen < 2) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s, Section length: 0x%02X%02X", EXIF_ERROR_CORRUPT, lh, ll); +#else + EXIF_ERRLOG_CORRUPT(ImageInfo) +#endif + return FALSE; + } + + sn = exif_file_sections_add(ImageInfo, marker, itemlen+1, NULL); + Data = ImageInfo->file.list[sn].data; + + /* Store first two pre-read bytes. */ + Data[0] = (uchar)lh; + Data[1] = (uchar)ll; + + got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ + if (got != itemlen-2) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2); + return FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process section(x%02X=%s) @ x%04X + x%04X(=%d)", marker, exif_get_markername(marker), fpos, itemlen, itemlen); +#endif + switch(marker) { + case M_SOS: /* stop before hitting compressed data */ + /* If reading entire image is requested, read the rest of the data. */ + if (ImageInfo->read_all) { + /* Determine how much file is left. */ + fpos = php_stream_tell(ImageInfo->infile); + size = ImageInfo->FileSize - fpos; + sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL); + Data = ImageInfo->file.list[sn].data; + got = php_stream_read(ImageInfo->infile, (char*)Data, size); + if (got != size) { + EXIF_ERRLOG_FILEEOF(ImageInfo) + return FALSE; + } + } + return TRUE; + + case M_EOI: /* in case it's a tables-only JPEG stream */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "No image in jpeg!"); + return (ImageInfo->sections_found&(~FOUND_COMPUTED)) ? TRUE : FALSE; + + case M_COM: /* Comment section */ + exif_process_COM(ImageInfo, (char *)Data, itemlen); + break; + + case M_EXIF: + if (!(ImageInfo->sections_found&FOUND_IFD0)) { + /*ImageInfo->sections_found |= FOUND_EXIF;*/ + /* Seen files from some 'U-lead' software with Vivitar scanner + that uses marker 31 later in the file (no clue what for!) */ + exif_process_APP1(ImageInfo, (char *)Data, itemlen, fpos); + } + break; + + case M_APP12: + exif_process_APP12(ImageInfo, (char *)Data, itemlen); + break; + + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + if ((itemlen - 2) < 6) { + return FALSE; + } + + exif_process_SOFn(Data, marker, &sof_info); + ImageInfo->Width = sof_info.width; + ImageInfo->Height = sof_info.height; + if (sof_info.num_components == 3) { + ImageInfo->IsColor = 1; + } else { + ImageInfo->IsColor = 0; + } + break; + default: + /* skip any other marker silently. */ + break; + } + + /* keep track of last marker */ + last_marker = marker; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Done"); +#endif + return TRUE; +} +/* }}} */ + +/* {{{ exif_scan_thumbnail + * scan JPEG in thumbnail (memory) */ +static int exif_scan_thumbnail(image_info_type *ImageInfo) +{ + uchar c, *data = (uchar*)ImageInfo->Thumbnail.data; + int n, marker; + size_t length=2, pos=0; + jpeg_sof_info sof_info; + + if (!data) { + return FALSE; /* nothing to do here */ + } + if (memcmp(data, "\xFF\xD8\xFF", 3)) { + if (!ImageInfo->Thumbnail.width && !ImageInfo->Thumbnail.height) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Thumbnail is not a JPEG image"); + } + return FALSE; + } + for (;;) { + pos += length; + if (pos>=ImageInfo->Thumbnail.size) + return FALSE; + c = data[pos++]; + if (pos>=ImageInfo->Thumbnail.size) + return FALSE; + if (c != 0xFF) { + return FALSE; + } + n = 8; + while ((c = data[pos++]) == 0xFF && n--) { + if (pos+3>=ImageInfo->Thumbnail.size) + return FALSE; + /* +3 = pos++ of next check when reaching marker + 2 bytes for length */ + } + if (c == 0xFF) + return FALSE; + marker = c; + length = php_jpg_get16(data+pos); + if (pos+length>=ImageInfo->Thumbnail.size) { + return FALSE; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: process section(x%02X=%s) @ x%04X + x%04X", marker, exif_get_markername(marker), pos, length); +#endif + switch (marker) { + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + /* handle SOFn block */ + exif_process_SOFn(data+pos, marker, &sof_info); + ImageInfo->Thumbnail.height = sof_info.height; + ImageInfo->Thumbnail.width = sof_info.width; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: size: %d * %d", sof_info.width, sof_info.height); +#endif + return TRUE; + + case M_SOS: + case M_EOI: + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Could not compute size of thumbnail"); + return FALSE; + break; + + default: + /* just skip */ + break; + } + } + + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Could not compute size of thumbnail"); + return FALSE; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_TIFF + * Parse the TIFF header; */ +static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offset, int section_index) +{ + int i, sn, num_entries, sub_section_index = 0; + unsigned char *dir_entry; + char tagname[64]; + size_t ifd_size, dir_size, entry_offset, next_offset, entry_length, entry_value=0, fgot; + int entry_tag , entry_type; + tag_table_type tag_table = exif_get_tag_table(section_index); + + if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { + return FALSE; + } + + if (ImageInfo->FileSize >= dir_offset+2) { + sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); +#endif + php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ + php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); + num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); + dir_size = 2/*num dir entries*/ +12/*length of entry*/*num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; + if (ImageInfo->FileSize >= dir_offset+dir_size) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X), IFD entries(%d)", ImageInfo->FileSize, dir_offset+2, dir_size-2, num_entries); +#endif + if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) { + return FALSE; + } + php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2); + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Dump: %s", exif_char_dump(ImageInfo->file.list[sn].data, dir_size, 0));*/ + next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset); +#endif + /* now we have the directory we can look how long it should be */ + ifd_size = dir_size; + for(i=0;ifile.list[sn].data+2+i*12; + entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel); + entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + if (entry_type > NUM_FORMATS) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: tag(0x%04X,%12s): Illegal format code 0x%04X, switching to BYTE", entry_tag, exif_get_tagname(entry_tag, tagname, -12, tag_table), entry_type); + /* Since this is repeated in exif_process_IFD_TAG make it a notice here */ + /* and make it a warning in the exif_process_IFD_TAG which is called */ + /* elsewhere. */ + entry_type = TAG_FMT_BYTE; + /*The next line would break the image on writeback: */ + /* php_ifd_set16u(dir_entry+2, entry_type, ImageInfo->motorola_intel);*/ + } + entry_length = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel) * php_tiff_bytes_per_format[entry_type]; + if (entry_length <= 4) { + switch(entry_type) { + case TAG_FMT_USHORT: + entry_value = php_ifd_get16u(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_SSHORT: + entry_value = php_ifd_get16s(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_ULONG: + entry_value = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_SLONG: + entry_value = php_ifd_get32s(dir_entry+8, ImageInfo->motorola_intel); + break; + } + switch(entry_tag) { + case TAG_IMAGEWIDTH: + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->Width = entry_value; + break; + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Height = entry_value; + break; + case TAG_PHOTOMETRIC_INTERPRETATION: + switch (entry_value) { + case PMI_BLACK_IS_ZERO: + case PMI_WHITE_IS_ZERO: + case PMI_TRANSPARENCY_MASK: + ImageInfo->IsColor = 0; + break; + case PMI_RGB: + case PMI_PALETTE_COLOR: + case PMI_SEPARATED: + case PMI_YCBCR: + case PMI_CIELAB: + ImageInfo->IsColor = 1; + break; + } + break; + } + } else { + entry_offset = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + /* if entry needs expading ifd cache and entry is at end of current ifd cache. */ + /* otherwise there may be huge holes between two entries */ + if (entry_offset + entry_length > dir_offset + ifd_size + && entry_offset == dir_offset + ifd_size) { + ifd_size = entry_offset + entry_length - dir_offset; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Resize struct: x%04X + x%04X - x%04X = x%04X", entry_offset, entry_length, dir_offset, ifd_size); +#endif + } + } + } + if (ImageInfo->FileSize >= dir_offset + ImageInfo->file.list[sn].size) { + if (ifd_size > dir_size) { + if (dir_offset + ifd_size > ImageInfo->FileSize) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); + return FALSE; + } + if (exif_file_sections_realloc(ImageInfo, sn, ifd_size)) { + return FALSE; + } + /* read values not stored in directory itself */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); +#endif + php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done"); +#endif + } + /* now process the tags */ + for(i=0;ifile.list[sn].data+2+i*12; + entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel); + entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + /*entry_length = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel);*/ + if (entry_tag == TAG_EXIF_IFD_POINTER || + entry_tag == TAG_INTEROP_IFD_POINTER || + entry_tag == TAG_GPS_IFD_POINTER || + entry_tag == TAG_SUB_IFD + ) { + switch(entry_tag) { + case TAG_EXIF_IFD_POINTER: + ImageInfo->sections_found |= FOUND_EXIF; + sub_section_index = SECTION_EXIF; + break; + case TAG_GPS_IFD_POINTER: + ImageInfo->sections_found |= FOUND_GPS; + sub_section_index = SECTION_GPS; + break; + case TAG_INTEROP_IFD_POINTER: + ImageInfo->sections_found |= FOUND_INTEROP; + sub_section_index = SECTION_INTEROP; + break; + case TAG_SUB_IFD: + ImageInfo->sections_found |= FOUND_THUMBNAIL; + sub_section_index = SECTION_THUMBNAIL; + break; + } + entry_offset = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Next IFD: %s @x%04X", exif_get_sectionname(sub_section_index), entry_offset); +#endif + ImageInfo->ifd_nesting_level++; + exif_process_IFD_in_TIFF(ImageInfo, entry_offset, sub_section_index); + if (section_index!=SECTION_THUMBNAIL && entry_tag==TAG_SUB_IFD) { + if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN + && ImageInfo->Thumbnail.size + && ImageInfo->Thumbnail.offset + && ImageInfo->read_thumbnail + ) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); +#endif + if (!ImageInfo->Thumbnail.data) { + ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); + php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); + fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + if (fgot < ImageInfo->Thumbnail.size) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + efree(ImageInfo->Thumbnail.data); + + ImageInfo->Thumbnail.data = NULL; + } else { + exif_thumbnail_build(ImageInfo); + } + } + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Next IFD: %s done", exif_get_sectionname(sub_section_index)); +#endif + } else { + if (!exif_process_IFD_TAG(ImageInfo, (char*)dir_entry, + (char*)(ImageInfo->file.list[sn].data-dir_offset), + ifd_size, 0, section_index, 0, tag_table)) { + return FALSE; + } + } + } + /* If we had a thumbnail in a SUB_IFD we have ANOTHER image in NEXT IFD */ + if (next_offset && section_index != SECTION_THUMBNAIL) { + /* this should be a thumbnail IFD */ + /* the thumbnail itself is stored at Tag=StripOffsets */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read next IFD (THUMBNAIL) at x%04X", next_offset); +#endif + ImageInfo->ifd_nesting_level++; + exif_process_IFD_in_TIFF(ImageInfo, next_offset, SECTION_THUMBNAIL); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); +#endif + if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) { + ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); + php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); + fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + if (fgot < ImageInfo->Thumbnail.size) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + efree(ImageInfo->Thumbnail.data); + ImageInfo->Thumbnail.data = NULL; + } else { + exif_thumbnail_build(ImageInfo); + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read next IFD (THUMBNAIL) done"); +#endif + } + return TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X)", ImageInfo->FileSize, dir_offset+ImageInfo->file.list[sn].size); + return FALSE; + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+dir_size); + return FALSE; + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than start of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+2); + return FALSE; + } +} +/* }}} */ + +/* {{{ exif_scan_FILE_header + * Parse the marker stream until SOS or EOI is seen; */ +static int exif_scan_FILE_header(image_info_type *ImageInfo) +{ + unsigned char file_header[8]; + int ret = FALSE; + + ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN; + + if (ImageInfo->FileSize >= 2) { + php_stream_seek(ImageInfo->infile, 0, SEEK_SET); + if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) { + return FALSE; + } + if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) { + ImageInfo->FileType = IMAGE_FILETYPE_JPEG; + if (exif_scan_JPEG_header(ImageInfo)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file"); + } + } else if (ImageInfo->FileSize >= 8) { + if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { + return FALSE; + } + if (!memcmp(file_header, "II\x2A\x00", 4)) { + ImageInfo->FileType = IMAGE_FILETYPE_TIFF_II; + ImageInfo->motorola_intel = 0; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "File has TIFF/II format"); +#endif + ImageInfo->sections_found |= FOUND_IFD0; + if (exif_process_IFD_in_TIFF(ImageInfo, + php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel), + SECTION_IFD0)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file"); + } + } else if (!memcmp(file_header, "MM\x00\x2a", 4)) { + ImageInfo->FileType = IMAGE_FILETYPE_TIFF_MM; + ImageInfo->motorola_intel = 1; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "File has TIFF/MM format"); +#endif + ImageInfo->sections_found |= FOUND_IFD0; + if (exif_process_IFD_in_TIFF(ImageInfo, + php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel), + SECTION_IFD0)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file"); + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported"); + return FALSE; + } + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File too small (%d)", ImageInfo->FileSize); + } + return ret; +} +/* }}} */ + +/* {{{ exif_discard_imageinfo + Discard data scanned by exif_read_file. +*/ +static int exif_discard_imageinfo(image_info_type *ImageInfo) +{ + int i; + + EFREE_IF(ImageInfo->FileName); + EFREE_IF(ImageInfo->UserComment); + EFREE_IF(ImageInfo->UserCommentEncoding); + EFREE_IF(ImageInfo->Copyright); + EFREE_IF(ImageInfo->CopyrightPhotographer); + EFREE_IF(ImageInfo->CopyrightEditor); + EFREE_IF(ImageInfo->Thumbnail.data); + EFREE_IF(ImageInfo->encode_unicode); + EFREE_IF(ImageInfo->decode_unicode_be); + EFREE_IF(ImageInfo->decode_unicode_le); + EFREE_IF(ImageInfo->encode_jis); + EFREE_IF(ImageInfo->decode_jis_be); + EFREE_IF(ImageInfo->decode_jis_le); + EFREE_IF(ImageInfo->make); + EFREE_IF(ImageInfo->model); + for (i=0; ixp_fields.count; i++) { + EFREE_IF(ImageInfo->xp_fields.list[i].value); + } + EFREE_IF(ImageInfo->xp_fields.list); + for (i=0; imotorola_intel = -1; /* flag as unknown */ + ImageInfo->infile = stream; + ImageInfo->FileName = NULL; + + if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) { + if (VCWD_STAT(stream->orig_path, &st) >= 0) { + zend_string *base; + if ((st.st_mode & S_IFMT) != S_IFREG) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file"); + php_stream_close(ImageInfo->infile); + return FALSE; + } + + /* Store file name */ + base = php_basename(stream->orig_path, strlen(stream->orig_path), NULL, 0); + ImageInfo->FileName = estrndup(ZSTR_VAL(base), ZSTR_LEN(base)); + + zend_string_release(base); + + /* Store file date/time. */ + ImageInfo->FileDateTime = st.st_mtime; + ImageInfo->FileSize = st.st_size; + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Opened stream is file: %d", ImageInfo->FileSize);*/ + } + } else { + if (!ImageInfo->FileSize) { + php_stream_seek(ImageInfo->infile, 0, SEEK_END); + ImageInfo->FileSize = php_stream_tell(ImageInfo->infile); + php_stream_seek(ImageInfo->infile, 0, SEEK_SET); + } + } + + ImageInfo->read_thumbnail = read_thumbnail; + ImageInfo->read_all = read_all; + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_UNKNOWN; + + ImageInfo->encode_unicode = estrdup(EXIF_G(encode_unicode)); + ImageInfo->decode_unicode_be = estrdup(EXIF_G(decode_unicode_be)); + ImageInfo->decode_unicode_le = estrdup(EXIF_G(decode_unicode_le)); + ImageInfo->encode_jis = estrdup(EXIF_G(encode_jis)); + ImageInfo->decode_jis_be = estrdup(EXIF_G(decode_jis_be)); + ImageInfo->decode_jis_le = estrdup(EXIF_G(decode_jis_le)); + + + ImageInfo->ifd_nesting_level = 0; + + /* Scan the headers */ + ret = exif_scan_FILE_header(ImageInfo); + + return ret; +} +/* }}} */ + +/* {{{ exif_read_from_stream + */ +static int exif_read_from_stream(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all) +{ + int ret; + off_t old_pos = php_stream_tell(stream); + + if (old_pos) { + php_stream_seek(stream, 0, SEEK_SET); + } + + ret = exif_read_from_impl(ImageInfo, stream, read_thumbnail, read_all); + + if (old_pos) { + php_stream_seek(stream, old_pos, SEEK_SET); + } + + return ret; +} +/* }}} */ + +/* {{{ exif_read_from_file + */ +static int exif_read_from_file(image_info_type *ImageInfo, char *FileName, int read_thumbnail, int read_all) +{ + int ret; + php_stream *stream; + + stream = php_stream_open_wrapper(FileName, "rb", STREAM_MUST_SEEK | IGNORE_PATH, NULL); + + if (!stream) { + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Unable to open file"); + + return FALSE; + } + + ret = exif_read_from_stream(ImageInfo, stream, read_thumbnail, read_all); + + php_stream_close(stream); + + return ret; +} +/* }}} */ + +/* {{{ proto array exif_read_data(mixed stream [, string sections_needed [, bool sub_arrays[, bool read_thumbnail]]]) + Reads header data from an image and optionally reads the internal thumbnails */ +PHP_FUNCTION(exif_read_data) +{ + zend_string *z_sections_needed = NULL; + zend_bool sub_arrays = 0, read_thumbnail = 0, read_all = 0; + zval *stream; + int i, ret, sections_needed = 0; + image_info_type ImageInfo; + char tmp[64], *sections_str, *s; + + /* Parse arguments */ + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_ZVAL(stream) + Z_PARAM_OPTIONAL + Z_PARAM_STR(z_sections_needed) + Z_PARAM_BOOL(sub_arrays) + Z_PARAM_BOOL(read_thumbnail) + ZEND_PARSE_PARAMETERS_END(); + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (z_sections_needed) { + spprintf(§ions_str, 0, ",%s,", ZSTR_VAL(z_sections_needed)); + /* sections_str DOES start with , and SPACES are NOT allowed in names */ + s = sections_str; + while (*++s) { + if (*s == ' ') { + *s = ','; + } + } + + for (i = 0; i < SECTION_COUNT; i++) { + snprintf(tmp, sizeof(tmp), ",%s,", exif_get_sectionname(i)); + if (strstr(sections_str, tmp)) { + sections_needed |= 1<0 && ImageInfo.Height>0) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "html" , "width=\"%d\" height=\"%d\"", ImageInfo.Width, ImageInfo.Height); + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Height", ImageInfo.Height); + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Width", ImageInfo.Width); + } + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "IsColor", ImageInfo.IsColor); + if (ImageInfo.motorola_intel != -1) { + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "ByteOrderMotorola", ImageInfo.motorola_intel); + } + if (ImageInfo.FocalLength) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocalLength", "%4.1Fmm", ImageInfo.FocalLength); + if(ImageInfo.CCDWidth) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "35mmFocalLength", "%dmm", (int)(ImageInfo.FocalLength/ImageInfo.CCDWidth*35+0.5)); + } + } + if(ImageInfo.CCDWidth) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "CCDWidth", "%dmm", (int)ImageInfo.CCDWidth); + } + if(ImageInfo.ExposureTime>0) { + if(ImageInfo.ExposureTime <= 0.5) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime", "%0.3F s (1/%d)", ImageInfo.ExposureTime, (int)(0.5 + 1/ImageInfo.ExposureTime)); + } else { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime", "%0.3F s", ImageInfo.ExposureTime); + } + } + if(ImageInfo.ApertureFNumber) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ApertureFNumber", "f/%.1F", ImageInfo.ApertureFNumber); + } + if(ImageInfo.Distance) { + if(ImageInfo.Distance<0) { + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "FocusDistance", "Infinite"); + } else { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocusDistance", "%0.2Fm", ImageInfo.Distance); + } + } + if (ImageInfo.UserComment) { + exif_iif_add_buffer(&ImageInfo, SECTION_COMPUTED, "UserComment", ImageInfo.UserCommentLength, ImageInfo.UserComment); + if (ImageInfo.UserCommentEncoding && strlen(ImageInfo.UserCommentEncoding)) { + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "UserCommentEncoding", ImageInfo.UserCommentEncoding); + } + } + + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright", ImageInfo.Copyright); + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Photographer", ImageInfo.CopyrightPhotographer); + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Editor", ImageInfo.CopyrightEditor); + + for (i=0; i" : Z_STRVAL_P(stream)), E_NOTICE, "Done"); +#endif +} +/* }}} */ + +/* {{{ proto string exif_thumbnail(string filename [, &width, &height [, &imagetype]]) + Reads the embedded thumbnail */ +PHP_FUNCTION(exif_thumbnail) +{ + int ret, arg_c = ZEND_NUM_ARGS(); + image_info_type ImageInfo; + zval *stream; + zval *z_width = NULL, *z_height = NULL, *z_imagetype = NULL; + + /* Parse arguments */ + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_ZVAL(stream) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_DEREF(z_width) + Z_PARAM_ZVAL_DEREF(z_height) + Z_PARAM_ZVAL_DEREF(z_imagetype) + ZEND_PARSE_PARAMETERS_END(); + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (Z_TYPE_P(stream) == IS_RESOURCE) { + php_stream *p_stream = NULL; + + php_stream_from_res(p_stream, Z_RES_P(stream)); + + ret = exif_read_from_stream(&ImageInfo, p_stream, 1, 0); + } else { + convert_to_string(stream); + + if (!Z_STRLEN_P(stream)) { + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_WARNING, "Filename cannot be empty"); + + RETURN_FALSE; + } + + ret = exif_read_from_file(&ImageInfo, Z_STRVAL_P(stream), 1, 0); + } + + if (ret == FALSE) { + exif_discard_imageinfo(&ImageInfo); + RETURN_FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Thumbnail data %d %d %d, %d x %d", ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size, ImageInfo.Thumbnail.filetype, ImageInfo.Thumbnail.width, ImageInfo.Thumbnail.height); +#endif + if (!ImageInfo.Thumbnail.data || !ImageInfo.Thumbnail.size) { + exif_discard_imageinfo(&ImageInfo); + RETURN_FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Returning thumbnail(%d)", ImageInfo.Thumbnail.size); +#endif + + ZVAL_STRINGL(return_value, ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size); + if (arg_c >= 3) { + if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) { + exif_scan_thumbnail(&ImageInfo); + } + zval_dtor(z_width); + zval_dtor(z_height); + ZVAL_LONG(z_width, ImageInfo.Thumbnail.width); + ZVAL_LONG(z_height, ImageInfo.Thumbnail.height); + } + if (arg_c >= 4) { + zval_dtor(z_imagetype); + ZVAL_LONG(z_imagetype, ImageInfo.Thumbnail.filetype); + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Discarding info"); +#endif + + exif_discard_imageinfo(&ImageInfo); + +#ifdef EXIF_DEBUG + php_error_docref1(NULL, (Z_TYPE_P(stream) == IS_RESOURCE ? "" : Z_STRVAL_P(stream)), E_NOTICE, "Done"); +#endif +} +/* }}} */ + +/* {{{ proto int exif_imagetype(string imagefile) + Get the type of an image */ +PHP_FUNCTION(exif_imagetype) +{ + char *imagefile; + size_t imagefile_len; + php_stream * stream; + int itype = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &imagefile, &imagefile_len) == FAILURE) { + return; + } + + stream = php_stream_open_wrapper(imagefile, "rb", IGNORE_PATH|REPORT_ERRORS, NULL); + + if (stream == NULL) { + RETURN_FALSE; + } + + itype = php_getimagetype(stream, NULL); + + php_stream_close(stream); + + if (itype == IMAGE_FILETYPE_UNKNOWN) { + RETURN_FALSE; + } else { + ZVAL_LONG(return_value, itype); + } +} +/* }}} */ + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 tw=78 fdm=marker + * vim<600: sw=4 ts=4 tw=78 + */ From 021aa08a161697da9a92d28b0e4d3f072afef2f3 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:53 +0000 Subject: [PATCH 09/25] commit patch 19348639 --- ext/spl/spl_directory.c | 4 +- ext/spl/spl_directory.c.orig | 3188 ++++++++++++++++++++++++++++++++++ ext/spl/tests/bug78863.phpt | 31 + 3 files changed, 3221 insertions(+), 2 deletions(-) create mode 100644 ext/spl/spl_directory.c.orig create mode 100644 ext/spl/tests/bug78863.phpt diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 049b517c46def..08ac37b3b12b6 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -701,10 +701,10 @@ void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_long cto if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_FLAGS)) { flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO; - parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &len, &flags); + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "p|l", &path, &len, &flags); } else { flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_SELF; - parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s", &path, &len); + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "p", &path, &len); } if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_SKIPDOTS)) { flags |= SPL_FILE_DIR_SKIPDOTS; diff --git a/ext/spl/spl_directory.c.orig b/ext/spl/spl_directory.c.orig new file mode 100644 index 0000000000000..049b517c46def --- /dev/null +++ b/ext/spl/spl_directory.c.orig @@ -0,0 +1,3188 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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. | + +----------------------------------------------------------------------+ + | Author: Marcus Boerger | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "ext/standard/php_string.h" +#include "zend_compile.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" + +#include "php_spl.h" +#include "spl_functions.h" +#include "spl_engine.h" +#include "spl_iterators.h" +#include "spl_directory.h" +#include "spl_exceptions.h" + +#include "php.h" +#include "fopen_wrappers.h" + +#include "ext/standard/basic_functions.h" +#include "ext/standard/php_filestat.h" + +#define SPL_HAS_FLAG(flags, test_flag) ((flags & test_flag) ? 1 : 0) + +/* declare the class handlers */ +static zend_object_handlers spl_filesystem_object_handlers; +/* includes handler to validate object state when retrieving methods */ +static zend_object_handlers spl_filesystem_object_check_handlers; + +/* decalre the class entry */ +PHPAPI zend_class_entry *spl_ce_SplFileInfo; +PHPAPI zend_class_entry *spl_ce_DirectoryIterator; +PHPAPI zend_class_entry *spl_ce_FilesystemIterator; +PHPAPI zend_class_entry *spl_ce_RecursiveDirectoryIterator; +PHPAPI zend_class_entry *spl_ce_GlobIterator; +PHPAPI zend_class_entry *spl_ce_SplFileObject; +PHPAPI zend_class_entry *spl_ce_SplTempFileObject; + +static void spl_filesystem_file_free_line(spl_filesystem_object *intern) /* {{{ */ +{ + if (intern->u.file.current_line) { + efree(intern->u.file.current_line); + intern->u.file.current_line = NULL; + } + if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval_ptr_dtor(&intern->u.file.current_zval); + ZVAL_UNDEF(&intern->u.file.current_zval); + } +} /* }}} */ + +static void spl_filesystem_object_destroy_object(zend_object *object) /* {{{ */ +{ + spl_filesystem_object *intern = spl_filesystem_from_obj(object); + + zend_objects_destroy_object(object); + + switch(intern->type) { + case SPL_FS_DIR: + if (intern->u.dir.dirp) { + php_stream_close(intern->u.dir.dirp); + intern->u.dir.dirp = NULL; + } + break; + case SPL_FS_FILE: + if (intern->u.file.stream) { + /* + if (intern->u.file.zcontext) { + zend_list_delref(Z_RESVAL_P(intern->zcontext)); + } + */ + if (!intern->u.file.stream->is_persistent) { + php_stream_close(intern->u.file.stream); + } else { + php_stream_pclose(intern->u.file.stream); + } + } + break; + } +} /* }}} */ + +static void spl_filesystem_object_free_storage(zend_object *object) /* {{{ */ +{ + spl_filesystem_object *intern = spl_filesystem_from_obj(object); + + if (intern->oth_handler && intern->oth_handler->dtor) { + intern->oth_handler->dtor(intern); + } + + zend_object_std_dtor(&intern->std); + + if (intern->_path) { + efree(intern->_path); + } + if (intern->file_name) { + efree(intern->file_name); + } + switch(intern->type) { + case SPL_FS_INFO: + break; + case SPL_FS_DIR: + if (intern->u.dir.sub_path) { + efree(intern->u.dir.sub_path); + } + break; + case SPL_FS_FILE: + if (intern->u.file.stream) { + if (intern->u.file.open_mode) { + efree(intern->u.file.open_mode); + } + if (intern->orig_path) { + efree(intern->orig_path); + } + } + spl_filesystem_file_free_line(intern); + break; + } +} /* }}} */ + +/* {{{ spl_ce_dir_object_new */ +/* creates the object by + - allocating memory + - initializing the object members + - storing the object + - setting it's handlers + + called from + - clone + - new + */ +static zend_object *spl_filesystem_object_new_ex(zend_class_entry *class_type) +{ + spl_filesystem_object *intern; + + intern = ecalloc(1, sizeof(spl_filesystem_object) + zend_object_properties_size(class_type)); + /* intern->type = SPL_FS_INFO; done by set 0 */ + intern->file_class = spl_ce_SplFileObject; + intern->info_class = spl_ce_SplFileInfo; + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &spl_filesystem_object_handlers; + + return &intern->std; +} +/* }}} */ + +/* {{{ spl_filesystem_object_new */ +/* See spl_filesystem_object_new_ex */ +static zend_object *spl_filesystem_object_new(zend_class_entry *class_type) +{ + return spl_filesystem_object_new_ex(class_type); +} +/* }}} */ + +/* {{{ spl_filesystem_object_new_check */ +static zend_object *spl_filesystem_object_new_check(zend_class_entry *class_type) +{ + spl_filesystem_object *ret = spl_filesystem_from_obj(spl_filesystem_object_new_ex(class_type)); + ret->std.handlers = &spl_filesystem_object_check_handlers; + return &ret->std; +} +/* }}} */ + +PHPAPI char* spl_filesystem_object_get_path(spl_filesystem_object *intern, size_t *len) /* {{{ */ +{ +#ifdef HAVE_GLOB + if (intern->type == SPL_FS_DIR) { + if (php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + return php_glob_stream_get_path(intern->u.dir.dirp, 0, len); + } + } +#endif + if (len) { + *len = intern->_path_len; + } + return intern->_path; +} /* }}} */ + +static inline void spl_filesystem_object_get_file_name(spl_filesystem_object *intern) /* {{{ */ +{ + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + if (!intern->file_name) { + php_error_docref(NULL, E_ERROR, "Object not initialized"); + } + break; + case SPL_FS_DIR: + if (intern->file_name) { + efree(intern->file_name); + } + intern->file_name_len = spprintf(&intern->file_name, 0, "%s%c%s", + spl_filesystem_object_get_path(intern, NULL), + slash, intern->u.dir.entry.d_name); + break; + } +} /* }}} */ + +static int spl_filesystem_dir_read(spl_filesystem_object *intern) /* {{{ */ +{ + if (!intern->u.dir.dirp || !php_stream_readdir(intern->u.dir.dirp, &intern->u.dir.entry)) { + intern->u.dir.entry.d_name[0] = '\0'; + return 0; + } else { + return 1; + } +} +/* }}} */ + +#define IS_SLASH_AT(zs, pos) (IS_SLASH(zs[pos])) + +static inline int spl_filesystem_is_dot(const char * d_name) /* {{{ */ +{ + return !strcmp(d_name, ".") || !strcmp(d_name, ".."); +} +/* }}} */ + +/* {{{ spl_filesystem_dir_open */ +/* open a directory resource */ +static void spl_filesystem_dir_open(spl_filesystem_object* intern, char *path) +{ + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + intern->type = SPL_FS_DIR; + intern->_path_len = strlen(path); + intern->u.dir.dirp = php_stream_opendir(path, REPORT_ERRORS, FG(default_context)); + + if (intern->_path_len > 1 && IS_SLASH_AT(path, intern->_path_len-1)) { + intern->_path = estrndup(path, --intern->_path_len); + } else { + intern->_path = estrndup(path, intern->_path_len); + } + intern->u.dir.index = 0; + + if (EG(exception) || intern->u.dir.dirp == NULL) { + intern->u.dir.entry.d_name[0] = '\0'; + if (!EG(exception)) { + /* open failed w/out notice (turned to exception due to EH_THROW) */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Failed to open directory \"%s\"", path); + } + } else { + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + } +} +/* }}} */ + +static int spl_filesystem_file_open(spl_filesystem_object *intern, int use_include_path, int silent) /* {{{ */ +{ + zval tmp; + + intern->type = SPL_FS_FILE; + + php_stat(intern->file_name, intern->file_name_len, FS_IS_DIR, &tmp); + if (Z_TYPE(tmp) == IS_TRUE) { + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + zend_throw_exception_ex(spl_ce_LogicException, 0, "Cannot use SplFileObject with directories"); + return FAILURE; + } + + intern->u.file.context = php_stream_context_from_zval(intern->u.file.zcontext, 0); + intern->u.file.stream = php_stream_open_wrapper_ex(intern->file_name, intern->u.file.open_mode, (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, intern->u.file.context); + + if (!intern->file_name_len || !intern->u.file.stream) { + if (!EG(exception)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot open file '%s'", intern->file_name_len ? intern->file_name : ""); + } + intern->file_name = NULL; /* until here it is not a copy */ + intern->u.file.open_mode = NULL; + return FAILURE; + } + + /* + if (intern->u.file.zcontext) { + //zend_list_addref(Z_RES_VAL(intern->u.file.zcontext)); + Z_ADDREF_P(intern->u.file.zcontext); + } + */ + + if (intern->file_name_len > 1 && IS_SLASH_AT(intern->file_name, intern->file_name_len-1)) { + intern->file_name_len--; + } + + intern->orig_path = estrndup(intern->u.file.stream->orig_path, strlen(intern->u.file.stream->orig_path)); + + intern->file_name = estrndup(intern->file_name, intern->file_name_len); + intern->u.file.open_mode = estrndup(intern->u.file.open_mode, intern->u.file.open_mode_len); + + /* avoid reference counting in debug mode, thus do it manually */ + ZVAL_RES(&intern->u.file.zresource, intern->u.file.stream->res); + /*!!! TODO: maybe bug? + Z_SET_REFCOUNT(intern->u.file.zresource, 1); + */ + + intern->u.file.delimiter = ','; + intern->u.file.enclosure = '"'; + intern->u.file.escape = '\\'; + + intern->u.file.func_getCurr = zend_hash_str_find_ptr(&intern->std.ce->function_table, "getcurrentline", sizeof("getcurrentline") - 1); + + return SUCCESS; +} /* }}} */ + +/* {{{ spl_filesystem_object_clone */ +/* Local zend_object creation (on stack) + Load the 'other' object + Create a new empty object (See spl_filesystem_object_new_ex) + Open the directory + Clone other members (properties) + */ +static zend_object *spl_filesystem_object_clone(zval *zobject) +{ + zend_object *old_object; + zend_object *new_object; + spl_filesystem_object *intern; + spl_filesystem_object *source; + int index, skip_dots; + + old_object = Z_OBJ_P(zobject); + source = spl_filesystem_from_obj(old_object); + new_object = spl_filesystem_object_new_ex(old_object->ce); + intern = spl_filesystem_from_obj(new_object); + + intern->flags = source->flags; + + switch (source->type) { + case SPL_FS_INFO: + intern->_path_len = source->_path_len; + intern->_path = estrndup(source->_path, source->_path_len); + intern->file_name_len = source->file_name_len; + intern->file_name = estrndup(source->file_name, intern->file_name_len); + break; + case SPL_FS_DIR: + spl_filesystem_dir_open(intern, source->_path); + /* read until we hit the position in which we were before */ + skip_dots = SPL_HAS_FLAG(source->flags, SPL_FILE_DIR_SKIPDOTS); + for(index = 0; index < source->u.dir.index; ++index) { + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + } + intern->u.dir.index = index; + break; + case SPL_FS_FILE: + zend_throw_error(NULL, "An object of class %s cannot be cloned", ZSTR_VAL(old_object->ce->name)); + return new_object; + } + + intern->file_class = source->file_class; + intern->info_class = source->info_class; + intern->oth = source->oth; + intern->oth_handler = source->oth_handler; + + zend_objects_clone_members(new_object, old_object); + + if (intern->oth_handler && intern->oth_handler->clone) { + intern->oth_handler->clone(source, intern); + } + + return new_object; +} +/* }}} */ + +void spl_filesystem_info_set_filename(spl_filesystem_object *intern, char *path, size_t len, size_t use_copy) /* {{{ */ +{ + char *p1, *p2; + + if (intern->file_name) { + efree(intern->file_name); + } + + intern->file_name = use_copy ? estrndup(path, len) : path; + intern->file_name_len = len; + + while (intern->file_name_len > 1 && IS_SLASH_AT(intern->file_name, intern->file_name_len-1)) { + intern->file_name[intern->file_name_len-1] = 0; + intern->file_name_len--; + } + + p1 = strrchr(intern->file_name, '/'); +#if defined(PHP_WIN32) + p2 = strrchr(intern->file_name, '\\'); +#else + p2 = 0; +#endif + if (p1 || p2) { + intern->_path_len = ((p1 > p2 ? p1 : p2) - intern->file_name); + } else { + intern->_path_len = 0; + } + + if (intern->_path) { + efree(intern->_path); + } + intern->_path = estrndup(path, intern->_path_len); +} /* }}} */ + +static spl_filesystem_object *spl_filesystem_object_create_info(spl_filesystem_object *source, char *file_path, size_t file_path_len, int use_copy, zend_class_entry *ce, zval *return_value) /* {{{ */ +{ + spl_filesystem_object *intern; + zval arg1; + zend_error_handling error_handling; + + if (!file_path || !file_path_len) { +#if defined(PHP_WIN32) + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot create SplFileInfo for empty path"); + if (file_path && !use_copy) { + efree(file_path); + } +#else + if (file_path && !use_copy) { + efree(file_path); + } + file_path_len = 1; + file_path = "/"; +#endif + return NULL; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + ce = ce ? ce : source->info_class; + + zend_update_class_constants(ce); + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + ZVAL_OBJ(return_value, &intern->std); + + if (ce->constructor->common.scope != spl_ce_SplFileInfo) { + ZVAL_STRINGL(&arg1, file_path, file_path_len); + zend_call_method_with_1_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + } else { + spl_filesystem_info_set_filename(intern, file_path, file_path_len, use_copy); + } + + zend_restore_error_handling(&error_handling); + return intern; +} /* }}} */ + +static spl_filesystem_object *spl_filesystem_object_create_type(int ht, spl_filesystem_object *source, int type, zend_class_entry *ce, zval *return_value) /* {{{ */ +{ + spl_filesystem_object *intern; + zend_bool use_include_path = 0; + zval arg1, arg2; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + switch (source->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + break; + case SPL_FS_DIR: + if (!source->u.dir.entry.d_name[0]) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Could not open file"); + zend_restore_error_handling(&error_handling); + return NULL; + } + } + + switch (type) { + case SPL_FS_INFO: + ce = ce ? ce : source->info_class; + + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + break; + } + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + ZVAL_OBJ(return_value, &intern->std); + + spl_filesystem_object_get_file_name(source); + if (ce->constructor->common.scope != spl_ce_SplFileInfo) { + ZVAL_STRINGL(&arg1, source->file_name, source->file_name_len); + zend_call_method_with_1_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + } else { + intern->file_name = estrndup(source->file_name, source->file_name_len); + intern->file_name_len = source->file_name_len; + intern->_path = spl_filesystem_object_get_path(source, &intern->_path_len); + intern->_path = estrndup(intern->_path, intern->_path_len); + } + break; + case SPL_FS_FILE: + ce = ce ? ce : source->file_class; + + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + break; + } + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + + ZVAL_OBJ(return_value, &intern->std); + + spl_filesystem_object_get_file_name(source); + + if (ce->constructor->common.scope != spl_ce_SplFileObject) { + ZVAL_STRINGL(&arg1, source->file_name, source->file_name_len); + ZVAL_STRINGL(&arg2, "r", 1); + zend_call_method_with_2_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1, &arg2); + zval_ptr_dtor(&arg1); + zval_ptr_dtor(&arg2); + } else { + intern->file_name = source->file_name; + intern->file_name_len = source->file_name_len; + intern->_path = spl_filesystem_object_get_path(source, &intern->_path_len); + intern->_path = estrndup(intern->_path, intern->_path_len); + + intern->u.file.open_mode = "r"; + intern->u.file.open_mode_len = 1; + + if (ht && zend_parse_parameters(ht, "|sbr", + &intern->u.file.open_mode, &intern->u.file.open_mode_len, + &use_include_path, &intern->u.file.zcontext) == FAILURE) { + zend_restore_error_handling(&error_handling); + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return NULL; + } + + if (spl_filesystem_file_open(intern, use_include_path, 0) == FAILURE) { + zend_restore_error_handling(&error_handling); + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return NULL; + } + } + break; + case SPL_FS_DIR: + zend_restore_error_handling(&error_handling); + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Operation not supported"); + return NULL; + } + zend_restore_error_handling(&error_handling); + return NULL; +} /* }}} */ + +static int spl_filesystem_is_invalid_or_dot(const char * d_name) /* {{{ */ +{ + return d_name[0] == '\0' || spl_filesystem_is_dot(d_name); +} +/* }}} */ + +static char *spl_filesystem_object_get_pathname(spl_filesystem_object *intern, size_t *len) { /* {{{ */ + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + *len = intern->file_name_len; + return intern->file_name; + case SPL_FS_DIR: + if (intern->u.dir.entry.d_name[0]) { + spl_filesystem_object_get_file_name(intern); + *len = intern->file_name_len; + return intern->file_name; + } + } + *len = 0; + return NULL; +} +/* }}} */ + +static HashTable *spl_filesystem_object_get_debug_info(zval *object, int *is_temp) /* {{{ */ +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(object); + zval tmp; + HashTable *rv; + zend_string *pnstr; + char *path; + size_t path_len; + char stmp[2]; + + *is_temp = 1; + + if (!intern->std.properties) { + rebuild_object_properties(&intern->std); + } + + rv = zend_array_dup(intern->std.properties); + + pnstr = spl_gen_private_prop_name(spl_ce_SplFileInfo, "pathName", sizeof("pathName")-1); + path = spl_filesystem_object_get_pathname(intern, &path_len); + ZVAL_STRINGL(&tmp, path, path_len); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + + if (intern->file_name) { + pnstr = spl_gen_private_prop_name(spl_ce_SplFileInfo, "fileName", sizeof("fileName")-1); + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + ZVAL_STRINGL(&tmp, intern->file_name + path_len + 1, intern->file_name_len - (path_len + 1)); + } else { + ZVAL_STRINGL(&tmp, intern->file_name, intern->file_name_len); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + if (intern->type == SPL_FS_DIR) { +#ifdef HAVE_GLOB + pnstr = spl_gen_private_prop_name(spl_ce_DirectoryIterator, "glob", sizeof("glob")-1); + if (php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + ZVAL_STRINGL(&tmp, intern->_path, intern->_path_len); + } else { + ZVAL_FALSE(&tmp); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); +#endif + pnstr = spl_gen_private_prop_name(spl_ce_RecursiveDirectoryIterator, "subPathName", sizeof("subPathName")-1); + if (intern->u.dir.sub_path) { + ZVAL_STRINGL(&tmp, intern->u.dir.sub_path, intern->u.dir.sub_path_len); + } else { + ZVAL_EMPTY_STRING(&tmp); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + if (intern->type == SPL_FS_FILE) { + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "openMode", sizeof("openMode")-1); + ZVAL_STRINGL(&tmp, intern->u.file.open_mode, intern->u.file.open_mode_len); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + stmp[1] = '\0'; + stmp[0] = intern->u.file.delimiter; + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "delimiter", sizeof("delimiter")-1); + ZVAL_STRINGL(&tmp, stmp, 1); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + stmp[0] = intern->u.file.enclosure; + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "enclosure", sizeof("enclosure")-1); + ZVAL_STRINGL(&tmp, stmp, 1); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + + return rv; +} +/* }}} */ + +zend_function *spl_filesystem_object_get_method_check(zend_object **object, zend_string *method, const zval *key) /* {{{ */ +{ + spl_filesystem_object *fsobj = spl_filesystem_from_obj(*object); + + if (fsobj->u.dir.dirp == NULL && fsobj->orig_path == NULL) { + zend_function *func; + zend_string *tmp = zend_string_init("_bad_state_ex", sizeof("_bad_state_ex") - 1, 0); + func = zend_get_std_object_handlers()->get_method(object, tmp, NULL); + zend_string_release(tmp); + return func; + } + + return zend_get_std_object_handlers()->get_method(object, method, key); +} +/* }}} */ + +#define DIT_CTOR_FLAGS 0x00000001 +#define DIT_CTOR_GLOB 0x00000002 + +void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_long ctor_flags) /* {{{ */ +{ + spl_filesystem_object *intern; + char *path; + int parsed; + size_t len; + zend_long flags; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_FLAGS)) { + flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO; + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &len, &flags); + } else { + flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_SELF; + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s", &path, &len); + } + if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_SKIPDOTS)) { + flags |= SPL_FILE_DIR_SKIPDOTS; + } + if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_UNIXPATHS)) { + flags |= SPL_FILE_DIR_UNIXPATHS; + } + if (parsed == FAILURE) { + zend_restore_error_handling(&error_handling); + return; + } + if (!len) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Directory name must not be empty."); + zend_restore_error_handling(&error_handling); + return; + } + + intern = Z_SPLFILESYSTEM_P(getThis()); + if (intern->_path) { + /* object is alreay initialized */ + zend_restore_error_handling(&error_handling); + php_error_docref(NULL, E_WARNING, "Directory object is already initialized"); + return; + } + intern->flags = flags; +#ifdef HAVE_GLOB + if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_GLOB) && strstr(path, "glob://") != path) { + spprintf(&path, 0, "glob://%s", path); + spl_filesystem_dir_open(intern, path); + efree(path); + } else +#endif + { + spl_filesystem_dir_open(intern, path); + + } + + intern->u.dir.is_recursive = instanceof_function(intern->std.ce, spl_ce_RecursiveDirectoryIterator) ? 1 : 0; + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::__construct(string path) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(DirectoryIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::rewind() + Rewind dir back to the start */ +SPL_METHOD(DirectoryIterator, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index = 0; + if (intern->u.dir.dirp) { + php_stream_rewinddir(intern->u.dir.dirp); + } + spl_filesystem_dir_read(intern); +} +/* }}} */ + +/* {{{ proto string DirectoryIterator::key() + Return current dir entry */ +SPL_METHOD(DirectoryIterator, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.dirp) { + RETURN_LONG(intern->u.dir.index); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto DirectoryIterator DirectoryIterator::current() + Return this (needed for Iterator interface) */ +SPL_METHOD(DirectoryIterator, current) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + ZVAL_OBJ(return_value, Z_OBJ_P(getThis())); + Z_ADDREF_P(return_value); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::next() + Move to next entry */ +SPL_METHOD(DirectoryIterator, next) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index++; + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + if (intern->file_name) { + efree(intern->file_name); + intern->file_name = NULL; + } +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::seek(int position) + Seek to the given position */ +SPL_METHOD(DirectoryIterator, seek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zval retval; + zend_long pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &pos) == FAILURE) { + return; + } + + if (intern->u.dir.index > pos) { + /* we first rewind */ + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_rewind, "rewind", NULL); + } + + while (intern->u.dir.index < pos) { + int valid = 0; + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_valid, "valid", &retval); + if (!Z_ISUNDEF(retval)) { + valid = zend_is_true(&retval); + zval_ptr_dtor(&retval); + } + if (!valid) { + zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", pos); + return; + } + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_next, "next", NULL); + } +} /* }}} */ + +/* {{{ proto string DirectoryIterator::valid() + Check whether dir contains more entries */ +SPL_METHOD(DirectoryIterator, valid) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(intern->u.dir.entry.d_name[0] != '\0'); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getPath() + Return the path */ +SPL_METHOD(SplFileInfo, getPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *path; + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + path = spl_filesystem_object_get_path(intern, &path_len); + RETURN_STRINGL(path, path_len); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getFilename() + Return filename only */ +SPL_METHOD(SplFileInfo, getFilename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + RETURN_STRINGL(intern->file_name + path_len + 1, intern->file_name_len - (path_len + 1)); + } else { + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } +} +/* }}} */ + +/* {{{ proto string DirectoryIterator::getFilename() + Return filename of current dir entry */ +SPL_METHOD(DirectoryIterator, getFilename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRING(intern->u.dir.entry.d_name); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getExtension() + Returns file extension component of path */ +SPL_METHOD(SplFileInfo, getExtension) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *fname = NULL; + const char *p; + size_t flen; + size_t path_len; + size_t idx; + zend_string *ret; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + fname = intern->file_name + path_len + 1; + flen = intern->file_name_len - (path_len + 1); + } else { + fname = intern->file_name; + flen = intern->file_name_len; + } + + ret = php_basename(fname, flen, NULL, 0); + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + if (p) { + idx = p - ZSTR_VAL(ret); + RETVAL_STRINGL(ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1); + zend_string_release(ret); + return; + } else { + zend_string_release(ret); + RETURN_EMPTY_STRING(); + } +} +/* }}}*/ + +/* {{{ proto string DirectoryIterator::getExtension() + Returns the file extension component of path */ +SPL_METHOD(DirectoryIterator, getExtension) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + const char *p; + size_t idx; + zend_string *fname; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + fname = php_basename(intern->u.dir.entry.d_name, strlen(intern->u.dir.entry.d_name), NULL, 0); + + p = zend_memrchr(ZSTR_VAL(fname), '.', ZSTR_LEN(fname)); + if (p) { + idx = p - ZSTR_VAL(fname); + RETVAL_STRINGL(ZSTR_VAL(fname) + idx + 1, ZSTR_LEN(fname) - idx - 1); + zend_string_release(fname); + } else { + zend_string_release(fname); + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getBasename([string $suffix]) + Returns filename component of path */ +SPL_METHOD(SplFileInfo, getBasename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *fname, *suffix = 0; + size_t flen; + size_t slen = 0, path_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &suffix, &slen) == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + fname = intern->file_name + path_len + 1; + flen = intern->file_name_len - (path_len + 1); + } else { + fname = intern->file_name; + flen = intern->file_name_len; + } + + RETURN_STR(php_basename(fname, flen, suffix, slen)); +} +/* }}}*/ + +/* {{{ proto string DirectoryIterator::getBasename([string $suffix]) + Returns filename component of current dir entry */ +SPL_METHOD(DirectoryIterator, getBasename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *suffix = 0; + size_t slen = 0; + zend_string *fname; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &suffix, &slen) == FAILURE) { + return; + } + + fname = php_basename(intern->u.dir.entry.d_name, strlen(intern->u.dir.entry.d_name), suffix, slen); + + RETVAL_STR(fname); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getPathname() + Return path and filename */ +SPL_METHOD(SplFileInfo, getPathname) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *path; + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + path = spl_filesystem_object_get_pathname(intern, &path_len); + if (path != NULL) { + RETURN_STRINGL(path, path_len); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string FilesystemIterator::key() + Return getPathname() or getFilename() depending on flags */ +SPL_METHOD(FilesystemIterator, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_FILE_DIR_KEY(intern, SPL_FILE_DIR_KEY_AS_FILENAME)) { + RETURN_STRING(intern->u.dir.entry.d_name); + } else { + spl_filesystem_object_get_file_name(intern); + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } +} +/* }}} */ + +/* {{{ proto string FilesystemIterator::current() + Return getFilename(), getFileInfo() or $this depending on flags */ +SPL_METHOD(FilesystemIterator, current) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_FILE_DIR_CURRENT(intern, SPL_FILE_DIR_CURRENT_AS_PATHNAME)) { + spl_filesystem_object_get_file_name(intern); + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } else if (SPL_FILE_DIR_CURRENT(intern, SPL_FILE_DIR_CURRENT_AS_FILEINFO)) { + spl_filesystem_object_get_file_name(intern); + spl_filesystem_object_create_type(0, intern, SPL_FS_INFO, NULL, return_value); + } else { + ZVAL_OBJ(return_value, Z_OBJ_P(getThis())); + Z_ADDREF_P(return_value); + /*RETURN_STRING(intern->u.dir.entry.d_name, 1);*/ + } +} +/* }}} */ + +/* {{{ proto bool DirectoryIterator::isDot() + Returns true if current entry is '.' or '..' */ +SPL_METHOD(DirectoryIterator, isDot) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(spl_filesystem_is_dot(intern->u.dir.entry.d_name)); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::__construct(string file_name) + Cronstructs a new SplFileInfo from a path. */ +/* When the constructor gets called the object is already created + by the engine, so we must only call 'additional' initializations. + */ +SPL_METHOD(SplFileInfo, __construct) +{ + spl_filesystem_object *intern; + char *path; + size_t len; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s", &path, &len) == FAILURE) { + return; + } + + intern = Z_SPLFILESYSTEM_P(getThis()); + + spl_filesystem_info_set_filename(intern, path, len, 1); + + /* intern->type = SPL_FS_INFO; already set */ +} +/* }}} */ + +/* {{{ FileInfoFunction */ +#define FileInfoFunction(func_name, func_num) \ +SPL_METHOD(SplFileInfo, func_name) \ +{ \ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); \ + zend_error_handling error_handling; \ + if (zend_parse_parameters_none() == FAILURE) { \ + return; \ + } \ + \ + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling);\ + spl_filesystem_object_get_file_name(intern); \ + php_stat(intern->file_name, intern->file_name_len, func_num, return_value); \ + zend_restore_error_handling(&error_handling); \ +} +/* }}} */ + +/* {{{ proto int SplFileInfo::getPerms() + Get file permissions */ +FileInfoFunction(getPerms, FS_PERMS) +/* }}} */ + +/* {{{ proto int SplFileInfo::getInode() + Get file inode */ +FileInfoFunction(getInode, FS_INODE) +/* }}} */ + +/* {{{ proto int SplFileInfo::getSize() + Get file size */ +FileInfoFunction(getSize, FS_SIZE) +/* }}} */ + +/* {{{ proto int SplFileInfo::getOwner() + Get file owner */ +FileInfoFunction(getOwner, FS_OWNER) +/* }}} */ + +/* {{{ proto int SplFileInfo::getGroup() + Get file group */ +FileInfoFunction(getGroup, FS_GROUP) +/* }}} */ + +/* {{{ proto int SplFileInfo::getATime() + Get last access time of file */ +FileInfoFunction(getATime, FS_ATIME) +/* }}} */ + +/* {{{ proto int SplFileInfo::getMTime() + Get last modification time of file */ +FileInfoFunction(getMTime, FS_MTIME) +/* }}} */ + +/* {{{ proto int SplFileInfo::getCTime() + Get inode modification time of file */ +FileInfoFunction(getCTime, FS_CTIME) +/* }}} */ + +/* {{{ proto string SplFileInfo::getType() + Get file type */ +FileInfoFunction(getType, FS_TYPE) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isWritable() + Returns true if file can be written */ +FileInfoFunction(isWritable, FS_IS_W) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isReadable() + Returns true if file can be read */ +FileInfoFunction(isReadable, FS_IS_R) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isExecutable() + Returns true if file is executable */ +FileInfoFunction(isExecutable, FS_IS_X) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isFile() + Returns true if file is a regular file */ +FileInfoFunction(isFile, FS_IS_FILE) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isDir() + Returns true if file is directory */ +FileInfoFunction(isDir, FS_IS_DIR) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isLink() + Returns true if file is symbolic link */ +FileInfoFunction(isLink, FS_IS_LINK) +/* }}} */ + +/* {{{ proto string SplFileInfo::getLinkTarget() + Return the target of a symbolic link */ +SPL_METHOD(SplFileInfo, getLinkTarget) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int ret; + char buff[MAXPATHLEN]; + zend_error_handling error_handling; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + +#if defined(PHP_WIN32) || HAVE_SYMLINK + if (intern->file_name == NULL) { + php_error_docref(NULL, E_WARNING, "Empty filename"); + RETURN_FALSE; + } else if (!IS_ABSOLUTE_PATH(intern->file_name, intern->file_name_len)) { + char expanded_path[MAXPATHLEN]; + if (!expand_filepath_with_mode(intern->file_name, expanded_path, NULL, 0, CWD_EXPAND )) { + php_error_docref(NULL, E_WARNING, "No such file or directory"); + RETURN_FALSE; + } + ret = php_sys_readlink(expanded_path, buff, MAXPATHLEN - 1); + } else { + ret = php_sys_readlink(intern->file_name, buff, MAXPATHLEN-1); + } +#else + ret = -1; /* always fail if not implemented */ +#endif + + if (ret == -1) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Unable to read link %s, error: %s", intern->file_name, strerror(errno)); + RETVAL_FALSE; + } else { + /* Append NULL to the end of the string */ + buff[ret] = '\0'; + + RETVAL_STRINGL(buff, ret); + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +#if (!defined(__BEOS__) && HAVE_REALPATH) || defined(ZTS) +/* {{{ proto string SplFileInfo::getRealPath() + Return the resolved path */ +SPL_METHOD(SplFileInfo, getRealPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char buff[MAXPATHLEN]; + char *filename; + zend_error_handling error_handling; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + if (intern->type == SPL_FS_DIR && !intern->file_name && intern->u.dir.entry.d_name[0]) { + spl_filesystem_object_get_file_name(intern); + } + + if (intern->orig_path) { + filename = intern->orig_path; + } else { + filename = intern->file_name; + } + + + if (filename && VCWD_REALPATH(filename, buff)) { +#ifdef ZTS + if (VCWD_ACCESS(buff, F_OK)) { + RETVAL_FALSE; + } else +#endif + RETVAL_STRING(buff); + } else { + RETVAL_FALSE; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ +#endif + +/* {{{ proto SplFileObject SplFileInfo::openFile([string mode = 'r' [, bool use_include_path [, resource context]]]) + Open the current file */ +SPL_METHOD(SplFileInfo, openFile) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + spl_filesystem_object_create_type(ZEND_NUM_ARGS(), intern, SPL_FS_FILE, NULL, return_value); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::setFileClass([string class_name]) + Class to use in openFile() */ +SPL_METHOD(SplFileInfo, setFileClass) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = spl_ce_SplFileObject; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + intern->file_class = ce; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::setInfoClass([string class_name]) + Class to use in getFileInfo(), getPathInfo() */ +SPL_METHOD(SplFileInfo, setInfoClass) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = spl_ce_SplFileInfo; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling ); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + intern->info_class = ce; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo SplFileInfo::getFileInfo([string $class_name]) + Get/copy file info */ +SPL_METHOD(SplFileInfo, getFileInfo) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = intern->info_class; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + spl_filesystem_object_create_type(ZEND_NUM_ARGS(), intern, SPL_FS_INFO, ce, return_value); + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo SplFileInfo::getPathInfo([string $class_name]) + Get/copy file info */ +SPL_METHOD(SplFileInfo, getPathInfo) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = intern->info_class; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + size_t path_len; + char *path = spl_filesystem_object_get_pathname(intern, &path_len); + if (path) { + char *dpath = estrndup(path, path_len); + path_len = php_dirname(dpath, path_len); + spl_filesystem_object_create_info(intern, dpath, path_len, 1, ce, return_value); + efree(dpath); + } + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo::_bad_state_ex(void) */ +SPL_METHOD(SplFileInfo, _bad_state_ex) +{ + zend_throw_exception_ex(spl_ce_LogicException, 0, + "The parent constructor was not called: the object is in an " + "invalid state "); +} +/* }}} */ + +/* {{{ proto void FilesystemIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(FilesystemIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS | SPL_FILE_DIR_SKIPDOTS); +} +/* }}} */ + +/* {{{ proto void FilesystemIterator::rewind() + Rewind dir back to the start */ +SPL_METHOD(FilesystemIterator, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index = 0; + if (intern->u.dir.dirp) { + php_stream_rewinddir(intern->u.dir.dirp); + } + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); +} +/* }}} */ + +/* {{{ proto int FilesystemIterator::getFlags() + Get handling flags */ +SPL_METHOD(FilesystemIterator, getFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(intern->flags & (SPL_FILE_DIR_KEY_MODE_MASK | SPL_FILE_DIR_CURRENT_MODE_MASK | SPL_FILE_DIR_OTHERS_MASK)); +} /* }}} */ + +/* {{{ proto void FilesystemIterator::setFlags(long $flags) + Set handling flags */ +SPL_METHOD(FilesystemIterator, setFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long flags; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &flags) == FAILURE) { + return; + } + + intern->flags &= ~(SPL_FILE_DIR_KEY_MODE_MASK|SPL_FILE_DIR_CURRENT_MODE_MASK|SPL_FILE_DIR_OTHERS_MASK); + intern->flags |= ((SPL_FILE_DIR_KEY_MODE_MASK|SPL_FILE_DIR_CURRENT_MODE_MASK|SPL_FILE_DIR_OTHERS_MASK) & flags); +} /* }}} */ + +/* {{{ proto bool RecursiveDirectoryIterator::hasChildren([bool $allow_links = false]) + Returns whether current entry is a directory and not '.' or '..' */ +SPL_METHOD(RecursiveDirectoryIterator, hasChildren) +{ + zend_bool allow_links = 0; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_links) == FAILURE) { + return; + } + if (spl_filesystem_is_invalid_or_dot(intern->u.dir.entry.d_name)) { + RETURN_FALSE; + } else { + spl_filesystem_object_get_file_name(intern); + if (!allow_links && !(intern->flags & SPL_FILE_DIR_FOLLOW_SYMLINKS)) { + php_stat(intern->file_name, intern->file_name_len, FS_IS_LINK, return_value); + if (zend_is_true(return_value)) { + RETURN_FALSE; + } + } + php_stat(intern->file_name, intern->file_name_len, FS_IS_DIR, return_value); + } +} +/* }}} */ + +/* {{{ proto RecursiveDirectoryIterator DirectoryIterator::getChildren() + Returns an iterator for the current entry if it is a directory */ +SPL_METHOD(RecursiveDirectoryIterator, getChildren) +{ + zval zpath, zflags; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + spl_filesystem_object *subdir; + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_file_name(intern); + + ZVAL_LONG(&zflags, intern->flags); + ZVAL_STRINGL(&zpath, intern->file_name, intern->file_name_len); + spl_instantiate_arg_ex2(Z_OBJCE_P(getThis()), return_value, &zpath, &zflags); + zval_ptr_dtor(&zpath); + zval_ptr_dtor(&zflags); + + subdir = Z_SPLFILESYSTEM_P(return_value); + if (subdir) { + if (intern->u.dir.sub_path && intern->u.dir.sub_path[0]) { + subdir->u.dir.sub_path_len = spprintf(&subdir->u.dir.sub_path, 0, "%s%c%s", intern->u.dir.sub_path, slash, intern->u.dir.entry.d_name); + } else { + subdir->u.dir.sub_path_len = strlen(intern->u.dir.entry.d_name); + subdir->u.dir.sub_path = estrndup(intern->u.dir.entry.d_name, subdir->u.dir.sub_path_len); + } + subdir->info_class = intern->info_class; + subdir->file_class = intern->file_class; + subdir->oth = intern->oth; + } +} +/* }}} */ + +/* {{{ proto void RecursiveDirectoryIterator::getSubPath() + Get sub path */ +SPL_METHOD(RecursiveDirectoryIterator, getSubPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.sub_path) { + RETURN_STRINGL(intern->u.dir.sub_path, intern->u.dir.sub_path_len); + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto void RecursiveDirectoryIterator::getSubPathname() + Get sub path and file name */ +SPL_METHOD(RecursiveDirectoryIterator, getSubPathname) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.sub_path) { + RETURN_NEW_STR(strpprintf(0, "%s%c%s", intern->u.dir.sub_path, slash, intern->u.dir.entry.d_name)); + } else { + RETURN_STRING(intern->u.dir.entry.d_name); + } +} +/* }}} */ + +/* {{{ proto int RecursiveDirectoryIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(RecursiveDirectoryIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS); +} +/* }}} */ + +#ifdef HAVE_GLOB +/* {{{ proto int GlobIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a glob expression (no glob:// needed). */ +SPL_METHOD(GlobIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS|DIT_CTOR_GLOB); +} +/* }}} */ + +/* {{{ proto int GlobIterator::cont() + Return the number of directories and files found by globbing */ +SPL_METHOD(GlobIterator, count) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.dirp && php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + RETURN_LONG(php_glob_stream_get_count(intern->u.dir.dirp, NULL)); + } else { + /* should not happen */ + php_error_docref(NULL, E_ERROR, "GlobIterator lost glob state"); + } +} +/* }}} */ +#endif /* HAVE_GLOB */ + +/* {{{ forward declarations to the iterator handlers */ +static void spl_filesystem_dir_it_dtor(zend_object_iterator *iter); +static int spl_filesystem_dir_it_valid(zend_object_iterator *iter); +static zval *spl_filesystem_dir_it_current_data(zend_object_iterator *iter); +static void spl_filesystem_dir_it_current_key(zend_object_iterator *iter, zval *key); +static void spl_filesystem_dir_it_move_forward(zend_object_iterator *iter); +static void spl_filesystem_dir_it_rewind(zend_object_iterator *iter); + +/* iterator handler table */ +zend_object_iterator_funcs spl_filesystem_dir_it_funcs = { + spl_filesystem_dir_it_dtor, + spl_filesystem_dir_it_valid, + spl_filesystem_dir_it_current_data, + spl_filesystem_dir_it_current_key, + spl_filesystem_dir_it_move_forward, + spl_filesystem_dir_it_rewind, + NULL +}; +/* }}} */ + +/* {{{ spl_ce_dir_get_iterator */ +zend_object_iterator *spl_filesystem_dir_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + spl_filesystem_iterator *iterator; + spl_filesystem_object *dir_object; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + dir_object = Z_SPLFILESYSTEM_P(object); + iterator = spl_filesystem_object_to_iterator(dir_object); + ZVAL_COPY(&iterator->intern.data, object); + iterator->intern.funcs = &spl_filesystem_dir_it_funcs; + /* ->current must be initialized; rewind doesn't set it and valid + * doesn't check whether it's set */ + iterator->current = *object; + + return &iterator->intern; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_dtor */ +static void spl_filesystem_dir_it_dtor(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + if (!Z_ISUNDEF(iterator->intern.data)) { + zval *object = &iterator->intern.data; + zval_ptr_dtor(object); + } + /* Otherwise we were called from the owning object free storage handler as + * it sets iterator->intern.data to IS_UNDEF. + * We don't even need to destroy iterator->current as we didn't add a + * reference to it in move_forward or get_iterator */ +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_valid */ +static int spl_filesystem_dir_it_valid(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + return object->u.dir.entry.d_name[0] != '\0' ? SUCCESS : FAILURE; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_current_data */ +static zval *spl_filesystem_dir_it_current_data(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + return &iterator->current; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_current_key */ +static void spl_filesystem_dir_it_current_key(zend_object_iterator *iter, zval *key) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + ZVAL_LONG(key, object->u.dir.index); +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_move_forward */ +static void spl_filesystem_dir_it_move_forward(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + object->u.dir.index++; + spl_filesystem_dir_read(object); + if (object->file_name) { + efree(object->file_name); + object->file_name = NULL; + } +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_rewind */ +static void spl_filesystem_dir_it_rewind(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + object->u.dir.index = 0; + if (object->u.dir.dirp) { + php_stream_rewinddir(object->u.dir.dirp); + } + spl_filesystem_dir_read(object); +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_dtor */ +static void spl_filesystem_tree_it_dtor(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + if (!Z_ISUNDEF(iterator->intern.data)) { + zval *object = &iterator->intern.data; + zval_ptr_dtor(object); + } else { + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_current_data */ +static zval *spl_filesystem_tree_it_current_data(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + if (SPL_FILE_DIR_CURRENT(object, SPL_FILE_DIR_CURRENT_AS_PATHNAME)) { + if (Z_ISUNDEF(iterator->current)) { + spl_filesystem_object_get_file_name(object); + ZVAL_STRINGL(&iterator->current, object->file_name, object->file_name_len); + } + return &iterator->current; + } else if (SPL_FILE_DIR_CURRENT(object, SPL_FILE_DIR_CURRENT_AS_FILEINFO)) { + if (Z_ISUNDEF(iterator->current)) { + spl_filesystem_object_get_file_name(object); + spl_filesystem_object_create_type(0, object, SPL_FS_INFO, NULL, &iterator->current); + } + return &iterator->current; + } else { + return &iterator->intern.data; + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_current_key */ +static void spl_filesystem_tree_it_current_key(zend_object_iterator *iter, zval *key) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + if (SPL_FILE_DIR_KEY(object, SPL_FILE_DIR_KEY_AS_FILENAME)) { + ZVAL_STRING(key, object->u.dir.entry.d_name); + } else { + spl_filesystem_object_get_file_name(object); + ZVAL_STRINGL(key, object->file_name, object->file_name_len); + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_move_forward */ +static void spl_filesystem_tree_it_move_forward(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + object->u.dir.index++; + do { + spl_filesystem_dir_read(object); + } while (spl_filesystem_is_dot(object->u.dir.entry.d_name)); + if (object->file_name) { + efree(object->file_name); + object->file_name = NULL; + } + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_rewind */ +static void spl_filesystem_tree_it_rewind(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + object->u.dir.index = 0; + if (object->u.dir.dirp) { + php_stream_rewinddir(object->u.dir.dirp); + } + do { + spl_filesystem_dir_read(object); + } while (spl_filesystem_is_dot(object->u.dir.entry.d_name)); + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } +} +/* }}} */ + +/* {{{ iterator handler table */ +zend_object_iterator_funcs spl_filesystem_tree_it_funcs = { + spl_filesystem_tree_it_dtor, + spl_filesystem_dir_it_valid, + spl_filesystem_tree_it_current_data, + spl_filesystem_tree_it_current_key, + spl_filesystem_tree_it_move_forward, + spl_filesystem_tree_it_rewind, + NULL +}; +/* }}} */ + +/* {{{ spl_ce_dir_get_iterator */ +zend_object_iterator *spl_filesystem_tree_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + spl_filesystem_iterator *iterator; + spl_filesystem_object *dir_object; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + dir_object = Z_SPLFILESYSTEM_P(object); + iterator = spl_filesystem_object_to_iterator(dir_object); + + ZVAL_COPY(&iterator->intern.data, object); + iterator->intern.funcs = &spl_filesystem_tree_it_funcs; + + return &iterator->intern; +} +/* }}} */ + +/* {{{ spl_filesystem_object_cast */ +static int spl_filesystem_object_cast(zval *readobj, zval *writeobj, int type) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(readobj); + + if (type == IS_STRING) { + if (Z_OBJCE_P(readobj)->__tostring) { + return std_object_handlers.cast_object(readobj, writeobj, type); + } + + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + if (readobj == writeobj) { + zval retval; + zval *retval_ptr = &retval; + + ZVAL_STRINGL(retval_ptr, intern->file_name, intern->file_name_len); + zval_ptr_dtor(readobj); + ZVAL_NEW_STR(writeobj, Z_STR_P(retval_ptr)); + } else { + ZVAL_STRINGL(writeobj, intern->file_name, intern->file_name_len); + } + return SUCCESS; + case SPL_FS_DIR: + if (readobj == writeobj) { + zval retval; + zval *retval_ptr = &retval; + + ZVAL_STRING(retval_ptr, intern->u.dir.entry.d_name); + zval_ptr_dtor(readobj); + ZVAL_NEW_STR(writeobj, Z_STR_P(retval_ptr)); + } else { + ZVAL_STRING(writeobj, intern->u.dir.entry.d_name); + } + return SUCCESS; + } + } else if (type == _IS_BOOL) { + ZVAL_TRUE(writeobj); + return SUCCESS; + } + if (readobj == writeobj) { + zval_ptr_dtor(readobj); + } + ZVAL_NULL(writeobj); + return FAILURE; +} +/* }}} */ + +/* {{{ declare method parameters */ +/* supply a name and default to call by parameter */ +ZEND_BEGIN_ARG_INFO(arginfo_info___construct, 0) + ZEND_ARG_INFO(0, file_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_info_openFile, 0, 0, 0) + ZEND_ARG_INFO(0, open_mode) + ZEND_ARG_INFO(0, use_include_path) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_info_optinalFileClass, 0, 0, 0) + ZEND_ARG_INFO(0, class_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_optinalSuffix, 0, 0, 0) + ZEND_ARG_INFO(0, suffix) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_splfileinfo_void, 0) +ZEND_END_ARG_INFO() + +/* the method table */ +/* each method can have its own parameters and visibility */ +static const zend_function_entry spl_SplFileInfo_functions[] = { + SPL_ME(SplFileInfo, __construct, arginfo_info___construct, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getExtension, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getBasename, arginfo_optinalSuffix, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPathname, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPerms, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getInode, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getSize, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getOwner, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getGroup, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getATime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getMTime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getCTime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getType, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isWritable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isReadable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isExecutable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isFile, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isDir, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isLink, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getLinkTarget, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) +#if (!defined(__BEOS__) && HAVE_REALPATH) || defined(ZTS) + SPL_ME(SplFileInfo, getRealPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) +#endif + SPL_ME(SplFileInfo, getFileInfo, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPathInfo, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, openFile, arginfo_info_openFile, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, setFileClass, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, setInfoClass, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, _bad_state_ex, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + SPL_MA(SplFileInfo, __toString, SplFileInfo, getPathname, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO(arginfo_dir___construct, 0) + ZEND_ARG_INFO(0, path) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_dir_it_seek, 0) + ZEND_ARG_INFO(0, position) +ZEND_END_ARG_INFO(); + +/* the method table */ +/* each method can have its own parameters and visibility */ +static const zend_function_entry spl_DirectoryIterator_functions[] = { + SPL_ME(DirectoryIterator, __construct, arginfo_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getExtension, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getBasename, arginfo_optinalSuffix, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, isDot, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, valid, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, seek, arginfo_dir_it_seek, ZEND_ACC_PUBLIC) + SPL_MA(DirectoryIterator, __toString, DirectoryIterator, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir___construct, 0, 0, 1) + ZEND_ARG_INFO(0, path) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir_hasChildren, 0, 0, 0) + ZEND_ARG_INFO(0, allow_links) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir_setFlags, 0, 0, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_FilesystemIterator_functions[] = { + SPL_ME(FilesystemIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, getFlags, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, setFlags, arginfo_r_dir_setFlags, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry spl_RecursiveDirectoryIterator_functions[] = { + SPL_ME(RecursiveDirectoryIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, hasChildren, arginfo_r_dir_hasChildren, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getSubPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getSubPathname,arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +#ifdef HAVE_GLOB +static const zend_function_entry spl_GlobIterator_functions[] = { + SPL_ME(GlobIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(GlobIterator, count, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +#endif +/* }}} */ + +static int spl_filesystem_file_read(spl_filesystem_object *intern, int silent) /* {{{ */ +{ + char *buf; + size_t line_len = 0; + zend_long line_add = (intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)) ? 1 : 0; + + spl_filesystem_file_free_line(intern); + + if (php_stream_eof(intern->u.file.stream)) { + if (!silent) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot read from file %s", intern->file_name); + } + return FAILURE; + } + + if (intern->u.file.max_line_len > 0) { + buf = safe_emalloc((intern->u.file.max_line_len + 1), sizeof(char), 0); + if (php_stream_get_line(intern->u.file.stream, buf, intern->u.file.max_line_len + 1, &line_len) == NULL) { + efree(buf); + buf = NULL; + } else { + buf[line_len] = '\0'; + } + } else { + buf = php_stream_get_line(intern->u.file.stream, NULL, 0, &line_len); + } + + if (!buf) { + intern->u.file.current_line = estrdup(""); + intern->u.file.current_line_len = 0; + } else { + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_DROP_NEW_LINE)) { + line_len = strcspn(buf, "\r\n"); + buf[line_len] = '\0'; + } + + intern->u.file.current_line = buf; + intern->u.file.current_line_len = line_len; + } + intern->u.file.current_line_num += line_add; + + return SUCCESS; +} /* }}} */ + +static int spl_filesystem_file_call(spl_filesystem_object *intern, zend_function *func_ptr, int pass_num_args, zval *return_value, zval *arg2) /* {{{ */ +{ + zend_fcall_info fci; + zend_fcall_info_cache fcic; + zval *zresource_ptr = &intern->u.file.zresource, retval; + int result; + int num_args = pass_num_args + (arg2 ? 2 : 1); + + zval *params = (zval*)safe_emalloc(num_args, sizeof(zval), 0); + + params[0] = *zresource_ptr; + + if (arg2) { + params[1] = *arg2; + } + + if (zend_get_parameters_array_ex(pass_num_args, params + (arg2 ? 2 : 1)) != SUCCESS) { + efree(params); + WRONG_PARAM_COUNT_WITH_RETVAL(FAILURE); + } + + ZVAL_UNDEF(&retval); + + fci.size = sizeof(fci); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = num_args; + fci.params = params; + fci.no_separation = 1; + ZVAL_STR(&fci.function_name, func_ptr->common.function_name); + + fcic.initialized = 1; + fcic.function_handler = func_ptr; + fcic.calling_scope = NULL; + fcic.called_scope = NULL; + fcic.object = NULL; + + result = zend_call_function(&fci, &fcic); + + if (result == FAILURE || Z_ISUNDEF(retval)) { + RETVAL_FALSE; + } else { + ZVAL_ZVAL(return_value, &retval, 0, 0); + } + + efree(params); + return result; +} /* }}} */ + +#define FileFunctionCall(func_name, pass_num_args, arg2) /* {{{ */ \ +{ \ + zend_function *func_ptr; \ + func_ptr = (zend_function *)zend_hash_str_find_ptr(EG(function_table), #func_name, sizeof(#func_name) - 1); \ + if (func_ptr == NULL) { \ + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Internal error, function '%s' not found. Please report", #func_name); \ + return; \ + } \ + spl_filesystem_file_call(intern, func_ptr, pass_num_args, return_value, arg2); \ +} /* }}} */ + +static int spl_filesystem_file_read_csv(spl_filesystem_object *intern, char delimiter, char enclosure, char escape, zval *return_value) /* {{{ */ +{ + int ret = SUCCESS; + zval *value; + + do { + ret = spl_filesystem_file_read(intern, 1); + } while (ret == SUCCESS && !intern->u.file.current_line_len && SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_SKIP_EMPTY)); + + if (ret == SUCCESS) { + size_t buf_len = intern->u.file.current_line_len; + char *buf = estrndup(intern->u.file.current_line, buf_len); + + if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval_ptr_dtor(&intern->u.file.current_zval); + ZVAL_UNDEF(&intern->u.file.current_zval); + } + + php_fgetcsv(intern->u.file.stream, delimiter, enclosure, escape, buf_len, buf, &intern->u.file.current_zval); + if (return_value) { + zval_ptr_dtor(return_value); + value = &intern->u.file.current_zval; + ZVAL_DEREF(value); + ZVAL_COPY(return_value, value); + } + } + return ret; +} +/* }}} */ + +static int spl_filesystem_file_read_line_ex(zval * this_ptr, spl_filesystem_object *intern, int silent) /* {{{ */ +{ + zval retval; + + /* 1) use fgetcsv? 2) overloaded call the function, 3) do it directly */ + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) || intern->u.file.func_getCurr->common.scope != spl_ce_SplFileObject) { + if (php_stream_eof(intern->u.file.stream)) { + if (!silent) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot read from file %s", intern->file_name); + } + return FAILURE; + } + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV)) { + return spl_filesystem_file_read_csv(intern, intern->u.file.delimiter, intern->u.file.enclosure, intern->u.file.escape, NULL); + } else { + zend_execute_data *execute_data = EG(current_execute_data); + zend_call_method_with_0_params(this_ptr, Z_OBJCE(EX(This)), &intern->u.file.func_getCurr, "getCurrentLine", &retval); + } + if (!Z_ISUNDEF(retval)) { + if (intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)) { + intern->u.file.current_line_num++; + } + spl_filesystem_file_free_line(intern); + if (Z_TYPE(retval) == IS_STRING) { + intern->u.file.current_line = estrndup(Z_STRVAL(retval), Z_STRLEN(retval)); + intern->u.file.current_line_len = Z_STRLEN(retval); + } else { + zval *value = &retval; + + ZVAL_DEREF(value); + ZVAL_COPY(&intern->u.file.current_zval, value); + } + zval_ptr_dtor(&retval); + return SUCCESS; + } else { + return FAILURE; + } + } else { + return spl_filesystem_file_read(intern, silent); + } +} /* }}} */ + +static int spl_filesystem_file_is_empty_line(spl_filesystem_object *intern) /* {{{ */ +{ + if (intern->u.file.current_line) { + return intern->u.file.current_line_len == 0; + } else if (!Z_ISUNDEF(intern->u.file.current_zval)) { + switch(Z_TYPE(intern->u.file.current_zval)) { + case IS_STRING: + return Z_STRLEN(intern->u.file.current_zval) == 0; + case IS_ARRAY: + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) + && zend_hash_num_elements(Z_ARRVAL(intern->u.file.current_zval)) == 1) { + uint32_t idx = 0; + zval *first; + + while (Z_ISUNDEF(Z_ARRVAL(intern->u.file.current_zval)->arData[idx].val)) { + idx++; + } + first = &Z_ARRVAL(intern->u.file.current_zval)->arData[idx].val; + return Z_TYPE_P(first) == IS_STRING && Z_STRLEN_P(first) == 0; + } + return zend_hash_num_elements(Z_ARRVAL(intern->u.file.current_zval)) == 0; + case IS_NULL: + return 1; + default: + return 0; + } + } else { + return 1; + } +} +/* }}} */ + +static int spl_filesystem_file_read_line(zval * this_ptr, spl_filesystem_object *intern, int silent) /* {{{ */ +{ + int ret = spl_filesystem_file_read_line_ex(this_ptr, intern, silent); + + while (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_SKIP_EMPTY) && ret == SUCCESS && spl_filesystem_file_is_empty_line(intern)) { + spl_filesystem_file_free_line(intern); + ret = spl_filesystem_file_read_line_ex(this_ptr, intern, silent); + } + + return ret; +} +/* }}} */ + +static void spl_filesystem_file_rewind(zval * this_ptr, spl_filesystem_object *intern) /* {{{ */ +{ + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + if (-1 == php_stream_rewind(intern->u.file.stream)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot rewind file %s", intern->file_name); + } else { + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num = 0; + } + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + spl_filesystem_file_read_line(this_ptr, intern, 1); + } +} /* }}} */ + +/* {{{ proto void SplFileObject::__construct(string filename [, string mode = 'r' [, bool use_include_path [, resource context]]]]) + Construct a new file object */ +SPL_METHOD(SplFileObject, __construct) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_bool use_include_path = 0; + char *p1, *p2; + char *tmp_path; + size_t tmp_path_len; + zend_error_handling error_handling; + + intern->u.file.open_mode = NULL; + intern->u.file.open_mode_len = 0; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|sbr!", + &intern->file_name, &intern->file_name_len, + &intern->u.file.open_mode, &intern->u.file.open_mode_len, + &use_include_path, &intern->u.file.zcontext) == FAILURE) { + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + return; + } + + if (intern->u.file.open_mode == NULL) { + intern->u.file.open_mode = "r"; + intern->u.file.open_mode_len = 1; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + if (spl_filesystem_file_open(intern, use_include_path, 0) == SUCCESS) { + tmp_path_len = strlen(intern->u.file.stream->orig_path); + + if (tmp_path_len > 1 && IS_SLASH_AT(intern->u.file.stream->orig_path, tmp_path_len-1)) { + tmp_path_len--; + } + + tmp_path = estrndup(intern->u.file.stream->orig_path, tmp_path_len); + + p1 = strrchr(tmp_path, '/'); +#if defined(PHP_WIN32) + p2 = strrchr(tmp_path, '\\'); +#else + p2 = 0; +#endif + if (p1 || p2) { + intern->_path_len = ((p1 > p2 ? p1 : p2) - tmp_path); + } else { + intern->_path_len = 0; + } + + efree(tmp_path); + + intern->_path = estrndup(intern->u.file.stream->orig_path, intern->_path_len); + } + + zend_restore_error_handling(&error_handling); + +} /* }}} */ + +/* {{{ proto void SplTempFileObject::__construct([int max_memory]) + Construct a new temp file object */ +SPL_METHOD(SplTempFileObject, __construct) +{ + zend_long max_memory = PHP_STREAM_MAX_MEM; + char tmp_fname[48]; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_error_handling error_handling; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "|l", &max_memory) == FAILURE) { + return; + } + + if (max_memory < 0) { + intern->file_name = "php://memory"; + intern->file_name_len = 12; + } else if (ZEND_NUM_ARGS()) { + intern->file_name_len = slprintf(tmp_fname, sizeof(tmp_fname), "php://temp/maxmemory:" ZEND_LONG_FMT, max_memory); + intern->file_name = tmp_fname; + } else { + intern->file_name = "php://temp"; + intern->file_name_len = 10; + } + intern->u.file.open_mode = "wb"; + intern->u.file.open_mode_len = 1; + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + if (spl_filesystem_file_open(intern, 0, 0) == SUCCESS) { + intern->_path_len = 0; + intern->_path = estrndup("", 0); + } + zend_restore_error_handling(&error_handling); +} /* }}} */ + +/* {{{ proto void SplFileObject::rewind() + Rewind the file and read the first line */ +SPL_METHOD(SplFileObject, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_file_rewind(getThis(), intern); +} /* }}} */ + +/* {{{ proto void SplFileObject::eof() + Return whether end of file is reached */ +SPL_METHOD(SplFileObject, eof) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_BOOL(php_stream_eof(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto void SplFileObject::valid() + Return !eof() */ +SPL_METHOD(SplFileObject, valid) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + RETURN_BOOL(intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)); + } else { + if(!intern->u.file.stream) { + RETURN_FALSE; + } + RETVAL_BOOL(!php_stream_eof(intern->u.file.stream)); + } +} /* }}} */ + +/* {{{ proto string SplFileObject::fgets() + Rturn next line from file */ +SPL_METHOD(SplFileObject, fgets) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (spl_filesystem_file_read(intern, 0) == FAILURE) { + RETURN_FALSE; + } + RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len); +} /* }}} */ + +/* {{{ proto string SplFileObject::current() + Return current line from file */ +SPL_METHOD(SplFileObject, current) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (!intern->u.file.current_line && Z_ISUNDEF(intern->u.file.current_zval)) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } + if (intern->u.file.current_line && (!SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) || Z_ISUNDEF(intern->u.file.current_zval))) { + RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len); + } else if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval *value = &intern->u.file.current_zval; + + ZVAL_DEREF(value); + ZVAL_COPY(return_value, value); + return; + } + RETURN_FALSE; +} /* }}} */ + +/* {{{ proto int SplFileObject::key() + Return line number */ +SPL_METHOD(SplFileObject, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + +/* Do not read the next line to support correct counting with fgetc() + if (!intern->current_line) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } */ + RETURN_LONG(intern->u.file.current_line_num); +} /* }}} */ + +/* {{{ proto void SplFileObject::next() + Read next line */ +SPL_METHOD(SplFileObject, next) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_file_free_line(intern); + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } + intern->u.file.current_line_num++; +} /* }}} */ + +/* {{{ proto void SplFileObject::setFlags(int flags) + Set file handling flags */ +SPL_METHOD(SplFileObject, setFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &intern->flags) == FAILURE) { + return; + } +} /* }}} */ + +/* {{{ proto int SplFileObject::getFlags() + Get file handling flags */ +SPL_METHOD(SplFileObject, getFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(intern->flags & SPL_FILE_OBJECT_MASK); +} /* }}} */ + +/* {{{ proto void SplFileObject::setMaxLineLen(int max_len) + Set maximum line length */ +SPL_METHOD(SplFileObject, setMaxLineLen) +{ + zend_long max_len; + + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &max_len) == FAILURE) { + return; + } + + if (max_len < 0) { + zend_throw_exception_ex(spl_ce_DomainException, 0, "Maximum line length must be greater than or equal zero"); + return; + } + + intern->u.file.max_line_len = max_len; +} /* }}} */ + +/* {{{ proto int SplFileObject::getMaxLineLen() + Get maximum line length */ +SPL_METHOD(SplFileObject, getMaxLineLen) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG((zend_long)intern->u.file.max_line_len); +} /* }}} */ + +/* {{{ proto bool SplFileObject::hasChildren() + Return false */ +SPL_METHOD(SplFileObject, hasChildren) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_FALSE; +} /* }}} */ + +/* {{{ proto bool SplFileObject::getChildren() + Read NULL */ +SPL_METHOD(SplFileObject, getChildren) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + /* return NULL */ +} /* }}} */ + +/* {{{ FileFunction */ +#define FileFunction(func_name) \ +SPL_METHOD(SplFileObject, func_name) \ +{ \ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); \ + FileFunctionCall(func_name, ZEND_NUM_ARGS(), NULL); \ +} +/* }}} */ + +/* {{{ proto array SplFileObject::fgetcsv([string delimiter [, string enclosure [, escape = '\\']]]) + Return current line as csv */ +SPL_METHOD(SplFileObject, fgetcsv) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + switch(ZEND_NUM_ARGS()) + { + case 3: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 2: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 1: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 0: + break; + } + spl_filesystem_file_read_csv(intern, delimiter, enclosure, escape, return_value); + } +} +/* }}} */ + +/* {{{ proto int SplFileObject::fputcsv(array fields, [string delimiter [, string enclosure [, string escape]]]) + Output a field array as a CSV line */ +SPL_METHOD(SplFileObject, fputcsv) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + zend_long ret; + zval *fields = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|sss", &fields, &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + switch(ZEND_NUM_ARGS()) + { + case 4: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 3: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 2: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 1: + case 0: + break; + } + ret = php_fputcsv(intern->u.file.stream, fields, delimiter, enclosure, escape); + RETURN_LONG(ret); + } +} +/* }}} */ + +/* {{{ proto void SplFileObject::setCsvControl([string delimiter [, string enclosure [, string escape ]]]) + Set the delimiter, enclosure and escape character used in fgetcsv */ +SPL_METHOD(SplFileObject, setCsvControl) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = ',', enclosure = '"', escape='\\'; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + switch(ZEND_NUM_ARGS()) + { + case 3: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 2: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 1: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 0: + break; + } + intern->u.file.delimiter = delimiter; + intern->u.file.enclosure = enclosure; + intern->u.file.escape = escape; + } +} +/* }}} */ + +/* {{{ proto array SplFileObject::getCsvControl() + Get the delimiter, enclosure and escape character used in fgetcsv */ +SPL_METHOD(SplFileObject, getCsvControl) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter[2], enclosure[2], escape[2]; + + array_init(return_value); + + delimiter[0] = intern->u.file.delimiter; + delimiter[1] = '\0'; + enclosure[0] = intern->u.file.enclosure; + enclosure[1] = '\0'; + escape[0] = intern->u.file.escape; + escape[1] = '\0'; + + add_next_index_string(return_value, delimiter); + add_next_index_string(return_value, enclosure); + add_next_index_string(return_value, escape); +} +/* }}} */ + +/* {{{ proto bool SplFileObject::flock(int operation [, int &wouldblock]) + Portable file locking */ +FileFunction(flock) +/* }}} */ + +/* {{{ proto bool SplFileObject::fflush() + Flush the file */ +SPL_METHOD(SplFileObject, fflush) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_BOOL(!php_stream_flush(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto int SplFileObject::ftell() + Return current file position */ +SPL_METHOD(SplFileObject, ftell) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long ret; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + ret = php_stream_tell(intern->u.file.stream); + + if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(ret); + } +} /* }}} */ + +/* {{{ proto int SplFileObject::fseek(int pos [, int whence = SEEK_SET]) + Return current file position */ +SPL_METHOD(SplFileObject, fseek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long pos, whence = SEEK_SET; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &pos, &whence) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + RETURN_LONG(php_stream_seek(intern->u.file.stream, pos, (int)whence)); +} /* }}} */ + +/* {{{ proto int SplFileObject::fgetc() + Get a character form the file */ +SPL_METHOD(SplFileObject, fgetc) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char buf[2]; + int result; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + + result = php_stream_getc(intern->u.file.stream); + + if (result == EOF) { + RETVAL_FALSE; + } else { + if (result == '\n') { + intern->u.file.current_line_num++; + } + buf[0] = result; + buf[1] = '\0'; + + RETURN_STRINGL(buf, 1); + } +} /* }}} */ + +/* {{{ proto string SplFileObject::fgetss([string allowable_tags]) + Get a line from file pointer and strip HTML tags */ +SPL_METHOD(SplFileObject, fgetss) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zval arg2; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (intern->u.file.max_line_len > 0) { + ZVAL_LONG(&arg2, intern->u.file.max_line_len); + } else { + ZVAL_LONG(&arg2, 1024); + } + + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num++; + + FileFunctionCall(fgetss, ZEND_NUM_ARGS(), &arg2); +} /* }}} */ + +/* {{{ proto int SplFileObject::fpassthru() + Output all remaining data from a file pointer */ +SPL_METHOD(SplFileObject, fpassthru) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_LONG(php_stream_passthru(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto bool SplFileObject::fscanf(string format [, string ...]) + Implements a mostly ANSI compatible fscanf() */ +SPL_METHOD(SplFileObject, fscanf) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num++; + + FileFunctionCall(fscanf, ZEND_NUM_ARGS(), NULL); +} +/* }}} */ + +/* {{{ proto mixed SplFileObject::fwrite(string str [, int length]) + Binary-safe file write */ +SPL_METHOD(SplFileObject, fwrite) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *str; + size_t str_len; + zend_long length = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &length) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (ZEND_NUM_ARGS() > 1) { + if (length >= 0) { + str_len = MIN((size_t)length, str_len); + } else { + /* Negative length given, nothing to write */ + str_len = 0; + } + } + if (!str_len) { + RETURN_LONG(0); + } + + RETURN_LONG(php_stream_write(intern->u.file.stream, str, str_len)); +} /* }}} */ + +SPL_METHOD(SplFileObject, fread) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long length = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &length) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (length <= 0) { + php_error_docref(NULL, E_WARNING, "Length parameter must be greater than 0"); + RETURN_FALSE; + } + + ZVAL_NEW_STR(return_value, zend_string_alloc(length, 0)); + Z_STRLEN_P(return_value) = php_stream_read(intern->u.file.stream, Z_STRVAL_P(return_value), length); + + /* needed because recv/read/gzread doesnt put a null at the end*/ + Z_STRVAL_P(return_value)[Z_STRLEN_P(return_value)] = 0; +} + +/* {{{ proto bool SplFileObject::fstat() + Stat() on a filehandle */ +FileFunction(fstat) +/* }}} */ + +/* {{{ proto bool SplFileObject::ftruncate(int size) + Truncate file to 'size' length */ +SPL_METHOD(SplFileObject, ftruncate) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long size; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &size) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (!php_stream_truncate_supported(intern->u.file.stream)) { + zend_throw_exception_ex(spl_ce_LogicException, 0, "Can't truncate file %s", intern->file_name); + RETURN_FALSE; + } + + RETURN_BOOL(0 == php_stream_truncate_set_size(intern->u.file.stream, size)); +} /* }}} */ + +/* {{{ proto void SplFileObject::seek(int line_pos) + Seek to specified line */ +SPL_METHOD(SplFileObject, seek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long line_pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &line_pos) == FAILURE) { + return; + } + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (line_pos < 0) { + zend_throw_exception_ex(spl_ce_LogicException, 0, "Can't seek file %s to negative line " ZEND_LONG_FMT, intern->file_name, line_pos); + RETURN_FALSE; + } + + spl_filesystem_file_rewind(getThis(), intern); + + while(intern->u.file.current_line_num < line_pos) { + if (spl_filesystem_file_read_line(getThis(), intern, 1) == FAILURE) { + break; + } + } +} /* }}} */ + +/* {{{ Function/Class/Method definitions */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object___construct, 0, 0, 1) + ZEND_ARG_INFO(0, file_name) + ZEND_ARG_INFO(0, open_mode) + ZEND_ARG_INFO(0, use_include_path) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_file_object_setFlags, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_file_object_setMaxLineLen, 0) + ZEND_ARG_INFO(0, max_len) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fgetcsv, 0, 0, 0) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, enclosure) + ZEND_ARG_INFO(0, escape) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fputcsv, 0, 0, 1) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, enclosure) + ZEND_ARG_INFO(0, escape) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_flock, 0, 0, 1) + ZEND_ARG_INFO(0, operation) + ZEND_ARG_INFO(1, wouldblock) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fseek, 0, 0, 1) + ZEND_ARG_INFO(0, pos) + ZEND_ARG_INFO(0, whence) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fgetss, 0, 0, 0) + ZEND_ARG_INFO(0, allowable_tags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fscanf, 0, 0, 1) + ZEND_ARG_INFO(0, format) + ZEND_ARG_VARIADIC_INFO(1, vars) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fwrite, 0, 0, 1) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fread, 0, 0, 1) + ZEND_ARG_INFO(0, length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_ftruncate, 0, 0, 1) + ZEND_ARG_INFO(0, size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_seek, 0, 0, 1) + ZEND_ARG_INFO(0, line_pos) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_SplFileObject_functions[] = { + SPL_ME(SplFileObject, __construct, arginfo_file_object___construct, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, eof, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, valid, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgets, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetcsv, arginfo_file_object_fgetcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fputcsv, arginfo_file_object_fputcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setCsvControl, arginfo_file_object_fgetcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getCsvControl, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, flock, arginfo_file_object_flock, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fflush, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, ftell, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fseek, arginfo_file_object_fseek, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetc, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fpassthru, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetss, arginfo_file_object_fgetss, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fscanf, arginfo_file_object_fscanf, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fwrite, arginfo_file_object_fwrite, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fread, arginfo_file_object_fread, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fstat, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, ftruncate, arginfo_file_object_ftruncate, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setFlags, arginfo_file_object_setFlags, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getFlags, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setMaxLineLen, arginfo_file_object_setMaxLineLen, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getMaxLineLen, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, hasChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, seek, arginfo_file_object_seek, ZEND_ACC_PUBLIC) + /* mappings */ + SPL_MA(SplFileObject, getCurrentLine, SplFileObject, fgets, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_MA(SplFileObject, __toString, SplFileObject, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_temp_file_object___construct, 0, 0, 0) + ZEND_ARG_INFO(0, max_memory) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_SplTempFileObject_functions[] = { + SPL_ME(SplTempFileObject, __construct, arginfo_temp_file_object___construct, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(spl_directory) + */ +PHP_MINIT_FUNCTION(spl_directory) +{ + REGISTER_SPL_STD_CLASS_EX(SplFileInfo, spl_filesystem_object_new, spl_SplFileInfo_functions); + memcpy(&spl_filesystem_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + spl_filesystem_object_handlers.offset = XtOffsetOf(spl_filesystem_object, std); + spl_filesystem_object_handlers.clone_obj = spl_filesystem_object_clone; + spl_filesystem_object_handlers.cast_object = spl_filesystem_object_cast; + spl_filesystem_object_handlers.get_debug_info = spl_filesystem_object_get_debug_info; + spl_filesystem_object_handlers.dtor_obj = spl_filesystem_object_destroy_object; + spl_filesystem_object_handlers.free_obj = spl_filesystem_object_free_storage; + spl_ce_SplFileInfo->serialize = zend_class_serialize_deny; + spl_ce_SplFileInfo->unserialize = zend_class_unserialize_deny; + + + REGISTER_SPL_SUB_CLASS_EX(DirectoryIterator, SplFileInfo, spl_filesystem_object_new, spl_DirectoryIterator_functions); + zend_class_implements(spl_ce_DirectoryIterator, 1, zend_ce_iterator); + REGISTER_SPL_IMPLEMENTS(DirectoryIterator, SeekableIterator); + + spl_ce_DirectoryIterator->get_iterator = spl_filesystem_dir_get_iterator; + + REGISTER_SPL_SUB_CLASS_EX(FilesystemIterator, DirectoryIterator, spl_filesystem_object_new, spl_FilesystemIterator_functions); + + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_MODE_MASK", SPL_FILE_DIR_CURRENT_MODE_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_PATHNAME", SPL_FILE_DIR_CURRENT_AS_PATHNAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_FILEINFO", SPL_FILE_DIR_CURRENT_AS_FILEINFO); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_SELF", SPL_FILE_DIR_CURRENT_AS_SELF); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_MODE_MASK", SPL_FILE_DIR_KEY_MODE_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_AS_PATHNAME", SPL_FILE_DIR_KEY_AS_PATHNAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "FOLLOW_SYMLINKS", SPL_FILE_DIR_FOLLOW_SYMLINKS); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_AS_FILENAME", SPL_FILE_DIR_KEY_AS_FILENAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "NEW_CURRENT_AND_KEY", SPL_FILE_DIR_KEY_AS_FILENAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "OTHER_MODE_MASK", SPL_FILE_DIR_OTHERS_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "SKIP_DOTS", SPL_FILE_DIR_SKIPDOTS); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "UNIX_PATHS", SPL_FILE_DIR_UNIXPATHS); + + spl_ce_FilesystemIterator->get_iterator = spl_filesystem_tree_get_iterator; + + REGISTER_SPL_SUB_CLASS_EX(RecursiveDirectoryIterator, FilesystemIterator, spl_filesystem_object_new, spl_RecursiveDirectoryIterator_functions); + REGISTER_SPL_IMPLEMENTS(RecursiveDirectoryIterator, RecursiveIterator); + + memcpy(&spl_filesystem_object_check_handlers, &spl_filesystem_object_handlers, sizeof(zend_object_handlers)); + spl_filesystem_object_check_handlers.get_method = spl_filesystem_object_get_method_check; + +#ifdef HAVE_GLOB + REGISTER_SPL_SUB_CLASS_EX(GlobIterator, FilesystemIterator, spl_filesystem_object_new_check, spl_GlobIterator_functions); + REGISTER_SPL_IMPLEMENTS(GlobIterator, Countable); +#endif + + REGISTER_SPL_SUB_CLASS_EX(SplFileObject, SplFileInfo, spl_filesystem_object_new_check, spl_SplFileObject_functions); + REGISTER_SPL_IMPLEMENTS(SplFileObject, RecursiveIterator); + REGISTER_SPL_IMPLEMENTS(SplFileObject, SeekableIterator); + + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "DROP_NEW_LINE", SPL_FILE_OBJECT_DROP_NEW_LINE); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "READ_AHEAD", SPL_FILE_OBJECT_READ_AHEAD); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "SKIP_EMPTY", SPL_FILE_OBJECT_SKIP_EMPTY); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "READ_CSV", SPL_FILE_OBJECT_READ_CSV); + + REGISTER_SPL_SUB_CLASS_EX(SplTempFileObject, SplFileObject, spl_filesystem_object_new_check, spl_SplTempFileObject_functions); + return SUCCESS; +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/spl/tests/bug78863.phpt b/ext/spl/tests/bug78863.phpt new file mode 100644 index 0000000000000..dc88d98deee8c --- /dev/null +++ b/ext/spl/tests/bug78863.phpt @@ -0,0 +1,31 @@ +--TEST-- +Bug #78863 (DirectoryIterator class silently truncates after a null byte) +--FILE-- +isDot()) { + var_dump($fileinfo->getFilename()); + } +} +?> +--EXPECTF-- +Fatal error: Uncaught UnexpectedValueException: DirectoryIterator::__construct() expects parameter 1 to be a valid path, string given in %s:%d +Stack trace: +#0 %s(%d): DirectoryIterator->__construct('%s') +#1 {main} + thrown in %s on line %d +--CLEAN-- + From 473cc45ba60640b1959064554f0e60ec84333214 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:55 +0000 Subject: [PATCH 10/25] commit patch 20044069 --- ext/bcmath/libbcmath/src/str2num.c | 4 ++-- ext/bcmath/tests/bug78878.phpt | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 ext/bcmath/tests/bug78878.phpt diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c index 62544de80ec9f..76b71a7e93b9b 100644 --- a/ext/bcmath/libbcmath/src/str2num.c +++ b/ext/bcmath/libbcmath/src/str2num.c @@ -57,9 +57,9 @@ bc_str2num (bc_num *num, char *str, int scale) zero_int = FALSE; if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ while (*ptr == '0') ptr++; /* Skip leading zeros. */ - while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ + while (*ptr >= '0' && *ptr <= '9') ptr++, digits++; /* digits */ if (*ptr == '.') ptr++; /* decimal point */ - while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ + while (*ptr >= '0' && *ptr <= '9') ptr++, strscale++; /* digits */ if ((*ptr != '\0') || (digits+strscale == 0)) { *num = bc_copy_num (BCG(_zero_)); diff --git a/ext/bcmath/tests/bug78878.phpt b/ext/bcmath/tests/bug78878.phpt new file mode 100644 index 0000000000000..2c9d72b94680d --- /dev/null +++ b/ext/bcmath/tests/bug78878.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #78878 (Buffer underflow in bc_shift_addsub) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bc math warning: non-zero scale in modulus +0 From d22d0703a05e14ec7ed2509474755542b88f70f7 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:57 +0000 Subject: [PATCH 11/25] commit patch 27082416 --- ext/exif/exif.c | 3 ++- ext/exif/exif.c.orig | 14 +++++++------- ext/exif/tests/bug78910.phpt | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 ext/exif/tests/bug78910.phpt diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 602f43f6b79ac..ecc9ddf59099b 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -3143,7 +3143,8 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu continue; if (maker_note->model && (!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) continue; - if (maker_note->id_string && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) + if (maker_note->id_string && value_len >= maker_note->id_string_len + && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) continue; break; } diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig index 8c54e1942cf63..8fb05f15002b4 100644 --- a/ext/exif/exif.c.orig +++ b/ext/exif/exif.c.orig @@ -3978,10 +3978,10 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse tag_table_type tag_table = exif_get_tag_table(section_index); if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { - return FALSE; - } + return FALSE; + } - if (ImageInfo->FileSize >= dir_offset+2) { + if (ImageInfo->FileSize >= 2 && ImageInfo->FileSize - 2 >= dir_offset) { sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); @@ -3989,8 +3989,8 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); - dir_size = 2/*num dir entries*/ +12/*length of entry*/*num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; - if (ImageInfo->FileSize >= dir_offset+dir_size) { + dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; + if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X), IFD entries(%d)", ImageInfo->FileSize, dir_offset+2, dir_size-2, num_entries); #endif @@ -4073,9 +4073,9 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse } } } - if (ImageInfo->FileSize >= dir_offset + ImageInfo->file.list[sn].size) { + if (ImageInfo->FileSize >= ImageInfo->file.list[sn].size && ImageInfo->FileSize - ImageInfo->file.list[sn].size >= dir_offset) { if (ifd_size > dir_size) { - if (dir_offset + ifd_size > ImageInfo->FileSize) { + if (ImageInfo->FileSize < ifd_size || dir_offset > ImageInfo->FileSize - ifd_size) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); return FALSE; } diff --git a/ext/exif/tests/bug78910.phpt b/ext/exif/tests/bug78910.phpt new file mode 100644 index 0000000000000..f5b1c32c1bd02 --- /dev/null +++ b/ext/exif/tests/bug78910.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #78910: Heap-buffer-overflow READ in exif (OSS-Fuzz #19044) +--FILE-- + +--EXPECTF-- +Notice: exif_read_data(): Read from TIFF: tag(0x927C, MakerNote ): Illegal format code 0x2020, switching to BYTE in %s on line %d + +Warning: exif_read_data(): Process tag(x927C=MakerNote ): Illegal format code 0x2020, suppose BYTE in %s on line %d + +Warning: exif_read_data(): IFD data too short: 0x0000 offset 0x000C in %s on line %d + +Warning: exif_read_data(): Invalid TIFF file in %s on line %d +bool(false) From a6c57a1509418bb6f6b23a5a0ddca7aef1d6ea71 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:23:59 +0000 Subject: [PATCH 12/25] commit patch 26130911 --- ext/standard/string.c | 6 +- ext/standard/string.c.orig | 5856 +++++++++++++++++++++++++ ext/standard/tests/file/bug79099.phpt | 32 + 3 files changed, 5891 insertions(+), 3 deletions(-) create mode 100644 ext/standard/string.c.orig create mode 100644 ext/standard/tests/file/bug79099.phpt diff --git a/ext/standard/string.c b/ext/standard/string.c index 6ffc2ae2bb023..8236d6f903e66 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4844,7 +4844,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, uint8_t *stateptr, const if (state == 4) { /* Inside */ break; - } else if (state == 2 && *(p-1) != '\\') { + } else if (state == 2 && p >= buf + 1 && *(p-1) != '\\') { if (lc == c) { lc = '\0'; } else if (lc != '\\') { @@ -4871,7 +4871,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, uint8_t *stateptr, const case '!': /* JavaScript & Other HTML scripting languages */ - if (state == 1 && *(p-1) == '<') { + if (state == 1 && p >= buf + 1 && *(p-1) == '<') { state = 3; lc = c; } else { @@ -4898,7 +4898,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, uint8_t *stateptr, const case '?': - if (state == 1 && *(p-1) == '<') { + if (state == 1 && p >= buf + 1 && *(p-1) == '<') { br=0; state=2; break; diff --git a/ext/standard/string.c.orig b/ext/standard/string.c.orig new file mode 100644 index 0000000000000..6ffc2ae2bb023 --- /dev/null +++ b/ext/standard/string.c.orig @@ -0,0 +1,5856 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Rasmus Lerdorf | + | Stig S�ther Bakken | + | Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include "php.h" +#include "php_rand.h" +#include "php_string.h" +#include "php_variables.h" +#ifdef HAVE_LOCALE_H +# include +#endif +#ifdef HAVE_LANGINFO_H +# include +#endif +#ifdef HAVE_MONETARY_H +# include +#endif +/* + * This define is here because some versions of libintl redefine setlocale + * to point to libintl_setlocale. That's a ridiculous thing to do as far + * as I am concerned, but with this define and the subsequent undef we + * limit the damage to just the actual setlocale() call in this file + * without turning zif_setlocale into zif_libintl_setlocale. -Rasmus + */ +#define php_my_setlocale setlocale +#ifdef HAVE_LIBINTL +# include /* For LC_MESSAGES */ + #ifdef setlocale + # undef setlocale + #endif +#endif + +#include "scanf.h" +#include "zend_API.h" +#include "zend_execute.h" +#include "php_globals.h" +#include "basic_functions.h" +#include "zend_smart_str.h" +#include +#ifdef ZTS +#include "TSRM.h" +#endif + +/* For str_getcsv() support */ +#include "ext/standard/file.h" +/* For php_next_utf8_char() */ +#include "ext/standard/html.h" + +#define STR_PAD_LEFT 0 +#define STR_PAD_RIGHT 1 +#define STR_PAD_BOTH 2 +#define PHP_PATHINFO_DIRNAME 1 +#define PHP_PATHINFO_BASENAME 2 +#define PHP_PATHINFO_EXTENSION 4 +#define PHP_PATHINFO_FILENAME 8 +#define PHP_PATHINFO_ALL (PHP_PATHINFO_DIRNAME | PHP_PATHINFO_BASENAME | PHP_PATHINFO_EXTENSION | PHP_PATHINFO_FILENAME) + +#define STR_STRSPN 0 +#define STR_STRCSPN 1 + +/* {{{ register_string_constants + */ +void register_string_constants(INIT_FUNC_ARGS) +{ + REGISTER_LONG_CONSTANT("STR_PAD_LEFT", STR_PAD_LEFT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STR_PAD_RIGHT", STR_PAD_RIGHT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STR_PAD_BOTH", STR_PAD_BOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_DIRNAME", PHP_PATHINFO_DIRNAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_BASENAME", PHP_PATHINFO_BASENAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_EXTENSION", PHP_PATHINFO_EXTENSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_FILENAME", PHP_PATHINFO_FILENAME, CONST_CS | CONST_PERSISTENT); + +#ifdef HAVE_LOCALECONV + /* If last members of struct lconv equal CHAR_MAX, no grouping is done */ + +/* This is bad, but since we are going to be hardcoding in the POSIX stuff anyway... */ +# ifndef HAVE_LIMITS_H +# define CHAR_MAX 127 +# endif + + REGISTER_LONG_CONSTANT("CHAR_MAX", CHAR_MAX, CONST_CS | CONST_PERSISTENT); +#endif + +#ifdef HAVE_LOCALE_H + REGISTER_LONG_CONSTANT("LC_CTYPE", LC_CTYPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_NUMERIC", LC_NUMERIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_TIME", LC_TIME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_COLLATE", LC_COLLATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_MONETARY", LC_MONETARY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_ALL", LC_ALL, CONST_CS | CONST_PERSISTENT); +# ifdef LC_MESSAGES + REGISTER_LONG_CONSTANT("LC_MESSAGES", LC_MESSAGES, CONST_CS | CONST_PERSISTENT); +# endif +#endif + +} +/* }}} */ + +int php_tag_find(char *tag, size_t len, const char *set); + +#ifdef PHP_WIN32 +# define SET_ALIGNED(alignment, decl) __declspec(align(alignment)) decl +#elif HAVE_ATTRIBUTE_ALIGNED +# define SET_ALIGNED(alignment, decl) decl __attribute__ ((__aligned__ (alignment))) +#else +# define SET_ALIGNED(alignment, decl) decl +#endif + +/* this is read-only, so it's ok */ +SET_ALIGNED(16, static char hexconvtab[]) = "0123456789abcdef"; + +/* localeconv mutex */ +#ifdef ZTS +static MUTEX_T locale_mutex = NULL; +#endif + +/* {{{ php_bin2hex + */ +static zend_string *php_bin2hex(const unsigned char *old, const size_t oldlen) +{ + zend_string *result; + size_t i, j; + + result = zend_string_safe_alloc(oldlen, 2 * sizeof(char), 0, 0); + + for (i = j = 0; i < oldlen; i++) { + ZSTR_VAL(result)[j++] = hexconvtab[old[i] >> 4]; + ZSTR_VAL(result)[j++] = hexconvtab[old[i] & 15]; + } + ZSTR_VAL(result)[j] = '\0'; + + return result; +} +/* }}} */ + +/* {{{ php_hex2bin + */ +static zend_string *php_hex2bin(const unsigned char *old, const size_t oldlen) +{ + size_t target_length = oldlen >> 1; + zend_string *str = zend_string_alloc(target_length, 0); + unsigned char *ret = (unsigned char *)ZSTR_VAL(str); + size_t i, j; + + for (i = j = 0; i < target_length; i++) { + unsigned char c = old[j++]; + unsigned char l = c & ~0x20; + int is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1); + unsigned char d; + + /* basically (c >= '0' && c <= '9') || (l >= 'A' && l <= 'F') */ + if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) { + d = (l - 0x10 - 0x27 * is_letter) << 4; + } else { + zend_string_free(str); + return NULL; + } + c = old[j++]; + l = c & ~0x20; + is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1); + if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) { + d |= l - 0x10 - 0x27 * is_letter; + } else { + zend_string_free(str); + return NULL; + } + ret[i] = d; + } + ret[i] = '\0'; + + return str; +} +/* }}} */ + +#ifdef HAVE_LOCALECONV +/* {{{ localeconv_r + * glibc's localeconv is not reentrant, so lets make it so ... sorta */ +PHPAPI struct lconv *localeconv_r(struct lconv *out) +{ + +# ifdef ZTS + tsrm_mutex_lock( locale_mutex ); +# endif + +/* cur->locinfo is struct __crt_locale_info which implementation is + hidden in vc14. TODO revisit this and check if a workaround available + and needed. */ +#if defined(PHP_WIN32) && _MSC_VER < 1900 && defined(ZTS) + { + /* Even with the enabled per thread locale, localeconv + won't check any locale change in the master thread. */ + _locale_t cur = _get_current_locale(); + *out = *cur->locinfo->lconv; + _free_locale(cur); + } +#else + /* localeconv doesn't return an error condition */ + *out = *localeconv(); +#endif + +# ifdef ZTS + tsrm_mutex_unlock( locale_mutex ); +# endif + + return out; +} +/* }}} */ + +# ifdef ZTS +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(localeconv) +{ + locale_mutex = tsrm_mutex_alloc(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(localeconv) +{ + tsrm_mutex_free( locale_mutex ); + locale_mutex = NULL; + return SUCCESS; +} +/* }}} */ +# endif +#endif + +/* {{{ proto string bin2hex(string data) + Converts the binary representation of data to hex */ +PHP_FUNCTION(bin2hex) +{ + zend_string *result; + zend_string *data; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(data) + ZEND_PARSE_PARAMETERS_END(); + + result = php_bin2hex((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data)); + + if (!result) { + RETURN_FALSE; + } + + RETURN_STR(result); +} +/* }}} */ + +/* {{{ proto string hex2bin(string data) + Converts the hex representation of data to binary */ +PHP_FUNCTION(hex2bin) +{ + zend_string *result, *data; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(data) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(data) % 2 != 0) { + php_error_docref(NULL, E_WARNING, "Hexadecimal input string must have an even length"); + RETURN_FALSE; + } + + result = php_hex2bin((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data)); + + if (!result) { + php_error_docref(NULL, E_WARNING, "Input string must be hexadecimal string"); + RETURN_FALSE; + } + + RETVAL_STR(result); +} +/* }}} */ + +static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */ +{ + zend_string *s11, *s22; + zend_long start = 0, len = 0; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(s11) + Z_PARAM_STR(s22) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(start) + Z_PARAM_LONG(len) + ZEND_PARSE_PARAMETERS_END(); + + if (ZEND_NUM_ARGS() < 4) { + len = ZSTR_LEN(s11); + } + + /* look at substr() function for more information */ + + if (start < 0) { + start += (zend_long)ZSTR_LEN(s11); + if (start < 0) { + start = 0; + } + } else if ((size_t)start > ZSTR_LEN(s11)) { + RETURN_FALSE; + } + + if (len < 0) { + len += (ZSTR_LEN(s11) - start); + if (len < 0) { + len = 0; + } + } + + if (len > (zend_long)ZSTR_LEN(s11) - start) { + len = ZSTR_LEN(s11) - start; + } + + if(len == 0) { + RETURN_LONG(0); + } + + if (behavior == STR_STRSPN) { + RETURN_LONG(php_strspn(ZSTR_VAL(s11) + start /*str1_start*/, + ZSTR_VAL(s22) /*str2_start*/, + ZSTR_VAL(s11) + start + len /*str1_end*/, + ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/)); + } else if (behavior == STR_STRCSPN) { + RETURN_LONG(php_strcspn(ZSTR_VAL(s11) + start /*str1_start*/, + ZSTR_VAL(s22) /*str2_start*/, + ZSTR_VAL(s11) + start + len /*str1_end*/, + ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/)); + } + +} +/* }}} */ + +/* {{{ proto int strspn(string str, string mask [, int start [, int len]]) + Finds length of initial segment consisting entirely of characters found in mask. If start or/and length is provided works like strspn(substr($s,$start,$len),$good_chars) */ +PHP_FUNCTION(strspn) +{ + php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRSPN); +} +/* }}} */ + +/* {{{ proto int strcspn(string str, string mask [, int start [, int len]]) + Finds length of initial segment consisting entirely of characters not found in mask. If start or/and length is provide works like strcspn(substr($s,$start,$len),$bad_chars) */ +PHP_FUNCTION(strcspn) +{ + php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRCSPN); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(nl_langinfo) */ +#if HAVE_NL_LANGINFO +PHP_MINIT_FUNCTION(nl_langinfo) +{ +#define REGISTER_NL_LANGINFO_CONSTANT(x) REGISTER_LONG_CONSTANT(#x, x, CONST_CS | CONST_PERSISTENT) +#ifdef ABDAY_1 + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_1); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_2); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_3); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_4); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_5); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_6); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_7); +#endif +#ifdef DAY_1 + REGISTER_NL_LANGINFO_CONSTANT(DAY_1); + REGISTER_NL_LANGINFO_CONSTANT(DAY_2); + REGISTER_NL_LANGINFO_CONSTANT(DAY_3); + REGISTER_NL_LANGINFO_CONSTANT(DAY_4); + REGISTER_NL_LANGINFO_CONSTANT(DAY_5); + REGISTER_NL_LANGINFO_CONSTANT(DAY_6); + REGISTER_NL_LANGINFO_CONSTANT(DAY_7); +#endif +#ifdef ABMON_1 + REGISTER_NL_LANGINFO_CONSTANT(ABMON_1); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_2); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_3); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_4); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_5); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_6); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_7); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_8); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_9); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_10); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_11); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_12); +#endif +#ifdef MON_1 + REGISTER_NL_LANGINFO_CONSTANT(MON_1); + REGISTER_NL_LANGINFO_CONSTANT(MON_2); + REGISTER_NL_LANGINFO_CONSTANT(MON_3); + REGISTER_NL_LANGINFO_CONSTANT(MON_4); + REGISTER_NL_LANGINFO_CONSTANT(MON_5); + REGISTER_NL_LANGINFO_CONSTANT(MON_6); + REGISTER_NL_LANGINFO_CONSTANT(MON_7); + REGISTER_NL_LANGINFO_CONSTANT(MON_8); + REGISTER_NL_LANGINFO_CONSTANT(MON_9); + REGISTER_NL_LANGINFO_CONSTANT(MON_10); + REGISTER_NL_LANGINFO_CONSTANT(MON_11); + REGISTER_NL_LANGINFO_CONSTANT(MON_12); +#endif +#ifdef AM_STR + REGISTER_NL_LANGINFO_CONSTANT(AM_STR); +#endif +#ifdef PM_STR + REGISTER_NL_LANGINFO_CONSTANT(PM_STR); +#endif +#ifdef D_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(D_T_FMT); +#endif +#ifdef D_FMT + REGISTER_NL_LANGINFO_CONSTANT(D_FMT); +#endif +#ifdef T_FMT + REGISTER_NL_LANGINFO_CONSTANT(T_FMT); +#endif +#ifdef T_FMT_AMPM + REGISTER_NL_LANGINFO_CONSTANT(T_FMT_AMPM); +#endif +#ifdef ERA + REGISTER_NL_LANGINFO_CONSTANT(ERA); +#endif +#ifdef ERA_YEAR + REGISTER_NL_LANGINFO_CONSTANT(ERA_YEAR); +#endif +#ifdef ERA_D_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_D_T_FMT); +#endif +#ifdef ERA_D_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_D_FMT); +#endif +#ifdef ERA_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_T_FMT); +#endif +#ifdef ALT_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(ALT_DIGITS); +#endif +#ifdef INT_CURR_SYMBOL + REGISTER_NL_LANGINFO_CONSTANT(INT_CURR_SYMBOL); +#endif +#ifdef CURRENCY_SYMBOL + REGISTER_NL_LANGINFO_CONSTANT(CURRENCY_SYMBOL); +#endif +#ifdef CRNCYSTR + REGISTER_NL_LANGINFO_CONSTANT(CRNCYSTR); +#endif +#ifdef MON_DECIMAL_POINT + REGISTER_NL_LANGINFO_CONSTANT(MON_DECIMAL_POINT); +#endif +#ifdef MON_THOUSANDS_SEP + REGISTER_NL_LANGINFO_CONSTANT(MON_THOUSANDS_SEP); +#endif +#ifdef MON_GROUPING + REGISTER_NL_LANGINFO_CONSTANT(MON_GROUPING); +#endif +#ifdef POSITIVE_SIGN + REGISTER_NL_LANGINFO_CONSTANT(POSITIVE_SIGN); +#endif +#ifdef NEGATIVE_SIGN + REGISTER_NL_LANGINFO_CONSTANT(NEGATIVE_SIGN); +#endif +#ifdef INT_FRAC_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(INT_FRAC_DIGITS); +#endif +#ifdef FRAC_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(FRAC_DIGITS); +#endif +#ifdef P_CS_PRECEDES + REGISTER_NL_LANGINFO_CONSTANT(P_CS_PRECEDES); +#endif +#ifdef P_SEP_BY_SPACE + REGISTER_NL_LANGINFO_CONSTANT(P_SEP_BY_SPACE); +#endif +#ifdef N_CS_PRECEDES + REGISTER_NL_LANGINFO_CONSTANT(N_CS_PRECEDES); +#endif +#ifdef N_SEP_BY_SPACE + REGISTER_NL_LANGINFO_CONSTANT(N_SEP_BY_SPACE); +#endif +#ifdef P_SIGN_POSN + REGISTER_NL_LANGINFO_CONSTANT(P_SIGN_POSN); +#endif +#ifdef N_SIGN_POSN + REGISTER_NL_LANGINFO_CONSTANT(N_SIGN_POSN); +#endif +#ifdef DECIMAL_POINT + REGISTER_NL_LANGINFO_CONSTANT(DECIMAL_POINT); +#endif +#ifdef RADIXCHAR + REGISTER_NL_LANGINFO_CONSTANT(RADIXCHAR); +#endif +#ifdef THOUSANDS_SEP + REGISTER_NL_LANGINFO_CONSTANT(THOUSANDS_SEP); +#endif +#ifdef THOUSEP + REGISTER_NL_LANGINFO_CONSTANT(THOUSEP); +#endif +#ifdef GROUPING + REGISTER_NL_LANGINFO_CONSTANT(GROUPING); +#endif +#ifdef YESEXPR + REGISTER_NL_LANGINFO_CONSTANT(YESEXPR); +#endif +#ifdef NOEXPR + REGISTER_NL_LANGINFO_CONSTANT(NOEXPR); +#endif +#ifdef YESSTR + REGISTER_NL_LANGINFO_CONSTANT(YESSTR); +#endif +#ifdef NOSTR + REGISTER_NL_LANGINFO_CONSTANT(NOSTR); +#endif +#ifdef CODESET + REGISTER_NL_LANGINFO_CONSTANT(CODESET); +#endif +#undef REGISTER_NL_LANGINFO_CONSTANT + return SUCCESS; +} +/* }}} */ + +/* {{{ proto string nl_langinfo(int item) + Query language and locale information */ +PHP_FUNCTION(nl_langinfo) +{ + zend_long item; + char *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(item) + ZEND_PARSE_PARAMETERS_END(); + + switch(item) { /* {{{ */ +#ifdef ABDAY_1 + case ABDAY_1: + case ABDAY_2: + case ABDAY_3: + case ABDAY_4: + case ABDAY_5: + case ABDAY_6: + case ABDAY_7: +#endif +#ifdef DAY_1 + case DAY_1: + case DAY_2: + case DAY_3: + case DAY_4: + case DAY_5: + case DAY_6: + case DAY_7: +#endif +#ifdef ABMON_1 + case ABMON_1: + case ABMON_2: + case ABMON_3: + case ABMON_4: + case ABMON_5: + case ABMON_6: + case ABMON_7: + case ABMON_8: + case ABMON_9: + case ABMON_10: + case ABMON_11: + case ABMON_12: +#endif +#ifdef MON_1 + case MON_1: + case MON_2: + case MON_3: + case MON_4: + case MON_5: + case MON_6: + case MON_7: + case MON_8: + case MON_9: + case MON_10: + case MON_11: + case MON_12: +#endif +#ifdef AM_STR + case AM_STR: +#endif +#ifdef PM_STR + case PM_STR: +#endif +#ifdef D_T_FMT + case D_T_FMT: +#endif +#ifdef D_FMT + case D_FMT: +#endif +#ifdef T_FMT + case T_FMT: +#endif +#ifdef T_FMT_AMPM + case T_FMT_AMPM: +#endif +#ifdef ERA + case ERA: +#endif +#ifdef ERA_YEAR + case ERA_YEAR: +#endif +#ifdef ERA_D_T_FMT + case ERA_D_T_FMT: +#endif +#ifdef ERA_D_FMT + case ERA_D_FMT: +#endif +#ifdef ERA_T_FMT + case ERA_T_FMT: +#endif +#ifdef ALT_DIGITS + case ALT_DIGITS: +#endif +#ifdef INT_CURR_SYMBOL + case INT_CURR_SYMBOL: +#endif +#ifdef CURRENCY_SYMBOL + case CURRENCY_SYMBOL: +#endif +#ifdef CRNCYSTR + case CRNCYSTR: +#endif +#ifdef MON_DECIMAL_POINT + case MON_DECIMAL_POINT: +#endif +#ifdef MON_THOUSANDS_SEP + case MON_THOUSANDS_SEP: +#endif +#ifdef MON_GROUPING + case MON_GROUPING: +#endif +#ifdef POSITIVE_SIGN + case POSITIVE_SIGN: +#endif +#ifdef NEGATIVE_SIGN + case NEGATIVE_SIGN: +#endif +#ifdef INT_FRAC_DIGITS + case INT_FRAC_DIGITS: +#endif +#ifdef FRAC_DIGITS + case FRAC_DIGITS: +#endif +#ifdef P_CS_PRECEDES + case P_CS_PRECEDES: +#endif +#ifdef P_SEP_BY_SPACE + case P_SEP_BY_SPACE: +#endif +#ifdef N_CS_PRECEDES + case N_CS_PRECEDES: +#endif +#ifdef N_SEP_BY_SPACE + case N_SEP_BY_SPACE: +#endif +#ifdef P_SIGN_POSN + case P_SIGN_POSN: +#endif +#ifdef N_SIGN_POSN + case N_SIGN_POSN: +#endif +#ifdef DECIMAL_POINT + case DECIMAL_POINT: +#elif defined(RADIXCHAR) + case RADIXCHAR: +#endif +#ifdef THOUSANDS_SEP + case THOUSANDS_SEP: +#elif defined(THOUSEP) + case THOUSEP: +#endif +#ifdef GROUPING + case GROUPING: +#endif +#ifdef YESEXPR + case YESEXPR: +#endif +#ifdef NOEXPR + case NOEXPR: +#endif +#ifdef YESSTR + case YESSTR: +#endif +#ifdef NOSTR + case NOSTR: +#endif +#ifdef CODESET + case CODESET: +#endif + break; + default: + php_error_docref(NULL, E_WARNING, "Item '" ZEND_LONG_FMT "' is not valid", item); + RETURN_FALSE; + } + /* }}} */ + + value = nl_langinfo(item); + if (value == NULL) { + RETURN_FALSE; + } else { + RETURN_STRING(value); + } +} +#endif +/* }}} */ + +#ifdef HAVE_STRCOLL +/* {{{ proto int strcoll(string str1, string str2) + Compares two strings using the current locale */ +PHP_FUNCTION(strcoll) +{ + zend_string *s1, *s2; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(s1) + Z_PARAM_STR(s2) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_LONG(strcoll((const char *) ZSTR_VAL(s1), + (const char *) ZSTR_VAL(s2))); +} +/* }}} */ +#endif + +/* {{{ php_charmask + * Fills a 256-byte bytemask with input. You can specify a range like 'a..z', + * it needs to be incrementing. + * Returns: FAILURE/SUCCESS whether the input was correct (i.e. no range errors) + */ +static inline int php_charmask(unsigned char *input, size_t len, char *mask) +{ + unsigned char *end; + unsigned char c; + int result = SUCCESS; + + memset(mask, 0, 256); + for (end = input+len; input < end; input++) { + c=*input; + if ((input+3 < end) && input[1] == '.' && input[2] == '.' + && input[3] >= c) { + memset(mask+c, 1, input[3] - c + 1); + input+=3; + } else if ((input+1 < end) && input[0] == '.' && input[1] == '.') { + /* Error, try to be as helpful as possible: + (a range ending/starting with '.' won't be captured here) */ + if (end-len >= input) { /* there was no 'left' char */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the left of '..'"); + result = FAILURE; + continue; + } + if (input+2 >= end) { /* there is no 'right' char */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the right of '..'"); + result = FAILURE; + continue; + } + if (input[-1] > input[2]) { /* wrong order */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing"); + result = FAILURE; + continue; + } + /* FIXME: better error (a..b..c is the only left possibility?) */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range"); + result = FAILURE; + continue; + } else { + mask[c]=1; + } + } + return result; +} +/* }}} */ + +/* {{{ php_trim_int() + * mode 1 : trim left + * mode 2 : trim right + * mode 3 : trim left and right + * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0') + */ +static zend_always_inline zend_string *php_trim_int(zend_string *str, char *what, size_t what_len, int mode) +{ + const char *start = ZSTR_VAL(str); + const char *end = start + ZSTR_LEN(str); + char mask[256]; + + if (what) { + if (what_len == 1) { + char p = *what; + if (mode & 1) { + while (start != end) { + if (*start == p) { + start++; + } else { + break; + } + } + } + if (mode & 2) { + while (start != end) { + if (*(end-1) == p) { + end--; + } else { + break; + } + } + } + } else { + php_charmask((unsigned char*)what, what_len, mask); + + if (mode & 1) { + while (start != end) { + if (mask[(unsigned char)*start]) { + start++; + } else { + break; + } + } + } + if (mode & 2) { + while (start != end) { + if (mask[(unsigned char)*(end-1)]) { + end--; + } else { + break; + } + } + } + } + } else { + if (mode & 1) { + while (start != end) { + unsigned char c = (unsigned char)*start; + + if (c <= ' ' && + (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) { + start++; + } else { + break; + } + } + } + if (mode & 2) { + while (start != end) { + unsigned char c = (unsigned char)*(end-1); + + if (c <= ' ' && + (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) { + end--; + } else { + break; + } + } + } + } + + if (ZSTR_LEN(str) == end - start) { + return zend_string_copy(str); + } else if (end - start == 0) { + return ZSTR_EMPTY_ALLOC(); + } else { + return zend_string_init(start, end - start, 0); + } +} +/* }}} */ + +/* {{{ php_trim_int() + * mode 1 : trim left + * mode 2 : trim right + * mode 3 : trim left and right + * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0') + */ +PHPAPI zend_string *php_trim(zend_string *str, char *what, size_t what_len, int mode) +{ + return php_trim_int(str, what, what_len, mode); +} +/* }}} */ + +/* {{{ php_do_trim + * Base for trim(), rtrim() and ltrim() functions. + */ +static zend_always_inline void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zend_string *str; + zend_string *what = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(what) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_STR(return_value, php_trim_int(str, (what ? ZSTR_VAL(what) : NULL), (what ? ZSTR_LEN(what) : 0), mode)); +} +/* }}} */ + +/* {{{ proto string trim(string str [, string character_mask]) + Strips whitespace from the beginning and end of a string */ +PHP_FUNCTION(trim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto string rtrim(string str [, string character_mask]) + Removes trailing whitespace */ +PHP_FUNCTION(rtrim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto string ltrim(string str [, string character_mask]) + Strips whitespace from the beginning of a string */ +PHP_FUNCTION(ltrim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string wordwrap(string str [, int width [, string break [, boolean cut]]]) + Wraps buffer to selected number of characters using string break char */ +PHP_FUNCTION(wordwrap) +{ + zend_string *text; + char *breakchar = "\n"; + size_t newtextlen, chk, breakchar_len = 1; + size_t alloced; + zend_long current = 0, laststart = 0, lastspace = 0; + zend_long linelength = 75; + zend_bool docut = 0; + zend_string *newtext; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STR(text) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(linelength) + Z_PARAM_STRING(breakchar, breakchar_len) + Z_PARAM_BOOL(docut) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(text) == 0) { + RETURN_EMPTY_STRING(); + } + + if (breakchar_len == 0) { + php_error_docref(NULL, E_WARNING, "Break string cannot be empty"); + RETURN_FALSE; + } + + if (linelength == 0 && docut) { + php_error_docref(NULL, E_WARNING, "Can't force cut when width is zero"); + RETURN_FALSE; + } + + /* Special case for a single-character break as it needs no + additional storage space */ + if (breakchar_len == 1 && !docut) { + newtext = zend_string_init(ZSTR_VAL(text), ZSTR_LEN(text), 0); + + laststart = lastspace = 0; + for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) { + if (ZSTR_VAL(text)[current] == breakchar[0]) { + laststart = lastspace = current + 1; + } else if (ZSTR_VAL(text)[current] == ' ') { + if (current - laststart >= linelength) { + ZSTR_VAL(newtext)[current] = breakchar[0]; + laststart = current + 1; + } + lastspace = current; + } else if (current - laststart >= linelength && laststart != lastspace) { + ZSTR_VAL(newtext)[lastspace] = breakchar[0]; + laststart = lastspace + 1; + } + } + + RETURN_NEW_STR(newtext); + } else { + /* Multiple character line break or forced cut */ + if (linelength > 0) { + chk = (size_t)(ZSTR_LEN(text)/linelength + 1); + newtext = zend_string_safe_alloc(chk, breakchar_len, ZSTR_LEN(text), 0); + alloced = ZSTR_LEN(text) + chk * breakchar_len + 1; + } else { + chk = ZSTR_LEN(text); + alloced = ZSTR_LEN(text) * (breakchar_len + 1) + 1; + newtext = zend_string_safe_alloc(ZSTR_LEN(text), breakchar_len + 1, 0, 0); + } + + /* now keep track of the actual new text length */ + newtextlen = 0; + + laststart = lastspace = 0; + for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) { + if (chk <= 0) { + alloced += (size_t) (((ZSTR_LEN(text) - current + 1)/linelength + 1) * breakchar_len) + 1; + newtext = zend_string_extend(newtext, alloced, 0); + chk = (size_t) ((ZSTR_LEN(text) - current)/linelength) + 1; + } + /* when we hit an existing break, copy to new buffer, and + * fix up laststart and lastspace */ + if (ZSTR_VAL(text)[current] == breakchar[0] + && current + breakchar_len < ZSTR_LEN(text) + && !strncmp(ZSTR_VAL(text) + current, breakchar, breakchar_len)) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart + breakchar_len); + newtextlen += current - laststart + breakchar_len; + current += breakchar_len - 1; + laststart = lastspace = current + 1; + chk--; + } + /* if it is a space, check if it is at the line boundary, + * copy and insert a break, or just keep track of it */ + else if (ZSTR_VAL(text)[current] == ' ') { + if (current - laststart >= linelength) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = current + 1; + chk--; + } + lastspace = current; + } + /* if we are cutting, and we've accumulated enough + * characters, and we haven't see a space for this line, + * copy and insert a break. */ + else if (current - laststart >= linelength + && docut && laststart >= lastspace) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = lastspace = current; + chk--; + } + /* if the current word puts us over the linelength, copy + * back up until the last space, insert a break, and move + * up the laststart */ + else if (current - laststart >= linelength + && laststart < lastspace) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, lastspace - laststart); + newtextlen += lastspace - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = lastspace = lastspace + 1; + chk--; + } + } + + /* copy over any stragglers */ + if (laststart != current) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + } + + ZSTR_VAL(newtext)[newtextlen] = '\0'; + /* free unused memory */ + newtext = zend_string_truncate(newtext, newtextlen, 0); + + RETURN_NEW_STR(newtext); + } +} +/* }}} */ + +/* {{{ php_explode + */ +PHPAPI void php_explode(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit) +{ + char *p1 = ZSTR_VAL(str); + char *endp = ZSTR_VAL(str) + ZSTR_LEN(str); + char *p2 = (char *) php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + zval tmp; + + if (p2 == NULL) { + ZVAL_STR_COPY(&tmp, str); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } else { + do { + size_t l = p2 - p1; + + if (l == 0) { + ZVAL_EMPTY_STRING(&tmp); + } else if (l == 1) { + ZVAL_INTERNED_STR(&tmp, ZSTR_CHAR((zend_uchar)(*p1))); + } else { + ZVAL_STRINGL(&tmp, p1, p2 - p1); + } + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + p1 = p2 + ZSTR_LEN(delim); + p2 = (char *) php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + } while (p2 != NULL && --limit > 1); + + if (p1 <= endp) { + ZVAL_STRINGL(&tmp, p1, endp - p1); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + } +} +/* }}} */ + +/* {{{ php_explode_negative_limit + */ +PHPAPI void php_explode_negative_limit(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit) +{ +#define EXPLODE_ALLOC_STEP 64 + char *p1 = ZSTR_VAL(str); + char *endp = ZSTR_VAL(str) + ZSTR_LEN(str); + char *p2 = (char *) php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + zval tmp; + + if (p2 == NULL) { + /* + do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0 + by doing nothing we return empty array + */ + } else { + size_t allocated = EXPLODE_ALLOC_STEP, found = 0; + zend_long i, to_return; + char **positions = emalloc(allocated * sizeof(char *)); + + positions[found++] = p1; + do { + if (found >= allocated) { + allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */ + positions = erealloc(positions, allocated*sizeof(char *)); + } + positions[found++] = p1 = p2 + ZSTR_LEN(delim); + p2 = (char *) php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + } while (p2 != NULL); + + to_return = limit + found; + /* limit is at least -1 therefore no need of bounds checking : i will be always less than found */ + for (i = 0; i < to_return; i++) { /* this checks also for to_return > 0 */ + ZVAL_STRINGL(&tmp, positions[i], (positions[i+1] - ZSTR_LEN(delim)) - positions[i]); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + efree(positions); + } +#undef EXPLODE_ALLOC_STEP +} +/* }}} */ + +/* {{{ proto array explode(string separator, string str [, int limit]) + Splits a string on string separator and return array of components. If limit is positive only limit number of components is returned. If limit is negative all components except the last abs(limit) are returned. */ +PHP_FUNCTION(explode) +{ + zend_string *str, *delim; + zend_long limit = ZEND_LONG_MAX; /* No limit */ + zval tmp; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(delim) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(limit) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(delim) == 0) { + php_error_docref(NULL, E_WARNING, "Empty delimiter"); + RETURN_FALSE; + } + + array_init(return_value); + + if (ZSTR_LEN(str) == 0) { + if (limit >= 0) { + ZVAL_EMPTY_STRING(&tmp); + zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp); + } + return; + } + + if (limit > 1) { + php_explode(delim, str, return_value, limit); + } else if (limit < 0) { + php_explode_negative_limit(delim, str, return_value, limit); + } else { + ZVAL_STR_COPY(&tmp, str); + zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp); + } +} +/* }}} */ + +/* {{{ proto string join(array src, string glue) + An alias for implode */ +/* }}} */ + +/* {{{ php_implode + */ +PHPAPI void php_implode(const zend_string *glue, zval *pieces, zval *return_value) +{ + zval *tmp; + int numelems; + zend_string *str; + char *cptr; + size_t len = 0; + zend_string **strings, **strptr; + + numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces)); + + if (numelems == 0) { + RETURN_EMPTY_STRING(); + } else if (numelems == 1) { + /* loop to search the first not undefined element... */ + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) { + RETURN_STR(zval_get_string(tmp)); + } ZEND_HASH_FOREACH_END(); + } + + strings = emalloc((sizeof(zend_long) + sizeof(zend_string *)) * numelems); + strptr = strings - 1; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) { + if (Z_TYPE_P(tmp) == IS_LONG) { + zend_long val = Z_LVAL_P(tmp); + + *++strptr = NULL; + ((zend_long *) (strings + numelems))[strptr - strings] = Z_LVAL_P(tmp); + if (val <= 0) { + len++; + } + while (val) { + val /= 10; + len++; + } + } else { + *++strptr = zval_get_string(tmp); + len += ZSTR_LEN(*strptr); + } + } ZEND_HASH_FOREACH_END(); + /* numelems can not be 0, we checked above */ + str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0); + cptr = ZSTR_VAL(str) + ZSTR_LEN(str); + *cptr = 0; + + do { + if (*strptr) { + cptr -= ZSTR_LEN(*strptr); + memcpy(cptr, ZSTR_VAL(*strptr), ZSTR_LEN(*strptr)); + zend_string_release(*strptr); + } else { + char *oldPtr = cptr; + char oldVal = *cptr; + zend_long val = ((zend_long *) (strings + numelems))[strptr - strings]; + cptr = zend_print_long_to_buf(cptr, val); + *oldPtr = oldVal; + } + + cptr -= ZSTR_LEN(glue); + memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue)); + } while (--strptr > strings); + + if (*strptr) { + memcpy(ZSTR_VAL(str), ZSTR_VAL(*strptr), ZSTR_LEN(*strptr)); + zend_string_release(*strptr); + } else { + char *oldPtr = cptr; + char oldVal = *cptr; + zend_print_long_to_buf(cptr, ((zend_long *) (strings + numelems))[strptr - strings]); + *oldPtr = oldVal; + } + + efree(strings); + RETURN_NEW_STR(str); +} +/* }}} */ + +/* {{{ proto string implode([string glue,] array pieces) + Joins array elements placing glue string between items and return one string */ +PHP_FUNCTION(implode) +{ + zval *arg1, *arg2 = NULL, *pieces; + zend_string *glue; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(arg1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(arg2) + ZEND_PARSE_PARAMETERS_END(); + + if (arg2 == NULL) { + if (Z_TYPE_P(arg1) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Argument must be an array"); + return; + } + + glue = ZSTR_EMPTY_ALLOC(); + pieces = arg1; + } else { + if (Z_TYPE_P(arg1) == IS_ARRAY) { + glue = zval_get_string(arg2); + pieces = arg1; + } else if (Z_TYPE_P(arg2) == IS_ARRAY) { + glue = zval_get_string(arg1); + pieces = arg2; + } else { + php_error_docref(NULL, E_WARNING, "Invalid arguments passed"); + return; + } + } + + php_implode(glue, pieces, return_value); + zend_string_release(glue); +} +/* }}} */ + +#define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p] + +/* {{{ proto string strtok([string str,] string token) + Tokenize a string */ +PHP_FUNCTION(strtok) +{ + zend_string *str, *tok = NULL; + char *token; + char *token_end; + char *p; + char *pe; + size_t skipped = 0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(tok) + ZEND_PARSE_PARAMETERS_END(); + + if (ZEND_NUM_ARGS() == 1) { + tok = str; + } else { + zval_ptr_dtor(&BG(strtok_zval)); + ZVAL_STRINGL(&BG(strtok_zval), ZSTR_VAL(str), ZSTR_LEN(str)); + BG(strtok_last) = BG(strtok_string) = Z_STRVAL(BG(strtok_zval)); + BG(strtok_len) = ZSTR_LEN(str); + } + + p = BG(strtok_last); /* Where we start to search */ + pe = BG(strtok_string) + BG(strtok_len); + + if (!p || p >= pe) { + RETURN_FALSE; + } + + token = ZSTR_VAL(tok); + token_end = token + ZSTR_LEN(tok); + + while (token < token_end) { + STRTOK_TABLE(token++) = 1; + } + + /* Skip leading delimiters */ + while (STRTOK_TABLE(p)) { + if (++p >= pe) { + /* no other chars left */ + BG(strtok_last) = NULL; + RETVAL_FALSE; + goto restore; + } + skipped++; + } + + /* We know at this place that *p is no delimiter, so skip it */ + while (++p < pe) { + if (STRTOK_TABLE(p)) { + goto return_token; + } + } + + if (p - BG(strtok_last)) { +return_token: + RETVAL_STRINGL(BG(strtok_last) + skipped, (p - BG(strtok_last)) - skipped); + BG(strtok_last) = p + 1; + } else { + RETVAL_FALSE; + BG(strtok_last) = NULL; + } + + /* Restore table -- usually faster then memset'ing the table on every invocation */ +restore: + token = ZSTR_VAL(tok); + + while (token < token_end) { + STRTOK_TABLE(token++) = 0; + } +} +/* }}} */ + +/* {{{ php_strtoupper + */ +PHPAPI char *php_strtoupper(char *s, size_t len) +{ + unsigned char *c, *e; + + c = (unsigned char *)s; + e = (unsigned char *)c+len; + + while (c < e) { + *c = toupper(*c); + c++; + } + return s; +} +/* }}} */ + +/* {{{ php_string_toupper + */ +PHPAPI zend_string *php_string_toupper(zend_string *s) +{ + unsigned char *c, *e; + + c = (unsigned char *)ZSTR_VAL(s); + e = c + ZSTR_LEN(s); + + while (c < e) { + if (islower(*c)) { + register unsigned char *r; + zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0); + + if (c != (unsigned char*)ZSTR_VAL(s)) { + memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s)); + } + r = c + (ZSTR_VAL(res) - ZSTR_VAL(s)); + while (c < e) { + *r = toupper(*c); + r++; + c++; + } + *r = '\0'; + return res; + } + c++; + } + return zend_string_copy(s); +} +/* }}} */ + +/* {{{ proto string strtoupper(string str) + Makes a string uppercase */ +PHP_FUNCTION(strtoupper) +{ + zend_string *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(arg) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_string_toupper(arg)); +} +/* }}} */ + +/* {{{ php_strtolower + */ +PHPAPI char *php_strtolower(char *s, size_t len) +{ + unsigned char *c, *e; + + c = (unsigned char *)s; + e = c+len; + + while (c < e) { + *c = tolower(*c); + c++; + } + return s; +} +/* }}} */ + +/* {{{ php_string_tolower + */ +PHPAPI zend_string *php_string_tolower(zend_string *s) +{ + unsigned char *c, *e; + + c = (unsigned char *)ZSTR_VAL(s); + e = c + ZSTR_LEN(s); + + while (c < e) { + if (isupper(*c)) { + register unsigned char *r; + zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0); + + if (c != (unsigned char*)ZSTR_VAL(s)) { + memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s)); + } + r = c + (ZSTR_VAL(res) - ZSTR_VAL(s)); + while (c < e) { + *r = tolower(*c); + r++; + c++; + } + *r = '\0'; + return res; + } + c++; + } + return zend_string_copy(s); +} +/* }}} */ + +/* {{{ proto string strtolower(string str) + Makes a string lowercase */ +PHP_FUNCTION(strtolower) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_string_tolower(str)); +} +/* }}} */ + +/* {{{ php_basename + */ +PHPAPI zend_string *php_basename(const char *s, size_t len, char *suffix, size_t sufflen) +{ + char *c, *comp, *cend; + size_t inc_len, cnt; + int state; + zend_string *ret; + + c = comp = cend = (char*)s; + cnt = len; + state = 0; + while (cnt > 0) { + inc_len = (*c == '\0' ? 1 : php_mblen(c, cnt)); + + switch (inc_len) { + case -2: + case -1: + inc_len = 1; + php_mb_reset(); + break; + case 0: + goto quit_loop; + case 1: +#if defined(PHP_WIN32) + if (*c == '/' || *c == '\\') { +#else + if (*c == '/') { +#endif + if (state == 1) { + state = 0; + cend = c; + } +#if defined(PHP_WIN32) + /* Catch relative paths in c:file.txt style. They're not to confuse + with the NTFS streams. This part ensures also, that no drive + letter traversing happens. */ + } else if ((*c == ':' && (c - comp == 1))) { + if (state == 0) { + comp = c; + state = 1; + } else { + cend = c; + state = 0; + } +#endif + } else { + if (state == 0) { + comp = c; + state = 1; + } + } + break; + default: + if (state == 0) { + comp = c; + state = 1; + } + break; + } + c += inc_len; + cnt -= inc_len; + } + +quit_loop: + if (state == 1) { + cend = c; + } + if (suffix != NULL && sufflen < (size_t)(cend - comp) && + memcmp(cend - sufflen, suffix, sufflen) == 0) { + cend -= sufflen; + } + + len = cend - comp; + + ret = zend_string_init(comp, len, 0); + return ret; +} +/* }}} */ + +/* {{{ proto string basename(string path [, string suffix]) + Returns the filename component of the path */ +PHP_FUNCTION(basename) +{ + char *string, *suffix = NULL; + size_t string_len, suffix_len = 0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(string, string_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(suffix, suffix_len) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_basename(string, string_len, suffix, suffix_len)); +} +/* }}} */ + +/* {{{ php_dirname + Returns directory name component of path */ +PHPAPI size_t php_dirname(char *path, size_t len) +{ + return zend_dirname(path, len); +} +/* }}} */ + +/* {{{ proto string dirname(string path[, int levels]) + Returns the directory name component of the path */ +PHP_FUNCTION(dirname) +{ + char *str; + size_t str_len; + zend_string *ret; + zend_long levels = 1; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(levels) + ZEND_PARSE_PARAMETERS_END(); + + ret = zend_string_init(str, str_len, 0); + + if (levels == 1) { + /* Defaut case */ +#ifdef PHP_WIN32 + ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len); +#else + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len); +#endif + } else if (levels < 1) { + php_error_docref(NULL, E_WARNING, "Invalid argument, levels must be >= 1"); + zend_string_free(ret); + return; + } else { + /* Some levels up */ + do { +#ifdef PHP_WIN32 + ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret)); +#else + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret)); +#endif + } while (ZSTR_LEN(ret) < str_len && --levels); + } + + RETURN_NEW_STR(ret); +} +/* }}} */ + +/* {{{ proto array pathinfo(string path[, int options]) + Returns information about a certain string */ +PHP_FUNCTION(pathinfo) +{ + zval tmp; + char *path, *dirname; + size_t path_len; + int have_basename; + zend_long opt = PHP_PATHINFO_ALL; + zend_string *ret = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(path, path_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(opt) + ZEND_PARSE_PARAMETERS_END(); + + have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME); + + array_init(&tmp); + + if ((opt & PHP_PATHINFO_DIRNAME) == PHP_PATHINFO_DIRNAME) { + dirname = estrndup(path, path_len); + php_dirname(dirname, path_len); + if (*dirname) { + add_assoc_string(&tmp, "dirname", dirname); + } + efree(dirname); + } + + if (have_basename) { + ret = php_basename(path, path_len, NULL, 0); + add_assoc_str(&tmp, "basename", zend_string_copy(ret)); + } + + if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) { + const char *p; + ptrdiff_t idx; + + if (!have_basename) { + ret = php_basename(path, path_len, NULL, 0); + } + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + + if (p) { + idx = p - ZSTR_VAL(ret); + add_assoc_stringl(&tmp, "extension", ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1); + } + } + + if ((opt & PHP_PATHINFO_FILENAME) == PHP_PATHINFO_FILENAME) { + const char *p; + ptrdiff_t idx; + + /* Have we already looked up the basename? */ + if (!have_basename && !ret) { + ret = php_basename(path, path_len, NULL, 0); + } + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + + idx = p ? (p - ZSTR_VAL(ret)) : (ptrdiff_t)ZSTR_LEN(ret); + add_assoc_stringl(&tmp, "filename", ZSTR_VAL(ret), idx); + } + + if (ret) { + zend_string_release(ret); + } + + if (opt == PHP_PATHINFO_ALL) { + ZVAL_COPY_VALUE(return_value, &tmp); + } else { + zval *element; + if ((element = zend_hash_get_current_data(Z_ARRVAL(tmp))) != NULL) { + ZVAL_DEREF(element); + ZVAL_COPY(return_value, element); + } else { + ZVAL_EMPTY_STRING(return_value); + } + zval_ptr_dtor(&tmp); + } +} +/* }}} */ + +/* {{{ php_stristr + case insensitve strstr */ +PHPAPI char *php_stristr(char *s, char *t, size_t s_len, size_t t_len) +{ + php_strtolower(s, s_len); + php_strtolower(t, t_len); + return (char*)php_memnstr(s, t, t_len, s + s_len); +} +/* }}} */ + +/* {{{ php_strspn + */ +PHPAPI size_t php_strspn(char *s1, char *s2, char *s1_end, char *s2_end) +{ + register const char *p = s1, *spanp; + register char c = *p; + +cont: + for (spanp = s2; p != s1_end && spanp != s2_end;) { + if (*spanp++ == c) { + c = *(++p); + goto cont; + } + } + return (p - s1); +} +/* }}} */ + +/* {{{ php_strcspn + */ +PHPAPI size_t php_strcspn(char *s1, char *s2, char *s1_end, char *s2_end) +{ + register const char *p, *spanp; + register char c = *s1; + + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + /* NOTREACHED */ +} +/* }}} */ + +/* {{{ php_needle_char + */ +static int php_needle_char(zval *needle, char *target) +{ + switch (Z_TYPE_P(needle)) { + case IS_LONG: + *target = (char)Z_LVAL_P(needle); + return SUCCESS; + case IS_NULL: + case IS_FALSE: + *target = '\0'; + return SUCCESS; + case IS_TRUE: + *target = '\1'; + return SUCCESS; + case IS_DOUBLE: + *target = (char)(int)Z_DVAL_P(needle); + return SUCCESS; + case IS_OBJECT: + *target = (char) zval_get_long(needle); + return SUCCESS; + default: + php_error_docref(NULL, E_WARNING, "needle is not a string or an integer"); + return FAILURE; + } +} +/* }}} */ + +/* {{{ proto string stristr(string haystack, string needle[, bool part]) + Finds first occurrence of a string within another, case insensitive */ +PHP_FUNCTION(stristr) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + size_t found_offset; + char *haystack_dup; + char needle_char[2]; + zend_bool part = 0; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(part) + ZEND_PARSE_PARAMETERS_END(); + + haystack_dup = estrndup(ZSTR_VAL(haystack), ZSTR_LEN(haystack)); + + if (Z_TYPE_P(needle) == IS_STRING) { + char *orig_needle; + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + efree(haystack_dup); + RETURN_FALSE; + } + orig_needle = estrndup(Z_STRVAL_P(needle), Z_STRLEN_P(needle)); + found = php_stristr(haystack_dup, orig_needle, ZSTR_LEN(haystack), Z_STRLEN_P(needle)); + efree(orig_needle); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + efree(haystack_dup); + RETURN_FALSE; + } + needle_char[1] = 0; + + found = php_stristr(haystack_dup, needle_char, ZSTR_LEN(haystack), 1); + } + + if (found) { + found_offset = found - haystack_dup; + if (part) { + RETVAL_STRINGL(ZSTR_VAL(haystack), found_offset); + } else { + RETVAL_STRINGL(ZSTR_VAL(haystack) + found_offset, ZSTR_LEN(haystack) - found_offset); + } + } else { + RETVAL_FALSE; + } + + efree(haystack_dup); +} +/* }}} */ + +/* {{{ proto string strstr(string haystack, string needle[, bool part]) + Finds first occurrence of a string within another */ +PHP_FUNCTION(strstr) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + char needle_char[2]; + zend_long found_offset; + zend_bool part = 0; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(part) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(needle) == IS_STRING) { + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + found = (char*)php_memnstr(ZSTR_VAL(haystack), Z_STRVAL_P(needle), Z_STRLEN_P(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + needle_char[1] = 0; + + found = (char*)php_memnstr(ZSTR_VAL(haystack), needle_char, 1, ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } + + if (found) { + found_offset = found - ZSTR_VAL(haystack); + if (part) { + RETURN_STRINGL(ZSTR_VAL(haystack), found_offset); + } else { + RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset); + } + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto string strchr(string haystack, string needle) + An alias for strstr */ +/* }}} */ + +/* {{{ proto int strpos(string haystack, string needle [, int offset]) + Finds position of first occurrence of a string within another */ +PHP_FUNCTION(strpos) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + char needle_char[2]; + zend_long offset = 0; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (offset < 0) { + offset += (zend_long)ZSTR_LEN(haystack); + } + if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset, + Z_STRVAL_P(needle), + Z_STRLEN_P(needle), + ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + needle_char[1] = 0; + + found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset, + needle_char, + 1, + ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } + + if (found) { + RETURN_LONG(found - ZSTR_VAL(haystack)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int stripos(string haystack, string needle [, int offset]) + Finds position of first occurrence of a string within another, case insensitive */ +PHP_FUNCTION(stripos) +{ + char *found = NULL; + zend_string *haystack; + zend_long offset = 0; + char needle_char[2]; + zval *needle; + zend_string *needle_dup = NULL, *haystack_dup; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (offset < 0) { + offset += (zend_long)ZSTR_LEN(haystack); + } + if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + + if (ZSTR_LEN(haystack) == 0) { + RETURN_FALSE; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + if (Z_STRLEN_P(needle) == 0 || Z_STRLEN_P(needle) > ZSTR_LEN(haystack)) { + RETURN_FALSE; + } + + haystack_dup = php_string_tolower(haystack); + needle_dup = php_string_tolower(Z_STR_P(needle)); + found = (char*)php_memnstr(ZSTR_VAL(haystack_dup) + offset, + ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + haystack_dup = php_string_tolower(haystack); + needle_char[0] = tolower(needle_char[0]); + needle_char[1] = '\0'; + found = (char*)php_memnstr(ZSTR_VAL(haystack_dup) + offset, + needle_char, + sizeof(needle_char) - 1, + ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack)); + } + + + if (found) { + RETVAL_LONG(found - ZSTR_VAL(haystack_dup)); + } else { + RETVAL_FALSE; + } + + zend_string_release(haystack_dup); + if (needle_dup) { + zend_string_release(needle_dup); + } +} +/* }}} */ + +/* {{{ proto int strrpos(string haystack, string needle [, int offset]) + Finds position of last occurrence of a string within another string */ +PHP_FUNCTION(strrpos) +{ + zval *zneedle; + char *needle; + zend_string *haystack; + size_t needle_len; + zend_long offset = 0; + char *p, *e, ord_needle[2]; + char *found; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(zneedle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (Z_TYPE_P(zneedle) == IS_STRING) { + needle = Z_STRVAL_P(zneedle); + needle_len = Z_STRLEN_P(zneedle); + } else { + if (php_needle_char(zneedle, ord_needle) != SUCCESS) { + RETURN_FALSE; + } + ord_needle[1] = '\0'; + needle = ord_needle; + needle_len = 1; + } + + if ((ZSTR_LEN(haystack) == 0) || (needle_len == 0)) { + RETURN_FALSE; + } + + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack) + (size_t)offset; + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + } else { + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack); + if ((size_t)-offset < needle_len) { + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + } else { + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + offset + needle_len; + } + } + + if ((found = (char *)zend_memnrstr(p, needle, needle_len, e))) { + RETURN_LONG(found - ZSTR_VAL(haystack)); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int strripos(string haystack, string needle [, int offset]) + Finds position of last occurrence of a string within another string */ +PHP_FUNCTION(strripos) +{ + zval *zneedle; + zend_string *needle; + zend_string *haystack; + zend_long offset = 0; + char *p, *e; + char *found; + zend_string *needle_dup, *haystack_dup, *ord_needle = NULL; + ALLOCA_FLAG(use_heap); + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(zneedle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + ZSTR_ALLOCA_ALLOC(ord_needle, 1, use_heap); + if (Z_TYPE_P(zneedle) == IS_STRING) { + needle = Z_STR_P(zneedle); + } else { + if (php_needle_char(zneedle, ZSTR_VAL(ord_needle)) != SUCCESS) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + ZSTR_VAL(ord_needle)[1] = '\0'; + needle = ord_needle; + } + + if ((ZSTR_LEN(haystack) == 0) || (ZSTR_LEN(needle) == 0)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + + if (ZSTR_LEN(needle) == 1) { + /* Single character search can shortcut memcmps + Can also avoid tolower emallocs */ + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack) + (size_t)offset; + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - 1; + } else { + p = ZSTR_VAL(haystack); + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + (size_t)offset; + } + /* Borrow that ord_needle buffer to avoid repeatedly tolower()ing needle */ + *ZSTR_VAL(ord_needle) = tolower(*ZSTR_VAL(needle)); + while (e >= p) { + if (tolower(*e) == *ZSTR_VAL(ord_needle)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_LONG(e - p + (offset > 0 ? offset : 0)); + } + e--; + } + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + + haystack_dup = php_string_tolower(haystack); + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack_dup) + offset; + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack); + } else { + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack_dup); + if ((size_t)-offset < ZSTR_LEN(needle)) { + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack); + } else { + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle); + } + } + + needle_dup = php_string_tolower(needle); + if ((found = (char *)zend_memnrstr(p, ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), e))) { + RETVAL_LONG(found - ZSTR_VAL(haystack_dup)); + zend_string_release(needle_dup); + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + } else { + zend_string_release(needle_dup); + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string strrchr(string haystack, string needle) + Finds the last occurrence of a character in a string within another */ +PHP_FUNCTION(strrchr) +{ + zval *needle; + zend_string *haystack; + const char *found = NULL; + zend_long found_offset; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(needle) == IS_STRING) { + found = zend_memrchr(ZSTR_VAL(haystack), *Z_STRVAL_P(needle), ZSTR_LEN(haystack)); + } else { + char needle_chr; + if (php_needle_char(needle, &needle_chr) != SUCCESS) { + RETURN_FALSE; + } + + found = zend_memrchr(ZSTR_VAL(haystack), needle_chr, ZSTR_LEN(haystack)); + } + + if (found) { + found_offset = found - ZSTR_VAL(haystack); + RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ php_chunk_split + */ +static zend_string *php_chunk_split(char *src, size_t srclen, char *end, size_t endlen, size_t chunklen) +{ + char *p, *q; + size_t chunks; /* complete chunks! */ + size_t restlen; + size_t out_len; + zend_string *dest; + + chunks = srclen / chunklen; + restlen = srclen - chunks * chunklen; /* srclen % chunklen */ + + if (chunks > INT_MAX - 1) { + return NULL; + } + out_len = chunks + 1; + if (endlen !=0 && out_len > INT_MAX/endlen) { + return NULL; + } + out_len *= endlen; + if (out_len > INT_MAX - srclen - 1) { + return NULL; + } + out_len += srclen + 1; + + dest = zend_string_alloc(out_len * sizeof(char), 0); + + for (p = src, q = ZSTR_VAL(dest); p < (src + srclen - chunklen + 1); ) { + memcpy(q, p, chunklen); + q += chunklen; + memcpy(q, end, endlen); + q += endlen; + p += chunklen; + } + + if (restlen) { + memcpy(q, p, restlen); + q += restlen; + memcpy(q, end, endlen); + q += endlen; + } + + *q = '\0'; + ZSTR_LEN(dest) = q - ZSTR_VAL(dest); + + return dest; +} +/* }}} */ + +/* {{{ proto string chunk_split(string str [, int chunklen [, string ending]]) + Returns split line */ +PHP_FUNCTION(chunk_split) +{ + zend_string *str; + char *end = "\r\n"; + size_t endlen = 2; + zend_long chunklen = 76; + zend_string *result; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(chunklen) + Z_PARAM_STRING(end, endlen) + ZEND_PARSE_PARAMETERS_END(); + + if (chunklen <= 0) { + php_error_docref(NULL, E_WARNING, "Chunk length should be greater than zero"); + RETURN_FALSE; + } + + if ((size_t)chunklen > ZSTR_LEN(str)) { + /* to maintain BC, we must return original string + ending */ + result = zend_string_safe_alloc(ZSTR_LEN(str), 1, endlen, 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(str), ZSTR_LEN(str)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(str), end, endlen); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + RETURN_NEW_STR(result); + } + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + result = php_chunk_split(ZSTR_VAL(str), ZSTR_LEN(str), end, endlen, (size_t)chunklen); + + if (result) { + RETURN_STR(result); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string substr(string str, int start [, int length]) + Returns part of a string */ +PHP_FUNCTION(substr) +{ + zend_string *str; + zend_long l = 0, f; + int argc = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(str) + Z_PARAM_LONG(f) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(l) + ZEND_PARSE_PARAMETERS_END(); + + if (argc > 2) { + if ((l < 0 && (size_t)(-l) > ZSTR_LEN(str))) { + RETURN_FALSE; + } else if (l > (zend_long)ZSTR_LEN(str)) { + l = ZSTR_LEN(str); + } + } else { + l = ZSTR_LEN(str); + } + + if (f > (zend_long)ZSTR_LEN(str)) { + RETURN_FALSE; + } else if (f < 0 && (size_t)-f > ZSTR_LEN(str)) { + f = 0; + } + + if (l < 0 && (l + (zend_long)ZSTR_LEN(str) - f) < 0) { + RETURN_FALSE; + } + + /* if "from" position is negative, count start position from the end + * of the string + */ + if (f < 0) { + f = (zend_long)ZSTR_LEN(str) + f; + if (f < 0) { + f = 0; + } + } + + /* if "length" position is negative, set it to the length + * needed to stop that many chars from the end of the string + */ + if (l < 0) { + l = ((zend_long)ZSTR_LEN(str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if (f > (zend_long)ZSTR_LEN(str)) { + RETURN_FALSE; + } + + if ((size_t)l > ZSTR_LEN(str) - (size_t)f) { + l = ZSTR_LEN(str) - f; + } + + if (l == 0) { + RETURN_EMPTY_STRING(); + } else if (l == 1) { + RETURN_INTERNED_STR(ZSTR_CHAR((zend_uchar)(ZSTR_VAL(str)[f]))); + } else if (l == ZSTR_LEN(str)) { + RETURN_STR_COPY(str); + } + + RETURN_STRINGL(ZSTR_VAL(str) + f, l); +} +/* }}} */ + +/* {{{ proto mixed substr_replace(mixed str, mixed repl, mixed start [, mixed length]) + Replaces part of a string with another string */ +PHP_FUNCTION(substr_replace) +{ + zval *str; + zval *from; + zval *len = NULL; + zval *repl; + zend_long l = 0; + zend_long f; + int argc = ZEND_NUM_ARGS(); + zend_string *result; + HashPosition from_idx, repl_idx, len_idx; + zval *tmp_str = NULL, *tmp_from = NULL, *tmp_repl = NULL, *tmp_len= NULL; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_ZVAL(str) + Z_PARAM_ZVAL(repl) + Z_PARAM_ZVAL(from) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(len) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(str) != IS_ARRAY) { + convert_to_string_ex(str); + } + if (Z_TYPE_P(repl) != IS_ARRAY) { + convert_to_string_ex(repl); + } + if (Z_TYPE_P(from) != IS_ARRAY) { + convert_to_long_ex(from); + } + + if (argc > 3) { + if (Z_TYPE_P(len) != IS_ARRAY) { + convert_to_long_ex(len); + l = zval_get_long(len); + } + } else { + if (Z_TYPE_P(str) != IS_ARRAY) { + l = Z_STRLEN_P(str); + } + } + + if (Z_TYPE_P(str) == IS_STRING) { + if ( + (argc == 3 && Z_TYPE_P(from) == IS_ARRAY) || + (argc == 4 && Z_TYPE_P(from) != Z_TYPE_P(len)) + ) { + php_error_docref(NULL, E_WARNING, "'start' and 'length' should be of same type - numerical or array "); + RETURN_STR_COPY(Z_STR_P(str)); + } + if (argc == 4 && Z_TYPE_P(from) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(from)) != zend_hash_num_elements(Z_ARRVAL_P(len))) { + php_error_docref(NULL, E_WARNING, "'start' and 'length' should have the same number of elements"); + RETURN_STR_COPY(Z_STR_P(str)); + } + } + } + + if (Z_TYPE_P(str) != IS_ARRAY) { + if (Z_TYPE_P(from) != IS_ARRAY) { + zend_string *repl_str; + zend_bool repl_release = 0; + f = Z_LVAL_P(from); + + /* if "from" position is negative, count start position from the end + * of the string + */ + if (f < 0) { + f = (zend_long)Z_STRLEN_P(str) + f; + if (f < 0) { + f = 0; + } + } else if ((size_t)f > Z_STRLEN_P(str)) { + f = Z_STRLEN_P(str); + } + /* if "length" position is negative, set it to the length + * needed to stop that many chars from the end of the string + */ + if (l < 0) { + l = ((zend_long)Z_STRLEN_P(str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if ((size_t)l > Z_STRLEN_P(str) || (l < 0 && (size_t)(-l) > Z_STRLEN_P(str))) { + l = Z_STRLEN_P(str); + } + + if ((f + l) > (zend_long)Z_STRLEN_P(str)) { + l = Z_STRLEN_P(str) - f; + } + if (Z_TYPE_P(repl) == IS_ARRAY) { + repl_idx = 0; + while (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + tmp_repl = &Z_ARRVAL_P(repl)->arData[repl_idx].val; + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { + break; + } + repl_idx++; + } + if (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + repl_str = zval_get_string(tmp_repl); + repl_release = 1; + } else { + repl_str = STR_EMPTY_ALLOC(); + } + } else { + repl_str = Z_STR_P(repl); + } + + result = zend_string_safe_alloc(1, Z_STRLEN_P(str) - l + ZSTR_LEN(repl_str), 0, 0); + + memcpy(ZSTR_VAL(result), Z_STRVAL_P(str), f); + if (ZSTR_LEN(repl_str)) { + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str)); + } + memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), Z_STRVAL_P(str) + f + l, Z_STRLEN_P(str) - f - l); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + if (repl_release) { + zend_string_release(repl_str); + } + RETURN_NEW_STR(result); + } else { + php_error_docref(NULL, E_WARNING, "Functionality of 'start' and 'length' as arrays is not implemented"); + RETURN_STR_COPY(Z_STR_P(str)); + } + } else { /* str is array of strings */ + zend_string *str_index = NULL; + size_t result_len; + zend_ulong num_index; + + array_init(return_value); + + from_idx = len_idx = repl_idx = 0; + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(str), num_index, str_index, tmp_str) { + zend_string *orig_str = zval_get_string(tmp_str); + + if (Z_TYPE_P(from) == IS_ARRAY) { + while (from_idx < Z_ARRVAL_P(from)->nNumUsed) { + tmp_from = &Z_ARRVAL_P(from)->arData[from_idx].val; + if (Z_TYPE_P(tmp_from) != IS_UNDEF) { + break; + } + from_idx++; + } + if (from_idx < Z_ARRVAL_P(from)->nNumUsed) { + f = zval_get_long(tmp_from); + + if (f < 0) { + f = (zend_long)ZSTR_LEN(orig_str) + f; + if (f < 0) { + f = 0; + } + } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + f = ZSTR_LEN(orig_str); + } + from_idx++; + } else { + f = 0; + } + } else { + f = Z_LVAL_P(from); + if (f < 0) { + f = (zend_long)ZSTR_LEN(orig_str) + f; + if (f < 0) { + f = 0; + } + } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + f = ZSTR_LEN(orig_str); + } + } + + if (argc > 3 && Z_TYPE_P(len) == IS_ARRAY) { + while (len_idx < Z_ARRVAL_P(len)->nNumUsed) { + tmp_len = &Z_ARRVAL_P(len)->arData[len_idx].val; + if (Z_TYPE_P(tmp_len) != IS_UNDEF) { + break; + } + len_idx++; + } + if (len_idx < Z_ARRVAL_P(len)->nNumUsed) { + l = zval_get_long(tmp_len); + len_idx++; + } else { + l = ZSTR_LEN(orig_str); + } + } else if (argc > 3) { + l = Z_LVAL_P(len); + } else { + l = ZSTR_LEN(orig_str); + } + + if (l < 0) { + l = (ZSTR_LEN(orig_str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if ((f + l) > (zend_long)ZSTR_LEN(orig_str)) { + l = ZSTR_LEN(orig_str) - f; + } + + result_len = ZSTR_LEN(orig_str) - l; + + if (Z_TYPE_P(repl) == IS_ARRAY) { + while (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + tmp_repl = &Z_ARRVAL_P(repl)->arData[repl_idx].val; + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { + break; + } + repl_idx++; + } + if (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + zend_string *repl_str = zval_get_string(tmp_repl); + + result_len += ZSTR_LEN(repl_str); + repl_idx++; + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str)); + memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + zend_string_release(repl_str); + } else { + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + } + } else { + result_len += Z_STRLEN_P(repl); + + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), Z_STRVAL_P(repl), Z_STRLEN_P(repl)); + memcpy((ZSTR_VAL(result) + f + Z_STRLEN_P(repl)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + } + + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + + if (str_index) { + zval tmp; + + ZVAL_NEW_STR(&tmp, result); + zend_symtable_update(Z_ARRVAL_P(return_value), str_index, &tmp); + } else { + add_index_str(return_value, num_index, result); + } + + zend_string_release(orig_str); + } ZEND_HASH_FOREACH_END(); + } /* if */ +} +/* }}} */ + +/* {{{ proto string quotemeta(string str) + Quotes meta characters */ +PHP_FUNCTION(quotemeta) +{ + zend_string *old; + char *old_end; + char *p, *q; + char c; + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(old) + ZEND_PARSE_PARAMETERS_END(); + + old_end = ZSTR_VAL(old) + ZSTR_LEN(old); + + if (ZSTR_VAL(old) == old_end) { + RETURN_FALSE; + } + + str = zend_string_safe_alloc(2, ZSTR_LEN(old), 0, 0); + + for (p = ZSTR_VAL(old), q = ZSTR_VAL(str); p != old_end; p++) { + c = *p; + switch (c) { + case '.': + case '\\': + case '+': + case '*': + case '?': + case '[': + case '^': + case ']': + case '$': + case '(': + case ')': + *q++ = '\\'; + /* break is missing _intentionally_ */ + default: + *q++ = c; + } + } + + *q = '\0'; + + RETURN_NEW_STR(zend_string_truncate(str, q - ZSTR_VAL(str), 0)); +} +/* }}} */ + +/* {{{ proto int ord(string character) + Returns ASCII value of character + Warning: This function is special-cased by zend_compile.c and so is bypassed for constant string argument */ +PHP_FUNCTION(ord) +{ + char *str; + size_t str_len; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(str, str_len) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_LONG((unsigned char) str[0]); +} +/* }}} */ + +/* {{{ proto string chr(int ascii) + Converts ASCII code to a character + Warning: This function is special-cased by zend_compile.c and so is bypassed for constant integer argument */ +PHP_FUNCTION(chr) +{ + zend_long c; + + if (ZEND_NUM_ARGS() != 1) { + WRONG_PARAM_COUNT; + } + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 1, 1) + Z_PARAM_LONG(c) + ZEND_PARSE_PARAMETERS_END_EX(c = 0); + + c &= 0xff; + ZVAL_INTERNED_STR(return_value, ZSTR_CHAR(c)); +} +/* }}} */ + +/* {{{ php_ucfirst + Uppercase the first character of the word in a native string */ +static void php_ucfirst(char *str) +{ + register char *r; + r = str; + *r = toupper((unsigned char) *r); +} +/* }}} */ + +/* {{{ proto string ucfirst(string str) + Makes a string's first character uppercase */ +PHP_FUNCTION(ucfirst) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_ucfirst(Z_STRVAL_P(return_value)); +} +/* }}} */ + +/* {{{ + Lowercase the first character of the word in a native string */ +static void php_lcfirst(char *str) +{ + register char *r; + r = str; + *r = tolower((unsigned char) *r); +} +/* }}} */ + +/* {{{ proto string lcfirst(string str) + Make a string's first character lowercase */ +PHP_FUNCTION(lcfirst) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_lcfirst(Z_STRVAL_P(return_value)); +} +/* }}} */ + +/* {{{ proto string ucwords(string str [, string delims]) + Uppercase the first character of every word in a string */ +PHP_FUNCTION(ucwords) +{ + zend_string *str; + char *delims = " \t\r\n\f\v"; + register char *r, *r_end; + size_t delims_len = 6; + char mask[256]; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(delims, delims_len) + ZEND_PARSE_PARAMETERS_END(); + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + php_charmask((unsigned char *)delims, delims_len, mask); + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + r = Z_STRVAL_P(return_value); + + *r = toupper((unsigned char) *r); + for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) { + if (mask[(unsigned char)*r++]) { + *r = toupper((unsigned char) *r); + } + } +} +/* }}} */ + +/* {{{ php_strtr + */ +PHPAPI char *php_strtr(char *str, size_t len, char *str_from, char *str_to, size_t trlen) +{ + size_t i; + + if (UNEXPECTED(trlen < 1)) { + return str; + } else if (trlen == 1) { + char ch_from = *str_from; + char ch_to = *str_to; + + for (i = 0; i < len; i++) { + if (str[i] == ch_from) { + str[i] = ch_to; + } + } + } else { + unsigned char xlat[256], j = 0; + + do { xlat[j] = j; } while (++j != 0); + + for (i = 0; i < trlen; i++) { + xlat[(size_t)(unsigned char) str_from[i]] = str_to[i]; + } + + for (i = 0; i < len; i++) { + str[i] = xlat[(size_t)(unsigned char) str[i]]; + } + } + + return str; +} +/* }}} */ + +/* {{{ php_strtr_ex + */ +static zend_string *php_strtr_ex(zend_string *str, char *str_from, char *str_to, size_t trlen) +{ + zend_string *new_str = NULL; + size_t i; + + if (UNEXPECTED(trlen < 1)) { + return zend_string_copy(str); + } else if (trlen == 1) { + char ch_from = *str_from; + char ch_to = *str_to; + + for (i = 0; i < ZSTR_LEN(str); i++) { + if (ZSTR_VAL(str)[i] == ch_from) { + new_str = zend_string_alloc(ZSTR_LEN(str), 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i); + ZSTR_VAL(new_str)[i] = ch_to; + break; + } + } + for (; i < ZSTR_LEN(str); i++) { + ZSTR_VAL(new_str)[i] = (ZSTR_VAL(str)[i] != ch_from) ? ZSTR_VAL(str)[i] : ch_to; + } + } else { + unsigned char xlat[256], j = 0; + + do { xlat[j] = j; } while (++j != 0); + + for (i = 0; i < trlen; i++) { + xlat[(size_t)(unsigned char) str_from[i]] = str_to[i]; + } + + for (i = 0; i < ZSTR_LEN(str); i++) { + if (ZSTR_VAL(str)[i] != xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) { + new_str = zend_string_alloc(ZSTR_LEN(str), 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i); + ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]; + break; + } + } + + for (;i < ZSTR_LEN(str); i++) { + ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]; + } + } + + if (!new_str) { + return zend_string_copy(str); + } + + ZSTR_VAL(new_str)[ZSTR_LEN(new_str)] = 0; + return new_str; +} +/* }}} */ + +/* {{{ php_strtr_array */ +static void php_strtr_array(zval *return_value, zend_string *input, HashTable *pats) +{ + char *str = ZSTR_VAL(input); + size_t slen = ZSTR_LEN(input); + zend_ulong num_key; + zend_string *str_key; + size_t len, pos, old_pos; + int num_keys = 0; + size_t minlen = 128*1024; + size_t maxlen = 0; + HashTable str_hash; + zval *entry; + char *key; + smart_str result = {0}; + zend_ulong bitset[256/sizeof(zend_ulong)]; + zend_ulong *num_bitset; + + /* we will collect all possible key lengths */ + num_bitset = ecalloc((slen + sizeof(zend_ulong)) / sizeof(zend_ulong), sizeof(zend_ulong)); + memset(bitset, 0, sizeof(bitset)); + + /* check if original array has numeric keys */ + ZEND_HASH_FOREACH_STR_KEY(pats, str_key) { + if (UNEXPECTED(!str_key)) { + num_keys = 1; + } else { + len = ZSTR_LEN(str_key); + if (UNEXPECTED(len < 1)) { + efree(num_bitset); + RETURN_FALSE; + } else if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + if (len > maxlen) { + maxlen = len; + } + if (len < minlen) { + minlen = len; + } + /* remember possible key length */ + num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong)); + bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong)); + } + } ZEND_HASH_FOREACH_END(); + + if (UNEXPECTED(num_keys)) { + zend_string *key_used; + /* we have to rebuild HashTable with numeric keys */ + zend_hash_init(&str_hash, zend_hash_num_elements(pats), NULL, NULL, 0); + ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) { + if (UNEXPECTED(!str_key)) { + key_used = zend_long_to_str(num_key); + len = ZSTR_LEN(key_used); + if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + if (len > maxlen) { + maxlen = len; + } + if (len < minlen) { + minlen = len; + } + /* remember possible key length */ + num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong)); + bitset[((unsigned char)ZSTR_VAL(key_used)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(key_used)[0]) % sizeof(zend_ulong)); + } else { + key_used = str_key; + len = ZSTR_LEN(key_used); + if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + } + zend_hash_add(&str_hash, key_used, entry); + if (UNEXPECTED(!str_key)) { + zend_string_release(key_used); + } + } ZEND_HASH_FOREACH_END(); + pats = &str_hash; + } + + if (UNEXPECTED(minlen > maxlen)) { + /* return the original string */ + if (pats == &str_hash) { + zend_hash_destroy(&str_hash); + } + efree(num_bitset); + RETURN_STR_COPY(input); + } + + old_pos = pos = 0; + while (pos <= slen - minlen) { + key = str + pos; + if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) { + len = maxlen; + if (len > slen - pos) { + len = slen - pos; + } + while (len >= minlen) { + if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) { + entry = zend_hash_str_find(pats, key, len); + if (entry != NULL) { + zend_string *s = zval_get_string(entry); + smart_str_appendl(&result, str + old_pos, pos - old_pos); + smart_str_append(&result, s); + old_pos = pos + len; + pos = old_pos - 1; + zend_string_release(s); + break; + } + } + len--; + } + } + pos++; + } + + if (result.s) { + smart_str_appendl(&result, str + old_pos, slen - old_pos); + smart_str_0(&result); + RETVAL_NEW_STR(result.s); + } else { + smart_str_free(&result); + RETVAL_STR_COPY(input); + } + + if (pats == &str_hash) { + zend_hash_destroy(&str_hash); + } + efree(num_bitset); +} +/* }}} */ + +/* {{{ php_char_to_str_ex + */ +static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, size_t to_len, int case_sensitivity, zend_long *replace_count) +{ + zend_string *result; + size_t char_count = 0; + char lc_from = 0; + char *source, *target, *source_end= ZSTR_VAL(str) + ZSTR_LEN(str); + + if (case_sensitivity) { + char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str); + while ((p = memchr(p, from, (e - p)))) { + char_count++; + p++; + } + } else { + lc_from = tolower(from); + for (source = ZSTR_VAL(str); source < source_end; source++) { + if (tolower(*source) == lc_from) { + char_count++; + } + } + } + + if (char_count == 0) { + return zend_string_copy(str); + } + + if (to_len > 0) { + result = zend_string_safe_alloc(char_count, to_len - 1, ZSTR_LEN(str), 0); + } else { + result = zend_string_alloc(ZSTR_LEN(str) - char_count, 0); + } + target = ZSTR_VAL(result); + + if (case_sensitivity) { + char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str), *s = ZSTR_VAL(str); + while ((p = memchr(p, from, (e - p)))) { + memcpy(target, s, (p - s)); + target += p - s; + memcpy(target, to, to_len); + target += to_len; + p++; + s = p; + if (replace_count) { + *replace_count += 1; + } + } + if (s < e) { + memcpy(target, s, (e - s)); + target += e - s; + } + } else { + for (source = ZSTR_VAL(str); source < source_end; source++) { + if (tolower(*source) == lc_from) { + if (replace_count) { + *replace_count += 1; + } + memcpy(target, to, to_len); + target += to_len; + } else { + *target = *source; + target++; + } + } + } + *target = 0; + return result; +} +/* }}} */ + +/* {{{ php_str_to_str_ex + */ +static zend_string *php_str_to_str_ex(zend_string *haystack, + char *needle, size_t needle_len, char *str, size_t str_len, zend_long *replace_count) +{ + zend_string *new_str; + + if (needle_len < ZSTR_LEN(haystack)) { + char *end; + char *e, *s, *p, *r; + + if (needle_len == str_len) { + new_str = NULL; + end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + if (!new_str) { + new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0); + } + memcpy(ZSTR_VAL(new_str) + (r - ZSTR_VAL(haystack)), str, str_len); + (*replace_count)++; + } + if (!new_str) { + goto nothing_todo; + } + return new_str; + } else { + size_t count = 0; + char *o = ZSTR_VAL(haystack); + char *n = needle; + char *endp = o + ZSTR_LEN(haystack); + + while ((o = (char*)php_memnstr(o, n, needle_len, endp))) { + o += needle_len; + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + goto nothing_todo; + } + if (str_len > needle_len) { + new_str = zend_string_safe_alloc(count, str_len - needle_len, ZSTR_LEN(haystack), 0); + } else { + new_str = zend_string_alloc(count * (str_len - needle_len) + ZSTR_LEN(haystack), 0); + } + + e = s = ZSTR_VAL(new_str); + end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(e, p, r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + (*replace_count)++; + } + + if (p < end) { + memcpy(e, p, end - p); + e += end - p; + } + + *e = '\0'; + return new_str; + } + } else if (needle_len > ZSTR_LEN(haystack) || memcmp(ZSTR_VAL(haystack), needle, ZSTR_LEN(haystack))) { +nothing_todo: + return zend_string_copy(haystack); + } else { + if (str_len == 0) { + new_str = ZSTR_EMPTY_ALLOC(); + } else if (str_len == 1) { + new_str = ZSTR_CHAR((zend_uchar)(*str)); + } else { + new_str = zend_string_init(str, str_len, 0); + } + + (*replace_count)++; + return new_str; + } +} +/* }}} */ + +/* {{{ php_str_to_str_i_ex + */ +static zend_string *php_str_to_str_i_ex(zend_string *haystack, char *lc_haystack, + zend_string *needle, char *str, size_t str_len, zend_long *replace_count) +{ + zend_string *new_str = NULL; + zend_string *lc_needle; + + if (ZSTR_LEN(needle) < ZSTR_LEN(haystack)) { + char *end; + char *e, *s, *p, *r; + + if (ZSTR_LEN(needle) == str_len) { + lc_needle = php_string_tolower(needle); + end = lc_haystack + ZSTR_LEN(haystack); + for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) { + if (!new_str) { + new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0); + } + memcpy(ZSTR_VAL(new_str) + (r - lc_haystack), str, str_len); + (*replace_count)++; + } + zend_string_release(lc_needle); + + if (!new_str) { + goto nothing_todo; + } + return new_str; + } else { + size_t count = 0; + char *o = lc_haystack; + char *n; + char *endp = o + ZSTR_LEN(haystack); + + lc_needle = php_string_tolower(needle); + n = ZSTR_VAL(lc_needle); + + while ((o = (char*)php_memnstr(o, n, ZSTR_LEN(lc_needle), endp))) { + o += ZSTR_LEN(lc_needle); + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + zend_string_release(lc_needle); + goto nothing_todo; + } + + if (str_len > ZSTR_LEN(lc_needle)) { + new_str = zend_string_safe_alloc(count, str_len - ZSTR_LEN(lc_needle), ZSTR_LEN(haystack), 0); + } else { + new_str = zend_string_alloc(count * (str_len - ZSTR_LEN(lc_needle)) + ZSTR_LEN(haystack), 0); + } + + e = s = ZSTR_VAL(new_str); + end = lc_haystack + ZSTR_LEN(haystack); + + for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) { + memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + (*replace_count)++; + } + + if (p < end) { + memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), end - p); + e += end - p; + } + *e = '\0'; + + zend_string_release(lc_needle); + + return new_str; + } + } else if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) { +nothing_todo: + return zend_string_copy(haystack); + } else { + lc_needle = php_string_tolower(needle); + + if (memcmp(lc_haystack, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle))) { + zend_string_release(lc_needle); + goto nothing_todo; + } + zend_string_release(lc_needle); + + new_str = zend_string_init(str, str_len, 0); + + (*replace_count)++; + return new_str; + } +} +/* }}} */ + +/* {{{ php_str_to_str + */ +PHPAPI zend_string *php_str_to_str(char *haystack, size_t length, char *needle, size_t needle_len, char *str, size_t str_len) +{ + zend_string *new_str; + + if (needle_len < length) { + char *end; + char *e, *s, *p, *r; + + if (needle_len == str_len) { + new_str = zend_string_init(haystack, length, 0); + end = ZSTR_VAL(new_str) + length; + for (p = ZSTR_VAL(new_str); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(r, str, str_len); + } + return new_str; + } else { + if (str_len < needle_len) { + new_str = zend_string_alloc(length, 0); + } else { + size_t count = 0; + char *o = haystack; + char *n = needle; + char *endp = o + length; + + while ((o = (char*)php_memnstr(o, n, needle_len, endp))) { + o += needle_len; + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + new_str = zend_string_init(haystack, length, 0); + return new_str; + } else { + if (str_len > needle_len) { + new_str = zend_string_safe_alloc(count, str_len - needle_len, length, 0); + } else { + new_str = zend_string_alloc(count * (str_len - needle_len) + length, 0); + } + } + } + + e = s = ZSTR_VAL(new_str); + end = haystack + length; + for (p = haystack; (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(e, p, r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + } + + if (p < end) { + memcpy(e, p, end - p); + e += end - p; + } + + *e = '\0'; + new_str = zend_string_truncate(new_str, e - s, 0); + return new_str; + } + } else if (needle_len > length || memcmp(haystack, needle, length)) { + new_str = zend_string_init(haystack, length, 0); + return new_str; + } else { + new_str = zend_string_init(str, str_len, 0); + + return new_str; + } +} +/* }}} */ + +/* {{{ proto string strtr(string str, string from[, string to]) + Translates characters in str using given translation tables */ +PHP_FUNCTION(strtr) +{ + zval *from; + zend_string *str; + char *to = NULL; + size_t to_len = 0; + int ac = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(str) + Z_PARAM_ZVAL(from) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(to, to_len) + ZEND_PARSE_PARAMETERS_END(); + + if (ac == 2 && Z_TYPE_P(from) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "The second argument is not an array"); + RETURN_FALSE; + } + + /* shortcut for empty string */ + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + if (ac == 2) { + HashTable *pats = Z_ARRVAL_P(from); + + if (zend_hash_num_elements(pats) < 1) { + RETURN_STR_COPY(str); + } else if (zend_hash_num_elements(pats) == 1) { + zend_long num_key; + zend_string *str_key, *replace; + zval *entry, tmp; + + ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) { + ZVAL_UNDEF(&tmp); + if (UNEXPECTED(!str_key)) { + ZVAL_LONG(&tmp, num_key); + convert_to_string(&tmp); + str_key = Z_STR(tmp); + } + replace = zval_get_string(entry); + if (ZSTR_LEN(str_key) < 1) { + RETVAL_STR_COPY(str); + } else if (ZSTR_LEN(str_key) == 1) { + RETVAL_STR(php_char_to_str_ex(str, + ZSTR_VAL(str_key)[0], + ZSTR_VAL(replace), + ZSTR_LEN(replace), + 1, + NULL)); + } else { + zend_long dummy; + RETVAL_STR(php_str_to_str_ex(str, + ZSTR_VAL(str_key), ZSTR_LEN(str_key), + ZSTR_VAL(replace), ZSTR_LEN(replace), &dummy)); + } + zend_string_release(replace); + zval_dtor(&tmp); + return; + } ZEND_HASH_FOREACH_END(); + } else { + php_strtr_array(return_value, str, pats); + } + } else { + convert_to_string_ex(from); + + RETURN_STR(php_strtr_ex(str, + Z_STRVAL_P(from), + to, + MIN(Z_STRLEN_P(from), to_len))); + } +} +/* }}} */ + +/* {{{ proto string strrev(string str) + Reverse a string */ +PHP_FUNCTION(strrev) +{ + zend_string *str; + char *e, *p; + zend_string *n; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + n = zend_string_alloc(ZSTR_LEN(str), 0); + p = ZSTR_VAL(n); + + e = ZSTR_VAL(str) + ZSTR_LEN(str); + + while (--e >= ZSTR_VAL(str)) { + *p++ = *e; + } + + *p = '\0'; + + RETVAL_NEW_STR(n); +} +/* }}} */ + +/* {{{ php_similar_str + */ +static void php_similar_str(const char *txt1, size_t len1, const char *txt2, size_t len2, size_t *pos1, size_t *pos2, size_t *max) +{ + char *p, *q; + char *end1 = (char *) txt1 + len1; + char *end2 = (char *) txt2 + len2; + size_t l; + + *max = 0; + for (p = (char *) txt1; p < end1; p++) { + for (q = (char *) txt2; q < end2; q++) { + for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++); + if (l > *max) { + *max = l; + *pos1 = p - txt1; + *pos2 = q - txt2; + } + } + } +} +/* }}} */ + +/* {{{ php_similar_char + */ +static size_t php_similar_char(const char *txt1, size_t len1, const char *txt2, size_t len2) +{ + size_t sum; + size_t pos1 = 0, pos2 = 0, max; + + php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max); + if ((sum = max)) { + if (pos1 && pos2) { + sum += php_similar_char(txt1, pos1, + txt2, pos2); + } + if ((pos1 + max < len1) && (pos2 + max < len2)) { + sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max, + txt2 + pos2 + max, len2 - pos2 - max); + } + } + + return sum; +} +/* }}} */ + +/* {{{ proto int similar_text(string str1, string str2 [, float percent]) + Calculates the similarity between two strings */ +PHP_FUNCTION(similar_text) +{ + zend_string *t1, *t2; + zval *percent = NULL; + int ac = ZEND_NUM_ARGS(); + size_t sim; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(t1) + Z_PARAM_STR(t2) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_DEREF(percent) + ZEND_PARSE_PARAMETERS_END(); + + if (ac > 2) { + convert_to_double_ex(percent); + } + + if (ZSTR_LEN(t1) + ZSTR_LEN(t2) == 0) { + if (ac > 2) { + Z_DVAL_P(percent) = 0; + } + + RETURN_LONG(0); + } + + sim = php_similar_char(ZSTR_VAL(t1), ZSTR_LEN(t1), ZSTR_VAL(t2), ZSTR_LEN(t2)); + + if (ac > 2) { + Z_DVAL_P(percent) = sim * 200.0 / (ZSTR_LEN(t1) + ZSTR_LEN(t2)); + } + + RETURN_LONG(sim); +} +/* }}} */ + +/* {{{ php_stripslashes + * + * be careful, this edits the string in-place */ +PHPAPI void php_stripslashes(zend_string *str) +{ + char *s, *t; + size_t l; + + s = ZSTR_VAL(str); + t = ZSTR_VAL(str); + l = ZSTR_LEN(str); + + while (l > 0) { + if (*t == '\\') { + t++; /* skip the slash */ + ZSTR_LEN(str)--; + l--; + if (l > 0) { + if (*t == '0') { + *s++='\0'; + t++; + } else { + *s++ = *t++; /* preserve the next character */ + } + l--; + } + } else { + *s++ = *t++; + l--; + } + } + if (s != t) { + *s = '\0'; + } +} +/* }}} */ + +/* {{{ proto string addcslashes(string str, string charlist) + Escapes all chars mentioned in charlist with backslash. It creates octal representations if asked to backslash characters with 8th bit set or with ASCII<32 (except '\n', '\r', '\t' etc...) */ +PHP_FUNCTION(addcslashes) +{ + zend_string *str, *what; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(str) + Z_PARAM_STR(what) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + if (ZSTR_LEN(what) == 0) { + RETURN_STRINGL(ZSTR_VAL(str), ZSTR_LEN(str)); + } + + RETURN_STR(php_addcslashes(str, 0, ZSTR_VAL(what), ZSTR_LEN(what))); +} +/* }}} */ + +/* {{{ proto string addslashes(string str) + Escapes single quote, double quotes and backslash characters in a string with backslashes */ +PHP_FUNCTION(addslashes) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + RETURN_STR(php_addslashes(str, 0)); +} +/* }}} */ + +/* {{{ proto string stripcslashes(string str) + Strips backslashes from a string. Uses C-style conventions */ +PHP_FUNCTION(stripcslashes) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_stripcslashes(Z_STR_P(return_value)); +} +/* }}} */ + +/* {{{ proto string stripslashes(string str) + Strips backslashes from a string */ +PHP_FUNCTION(stripslashes) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_stripslashes(Z_STR_P(return_value)); +} +/* }}} */ + +#ifndef HAVE_STRERROR +/* {{{ php_strerror + */ +char *php_strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if ((unsigned int) errnum < sys_nerr) { + return(sys_errlist[errnum]); + } + + (void) snprintf(BG(str_ebuf), sizeof(php_basic_globals.str_ebuf), "Unknown error: %d", errnum); + return(BG(str_ebuf)); +} +/* }}} */ +#endif + +/* {{{ php_stripcslashes + */ +PHPAPI void php_stripcslashes(zend_string *str) +{ + char *source, *target, *end; + size_t nlen = ZSTR_LEN(str), i; + char numtmp[4]; + + for (source = (char*)ZSTR_VAL(str), end = source + ZSTR_LEN(str), target = ZSTR_VAL(str); source < end; source++) { + if (*source == '\\' && source + 1 < end) { + source++; + switch (*source) { + case 'n': *target++='\n'; nlen--; break; + case 'r': *target++='\r'; nlen--; break; + case 'a': *target++='\a'; nlen--; break; + case 't': *target++='\t'; nlen--; break; + case 'v': *target++='\v'; nlen--; break; + case 'b': *target++='\b'; nlen--; break; + case 'f': *target++='\f'; nlen--; break; + case '\\': *target++='\\'; nlen--; break; + case 'x': + if (source+1 < end && isxdigit((int)(*(source+1)))) { + numtmp[0] = *++source; + if (source+1 < end && isxdigit((int)(*(source+1)))) { + numtmp[1] = *++source; + numtmp[2] = '\0'; + nlen-=3; + } else { + numtmp[1] = '\0'; + nlen-=2; + } + *target++=(char)strtol(numtmp, NULL, 16); + break; + } + /* break is left intentionally */ + default: + i=0; + while (source < end && *source >= '0' && *source <= '7' && i<3) { + numtmp[i++] = *source++; + } + if (i) { + numtmp[i]='\0'; + *target++=(char)strtol(numtmp, NULL, 8); + nlen-=i; + source--; + } else { + *target++=*source; + nlen--; + } + } + } else { + *target++=*source; + } + } + + if (nlen != 0) { + *target='\0'; + } + + ZSTR_LEN(str) = nlen; +} +/* }}} */ + +/* {{{ php_addcslashes + */ +PHPAPI zend_string *php_addcslashes(zend_string *str, int should_free, char *what, size_t wlength) +{ + char flags[256]; + char *source, *target; + char *end; + char c; + size_t newlen; + zend_string *new_str = zend_string_safe_alloc(4, ZSTR_LEN(str), 0, 0); + + php_charmask((unsigned char *)what, wlength, flags); + + for (source = (char*)ZSTR_VAL(str), end = source + ZSTR_LEN(str), target = ZSTR_VAL(new_str); source < end; source++) { + c = *source; + if (flags[(unsigned char)c]) { + if ((unsigned char) c < 32 || (unsigned char) c > 126) { + *target++ = '\\'; + switch (c) { + case '\n': *target++ = 'n'; break; + case '\t': *target++ = 't'; break; + case '\r': *target++ = 'r'; break; + case '\a': *target++ = 'a'; break; + case '\v': *target++ = 'v'; break; + case '\b': *target++ = 'b'; break; + case '\f': *target++ = 'f'; break; + default: target += sprintf(target, "%03o", (unsigned char) c); + } + continue; + } + *target++ = '\\'; + } + *target++ = c; + } + *target = 0; + newlen = target - ZSTR_VAL(new_str); + if (newlen < ZSTR_LEN(str) * 4) { + new_str = zend_string_truncate(new_str, newlen, 0); + } + if (should_free) { + zend_string_release(str); + } + return new_str; +} +/* }}} */ + +/* {{{ php_addslashes + */ +PHPAPI zend_string *php_addslashes(zend_string *str, int should_free) +{ + /* maximum string length, worst case situation */ + char *source, *target; + char *end; + size_t offset; + zend_string *new_str; + + if (!str) { + return ZSTR_EMPTY_ALLOC(); + } + + source = ZSTR_VAL(str); + end = source + ZSTR_LEN(str); + + while (source < end) { + switch (*source) { + case '\0': + case '\'': + case '\"': + case '\\': + goto do_escape; + default: + source++; + break; + } + } + + if (!should_free) { + return zend_string_copy(str); + } + + return str; + +do_escape: + offset = source - (char *)ZSTR_VAL(str); + new_str = zend_string_safe_alloc(2, ZSTR_LEN(str) - offset, offset, 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), offset); + target = ZSTR_VAL(new_str) + offset; + + while (source < end) { + switch (*source) { + case '\0': + *target++ = '\\'; + *target++ = '0'; + break; + case '\'': + case '\"': + case '\\': + *target++ = '\\'; + /* break is missing *intentionally* */ + default: + *target++ = *source; + break; + } + + source++; + } + + *target = 0; + if (should_free) { + zend_string_release(str); + } + + if (ZSTR_LEN(new_str) - (target - ZSTR_VAL(new_str)) > 16) { + new_str = zend_string_truncate(new_str, target - ZSTR_VAL(new_str), 0); + } else { + ZSTR_LEN(new_str) = target - ZSTR_VAL(new_str); + } + + return new_str; +} +/* }}} */ + +#define _HEB_BLOCK_TYPE_ENG 1 +#define _HEB_BLOCK_TYPE_HEB 2 +#define isheb(c) (((((unsigned char) c) >= 224) && (((unsigned char) c) <= 250)) ? 1 : 0) +#define _isblank(c) (((((unsigned char) c) == ' ' || ((unsigned char) c) == '\t')) ? 1 : 0) +#define _isnewline(c) (((((unsigned char) c) == '\n' || ((unsigned char) c) == '\r')) ? 1 : 0) + +/* {{{ php_str_replace_in_subject + */ +static zend_long php_str_replace_in_subject(zval *search, zval *replace, zval *subject, zval *result, int case_sensitivity) +{ + zval *search_entry, + *replace_entry = NULL; + zend_string *tmp_result, + *replace_entry_str = NULL; + char *replace_value = NULL; + size_t replace_len = 0; + zend_long replace_count = 0; + zend_string *subject_str; + zend_string *lc_subject_str = NULL; + uint32_t replace_idx; + + /* Make sure we're dealing with strings. */ + subject_str = zval_get_string(subject); + if (ZSTR_LEN(subject_str) == 0) { + zend_string_release(subject_str); + ZVAL_EMPTY_STRING(result); + return 0; + } + + /* If search is an array */ + if (Z_TYPE_P(search) == IS_ARRAY) { + /* Duplicate subject string for repeated replacement */ + ZVAL_STR_COPY(result, subject_str); + + if (Z_TYPE_P(replace) == IS_ARRAY) { + replace_idx = 0; + } else { + /* Set replacement value to the passed one */ + replace_value = Z_STRVAL_P(replace); + replace_len = Z_STRLEN_P(replace); + } + + /* For each entry in the search array, get the entry */ + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(search), search_entry) { + /* Make sure we're dealing with strings. */ + zend_string *search_str = zval_get_string(search_entry); + if (ZSTR_LEN(search_str) == 0) { + if (Z_TYPE_P(replace) == IS_ARRAY) { + replace_idx++; + } + zend_string_release(search_str); + continue; + } + + /* If replace is an array. */ + if (Z_TYPE_P(replace) == IS_ARRAY) { + /* Get current entry */ + while (replace_idx < Z_ARRVAL_P(replace)->nNumUsed) { + replace_entry = &Z_ARRVAL_P(replace)->arData[replace_idx].val; + if (Z_TYPE_P(replace_entry) != IS_UNDEF) { + break; + } + replace_idx++; + } + if (replace_idx < Z_ARRVAL_P(replace)->nNumUsed) { + /* Make sure we're dealing with strings. */ + replace_entry_str = zval_get_string(replace_entry); + + /* Set replacement value to the one we got from array */ + replace_value = ZSTR_VAL(replace_entry_str); + replace_len = ZSTR_LEN(replace_entry_str); + + replace_idx++; + } else { + /* We've run out of replacement strings, so use an empty one. */ + replace_value = ""; + replace_len = 0; + } + } + + if (ZSTR_LEN(search_str) == 1) { + zend_long old_replace_count = replace_count; + + tmp_result = php_char_to_str_ex(Z_STR_P(result), + ZSTR_VAL(search_str)[0], + replace_value, + replace_len, + case_sensitivity, + &replace_count); + if (lc_subject_str && replace_count != old_replace_count) { + zend_string_release(lc_subject_str); + lc_subject_str = NULL; + } + } else if (ZSTR_LEN(search_str) > 1) { + if (case_sensitivity) { + tmp_result = php_str_to_str_ex(Z_STR_P(result), + ZSTR_VAL(search_str), ZSTR_LEN(search_str), + replace_value, replace_len, &replace_count); + } else { + zend_long old_replace_count = replace_count; + + if (!lc_subject_str) { + lc_subject_str = php_string_tolower(Z_STR_P(result)); + } + tmp_result = php_str_to_str_i_ex(Z_STR_P(result), ZSTR_VAL(lc_subject_str), + search_str, replace_value, replace_len, &replace_count); + if (replace_count != old_replace_count) { + zend_string_release(lc_subject_str); + lc_subject_str = NULL; + } + } + } + + zend_string_release(search_str); + + if (replace_entry_str) { + zend_string_release(replace_entry_str); + replace_entry_str = NULL; + } + zend_string_release(Z_STR_P(result)); + ZVAL_STR(result, tmp_result); + + if (Z_STRLEN_P(result) == 0) { + if (lc_subject_str) { + zend_string_release(lc_subject_str); + } + zend_string_release(subject_str); + return replace_count; + } + } ZEND_HASH_FOREACH_END(); + if (lc_subject_str) { + zend_string_release(lc_subject_str); + } + } else { + ZEND_ASSERT(Z_TYPE_P(search) == IS_STRING); + if (Z_STRLEN_P(search) == 1) { + ZVAL_STR(result, + php_char_to_str_ex(subject_str, + Z_STRVAL_P(search)[0], + Z_STRVAL_P(replace), + Z_STRLEN_P(replace), + case_sensitivity, + &replace_count)); + } else if (Z_STRLEN_P(search) > 1) { + if (case_sensitivity) { + ZVAL_STR(result, php_str_to_str_ex(subject_str, + Z_STRVAL_P(search), Z_STRLEN_P(search), + Z_STRVAL_P(replace), Z_STRLEN_P(replace), &replace_count)); + } else { + lc_subject_str = php_string_tolower(subject_str); + ZVAL_STR(result, php_str_to_str_i_ex(subject_str, ZSTR_VAL(lc_subject_str), + Z_STR_P(search), + Z_STRVAL_P(replace), Z_STRLEN_P(replace), &replace_count)); + zend_string_release(lc_subject_str); + } + } else { + ZVAL_STR_COPY(result, subject_str); + } + } + zend_string_release(subject_str); + return replace_count; +} +/* }}} */ + +/* {{{ php_str_replace_common + */ +static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity) +{ + zval *subject, *search, *replace, *subject_entry, *zcount = NULL; + zval result; + zend_string *string_key; + zend_ulong num_key; + zend_long count = 0; + int argc = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_ZVAL(search) + Z_PARAM_ZVAL(replace) + Z_PARAM_ZVAL(subject) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_DEREF(zcount) + ZEND_PARSE_PARAMETERS_END(); + + /* Make sure we're dealing with strings and do the replacement. */ + if (Z_TYPE_P(search) != IS_ARRAY) { + convert_to_string_ex(search); + if (Z_TYPE_P(replace) != IS_STRING) { + convert_to_string_ex(replace); + } + } else if (Z_TYPE_P(replace) != IS_ARRAY) { + convert_to_string_ex(replace); + } + + /* if subject is an array */ + if (Z_TYPE_P(subject) == IS_ARRAY) { + array_init(return_value); + + /* For each subject entry, convert it to string, then perform replacement + and add the result to the return_value array. */ + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(subject), num_key, string_key, subject_entry) { + ZVAL_DEREF(subject_entry); + if (Z_TYPE_P(subject_entry) != IS_ARRAY && Z_TYPE_P(subject_entry) != IS_OBJECT) { + count += php_str_replace_in_subject(search, replace, subject_entry, &result, case_sensitivity); + } else { + ZVAL_COPY(&result, subject_entry); + } + /* Add to return array */ + if (string_key) { + zend_hash_add_new(Z_ARRVAL_P(return_value), string_key, &result); + } else { + zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, &result); + } + } ZEND_HASH_FOREACH_END(); + } else { /* if subject is not an array */ + count = php_str_replace_in_subject(search, replace, subject, return_value, case_sensitivity); + } + if (argc > 3) { + zval_ptr_dtor(zcount); + ZVAL_LONG(zcount, count); + } +} +/* }}} */ + +/* {{{ proto mixed str_replace(mixed search, mixed replace, mixed subject [, int &replace_count]) + Replaces all occurrences of search in haystack with replace */ +PHP_FUNCTION(str_replace) +{ + php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto mixed str_ireplace(mixed search, mixed replace, mixed subject [, int &replace_count]) + Replaces all occurrences of search in haystack with replace / case-insensitive */ +PHP_FUNCTION(str_ireplace) +{ + php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ php_hebrev + * + * Converts Logical Hebrew text (Hebrew Windows style) to Visual text + * Cheers/complaints/flames - Zeev Suraski + */ +static void php_hebrev(INTERNAL_FUNCTION_PARAMETERS, int convert_newlines) +{ + char *str; + char *heb_str, *tmp, *target; + size_t block_start, block_end, block_type, block_length, i; + zend_long max_chars=0, char_count; + size_t begin, end, orig_begin; + size_t str_len; + zend_string *broken_str; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(max_chars) + ZEND_PARSE_PARAMETERS_END(); + + if (str_len == 0) { + RETURN_FALSE; + } + + tmp = str; + block_start=block_end=0; + + heb_str = (char *) emalloc(str_len+1); + target = heb_str+str_len; + *target = 0; + target--; + + block_length=0; + + if (isheb(*tmp)) { + block_type = _HEB_BLOCK_TYPE_HEB; + } else { + block_type = _HEB_BLOCK_TYPE_ENG; + } + + do { + if (block_type == _HEB_BLOCK_TYPE_HEB) { + while ((isheb((int)*(tmp+1)) || _isblank((int)*(tmp+1)) || ispunct((int)*(tmp+1)) || (int)*(tmp+1)=='\n' ) && block_end': + *target = '<'; + break; + case '\\': + *target = '/'; + break; + case '/': + *target = '\\'; + break; + default: + break; + } + target--; + } + block_type = _HEB_BLOCK_TYPE_ENG; + } else { + while (!isheb(*(tmp+1)) && (int)*(tmp+1)!='\n' && block_end < str_len-1) { + tmp++; + block_end++; + block_length++; + } + while ((_isblank((int)*tmp) || ispunct((int)*tmp)) && *tmp!='/' && *tmp!='-' && block_end > block_start) { + tmp--; + block_end--; + } + for (i = block_end+1; i >= block_start+1; i--) { + *target = str[i-1]; + target--; + } + block_type = _HEB_BLOCK_TYPE_HEB; + } + block_start=block_end+1; + } while (block_end < str_len-1); + + + broken_str = zend_string_alloc(str_len, 0); + begin = end = str_len-1; + target = ZSTR_VAL(broken_str); + + while (1) { + char_count=0; + while ((!max_chars || (max_chars > 0 && char_count < max_chars)) && begin > 0) { + char_count++; + begin--; + if (begin <= 0 || _isnewline(heb_str[begin])) { + while (begin > 0 && _isnewline(heb_str[begin-1])) { + begin--; + char_count++; + } + break; + } + } + if (max_chars >= 0 && char_count == max_chars) { /* try to avoid breaking words */ + size_t new_char_count=char_count, new_begin=begin; + + while (new_char_count > 0) { + if (_isblank(heb_str[new_begin]) || _isnewline(heb_str[new_begin])) { + break; + } + new_begin++; + new_char_count--; + } + if (new_char_count > 0) { + begin=new_begin; + } + } + orig_begin=begin; + + if (_isblank(heb_str[begin])) { + heb_str[begin]='\n'; + } + while (begin <= end && _isnewline(heb_str[begin])) { /* skip leading newlines */ + begin++; + } + for (i = begin; i <= end; i++) { /* copy content */ + *target = heb_str[i]; + target++; + } + for (i = orig_begin; i <= end && _isnewline(heb_str[i]); i++) { + *target = heb_str[i]; + target++; + } + begin=orig_begin; + + if (begin <= 0) { + *target = 0; + break; + } + begin--; + end=begin; + } + efree(heb_str); + + if (convert_newlines) { + RETVAL_STR(php_char_to_str_ex(broken_str, '\n', "
\n", 7, 1, NULL)); + zend_string_release(broken_str); + } else { + RETURN_NEW_STR(broken_str); + } +} +/* }}} */ + +/* {{{ proto string hebrev(string str [, int max_chars_per_line]) + Converts logical Hebrew text to visual text */ +PHP_FUNCTION(hebrev) +{ + php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto string hebrevc(string str [, int max_chars_per_line]) + Converts logical Hebrew text to visual text with newline conversion */ +PHP_FUNCTION(hebrevc) +{ + php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string nl2br(string str [, bool is_xhtml]) + Converts newlines to HTML line breaks */ +PHP_FUNCTION(nl2br) +{ + /* in brief this inserts
or
before matched regexp \n\r?|\r\n? */ + char *tmp; + zend_string *str; + char *end, *target; + size_t repl_cnt = 0; + zend_bool is_xhtml = 1; + zend_string *result; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(is_xhtml) + ZEND_PARSE_PARAMETERS_END(); + + tmp = ZSTR_VAL(str); + end = ZSTR_VAL(str) + ZSTR_LEN(str); + + /* it is really faster to scan twice and allocate mem once instead of scanning once + and constantly reallocing */ + while (tmp < end) { + if (*tmp == '\r') { + if (*(tmp+1) == '\n') { + tmp++; + } + repl_cnt++; + } else if (*tmp == '\n') { + if (*(tmp+1) == '\r') { + tmp++; + } + repl_cnt++; + } + + tmp++; + } + + if (repl_cnt == 0) { + RETURN_STR_COPY(str); + } + + { + size_t repl_len = is_xhtml ? (sizeof("
") - 1) : (sizeof("
") - 1); + + result = zend_string_safe_alloc(repl_cnt, repl_len, ZSTR_LEN(str), 0); + target = ZSTR_VAL(result); + } + + tmp = ZSTR_VAL(str); + while (tmp < end) { + switch (*tmp) { + case '\r': + case '\n': + *target++ = '<'; + *target++ = 'b'; + *target++ = 'r'; + + if (is_xhtml) { + *target++ = ' '; + *target++ = '/'; + } + + *target++ = '>'; + + if ((*tmp == '\r' && *(tmp+1) == '\n') || (*tmp == '\n' && *(tmp+1) == '\r')) { + *target++ = *tmp++; + } + /* lack of a break; is intentional */ + default: + *target++ = *tmp; + } + + tmp++; + } + + *target = '\0'; + + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ proto string strip_tags(string str [, string allowable_tags]) + Strips HTML and PHP tags from a string */ +PHP_FUNCTION(strip_tags) +{ + zend_string *buf; + zend_string *str; + zval *allow=NULL; + char *allowed_tags=NULL; + size_t allowed_tags_len=0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(allow) + ZEND_PARSE_PARAMETERS_END(); + + /* To maintain a certain BC, we allow anything for the second parameter and return original string */ + if (allow) { + convert_to_string(allow); + allowed_tags = Z_STRVAL_P(allow); + allowed_tags_len = Z_STRLEN_P(allow); + } + + buf = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0); + ZSTR_LEN(buf) = php_strip_tags_ex(ZSTR_VAL(buf), ZSTR_LEN(str), NULL, allowed_tags, allowed_tags_len, 0); + RETURN_NEW_STR(buf); +} +/* }}} */ + +/* {{{ proto string setlocale(mixed category, string locale [, string ...]) + Set locale information */ +PHP_FUNCTION(setlocale) +{ + zval *args = NULL; + zval *plocale; + zend_string *loc; + char *retval; + zend_long cat; + int num_args, i = 0; + uint32_t idx; + + ZEND_PARSE_PARAMETERS_START(2, -1) + Z_PARAM_LONG(cat) + Z_PARAM_VARIADIC('+', args, num_args) + ZEND_PARSE_PARAMETERS_END(); + +#ifdef HAVE_SETLOCALE + idx = 0; + while (1) { + if (Z_TYPE(args[0]) == IS_ARRAY) { + while (idx < Z_ARRVAL(args[0])->nNumUsed) { + plocale = &Z_ARRVAL(args[0])->arData[idx].val; + if (Z_TYPE_P(plocale) != IS_UNDEF) { + break; + } + idx++; + } + if (idx >= Z_ARRVAL(args[0])->nNumUsed) { + break; + } + } else { + plocale = &args[i]; + } + + loc = zval_get_string(plocale); + + if (!strcmp("0", ZSTR_VAL(loc))) { + zend_string_release(loc); + loc = NULL; + } else { + if (ZSTR_LEN(loc) >= 255) { + php_error_docref(NULL, E_WARNING, "Specified locale name is too long"); + zend_string_release(loc); + break; + } + } + + retval = php_my_setlocale(cat, loc ? ZSTR_VAL(loc) : NULL); + zend_update_current_locale(); + if (retval) { + if (loc) { + /* Remember if locale was changed */ + size_t len = strlen(retval); + + BG(locale_changed) = 1; + if (cat == LC_CTYPE || cat == LC_ALL) { + if (BG(locale_string)) { + zend_string_release(BG(locale_string)); + } + if (len == ZSTR_LEN(loc) && !memcmp(ZSTR_VAL(loc), retval, len)) { + BG(locale_string) = zend_string_copy(loc); + RETURN_STR(BG(locale_string)); + } else { + BG(locale_string) = zend_string_init(retval, len, 0); + zend_string_release(loc); + RETURN_STR_COPY(BG(locale_string)); + } + } else if (len == ZSTR_LEN(loc) && !memcmp(ZSTR_VAL(loc), retval, len)) { + RETURN_STR(loc); + } + zend_string_release(loc); + } + RETURN_STRING(retval); + } + if (loc) { + zend_string_release(loc); + } + + if (Z_TYPE(args[0]) == IS_ARRAY) { + idx++; + } else { + if (++i >= num_args) break; + } + } + +#endif + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto void parse_str(string encoded_string [, array &result]) + Parses GET/POST/COOKIE data and sets global variables */ +PHP_FUNCTION(parse_str) +{ + char *arg; + zval *arrayArg = NULL; + char *res = NULL; + size_t arglen; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(arg, arglen) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_DEREF(arrayArg) + ZEND_PARSE_PARAMETERS_END(); + + res = estrndup(arg, arglen); + + if (arrayArg == NULL) { + zval tmp; + zend_array *symbol_table; + if (zend_forbid_dynamic_call("parse_str() with a single argument") == FAILURE) { + efree(res); + return; + } + + php_error_docref(NULL, E_DEPRECATED, "Calling parse_str() without the result argument is deprecated"); + + symbol_table = zend_rebuild_symbol_table(); + ZVAL_ARR(&tmp, symbol_table); + sapi_module.treat_data(PARSE_STRING, res, &tmp); + if (UNEXPECTED(zend_hash_del(symbol_table, ZSTR_KNOWN(ZEND_STR_THIS)) == SUCCESS)) { + zend_throw_error(NULL, "Cannot re-assign $this"); + } + } else { + /* Clear out the array that was passed in. */ + zval_ptr_dtor(arrayArg); + array_init(arrayArg); + sapi_module.treat_data(PARSE_STRING, res, arrayArg); + } +} +/* }}} */ + +#define PHP_TAG_BUF_SIZE 1023 + +/* {{{ php_tag_find + * + * Check if tag is in a set of tags + * + * states: + * + * 0 start tag + * 1 first non-whitespace char seen + */ +int php_tag_find(char *tag, size_t len, const char *set) { + char c, *n, *t; + int state=0, done=0; + char *norm; + + if (len <= 0) { + return 0; + } + + norm = emalloc(len+1); + + n = norm; + t = tag; + c = tolower(*t); + /* + normalize the tag removing leading and trailing whitespace + and turn any into just and any + into + */ + while (!done) { + switch (c) { + case '<': + *(n++) = c; + break; + case '>': + done =1; + break; + default: + if (!isspace((int)c)) { + if (state == 0) { + state=1; + } + if (c != '/') { + *(n++) = c; + } + } else { + if (state == 1) + done=1; + } + break; + } + c = tolower(*(++t)); + } + *(n++) = '>'; + *n = '\0'; + if (strstr(set, norm)) { + done=1; + } else { + done=0; + } + efree(norm); + return done; +} +/* }}} */ + +PHPAPI size_t php_strip_tags(char *rbuf, size_t len, uint8_t *stateptr, const char *allow, size_t allow_len) /* {{{ */ +{ + return php_strip_tags_ex(rbuf, len, stateptr, allow, allow_len, 0); +} +/* }}} */ + +/* {{{ php_strip_tags + + A simple little state-machine to strip out html and php tags + + State 0 is the output state, State 1 means we are inside a + normal html tag and state 2 means we are inside a php tag. + + The state variable is passed in to allow a function like fgetss + to maintain state across calls to the function. + + lc holds the last significant character read and br is a bracket + counter. + + When an allow string is passed in we keep track of the string + in state 1 and when the tag is closed check it against the + allow string to see if we should allow it. + + swm: Added ability to strip = PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = '<'; + } + } else if (state == 1) { + depth++; + } + break; + + case '(': + if (state == 2) { + if (lc != '"' && lc != '\'') { + lc = '('; + br++; + } + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } else if (state == 0) { + *(rp++) = c; + } + break; + + case ')': + if (state == 2) { + if (lc != '"' && lc != '\'') { + lc = ')'; + br--; + } + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } else if (state == 0) { + *(rp++) = c; + } + break; + + case '>': + if (depth) { + depth--; + break; + } + + if (in_q) { + break; + } + + switch (state) { + case 1: /* HTML/XML */ + lc = '>'; + if (is_xml && *(p -1) == '-') { + break; + } + in_q = state = is_xml = 0; + if (allow) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = '>'; + *tp='\0'; + if (php_tag_find(tbuf, tp-tbuf, allow_actual)) { + memcpy(rp, tbuf, tp-tbuf); + rp += tp-tbuf; + } + tp = tbuf; + } + break; + + case 2: /* PHP */ + if (!br && lc != '\"' && *(p-1) == '?') { + in_q = state = 0; + tp = tbuf; + } + break; + + case 3: + in_q = state = 0; + tp = tbuf; + break; + + case 4: /* JavaScript/CSS/etc... */ + if (p >= buf + 2 && *(p-1) == '-' && *(p-2) == '-') { + in_q = state = 0; + tp = tbuf; + } + break; + + default: + *(rp++) = c; + break; + } + break; + + case '"': + case '\'': + if (state == 4) { + /* Inside */ + break; + } else if (state == 2 && *(p-1) != '\\') { + if (lc == c) { + lc = '\0'; + } else if (lc != '\\') { + lc = c; + } + } else if (state == 0) { + *(rp++) = c; + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + if (state && p != buf && (state == 1 || *(p-1) != '\\') && (!in_q || *p == in_q)) { + if (in_q) { + in_q = 0; + } else { + in_q = *p; + } + } + break; + + case '!': + /* JavaScript & Other HTML scripting languages */ + if (state == 1 && *(p-1) == '<') { + state = 3; + lc = c; + } else { + if (state == 0) { + *(rp++) = c; + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + } + break; + + case '-': + if (state == 3 && p >= buf + 2 && *(p-1) == '-' && *(p-2) == '!') { + state = 4; + } else { + goto reg_char; + } + break; + + case '?': + + if (state == 1 && *(p-1) == '<') { + br=0; + state=2; + break; + } + + case 'E': + case 'e': + /* !DOCTYPE exception */ + if (state==3 && p > buf+6 + && tolower(*(p-1)) == 'p' + && tolower(*(p-2)) == 'y' + && tolower(*(p-3)) == 't' + && tolower(*(p-4)) == 'c' + && tolower(*(p-5)) == 'o' + && tolower(*(p-6)) == 'd') { + state = 1; + break; + } + /* fall-through */ + + case 'l': + case 'L': + + /* swm: If we encounter ' buf+4 && strncasecmp(p-4, "= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + break; + } + c = *(++p); + i++; + } + if (rp < rbuf + len) { + *rp = '\0'; + } + efree(buf); + if (allow) { + efree(tbuf); + if (allow_free) { + efree(allow_free); + } + } + if (stateptr) + *stateptr = state; + + return (size_t)(rp - rbuf); +} +/* }}} */ + +/* {{{ proto array str_getcsv(string input[, string delimiter[, string enclosure[, string escape]]]) +Parse a CSV string into an array */ +PHP_FUNCTION(str_getcsv) +{ + zend_string *str; + char delim = ',', enc = '"', esc = '\\'; + char *delim_str = NULL, *enc_str = NULL, *esc_str = NULL; + size_t delim_len = 0, enc_len = 0, esc_len = 0; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(delim_str, delim_len) + Z_PARAM_STRING(enc_str, enc_len) + Z_PARAM_STRING(esc_str, esc_len) + ZEND_PARSE_PARAMETERS_END(); + + delim = delim_len ? delim_str[0] : delim; + enc = enc_len ? enc_str[0] : enc; + esc = esc_len ? esc_str[0] : esc; + + php_fgetcsv(NULL, delim, enc, esc, ZSTR_LEN(str), ZSTR_VAL(str), return_value); +} +/* }}} */ + +/* {{{ proto string str_repeat(string input, int mult) + Returns the input string repeat mult times */ +PHP_FUNCTION(str_repeat) +{ + zend_string *input_str; /* Input string */ + zend_long mult; /* Multiplier */ + zend_string *result; /* Resulting string */ + size_t result_len; /* Length of the resulting string */ + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(input_str) + Z_PARAM_LONG(mult) + ZEND_PARSE_PARAMETERS_END(); + + if (mult < 0) { + php_error_docref(NULL, E_WARNING, "Second argument has to be greater than or equal to 0"); + return; + } + + /* Don't waste our time if it's empty */ + /* ... or if the multiplier is zero */ + if (ZSTR_LEN(input_str) == 0 || mult == 0) + RETURN_EMPTY_STRING(); + + /* Initialize the result string */ + result = zend_string_safe_alloc(ZSTR_LEN(input_str), mult, 0, 0); + result_len = ZSTR_LEN(input_str) * mult; + + /* Heavy optimization for situations where input string is 1 byte long */ + if (ZSTR_LEN(input_str) == 1) { + memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult); + } else { + char *s, *e, *ee; + ptrdiff_t l=0; + memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str)); + s = ZSTR_VAL(result); + e = ZSTR_VAL(result) + ZSTR_LEN(input_str); + ee = ZSTR_VAL(result) + result_len; + + while (e 4) { + php_error_docref(NULL, E_WARNING, "Unknown mode"); + RETURN_FALSE; + } + + buf = (unsigned char *) ZSTR_VAL(input); + memset((void*) chars, 0, sizeof(chars)); + + while (tmp < ZSTR_LEN(input)) { + chars[*buf]++; + buf++; + tmp++; + } + + if (mymode < 3) { + array_init(return_value); + } + + for (inx = 0; inx < 256; inx++) { + switch (mymode) { + case 0: + add_index_long(return_value, inx, chars[inx]); + break; + case 1: + if (chars[inx] != 0) { + add_index_long(return_value, inx, chars[inx]); + } + break; + case 2: + if (chars[inx] == 0) { + add_index_long(return_value, inx, chars[inx]); + } + break; + case 3: + if (chars[inx] != 0) { + retstr[retlen++] = inx; + } + break; + case 4: + if (chars[inx] == 0) { + retstr[retlen++] = inx; + } + break; + } + } + + if (mymode >= 3 && mymode <= 4) { + RETURN_STRINGL(retstr, retlen); + } +} +/* }}} */ + +/* {{{ php_strnatcmp + */ +static void php_strnatcmp(INTERNAL_FUNCTION_PARAMETERS, int fold_case) +{ + zend_string *s1, *s2; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(s1) + Z_PARAM_STR(s2) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_LONG(strnatcmp_ex(ZSTR_VAL(s1), ZSTR_LEN(s1), + ZSTR_VAL(s2), ZSTR_LEN(s2), + fold_case)); +} +/* }}} */ + +PHPAPI int string_natural_compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool case_insensitive) /* {{{ */ +{ + zend_string *str1 = zval_get_string(op1); + zend_string *str2 = zval_get_string(op2); + + ZVAL_LONG(result, strnatcmp_ex(ZSTR_VAL(str1), ZSTR_LEN(str1), ZSTR_VAL(str2), ZSTR_LEN(str2), case_insensitive)); + + zend_string_release(str1); + zend_string_release(str2); + return SUCCESS; +} +/* }}} */ + +PHPAPI int string_natural_case_compare_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + return string_natural_compare_function_ex(result, op1, op2, 1); +} +/* }}} */ + +PHPAPI int string_natural_compare_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + return string_natural_compare_function_ex(result, op1, op2, 0); +} +/* }}} */ + +/* {{{ proto int strnatcmp(string s1, string s2) + Returns the result of string comparison using 'natural' algorithm */ +PHP_FUNCTION(strnatcmp) +{ + php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto array localeconv(void) + Returns numeric formatting information based on the current locale */ +PHP_FUNCTION(localeconv) +{ + zval grouping, mon_grouping; + int len, i; + + /* We don't need no stinkin' parameters... */ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + array_init(&grouping); + array_init(&mon_grouping); + +#ifdef HAVE_LOCALECONV + { + struct lconv currlocdata; + + localeconv_r( &currlocdata ); + + /* Grab the grouping data out of the array */ + len = (int)strlen(currlocdata.grouping); + + for (i = 0; i < len; i++) { + add_index_long(&grouping, i, currlocdata.grouping[i]); + } + + /* Grab the monetary grouping data out of the array */ + len = (int)strlen(currlocdata.mon_grouping); + + for (i = 0; i < len; i++) { + add_index_long(&mon_grouping, i, currlocdata.mon_grouping[i]); + } + + add_assoc_string(return_value, "decimal_point", currlocdata.decimal_point); + add_assoc_string(return_value, "thousands_sep", currlocdata.thousands_sep); + add_assoc_string(return_value, "int_curr_symbol", currlocdata.int_curr_symbol); + add_assoc_string(return_value, "currency_symbol", currlocdata.currency_symbol); + add_assoc_string(return_value, "mon_decimal_point", currlocdata.mon_decimal_point); + add_assoc_string(return_value, "mon_thousands_sep", currlocdata.mon_thousands_sep); + add_assoc_string(return_value, "positive_sign", currlocdata.positive_sign); + add_assoc_string(return_value, "negative_sign", currlocdata.negative_sign); + add_assoc_long( return_value, "int_frac_digits", currlocdata.int_frac_digits); + add_assoc_long( return_value, "frac_digits", currlocdata.frac_digits); + add_assoc_long( return_value, "p_cs_precedes", currlocdata.p_cs_precedes); + add_assoc_long( return_value, "p_sep_by_space", currlocdata.p_sep_by_space); + add_assoc_long( return_value, "n_cs_precedes", currlocdata.n_cs_precedes); + add_assoc_long( return_value, "n_sep_by_space", currlocdata.n_sep_by_space); + add_assoc_long( return_value, "p_sign_posn", currlocdata.p_sign_posn); + add_assoc_long( return_value, "n_sign_posn", currlocdata.n_sign_posn); + } +#else + /* Ok, it doesn't look like we have locale info floating around, so I guess it + wouldn't hurt to just go ahead and return the POSIX locale information? */ + + add_index_long(&grouping, 0, -1); + add_index_long(&mon_grouping, 0, -1); + + add_assoc_string(return_value, "decimal_point", "\x2E"); + add_assoc_string(return_value, "thousands_sep", ""); + add_assoc_string(return_value, "int_curr_symbol", ""); + add_assoc_string(return_value, "currency_symbol", ""); + add_assoc_string(return_value, "mon_decimal_point", "\x2E"); + add_assoc_string(return_value, "mon_thousands_sep", ""); + add_assoc_string(return_value, "positive_sign", ""); + add_assoc_string(return_value, "negative_sign", ""); + add_assoc_long( return_value, "int_frac_digits", CHAR_MAX); + add_assoc_long( return_value, "frac_digits", CHAR_MAX); + add_assoc_long( return_value, "p_cs_precedes", CHAR_MAX); + add_assoc_long( return_value, "p_sep_by_space", CHAR_MAX); + add_assoc_long( return_value, "n_cs_precedes", CHAR_MAX); + add_assoc_long( return_value, "n_sep_by_space", CHAR_MAX); + add_assoc_long( return_value, "p_sign_posn", CHAR_MAX); + add_assoc_long( return_value, "n_sign_posn", CHAR_MAX); +#endif + + zend_hash_str_update(Z_ARRVAL_P(return_value), "grouping", sizeof("grouping")-1, &grouping); + zend_hash_str_update(Z_ARRVAL_P(return_value), "mon_grouping", sizeof("mon_grouping")-1, &mon_grouping); +} +/* }}} */ + +/* {{{ proto int strnatcasecmp(string s1, string s2) + Returns the result of case-insensitive string comparison using 'natural' algorithm */ +PHP_FUNCTION(strnatcasecmp) +{ + php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto int substr_count(string haystack, string needle [, int offset [, int length]]) + Returns the number of times a substring occurs in the string */ +PHP_FUNCTION(substr_count) +{ + char *haystack, *needle; + zend_long offset = 0, length = 0; + int ac = ZEND_NUM_ARGS(); + int count = 0; + size_t haystack_len, needle_len; + char *p, *endp, cmp; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(haystack, haystack_len) + Z_PARAM_STRING(needle, needle_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + Z_PARAM_LONG(length) + ZEND_PARSE_PARAMETERS_END(); + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty substring"); + RETURN_FALSE; + } + + p = haystack; + endp = p + haystack_len; + + if (offset < 0) { + offset += (zend_long)haystack_len; + } + if ((offset < 0) || ((size_t)offset > haystack_len)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + p += offset; + + if (ac == 4) { + + if (length < 0) { + length += (haystack_len - offset); + } + if (length < 0 || ((size_t)length > (haystack_len - offset))) { + php_error_docref(NULL, E_WARNING, "Invalid length value"); + RETURN_FALSE; + } + endp = p + length; + } + + if (needle_len == 1) { + cmp = needle[0]; + + while ((p = memchr(p, cmp, endp - p))) { + count++; + p++; + } + } else { + while ((p = (char*)php_memnstr(p, needle, needle_len, endp))) { + p += needle_len; + count++; + } + } + + RETURN_LONG(count); +} +/* }}} */ + +/* {{{ proto string str_pad(string input, int pad_length [, string pad_string [, int pad_type]]) + Returns input string padded on the left or right to specified length with pad_string */ +PHP_FUNCTION(str_pad) +{ + /* Input arguments */ + zend_string *input; /* Input string */ + zend_long pad_length; /* Length to pad to */ + + /* Helper variables */ + size_t num_pad_chars; /* Number of padding characters (total - input size) */ + char *pad_str = " "; /* Pointer to padding string */ + size_t pad_str_len = 1; + zend_long pad_type_val = STR_PAD_RIGHT; /* The padding type value */ + size_t i, left_pad=0, right_pad=0; + zend_string *result = NULL; /* Resulting string */ + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(input) + Z_PARAM_LONG(pad_length) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(pad_str, pad_str_len) + Z_PARAM_LONG(pad_type_val) + ZEND_PARSE_PARAMETERS_END(); + + /* If resulting string turns out to be shorter than input string, + we simply copy the input and return. */ + if (pad_length < 0 || (size_t)pad_length <= ZSTR_LEN(input)) { + RETURN_STRINGL(ZSTR_VAL(input), ZSTR_LEN(input)); + } + + if (pad_str_len == 0) { + php_error_docref(NULL, E_WARNING, "Padding string cannot be empty"); + return; + } + + if (pad_type_val < STR_PAD_LEFT || pad_type_val > STR_PAD_BOTH) { + php_error_docref(NULL, E_WARNING, "Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH"); + return; + } + + num_pad_chars = pad_length - ZSTR_LEN(input); + if (num_pad_chars >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Padding length is too long"); + return; + } + + result = zend_string_safe_alloc(1, ZSTR_LEN(input), num_pad_chars, 0); + ZSTR_LEN(result) = 0; + + /* We need to figure out the left/right padding lengths. */ + switch (pad_type_val) { + case STR_PAD_RIGHT: + left_pad = 0; + right_pad = num_pad_chars; + break; + + case STR_PAD_LEFT: + left_pad = num_pad_chars; + right_pad = 0; + break; + + case STR_PAD_BOTH: + left_pad = num_pad_chars / 2; + right_pad = num_pad_chars - left_pad; + break; + } + + /* First we pad on the left. */ + for (i = 0; i < left_pad; i++) + ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + + /* Then we copy the input string. */ + memcpy(ZSTR_VAL(result) + ZSTR_LEN(result), ZSTR_VAL(input), ZSTR_LEN(input)); + ZSTR_LEN(result) += ZSTR_LEN(input); + + /* Finally, we pad on the right. */ + for (i = 0; i < right_pad; i++) + ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ proto mixed sscanf(string str, string format [, string ...]) + Implements an ANSI C compatible sscanf */ +PHP_FUNCTION(sscanf) +{ + zval *args = NULL; + char *str, *format; + size_t str_len, format_len; + int result, num_args = 0; + + ZEND_PARSE_PARAMETERS_START(2, -1) + Z_PARAM_STRING(str, str_len) + Z_PARAM_STRING(format, format_len) + Z_PARAM_VARIADIC('*', args, num_args) + ZEND_PARSE_PARAMETERS_END(); + + result = php_sscanf_internal(str, format, num_args, args, 0, return_value); + + if (SCAN_ERROR_WRONG_PARAM_COUNT == result) { + WRONG_PARAM_COUNT; + } +} +/* }}} */ + +static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"; + +/* {{{ proto string str_rot13(string str) + Perform the rot13 transform on a string */ +PHP_FUNCTION(str_rot13) +{ + zend_string *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(arg) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(arg) == 0) { + RETURN_EMPTY_STRING(); + } else { + RETURN_STR(php_strtr_ex(arg, rot13_from, rot13_to, 52)); + } +} +/* }}} */ + +static void php_string_shuffle(char *str, zend_long len) /* {{{ */ +{ + zend_long n_elems, rnd_idx, n_left; + char temp; + /* The implementation is stolen from array_data_shuffle */ + /* Thus the characteristics of the randomization are the same */ + n_elems = len; + + if (n_elems <= 1) { + return; + } + + n_left = n_elems; + + while (--n_left) { + rnd_idx = php_rand(); + RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX); + if (rnd_idx != n_left) { + temp = str[n_left]; + str[n_left] = str[rnd_idx]; + str[rnd_idx] = temp; + } + } +} +/* }}} */ + +/* {{{ proto void str_shuffle(string str) + Shuffles string. One permutation of all possible is created */ +PHP_FUNCTION(str_shuffle) +{ + zend_string *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(arg) + ZEND_PARSE_PARAMETERS_END(); + + RETVAL_STRINGL(ZSTR_VAL(arg), ZSTR_LEN(arg)); + if (Z_STRLEN_P(return_value) > 1) { + php_string_shuffle(Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value)); + } +} +/* }}} */ + +/* {{{ proto mixed str_word_count(string str, [int format [, string charlist]]) + Counts the number of words inside a string. If format of 1 is specified, + then the function will return an array containing all the words + found inside the string. If format of 2 is specified, then the function + will return an associated array where the position of the word is the key + and the word itself is the value. + + For the purpose of this function, 'word' is defined as a locale dependent + string containing alphabetic characters, which also may contain, but not start + with "'" and "-" characters. +*/ +PHP_FUNCTION(str_word_count) +{ + zend_string *str; + char *char_list = NULL, *p, *e, *s, ch[256]; + size_t char_list_len = 0, word_count = 0; + zend_long type = 0; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + Z_PARAM_STRING(char_list, char_list_len) + ZEND_PARSE_PARAMETERS_END(); + + switch(type) { + case 1: + case 2: + array_init(return_value); + if (!ZSTR_LEN(str)) { + return; + } + break; + case 0: + if (!ZSTR_LEN(str)) { + RETURN_LONG(0); + } + /* nothing to be done */ + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid format value " ZEND_LONG_FMT, type); + RETURN_FALSE; + } + + if (char_list) { + php_charmask((unsigned char *)char_list, char_list_len, ch); + } + + p = ZSTR_VAL(str); + e = ZSTR_VAL(str) + ZSTR_LEN(str); + + /* first character cannot be ' or -, unless explicitly allowed by the user */ + if ((*p == '\'' && (!char_list || !ch['\''])) || (*p == '-' && (!char_list || !ch['-']))) { + p++; + } + /* last character cannot be -, unless explicitly allowed by the user */ + if (*(e - 1) == '-' && (!char_list || !ch['-'])) { + e--; + } + + while (p < e) { + s = p; + while (p < e && (isalpha((unsigned char)*p) || (char_list && ch[(unsigned char)*p]) || *p == '\'' || *p == '-')) { + p++; + } + if (p > s) { + switch (type) + { + case 1: + add_next_index_stringl(return_value, s, p - s); + break; + case 2: + add_index_stringl(return_value, (s - ZSTR_VAL(str)), s, p - s); + break; + default: + word_count++; + break; + } + } + p++; + } + + if (!type) { + RETURN_LONG(word_count); + } +} + +/* }}} */ + +#if HAVE_STRFMON +/* {{{ proto string money_format(string format , float value) + Convert monetary value(s) to string */ +PHP_FUNCTION(money_format) +{ + size_t format_len = 0; + char *format, *p, *e; + double value; + zend_bool check = 0; + zend_string *str; + ssize_t res_len; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(format, format_len) + Z_PARAM_DOUBLE(value) + ZEND_PARSE_PARAMETERS_END(); + + p = format; + e = p + format_len; + while ((p = memchr(p, '%', (e - p)))) { + if (*(p + 1) == '%') { + p += 2; + } else if (!check) { + check = 1; + p++; + } else { + php_error_docref(NULL, E_WARNING, "Only a single %%i or %%n token can be used"); + RETURN_FALSE; + } + } + + str = zend_string_safe_alloc(format_len, 1, 1024, 0); + if ((res_len = strfmon(ZSTR_VAL(str), ZSTR_LEN(str), format, value)) < 0) { + zend_string_free(str); + RETURN_FALSE; + } +#ifdef _AIX + /* + On AIX strfmon seems to include the terminating \0 in the length returned by strfmon, + despite the documentation indicating it is not included. + */ + ZSTR_LEN(str) = strlen(ZSTR_VAL(str)); +#else + ZSTR_LEN(str) = (size_t)res_len; +#endif + ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0'; + + RETURN_NEW_STR(zend_string_truncate(str, ZSTR_LEN(str), 0)); +} +/* }}} */ +#endif + +/* {{{ proto array str_split(string str [, int split_length]) + Convert a string to an array. If split_length is specified, break the string down into chunks each split_length characters long. */ +PHP_FUNCTION(str_split) +{ + zend_string *str; + zend_long split_length = 1; + char *p; + size_t n_reg_segments; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(split_length) + ZEND_PARSE_PARAMETERS_END(); + + if (split_length <= 0) { + php_error_docref(NULL, E_WARNING, "The length of each segment must be greater than zero"); + RETURN_FALSE; + } + + + if (0 == ZSTR_LEN(str) || (size_t)split_length >= ZSTR_LEN(str)) { + array_init_size(return_value, 1); + add_next_index_stringl(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + return; + } + + array_init_size(return_value, (uint32_t)(((ZSTR_LEN(str) - 1) / split_length) + 1)); + + n_reg_segments = ZSTR_LEN(str) / split_length; + p = ZSTR_VAL(str); + + while (n_reg_segments-- > 0) { + add_next_index_stringl(return_value, p, split_length); + p += split_length; + } + + if (p != (ZSTR_VAL(str) + ZSTR_LEN(str))) { + add_next_index_stringl(return_value, p, (ZSTR_VAL(str) + ZSTR_LEN(str) - p)); + } +} +/* }}} */ + +/* {{{ proto array strpbrk(string haystack, string char_list) + Search a string for any of a set of characters */ +PHP_FUNCTION(strpbrk) +{ + zend_string *haystack, *char_list; + char *haystack_ptr, *cl_ptr; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_STR(char_list) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (!ZSTR_LEN(char_list)) { + php_error_docref(NULL, E_WARNING, "The character list cannot be empty"); + RETURN_FALSE; + } + + for (haystack_ptr = ZSTR_VAL(haystack); haystack_ptr < (ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); ++haystack_ptr) { + for (cl_ptr = ZSTR_VAL(char_list); cl_ptr < (ZSTR_VAL(char_list) + ZSTR_LEN(char_list)); ++cl_ptr) { + if (*cl_ptr == *haystack_ptr) { + RETURN_STRINGL(haystack_ptr, (ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - haystack_ptr)); + } + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int substr_compare(string main_str, string str, int offset [, int length [, bool case_sensitivity]]) + Binary safe optionally case insensitive comparison of 2 strings from an offset, up to length characters */ +PHP_FUNCTION(substr_compare) +{ + zend_string *s1, *s2; + zend_long offset, len=0; + zend_bool len_is_default=1; + zend_bool cs=0; + size_t cmp_len; + + ZEND_PARSE_PARAMETERS_START(3, 5) + Z_PARAM_STR(s1) + Z_PARAM_STR(s2) + Z_PARAM_LONG(offset) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_EX(len, len_is_default, 1, 0) + Z_PARAM_BOOL(cs) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (!len_is_default && len <= 0) { + if (len == 0) { + RETURN_LONG(0L); + } else { + php_error_docref(NULL, E_WARNING, "The length must be greater than or equal to zero"); + RETURN_FALSE; + } + } + + if (offset < 0) { + offset = ZSTR_LEN(s1) + offset; + offset = (offset < 0) ? 0 : offset; + } + + if ((size_t)offset >= ZSTR_LEN(s1)) { + php_error_docref(NULL, E_WARNING, "The start position cannot exceed initial string length"); + RETURN_FALSE; + } + + cmp_len = len ? (size_t)len : MAX(ZSTR_LEN(s2), (ZSTR_LEN(s1) - offset)); + + if (!cs) { + RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + } else { + RETURN_LONG(zend_binary_strncasecmp_l(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + } +} +/* }}} */ + +/* {{{ */ +static zend_string *php_utf8_encode(const char *s, size_t len) +{ + size_t pos = len; + zend_string *str; + unsigned char c; + + str = zend_string_safe_alloc(len, 2, 0, 0); + ZSTR_LEN(str) = 0; + while (pos > 0) { + /* The lower 256 codepoints of Unicode are identical to Latin-1, + * so we don't need to do any mapping here. */ + c = (unsigned char)(*s); + if (c < 0x80) { + ZSTR_VAL(str)[ZSTR_LEN(str)++] = (char) c; + /* We only account for the single-byte and two-byte cases because + * we're only dealing with the first 256 Unicode codepoints. */ + } else { + ZSTR_VAL(str)[ZSTR_LEN(str)++] = (0xc0 | (c >> 6)); + ZSTR_VAL(str)[ZSTR_LEN(str)++] = (0x80 | (c & 0x3f)); + } + pos--; + s++; + } + ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0'; + str = zend_string_truncate(str, ZSTR_LEN(str), 0); + return str; +} +/* }}} */ + +/* {{{ */ +static zend_string *php_utf8_decode(const char *s, size_t len) +{ + size_t pos = 0; + unsigned int c; + zend_string *str; + + str = zend_string_alloc(len, 0); + ZSTR_LEN(str) = 0; + while (pos < len) { + int status = FAILURE; + c = php_next_utf8_char((const unsigned char*)s, (size_t) len, &pos, &status); + + /* The lower 256 codepoints of Unicode are identical to Latin-1, + * so we don't need to do any mapping here beyond replacing non-Latin-1 + * characters. */ + if (status == FAILURE || c > 0xFFU) { + c = '?'; + } + + ZSTR_VAL(str)[ZSTR_LEN(str)++] = c; + } + ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0'; + if (ZSTR_LEN(str) < len) { + str = zend_string_truncate(str, ZSTR_LEN(str), 0); + } + + return str; +} +/* }}} */ + + +/* {{{ proto string utf8_encode(string data) + Encodes an ISO-8859-1 string to UTF-8 */ +PHP_FUNCTION(utf8_encode) +{ + char *arg; + size_t arg_len; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(arg, arg_len) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_utf8_encode(arg, arg_len)); +} +/* }}} */ + +/* {{{ proto string utf8_decode(string data) + Converts a UTF-8 encoded string to ISO-8859-1 */ +PHP_FUNCTION(utf8_decode) +{ + char *arg; + size_t arg_len; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(arg, arg_len) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_utf8_decode(arg, arg_len)); +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/standard/tests/file/bug79099.phpt b/ext/standard/tests/file/bug79099.phpt new file mode 100644 index 0000000000000..7c842f4654f06 --- /dev/null +++ b/ext/standard/tests/file/bug79099.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #79099 (OOB read in php_strip_tags_ex) +--FILE-- + +--EXPECT-- +string(0) "" +string(0) "" +string(0) "" +string(0) "" +string(0) "" +string(0) "" From fc6a5262de7522b84c1a2254a3984fdb7a0c9ce7 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:01 +0000 Subject: [PATCH 13/25] commit patch 25157847 --- ext/mbstring/libmbfl/filters/mbfilter_big5.c | 17 ++++++++++++----- ext/mbstring/tests/bug79037.phpt | 10 ++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 ext/mbstring/tests/bug79037.phpt diff --git a/ext/mbstring/libmbfl/filters/mbfilter_big5.c b/ext/mbstring/libmbfl/filters/mbfilter_big5.c index 122ff4c7780ef..657eb98aa58e7 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_big5.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_big5.c @@ -138,6 +138,17 @@ static unsigned short cp950_pua_tbl[][4] = { {0xf70f,0xf848,0xc740,0xc8fe}, }; +static inline int is_in_cp950_pua(int c1, int c) { + if ((c1 >= 0xfa && c1 <= 0xfe) || (c1 >= 0x8e && c1 <= 0xa0) || + (c1 >= 0x81 && c1 <= 0x8d) || (c1 >= 0xc7 && c1 <= 0xc8)) { + return (c >=0x40 && c <= 0x7e) || (c >= 0xa1 && c <= 0xfe); + } + if (c1 == 0xc6) { + return c >= 0xa1 && c <= 0xfe; + } + return 0; +} + /* * Big5 => wchar */ @@ -186,11 +197,7 @@ mbfl_filt_conv_big5_wchar(int c, mbfl_convert_filter *filter) if (filter->from->no_encoding == mbfl_no_encoding_cp950) { /* PUA for CP950 */ - if (w <= 0 && - (((c1 >= 0xfa && c1 <= 0xfe) || (c1 >= 0x8e && c1 <= 0xa0) || - (c1 >= 0x81 && c1 <= 0x8d) ||(c1 >= 0xc7 && c1 <= 0xc8)) - && ((c > 0x39 && c < 0x7f) || (c > 0xa0 && c < 0xff))) || - ((c1 == 0xc6) && (c > 0xa0 && c < 0xff))) { + if (w <= 0 && is_in_cp950_pua(c1, c)) { c2 = c1 << 8 | c; for (k = 0; k < sizeof(cp950_pua_tbl)/(sizeof(unsigned short)*4); k++) { if (c2 >= cp950_pua_tbl[k][2] && c2 <= cp950_pua_tbl[k][3]) { diff --git a/ext/mbstring/tests/bug79037.phpt b/ext/mbstring/tests/bug79037.phpt new file mode 100644 index 0000000000000..94ff01a4a1da9 --- /dev/null +++ b/ext/mbstring/tests/bug79037.phpt @@ -0,0 +1,10 @@ +--TEST-- +Bug #79037: global buffer-overflow in `mbfl_filt_conv_big5_wchar` +--FILE-- + +--EXPECT-- +string(1) "?" From 096945f52568e49b7ad0afbe64e6be37bd8e4144 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:03 +0000 Subject: [PATCH 14/25] commit patch 20488250 --- ext/session/session.c | 10 +- ext/session/session.c.orig | 3279 +++++++++++++++++++++++++++++++ ext/session/tests/bug79221.phpt | 45 + 3 files changed, 3330 insertions(+), 4 deletions(-) create mode 100644 ext/session/session.c.orig create mode 100644 ext/session/tests/bug79221.phpt diff --git a/ext/session/session.c b/ext/session/session.c index 2ae78bdf06736..3c37745f563a8 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -3218,10 +3218,12 @@ static int php_session_rfc1867_callback(unsigned int event, void *event_data, vo if (PS(rfc1867_cleanup)) { php_session_rfc1867_cleanup(progress); } else { - SEPARATE_ARRAY(&progress->data); - add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); - Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; - php_session_rfc1867_update(progress, 1); + if (!Z_ISUNDEF(progress->data)) { + SEPARATE_ARRAY(&progress->data); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 1); + } } php_rshutdown_session_globals(); } diff --git a/ext/session/session.c.orig b/ext/session/session.c.orig new file mode 100644 index 0000000000000..2ae78bdf06736 --- /dev/null +++ b/ext/session/session.c.orig @@ -0,0 +1,3279 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Sascha Schumann | + | Andrei Zmievski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" + +#ifdef PHP_WIN32 +# include "win32/winutil.h" +# include "win32/time.h" +#else +# include +#endif + +#include +#include + +#include "php_ini.h" +#include "SAPI.h" +#include "rfc1867.h" +#include "php_variables.h" +#include "php_session.h" +#include "ext/standard/php_random.h" +#include "ext/standard/php_var.h" +#include "ext/date/php_date.h" +#include "ext/standard/php_lcg.h" +#include "ext/standard/url_scanner_ex.h" +#include "ext/standard/info.h" +#include "zend_smart_str.h" +#include "ext/standard/url.h" +#include "ext/standard/basic_functions.h" +#include "ext/standard/head.h" + +#include "mod_files.h" +#include "mod_user.h" + +#ifdef HAVE_LIBMM +#include "mod_mm.h" +#endif + +PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps) + +static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra); +static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra); +static void php_session_track_init(void); + +/* SessionHandler class */ +zend_class_entry *php_session_class_entry; + +/* SessionHandlerInterface */ +zend_class_entry *php_session_iface_entry; + +/* SessionIdInterface */ +zend_class_entry *php_session_id_iface_entry; + +/* SessionUpdateTimestampHandler class */ +zend_class_entry *php_session_update_timestamp_class_entry; + +/* SessionUpdateTimestampInterface */ +zend_class_entry *php_session_update_timestamp_iface_entry; + +#define PS_MAX_SID_LENGTH 256 + +/* *********** + * Helpers * + *********** */ + +#define IF_SESSION_VARS() \ + if (Z_ISREF_P(&PS(http_session_vars)) && Z_TYPE_P(Z_REFVAL(PS(http_session_vars))) == IS_ARRAY) + +#define SESSION_CHECK_ACTIVE_STATE \ + if (PS(session_status) == php_session_active) { \ + php_error_docref(NULL, E_WARNING, "A session is active. You cannot change the session module's ini settings at this time"); \ + return FAILURE; \ + } + +#define SESSION_CHECK_OUTPUT_STATE \ + if (SG(headers_sent) && stage != ZEND_INI_STAGE_DEACTIVATE) { \ + php_error_docref(NULL, E_WARNING, "Headers already sent. You cannot change the session module's ini settings at this time"); \ + return FAILURE; \ + } + +#define APPLY_TRANS_SID (PS(use_trans_sid) && !PS(use_only_cookies)) + +static int php_session_send_cookie(void); +static int php_session_abort(void); + +/* Initialized in MINIT, readonly otherwise. */ +static int my_module_number = 0; + +/* Dispatched by RINIT and by php_session_destroy */ +static inline void php_rinit_session_globals(void) /* {{{ */ +{ + /* Do NOT init PS(mod_user_names) here! */ + /* TODO: These could be moved to MINIT and removed. These should be initialized by php_rshutdown_session_globals() always when execution is finished. */ + PS(id) = NULL; + PS(session_status) = php_session_none; + PS(in_save_handler) = 0; + PS(set_handler) = 0; + PS(mod_data) = NULL; + PS(mod_user_is_open) = 0; + PS(define_sid) = 1; + PS(session_vars) = NULL; + PS(module_number) = my_module_number; + ZVAL_UNDEF(&PS(http_session_vars)); +} +/* }}} */ + +/* Dispatched by RSHUTDOWN and by php_session_destroy */ +static inline void php_rshutdown_session_globals(void) /* {{{ */ +{ + /* Do NOT destroy PS(mod_user_names) here! */ + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + ZVAL_UNDEF(&PS(http_session_vars)); + } + if (PS(mod_data) || PS(mod_user_implemented)) { + zend_try { + PS(mod)->s_close(&PS(mod_data)); + } zend_end_try(); + } + if (PS(id)) { + zend_string_release(PS(id)); + PS(id) = NULL; + } + + if (PS(session_vars)) { + zend_string_release(PS(session_vars)); + PS(session_vars) = NULL; + } + + /* User save handlers may end up directly here by misuse, bugs in user script, etc. */ + /* Set session status to prevent error while restoring save handler INI value. */ + PS(session_status) = php_session_none; +} +/* }}} */ + +PHPAPI int php_session_destroy(void) /* {{{ */ +{ + int retval = SUCCESS; + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Trying to destroy uninitialized session"); + return FAILURE; + } + + if (PS(id) && PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) { + retval = FAILURE; + php_error_docref(NULL, E_WARNING, "Session object destruction failed"); + } + + php_rshutdown_session_globals(); + php_rinit_session_globals(); + + return retval; +} +/* }}} */ + +PHPAPI void php_add_session_var(zend_string *name) /* {{{ */ +{ + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + if (!zend_hash_exists(Z_ARRVAL_P(sess_var), name)) { + zval empty_var; + ZVAL_NULL(&empty_var); + zend_hash_update(Z_ARRVAL_P(sess_var), name, &empty_var); + } + } +} +/* }}} */ + +PHPAPI zval* php_set_session_var(zend_string *name, zval *state_val, php_unserialize_data_t *var_hash) /* {{{ */ +{ + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + return zend_hash_update(Z_ARRVAL_P(sess_var), name, state_val); + } + return NULL; +} +/* }}} */ + +PHPAPI zval* php_get_session_var(zend_string *name) /* {{{ */ +{ + IF_SESSION_VARS() { + return zend_hash_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name); + } + return NULL; +} +/* }}} */ + +static void php_session_track_init(void) /* {{{ */ +{ + zval session_vars; + zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0); + /* Unconditionally destroy existing array -- possible dirty data */ + zend_delete_global_variable(var_name); + + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + } + + array_init(&session_vars); + ZVAL_NEW_REF(&PS(http_session_vars), &session_vars); + Z_ADDREF_P(&PS(http_session_vars)); + zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars)); + zend_string_release(var_name); +} +/* }}} */ + +static zend_string *php_session_encode(void) /* {{{ */ +{ + IF_SESSION_VARS() { + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to encode session object"); + return NULL; + } + return PS(serializer)->encode(); + } else { + php_error_docref(NULL, E_WARNING, "Cannot encode non-existent session"); + } + return NULL; +} +/* }}} */ + +static int php_session_decode(zend_string *data) /* {{{ */ +{ + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object"); + return FAILURE; + } + if (PS(serializer)->decode(ZSTR_VAL(data), ZSTR_LEN(data)) == FAILURE) { + php_session_destroy(); + php_session_track_init(); + php_error_docref(NULL, E_WARNING, "Failed to decode session object. Session has been destroyed"); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +/* + * Note that we cannot use the BASE64 alphabet here, because + * it contains "/" and "+": both are unacceptable for simple inclusion + * into URLs. + */ + +static char hexconvtab[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-"; + +/* returns a pointer to the byte after the last valid character in out */ +static size_t bin_to_readable(unsigned char *in, size_t inlen, char *out, char nbits) /* {{{ */ +{ + unsigned char *p, *q; + unsigned short w; + size_t len = inlen; + int mask; + int have; + + p = (unsigned char *)in; + q = (unsigned char *)in + inlen; + + w = 0; + have = 0; + mask = (1 << nbits) - 1; + + while (inlen--) { + if (have < nbits) { + if (p < q) { + w |= *p++ << have; + have += 8; + } else { + /* consumed everything? */ + if (have == 0) break; + /* No? We need a final round */ + have = nbits; + } + } + + /* consume nbits */ + *out++ = hexconvtab[w & mask]; + w >>= nbits; + have -= nbits; + } + + *out = '\0'; + return len; +} +/* }}} */ + +#define PS_EXTRA_RAND_BYTES 60 + +PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */ +{ + unsigned char rbuf[PS_MAX_SID_LENGTH + PS_EXTRA_RAND_BYTES]; + zend_string *outid; + + /* Read additional PS_EXTRA_RAND_BYTES just in case CSPRNG is not safe enough */ + if (php_random_bytes_throw(rbuf, PS(sid_length) + PS_EXTRA_RAND_BYTES) == FAILURE) { + return NULL; + } + + outid = zend_string_alloc(PS(sid_length), 0); + ZSTR_LEN(outid) = bin_to_readable(rbuf, PS(sid_length), ZSTR_VAL(outid), (char)PS(sid_bits_per_character)); + + return outid; +} +/* }}} */ + +/* Default session id char validation function allowed by ps_modules. + * If you change the logic here, please also update the error message in + * ps_modules appropriately */ +PHPAPI int php_session_valid_key(const char *key) /* {{{ */ +{ + size_t len; + const char *p; + char c; + int ret = SUCCESS; + + for (p = key; (c = *p); p++) { + /* valid characters are a..z,A..Z,0..9 */ + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == ',' + || c == '-')) { + ret = FAILURE; + break; + } + } + + len = p - key; + + /* Somewhat arbitrary length limit here, but should be way more than + anyone needs and avoids file-level warnings later on if we exceed MAX_PATH */ + if (len == 0 || len > PS_MAX_SID_LENGTH) { + ret = FAILURE; + } + + return ret; +} +/* }}} */ + + +static zend_long php_session_gc(zend_bool immediate) /* {{{ */ +{ + int nrand; + zend_long num = -1; + + /* GC must be done before reading session data. */ + if ((PS(mod_data) || PS(mod_user_implemented))) { + if (immediate) { + PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &num); + return num; + } + nrand = (zend_long) ((float) PS(gc_divisor) * php_combined_lcg()); + if (PS(gc_probability) > 0 && nrand < PS(gc_probability)) { + PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &num); + } + } + return num; +} /* }}} */ + +static int php_session_initialize(void) /* {{{ */ +{ + zend_string *val = NULL; + + PS(session_status) = php_session_active; + + if (!PS(mod)) { + PS(session_status) = php_session_disabled; + php_error_docref(NULL, E_WARNING, "No storage module chosen - failed to initialize session"); + return FAILURE; + } + + /* Open session handler first */ + if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE + /* || PS(mod_data) == NULL */ /* FIXME: open must set valid PS(mod_data) with success */ + ) { + php_session_abort(); + php_error_docref(NULL, E_WARNING, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + + /* If there is no ID, use session module to create one */ + if (!PS(id) || !ZSTR_VAL(PS(id))[0]) { + if (PS(id)) { + zend_string_release(PS(id)); + } + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + php_session_abort(); + zend_throw_error(NULL, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + } else if (PS(use_strict_mode) && PS(mod)->s_validate_sid && + PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { + if (PS(id)) { + zend_string_release(PS(id)); + } + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(id) = php_session_create_id(NULL); + } + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + } + + if (php_session_reset_id() == FAILURE) { + php_session_abort(); + return FAILURE; + } + + /* Read data */ + php_session_track_init(); + if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, PS(gc_maxlifetime)) == FAILURE) { + php_session_abort(); + /* FYI: Some broken save handlers return FAILURE for non-existent session ID, this is incorrect */ + php_error_docref(NULL, E_WARNING, "Failed to read session data: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + + /* GC must be done after read */ + php_session_gc(0); + + if (PS(session_vars)) { + zend_string_release(PS(session_vars)); + PS(session_vars) = NULL; + } + if (val) { + if (PS(lazy_write)) { + PS(session_vars) = zend_string_copy(val); + } + php_session_decode(val); + zend_string_release(val); + } + return SUCCESS; +} +/* }}} */ + +static void php_session_save_current_state(int write) /* {{{ */ +{ + int ret = FAILURE; + + if (write) { + IF_SESSION_VARS() { + if (PS(mod_data) || PS(mod_user_implemented)) { + zend_string *val; + + val = php_session_encode(); + if (val) { + if (PS(lazy_write) && PS(session_vars) + && PS(mod)->s_update_timestamp + && PS(mod)->s_update_timestamp != php_session_update_timestamp + && ZSTR_LEN(val) == ZSTR_LEN(PS(session_vars)) + && !memcmp(ZSTR_VAL(val), ZSTR_VAL(PS(session_vars)), ZSTR_LEN(val)) + ) { + ret = PS(mod)->s_update_timestamp(&PS(mod_data), PS(id), val, PS(gc_maxlifetime)); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), val, PS(gc_maxlifetime)); + } + zend_string_release(val); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime)); + } + } + + if ((ret == FAILURE) && !EG(exception)) { + if (!PS(mod_user_implemented)) { + php_error_docref(NULL, E_WARNING, "Failed to write session data (%s). Please " + "verify that the current setting of session.save_path " + "is correct (%s)", + PS(mod)->s_name, + PS(save_path)); + } else { + php_error_docref(NULL, E_WARNING, "Failed to write session data using user " + "defined save handler. (session.save_path: %s)", PS(save_path)); + } + } + } + } + + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } +} +/* }}} */ + +static void php_session_normalize_vars() /* {{{ */ +{ + PS_ENCODE_VARS; + + IF_SESSION_VARS() { + PS_ENCODE_LOOP( + if (Z_TYPE_P(struc) == IS_PTR) { + zval *zv = (zval *)Z_PTR_P(struc); + ZVAL_COPY_VALUE(struc, zv); + ZVAL_UNDEF(zv); + } + ); + } +} +/* }}} */ + +/* ************************* + * INI Settings/Handlers * + ************************* */ + +static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */ +{ + ps_module *tmp; + + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + tmp = _php_find_ps_module(ZSTR_VAL(new_value)); + + if (PG(modules_activated) && !tmp) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "Cannot find save handler '%s'", ZSTR_VAL(new_value)); + } + + return FAILURE; + } + + /* "user" save handler should not be set by user */ + if (!PS(set_handler) && tmp == ps_user_ptr) { + php_error_docref(NULL, E_RECOVERABLE_ERROR, "Cannot set 'user' save handler by ini_set() or session_module_name()"); + return FAILURE; + } + + PS(default_mod) = PS(mod); + PS(mod) = tmp; + + return SUCCESS; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateSerializer) /* {{{ */ +{ + const ps_serializer *tmp; + + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + tmp = _php_find_ps_serializer(ZSTR_VAL(new_value)); + + if (PG(modules_activated) && !tmp) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "Cannot find serialization handler '%s'", ZSTR_VAL(new_value)); + } + return FAILURE; + } + PS(serializer) = tmp; + + return SUCCESS; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateTransSid) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + if (!strncasecmp(ZSTR_VAL(new_value), "on", sizeof("on"))) { + PS(use_trans_sid) = (zend_bool) 1; + } else { + PS(use_trans_sid) = (zend_bool) atoi(ZSTR_VAL(new_value)); + } + + return SUCCESS; +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSaveDir) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + /* Only do the safemode/open_basedir check at runtime */ + if (stage == PHP_INI_STAGE_RUNTIME || stage == PHP_INI_STAGE_HTACCESS) { + char *p; + + if (memchr(ZSTR_VAL(new_value), '\0', ZSTR_LEN(new_value)) != NULL) { + return FAILURE; + } + + /* we do not use zend_memrchr() since path can contain ; itself */ + if ((p = strchr(ZSTR_VAL(new_value), ';'))) { + char *p2; + p++; + if ((p2 = strchr(p, ';'))) { + p = p2 + 1; + } + } else { + p = ZSTR_VAL(new_value); + } + + if (PG(open_basedir) && *p && php_check_open_basedir(p)) { + return FAILURE; + } + } + + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateName) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + /* Numeric session.name won't work at all */ + if ((!ZSTR_LEN(new_value) || is_numeric_string(ZSTR_VAL(new_value), ZSTR_LEN(new_value), NULL, NULL, 0))) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME || stage == ZEND_INI_STAGE_ACTIVATE || stage == ZEND_INI_STAGE_STARTUP) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "session.name cannot be a numeric or empty '%s'", ZSTR_VAL(new_value)); + } + return FAILURE; + } + + return OnUpdateStringUnempty(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateCookieLifetime) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + if (atol(ZSTR_VAL(new_value)) < 0) { + php_error_docref(NULL, E_WARNING, "CookieLifetime cannot be negative"); + return FAILURE; + } + return OnUpdateLongGEZero(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionLong) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionString) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionBool) /* {{{ */ +{ + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSidLength) /* {{{ */ +{ + zend_long val; + char *endptr = NULL; + + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + val = ZEND_STRTOL(ZSTR_VAL(new_value), &endptr, 10); + if (endptr && (*endptr == '\0') + && val >= 22 && val <= PS_MAX_SID_LENGTH) { + /* Numeric value */ + PS(sid_length) = val; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, "session.configuration 'session.sid_length' must be between 22 and 256."); + return FAILURE; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateSidBits) /* {{{ */ +{ + zend_long val; + char *endptr = NULL; + + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + val = ZEND_STRTOL(ZSTR_VAL(new_value), &endptr, 10); + if (endptr && (*endptr == '\0') + && val >= 4 && val <=6) { + /* Numeric value */ + PS(sid_bits_per_character) = val; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, "session.configuration 'session.sid_bits' must be between 4 and 6."); + return FAILURE; +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateLazyWrite) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + + +static PHP_INI_MH(OnUpdateRfc1867Freq) /* {{{ */ +{ + int tmp; + tmp = zend_atoi(ZSTR_VAL(new_value), (int)ZSTR_LEN(new_value)); + if(tmp < 0) { + php_error_docref(NULL, E_WARNING, "session.upload_progress.freq must be greater than or equal to zero"); + return FAILURE; + } + if(ZSTR_LEN(new_value) > 0 && ZSTR_VAL(new_value)[ZSTR_LEN(new_value)-1] == '%') { + if(tmp > 100) { + php_error_docref(NULL, E_WARNING, "session.upload_progress.freq cannot be over 100%%"); + return FAILURE; + } + PS(rfc1867_freq) = -tmp; + } else { + PS(rfc1867_freq) = tmp; + } + return SUCCESS; +} /* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("session.save_path", "", PHP_INI_ALL, OnUpdateSaveDir, save_path, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.name", "PHPSESSID", PHP_INI_ALL, OnUpdateName, session_name, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.save_handler", "files", PHP_INI_ALL, OnUpdateSaveHandler) + STD_PHP_INI_BOOLEAN("session.auto_start", "0", PHP_INI_PERDIR, OnUpdateBool, auto_start, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_probability", "1", PHP_INI_ALL, OnUpdateSessionLong, gc_probability, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_divisor", "100", PHP_INI_ALL, OnUpdateSessionLong, gc_divisor, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_maxlifetime", "1440", PHP_INI_ALL, OnUpdateSessionLong, gc_maxlifetime, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.serialize_handler", "php", PHP_INI_ALL, OnUpdateSerializer) + STD_PHP_INI_ENTRY("session.cookie_lifetime", "0", PHP_INI_ALL, OnUpdateCookieLifetime,cookie_lifetime, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateSessionString, cookie_path, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateSessionString, cookie_domain, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_secure", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_secure, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_httponly", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_httponly, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_cookies, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_only_cookies, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_strict_mode", "0", PHP_INI_ALL, OnUpdateSessionBool, use_strict_mode, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateSessionString, extern_referer_chk, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateSessionString, cache_limiter, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateSessionLong, cache_expire, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid) + PHP_INI_ENTRY("session.sid_length", "32", PHP_INI_ALL, OnUpdateSidLength) + PHP_INI_ENTRY("session.sid_bits_per_character", "4", PHP_INI_ALL, OnUpdateSidBits) + STD_PHP_INI_BOOLEAN("session.lazy_write", "1", PHP_INI_ALL, OnUpdateLazyWrite, lazy_write, php_ps_globals, ps_globals) + + /* Upload progress */ + STD_PHP_INI_BOOLEAN("session.upload_progress.enabled", + "1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_enabled, php_ps_globals, ps_globals) + STD_PHP_INI_BOOLEAN("session.upload_progress.cleanup", + "1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_cleanup, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.prefix", + "upload_progress_", ZEND_INI_PERDIR, OnUpdateString, rfc1867_prefix, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.name", + "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateString, rfc1867_name, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.freq", "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.min_freq", + "1", ZEND_INI_PERDIR, OnUpdateReal, rfc1867_min_freq,php_ps_globals, ps_globals) + + /* Commented out until future discussion */ + /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */ +PHP_INI_END() +/* }}} */ + +/* *************** + * Serializers * + *************** */ +PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + + IF_SESSION_VARS() { + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + } + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */ +{ + const char *endptr = val + vallen; + zval session_vars; + php_unserialize_data_t var_hash; + int result; + zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0); + + ZVAL_NULL(&session_vars); + PHP_VAR_UNSERIALIZE_INIT(var_hash); + result = php_var_unserialize( + &session_vars, (const unsigned char **)&val, (const unsigned char *)endptr, &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + if (!result) { + zval_ptr_dtor(&session_vars); + ZVAL_NULL(&session_vars); + } + + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + } + if (Z_TYPE(session_vars) == IS_NULL) { + array_init(&session_vars); + } + ZVAL_NEW_REF(&PS(http_session_vars), &session_vars); + Z_ADDREF_P(&PS(http_session_vars)); + zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars)); + zend_string_release(var_name); + return SUCCESS; +} +/* }}} */ + +#define PS_BIN_NR_OF_BITS 8 +#define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1)) +#define PS_BIN_MAX (PS_BIN_UNDEF-1) + +PS_SERIALIZER_ENCODE_FUNC(php_binary) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + PS_ENCODE_VARS; + + PHP_VAR_SERIALIZE_INIT(var_hash); + + PS_ENCODE_LOOP( + if (ZSTR_LEN(key) > PS_BIN_MAX) continue; + smart_str_appendc(&buf, (unsigned char)ZSTR_LEN(key)); + smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key)); + php_var_serialize(&buf, struc, &var_hash); + ); + + smart_str_0(&buf); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */ +{ + const char *p; + const char *endptr = val + vallen; + int namelen; + zend_string *name; + php_unserialize_data_t var_hash; + zval *current, rv; + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + for (p = val; p < endptr; ) { + namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF); + + if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) { + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; + } + + name = zend_string_init(p + 1, namelen, 0); + p += namelen + 1; + current = var_tmp_var(&var_hash); + + if (php_var_unserialize(current, (const unsigned char **) &p, (const unsigned char *) endptr, &var_hash)) { + ZVAL_PTR(&rv, current); + php_set_session_var(name, &rv, &var_hash); + } else { + zend_string_release(name); + php_session_normalize_vars(); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; + } + zend_string_release(name); + } + + php_session_normalize_vars(); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + return SUCCESS; +} +/* }}} */ + +#define PS_DELIMITER '|' + +PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + PS_ENCODE_VARS; + + PHP_VAR_SERIALIZE_INIT(var_hash); + + PS_ENCODE_LOOP( + smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key)); + if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) { + PHP_VAR_SERIALIZE_DESTROY(var_hash); + smart_str_free(&buf); + return NULL; + } + smart_str_appendc(&buf, PS_DELIMITER); + php_var_serialize(&buf, struc, &var_hash); + ); + + smart_str_0(&buf); + + PHP_VAR_SERIALIZE_DESTROY(var_hash); + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */ +{ + const char *p, *q; + const char *endptr = val + vallen; + ptrdiff_t namelen; + zend_string *name; + int retval = SUCCESS; + php_unserialize_data_t var_hash; + zval *current, rv; + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + p = val; + + while (p < endptr) { + q = p; + while (*q != PS_DELIMITER) { + if (++q >= endptr) goto break_outer_loop; + } + + namelen = q - p; + name = zend_string_init(p, namelen, 0); + q++; + + current = var_tmp_var(&var_hash); + if (php_var_unserialize(current, (const unsigned char **)&q, (const unsigned char *)endptr, &var_hash)) { + ZVAL_PTR(&rv, current); + php_set_session_var(name, &rv, &var_hash); + } else { + zend_string_release(name); + retval = FAILURE; + goto break_outer_loop; + } + zend_string_release(name); + p = q; + } + +break_outer_loop: + php_session_normalize_vars(); + + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + return retval; +} +/* }}} */ + +#define MAX_SERIALIZERS 32 +#define PREDEFINED_SERIALIZERS 3 + +static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = { + PS_SERIALIZER_ENTRY(php_serialize), + PS_SERIALIZER_ENTRY(php), + PS_SERIALIZER_ENTRY(php_binary) +}; + +PHPAPI int php_session_register_serializer(const char *name, zend_string *(*encode)(PS_SERIALIZER_ENCODE_ARGS), int (*decode)(PS_SERIALIZER_DECODE_ARGS)) /* {{{ */ +{ + int ret = FAILURE; + int i; + + for (i = 0; i < MAX_SERIALIZERS; i++) { + if (ps_serializers[i].name == NULL) { + ps_serializers[i].name = name; + ps_serializers[i].encode = encode; + ps_serializers[i].decode = decode; + ps_serializers[i + 1].name = NULL; + ret = SUCCESS; + break; + } + } + return ret; +} +/* }}} */ + +/* ******************* + * Storage Modules * + ******************* */ + +#define MAX_MODULES 32 +#define PREDEFINED_MODULES 2 + +static ps_module *ps_modules[MAX_MODULES + 1] = { + ps_files_ptr, + ps_user_ptr +}; + +PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */ +{ + int ret = FAILURE; + int i; + + for (i = 0; i < MAX_MODULES; i++) { + if (!ps_modules[i]) { + ps_modules[i] = ptr; + ret = SUCCESS; + break; + } + } + return ret; +} +/* }}} */ + +/* Dummy PS module function */ +PHPAPI int php_session_validate_sid(PS_VALIDATE_SID_ARGS) { + return SUCCESS; +} + +/* Dummy PS module function */ +PHPAPI int php_session_update_timestamp(PS_UPDATE_TIMESTAMP_ARGS) { + return SUCCESS; +} + + +/* ****************** + * Cache Limiters * + ****************** */ + +typedef struct { + char *name; + void (*func)(void); +} php_session_cache_limiter_t; + +#define CACHE_LIMITER(name) _php_cache_limiter_##name +#define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)(void) +#define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) }, +#define ADD_HEADER(a) sapi_add_header(a, strlen(a), 1); +#define MAX_STR 512 + +static char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static char *week_days[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" +}; + +static inline void strcpy_gmt(char *ubuf, time_t *when) /* {{{ */ +{ + char buf[MAX_STR]; + struct tm tm, *res; + int n; + + res = php_gmtime_r(when, &tm); + + if (!res) { + ubuf[0] = '\0'; + return; + } + + n = slprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", /* SAFE */ + week_days[tm.tm_wday], tm.tm_mday, + month_names[tm.tm_mon], tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, + tm.tm_sec); + memcpy(ubuf, buf, n); + ubuf[n] = '\0'; +} +/* }}} */ + +static inline void last_modified(void) /* {{{ */ +{ + const char *path; + zend_stat_t sb; + char buf[MAX_STR + 1]; + + path = SG(request_info).path_translated; + if (path) { + if (VCWD_STAT(path, &sb) == -1) { + return; + } + +#define LAST_MODIFIED "Last-Modified: " + memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1); + strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime); + ADD_HEADER(buf); + } +} +/* }}} */ + +#define EXPIRES "Expires: " +CACHE_LIMITER_FUNC(public) /* {{{ */ +{ + char buf[MAX_STR + 1]; + struct timeval tv; + time_t now; + + gettimeofday(&tv, NULL); + now = tv.tv_sec + PS(cache_expire) * 60; + memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1); + strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now); + ADD_HEADER(buf); + + snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */ + ADD_HEADER(buf); + + last_modified(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(private_no_expire) /* {{{ */ +{ + char buf[MAX_STR + 1]; + + snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */ + ADD_HEADER(buf); + + last_modified(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(private) /* {{{ */ +{ + ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT"); + CACHE_LIMITER(private_no_expire)(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(nocache) /* {{{ */ +{ + ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT"); + + /* For HTTP/1.1 conforming clients */ + ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate"); + + /* For HTTP/1.0 conforming clients */ + ADD_HEADER("Pragma: no-cache"); +} +/* }}} */ + +static php_session_cache_limiter_t php_session_cache_limiters[] = { + CACHE_LIMITER_ENTRY(public) + CACHE_LIMITER_ENTRY(private) + CACHE_LIMITER_ENTRY(private_no_expire) + CACHE_LIMITER_ENTRY(nocache) + {0} +}; + +static int php_session_cache_limiter(void) /* {{{ */ +{ + php_session_cache_limiter_t *lim; + + if (PS(cache_limiter)[0] == '\0') return 0; + if (PS(session_status) != php_session_active) return -1; + + if (SG(headers_sent)) { + const char *output_start_filename = php_output_get_start_filename(); + int output_start_lineno = php_output_get_start_lineno(); + + php_session_abort(); + if (output_start_filename) { + php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno); + } else { + php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent"); + } + return -2; + } + + for (lim = php_session_cache_limiters; lim->name; lim++) { + if (!strcasecmp(lim->name, PS(cache_limiter))) { + lim->func(); + return 0; + } + } + + return -1; +} +/* }}} */ + +/* ********************* + * Cookie Management * + ********************* */ + +/* + * Remove already sent session ID cookie. + * It must be directly removed from SG(sapi_header) because sapi_add_header_ex() + * removes all of matching cookie. i.e. It deletes all of Set-Cookie headers. + */ +static void php_session_remove_cookie(void) { + sapi_header_struct *header; + zend_llist *l = &SG(sapi_headers).headers; + zend_llist_element *next; + zend_llist_element *current; + char *session_cookie; + zend_string *e_session_name; + size_t session_cookie_len; + size_t len = sizeof("Set-Cookie")-1; + + e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name))); + spprintf(&session_cookie, 0, "Set-Cookie: %s=", ZSTR_VAL(e_session_name)); + zend_string_free(e_session_name); + + session_cookie_len = strlen(session_cookie); + current = l->head; + while (current) { + header = (sapi_header_struct *)(current->data); + next = current->next; + if (header->header_len > len && header->header[len] == ':' + && !strncmp(header->header, session_cookie, session_cookie_len)) { + if (current->prev) { + current->prev->next = next; + } else { + l->head = next; + } + if (next) { + next->prev = current->prev; + } else { + l->tail = current->prev; + } + sapi_free_header(header); + efree(current); + --l->count; + } + current = next; + } + efree(session_cookie); +} + +static int php_session_send_cookie(void) /* {{{ */ +{ + smart_str ncookie = {0}; + zend_string *date_fmt = NULL; + zend_string *e_session_name, *e_id; + + if (SG(headers_sent)) { + const char *output_start_filename = php_output_get_start_filename(); + int output_start_lineno = php_output_get_start_lineno(); + + if (output_start_filename) { + php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)", output_start_filename, output_start_lineno); + } else { + php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent"); + } + return FAILURE; + } + + /* URL encode session_name and id because they might be user supplied */ + e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name))); + e_id = php_url_encode(ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id))); + + smart_str_appendl(&ncookie, "Set-Cookie: ", sizeof("Set-Cookie: ")-1); + smart_str_appendl(&ncookie, ZSTR_VAL(e_session_name), ZSTR_LEN(e_session_name)); + smart_str_appendc(&ncookie, '='); + smart_str_appendl(&ncookie, ZSTR_VAL(e_id), ZSTR_LEN(e_id)); + + zend_string_release(e_session_name); + zend_string_release(e_id); + + if (PS(cookie_lifetime) > 0) { + struct timeval tv; + time_t t; + + gettimeofday(&tv, NULL); + t = tv.tv_sec + PS(cookie_lifetime); + + if (t > 0) { + date_fmt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, t, 0); + smart_str_appends(&ncookie, COOKIE_EXPIRES); + smart_str_appendl(&ncookie, ZSTR_VAL(date_fmt), ZSTR_LEN(date_fmt)); + zend_string_release(date_fmt); + + smart_str_appends(&ncookie, COOKIE_MAX_AGE); + smart_str_append_long(&ncookie, PS(cookie_lifetime)); + } + } + + if (PS(cookie_path)[0]) { + smart_str_appends(&ncookie, COOKIE_PATH); + smart_str_appends(&ncookie, PS(cookie_path)); + } + + if (PS(cookie_domain)[0]) { + smart_str_appends(&ncookie, COOKIE_DOMAIN); + smart_str_appends(&ncookie, PS(cookie_domain)); + } + + if (PS(cookie_secure)) { + smart_str_appends(&ncookie, COOKIE_SECURE); + } + + if (PS(cookie_httponly)) { + smart_str_appends(&ncookie, COOKIE_HTTPONLY); + } + + smart_str_0(&ncookie); + + php_session_remove_cookie(); /* remove already sent session ID cookie */ + /* 'replace' must be 0 here, else a previous Set-Cookie + header, probably sent with setcookie() will be replaced! */ + sapi_add_header_ex(estrndup(ZSTR_VAL(ncookie.s), ZSTR_LEN(ncookie.s)), ZSTR_LEN(ncookie.s), 0, 0); + smart_str_free(&ncookie); + + return SUCCESS; +} +/* }}} */ + +PHPAPI ps_module *_php_find_ps_module(char *name) /* {{{ */ +{ + ps_module *ret = NULL; + ps_module **mod; + int i; + + for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) { + if (*mod && !strcasecmp(name, (*mod)->s_name)) { + ret = *mod; + break; + } + } + return ret; +} +/* }}} */ + +PHPAPI const ps_serializer *_php_find_ps_serializer(char *name) /* {{{ */ +{ + const ps_serializer *ret = NULL; + const ps_serializer *mod; + + for (mod = ps_serializers; mod->name; mod++) { + if (!strcasecmp(name, mod->name)) { + ret = mod; + break; + } + } + return ret; +} +/* }}} */ + +static void ppid2sid(zval *ppid) { + ZVAL_DEREF(ppid); + if (Z_TYPE_P(ppid) == IS_STRING) { + PS(id) = zend_string_init(Z_STRVAL_P(ppid), Z_STRLEN_P(ppid), 0); + PS(send_cookie) = 0; + } else { + PS(id) = NULL; + PS(send_cookie) = 1; + } +} + + +PHPAPI int php_session_reset_id(void) /* {{{ */ +{ + int module_number = PS(module_number); + zval *sid, *data, *ppid; + zend_bool apply_trans_sid; + + if (!PS(id)) { + php_error_docref(NULL, E_WARNING, "Cannot set session ID - session ID is not initialized"); + return FAILURE; + } + + if (PS(use_cookies) && PS(send_cookie)) { + php_session_send_cookie(); + PS(send_cookie) = 0; + } + + /* If the SID constant exists, destroy it. */ + /* We must not delete any items in EG(zend_contants) */ + /* zend_hash_str_del(EG(zend_constants), "sid", sizeof("sid") - 1); */ + sid = zend_get_constant_str("SID", sizeof("SID") - 1); + + if (PS(define_sid)) { + smart_str var = {0}; + + smart_str_appends(&var, PS(session_name)); + smart_str_appendc(&var, '='); + smart_str_appends(&var, ZSTR_VAL(PS(id))); + smart_str_0(&var); + if (sid) { + zend_string_release(Z_STR_P(sid)); + ZVAL_NEW_STR(sid, var.s); + } else { + REGISTER_STRINGL_CONSTANT("SID", ZSTR_VAL(var.s), ZSTR_LEN(var.s), 0); + smart_str_free(&var); + } + } else { + if (sid) { + zend_string_release(Z_STR_P(sid)); + ZVAL_EMPTY_STRING(sid); + } else { + REGISTER_STRINGL_CONSTANT("SID", "", 0, 0); + } + } + + /* Apply trans sid if sid cookie is not set */ + apply_trans_sid = 0; + if (APPLY_TRANS_SID) { + apply_trans_sid = 1; + if (PS(use_cookies) && + (data = zend_hash_str_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && + (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), strlen(PS(session_name))))) { + ZVAL_DEREF(ppid); + apply_trans_sid = 0; + } + } + } + if (apply_trans_sid) { + zend_string *sname; + sname = zend_string_init(PS(session_name), strlen(PS(session_name)), 0); + php_url_scanner_reset_session_var(sname, 1); /* This may fail when session name has changed */ + zend_string_release(sname); + php_url_scanner_add_session_var(PS(session_name), strlen(PS(session_name)), ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id)), 1); + } + return SUCCESS; +} +/* }}} */ + + +PHPAPI int php_session_start(void) /* {{{ */ +{ + zval *ppid; + zval *data; + char *p, *value; + size_t lensess; + + switch (PS(session_status)) { + case php_session_active: + php_error(E_NOTICE, "A session had already been started - ignoring session_start()"); + return FAILURE; + break; + + case php_session_disabled: + value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0); + if (!PS(mod) && value) { + PS(mod) = _php_find_ps_module(value); + if (!PS(mod)) { + php_error_docref(NULL, E_WARNING, "Cannot find save handler '%s' - session startup failed", value); + return FAILURE; + } + } + value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0); + if (!PS(serializer) && value) { + PS(serializer) = _php_find_ps_serializer(value); + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value); + return FAILURE; + } + } + PS(session_status) = php_session_none; + /* Fall through */ + + case php_session_none: + default: + /* Setup internal flags */ + PS(define_sid) = !PS(use_only_cookies); /* SID constant is defined when non-cookie ID is used */ + PS(send_cookie) = PS(use_cookies) || PS(use_only_cookies); + } + + lensess = strlen(PS(session_name)); + + /* + * Cookies are preferred, because initially cookie and get + * variables will be available. + * URL/POST session ID may be used when use_only_cookies=Off. + * session.use_strice_mode=On prevents session adoption. + * Session based file upload progress uses non-cookie ID. + */ + + if (!PS(id)) { + if (PS(use_cookies) && (data = zend_hash_str_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + PS(send_cookie) = 0; + PS(define_sid) = 0; + } + } + /* Initilize session ID from non cookie values */ + if (!PS(use_only_cookies)) { + if (!PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_GET", sizeof("_GET") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + } + } + if (!PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_POST", sizeof("_POST") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + } + } + /* Check the REQUEST_URI symbol for a string of the form + * '=' to allow URLs of the form + * http://yoursite/=/script.php */ + if (!PS(id) && zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1) == SUCCESS && + (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI") - 1)) && + Z_TYPE_P(data) == IS_STRING && + (p = strstr(Z_STRVAL_P(data), PS(session_name))) && + p[lensess] == '=' + ) { + char *q; + p += lensess + 1; + if ((q = strpbrk(p, "/?\\"))) { + PS(id) = zend_string_init(p, q - p, 0); + } + } + /* Check whether the current request was referred to by + * an external site which invalidates the previously found id. */ + if (PS(id) && PS(extern_referer_chk)[0] != '\0' && + !Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER]) && + (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER") - 1)) && + Z_TYPE_P(data) == IS_STRING && + Z_STRLEN_P(data) != 0 && + strstr(Z_STRVAL_P(data), PS(extern_referer_chk)) == NULL + ) { + zend_string_release(PS(id)); + PS(id) = NULL; + } + } + } + + /* Finally check session id for dangerous characters + * Security note: session id may be embedded in HTML pages.*/ + if (PS(id) && strpbrk(ZSTR_VAL(PS(id)), "\r\n\t <>'\"\\")) { + zend_string_release(PS(id)); + PS(id) = NULL; + } + + if (php_session_initialize() == FAILURE + || php_session_cache_limiter() == -2) { + PS(session_status) = php_session_none; + if (PS(id)) { + zend_string_release(PS(id)); + PS(id) = NULL; + } + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +PHPAPI int php_session_flush(int write) /* {{{ */ +{ + if (PS(session_status) == php_session_active) { + php_session_save_current_state(write); + PS(session_status) = php_session_none; + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static int php_session_abort(void) /* {{{ */ +{ + if (PS(session_status) == php_session_active) { + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } + PS(session_status) = php_session_none; + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static int php_session_reset(void) /* {{{ */ +{ + if (PS(session_status) == php_session_active + && php_session_initialize() == SUCCESS) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + + +/* This API is not used by any PHP modules including session currently. + session_adapt_url() may be used to set Session ID to target url without + starting "URL-Rewriter" output handler. */ +PHPAPI void session_adapt_url(const char *url, size_t urllen, char **new, size_t *newlen) /* {{{ */ +{ + if (APPLY_TRANS_SID && (PS(session_status) == php_session_active)) { + *new = php_url_scanner_adapt_single_url(url, urllen, PS(session_name), ZSTR_VAL(PS(id)), newlen, 1); + } +} +/* }}} */ + +/* ******************************** + * Userspace exported functions * + ******************************** */ + +/* {{{ proto bool session_set_cookie_params(int lifetime [, string path [, string domain [, bool secure[, bool httponly]]]]) + Set session cookie parameters */ +static PHP_FUNCTION(session_set_cookie_params) +{ + zval *lifetime; + zend_string *path = NULL, *domain = NULL; + int argc = ZEND_NUM_ARGS(); + zend_bool secure = 0, httponly = 0; + zend_string *ini_name; + + if (!PS(use_cookies) || + zend_parse_parameters(argc, "z|SSbb", &lifetime, &path, &domain, &secure, &httponly) == FAILURE) { + return; + } + + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change session cookie parameters when session is active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session cookie parameters when headers already sent"); + RETURN_FALSE; + } + + convert_to_string_ex(lifetime); + + ini_name = zend_string_init("session.cookie_lifetime", sizeof("session.cookie_lifetime") - 1, 0); + if (zend_alter_ini_entry(ini_name, Z_STR_P(lifetime), PHP_INI_USER, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_string_release(ini_name); + RETURN_FALSE; + } + zend_string_release(ini_name); + + if (path) { + ini_name = zend_string_init("session.cookie_path", sizeof("session.cookie_path") - 1, 0); + if (zend_alter_ini_entry(ini_name, path, PHP_INI_USER, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_string_release(ini_name); + RETURN_FALSE; + } + zend_string_release(ini_name); + } + if (domain) { + ini_name = zend_string_init("session.cookie_domain", sizeof("session.cookie_domain") - 1, 0); + if (zend_alter_ini_entry(ini_name, domain, PHP_INI_USER, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_string_release(ini_name); + RETURN_FALSE; + } + zend_string_release(ini_name); + } + + if (argc > 3) { + ini_name = zend_string_init("session.cookie_secure", sizeof("session.cookie_secure") - 1, 0); + if (zend_alter_ini_entry_chars(ini_name, secure ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_string_release(ini_name); + RETURN_FALSE; + } + zend_string_release(ini_name); + } + if (argc > 4) { + ini_name = zend_string_init("session.cookie_httponly", sizeof("session.cookie_httponly") - 1, 0); + if (zend_alter_ini_entry_chars(ini_name, httponly ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_string_release(ini_name); + RETURN_FALSE; + } + zend_string_release(ini_name); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array session_get_cookie_params(void) + Return the session cookie parameters */ +static PHP_FUNCTION(session_get_cookie_params) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + add_assoc_long(return_value, "lifetime", PS(cookie_lifetime)); + add_assoc_string(return_value, "path", PS(cookie_path)); + add_assoc_string(return_value, "domain", PS(cookie_domain)); + add_assoc_bool(return_value, "secure", PS(cookie_secure)); + add_assoc_bool(return_value, "httponly", PS(cookie_httponly)); +} +/* }}} */ + +/* {{{ proto string session_name([string newname]) + Return the current session name. If newname is given, the session name is replaced with newname */ +static PHP_FUNCTION(session_name) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change session name when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session name when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(session_name)); + + if (name) { + ini_name = zend_string_init("session.name", sizeof("session.name") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(ini_name); + } +} +/* }}} */ + +/* {{{ proto string session_module_name([string newname]) + Return the current module name used for accessing session data. If newname is given, the module name is replaced with newname */ +static PHP_FUNCTION(session_module_name) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler module when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler module when headers already sent"); + RETURN_FALSE; + } + + /* Set return_value to current module name */ + if (PS(mod) && PS(mod)->s_name) { + RETVAL_STRING(PS(mod)->s_name); + } else { + RETVAL_EMPTY_STRING(); + } + + if (name) { + if (!_php_find_ps_module(ZSTR_VAL(name))) { + php_error_docref(NULL, E_WARNING, "Cannot find named PHP session module (%s)", ZSTR_VAL(name)); + + zval_dtor(return_value); + RETURN_FALSE; + } + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } + PS(mod_data) = NULL; + + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(ini_name); + } +} +/* }}} */ + +/* {{{ proto bool session_set_save_handler(string open, string close, string read, string write, string destroy, string gc, string create_sid) + Sets user-level functions */ +static PHP_FUNCTION(session_set_save_handler) +{ + zval *args = NULL; + int i, num_args, argc = ZEND_NUM_ARGS(); + zend_string *ini_name, *ini_val; + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler when session is active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler when headers already sent"); + RETURN_FALSE; + } + + if (argc > 0 && argc <= 2) { + zval *obj = NULL; + zend_string *func_name; + zend_function *current_mptr; + zend_bool register_shutdown = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &obj, php_session_iface_entry, ®ister_shutdown) == FAILURE) { + RETURN_FALSE; + } + + /* For compatibility reason, implemeted interface is not checked */ + /* Find implemented methods - SessionHandlerInterface */ + i = 0; + ZEND_HASH_FOREACH_STR_KEY(&php_session_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + php_error_docref(NULL, E_ERROR, "Session handler's function table is corrupt"); + RETURN_FALSE; + } + + ++i; + } ZEND_HASH_FOREACH_END(); + + /* Find implemented methods - SessionIdInterface (optional) */ + ZEND_HASH_FOREACH_STR_KEY(&php_session_id_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + + ++i; + } ZEND_HASH_FOREACH_END(); + + /* Find implemented methods - SessionUpdateTimestampInterface (optional) */ + ZEND_HASH_FOREACH_STR_KEY(&php_session_update_timestamp_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + ++i; + } ZEND_HASH_FOREACH_END(); + + if (register_shutdown) { + /* create shutdown function */ + php_shutdown_function_entry shutdown_function_entry; + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0); + + ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_register_shutdown"); + + /* add shutdown function, removing the old one if it exists */ + if (!register_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1, &shutdown_function_entry)) { + zval_ptr_dtor(&shutdown_function_entry.arguments[0]); + efree(shutdown_function_entry.arguments); + php_error_docref(NULL, E_WARNING, "Unable to register session shutdown function"); + RETURN_FALSE; + } + } else { + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); + } + + if (PS(mod) && PS(session_status) != php_session_active && PS(mod) != &ps_mod_user) { + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + ini_val = zend_string_init("user", sizeof("user") - 1, 0); + PS(set_handler) = 1; + zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + PS(set_handler) = 0; + zend_string_release(ini_val); + zend_string_release(ini_name); + } + + RETURN_TRUE; + } + + /* Set procedural save handler functions */ + if (argc < 6 || PS_NUM_APIS < argc) { + WRONG_PARAM_COUNT; + } + + if (zend_parse_parameters(argc, "+", &args, &num_args) == FAILURE) { + return; + } + + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); + + /* At this point argc can only be between 6 and PS_NUM_APIS */ + for (i = 0; i < argc; i++) { + if (!zend_is_callable(&args[i], 0, NULL)) { + zend_string *name = zend_get_callable_name(&args[i]); + php_error_docref(NULL, E_WARNING, "Argument %d is not a valid callback", i+1); + zend_string_release(name); + RETURN_FALSE; + } + } + + if (PS(mod) && PS(mod) != &ps_mod_user) { + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + ini_val = zend_string_init("user", sizeof("user") - 1, 0); + PS(set_handler) = 1; + zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + PS(set_handler) = 0; + zend_string_release(ini_val); + zend_string_release(ini_name); + } + + for (i = 0; i < argc; i++) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + ZVAL_COPY(&PS(mod_user_names).names[i], &args[i]); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string session_save_path([string newname]) + Return the current save path passed to module_name. If newname is given, the save path is replaced with newname */ +static PHP_FUNCTION(session_save_path) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save path when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save path when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(save_path)); + + if (name) { + if (memchr(ZSTR_VAL(name), '\0', ZSTR_LEN(name)) != NULL) { + php_error_docref(NULL, E_WARNING, "The save_path cannot contain NULL characters"); + zval_dtor(return_value); + RETURN_FALSE; + } + ini_name = zend_string_init("session.save_path", sizeof("session.save_path") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(ini_name); + } +} +/* }}} */ + +/* {{{ proto string session_id([string newid]) + Return the current session id. If newid is given, the session id is replaced with newid */ +static PHP_FUNCTION(session_id) +{ + zend_string *name = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "|S", &name) == FAILURE) { + return; + } + + if (name && PS(use_cookies) && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session id when headers already sent"); + RETURN_FALSE; + } + + if (PS(id)) { + /* keep compatibility for "\0" characters ??? + * see: ext/session/tests/session_id_error3.phpt */ + size_t len = strlen(ZSTR_VAL(PS(id))); + if (UNEXPECTED(len != ZSTR_LEN(PS(id)))) { + RETVAL_NEW_STR(zend_string_init(ZSTR_VAL(PS(id)), len, 0)); + } else { + RETVAL_STR_COPY(PS(id)); + } + } else { + RETVAL_EMPTY_STRING(); + } + + if (name) { + if (PS(id)) { + zend_string_release(PS(id)); + } + PS(id) = zend_string_copy(name); + } +} +/* }}} */ + +/* {{{ proto bool session_regenerate_id([bool delete_old_session]) + Update the current session id with a newly generated one. If delete_old_session is set to true, remove the old session. */ +static PHP_FUNCTION(session_regenerate_id) +{ + zend_bool del_ses = 0; + zend_string *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &del_ses) == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - session is not active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - headers already sent"); + RETURN_FALSE; + } + + /* Process old session data */ + if (del_ses) { + if (PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + php_error_docref(NULL, E_WARNING, "Session object destruction failed. ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } else { + int ret; + data = php_session_encode(); + if (data) { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), data, PS(gc_maxlifetime)); + zend_string_release(data); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime)); + } + if (ret == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + php_error_docref(NULL, E_WARNING, "Session write failed. ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } + PS(mod)->s_close(&PS(mod_data)); + + /* New session data */ + if (PS(session_vars)) { + zend_string_release(PS(session_vars)); + PS(session_vars) = NULL; + } + zend_string_release(PS(id)); + PS(id) = NULL; + + if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE) { + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to open session: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create new session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + if (PS(use_strict_mode) && PS(mod)->s_validate_sid && + PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { + zend_string_release(PS(id)); + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create session ID by collision: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } + /* Read is required to make new session data at this point. */ + if (PS(mod)->s_read(&PS(mod_data), PS(id), &data, PS(gc_maxlifetime)) == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create(read) session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + if (data) { + zend_string_release(data); + } + + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + if (php_session_reset_id() == FAILURE) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string session_create_id([string prefix]) + Generate new session ID. Intended for user save handlers. */ +/* This is not used yet */ +static PHP_FUNCTION(session_create_id) +{ + zend_string *prefix = NULL, *new_id; + smart_str id = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &prefix) == FAILURE) { + return; + } + + if (prefix && ZSTR_LEN(prefix)) { + if (php_session_valid_key(ZSTR_VAL(prefix)) == FAILURE) { + /* E_ERROR raised for security reason. */ + php_error_docref(NULL, E_WARNING, "Prefix cannot contain special characters. Only aphanumeric, ',', '-' are allowed"); + RETURN_FALSE; + } else { + smart_str_append(&id, prefix); + } + } + + if (!PS(in_save_handler) && PS(session_status) == php_session_active) { + int limit = 3; + while (limit--) { + new_id = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(mod)->s_validate_sid) { + break; + } else { + /* Detect collision and retry */ + if (PS(mod)->s_validate_sid(&PS(mod_data), new_id) == FAILURE) { + zend_string_release(new_id); + continue; + } + break; + } + } + } else { + new_id = php_session_create_id(NULL); + } + + if (new_id) { + smart_str_append(&id, new_id); + zend_string_release(new_id); + } else { + smart_str_free(&id); + php_error_docref(NULL, E_WARNING, "Failed to create new ID"); + RETURN_FALSE; + } + smart_str_0(&id); + RETVAL_NEW_STR(id.s); +} +/* }}} */ + +/* {{{ proto string session_cache_limiter([string new_cache_limiter]) + Return the current cache limiter. If new_cache_limited is given, the current cache_limiter is replaced with new_cache_limiter */ +static PHP_FUNCTION(session_cache_limiter) +{ + zend_string *limiter = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &limiter) == FAILURE) { + return; + } + + if (limiter && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change cache limiter when session is active"); + RETURN_FALSE; + } + + if (limiter && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change cache limiter when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(cache_limiter)); + + if (limiter) { + ini_name = zend_string_init("session.cache_limiter", sizeof("session.cache_limiter") - 1, 0); + zend_alter_ini_entry(ini_name, limiter, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(ini_name); + } +} +/* }}} */ + +/* {{{ proto int session_cache_expire([int new_cache_expire]) + Return the current cache expire. If new_cache_expire is given, the current cache_expire is replaced with new_cache_expire */ +static PHP_FUNCTION(session_cache_expire) +{ + zval *expires = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z", &expires) == FAILURE) { + return; + } + + if (expires && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change cache expire when session is active"); + RETURN_LONG(PS(cache_expire)); + } + + if (expires && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change cache expire when headers already sent"); + RETURN_FALSE; + } + + RETVAL_LONG(PS(cache_expire)); + + if (expires) { + convert_to_string_ex(expires); + ini_name = zend_string_init("session.cache_expire", sizeof("session.cache_expire") - 1, 0); + zend_alter_ini_entry(ini_name, Z_STR_P(expires), ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); + zend_string_release(ini_name); + } +} +/* }}} */ + +/* {{{ proto string session_encode(void) + Serializes the current setup and returns the serialized representation */ +static PHP_FUNCTION(session_encode) +{ + zend_string *enc; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + enc = php_session_encode(); + if (enc == NULL) { + RETURN_FALSE; + } + + RETURN_STR(enc); +} +/* }}} */ + +/* {{{ proto bool session_decode(string data) + Deserializes data and reinitializes the variables */ +static PHP_FUNCTION(session_decode) +{ + zend_string *str = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Session is not active. You cannot decode session data"); + RETURN_FALSE; + } + + if (php_session_decode(str) == FAILURE) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +static int php_session_start_set_ini(zend_string *varname, zend_string *new_value) { + int ret; + smart_str buf ={0}; + smart_str_appends(&buf, "session"); + smart_str_appendc(&buf, '.'); + smart_str_append(&buf, varname); + smart_str_0(&buf); + ret = zend_alter_ini_entry_ex(buf.s, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0); + smart_str_free(&buf); + return ret; +} + +/* {{{ proto bool session_start([array options]) ++ Begin session */ +static PHP_FUNCTION(session_start) +{ + zval *options = NULL; + zval *value; + zend_ulong num_idx; + zend_string *str_idx; + zend_long read_and_close = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a", &options) == FAILURE) { + RETURN_FALSE; + } + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_NOTICE, "A session had already been started - ignoring"); + RETURN_TRUE; + } + + /* + * TODO: To prevent unusable session with trans sid, actual output started status is + * required. i.e. There shouldn't be any outputs in output buffer, otherwise session + * module is unable to rewrite output. + */ + if (PS(use_cookies) && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot start session when headers already sent"); + RETURN_FALSE; + } + + /* set options */ + if (options) { + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(options), num_idx, str_idx, value) { + if (str_idx) { + switch(Z_TYPE_P(value)) { + case IS_STRING: + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + if (zend_string_equals_literal(str_idx, "read_and_close")) { + read_and_close = zval_get_long(value); + } else { + zend_string *val = zval_get_string(value); + if (php_session_start_set_ini(str_idx, val) == FAILURE) { + php_error_docref(NULL, E_WARNING, "Setting option '%s' failed", ZSTR_VAL(str_idx)); + } + zend_string_release(val); + } + break; + default: + php_error_docref(NULL, E_WARNING, "Option(%s) value must be string, boolean or long", ZSTR_VAL(str_idx)); + break; + } + } + (void) num_idx; + } ZEND_HASH_FOREACH_END(); + } + + php_session_start(); + + if (PS(session_status) != php_session_active) { + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + /* Clean $_SESSION. */ + zend_hash_clean(Z_ARRVAL_P(sess_var)); + } + RETURN_FALSE; + } + + if (read_and_close) { + php_session_flush(0); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_destroy(void) + Destroy the current session and all data associated with it */ +static PHP_FUNCTION(session_destroy) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(php_session_destroy() == SUCCESS); +} +/* }}} */ + +/* {{{ proto bool session_unset(void) + Unset all registered variables */ +static PHP_FUNCTION(session_unset) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + + /* Clean $_SESSION. */ + zend_hash_clean(Z_ARRVAL_P(sess_var)); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int session_gc(void) + Perform GC and return number of deleted sessions */ +static PHP_FUNCTION(session_gc) +{ + zend_long num; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Session is not active"); + RETURN_FALSE; + } + + num = php_session_gc(1); + if (num < 0) { + RETURN_FALSE; + } + + RETURN_LONG(num); +} +/* }}} */ + + +/* {{{ proto bool session_write_close(void) + Write session data and end session */ +static PHP_FUNCTION(session_write_close) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_flush(1); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_abort(void) + Abort session and end session. Session data will not be written */ +static PHP_FUNCTION(session_abort) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_abort(); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_reset(void) + Reset session data from saved session data */ +static PHP_FUNCTION(session_reset) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_reset(); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int session_status(void) + Returns the current session status */ +static PHP_FUNCTION(session_status) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(PS(session_status)); +} +/* }}} */ + +/* {{{ proto void session_register_shutdown(void) + Registers session_write_close() as a shutdown function */ +static PHP_FUNCTION(session_register_shutdown) +{ + php_shutdown_function_entry shutdown_function_entry; + + /* This function is registered itself as a shutdown function by + * session_set_save_handler($obj). The reason we now register another + * shutdown function is in case the user registered their own shutdown + * function after calling session_set_save_handler(), which expects + * the session still to be available. + */ + + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0); + + ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_write_close"); + + if (!append_user_shutdown_function(shutdown_function_entry)) { + zval_ptr_dtor(&shutdown_function_entry.arguments[0]); + efree(shutdown_function_entry.arguments); + + /* Unable to register shutdown function, presumably because of lack + * of memory, so flush the session now. It would be done in rshutdown + * anyway but the handler will have had it's dtor called by then. + * If the user does have a later shutdown function which needs the + * session then tough luck. + */ + php_session_flush(1); + php_error_docref(NULL, E_WARNING, "Unable to register session flush function"); + } +} +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_name, 0, 0, 0) + ZEND_ARG_INFO(0, name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_module_name, 0, 0, 0) + ZEND_ARG_INFO(0, module) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_save_path, 0, 0, 0) + ZEND_ARG_INFO(0, path) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_id, 0, 0, 0) + ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_create_id, 0, 0, 0) + ZEND_ARG_INFO(0, prefix) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_regenerate_id, 0, 0, 0) + ZEND_ARG_INFO(0, delete_old_session) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_decode, 0, 0, 1) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_void, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_save_handler, 0, 0, 1) + ZEND_ARG_INFO(0, open) + ZEND_ARG_INFO(0, close) + ZEND_ARG_INFO(0, read) + ZEND_ARG_INFO(0, write) + ZEND_ARG_INFO(0, destroy) + ZEND_ARG_INFO(0, gc) + ZEND_ARG_INFO(0, create_sid) + ZEND_ARG_INFO(0, validate_sid) + ZEND_ARG_INFO(0, update_timestamp) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_limiter, 0, 0, 0) + ZEND_ARG_INFO(0, cache_limiter) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_expire, 0, 0, 0) + ZEND_ARG_INFO(0, new_cache_expire) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_cookie_params, 0, 0, 1) + ZEND_ARG_INFO(0, lifetime) + ZEND_ARG_INFO(0, path) + ZEND_ARG_INFO(0, domain) + ZEND_ARG_INFO(0, secure) + ZEND_ARG_INFO(0, httponly) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_open, 0) + ZEND_ARG_INFO(0, save_path) + ZEND_ARG_INFO(0, session_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_close, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_read, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_write, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_destroy, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_gc, 0) + ZEND_ARG_INFO(0, maxlifetime) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_create_sid, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_validateId, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_updateTimestamp, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_start, 0, 0, 0) + ZEND_ARG_INFO(0, options) /* array */ +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ session_functions[] + */ +static const zend_function_entry session_functions[] = { + PHP_FE(session_name, arginfo_session_name) + PHP_FE(session_module_name, arginfo_session_module_name) + PHP_FE(session_save_path, arginfo_session_save_path) + PHP_FE(session_id, arginfo_session_id) + PHP_FE(session_create_id, arginfo_session_create_id) + PHP_FE(session_regenerate_id, arginfo_session_regenerate_id) + PHP_FE(session_decode, arginfo_session_decode) + PHP_FE(session_encode, arginfo_session_void) + PHP_FE(session_start, arginfo_session_start) + PHP_FE(session_destroy, arginfo_session_void) + PHP_FE(session_unset, arginfo_session_void) + PHP_FE(session_gc, arginfo_session_void) + PHP_FE(session_set_save_handler, arginfo_session_set_save_handler) + PHP_FE(session_cache_limiter, arginfo_session_cache_limiter) + PHP_FE(session_cache_expire, arginfo_session_cache_expire) + PHP_FE(session_set_cookie_params, arginfo_session_set_cookie_params) + PHP_FE(session_get_cookie_params, arginfo_session_void) + PHP_FE(session_write_close, arginfo_session_void) + PHP_FE(session_abort, arginfo_session_void) + PHP_FE(session_reset, arginfo_session_void) + PHP_FE(session_status, arginfo_session_void) + PHP_FE(session_register_shutdown, arginfo_session_void) + PHP_FALIAS(session_commit, session_write_close, arginfo_session_void) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionHandlerInterface functions[] +*/ +static const zend_function_entry php_session_iface_functions[] = { + PHP_ABSTRACT_ME(SessionHandlerInterface, open, arginfo_session_class_open) + PHP_ABSTRACT_ME(SessionHandlerInterface, close, arginfo_session_class_close) + PHP_ABSTRACT_ME(SessionHandlerInterface, read, arginfo_session_class_read) + PHP_ABSTRACT_ME(SessionHandlerInterface, write, arginfo_session_class_write) + PHP_ABSTRACT_ME(SessionHandlerInterface, destroy, arginfo_session_class_destroy) + PHP_ABSTRACT_ME(SessionHandlerInterface, gc, arginfo_session_class_gc) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionIdInterface functions[] +*/ +static const zend_function_entry php_session_id_iface_functions[] = { + PHP_ABSTRACT_ME(SessionIdInterface, create_sid, arginfo_session_class_create_sid) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionUpdateTimestampHandler functions[] + */ +static const zend_function_entry php_session_update_timestamp_iface_functions[] = { + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, validateId, arginfo_session_class_validateId) + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, updateTimestamp, arginfo_session_class_updateTimestamp) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionHandler functions[] + */ +static const zend_function_entry php_session_class_functions[] = { + PHP_ME(SessionHandler, open, arginfo_session_class_open, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, close, arginfo_session_class_close, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, read, arginfo_session_class_read, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, write, arginfo_session_class_write, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, destroy, arginfo_session_class_destroy, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, gc, arginfo_session_class_gc, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, create_sid, arginfo_session_class_create_sid, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +/* }}} */ + +/* ******************************** + * Module Setup and Destruction * + ******************************** */ + +static int php_rinit_session(zend_bool auto_start) /* {{{ */ +{ + php_rinit_session_globals(); + + if (PS(mod) == NULL) { + char *value; + + value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0); + if (value) { + PS(mod) = _php_find_ps_module(value); + } + } + + if (PS(serializer) == NULL) { + char *value; + + value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0); + if (value) { + PS(serializer) = _php_find_ps_serializer(value); + } + } + + if (PS(mod) == NULL || PS(serializer) == NULL) { + /* current status is unusable */ + PS(session_status) = php_session_disabled; + return SUCCESS; + } + + if (auto_start) { + php_session_start(); + } + + return SUCCESS; +} /* }}} */ + +static PHP_RINIT_FUNCTION(session) /* {{{ */ +{ + return php_rinit_session(PS(auto_start)); +} +/* }}} */ + +static PHP_RSHUTDOWN_FUNCTION(session) /* {{{ */ +{ + int i; + + if (PS(session_status) == php_session_active) { + zend_try { + php_session_flush(1); + } zend_end_try(); + } + php_rshutdown_session_globals(); + + /* this should NOT be done in php_rshutdown_session_globals() */ + for (i = 0; i < PS_NUM_APIS; i++) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + + return SUCCESS; +} +/* }}} */ + +static PHP_GINIT_FUNCTION(ps) /* {{{ */ +{ + int i; + +#if defined(COMPILE_DL_SESSION) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + ps_globals->save_path = NULL; + ps_globals->session_name = NULL; + ps_globals->id = NULL; + ps_globals->mod = NULL; + ps_globals->serializer = NULL; + ps_globals->mod_data = NULL; + ps_globals->session_status = php_session_none; + ps_globals->default_mod = NULL; + ps_globals->mod_user_implemented = 0; + ps_globals->mod_user_is_open = 0; + ps_globals->session_vars = NULL; + ps_globals->set_handler = 0; + for (i = 0; i < PS_NUM_APIS; i++) { + ZVAL_UNDEF(&ps_globals->mod_user_names.names[i]); + } + ZVAL_UNDEF(&ps_globals->http_session_vars); +} +/* }}} */ + +static PHP_MINIT_FUNCTION(session) /* {{{ */ +{ + zend_class_entry ce; + + zend_register_auto_global(zend_string_init("_SESSION", sizeof("_SESSION") - 1, 1), 0, NULL); + + my_module_number = module_number; + PS(module_number) = module_number; + + PS(session_status) = php_session_none; + REGISTER_INI_ENTRIES(); + +#ifdef HAVE_LIBMM + PHP_MINIT(ps_mm) (INIT_FUNC_ARGS_PASSTHRU); +#endif + php_session_rfc1867_orig_callback = php_rfc1867_callback; + php_rfc1867_callback = php_session_rfc1867_callback; + + /* Register interfaces */ + INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions); + php_session_iface_entry = zend_register_internal_class(&ce); + php_session_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + INIT_CLASS_ENTRY(ce, PS_SID_IFACE_NAME, php_session_id_iface_functions); + php_session_id_iface_entry = zend_register_internal_class(&ce); + php_session_id_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + INIT_CLASS_ENTRY(ce, PS_UPDATE_TIMESTAMP_IFACE_NAME, php_session_update_timestamp_iface_functions); + php_session_update_timestamp_iface_entry = zend_register_internal_class(&ce); + php_session_update_timestamp_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + /* Register base class */ + INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions); + php_session_class_entry = zend_register_internal_class(&ce); + zend_class_implements(php_session_class_entry, 1, php_session_iface_entry); + zend_class_implements(php_session_class_entry, 1, php_session_id_iface_entry); + + REGISTER_LONG_CONSTANT("PHP_SESSION_DISABLED", php_session_disabled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_SESSION_NONE", php_session_none, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_SESSION_ACTIVE", php_session_active, CONST_CS | CONST_PERSISTENT); + + return SUCCESS; +} +/* }}} */ + +static PHP_MSHUTDOWN_FUNCTION(session) /* {{{ */ +{ + UNREGISTER_INI_ENTRIES(); + +#ifdef HAVE_LIBMM + PHP_MSHUTDOWN(ps_mm) (SHUTDOWN_FUNC_ARGS_PASSTHRU); +#endif + + /* reset rfc1867 callbacks */ + php_session_rfc1867_orig_callback = NULL; + if (php_rfc1867_callback == php_session_rfc1867_callback) { + php_rfc1867_callback = NULL; + } + + ps_serializers[PREDEFINED_SERIALIZERS].name = NULL; + memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *)); + + return SUCCESS; +} +/* }}} */ + +static PHP_MINFO_FUNCTION(session) /* {{{ */ +{ + ps_module **mod; + ps_serializer *ser; + smart_str save_handlers = {0}; + smart_str ser_handlers = {0}; + int i; + + /* Get save handlers */ + for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) { + if (*mod && (*mod)->s_name) { + smart_str_appends(&save_handlers, (*mod)->s_name); + smart_str_appendc(&save_handlers, ' '); + } + } + + /* Get serializer handlers */ + for (i = 0, ser = ps_serializers; i < MAX_SERIALIZERS; i++, ser++) { + if (ser && ser->name) { + smart_str_appends(&ser_handlers, ser->name); + smart_str_appendc(&ser_handlers, ' '); + } + } + + php_info_print_table_start(); + php_info_print_table_row(2, "Session Support", "enabled" ); + + if (save_handlers.s) { + smart_str_0(&save_handlers); + php_info_print_table_row(2, "Registered save handlers", ZSTR_VAL(save_handlers.s)); + smart_str_free(&save_handlers); + } else { + php_info_print_table_row(2, "Registered save handlers", "none"); + } + + if (ser_handlers.s) { + smart_str_0(&ser_handlers); + php_info_print_table_row(2, "Registered serializer handlers", ZSTR_VAL(ser_handlers.s)); + smart_str_free(&ser_handlers); + } else { + php_info_print_table_row(2, "Registered serializer handlers", "none"); + } + + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +static const zend_module_dep session_deps[] = { /* {{{ */ + ZEND_MOD_OPTIONAL("hash") + ZEND_MOD_REQUIRED("spl") + ZEND_MOD_END +}; +/* }}} */ + +/* ************************ + * Upload hook handling * + ************************ */ + +static zend_bool early_find_sid_in(zval *dest, int where, php_session_rfc1867_progress *progress) /* {{{ */ +{ + zval *ppid; + + if (Z_ISUNDEF(PG(http_globals)[where])) { + return 0; + } + + if ((ppid = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[where]), PS(session_name), progress->sname_len)) + && Z_TYPE_P(ppid) == IS_STRING) { + zval_dtor(dest); + ZVAL_DEREF(ppid); + ZVAL_COPY(dest, ppid); + return 1; + } + + return 0; +} /* }}} */ + +static void php_session_rfc1867_early_find_sid(php_session_rfc1867_progress *progress) /* {{{ */ +{ + + if (PS(use_cookies)) { + sapi_module.treat_data(PARSE_COOKIE, NULL, NULL); + if (early_find_sid_in(&progress->sid, TRACK_VARS_COOKIE, progress)) { + progress->apply_trans_sid = 0; + return; + } + } + if (PS(use_only_cookies)) { + return; + } + sapi_module.treat_data(PARSE_GET, NULL, NULL); + early_find_sid_in(&progress->sid, TRACK_VARS_GET, progress); +} /* }}} */ + +static zend_bool php_check_cancel_upload(php_session_rfc1867_progress *progress) /* {{{ */ +{ + zval *progress_ary, *cancel_upload; + + if ((progress_ary = zend_symtable_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), progress->key.s)) == NULL) { + return 0; + } + if (Z_TYPE_P(progress_ary) != IS_ARRAY) { + return 0; + } + if ((cancel_upload = zend_hash_str_find(Z_ARRVAL_P(progress_ary), "cancel_upload", sizeof("cancel_upload") - 1)) == NULL) { + return 0; + } + return Z_TYPE_P(cancel_upload) == IS_TRUE; +} /* }}} */ + +static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, int force_update) /* {{{ */ +{ + if (!force_update) { + if (Z_LVAL_P(progress->post_bytes_processed) < progress->next_update) { + return; + } +#ifdef HAVE_GETTIMEOFDAY + if (PS(rfc1867_min_freq) > 0.0) { + struct timeval tv = {0}; + double dtv; + gettimeofday(&tv, NULL); + dtv = (double) tv.tv_sec + tv.tv_usec / 1000000.0; + if (dtv < progress->next_update_time) { + return; + } + progress->next_update_time = dtv + PS(rfc1867_min_freq); + } +#endif + progress->next_update = Z_LVAL_P(progress->post_bytes_processed) + progress->update_step; + } + + php_session_initialize(); + PS(session_status) = php_session_active; + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + + progress->cancel_upload |= php_check_cancel_upload(progress); + Z_TRY_ADDREF(progress->data); + zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data); + } + php_session_flush(1); +} /* }}} */ + +static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress) /* {{{ */ +{ + php_session_initialize(); + PS(session_status) = php_session_active; + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s); + } + php_session_flush(1); +} /* }}} */ + +static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) /* {{{ */ +{ + php_session_rfc1867_progress *progress; + int retval = SUCCESS; + + if (php_session_rfc1867_orig_callback) { + retval = php_session_rfc1867_orig_callback(event, event_data, extra); + } + if (!PS(rfc1867_enabled)) { + return retval; + } + + progress = PS(rfc1867_progress); + + switch(event) { + case MULTIPART_EVENT_START: { + multipart_event_start *data = (multipart_event_start *) event_data; + progress = ecalloc(1, sizeof(php_session_rfc1867_progress)); + progress->content_length = data->content_length; + progress->sname_len = strlen(PS(session_name)); + PS(rfc1867_progress) = progress; + } + break; + case MULTIPART_EVENT_FORMDATA: { + multipart_event_formdata *data = (multipart_event_formdata *) event_data; + size_t value_len; + + if (Z_TYPE(progress->sid) && progress->key.s) { + break; + } + + /* orig callback may have modified *data->newlength */ + if (data->newlength) { + value_len = *data->newlength; + } else { + value_len = data->length; + } + + if (data->name && data->value && value_len) { + size_t name_len = strlen(data->name); + + if (name_len == progress->sname_len && memcmp(data->name, PS(session_name), name_len) == 0) { + zval_dtor(&progress->sid); + ZVAL_STRINGL(&progress->sid, (*data->value), value_len); + } else if (name_len == strlen(PS(rfc1867_name)) && memcmp(data->name, PS(rfc1867_name), name_len + 1) == 0) { + smart_str_free(&progress->key); + smart_str_appends(&progress->key, PS(rfc1867_prefix)); + smart_str_appendl(&progress->key, *data->value, value_len); + smart_str_0(&progress->key); + + progress->apply_trans_sid = APPLY_TRANS_SID; + php_session_rfc1867_early_find_sid(progress); + } + } + } + break; + case MULTIPART_EVENT_FILE_START: { + multipart_event_file_start *data = (multipart_event_file_start *) event_data; + + /* Do nothing when $_POST["PHP_SESSION_UPLOAD_PROGRESS"] is not set + * or when we have no session id */ + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + /* First FILE_START event, initializing data */ + if (Z_ISUNDEF(progress->data)) { + + if (PS(rfc1867_freq) >= 0) { + progress->update_step = PS(rfc1867_freq); + } else if (PS(rfc1867_freq) < 0) { /* % of total size */ + progress->update_step = progress->content_length * -PS(rfc1867_freq) / 100; + } + progress->next_update = 0; + progress->next_update_time = 0.0; + + array_init(&progress->data); + array_init(&progress->files); + + add_assoc_long_ex(&progress->data, "start_time", sizeof("start_time") - 1, (zend_long)sapi_get_request_time()); + add_assoc_long_ex(&progress->data, "content_length", sizeof("content_length") - 1, progress->content_length); + add_assoc_long_ex(&progress->data, "bytes_processed", sizeof("bytes_processed") - 1, data->post_bytes_processed); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 0); + add_assoc_zval_ex(&progress->data, "files", sizeof("files") - 1, &progress->files); + + progress->post_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->data), "bytes_processed", sizeof("bytes_processed") - 1); + + php_rinit_session(0); + PS(id) = zend_string_init(Z_STRVAL(progress->sid), Z_STRLEN(progress->sid), 0); + if (progress->apply_trans_sid) { + /* Enable trans sid by modifying flags */ + PS(use_trans_sid) = 1; + PS(use_only_cookies) = 0; + } + PS(send_cookie) = 0; + } + + array_init(&progress->current_file); + + /* Each uploaded file has its own array. Trying to make it close to $_FILES entries. */ + add_assoc_string_ex(&progress->current_file, "field_name", sizeof("field_name") - 1, data->name); + add_assoc_string_ex(&progress->current_file, "name", sizeof("name") - 1, *data->filename); + add_assoc_null_ex(&progress->current_file, "tmp_name", sizeof("tmp_name") - 1); + add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, 0); + + add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1, 0); + add_assoc_long_ex(&progress->current_file, "start_time", sizeof("start_time") - 1, (zend_long)time(NULL)); + add_assoc_long_ex(&progress->current_file, "bytes_processed", sizeof("bytes_processed") - 1, 0); + + add_next_index_zval(&progress->files, &progress->current_file); + + progress->current_file_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->current_file), "bytes_processed", sizeof("bytes_processed") - 1); + + Z_LVAL_P(progress->current_file_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_FILE_DATA: { + multipart_event_file_data *data = (multipart_event_file_data *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + Z_LVAL_P(progress->current_file_bytes_processed) = data->offset + data->length; + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_FILE_END: { + multipart_event_file_end *data = (multipart_event_file_end *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + if (data->temp_filename) { + add_assoc_string_ex(&progress->current_file, "tmp_name", sizeof("tmp_name") - 1, data->temp_filename); + } + + add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, data->cancel_upload); + add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1, 1); + + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_END: { + multipart_event_end *data = (multipart_event_end *) event_data; + + if (Z_TYPE(progress->sid) && progress->key.s) { + if (PS(rfc1867_cleanup)) { + php_session_rfc1867_cleanup(progress); + } else { + SEPARATE_ARRAY(&progress->data); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 1); + } + php_rshutdown_session_globals(); + } + + if (!Z_ISUNDEF(progress->data)) { + zval_ptr_dtor(&progress->data); + } + zval_ptr_dtor(&progress->sid); + smart_str_free(&progress->key); + efree(progress); + progress = NULL; + PS(rfc1867_progress) = NULL; + } + break; + } + + if (progress && progress->cancel_upload) { + return FAILURE; + } + return retval; + +} /* }}} */ + +zend_module_entry session_module_entry = { + STANDARD_MODULE_HEADER_EX, + NULL, + session_deps, + "session", + session_functions, + PHP_MINIT(session), PHP_MSHUTDOWN(session), + PHP_RINIT(session), PHP_RSHUTDOWN(session), + PHP_MINFO(session), + PHP_SESSION_VERSION, + PHP_MODULE_GLOBALS(ps), + PHP_GINIT(ps), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SESSION +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(session) +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/session/tests/bug79221.phpt b/ext/session/tests/bug79221.phpt new file mode 100644 index 0000000000000..b0972c4697054 --- /dev/null +++ b/ext/session/tests/bug79221.phpt @@ -0,0 +1,45 @@ +--TEST-- +Null Pointer Dereference in PHP Session Upload Progress +--INI-- +error_reporting=0 +file_uploads=1 +upload_max_filesize=1024 +session.save_path= +session.name=PHPSESSID +session.serialize_handler=php +session.use_strict_mode=0 +session.use_cookies=1 +session.use_only_cookies=0 +session.upload_progress.enabled=1 +session.upload_progress.cleanup=0 +session.upload_progress.prefix=upload_progress_ +session.upload_progress.name=PHP_SESSION_UPLOAD_PROGRESS +session.upload_progress.freq=1% +session.upload_progress.min_freq=0.000000001 +--COOKIE-- +PHPSESSID=session-upload +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737 +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; name="PHPSESSID" + +session-upload +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" + +ryat +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; file="file"; ryat="filename" + +1 +-----------------------------20896060251896012921717172737-- +--FILE-- + Date: Wed, 11 Dec 2024 16:24:05 +0000 Subject: [PATCH 15/25] commit patch 21164546 --- ext/phar/phar_object.c | 11 + ext/phar/phar_object.c.orig | 5508 ++++++++++++++++++ ext/phar/tests/bug79082.phpt | 52 + ext/phar/tests/test79082/test79082-testfile | 1 + ext/phar/tests/test79082/test79082-testfile2 | 1 + 5 files changed, 5573 insertions(+) create mode 100644 ext/phar/phar_object.c.orig create mode 100644 ext/phar/tests/bug79082.phpt create mode 100644 ext/phar/tests/test79082/test79082-testfile create mode 100644 ext/phar/tests/test79082/test79082-testfile2 diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 876d5da83dfa9..b10cb3a23bc82 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -1441,6 +1441,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ char *str_key; zend_class_entry *ce = p_obj->c; phar_archive_object *phar_obj = p_obj->p; + php_stream_statbuf ssb; value = iter->funcs->get_current_data(iter); @@ -1720,6 +1721,16 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ php_stream_copy_to_stream_ex(fp, p_obj->fp, PHP_STREAM_COPY_ALL, &contents_len); data->internal_file->uncompressed_filesize = data->internal_file->compressed_filesize = php_stream_tell(p_obj->fp) - data->internal_file->offset; + if (php_stream_stat(fp, &ssb) != -1) { + data->internal_file->flags = ssb.sb.st_mode & PHAR_ENT_PERM_MASK ; + } else { +#ifndef _WIN32 + mode_t mask; + mask = umask(0); + umask(mask); + data->internal_file->flags &= ~mask; +#endif + } } if (close_fp) { diff --git a/ext/phar/phar_object.c.orig b/ext/phar/phar_object.c.orig new file mode 100644 index 0000000000000..876d5da83dfa9 --- /dev/null +++ b/ext/phar/phar_object.c.orig @@ -0,0 +1,5508 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gregory Beaver | + | Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "phar_internal.h" +#include "func_interceptors.h" + +static zend_class_entry *phar_ce_archive; +static zend_class_entry *phar_ce_data; +static zend_class_entry *phar_ce_PharException; +static zend_class_entry *phar_ce_entry; + +static int phar_file_type(HashTable *mimes, char *file, char **mime_type) /* {{{ */ +{ + char *ext; + phar_mime_type *mime; + ext = strrchr(file, '.'); + if (!ext) { + *mime_type = "text/plain"; + /* no file extension = assume text/plain */ + return PHAR_MIME_OTHER; + } + ++ext; + if (NULL == (mime = zend_hash_str_find_ptr(mimes, ext, strlen(ext)))) { + *mime_type = "application/octet-stream"; + return PHAR_MIME_OTHER; + } + *mime_type = mime->mime; + return mime->type; +} +/* }}} */ + +static void phar_mung_server_vars(char *fname, char *entry, int entry_len, char *basename, int request_uri_len) /* {{{ */ +{ + HashTable *_SERVER; + zval *stuff; + char *path_info; + size_t basename_len = strlen(basename); + size_t code; + zval temp; + + /* "tweak" $_SERVER variables requested in earlier call to Phar::mungServer() */ + if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_UNDEF) { + return; + } + + _SERVER = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]); + + /* PATH_INFO and PATH_TRANSLATED should always be munged */ + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PATH_INFO", sizeof("PATH_INFO")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + if (code > entry_len && !memcmp(path_info, entry, entry_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + entry_len, request_uri_len); + zend_hash_str_update(_SERVER, "PHAR_PATH_INFO", sizeof("PHAR_PATH_INFO")-1, &temp); + } + } + + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PATH_TRANSLATED", sizeof("PATH_TRANSLATED")-1))) { + zend_string *str = strpprintf(4096, "phar://%s%s", fname, entry); + + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_NEW_STR(stuff, str); + + zend_hash_str_update(_SERVER, "PHAR_PATH_TRANSLATED", sizeof("PHAR_PATH_TRANSLATED")-1, &temp); + } + + if (!PHAR_G(phar_SERVER_mung_list)) { + return; + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_REQUEST_URI) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "REQUEST_URI", sizeof("REQUEST_URI")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + if (code > basename_len && !memcmp(path_info, basename, basename_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + basename_len, code - basename_len); + zend_hash_str_update(_SERVER, "PHAR_REQUEST_URI", sizeof("PHAR_REQUEST_URI")-1, &temp); + } + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_PHP_SELF) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PHP_SELF", sizeof("PHP_SELF")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + + if (code > basename_len && !memcmp(path_info, basename, basename_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + basename_len, code - basename_len); + zend_hash_str_update(_SERVER, "PHAR_PHP_SELF", sizeof("PHAR_PHP_SELF")-1, &temp); + } + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_SCRIPT_NAME) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1))) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, entry, entry_len); + zend_hash_str_update(_SERVER, "PHAR_SCRIPT_NAME", sizeof("PHAR_SCRIPT_NAME")-1, &temp); + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_SCRIPT_FILENAME) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1))) { + zend_string *str = strpprintf(4096, "phar://%s%s", fname, entry); + + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_NEW_STR(stuff, str); + + zend_hash_str_update(_SERVER, "PHAR_SCRIPT_FILENAME", sizeof("PHAR_SCRIPT_FILENAME")-1, &temp); + } + } +} +/* }}} */ + +static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char *mime_type, int code, char *entry, int entry_len, char *arch, char *basename, char *ru, int ru_len) /* {{{ */ +{ + char *name = NULL, buf[8192]; + const char *cwd; + zend_syntax_highlighter_ini syntax_highlighter_ini; + sapi_header_line ctr = {0}; + size_t got; + zval dummy; + size_t name_len; + zend_file_handle file_handle; + zend_op_array *new_op_array; + zval result; + php_stream *fp; + zend_off_t position; + + switch (code) { + case PHAR_MIME_PHPS: + efree(basename); + /* highlight source */ + if (entry[0] == '/') { + spprintf(&name, 4096, "phar://%s%s", arch, entry); + } else { + spprintf(&name, 4096, "phar://%s/%s", arch, entry); + } + php_get_highlight_struct(&syntax_highlighter_ini); + + highlight_file(name, &syntax_highlighter_ini); + + efree(name); +#ifdef PHP_WIN32 + efree(arch); +#endif + zend_bailout(); + case PHAR_MIME_OTHER: + /* send headers, output file contents */ + efree(basename); + ctr.line_len = spprintf(&(ctr.line), 0, "Content-type: %s", mime_type); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + efree(ctr.line); + ctr.line_len = spprintf(&(ctr.line), 0, "Content-length: %u", info->uncompressed_filesize); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + efree(ctr.line); + + if (FAILURE == sapi_send_headers()) { + zend_bailout(); + } + + /* prepare to output */ + fp = phar_get_efp(info, 1); + + if (!fp) { + char *error; + if (!phar_open_jit(phar, info, &error)) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + return -1; + } + fp = phar_get_efp(info, 1); + } + position = 0; + phar_seek_efp(info, 0, SEEK_SET, 0, 1); + + do { + got = php_stream_read(fp, buf, MIN(8192, info->uncompressed_filesize - position)); + if (got > 0) { + PHPWRITE(buf, got); + position += got; + if (position == (zend_off_t) info->uncompressed_filesize) { + break; + } + } + } while (1); + + zend_bailout(); + case PHAR_MIME_PHP: + if (basename) { + phar_mung_server_vars(arch, entry, entry_len, basename, ru_len); + efree(basename); + } + + if (entry[0] == '/') { + name_len = spprintf(&name, 4096, "phar://%s%s", arch, entry); + } else { + name_len = spprintf(&name, 4096, "phar://%s/%s", arch, entry); + } + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.handle.fd = 0; + file_handle.filename = name; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + + ZVAL_NULL(&dummy); + if (zend_hash_str_add(&EG(included_files), name, name_len, &dummy) != NULL) { + if ((cwd = zend_memrchr(entry, '/', entry_len))) { + PHAR_G(cwd_init) = 1; + if (entry == cwd) { + /* root directory */ + PHAR_G(cwd_len) = 0; + PHAR_G(cwd) = NULL; + } else if (entry[0] == '/') { + PHAR_G(cwd_len) = (int)(cwd - (entry + 1)); + PHAR_G(cwd) = estrndup(entry + 1, PHAR_G(cwd_len)); + } else { + PHAR_G(cwd_len) = (int)(cwd - entry); + PHAR_G(cwd) = estrndup(entry, PHAR_G(cwd_len)); + } + } + + new_op_array = zend_compile_file(&file_handle, ZEND_REQUIRE); + + if (!new_op_array) { + zend_hash_str_del(&EG(included_files), name, name_len); + } + + zend_destroy_file_handle(&file_handle); + + } else { + efree(name); + new_op_array = NULL; + } +#ifdef PHP_WIN32 + efree(arch); +#endif + if (new_op_array) { + ZVAL_UNDEF(&result); + + zend_try { + zend_execute(new_op_array, &result); + if (PHAR_G(cwd)) { + efree(PHAR_G(cwd)); + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + } + + PHAR_G(cwd_init) = 0; + efree(name); + destroy_op_array(new_op_array); + efree(new_op_array); + zval_ptr_dtor(&result); + } zend_catch { + if (PHAR_G(cwd)) { + efree(PHAR_G(cwd)); + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + } + + PHAR_G(cwd_init) = 0; + efree(name); + } zend_end_try(); + + zend_bailout(); + } + + return PHAR_MIME_PHP; + } + return -1; +} +/* }}} */ + +static void phar_do_403(char *entry, int entry_len) /* {{{ */ +{ + sapi_header_line ctr = {0}; + + ctr.response_code = 403; + ctr.line_len = sizeof("HTTP/1.0 403 Access Denied")-1; + ctr.line = "HTTP/1.0 403 Access Denied"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + PHPWRITE("\n \n Access Denied\n \n \n

403 - File ", sizeof("\n \n Access Denied\n \n \n

403 - File ") - 1); + PHPWRITE(entry, entry_len); + PHPWRITE(" Access Denied

\n \n", sizeof(" Access Denied\n \n") - 1); +} +/* }}} */ + +static void phar_do_404(phar_archive_data *phar, char *fname, int fname_len, char *f404, int f404_len, char *entry, size_t entry_len) /* {{{ */ +{ + sapi_header_line ctr = {0}; + phar_entry_info *info; + + if (phar && f404_len) { + info = phar_get_entry_info(phar, f404, f404_len, NULL, 1); + + if (info) { + phar_file_action(phar, info, "text/html", PHAR_MIME_PHP, f404, f404_len, fname, NULL, NULL, 0); + return; + } + } + + ctr.response_code = 404; + ctr.line_len = sizeof("HTTP/1.0 404 Not Found")-1; + ctr.line = "HTTP/1.0 404 Not Found"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + PHPWRITE("\n \n File Not Found\n \n \n

404 - File ", sizeof("\n \n File Not Found\n \n \n

404 - File ") - 1); + PHPWRITE(entry, entry_len); + PHPWRITE(" Not Found

\n \n", sizeof(" Not Found\n \n") - 1); +} +/* }}} */ + +/* post-process REQUEST_URI and retrieve the actual request URI. This is for + cases like http://localhost/blah.phar/path/to/file.php/extra/stuff + which calls "blah.phar" file "path/to/file.php" with PATH_INFO "/extra/stuff" */ +static void phar_postprocess_ru_web(char *fname, int fname_len, char **entry, int *entry_len, char **ru, int *ru_len) /* {{{ */ +{ + char *e = *entry + 1, *u = NULL, *u1 = NULL, *saveu = NULL; + int e_len = *entry_len - 1, u_len = 0; + phar_archive_data *pphar; + + /* we already know we can retrieve the phar if we reach here */ + pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), fname, fname_len); + + if (!pphar && PHAR_G(manifest_cached)) { + pphar = zend_hash_str_find_ptr(&cached_phars, fname, fname_len); + } + + do { + if (zend_hash_str_exists(&(pphar->manifest), e, e_len)) { + if (u) { + u[0] = '/'; + *ru = estrndup(u, u_len+1); + ++u_len; + u[0] = '\0'; + } else { + *ru = NULL; + } + *ru_len = u_len; + *entry_len = e_len + 1; + return; + } + + if (u) { + u1 = strrchr(e, '/'); + u[0] = '/'; + saveu = u; + e_len += u_len + 1; + u = u1; + if (!u) { + return; + } + } else { + u = strrchr(e, '/'); + if (!u) { + if (saveu) { + saveu[0] = '/'; + } + return; + } + } + + u[0] = '\0'; + u_len = (int)strlen(u + 1); + e_len -= u_len + 1; + + if (e_len < 0) { + if (saveu) { + saveu[0] = '/'; + } + return; + } + } while (1); +} +/* }}} */ + +/* {{{ proto void Phar::running([bool retphar = true]) + * return the name of the currently running phar archive. If the optional parameter + * is set to true, return the phar:// URL to the currently running phar + */ +PHP_METHOD(Phar, running) +{ + char *fname, *arch, *entry; + int fname_len, arch_len, entry_len; + zend_bool retphar = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &retphar) == FAILURE) { + return; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = (int)strlen(fname); + + if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + efree(entry); + if (retphar) { + RETVAL_STRINGL(fname, arch_len + 7); + efree(arch); + return; + } else { + // TODO: avoid reallocation ??? + RETVAL_STRINGL(arch, arch_len); + efree(arch); + return; + } + } + + RETURN_EMPTY_STRING(); +} +/* }}} */ + +/* {{{ proto void Phar::mount(string pharpath, string externalfile) + * mount an external file or path to a location within the phar. This maps + * an external file or directory to a location within the phar archive, allowing + * reference to an external location as if it were within the phar archive. This + * is useful for writable temp files like databases + */ +PHP_METHOD(Phar, mount) +{ + char *fname, *arch = NULL, *entry = NULL, *path, *actual; + int fname_len, arch_len, entry_len; + size_t path_len, actual_len; + phar_archive_data *pphar; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &path, &path_len, &actual, &actual_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(path_len) || ZEND_SIZE_T_INT_OVFL(actual_len)) { + RETURN_FALSE; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = (int)strlen(fname); + +#ifdef PHP_WIN32 + phar_unixify_path_separators(fname, fname_len); +#endif + + if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + efree(entry); + entry = NULL; + + if (path_len > 7 && !memcmp(path, "phar://", 7)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Can only mount internal paths within a phar archive, use a relative path instead of \"%s\"", path); + efree(arch); + return; + } +carry_on2: + if (NULL == (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len))) { + if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, arch, arch_len))) { + if (SUCCESS == phar_copy_on_write(&pphar)) { + goto carry_on; + } + } + + zend_throw_exception_ex(phar_ce_PharException, 0, "%s is not a phar archive, cannot mount", arch); + + if (arch) { + efree(arch); + } + return; + } +carry_on: + if (SUCCESS != phar_mount_entry(pphar, actual, (int)actual_len, path, (int)path_len)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s within phar %s failed", path, actual, arch); + if (path && path == entry) { + efree(entry); + } + + if (arch) { + efree(arch); + } + + return; + } + + if (entry && path && path == entry) { + efree(entry); + } + + if (arch) { + efree(arch); + } + + return; + } else if (PHAR_G(phar_fname_map.u.flags) && NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), fname, fname_len))) { + goto carry_on; + } else if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, fname, fname_len))) { + if (SUCCESS == phar_copy_on_write(&pphar)) { + goto carry_on; + } + + goto carry_on; + } else if (SUCCESS == phar_split_fname(path, (int)path_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + path = entry; + path_len = entry_len; + goto carry_on2; + } + + zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s failed", path, actual); +} +/* }}} */ + +/* {{{ proto void Phar::webPhar([string alias, [string index, [string f404, [array mimetypes, [callback rewrites]]]]]) + * mapPhar for web-based phars. Reads the currently executed file (a phar) + * and registers its manifest. When executed in the CLI or CGI command-line sapi, + * this works exactly like mapPhar(). When executed by a web-based sapi, this + * reads $_SERVER['REQUEST_URI'] (the actual original value) and parses out the + * intended internal file. + */ +PHP_METHOD(Phar, webPhar) +{ + zval *mimeoverride = NULL, *rewrite = NULL; + char *alias = NULL, *error, *index_php = NULL, *f404 = NULL, *ru = NULL; + size_t alias_len = 0, f404_len = 0, free_pathinfo = 0; + int ru_len = 0; + char *fname, *path_info, *mime_type = NULL, *entry, *pt; + const char *basename; + size_t fname_len, index_php_len = 0; + int entry_len, code, not_cgi; + phar_archive_data *phar = NULL; + phar_entry_info *info = NULL; + size_t sapi_mod_name_len = strlen(sapi_module.name); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s!saz", &alias, &alias_len, &index_php, &index_php_len, &f404, &f404_len, &mimeoverride, &rewrite) == FAILURE) { + return; + } + + phar_request_initialize(); + fname = (char*)zend_get_executed_filename(); + fname_len = strlen(fname); + + if (ZEND_SIZE_T_INT_OVFL(alias_len) + || ZEND_SIZE_T_INT_OVFL(f404_len) || ZEND_SIZE_T_INT_OVFL(index_php_len)) { + RETURN_FALSE; + } + + if (phar_open_executed_filename(alias, (int)alias_len, &error) != SUCCESS) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + return; + } + + /* retrieve requested file within phar */ + if (!(SG(request_info).request_method + && SG(request_info).request_uri + && (!strcmp(SG(request_info).request_method, "GET") + || !strcmp(SG(request_info).request_method, "POST") + || !strcmp(SG(request_info).request_method, "DELETE") + || !strcmp(SG(request_info).request_method, "HEAD") + || !strcmp(SG(request_info).request_method, "OPTIONS") + || !strcmp(SG(request_info).request_method, "PATCH") + || !strcmp(SG(request_info).request_method, "PUT") + ) + ) + ) { + return; + } + +#ifdef PHP_WIN32 + fname = estrndup(fname, fname_len); + phar_unixify_path_separators(fname, fname_len); +#endif + basename = zend_memrchr(fname, '/', fname_len); + + if (!basename) { + basename = fname; + } else { + ++basename; + } + + if ((sapi_mod_name_len == sizeof("cgi-fcgi") - 1 && !strncmp(sapi_module.name, "cgi-fcgi", sizeof("cgi-fcgi") - 1)) + || (sapi_mod_name_len == sizeof("fpm-fcgi") - 1 && !strncmp(sapi_module.name, "fpm-fcgi", sizeof("fpm-fcgi") - 1)) + || (sapi_mod_name_len == sizeof("cgi") - 1 && !strncmp(sapi_module.name, "cgi", sizeof("cgi") - 1))) { + + if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) != IS_UNDEF) { + HashTable *_server = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]); + zval *z_script_name, *z_path_info; + + if (NULL == (z_script_name = zend_hash_str_find(_server, "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1)) || + IS_STRING != Z_TYPE_P(z_script_name) || + !strstr(Z_STRVAL_P(z_script_name), basename)) { + return; + } + + if (NULL != (z_path_info = zend_hash_str_find(_server, "PATH_INFO", sizeof("PATH_INFO")-1)) && + IS_STRING == Z_TYPE_P(z_path_info)) { + entry_len = (int)Z_STRLEN_P(z_path_info); + entry = estrndup(Z_STRVAL_P(z_path_info), entry_len); + path_info = emalloc(Z_STRLEN_P(z_script_name) + entry_len + 1); + memcpy(path_info, Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name)); + memcpy(path_info + Z_STRLEN_P(z_script_name), entry, entry_len + 1); + free_pathinfo = 1; + } else { + entry_len = 0; + entry = estrndup("", 0); + path_info = Z_STRVAL_P(z_script_name); + } + + pt = estrndup(Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name)); + + } else { + char *testit; + + testit = sapi_getenv("SCRIPT_NAME", sizeof("SCRIPT_NAME")-1); + if (!(pt = strstr(testit, basename))) { + efree(testit); + return; + } + + path_info = sapi_getenv("PATH_INFO", sizeof("PATH_INFO")-1); + + if (path_info) { + entry = path_info; + entry_len = (int)strlen(entry); + spprintf(&path_info, 0, "%s%s", testit, path_info); + free_pathinfo = 1; + } else { + path_info = testit; + free_pathinfo = 1; + entry = estrndup("", 0); + entry_len = 0; + } + + pt = estrndup(testit, (pt - testit) + (fname_len - (basename - fname))); + } + not_cgi = 0; + } else { + path_info = SG(request_info).request_uri; + + if (!(pt = strstr(path_info, basename))) { + /* this can happen with rewrite rules - and we have no idea what to do then, so return */ + return; + } + + entry_len = (int)strlen(path_info); + entry_len -= (pt - path_info) + (fname_len - (basename - fname)); + entry = estrndup(pt + (fname_len - (basename - fname)), entry_len); + + pt = estrndup(path_info, (pt - path_info) + (fname_len - (basename - fname))); + not_cgi = 1; + } + + if (rewrite) { + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zval params, retval; + + ZVAL_STRINGL(¶ms, entry, entry_len); + + if (FAILURE == zend_fcall_info_init(rewrite, 0, &fci, &fcc, NULL, NULL)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: invalid rewrite callback"); + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + return; + } + + fci.param_count = 1; + fci.params = ¶ms; + Z_ADDREF(params); + fci.retval = &retval; + + if (FAILURE == zend_call_function(&fci, &fcc)) { + if (!EG(exception)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: failed to call rewrite callback"); + } + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + return; + } + + if (Z_TYPE_P(fci.retval) == IS_UNDEF || Z_TYPE(retval) == IS_UNDEF) { + if (free_pathinfo) { + efree(path_info); + } + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback must return a string or false"); + efree(pt); + return; + } + + switch (Z_TYPE(retval)) { + case IS_STRING: + efree(entry); + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN_P(fci.retval))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback returned oversized value"); + return; + } + entry = estrndup(Z_STRVAL_P(fci.retval), Z_STRLEN_P(fci.retval)); + entry_len = (int)Z_STRLEN_P(fci.retval); + break; + case IS_TRUE: + case IS_FALSE: + phar_do_403(entry, entry_len); + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + zend_bailout(); + return; + default: + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback must return a string or false"); + return; + } + } + + if (entry_len) { + phar_postprocess_ru_web(fname, (int)fname_len, &entry, &entry_len, &ru, &ru_len); + } + + if (!entry_len || (entry_len == 1 && entry[0] == '/')) { + efree(entry); + /* direct request */ + if (index_php_len) { + entry = index_php; + entry_len = (int)index_php_len; + if (entry[0] != '/') { + spprintf(&entry, 0, "/%s", index_php); + ++entry_len; + } + } else { + /* assume "index.php" is starting point */ + entry = estrndup("/index.php", sizeof("/index.php")); + entry_len = sizeof("/index.php")-1; + } + + if (FAILURE == phar_get_archive(&phar, fname, (int)fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, (int)fname_len, f404, (int)f404_len, entry, entry_len); + + if (free_pathinfo) { + efree(path_info); + } + + zend_bailout(); + } else { + char *tmp = NULL, sa = '\0'; + sapi_header_line ctr = {0}; + ctr.response_code = 301; + ctr.line_len = sizeof("HTTP/1.1 301 Moved Permanently")-1; + ctr.line = "HTTP/1.1 301 Moved Permanently"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + + if (not_cgi) { + tmp = strstr(path_info, basename) + fname_len; + sa = *tmp; + *tmp = '\0'; + } + + ctr.response_code = 0; + + if (path_info[strlen(path_info)-1] == '/') { + ctr.line_len = spprintf(&(ctr.line), 4096, "Location: %s%s", path_info, entry + 1); + } else { + ctr.line_len = spprintf(&(ctr.line), 4096, "Location: %s%s", path_info, entry); + } + + if (not_cgi) { + *tmp = sa; + } + + if (free_pathinfo) { + efree(path_info); + } + + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + efree(ctr.line); + zend_bailout(); + } + } + + if (FAILURE == phar_get_archive(&phar, fname, (int)fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, (int)fname_len, f404, (int)f404_len, entry, entry_len); +#ifdef PHP_WIN32 + efree(fname); +#endif + zend_bailout(); + } + + if (mimeoverride && zend_hash_num_elements(Z_ARRVAL_P(mimeoverride))) { + const char *ext = zend_memrchr(entry, '.', entry_len); + zval *val; + + if (ext) { + ++ext; + + if (NULL != (val = zend_hash_str_find(Z_ARRVAL_P(mimeoverride), ext, strlen(ext)))) { + switch (Z_TYPE_P(val)) { + case IS_LONG: + if (Z_LVAL_P(val) == PHAR_MIME_PHP || Z_LVAL_P(val) == PHAR_MIME_PHPS) { + mime_type = ""; + code = (int)Z_LVAL_P(val); + } else { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown mime type specifier used, only Phar::PHP, Phar::PHPS and a mime type string are allowed"); + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + efree(entry); +#ifdef PHP_WIN32 + efree(fname); +#endif + RETURN_FALSE; + } + break; + case IS_STRING: + mime_type = Z_STRVAL_P(val); + code = PHAR_MIME_OTHER; + break; + default: + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown mime type specifier used (not a string or int), only Phar::PHP, Phar::PHPS and a mime type string are allowed"); + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + efree(entry); +#ifdef PHP_WIN32 + efree(fname); +#endif + RETURN_FALSE; + } + } + } + } + + if (!mime_type) { + code = phar_file_type(&PHAR_G(mime_types), entry, &mime_type); + } + phar_file_action(phar, info, mime_type, code, entry, entry_len, fname, pt, ru, ru_len); +} +/* }}} */ + +/* {{{ proto void Phar::mungServer(array munglist) + * Defines a list of up to 4 $_SERVER variables that should be modified for execution + * to mask the presence of the phar archive. This should be used in conjunction with + * Phar::webPhar(), and has no effect otherwise + * SCRIPT_NAME, PHP_SELF, REQUEST_URI and SCRIPT_FILENAME + */ +PHP_METHOD(Phar, mungServer) +{ + zval *mungvalues, *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &mungvalues) == FAILURE) { + return; + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(mungvalues))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "No values passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + if (zend_hash_num_elements(Z_ARRVAL_P(mungvalues)) > 4) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Too many values passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + phar_request_initialize(); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(mungvalues), data) { + + if (Z_TYPE_P(data) != IS_STRING) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Non-string value passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + if (Z_STRLEN_P(data) == sizeof("PHP_SELF")-1 && !strncmp(Z_STRVAL_P(data), "PHP_SELF", sizeof("PHP_SELF")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_PHP_SELF; + } + + if (Z_STRLEN_P(data) == sizeof("REQUEST_URI")-1) { + if (!strncmp(Z_STRVAL_P(data), "REQUEST_URI", sizeof("REQUEST_URI")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_REQUEST_URI; + } + if (!strncmp(Z_STRVAL_P(data), "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_NAME; + } + } + + if (Z_STRLEN_P(data) == sizeof("SCRIPT_FILENAME")-1 && !strncmp(Z_STRVAL_P(data), "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_FILENAME; + } + } ZEND_HASH_FOREACH_END(); +} +/* }}} */ + +/* {{{ proto void Phar::interceptFileFuncs() + * instructs phar to intercept fopen, file_get_contents, opendir, and all of the stat-related functions + * and return stat on files within the phar for relative paths + * + * Once called, this cannot be reversed, and continue until the end of the request. + * + * This allows legacy scripts to be pharred unmodified + */ +PHP_METHOD(Phar, interceptFileFuncs) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + phar_intercept_functions(); +} +/* }}} */ + +/* {{{ proto array Phar::createDefaultStub([string indexfile[, string webindexfile]]) + * Return a stub that can be used to run a phar-based archive without the phar extension + * indexfile is the CLI startup filename, which defaults to "index.php", webindexfile + * is the web startup filename, and also defaults to "index.php" + */ +PHP_METHOD(Phar, createDefaultStub) +{ + char *index = NULL, *webindex = NULL, *error; + zend_string *stub; + size_t index_len = 0, webindex_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|pp", &index, &index_len, &webindex, &webindex_len) == FAILURE) { + return; + } + + stub = phar_create_default_stub(index, webindex, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + return; + } + RETURN_NEW_STR(stub); +} +/* }}} */ + +/* {{{ proto mixed Phar::mapPhar([string alias, [int dataoffset]]) + * Reads the currently executed file (a phar) and registers its manifest */ +PHP_METHOD(Phar, mapPhar) +{ + char *alias = NULL, *error; + size_t alias_len = 0; + zend_long dataoffset = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!l", &alias, &alias_len, &dataoffset) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len)) { + RETURN_FALSE; + } + phar_request_initialize(); + + RETVAL_BOOL(phar_open_executed_filename(alias, (int)alias_len, &error) == SUCCESS); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} /* }}} */ + +/* {{{ proto mixed Phar::loadPhar(string filename [, string alias]) + * Loads any phar archive with an alias */ +PHP_METHOD(Phar, loadPhar) +{ + char *fname, *alias = NULL, *error; + size_t fname_len, alias_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s!", &fname, &fname_len, &alias, &alias_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len) || ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + phar_request_initialize(); + + RETVAL_BOOL(phar_open_from_filename(fname, (int)fname_len, alias, (int)alias_len, REPORT_ERRORS, NULL, &error) == SUCCESS); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} /* }}} */ + +/* {{{ proto string Phar::apiVersion() + * Returns the api version */ +PHP_METHOD(Phar, apiVersion) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + RETURN_STRINGL(PHP_PHAR_API_VERSION, sizeof(PHP_PHAR_API_VERSION)-1); +} +/* }}}*/ + +/* {{{ proto bool Phar::canCompress([int method]) + * Returns whether phar extension supports compression using zlib/bzip2 */ +PHP_METHOD(Phar, canCompress) +{ + zend_long method = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &method) == FAILURE) { + return; + } + + phar_request_initialize(); + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (PHAR_G(has_zlib)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + case PHAR_ENT_COMPRESSED_BZ2: + if (PHAR_G(has_bz2)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + default: + if (PHAR_G(has_zlib) || PHAR_G(has_bz2)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto bool Phar::canWrite() + * Returns whether phar extension supports writing and creating phars */ +PHP_METHOD(Phar, canWrite) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + RETURN_BOOL(!PHAR_G(readonly)); +} +/* }}} */ + +/* {{{ proto bool Phar::isValidPharFilename(string filename[, bool executable = true]) + * Returns whether the given filename is a valid phar filename */ +PHP_METHOD(Phar, isValidPharFilename) +{ + char *fname; + const char *ext_str; + size_t fname_len; + int ext_len, is_executable; + zend_bool executable = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &fname, &fname_len, &executable) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + is_executable = executable; + RETVAL_BOOL(phar_detect_phar_fname_ext(fname, (int)fname_len, &ext_str, &ext_len, is_executable, 2, 1) == SUCCESS); +} +/* }}} */ + +/** + * from spl_directory + */ +static void phar_spl_foreign_dtor(spl_filesystem_object *object) /* {{{ */ +{ + phar_archive_data *phar = (phar_archive_data *) object->oth; + + if (!phar->is_persistent) { + phar_archive_delref(phar); + } + + object->oth = NULL; +} +/* }}} */ + +/** + * from spl_directory + */ +static void phar_spl_foreign_clone(spl_filesystem_object *src, spl_filesystem_object *dst) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *) dst->oth; + + if (!phar_data->is_persistent) { + ++(phar_data->refcount); + } +} +/* }}} */ + +static spl_other_handler phar_spl_foreign_handler = { + phar_spl_foreign_dtor, + phar_spl_foreign_clone +}; + +/* {{{ proto void Phar::__construct(string fname [, int flags [, string alias]]) + * Construct a Phar archive object + * + * proto void PharData::__construct(string fname [[, int flags [, string alias]], int file format = Phar::TAR]) + * Construct a PharData archive object + * + * This function is used as the constructor for both the Phar and PharData + * classes, hence the two prototypes above. + */ +PHP_METHOD(Phar, __construct) +{ + char *fname, *alias = NULL, *error, *arch = NULL, *entry = NULL, *save_fname; + size_t fname_len, alias_len = 0; + int arch_len, entry_len, is_data; + zend_long flags = SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS; + zend_long format = 0; + phar_archive_object *phar_obj; + phar_archive_data *phar_data; + zval *zobj = getThis(), arg1, arg2; + + phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + is_data = instanceof_function(Z_OBJCE_P(zobj), phar_ce_data); + + if (is_data) { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|ls!l", &fname, &fname_len, &flags, &alias, &alias_len, &format) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|ls!", &fname, &fname_len, &flags, &alias, &alias_len) == FAILURE) { + return; + } + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len) || ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (phar_obj->archive) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot call constructor twice"); + return; + } + + save_fname = fname; + if (SUCCESS == phar_split_fname(fname, (int)fname_len, &arch, &arch_len, &entry, &entry_len, !is_data, 2)) { + /* use arch (the basename for the archive) for fname instead of fname */ + /* this allows support for RecursiveDirectoryIterator of subdirectories */ +#ifdef PHP_WIN32 + phar_unixify_path_separators(arch, arch_len); +#endif + fname = arch; + fname_len = arch_len; +#ifdef PHP_WIN32 + } else { + arch = estrndup(fname, fname_len); + arch_len = fname_len; + fname = arch; + phar_unixify_path_separators(arch, arch_len); +#endif + } + + if (phar_open_or_create_filename(fname, (int)fname_len, alias, (int)alias_len, is_data, REPORT_ERRORS, &phar_data, &error) == FAILURE) { + + if (fname == arch && fname != save_fname) { + efree(arch); + fname = save_fname; + } + + if (entry) { + efree(entry); + } + + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "%s", error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Phar creation or opening failed"); + } + + return; + } + + if (is_data && phar_data->is_tar && phar_data->is_brandnew && format == PHAR_FORMAT_ZIP) { + phar_data->is_zip = 1; + phar_data->is_tar = 0; + } + + if (fname == arch) { + efree(arch); + fname = save_fname; + } + + if ((is_data && !phar_data->is_data) || (!is_data && phar_data->is_data)) { + if (is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "PharData class can only be used for non-executable tar and zip archives"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Phar class can only be used for executable tar and zip archives"); + } + efree(entry); + return; + } + + is_data = phar_data->is_data; + + if (!phar_data->is_persistent) { + ++(phar_data->refcount); + } + + phar_obj->archive = phar_data; + phar_obj->spl.oth_handler = &phar_spl_foreign_handler; + + if (entry) { + fname_len = spprintf(&fname, 0, "phar://%s%s", phar_data->fname, entry); + efree(entry); + } else { + fname_len = spprintf(&fname, 0, "phar://%s", phar_data->fname); + } + + ZVAL_STRINGL(&arg1, fname, fname_len); + ZVAL_LONG(&arg2, flags); + + zend_call_method_with_2_params(zobj, Z_OBJCE_P(zobj), + &spl_ce_RecursiveDirectoryIterator->constructor, "__construct", NULL, &arg1, &arg2); + + zval_ptr_dtor(&arg1); + + if (!phar_data->is_persistent) { + phar_obj->archive->is_data = is_data; + } else if (!EG(exception)) { + /* register this guy so we can modify if necessary */ + zend_hash_str_add_ptr(&PHAR_G(phar_persist_map), (const char *) phar_obj->archive, sizeof(phar_obj->archive), phar_obj); + } + + phar_obj->spl.info_class = phar_ce_entry; + efree(fname); +} +/* }}} */ + +/* {{{ proto array Phar::getSupportedSignatures() + * Return array of supported signature types + */ +PHP_METHOD(Phar, getSupportedSignatures) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + add_next_index_stringl(return_value, "MD5", 3); + add_next_index_stringl(return_value, "SHA-1", 5); +#ifdef PHAR_HASH_OK + add_next_index_stringl(return_value, "SHA-256", 7); + add_next_index_stringl(return_value, "SHA-512", 7); +#endif +#if PHAR_HAVE_OPENSSL + add_next_index_stringl(return_value, "OpenSSL", 7); +#else + if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) { + add_next_index_stringl(return_value, "OpenSSL", 7); + } +#endif +} +/* }}} */ + +/* {{{ proto array Phar::getSupportedCompression() + * Return array of supported comparession algorithms + */ +PHP_METHOD(Phar, getSupportedCompression) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + phar_request_initialize(); + + if (PHAR_G(has_zlib)) { + add_next_index_stringl(return_value, "GZ", 2); + } + + if (PHAR_G(has_bz2)) { + add_next_index_stringl(return_value, "BZIP2", 5); + } +} +/* }}} */ + +/* {{{ proto array Phar::unlinkArchive(string archive) + * Completely remove a phar archive from memory and disk + */ +PHP_METHOD(Phar, unlinkArchive) +{ + char *fname, *error, *zname, *arch, *entry; + size_t fname_len; + int zname_len, arch_len, entry_len; + phar_archive_data *phar; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (!fname_len) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"\""); + return; + } + + if (FAILURE == phar_open_from_filename(fname, (int)fname_len, NULL, 0, REPORT_ERRORS, &phar, &error)) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"%s\": %s", fname, error); + efree(error); + } else { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"%s\"", fname); + } + return; + } + + zname = (char*)zend_get_executed_filename(); + zname_len = (int)strlen(zname); + + if (zname_len > 7 && !memcmp(zname, "phar://", 7) && SUCCESS == phar_split_fname(zname, zname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + if ((size_t)arch_len == fname_len && !memcmp(arch, fname, arch_len)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" cannot be unlinked from within itself", fname); + efree(arch); + efree(entry); + return; + } + efree(arch); + efree(entry); + } + + if (phar->is_persistent) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" is in phar.cache_list, cannot unlinkArchive()", fname); + return; + } + + if (phar->refcount) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" has open file handles or objects. fclose() all file handles, and unset() all objects prior to calling unlinkArchive()", fname); + return; + } + + fname = estrndup(phar->fname, phar->fname_len); + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + phar_archive_delref(phar); + unlink(fname); + efree(fname); + RETURN_TRUE; +} +/* }}} */ + +#define PHAR_ARCHIVE_OBJECT() \ + zval *zobj = getThis(); \ + phar_archive_object *phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); \ + if (!phar_obj->archive) { \ + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Cannot call method on an uninitialized Phar object"); \ + return; \ + } + +/* {{{ proto void Phar::__destruct() + * if persistent, remove from the cache + */ +PHP_METHOD(Phar, __destruct) +{ + zval *zobj = getThis(); + phar_archive_object *phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (phar_obj->archive && phar_obj->archive->is_persistent) { + zend_hash_str_del(&PHAR_G(phar_persist_map), (const char *) phar_obj->archive, sizeof(phar_obj->archive)); + } +} +/* }}} */ + +struct _phar_t { + phar_archive_object *p; + zend_class_entry *c; + char *b; + zval *ret; + php_stream *fp; + uint32_t l; + int count; +}; + +static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ +{ + zval *value; + zend_bool close_fp = 1; + struct _phar_t *p_obj = (struct _phar_t*) puser; + uint32_t str_key_len, base_len = p_obj->l; + phar_entry_data *data; + php_stream *fp; + size_t fname_len; + size_t contents_len; + char *fname, *error = NULL, *base = p_obj->b, *save = NULL, *temp = NULL; + zend_string *opened; + char *str_key; + zend_class_entry *ce = p_obj->c; + phar_archive_object *phar_obj = p_obj->p; + + value = iter->funcs->get_current_data(iter); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (!value) { + /* failure in get_current_data */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned no value", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + switch (Z_TYPE_P(value)) { + case IS_STRING: + break; + case IS_RESOURCE: + php_stream_from_zval_no_verify(fp, value); + + if (!fp) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Iterator %s returned an invalid stream handle", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (iter->funcs->get_current_key) { + zval key; + iter->funcs->get_current_key(iter, &key); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (Z_TYPE(key) != IS_STRING) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN(key))) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (too long)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = (int)Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_dtor(&key); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + close_fp = 0; + opened = zend_string_init("[stream]", sizeof("[stream]") - 1, 0); + goto after_open_fp; + case IS_OBJECT: + if (instanceof_function(Z_OBJCE_P(value), spl_ce_SplFileInfo)) { + char *test = NULL; + zval dummy; + spl_filesystem_object *intern = (spl_filesystem_object*)((char*)Z_OBJ_P(value) - Z_OBJ_P(value)->handlers->offset); + + if (!base_len) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Iterator %s returns an SplFileInfo object, so base directory must be specified", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + switch (intern->type) { + case SPL_FS_DIR: + test = spl_filesystem_object_get_path(intern, NULL); + fname_len = spprintf(&fname, 0, "%s%c%s", test, DEFAULT_SLASH, intern->u.dir.entry.d_name); + php_stat(fname, fname_len, FS_IS_DIR, &dummy); + + if (Z_TYPE(dummy) == IS_TRUE) { + /* ignore directories */ + efree(fname); + return ZEND_HASH_APPLY_KEEP; + } + + test = expand_filepath(fname, NULL); + efree(fname); + + if (test) { + fname = test; + fname_len = strlen(fname); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + return ZEND_HASH_APPLY_STOP; + } + + save = fname; + goto phar_spl_fileinfo; + case SPL_FS_INFO: + case SPL_FS_FILE: + fname = expand_filepath(intern->file_name, NULL); + if (!fname) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + return ZEND_HASH_APPLY_STOP; + } + + fname_len = strlen(fname); + save = fname; + goto phar_spl_fileinfo; + } + } + /* fall-through */ + default: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid value (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + fname = Z_STRVAL_P(value); + fname_len = Z_STRLEN_P(value); + +phar_spl_fileinfo: + if (base_len) { + temp = expand_filepath(base, NULL); + if (!temp) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + if (save) { + efree(save); + } + return ZEND_HASH_APPLY_STOP; + } + + base = temp; + base_len = (int)strlen(base); + + if (strstr(fname, base)) { + str_key_len = fname_len - base_len; + + if (str_key_len <= 0) { + if (save) { + efree(save); + efree(temp); + } + return ZEND_HASH_APPLY_KEEP; + } + + str_key = fname + base_len; + + if (*str_key == '/' || *str_key == '\\') { + str_key++; + str_key_len--; + } + + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a path \"%s\" that is not in the base directory \"%s\"", ZSTR_VAL(ce->name), fname, base); + + if (save) { + efree(save); + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } + } else { + if (iter->funcs->get_current_key) { + zval key; + iter->funcs->get_current_key(iter, &key); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (Z_TYPE(key) != IS_STRING) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN(key))) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (too long)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = (int)Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_dtor(&key); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + } + + if (php_check_open_basedir(fname)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a path \"%s\" that open_basedir prevents opening", ZSTR_VAL(ce->name), fname); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } + + /* try to open source file, then create internal phar file and copy contents */ + fp = php_stream_open_wrapper(fname, "rb", STREAM_MUST_SEEK|0, &opened); + + if (!fp) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a file that could not be opened \"%s\"", ZSTR_VAL(ce->name), fname); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } +after_open_fp: + if (str_key_len >= sizeof(".phar")-1 && !memcmp(str_key, ".phar", sizeof(".phar")-1)) { + /* silently skip any files that would be added to the magic .phar directory */ + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + if (opened) { + zend_string_release(opened); + } + + if (close_fp) { + php_stream_close(fp); + } + + return ZEND_HASH_APPLY_KEEP; + } + + if (!(data = phar_get_or_create_entry_data(phar_obj->archive->fname, phar_obj->archive->fname_len, str_key, str_key_len, "w+b", 0, &error, 1))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s cannot be created: %s", str_key, error); + efree(error); + + if (save) { + efree(save); + } + + if (opened) { + zend_string_release(opened); + } + + if (temp) { + efree(temp); + } + + if (close_fp) { + php_stream_close(fp); + } + + return ZEND_HASH_APPLY_STOP; + + } else { + if (error) { + efree(error); + } + /* convert to PHAR_UFP */ + if (data->internal_file->fp_type == PHAR_MOD) { + php_stream_close(data->internal_file->fp); + } + + data->internal_file->fp = NULL; + data->internal_file->fp_type = PHAR_UFP; + data->internal_file->offset_abs = data->internal_file->offset = php_stream_tell(p_obj->fp); + data->fp = NULL; + php_stream_copy_to_stream_ex(fp, p_obj->fp, PHP_STREAM_COPY_ALL, &contents_len); + data->internal_file->uncompressed_filesize = data->internal_file->compressed_filesize = + php_stream_tell(p_obj->fp) - data->internal_file->offset; + } + + if (close_fp) { + php_stream_close(fp); + } + + add_assoc_str(p_obj->ret, str_key, opened); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize = contents_len; + phar_entry_delref(data); + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/* {{{ proto array Phar::buildFromDirectory(string base_dir[, string regex]) + * Construct a phar archive from an existing directory, recursively. + * Optional second parameter is a regular expression for filtering directory contents. + * + * Return value is an array mapping phar index to actual files added. + */ +PHP_METHOD(Phar, buildFromDirectory) +{ + char *dir, *error, *regex = NULL; + size_t dir_len, regex_len = 0; + zend_bool apply_reg = 0; + zval arg, arg2, iter, iteriter, regexiter; + struct _phar_t pass; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write to archive - write operations restricted by INI setting"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &dir, &dir_len, ®ex, ®ex_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_UINT_OVFL(dir_len)) { + RETURN_FALSE; + } + + if (SUCCESS != object_init_ex(&iter, spl_ce_RecursiveDirectoryIterator)) { + zval_ptr_dtor(&iter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate directory iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + ZVAL_STRINGL(&arg, dir, dir_len); + ZVAL_LONG(&arg2, SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS); + + zend_call_method_with_2_params(&iter, spl_ce_RecursiveDirectoryIterator, + &spl_ce_RecursiveDirectoryIterator->constructor, "__construct", NULL, &arg, &arg2); + + zval_ptr_dtor(&arg); + if (EG(exception)) { + zval_ptr_dtor(&iter); + RETURN_FALSE; + } + + if (SUCCESS != object_init_ex(&iteriter, spl_ce_RecursiveIteratorIterator)) { + zval_ptr_dtor(&iter); + zval_ptr_dtor(&iteriter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate directory iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + zend_call_method_with_1_params(&iteriter, spl_ce_RecursiveIteratorIterator, + &spl_ce_RecursiveIteratorIterator->constructor, "__construct", NULL, &iter); + + if (EG(exception)) { + zval_ptr_dtor(&iter); + zval_ptr_dtor(&iteriter); + RETURN_FALSE; + } + + zval_ptr_dtor(&iter); + + if (regex_len > 0) { + apply_reg = 1; + + if (SUCCESS != object_init_ex(®exiter, spl_ce_RegexIterator)) { + zval_ptr_dtor(&iteriter); + zval_dtor(®exiter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate regex iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + ZVAL_STRINGL(&arg2, regex, regex_len); + + zend_call_method_with_2_params(®exiter, spl_ce_RegexIterator, + &spl_ce_RegexIterator->constructor, "__construct", NULL, &iteriter, &arg2); + zval_ptr_dtor(&arg2); + } + + array_init(return_value); + + pass.c = apply_reg ? Z_OBJCE(regexiter) : Z_OBJCE(iteriter); + pass.p = phar_obj; + pass.b = dir; + pass.l = (uint32_t)dir_len; + pass.count = 0; + pass.ret = return_value; + pass.fp = php_stream_fopen_tmpfile(); + if (pass.fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" unable to create temporary file", phar_obj->archive->fname); + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zval_ptr_dtor(&iteriter); + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + php_stream_close(pass.fp); + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + + if (SUCCESS == spl_iterator_apply((apply_reg ? ®exiter : &iteriter), (spl_iterator_apply_func_t) phar_build, (void *) &pass)) { + zval_ptr_dtor(&iteriter); + + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + + phar_obj->archive->ufp = pass.fp; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + } else { + zval_ptr_dtor(&iteriter); + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + php_stream_close(pass.fp); + } +} +/* }}} */ + +/* {{{ proto array Phar::buildFromIterator(Iterator iter[, string base_directory]) + * Construct a phar archive from an iterator. The iterator must return a series of strings + * that are full paths to files that should be added to the phar. The iterator key should + * be the path that the file will have within the phar archive. + * + * If base directory is specified, then the key will be ignored, and instead the portion of + * the current value minus the base directory will be used + * + * Returned is an array mapping phar index to actual file added + */ +PHP_METHOD(Phar, buildFromIterator) +{ + zval *obj; + char *error; + size_t base_len = 0; + char *base = NULL; + struct _phar_t pass; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|s", &obj, zend_ce_traversable, &base, &base_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_UINT_OVFL(base_len)) { + RETURN_FALSE; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + + array_init(return_value); + + pass.c = Z_OBJCE_P(obj); + pass.p = phar_obj; + pass.b = base; + pass.l = (uint32_t)base_len; + pass.ret = return_value; + pass.count = 0; + pass.fp = php_stream_fopen_tmpfile(); + if (pass.fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\": unable to create temporary file", phar_obj->archive->fname); + return; + } + + if (SUCCESS == spl_iterator_apply(obj, (spl_iterator_apply_func_t) phar_build, (void *) &pass)) { + phar_obj->archive->ufp = pass.fp; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } else { + php_stream_close(pass.fp); + } +} +/* }}} */ + +/* {{{ proto int Phar::count() + * Returns the number of entries in the Phar archive + */ +PHP_METHOD(Phar, count) +{ + /* mode can be ignored, maximum depth is 1 */ + zend_long mode; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &mode) == FAILURE) { + RETURN_FALSE; + } + + RETURN_LONG(zend_hash_num_elements(&phar_obj->archive->manifest)); +} +/* }}} */ + +/* {{{ proto bool Phar::isFileFormat(int format) + * Returns true if the phar archive is based on the tar/zip/phar file format depending + * on whether Phar::TAR, Phar::ZIP or Phar::PHAR was passed in + */ +PHP_METHOD(Phar, isFileFormat) +{ + zend_long type; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &type) == FAILURE) { + RETURN_FALSE; + } + + switch (type) { + case PHAR_FORMAT_TAR: + RETURN_BOOL(phar_obj->archive->is_tar); + case PHAR_FORMAT_ZIP: + RETURN_BOOL(phar_obj->archive->is_zip); + case PHAR_FORMAT_PHAR: + RETURN_BOOL(!phar_obj->archive->is_tar && !phar_obj->archive->is_zip); + default: + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown file format specified"); + } +} +/* }}} */ + +static int phar_copy_file_contents(phar_entry_info *entry, php_stream *fp) /* {{{ */ +{ + char *error; + zend_off_t offset; + phar_entry_info *link; + + if (FAILURE == phar_open_entry_fp(entry, &error, 1)) { + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents: %s", entry->phar->fname, entry->filename, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents", entry->phar->fname, entry->filename); + } + return FAILURE; + } + + /* copy old contents in entirety */ + phar_seek_efp(entry, 0, SEEK_SET, 0, 1); + offset = php_stream_tell(fp); + link = phar_get_link_source(entry); + + if (!link) { + link = entry; + } + + if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(link, 0), fp, link->uncompressed_filesize, NULL)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to copy entry \"%s\" contents", entry->phar->fname, entry->filename); + return FAILURE; + } + + if (entry->fp_type == PHAR_MOD) { + /* save for potential restore on error */ + entry->cfp = entry->fp; + entry->fp = NULL; + } + + /* set new location of file contents */ + entry->fp_type = PHAR_FP; + entry->offset = offset; + return SUCCESS; +} +/* }}} */ + +static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /* {{{ */ +{ + const char *oldname = NULL; + phar_archive_data *phar = *sphar; + char *oldpath = NULL; + char *basename = NULL, *basepath = NULL; + char *newname = NULL, *newpath = NULL; + zval ret, arg1; + zend_class_entry *ce; + char *error; + const char *pcr_error; + int ext_len = ext ? strlen(ext) : 0; + size_t new_len, oldname_len, phar_ext_len; + phar_archive_data *pphar = NULL; + php_stream_statbuf ssb; + + int phar_ext_list_len, i = 0; + char *ext_pos = NULL; + /* Array of PHAR extensions, Must be in order, starting with longest + * ending with the shortest. */ + char *phar_ext_list[] = { + ".phar.tar.bz2", + ".phar.tar.gz", + ".phar.php", + ".phar.bz2", + ".phar.zip", + ".phar.tar", + ".phar.gz", + ".tar.bz2", + ".tar.gz", + ".phar", + ".tar", + ".zip" + }; + + if (!ext) { + if (phar->is_zip) { + + if (phar->is_data) { + ext = "zip"; + } else { + ext = "phar.zip"; + } + + } else if (phar->is_tar) { + + switch (phar->flags) { + case PHAR_FILE_COMPRESSED_GZ: + if (phar->is_data) { + ext = "tar.gz"; + } else { + ext = "phar.tar.gz"; + } + break; + case PHAR_FILE_COMPRESSED_BZ2: + if (phar->is_data) { + ext = "tar.bz2"; + } else { + ext = "phar.tar.bz2"; + } + break; + default: + if (phar->is_data) { + ext = "tar"; + } else { + ext = "phar.tar"; + } + } + } else { + + switch (phar->flags) { + case PHAR_FILE_COMPRESSED_GZ: + ext = "phar.gz"; + break; + case PHAR_FILE_COMPRESSED_BZ2: + ext = "phar.bz2"; + break; + default: + ext = "phar"; + } + } + } else if (phar_path_check(&ext, &ext_len, &pcr_error) > pcr_is_ok) { + + if (phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "data phar converted from \"%s\" has invalid extension %s", phar->fname, ext); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar converted from \"%s\" has invalid extension %s", phar->fname, ext); + } + return NULL; + } + + + oldpath = estrndup(phar->fname, phar->fname_len); + if ((oldname = zend_memrchr(phar->fname, '/', phar->fname_len))) { + ++oldname; + } else { + oldname = phar->fname; + } + + oldname_len = strlen(oldname); + /* Copy the old name to create base for the new name */ + basename = estrndup(oldname, oldname_len); + + phar_ext_list_len = sizeof(phar_ext_list)/sizeof(phar_ext_list[0]); + /* Remove possible PHAR extensions */ + /* phar_ext_list must be in order of longest extension to shortest */ + for (i=0; i < phar_ext_list_len; i++) { + phar_ext_len = strlen(phar_ext_list[i]); + if (phar_ext_len && oldname_len > phar_ext_len) { + /* Check if the basename strings ends with the extension */ + if (memcmp(phar_ext_list[i], basename + (oldname_len - phar_ext_len), phar_ext_len) == 0) { + ext_pos = basename + (oldname_len - phar_ext_len); + ext_pos[0] = '\0'; + break; + } + } + ext_pos = NULL; + } + + /* If no default PHAR extension found remove the last extension */ + if (!ext_pos) { + ext_pos = strrchr(basename, '.'); + if (ext_pos) { + ext_pos[0] = '\0'; + } + } + ext_pos = NULL; + + if (ext[0] == '.') { + ++ext; + } + /* Append extension to the basename */ + spprintf(&newname, 0, "%s.%s", basename, ext); + efree(basename); + + basepath = estrndup(oldpath, (strlen(oldpath) - oldname_len)); + new_len = spprintf(&newpath, 0, "%s%s", basepath, newname); + if (ZEND_SIZE_T_INT_OVFL(new_len)) { + efree(oldpath); + efree(basepath); + efree(newpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "New name is too long"); + return NULL; + } + phar->fname_len = (int)new_len; + phar->fname = newpath; + phar->ext = newpath + phar->fname_len - strlen(ext) - 1; + efree(basepath); + efree(newname); + + if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, newpath, phar->fname_len))) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, new phar name is in phar.cache_list", phar->fname); + return NULL; + } + + if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len))) { + if (pphar->fname_len == phar->fname_len && !memcmp(pphar->fname, phar->fname, phar->fname_len)) { + if (!zend_hash_num_elements(&phar->manifest)) { + pphar->is_tar = phar->is_tar; + pphar->is_zip = phar->is_zip; + pphar->is_data = phar->is_data; + pphar->flags = phar->flags; + pphar->fp = phar->fp; + phar->fp = NULL; + phar_destroy_phar_data(phar); + *sphar = NULL; + phar = pphar; + phar->refcount++; + newpath = oldpath; + goto its_ok; + } + } + + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, a phar with that name already exists", phar->fname); + return NULL; + } +its_ok: + if (SUCCESS == php_stream_stat_path(newpath, &ssb)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" exists and must be unlinked prior to conversion", newpath); + efree(oldpath); + return NULL; + } + if (!phar->is_data) { + if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &(phar->ext_len), 1, 1, 1)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" has invalid extension %s", phar->fname, ext); + return NULL; + } + + if (phar->alias) { + if (phar->is_temporary_alias) { + phar->alias = NULL; + phar->alias_len = 0; + } else { + phar->alias = estrndup(newpath, strlen(newpath)); + phar->alias_len = (int)strlen(newpath); + phar->is_temporary_alias = 1; + zend_hash_str_update_ptr(&(PHAR_G(phar_alias_map)), newpath, phar->fname_len, phar); + } + } + + } else { + + if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &(phar->ext_len), 0, 1, 1)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "data phar \"%s\" has invalid extension %s", phar->fname, ext); + return NULL; + } + + phar->alias = NULL; + phar->alias_len = 0; + } + + if ((!pphar || phar == pphar) && NULL == zend_hash_str_update_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len, phar)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars", phar->fname); + return NULL; + } + + phar_flush(phar, 0, 0, 1, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + efree(oldpath); + return NULL; + } + + efree(oldpath); + + if (phar->is_data) { + ce = phar_ce_data; + } else { + ce = phar_ce_archive; + } + + ZVAL_NULL(&ret); + if (SUCCESS != object_init_ex(&ret, ce)) { + zval_dtor(&ret); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate phar object when converting archive \"%s\"", phar->fname); + return NULL; + } + + ZVAL_STRINGL(&arg1, phar->fname, phar->fname_len); + + zend_call_method_with_1_params(&ret, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + return Z_OBJ(ret); +} +/* }}} */ + +static zend_object *phar_convert_to_other(phar_archive_data *source, int convert, char *ext, uint32_t flags) /* {{{ */ +{ + phar_archive_data *phar; + phar_entry_info *entry, newentry; + zend_object *ret; + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + phar = (phar_archive_data *) ecalloc(1, sizeof(phar_archive_data)); + /* set whole-archive compression and type from parameter */ + phar->flags = flags; + phar->is_data = source->is_data; + + switch (convert) { + case PHAR_FORMAT_TAR: + phar->is_tar = 1; + break; + case PHAR_FORMAT_ZIP: + phar->is_zip = 1; + break; + default: + phar->is_data = 0; + break; + } + + zend_hash_init(&(phar->manifest), sizeof(phar_entry_info), + zend_get_hash_value, destroy_phar_manifest_entry, 0); + zend_hash_init(&phar->mounted_dirs, sizeof(char *), + zend_get_hash_value, NULL, 0); + zend_hash_init(&phar->virtual_dirs, sizeof(char *), + zend_get_hash_value, NULL, 0); + + phar->fp = php_stream_fopen_tmpfile(); + if (phar->fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "unable to create temporary file"); + return NULL; + } + phar->fname = source->fname; + phar->fname_len = source->fname_len; + phar->is_temporary_alias = source->is_temporary_alias; + phar->alias = source->alias; + + if (Z_TYPE(source->metadata) != IS_UNDEF) { + ZVAL_DUP(&phar->metadata, &source->metadata); + phar->metadata_len = 0; + } + + /* first copy each file's uncompressed contents to a temporary file and set per-file flags */ + ZEND_HASH_FOREACH_PTR(&source->manifest, entry) { + + newentry = *entry; + + if (newentry.link) { + newentry.link = estrdup(newentry.link); + goto no_copy; + } + + if (newentry.tmp) { + newentry.tmp = estrdup(newentry.tmp); + goto no_copy; + } + + newentry.metadata_str.s = NULL; + + if (FAILURE == phar_copy_file_contents(&newentry, phar->fp)) { + zend_hash_destroy(&(phar->manifest)); + php_stream_close(phar->fp); + efree(phar); + /* exception already thrown */ + return NULL; + } +no_copy: + newentry.filename = estrndup(newentry.filename, newentry.filename_len); + + if (Z_TYPE(newentry.metadata) != IS_UNDEF) { + zval_copy_ctor(&newentry.metadata); + newentry.metadata_str.s = NULL; + } + + newentry.is_zip = phar->is_zip; + newentry.is_tar = phar->is_tar; + + if (newentry.is_tar) { + newentry.tar_type = (entry->is_dir ? TAR_DIR : TAR_FILE); + } + + newentry.is_modified = 1; + newentry.phar = phar; + newentry.old_flags = newentry.flags & ~PHAR_ENT_COMPRESSION_MASK; /* remove compression from old_flags */ + phar_set_inode(&newentry); + zend_hash_str_add_mem(&(phar->manifest), newentry.filename, newentry.filename_len, (void*)&newentry, sizeof(phar_entry_info)); + phar_add_virtual_dirs(phar, newentry.filename, newentry.filename_len); + } ZEND_HASH_FOREACH_END(); + + if ((ret = phar_rename_archive(&phar, ext))) { + return ret; + } else { + if(phar != NULL) { + zend_hash_destroy(&(phar->manifest)); + zend_hash_destroy(&(phar->mounted_dirs)); + zend_hash_destroy(&(phar->virtual_dirs)); + if (phar->fp) { + php_stream_close(phar->fp); + } + efree(phar->fname); + efree(phar); + } + return NULL; + } +} +/* }}} */ + +/* {{{ proto object Phar::convertToExecutable([int format[, int compression [, string file_ext]]]) + * Convert a phar.tar or phar.zip archive to the phar file format. The + * optional parameter allows the user to determine the new + * filename extension (default is phar). + */ +PHP_METHOD(Phar, convertToExecutable) +{ + char *ext = NULL; + int is_data; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + /* a number that is not 0, 1 or 2 (Which is also Greg's birthday, so there) */ + zend_long format = 9021976, method = 9021976; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lls", &format, &method, &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out executable phar archive, phar is read-only"); + return; + } + + switch (format) { + case 9021976: + case PHAR_FORMAT_SAME: /* null is converted to 0 */ + /* by default, use the existing format */ + if (phar_obj->archive->is_tar) { + format = PHAR_FORMAT_TAR; + } else if (phar_obj->archive->is_zip) { + format = PHAR_FORMAT_ZIP; + } else { + format = PHAR_FORMAT_PHAR; + } + break; + case PHAR_FORMAT_PHAR: + case PHAR_FORMAT_TAR: + case PHAR_FORMAT_ZIP: + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown file format specified, please pass one of Phar::PHAR, Phar::TAR or Phar::ZIP"); + return; + } + + switch (method) { + case 9021976: + flags = phar_obj->archive->flags & PHAR_FILE_COMPRESSION_MASK; + break; + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + is_data = phar_obj->archive->is_data; + phar_obj->archive->is_data = 0; + ret = phar_convert_to_other(phar_obj->archive, (int)format, ext, flags); + phar_obj->archive->is_data = is_data; + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::convertToData([int format[, int compression [, string file_ext]]]) + * Convert an archive to a non-executable .tar or .zip. + * The optional parameter allows the user to determine the new + * filename extension (default is .zip or .tar). + */ +PHP_METHOD(Phar, convertToData) +{ + char *ext = NULL; + int is_data; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + /* a number that is not 0, 1 or 2 (Which is also Greg's birthday so there) */ + zend_long format = 9021976, method = 9021976; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lls", &format, &method, &ext, &ext_len) == FAILURE) { + return; + } + + switch (format) { + case 9021976: + case PHAR_FORMAT_SAME: /* null is converted to 0 */ + /* by default, use the existing format */ + if (phar_obj->archive->is_tar) { + format = PHAR_FORMAT_TAR; + } else if (phar_obj->archive->is_zip) { + format = PHAR_FORMAT_ZIP; + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out data phar archive, use Phar::TAR or Phar::ZIP"); + return; + } + break; + case PHAR_FORMAT_PHAR: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out data phar archive, use Phar::TAR or Phar::ZIP"); + return; + case PHAR_FORMAT_TAR: + case PHAR_FORMAT_ZIP: + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown file format specified, please pass one of Phar::TAR or Phar::ZIP"); + return; + } + + switch (method) { + case 9021976: + flags = phar_obj->archive->flags & PHAR_FILE_COMPRESSION_MASK; + break; + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + is_data = phar_obj->archive->is_data; + phar_obj->archive->is_data = 1; + ret = phar_convert_to_other(phar_obj->archive, (int)format, ext, flags); + phar_obj->archive->is_data = is_data; + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto int|false Phar::isCompressed() + * Returns Phar::GZ or PHAR::BZ2 if the entire archive is compressed + * (.tar.gz/tar.bz2 and so on), or FALSE otherwise. + */ +PHP_METHOD(Phar, isCompressed) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->flags & PHAR_FILE_COMPRESSED_GZ) { + RETURN_LONG(PHAR_ENT_COMPRESSED_GZ); + } + + if (phar_obj->archive->flags & PHAR_FILE_COMPRESSED_BZ2) { + RETURN_LONG(PHAR_ENT_COMPRESSED_BZ2); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool Phar::isWritable() + * Returns true if phar.readonly=0 or phar is a PharData AND the actual file is writable. + */ +PHP_METHOD(Phar, isWritable) +{ + php_stream_statbuf ssb; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!phar_obj->archive->is_writeable) { + RETURN_FALSE; + } + + if (SUCCESS != php_stream_stat_path(phar_obj->archive->fname, &ssb)) { + if (phar_obj->archive->is_brandnew) { + /* assume it works if the file doesn't exist yet */ + RETURN_TRUE; + } + RETURN_FALSE; + } + + RETURN_BOOL((ssb.sb.st_mode & (S_IWOTH | S_IWGRP | S_IWUSR)) != 0); +} +/* }}} */ + +/* {{{ proto bool Phar::delete(string entry) + * Deletes a named file within the archive. + */ +PHP_METHOD(Phar, delete) +{ + char *fname; + size_t fname_len; + char *error; + phar_entry_info *entry; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + RETURN_FALSE; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + RETURN_TRUE; + } else { + entry->is_deleted = 1; + entry->is_modified = 1; + phar_obj->archive->is_modified = 1; + } + } + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be deleted", fname); + RETURN_FALSE; + } + + phar_flush(phar_obj->archive, NULL, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int Phar::getAlias() + * Returns the alias for the Phar or NULL. + */ +PHP_METHOD(Phar, getAlias) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->alias && phar_obj->archive->alias != phar_obj->archive->fname) { + RETURN_STRINGL(phar_obj->archive->alias, phar_obj->archive->alias_len); + } +} +/* }}} */ + +/* {{{ proto int Phar::getPath() + * Returns the real path to the phar archive on disk + */ +PHP_METHOD(Phar, getPath) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRINGL(phar_obj->archive->fname, phar_obj->archive->fname_len); +} +/* }}} */ + +/* {{{ proto bool Phar::setAlias(string alias) + * Sets the alias for a Phar archive. The default value is the full path + * to the archive. + */ +PHP_METHOD(Phar, setAlias) +{ + char *alias, *error, *oldalias; + phar_archive_data *fd_ptr; + size_t alias_len, oldalias_len; + int old_temp, readd = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + RETURN_FALSE; + } + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar alias cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar alias cannot be set in a plain zip archive"); + } + RETURN_FALSE; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &alias, &alias_len) == SUCCESS) { + if (ZEND_SIZE_T_INT_OVFL(alias_len)) { + RETURN_FALSE; + } + if (alias_len == (size_t)phar_obj->archive->alias_len && memcmp(phar_obj->archive->alias, alias, alias_len) == 0) { + RETURN_TRUE; + } + if (alias_len && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + spprintf(&error, 0, "alias \"%s\" is already used for archive \"%s\" and cannot be used for other archives", alias, fd_ptr->fname); + if (SUCCESS == phar_free_alias(fd_ptr, alias, (int)alias_len)) { + efree(error); + goto valid_alias; + } + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } + if (!phar_validate_alias(alias, (int)alias_len)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Invalid alias \"%s\" specified for phar \"%s\"", alias, phar_obj->archive->fname); + RETURN_FALSE; + } +valid_alias: + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (phar_obj->archive->alias_len && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), phar_obj->archive->alias, phar_obj->archive->alias_len))) { + zend_hash_str_del(&(PHAR_G(phar_alias_map)), phar_obj->archive->alias, phar_obj->archive->alias_len); + readd = 1; + } + + oldalias = phar_obj->archive->alias; + oldalias_len = phar_obj->archive->alias_len; + old_temp = phar_obj->archive->is_temporary_alias; + + if (alias_len) { + phar_obj->archive->alias = estrndup(alias, alias_len); + } else { + phar_obj->archive->alias = NULL; + } + + phar_obj->archive->alias_len = (int)alias_len; + phar_obj->archive->is_temporary_alias = 0; + phar_flush(phar_obj->archive, NULL, 0, 0, &error); + + if (error) { + phar_obj->archive->alias = oldalias; + phar_obj->archive->alias_len = (int)oldalias_len; + phar_obj->archive->is_temporary_alias = old_temp; + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + if (readd) { + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), oldalias, oldalias_len, phar_obj->archive); + } + efree(error); + RETURN_FALSE; + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, phar_obj->archive); + + if (oldalias) { + efree(oldalias); + } + + RETURN_TRUE; + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto string Phar::getVersion() + * Return version info of Phar archive + */ +PHP_METHOD(Phar, getVersion) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRING(phar_obj->archive->version); +} +/* }}} */ + +/* {{{ proto void Phar::startBuffering() + * Do not flush a writeable phar (save its contents) until explicitly requested + */ +PHP_METHOD(Phar, startBuffering) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + phar_obj->archive->donotflush = 1; +} +/* }}} */ + +/* {{{ proto bool Phar::isBuffering() + * Returns whether write operations are flushing to disk immediately. + */ +PHP_METHOD(Phar, isBuffering) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(phar_obj->archive->donotflush); +} +/* }}} */ + +/* {{{ proto bool Phar::stopBuffering() + * Saves the contents of a modified archive to disk. + */ +PHP_METHOD(Phar, stopBuffering) +{ + char *error; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + phar_obj->archive->donotflush = 0; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool Phar::setStub(string|stream stub [, int len]) + * Change the stub in a phar, phar.tar or phar.zip archive to something other + * than the default. The stub *must* end with a call to __HALT_COMPILER(). + */ +PHP_METHOD(Phar, setStub) +{ + zval *zstub; + char *stub, *error; + size_t stub_len; + zend_long len = -1; + php_stream *stream; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub, phar is read-only"); + return; + } + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain zip archive"); + } + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r|l", &zstub, &len) == SUCCESS) { + if ((php_stream_from_zval_no_verify(stream, zstub)) != NULL) { + if (len > 0) { + len = -len; + } else { + len = -1; + } + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, (char *) zstub, len, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + RETURN_TRUE; + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub, unable to read from input stream"); + } + } else if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &stub, &stub_len) == SUCCESS) { + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, stub, stub_len, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool Phar::setDefaultStub([string index[, string webindex]]) + * In a pure phar archive, sets a stub that can be used to run the archive + * regardless of whether the phar extension is available. The first parameter + * is the CLI startup filename, which defaults to "index.php". The second + * parameter is the web startup filename and also defaults to "index.php" + * (falling back to CLI behaviour). + * Both parameters are optional. + * In a phar.zip or phar.tar archive, the default stub is used only to + * identify the archive to the extension as a Phar object. This allows the + * extension to treat phar.zip and phar.tar types as honorary phars. Since + * files cannot be loaded via this kind of stub, no parameters are accepted + * when the Phar object is zip- or tar-based. + */ +PHP_METHOD(Phar, setDefaultStub) +{ + char *index = NULL, *webindex = NULL, *error = NULL; + zend_string *stub = NULL; + size_t index_len = 0, webindex_len = 0; + int created_stub = 0; + PHAR_ARCHIVE_OBJECT(); + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain zip archive"); + } + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s", &index, &index_len, &webindex, &webindex_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_NUM_ARGS() > 0 && (phar_obj->archive->is_tar || phar_obj->archive->is_zip)) { + php_error_docref(NULL, E_WARNING, "method accepts no arguments for a tar- or zip-based phar stub, %d given", ZEND_NUM_ARGS()); + RETURN_FALSE; + } + + if (PHAR_G(readonly)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub: phar.readonly=1"); + RETURN_FALSE; + } + + if (!phar_obj->archive->is_tar && !phar_obj->archive->is_zip) { + stub = phar_create_default_stub(index, webindex, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "%s", error); + efree(error); + if (stub) { + zend_string_free(stub); + } + RETURN_FALSE; + } + + created_stub = 1; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, stub ? ZSTR_VAL(stub) : 0, stub ? ZSTR_LEN(stub) : 0, 1, &error); + + if (created_stub) { + zend_string_free(stub); + } + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array Phar::setSignatureAlgorithm(int sigtype[, string privatekey]) + * Sets the signature algorithm for a phar and applies it. The signature + * algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, + * Phar::SHA512, or Phar::OPENSSL. Note that zip- based phar archives + * cannot support signatures. + */ +PHP_METHOD(Phar, setSignatureAlgorithm) +{ + zend_long algo; + char *error, *key = NULL; + size_t key_len = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot set signature algorithm, phar is read-only"); + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "l|s", &algo, &key, &key_len) != SUCCESS) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(key_len)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot set signature algorithm, key too long"); + return; + } + + switch (algo) { + case PHAR_SIG_SHA256: + case PHAR_SIG_SHA512: +#ifndef PHAR_HASH_OK + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "SHA-256 and SHA-512 signatures are only supported if the hash extension is enabled and built non-shared"); + return; +#endif + case PHAR_SIG_MD5: + case PHAR_SIG_SHA1: + case PHAR_SIG_OPENSSL: + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_obj->archive->sig_flags = (php_uint32)algo; + phar_obj->archive->is_modified = 1; + PHAR_G(openssl_privatekey) = key; + PHAR_G(openssl_privatekey_len) = (int)key_len; + + phar_flush(phar_obj->archive, 0, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + break; + default: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Unknown signature algorithm specified"); + } +} +/* }}} */ + +/* {{{ proto array|false Phar::getSignature() + * Returns a hash signature, or FALSE if the archive is unsigned. + */ +PHP_METHOD(Phar, getSignature) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->signature) { + zend_string *unknown; + + array_init(return_value); + add_assoc_stringl(return_value, "hash", phar_obj->archive->signature, phar_obj->archive->sig_len); + switch(phar_obj->archive->sig_flags) { + case PHAR_SIG_MD5: + add_assoc_stringl(return_value, "hash_type", "MD5", 3); + break; + case PHAR_SIG_SHA1: + add_assoc_stringl(return_value, "hash_type", "SHA-1", 5); + break; + case PHAR_SIG_SHA256: + add_assoc_stringl(return_value, "hash_type", "SHA-256", 7); + break; + case PHAR_SIG_SHA512: + add_assoc_stringl(return_value, "hash_type", "SHA-512", 7); + break; + case PHAR_SIG_OPENSSL: + add_assoc_stringl(return_value, "hash_type", "OpenSSL", 7); + break; + default: + unknown = strpprintf(0, "Unknown (%u)", phar_obj->archive->sig_flags); + add_assoc_str(return_value, "hash_type", unknown); + break; + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool Phar::getModified() + * Return whether phar was modified + */ +PHP_METHOD(Phar, getModified) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(phar_obj->archive->is_modified); +} +/* }}} */ + +static int phar_set_compression(zval *zv, void *argument) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + uint32_t compress = *(uint32_t *)argument; + + if (entry->is_deleted) { + return ZEND_HASH_APPLY_KEEP; + } + + entry->old_flags = entry->flags; + entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry->flags |= compress; + entry->is_modified = 1; + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int phar_test_compression(zval *zv, void *argument) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + + if (entry->is_deleted) { + return ZEND_HASH_APPLY_KEEP; + } + + if (!PHAR_G(has_bz2)) { + if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) { + *(int *) argument = 0; + } + } + + if (!PHAR_G(has_zlib)) { + if (entry->flags & PHAR_ENT_COMPRESSED_GZ) { + *(int *) argument = 0; + } + } + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static void pharobj_set_compression(HashTable *manifest, uint32_t compress) /* {{{ */ +{ + zend_hash_apply_with_argument(manifest, phar_set_compression, &compress); +} +/* }}} */ + +static int pharobj_cancompress(HashTable *manifest) /* {{{ */ +{ + int test; + + test = 1; + zend_hash_apply_with_argument(manifest, phar_test_compression, &test); + return test; +} +/* }}} */ + +/* {{{ proto object Phar::compress(int method[, string extension]) + * Compress a .tar, or .phar.tar with whole-file compression + * The parameter can be one of Phar::GZ or Phar::BZ2 to specify + * the kind of compression desired + */ +PHP_METHOD(Phar, compress) +{ + zend_long method; + char *ext = NULL; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|s", &method, &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot compress phar archive, phar is read-only"); + return; + } + + if (phar_obj->archive->is_zip) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot compress zip-based archives with whole-archive compression"); + return; + } + + switch (method) { + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + flags = PHAR_FILE_COMPRESSED_GZ; + break; + + case PHAR_ENT_COMPRESSED_BZ2: + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + if (phar_obj->archive->is_tar) { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_TAR, ext, flags); + } else { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_PHAR, ext, flags); + } + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::decompress([string extension]) + * Decompress a .tar, or .phar.tar with whole-file compression + */ +PHP_METHOD(Phar, decompress) +{ + char *ext = NULL; + size_t ext_len = 0; + zend_object *ret; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot decompress phar archive, phar is read-only"); + return; + } + + if (phar_obj->archive->is_zip) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot decompress zip-based archives with whole-archive compression"); + return; + } + + if (phar_obj->archive->is_tar) { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_TAR, ext, PHAR_FILE_COMPRESSED_NONE); + } else { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_PHAR, ext, PHAR_FILE_COMPRESSED_NONE); + } + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::compressFiles(int method) + * Compress all files within a phar or zip archive using the specified compression + * The parameter can be one of Phar::GZ or Phar::BZ2 to specify + * the kind of compression desired + */ +PHP_METHOD(Phar, compressFiles) +{ + char *error; + uint32_t flags; + zend_long method; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &method) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress files within archive with gzip, enable ext/zlib in php.ini"); + return; + } + flags = PHAR_ENT_COMPRESSED_GZ; + break; + + case PHAR_ENT_COMPRESSED_BZ2: + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress files within archive with bz2, enable ext/bz2 in php.ini"); + return; + } + flags = PHAR_ENT_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with Gzip compression, tar archives cannot compress individual files, use compress() to compress the whole archive"); + return; + } + + if (!pharobj_cancompress(&phar_obj->archive->manifest)) { + if (flags == PHAR_FILE_COMPRESSED_GZ) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress all files as Gzip, some are compressed as bzip2 and cannot be decompressed"); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress all files as Bzip2, some are compressed as gzip and cannot be decompressed"); + } + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + pharobj_set_compression(&phar_obj->archive->manifest, flags); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool Phar::decompressFiles() + * decompress every file + */ +PHP_METHOD(Phar, decompressFiles) +{ + char *error; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + if (!pharobj_cancompress(&phar_obj->archive->manifest)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress all files, some are compressed as bzip2 or gzip and cannot be decompressed"); + return; + } + + if (phar_obj->archive->is_tar) { + RETURN_TRUE; + } else { + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + pharobj_set_compression(&phar_obj->archive->manifest, PHAR_ENT_COMPRESSED_NONE); + } + + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool Phar::copy(string oldfile, string newfile) + * copy a file internal to the phar archive to another new file within the phar + */ +PHP_METHOD(Phar, copy) +{ + char *oldfile, *newfile, *error; + const char *pcr_error; + size_t oldfile_len, newfile_len; + phar_entry_info *oldentry, newentry = {0}, *temp; + int tmp_len = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(newfile_len)) { + RETURN_FALSE; + } + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot copy \"%s\" to \"%s\", phar is read-only", oldfile, newfile); + RETURN_FALSE; + } + + if (oldfile_len >= sizeof(".phar")-1 && !memcmp(oldfile, ".phar", sizeof(".phar")-1)) { + /* can't copy a meta file */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", cannot copy Phar meta-file in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (newfile_len >= sizeof(".phar")-1 && !memcmp(newfile, ".phar", sizeof(".phar")-1)) { + /* can't copy a meta file */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", cannot copy to Phar meta-file in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (!zend_hash_str_exists(&phar_obj->archive->manifest, oldfile, (uint32_t) oldfile_len) || NULL == (oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint32_t) oldfile_len)) || oldentry->is_deleted) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", file does not exist in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, newfile, (uint32_t) newfile_len)) { + if (NULL != (temp = zend_hash_str_find_ptr(&phar_obj->archive->manifest, newfile, (uint32_t) newfile_len)) || !temp->is_deleted) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", file must not already exist in phar %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + } + + tmp_len = (int)newfile_len; + if (phar_path_check(&newfile, &tmp_len, &pcr_error) > pcr_is_ok) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" contains invalid characters %s, cannot be copied from \"%s\" in phar %s", newfile, pcr_error, oldfile, phar_obj->archive->fname); + RETURN_FALSE; + } + newfile_len = tmp_len; + + if (phar_obj->archive->is_persistent) { + if (FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + /* re-populate with copied-on-write entry */ + oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint32_t) oldfile_len); + } + + memcpy((void *) &newentry, oldentry, sizeof(phar_entry_info)); + + if (Z_TYPE(newentry.metadata) != IS_UNDEF) { + zval_copy_ctor(&newentry.metadata); + newentry.metadata_str.s = NULL; + } + + newentry.filename = estrndup(newfile, newfile_len); + newentry.filename_len = (int)newfile_len; + newentry.fp_refcount = 0; + + if (oldentry->fp_type != PHAR_FP) { + if (FAILURE == phar_copy_entry_fp(oldentry, &newentry, &error)) { + efree(newentry.filename); + php_stream_close(newentry.fp); + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + return; + } + } + + zend_hash_str_add_mem(&oldentry->phar->manifest, newfile, newfile_len, &newentry, sizeof(phar_entry_info)); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int Phar::offsetExists(string entry) + * determines whether a file exists in the phar + */ +PHP_METHOD(Phar, offsetExists) +{ + char *fname; + size_t fname_len; + phar_entry_info *entry; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + RETURN_FALSE; + } + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + /* none of these are real files, so they don't exist */ + RETURN_FALSE; + } + RETURN_TRUE; + } else { + if (zend_hash_str_exists(&phar_obj->archive->virtual_dirs, fname, (uint32_t) fname_len)) { + RETURN_TRUE; + } + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int Phar::offsetGet(string entry) + * get a PharFileInfo object for a specific file + */ +PHP_METHOD(Phar, offsetGet) +{ + char *fname, *error; + size_t fname_len; + zval zfname; + phar_entry_info *entry; + zend_string *sfname; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + /* security is 0 here so that we can get a better error message than "entry doesn't exist" */ + if (!(entry = phar_get_entry_info_dir(phar_obj->archive, fname, (int)fname_len, 1, &error, 0))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist%s%s", fname, error?", ":"", error?error:""); + } else { + if (fname_len == sizeof(".phar/stub.php")-1 && !memcmp(fname, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get stub \".phar/stub.php\" directly in phar \"%s\", use getStub", phar_obj->archive->fname); + return; + } + + if (fname_len == sizeof(".phar/alias.txt")-1 && !memcmp(fname, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get alias \".phar/alias.txt\" directly in phar \"%s\", use getAlias", phar_obj->archive->fname); + return; + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot directly get any files or directories in magic \".phar\" directory"); + return; + } + + if (entry->is_temp_dir) { + efree(entry->filename); + efree(entry); + } + + sfname = strpprintf(0, "phar://%s/%s", phar_obj->archive->fname, fname); + ZVAL_NEW_STR(&zfname, sfname); + spl_instantiate_arg_ex1(phar_obj->spl.info_class, return_value, &zfname); + zval_ptr_dtor(&zfname); + } +} +/* }}} */ + +/* {{{ add a file within the phar archive from a string or resource + */ +static void phar_add_file(phar_archive_data **pphar, char *filename, int filename_len, char *cont_str, size_t cont_len, zval *zresource) +{ + char *error; + size_t contents_len; + phar_entry_data *data; + php_stream *contents_file; + + if (filename_len >= (int)sizeof(".phar")-1 && !memcmp(filename, ".phar", sizeof(".phar")-1) && (filename[5] == '/' || filename[5] == '\\' || filename[5] == '\0')) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create any files in magic \".phar\" directory"); + return; + } + + if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, filename, filename_len, "w+b", 0, &error, 1))) { + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created: %s", filename, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created", filename); + } + return; + } else { + if (error) { + efree(error); + } + + if (!data->internal_file->is_dir) { + if (cont_str) { + contents_len = php_stream_write(data->fp, cont_str, cont_len); + if (contents_len != cont_len) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename); + return; + } + } else { + if (!(php_stream_from_zval_no_verify(contents_file, zresource))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename); + return; + } + php_stream_copy_to_stream_ex(contents_file, data->fp, PHP_STREAM_COPY_ALL, &contents_len); + } + + data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize = contents_len; + } + + /* check for copy-on-write */ + if (pphar[0] != data->phar) { + *pphar = data->phar; + } + phar_entry_delref(data); + phar_flush(*pphar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } +} +/* }}} */ + +/* {{{ create a directory within the phar archive + */ +static void phar_mkdir(phar_archive_data **pphar, char *dirname, int dirname_len) +{ + char *error; + phar_entry_data *data; + + if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, dirname, dirname_len, "w+b", 2, &error, 1))) { + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Directory %s does not exist and cannot be created: %s", dirname, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Directory %s does not exist and cannot be created", dirname); + } + + return; + } else { + if (error) { + efree(error); + } + + /* check for copy on write */ + if (data->phar != *pphar) { + *pphar = data->phar; + } + phar_entry_delref(data); + phar_flush(*pphar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } +} +/* }}} */ + +/* {{{ proto int Phar::offsetSet(string entry, string value) + * set the contents of an internal file to those of an external file + */ +PHP_METHOD(Phar, offsetSet) +{ + char *fname, *cont_str = NULL; + size_t fname_len, cont_len; + zval *zresource; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "pr", &fname, &fname_len, &zresource) == FAILURE + && zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &fname, &fname_len, &cont_str, &cont_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (fname_len == sizeof(".phar/stub.php")-1 && !memcmp(fname, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set stub \".phar/stub.php\" directly in phar \"%s\", use setStub", phar_obj->archive->fname); + return; + } + + if (fname_len == sizeof(".phar/alias.txt")-1 && !memcmp(fname, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set alias \".phar/alias.txt\" directly in phar \"%s\", use setAlias", phar_obj->archive->fname); + return; + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set any files or directories in magic \".phar\" directory"); + return; + } + + phar_add_file(&(phar_obj->archive), fname, (int)fname_len, cont_str, cont_len, zresource); +} +/* }}} */ + +/* {{{ proto int Phar::offsetUnset(string entry) + * remove a file from a phar + */ +PHP_METHOD(Phar, offsetUnset) +{ + char *fname, *error; + size_t fname_len; + phar_entry_info *entry; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + return; + } + + if (phar_obj->archive->is_persistent) { + if (FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + /* re-populate entry after copy on write */ + entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) fname_len); + } + entry->is_modified = 0; + entry->is_deleted = 1; + /* we need to "flush" the stream to save the newly deleted file on disk */ + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string Phar::addEmptyDir(string dirname) + * Adds an empty directory to the phar archive + */ +PHP_METHOD(Phar, addEmptyDir) +{ + char *dirname; + size_t dirname_len; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &dirname, &dirname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(dirname_len)) { + RETURN_FALSE; + } + + if (dirname_len >= sizeof(".phar")-1 && !memcmp(dirname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory"); + return; + } + + phar_mkdir(&phar_obj->archive, dirname, (int)dirname_len); +} +/* }}} */ + +/* {{{ proto string Phar::addFile(string filename[, string localname]) + * Adds a file to the archive using the filename, or the second parameter as the name within the archive + */ +PHP_METHOD(Phar, addFile) +{ + char *fname, *localname = NULL; + size_t fname_len, localname_len = 0; + php_stream *resource; + zval zresource; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &fname, &fname_len, &localname, &localname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (!strstr(fname, "://") && php_check_open_basedir(fname)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "phar error: unable to open file \"%s\" to add to phar archive, open_basedir restrictions prevent this", fname); + return; + } + + if (!(resource = php_stream_open_wrapper(fname, "rb", 0, NULL))) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "phar error: unable to open file \"%s\" to add to phar archive", fname); + return; + } + + if (localname) { + fname = localname; + fname_len = localname_len; + } + + php_stream_to_zval(resource, &zresource); + phar_add_file(&(phar_obj->archive), fname, (int)fname_len, NULL, 0, &zresource); + zval_ptr_dtor(&zresource); +} +/* }}} */ + +/* {{{ proto string Phar::addFromString(string localname, string contents) + * Adds a file to the archive using its contents as a string + */ +PHP_METHOD(Phar, addFromString) +{ + char *localname, *cont_str; + size_t localname_len, cont_len; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &localname, &localname_len, &cont_str, &cont_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(localname_len)) { + RETURN_FALSE; + } + + phar_add_file(&(phar_obj->archive), localname, (int)localname_len, cont_str, cont_len, NULL); +} +/* }}} */ + +/* {{{ proto string Phar::getStub() + * Returns the stub at the head of a phar archive as a string. + */ +PHP_METHOD(Phar, getStub) +{ + size_t len; + zend_string *buf; + php_stream *fp; + php_stream_filter *filter = NULL; + phar_entry_info *stub; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->is_tar || phar_obj->archive->is_zip) { + + if (NULL != (stub = zend_hash_str_find_ptr(&(phar_obj->archive->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + if (phar_obj->archive->fp && !phar_obj->archive->is_brandnew && !(stub->flags & PHAR_ENT_COMPRESSION_MASK)) { + fp = phar_obj->archive->fp; + } else { + if (!(fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", 0, NULL))) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "phar error: unable to open phar \"%s\"", phar_obj->archive->fname); + return; + } + if (stub->flags & PHAR_ENT_COMPRESSION_MASK) { + char *filter_name; + + if ((filter_name = phar_decompress_filter(stub, 0)) != NULL) { + filter = php_stream_filter_create(filter_name, NULL, php_stream_is_persistent(fp)); + } else { + filter = NULL; + } + if (!filter) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "phar error: unable to read stub of phar \"%s\" (cannot create %s filter)", phar_obj->archive->fname, phar_decompress_filter(stub, 1)); + return; + } + php_stream_filter_append(&fp->readfilters, filter); + } + } + + if (!fp) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + return; + } + + php_stream_seek(fp, stub->offset_abs, SEEK_SET); + len = stub->uncompressed_filesize; + goto carry_on; + } else { + RETURN_EMPTY_STRING(); + } + } + len = phar_obj->archive->halt_offset; + + if (phar_obj->archive->fp && !phar_obj->archive->is_brandnew) { + fp = phar_obj->archive->fp; + } else { + fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", 0, NULL); + } + + if (!fp) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + return; + } + + php_stream_rewind(fp); +carry_on: + buf = zend_string_alloc(len, 0); + + if (len != php_stream_read(fp, ZSTR_VAL(buf), len)) { + if (fp != phar_obj->archive->fp) { + php_stream_close(fp); + } + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + zend_string_release(buf); + return; + } + + if (filter) { + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + } + + if (fp != phar_obj->archive->fp) { + php_stream_close(fp); + } + + ZSTR_VAL(buf)[len] = '\0'; + ZSTR_LEN(buf) = len; + RETVAL_STR(buf); +} +/* }}}*/ + +/* {{{ proto int Phar::hasMetaData() + * Returns TRUE if the phar has global metadata, FALSE otherwise. + */ +PHP_METHOD(Phar, hasMetadata) +{ + PHAR_ARCHIVE_OBJECT(); + + RETURN_BOOL(Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF); +} +/* }}} */ + +/* {{{ proto int Phar::getMetaData() + * Returns the global metadata of the phar + */ +PHP_METHOD(Phar, getMetadata) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + if (phar_obj->archive->is_persistent) { + char *buf = estrndup((char *) Z_PTR(phar_obj->archive->metadata), phar_obj->archive->metadata_len); + /* assume success, we would have failed before */ + phar_parse_metadata(&buf, return_value, phar_obj->archive->metadata_len); + efree(buf); + } else { + ZVAL_COPY(return_value, &phar_obj->archive->metadata); + } + } +} +/* }}} */ + +/* {{{ proto int Phar::setMetaData(mixed $metadata) + * Sets the global metadata of the phar + */ +PHP_METHOD(Phar, setMetadata) +{ + char *error; + zval *metadata; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &metadata) == FAILURE) { + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + zval_ptr_dtor(&phar_obj->archive->metadata); + ZVAL_UNDEF(&phar_obj->archive->metadata); + } + + ZVAL_COPY(&phar_obj->archive->metadata, metadata); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto int Phar::delMetadata() + * Deletes the global metadata of the phar + */ +PHP_METHOD(Phar, delMetadata) +{ + char *error; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + zval_ptr_dtor(&phar_obj->archive->metadata); + ZVAL_UNDEF(&phar_obj->archive->metadata); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } else { + RETURN_TRUE; + } + + } else { + RETURN_TRUE; + } +} +/* }}} */ + +static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *dest, int dest_len, char **error) /* {{{ */ +{ + php_stream_statbuf ssb; + size_t len; + php_stream *fp; + char *fullpath; + const char *slash; + mode_t mode; + cwd_state new_state; + char *filename; + size_t filename_len; + + if (entry->is_mounted) { + /* silently ignore mounted entries */ + return SUCCESS; + } + + if (entry->filename_len >= sizeof(".phar")-1 && !memcmp(entry->filename, ".phar", sizeof(".phar")-1)) { + return SUCCESS; + } + /* strip .. from path and restrict it to be under dest directory */ + new_state.cwd = (char*)emalloc(2); + new_state.cwd[0] = DEFAULT_SLASH; + new_state.cwd[1] = '\0'; + new_state.cwd_length = 1; + if (virtual_file_ex(&new_state, entry->filename, NULL, CWD_EXPAND) != 0 || + new_state.cwd_length <= 1) { + if (EINVAL == errno && entry->filename_len > 50) { + char *tmp = estrndup(entry->filename, 50); + spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, dest); + efree(tmp); + } else { + spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename); + } + efree(new_state.cwd); + return FAILURE; + } + filename = new_state.cwd + 1; + filename_len = new_state.cwd_length - 1; +#ifdef PHP_WIN32 + /* unixify the path back, otherwise non zip formats might be broken */ + { + int cnt = filename_len; + + do { + if ('\\' == filename[cnt]) { + filename[cnt] = '/'; + } + } while (cnt-- >= 0); + } +#endif + + len = spprintf(&fullpath, 0, "%s/%s", dest, filename); + + if (len >= MAXPATHLEN) { + char *tmp; + /* truncate for error message */ + fullpath[50] = '\0'; + if (entry->filename_len > 50) { + tmp = estrndup(entry->filename, 50); + spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, fullpath); + efree(tmp); + } else { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s...\", extracted filename is too long for filesystem", entry->filename, fullpath); + } + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + if (!len) { + spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + if (php_check_open_basedir(fullpath)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", openbasedir/safe mode restrictions in effect", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + /* let see if the path already exists */ + if (!overwrite && SUCCESS == php_stream_stat_path(fullpath, &ssb)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", path already exists", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + /* perform dirname */ + slash = zend_memrchr(filename, '/', filename_len); + + if (slash) { + fullpath[dest_len + (slash - filename) + 1] = '\0'; + } else { + fullpath[dest_len] = '\0'; + } + + if (FAILURE == php_stream_stat_path(fullpath, &ssb)) { + if (entry->is_dir) { + if (!php_stream_mkdir(fullpath, entry->flags & PHAR_ENT_PERM_MASK, PHP_STREAM_MKDIR_RECURSIVE, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + } else { + if (!php_stream_mkdir(fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + } + } + + if (slash) { + fullpath[dest_len + (slash - filename) + 1] = '/'; + } else { + fullpath[dest_len] = '/'; + } + + filename = NULL; + efree(new_state.cwd); + /* it is a standalone directory, job done */ + if (entry->is_dir) { + efree(fullpath); + return SUCCESS; + } + + fp = php_stream_open_wrapper(fullpath, "w+b", REPORT_ERRORS, NULL); + + if (!fp) { + spprintf(error, 4096, "Cannot extract \"%s\", could not open for writing \"%s\"", entry->filename, fullpath); + efree(fullpath); + return FAILURE; + } + + if (!phar_get_efp(entry, 0)) { + if (FAILURE == phar_open_entry_fp(entry, error, 1)) { + if (error) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to open internal file pointer: %s", entry->filename, fullpath, *error); + } else { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to open internal file pointer", entry->filename, fullpath); + } + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + } + + if (FAILURE == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to seek internal file pointer", entry->filename, fullpath); + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + + if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), fp, entry->uncompressed_filesize, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", copying contents failed", entry->filename, fullpath); + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + + php_stream_close(fp); + mode = (mode_t) entry->flags & PHAR_ENT_PERM_MASK; + + if (FAILURE == VCWD_CHMOD(fullpath, mode)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", setting file permissions failed", entry->filename, fullpath); + efree(fullpath); + return FAILURE; + } + + efree(fullpath); + return SUCCESS; +} +/* }}} */ + +/* {{{ proto bool Phar::extractTo(string pathto[[, mixed files], bool overwrite]) + * Extract one or more file from a phar archive, optionally overwriting existing files + */ +PHP_METHOD(Phar, extractTo) +{ + char *error = NULL; + php_stream *fp; + php_stream_statbuf ssb; + phar_entry_info *entry; + char *pathto, *filename; + size_t pathto_len, filename_len; + int ret, i; + int nelems; + zval *zval_files = NULL; + zend_bool overwrite = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|z!b", &pathto, &pathto_len, &zval_files, &overwrite) == FAILURE) { + return; + } + + fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, NULL); + + if (!fp) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, %s cannot be found", phar_obj->archive->fname); + return; + } + + php_stream_close(fp); + + if (pathto_len < 1) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, extraction path must be non-zero length"); + return; + } + + if (pathto_len >= MAXPATHLEN) { + char *tmp = estrndup(pathto, 50); + /* truncate for error message */ + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Cannot extract to \"%s...\", destination directory is too long for filesystem", tmp); + efree(tmp); + return; + } + + if (php_stream_stat_path(pathto, &ssb) < 0) { + ret = php_stream_mkdir(pathto, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL); + if (!ret) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to create path \"%s\" for extraction", pathto); + return; + } + } else if (!(ssb.sb.st_mode & S_IFDIR)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to use path \"%s\" for extraction, it is a file, must be a directory", pathto); + return; + } + + if (zval_files) { + switch (Z_TYPE_P(zval_files)) { + case IS_NULL: + goto all_files; + case IS_STRING: + filename = Z_STRVAL_P(zval_files); + filename_len = Z_STRLEN_P(zval_files); + break; + case IS_ARRAY: + nelems = zend_hash_num_elements(Z_ARRVAL_P(zval_files)); + if (nelems == 0 ) { + RETURN_FALSE; + } + for (i = 0; i < nelems; i++) { + zval *zval_file; + if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(zval_files), i)) != NULL) { + switch (Z_TYPE_P(zval_file)) { + case IS_STRING: + break; + default: + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, array of filenames to extract contains non-string value"); + return; + } + if (NULL == (entry = zend_hash_find_ptr(&phar_obj->archive->manifest, Z_STR_P(zval_file)))) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", Z_STRVAL_P(zval_file), phar_obj->archive->fname); + } + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error); + efree(error); + return; + } + } + } + RETURN_TRUE; + default: + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, expected a filename (string) or array of filenames"); + return; + } + + if (NULL == (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, filename, filename_len))) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", filename, phar_obj->archive->fname); + return; + } + + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error); + efree(error); + return; + } + } else { + phar_archive_data *phar; +all_files: + phar = phar_obj->archive; + /* Extract all files */ + if (!zend_hash_num_elements(&(phar->manifest))) { + RETURN_TRUE; + } + + ZEND_HASH_FOREACH_PTR(&phar->manifest, entry) { + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar->fname, error); + efree(error); + return; + } + } ZEND_HASH_FOREACH_END(); + } + RETURN_TRUE; +} +/* }}} */ + + +/* {{{ proto void PharFileInfo::__construct(string entry) + * Construct a Phar entry object + */ +PHP_METHOD(PharFileInfo, __construct) +{ + char *fname, *arch, *entry, *error; + size_t fname_len; + int arch_len, entry_len; + phar_entry_object *entry_obj; + phar_entry_info *entry_info; + phar_archive_data *phar_data; + zval *zobj = getThis(), arg1; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + + entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (entry_obj->entry) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot call constructor twice"); + return; + } + + if (fname_len < 7 || memcmp(fname, "phar://", 7) || phar_split_fname(fname, (int)fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0) == FAILURE) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "'%s' is not a valid phar archive URL (must have at least phar://filename.phar)", fname); + return; + } + + if (phar_open_from_filename(arch, arch_len, NULL, 0, REPORT_ERRORS, &phar_data, &error) == FAILURE) { + efree(arch); + efree(entry); + if (error) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot open phar file '%s': %s", fname, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot open phar file '%s'", fname); + } + return; + } + + if ((entry_info = phar_get_entry_info_dir(phar_data, entry, entry_len, 1, &error, 1)) == NULL) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot access phar file entry '%s' in archive '%s'%s%s", entry, arch, error ? ", " : "", error ? error : ""); + efree(arch); + efree(entry); + return; + } + + efree(arch); + efree(entry); + + entry_obj->entry = entry_info; + + ZVAL_STRINGL(&arg1, fname, fname_len); + + zend_call_method_with_1_params(zobj, Z_OBJCE_P(zobj), + &spl_ce_SplFileInfo->constructor, "__construct", NULL, &arg1); + + zval_ptr_dtor(&arg1); +} +/* }}} */ + +#define PHAR_ENTRY_OBJECT() \ + zval *zobj = getThis(); \ + phar_entry_object *entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); \ + if (!entry_obj->entry) { \ + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Cannot call method on an uninitialized PharFileInfo object"); \ + return; \ + } + +/* {{{ proto void PharFileInfo::__destruct() + * clean up directory-based entry objects + */ +PHP_METHOD(PharFileInfo, __destruct) +{ + zval *zobj = getThis(); + phar_entry_object *entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (entry_obj->entry && entry_obj->entry->is_temp_dir) { + if (entry_obj->entry->filename) { + efree(entry_obj->entry->filename); + entry_obj->entry->filename = NULL; + } + + efree(entry_obj->entry); + entry_obj->entry = NULL; + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getCompressedSize() + * Returns the compressed size + */ +PHP_METHOD(PharFileInfo, getCompressedSize) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(entry_obj->entry->compressed_filesize); +} +/* }}} */ + +/* {{{ proto bool PharFileInfo::isCompressed([int compression_type]) + * Returns whether the entry is compressed, and whether it is compressed with Phar::GZ or Phar::BZ2 if specified + */ +PHP_METHOD(PharFileInfo, isCompressed) +{ + /* a number that is not Phar::GZ or Phar::BZ2 */ + zend_long method = 9021976; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &method) == FAILURE) { + return; + } + + switch (method) { + case 9021976: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSION_MASK); + case PHAR_ENT_COMPRESSED_GZ: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ); + case PHAR_ENT_COMPRESSED_BZ2: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2); + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Unknown compression type specified"); \ + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getCRC32() + * Returns CRC32 code or throws an exception if not CRC checked + */ +PHP_METHOD(PharFileInfo, getCRC32) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, does not have a CRC"); \ + return; + } + + if (entry_obj->entry->is_crc_checked) { + RETURN_LONG(entry_obj->entry->crc32); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry was not CRC checked"); \ + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::isCRCChecked() + * Returns whether file entry is CRC checked + */ +PHP_METHOD(PharFileInfo, isCRCChecked) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(entry_obj->entry->is_crc_checked); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getPharFlags() + * Returns the Phar file entry flags + */ +PHP_METHOD(PharFileInfo, getPharFlags) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(entry_obj->entry->flags & ~(PHAR_ENT_PERM_MASK|PHAR_ENT_COMPRESSION_MASK)); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::chmod() + * set the file permissions for the Phar. This only allows setting execution bit, read/write + */ +PHP_METHOD(PharFileInfo, chmod) +{ + char *error; + zend_long perms; + PHAR_ENTRY_OBJECT(); + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry \"%s\" is a temporary directory (not an actual entry in the archive), cannot chmod", entry_obj->entry->filename); \ + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Cannot modify permissions for file \"%s\" in phar \"%s\", write operations are prohibited", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &perms) == FAILURE) { + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + /* clear permissions */ + entry_obj->entry->flags &= ~PHAR_ENT_PERM_MASK; + perms &= 0777; + entry_obj->entry->flags |= perms; + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + + /* hackish cache in php_stat needs to be cleared */ + /* if this code fails to work, check main/streams/streams.c, _php_stream_stat_path */ + if (BG(CurrentLStatFile)) { + efree(BG(CurrentLStatFile)); + } + + if (BG(CurrentStatFile)) { + efree(BG(CurrentStatFile)); + } + + BG(CurrentLStatFile) = NULL; + BG(CurrentStatFile) = NULL; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::hasMetaData() + * Returns the metadata of the entry + */ +PHP_METHOD(PharFileInfo, hasMetadata) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getMetaData() + * Returns the metadata of the entry + */ +PHP_METHOD(PharFileInfo, getMetadata) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + if (entry_obj->entry->is_persistent) { + char *buf = estrndup((char *) Z_PTR(entry_obj->entry->metadata), entry_obj->entry->metadata_len); + /* assume success, we would have failed before */ + phar_parse_metadata(&buf, return_value, entry_obj->entry->metadata_len); + efree(buf); + } else { + ZVAL_COPY(return_value, &entry_obj->entry->metadata); + } + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::setMetaData(mixed $metadata) + * Sets the metadata of the entry + */ +PHP_METHOD(PharFileInfo, setMetadata) +{ + char *error; + zval *metadata; + + PHAR_ENTRY_OBJECT(); + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a temporary directory (not an actual entry in the archive), cannot set metadata"); \ + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &metadata) == FAILURE) { + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + zval_ptr_dtor(&entry_obj->entry->metadata); + ZVAL_UNDEF(&entry_obj->entry->metadata); + } + + ZVAL_COPY(&entry_obj->entry->metadata, metadata); + + entry_obj->entry->is_modified = 1; + entry_obj->entry->phar->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool PharFileInfo::delMetaData() + * Deletes the metadata of the entry + */ +PHP_METHOD(PharFileInfo, delMetadata) +{ + char *error; + + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a temporary directory (not an actual entry in the archive), cannot delete metadata"); \ + return; + } + + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + zval_ptr_dtor(&entry_obj->entry->metadata); + ZVAL_UNDEF(&entry_obj->entry->metadata); + entry_obj->entry->is_modified = 1; + entry_obj->entry->phar->is_modified = 1; + + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } else { + RETURN_TRUE; + } + + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto string PharFileInfo::getContent() + * return the complete file contents of the entry (like file_get_contents) + */ +PHP_METHOD(PharFileInfo, getContent) +{ + char *error; + php_stream *fp; + phar_entry_info *link; + zend_string *str; + + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents, \"%s\" in phar \"%s\" is a directory", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + link = phar_get_link_source(entry_obj->entry); + + if (!link) { + link = entry_obj->entry; + } + + if (SUCCESS != phar_open_entry_fp(link, &error, 0)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents, \"%s\" in phar \"%s\": %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + + if (!(fp = phar_get_efp(link, 0))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents of \"%s\" in phar \"%s\"", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + phar_seek_efp(link, 0, SEEK_SET, 0, 0); + str = php_stream_copy_to_mem(fp, link->uncompressed_filesize, 0); + if (str) { + RETURN_STR(str); + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::compress(int compression_type) + * Instructs the Phar class to compress the current file using zlib or bzip2 compression + */ +PHP_METHOD(PharFileInfo, compress) +{ + zend_long method; + char *error; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &method) == FAILURE) { + return; + } + + if (entry_obj->entry->is_tar) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with Gzip compression, not possible with tar-based phar archives"); + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, cannot set compression"); \ + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + if (entry_obj->entry->is_deleted) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress deleted file"); + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) { + RETURN_TRUE; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) != 0) { + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with gzip compression, file is already compressed with bzip2 compression and bz2 extension is not enabled, cannot decompress"); + return; + } + + /* decompress this file indirectly */ + if (SUCCESS != phar_open_entry_fp(entry_obj->entry, &error, 1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot decompress bzip2-compressed file \"%s\" in phar \"%s\" in order to compress with gzip: %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with gzip compression, zlib extension is not enabled"); + return; + } + + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->flags |= PHAR_ENT_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) { + RETURN_TRUE; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) != 0) { + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with bzip2 compression, file is already compressed with gzip compression and zlib extension is not enabled, cannot decompress"); + return; + } + + /* decompress this file indirectly */ + if (SUCCESS != phar_open_entry_fp(entry_obj->entry, &error, 1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot decompress gzip-compressed file \"%s\" in phar \"%s\" in order to compress with bzip2: %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with bzip2 compression, bz2 extension is not enabled"); + return; + } + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->flags |= PHAR_ENT_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Unknown compression type specified"); \ + } + + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int PharFileInfo::decompress() + * Instructs the Phar class to decompress the current file + */ +PHP_METHOD(PharFileInfo, decompress) +{ + char *error; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, cannot set compression"); \ + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSION_MASK) == 0) { + RETURN_TRUE; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot decompress"); + return; + } + + if (entry_obj->entry->is_deleted) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress deleted file"); + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) != 0 && !PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress Gzip-compressed file, zlib extension is not enabled"); + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) != 0 && !PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress Bzip2-compressed file, bz2 extension is not enabled"); + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + if (!entry_obj->entry->fp) { + if (FAILURE == phar_open_archive_fp(entry_obj->entry->phar)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot decompress entry \"%s\", phar error: Cannot open phar archive \"%s\" for reading", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + entry_obj->entry->fp_type = PHAR_FP; + } + + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ phar methods */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_createDS, 0, 0, 0) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, webindex) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_cancompress, 0, 0, 0) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_isvalidpharfilename, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, executable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_loadPhar, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mapPhar, 0, 0, 0) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mount, 0, 0, 2) + ZEND_ARG_INFO(0, inphar) + ZEND_ARG_INFO(0, externalfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mungServer, 0, 0, 1) + ZEND_ARG_INFO(0, munglist) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_webPhar, 0, 0, 0) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, f404) + ZEND_ARG_INFO(0, mimetypes) + ZEND_ARG_INFO(0, rewrites) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_running, 0, 0, 0) + ZEND_ARG_INFO(0, retphar) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_ua, 0, 0, 1) + ZEND_ARG_INFO(0, archive) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_build, 0, 0, 1) + ZEND_ARG_INFO(0, iterator) + ZEND_ARG_INFO(0, base_directory) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_conv, 0, 0, 0) + ZEND_ARG_INFO(0, format) + ZEND_ARG_INFO(0, compression_type) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_comps, 0, 0, 1) + ZEND_ARG_INFO(0, compression_type) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_decomp, 0, 0, 0) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_comp, 0, 0, 1) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_compo, 0, 0, 0) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_copy, 0, 0, 2) + ZEND_ARG_INFO(0, newfile) + ZEND_ARG_INFO(0, oldfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_delete, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_fromdir, 0, 0, 1) + ZEND_ARG_INFO(0, base_dir) + ZEND_ARG_INFO(0, regex) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetExists, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetSet, 0, 0, 2) + ZEND_ARG_INFO(0, entry) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setAlias, 0, 0, 1) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setMetadata, 0, 0, 1) + ZEND_ARG_INFO(0, metadata) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setSigAlgo, 0, 0, 1) + ZEND_ARG_INFO(0, algorithm) + ZEND_ARG_INFO(0, privatekey) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setStub, 0, 0, 1) + ZEND_ARG_INFO(0, newstub) + ZEND_ARG_INFO(0, maxlen) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_emptydir, 0, 0, 0) + ZEND_ARG_INFO(0, dirname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_extract, 0, 0, 1) + ZEND_ARG_INFO(0, pathto) + ZEND_ARG_INFO(0, files) + ZEND_ARG_INFO(0, overwrite) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_addfile, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, localname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_fromstring, 0, 0, 1) + ZEND_ARG_INFO(0, localname) + ZEND_ARG_INFO(0, contents) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_isff, 0, 0, 1) + ZEND_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_phar__void, 0) +ZEND_END_ARG_INFO() + + +zend_function_entry php_archive_methods[] = { + PHP_ME(Phar, __construct, arginfo_phar___construct, ZEND_ACC_PUBLIC) + PHP_ME(Phar, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addEmptyDir, arginfo_phar_emptydir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFile, arginfo_phar_addfile, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFromString, arginfo_phar_fromstring, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromDirectory, arginfo_phar_fromdir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromIterator, arginfo_phar_build, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compressFiles, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompressFiles, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compress, arginfo_phar_comps, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompress, arginfo_phar_decomp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToExecutable, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToData, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, copy, arginfo_phar_copy, ZEND_ACC_PUBLIC) + PHP_ME(Phar, count, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delete, arginfo_phar_delete, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, extractTo, arginfo_phar_extract, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getAlias, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getPath, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getModified, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getSignature, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getStub, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getVersion, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isCompressed, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isFileFormat, arginfo_phar_isff, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isWritable, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetExists, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetGet, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetSet, arginfo_phar_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetUnset, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setAlias, arginfo_phar_setAlias, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setSignatureAlgorithm, arginfo_phar_setSigAlgo, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setStub, arginfo_phar_setStub, ZEND_ACC_PUBLIC) + PHP_ME(Phar, startBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, stopBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + /* static member functions */ + PHP_ME(Phar, apiVersion, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canCompress, arginfo_phar_cancompress, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canWrite, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, createDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedCompression,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedSignatures,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, interceptFileFuncs, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, isValidPharFilename, arginfo_phar_isvalidpharfilename, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, loadPhar, arginfo_phar_loadPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mapPhar, arginfo_phar_mapPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, running, arginfo_phar_running, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mount, arginfo_phar_mount, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mungServer, arginfo_phar_mungServer, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, unlinkArchive, arginfo_phar_ua, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, webPhar, arginfo_phar_webPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_FE_END +}; + + +ZEND_BEGIN_ARG_INFO_EX(arginfo_data___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +zend_function_entry php_data_methods[] = { + PHP_ME(Phar, __construct, arginfo_data___construct, ZEND_ACC_PUBLIC) + PHP_ME(Phar, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addEmptyDir, arginfo_phar_emptydir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFile, arginfo_phar_addfile, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFromString, arginfo_phar_fromstring, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromDirectory, arginfo_phar_fromdir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromIterator, arginfo_phar_build, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compressFiles, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompressFiles, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compress, arginfo_phar_comps, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompress, arginfo_phar_decomp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToExecutable, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToData, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, copy, arginfo_phar_copy, ZEND_ACC_PUBLIC) + PHP_ME(Phar, count, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delete, arginfo_phar_delete, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, extractTo, arginfo_phar_extract, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getAlias, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getPath, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getModified, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getSignature, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getStub, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getVersion, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isCompressed, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isFileFormat, arginfo_phar_isff, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isWritable, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetExists, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetGet, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetSet, arginfo_phar_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetUnset, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setAlias, arginfo_phar_setAlias, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setSignatureAlgorithm, arginfo_phar_setSigAlgo, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setStub, arginfo_phar_setStub, ZEND_ACC_PUBLIC) + PHP_ME(Phar, startBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, stopBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + /* static member functions */ + PHP_ME(Phar, apiVersion, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canCompress, arginfo_phar_cancompress, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canWrite, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, createDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedCompression,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedSignatures,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, interceptFileFuncs, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, isValidPharFilename, arginfo_phar_isvalidpharfilename, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, loadPhar, arginfo_phar_loadPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mapPhar, arginfo_phar_mapPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, running, arginfo_phar_running, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mount, arginfo_phar_mount, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mungServer, arginfo_phar_mungServer, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, unlinkArchive, arginfo_phar_ua, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, webPhar, arginfo_phar_webPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry_chmod, 0, 0, 1) + ZEND_ARG_INFO(0, perms) +ZEND_END_ARG_INFO() + +zend_function_entry php_entry_methods[] = { + PHP_ME(PharFileInfo, __construct, arginfo_entry___construct, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, chmod, arginfo_entry_chmod, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, compress, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, decompress, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getCompressedSize, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getCRC32, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getContent, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getPharFlags, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, isCompressed, arginfo_phar_compo, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, isCRCChecked, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +zend_function_entry phar_exception_methods[] = { + PHP_FE_END +}; +/* }}} */ + +#define REGISTER_PHAR_CLASS_CONST_LONG(class_name, const_name, value) \ + zend_declare_class_constant_long(class_name, const_name, sizeof(const_name)-1, (zend_long)value); + +void phar_object_init(void) /* {{{ */ +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "PharException", phar_exception_methods); + phar_ce_PharException = zend_register_internal_class_ex(&ce, zend_ce_exception); + + INIT_CLASS_ENTRY(ce, "Phar", php_archive_methods); + phar_ce_archive = zend_register_internal_class_ex(&ce, spl_ce_RecursiveDirectoryIterator); + + zend_class_implements(phar_ce_archive, 2, zend_ce_countable, zend_ce_arrayaccess); + + INIT_CLASS_ENTRY(ce, "PharData", php_data_methods); + phar_ce_data = zend_register_internal_class_ex(&ce, spl_ce_RecursiveDirectoryIterator); + + zend_class_implements(phar_ce_data, 2, zend_ce_countable, zend_ce_arrayaccess); + + INIT_CLASS_ENTRY(ce, "PharFileInfo", php_entry_methods); + phar_ce_entry = zend_register_internal_class_ex(&ce, spl_ce_SplFileInfo); + + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "BZ2", PHAR_ENT_COMPRESSED_BZ2) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "GZ", PHAR_ENT_COMPRESSED_GZ) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "NONE", PHAR_ENT_COMPRESSED_NONE) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHAR", PHAR_FORMAT_PHAR) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "TAR", PHAR_FORMAT_TAR) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "ZIP", PHAR_FORMAT_ZIP) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "COMPRESSED", PHAR_ENT_COMPRESSION_MASK) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHP", PHAR_MIME_PHP) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHPS", PHAR_MIME_PHPS) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "MD5", PHAR_SIG_MD5) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "OPENSSL", PHAR_SIG_OPENSSL) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA1", PHAR_SIG_SHA1) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA256", PHAR_SIG_SHA256) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA512", PHAR_SIG_SHA512) +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/phar/tests/bug79082.phpt b/ext/phar/tests/bug79082.phpt new file mode 100644 index 0000000000000..ca453d1b57bcf --- /dev/null +++ b/ext/phar/tests/bug79082.phpt @@ -0,0 +1,52 @@ +--TEST-- +Phar: Bug #79082: Files added to tar with Phar::buildFromIterator have all-access permissions +--SKIPIF-- + +--FILE-- + 'tar', Phar::ZIP => 'zip'] as $mode => $ext) { + clearstatcache(); + $phar = new PharData(__DIR__ . '/test79082.' . $ext, null, null, $mode); + $phar->buildFromIterator(new \RecursiveDirectoryIterator(__DIR__ . '/test79082', \FilesystemIterator::SKIP_DOTS), __DIR__ . '/test79082'); + $phar->extractTo(__DIR__); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile')['mode'])); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile2')['mode'])); + unlink(__DIR__ . '/test79082-testfile'); + unlink(__DIR__ . '/test79082-testfile2'); +} +foreach([Phar::TAR => 'tar', Phar::ZIP => 'zip'] as $mode => $ext) { + clearstatcache(); + $phar = new PharData(__DIR__ . '/test79082-d.' . $ext, null, null, $mode); + $phar->buildFromDirectory(__DIR__ . '/test79082'); + $phar->extractTo(__DIR__); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile')['mode'])); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile2')['mode'])); + unlink(__DIR__ . '/test79082-testfile'); + unlink(__DIR__ . '/test79082-testfile2'); +} +?> +--CLEAN-- + +--EXPECT-- +string(2) "22" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" diff --git a/ext/phar/tests/test79082/test79082-testfile b/ext/phar/tests/test79082/test79082-testfile new file mode 100644 index 0000000000000..9daeafb9864cf --- /dev/null +++ b/ext/phar/tests/test79082/test79082-testfile @@ -0,0 +1 @@ +test diff --git a/ext/phar/tests/test79082/test79082-testfile2 b/ext/phar/tests/test79082/test79082-testfile2 new file mode 100644 index 0000000000000..9daeafb9864cf --- /dev/null +++ b/ext/phar/tests/test79082/test79082-testfile2 @@ -0,0 +1 @@ +test From 04b06e9d7ea5a1d1c70e733868272e569bdc3582 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:07 +0000 Subject: [PATCH 16/25] commit patch 20403927 --- ext/exif/exif.c | 7 ++++++- ext/exif/exif.c.orig | 3 ++- ext/exif/tests/bug79282.phpt | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 ext/exif/tests/bug79282.phpt diff --git a/ext/exif/exif.c b/ext/exif/exif.c index ecc9ddf59099b..62e47ed7ff3aa 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -3635,6 +3635,11 @@ static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo, char *CharBuf, { unsigned exif_value_2a, offset_of_ifd; + if (length < 2) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Missing TIFF alignment marker"); + return; + } + /* set the thumbnail stuff to nothing so we can test to see if they get set up */ if (memcmp(CharBuf, "II", 2) == 0) { ImageInfo->motorola_intel = 0; @@ -3787,7 +3792,7 @@ static int exif_scan_JPEG_header(image_info_type *ImageInfo) return FALSE; } - sn = exif_file_sections_add(ImageInfo, marker, itemlen+1, NULL); + sn = exif_file_sections_add(ImageInfo, marker, itemlen, NULL); Data = ImageInfo->file.list[sn].data; /* Store first two pre-read bytes. */ diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig index 8fb05f15002b4..20732392fe6c3 100644 --- a/ext/exif/exif.c.orig +++ b/ext/exif/exif.c.orig @@ -3143,7 +3143,8 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu continue; if (maker_note->model && (!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) continue; - if (maker_note->id_string && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) + if (maker_note->id_string && value_len >= maker_note->id_string_len + && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) continue; break; } diff --git a/ext/exif/tests/bug79282.phpt b/ext/exif/tests/bug79282.phpt new file mode 100644 index 0000000000000..7b7e36565791f --- /dev/null +++ b/ext/exif/tests/bug79282.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #79282: Use-of-uninitialized-value in exif +--FILE-- + +--EXPECTF-- +Warning: exif_read_data(): Invalid TIFF alignment marker in %s on line %d + +Warning: exif_read_data(): File structure corrupted in %s on line %d + +Warning: exif_read_data(): Invalid JPEG file in %s on line %d +bool(false) From 29cb29b0633350694261dd0c601b0660585234c9 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:09 +0000 Subject: [PATCH 17/25] commit patch 22644178 --- ext/standard/url.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/url.c b/ext/standard/url.c index a3a19a2b22c6f..24c0f1ae2662b 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -680,7 +680,7 @@ PHP_FUNCTION(get_headers) php_stream_context *context; ZEND_PARSE_PARAMETERS_START(1, 3) - Z_PARAM_STRING(url, url_len) + Z_PARAM_PATH(url, url_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(format) Z_PARAM_RESOURCE_EX(zcontext, 1, 0) From 02e8be4dd79470415df37a5982276f22c53f57f5 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:11 +0000 Subject: [PATCH 18/25] commit patch 26680495 --- main/rfc1867.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main/rfc1867.c b/main/rfc1867.c index 74f47f63a2ede..341049108ce8b 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -692,7 +692,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ char *boundary, *s = NULL, *boundary_end = NULL, *start_arr = NULL, *array_index = NULL; char *lbuf = NULL, *abuf = NULL; zend_string *temp_filename = NULL; - int boundary_len = 0, cancel_upload = 0, is_arr_upload = 0, array_len = 0; + int boundary_len = 0, cancel_upload = 0, is_arr_upload = 0; + size_t array_len = 0; int64_t total_bytes = 0, max_file_size = 0; int skip_upload = 0, anonindex = 0, is_anonymous; HashTable *uploaded_files = NULL; @@ -1126,7 +1127,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']'); if (is_arr_upload) { - array_len = (int)strlen(start_arr); + array_len = strlen(start_arr); if (array_index) { efree(array_index); } From d7fc3ffba86d5895bff60390dfc3d6ad0a2614ae Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:13 +0000 Subject: [PATCH 19/25] commit patch 20995178 --- ext/standard/url.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/url.c b/ext/standard/url.c index 24c0f1ae2662b..13938fa2fb9f6 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -559,7 +559,7 @@ PHPAPI size_t php_url_decode(char *str, size_t len) #ifndef CHARSET_EBCDIC *dest = (char) php_htoi(data + 1); #else - *dest = os_toebcdic[(char) php_htoi(data + 1)]; + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; #endif data += 2; len -= 2; @@ -651,7 +651,7 @@ PHPAPI size_t php_raw_url_decode(char *str, size_t len) #ifndef CHARSET_EBCDIC *dest = (char) php_htoi(data + 1); #else - *dest = os_toebcdic[(char) php_htoi(data + 1)]; + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; #endif data += 2; len -= 2; From 7f9cb4d18c9790b22a0327d2b385e4380870fb70 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:15 +0000 Subject: [PATCH 20/25] commit patch 25580839 --- ext/standard/tests/strings/url_t.phpt | 6 +- ext/standard/tests/url/bug77423.phpt | 30 + .../tests/url/parse_url_basic_001.phpt | 6 +- .../tests/url/parse_url_basic_001.phpt.orig | 885 +++++++++++++++++ .../tests/url/parse_url_basic_003.phpt | 2 +- .../tests/url/parse_url_basic_005.phpt | 2 +- .../tests/url/parse_url_unterminated.phpt | 6 +- .../url/parse_url_unterminated.phpt.orig | 887 ++++++++++++++++++ ext/standard/url.c | 21 + 9 files changed, 1831 insertions(+), 14 deletions(-) create mode 100644 ext/standard/tests/url/bug77423.phpt create mode 100644 ext/standard/tests/url/parse_url_basic_001.phpt.orig create mode 100644 ext/standard/tests/url/parse_url_unterminated.phpt.orig diff --git a/ext/standard/tests/strings/url_t.phpt b/ext/standard/tests/strings/url_t.phpt index e172061ec2549..80e164a08ed75 100644 --- a/ext/standard/tests/strings/url_t.phpt +++ b/ext/standard/tests/strings/url_t.phpt @@ -575,15 +575,13 @@ $sample_urls = array ( string(16) "some_page_ref123" } ---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(6) { ["scheme"]=> string(4) "http" ["host"]=> - string(11) "www.php.net" + string(26) "secret@hideout@www.php.net" ["port"]=> int(80) - ["user"]=> - string(14) "secret@hideout" ["path"]=> string(10) "/index.php" ["query"]=> diff --git a/ext/standard/tests/url/bug77423.phpt b/ext/standard/tests/url/bug77423.phpt new file mode 100644 index 0000000000000..be03fe95e24e2 --- /dev/null +++ b/ext/standard/tests/url/bug77423.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug #77423 (parse_url() will deliver a wrong host to user) +--FILE-- + +--EXPECT-- +bool(false) +array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(19) "php.net\@aliyun.com" + ["path"]=> + string(7) "/aaa.do" +} +bool(false) +array(2) { + ["scheme"]=> + string(5) "https" + ["host"]=> + string(26) "example.com\uFF03@bing.com" +} diff --git a/ext/standard/tests/url/parse_url_basic_001.phpt b/ext/standard/tests/url/parse_url_basic_001.phpt index bd4bea5b9c913..3a92fbbaa06dc 100644 --- a/ext/standard/tests/url/parse_url_basic_001.phpt +++ b/ext/standard/tests/url/parse_url_basic_001.phpt @@ -507,15 +507,13 @@ echo "Done"; string(16) "some_page_ref123" } ---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(6) { ["scheme"]=> string(4) "http" ["host"]=> - string(11) "www.php.net" + string(26) "secret@hideout@www.php.net" ["port"]=> int(80) - ["user"]=> - string(14) "secret@hideout" ["path"]=> string(10) "/index.php" ["query"]=> diff --git a/ext/standard/tests/url/parse_url_basic_001.phpt.orig b/ext/standard/tests/url/parse_url_basic_001.phpt.orig new file mode 100644 index 0000000000000..bd4bea5b9c913 --- /dev/null +++ b/ext/standard/tests/url/parse_url_basic_001.phpt.orig @@ -0,0 +1,885 @@ +--TEST-- +Test parse_url() function: Parse a load of URLs without specifying the component +--FILE-- + $url: "; + var_dump(parse_url($url)); +} + +echo "Done"; +?> +--EXPECTF-- + +--> 64.246.30.37: array(1) { + ["path"]=> + string(12) "64.246.30.37" +} + +--> http://64.246.30.37: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(12) "64.246.30.37" +} + +--> http://64.246.30.37/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(12) "64.246.30.37" + ["path"]=> + string(1) "/" +} + +--> 64.246.30.37/: array(1) { + ["path"]=> + string(13) "64.246.30.37/" +} + +--> 64.246.30.37:80/: array(3) { + ["host"]=> + string(12) "64.246.30.37" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> php.net: array(1) { + ["path"]=> + string(7) "php.net" +} + +--> php.net/: array(1) { + ["path"]=> + string(8) "php.net/" +} + +--> http://php.net: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "php.net" +} + +--> http://php.net/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "php.net" + ["path"]=> + string(1) "/" +} + +--> www.php.net: array(1) { + ["path"]=> + string(11) "www.php.net" +} + +--> www.php.net/: array(1) { + ["path"]=> + string(12) "www.php.net/" +} + +--> http://www.php.net: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" +} + +--> http://www.php.net/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" +} + +--> www.php.net:80: array(2) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) +} + +--> http://www.php.net:80: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) +} + +--> http://www.php.net:80/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net/index.php: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" +} + +--> www.php.net/?: array(1) { + ["path"]=> + string(12) "www.php.net/" +} + +--> www.php.net:80/?: array(3) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net/?: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/?: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/foo/bar/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(18) "/foo/bar/index.php" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php?lots=1&of=2¶meters=3&too=4&here=5: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" + ["query"]=> + string(37) "lots=1&of=2¶meters=3&too=4&here=5" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(45) "/this/is/a/very/deep/directory/structure/and/" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" +} + +--> http://www.php.net:80/this/../a/../deep/directory: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(28) "/this/../a/../deep/directory" +} + +--> http://www.php.net:80/this/../a/../deep/directory/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(29) "/this/../a/../deep/directory/" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/../file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(42) "/this/is/a/very/deep/directory/../file.php" +} + +--> http://www.php.net:80/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/index.php?: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/#foo: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["fragment"]=> + string(3) "foo" +} + +--> http://www.php.net:80/?#: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/?test=1: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["query"]=> + string(6) "test=1" +} + +--> http://www.php.net/?test=1&: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" + ["query"]=> + string(7) "test=1&" +} + +--> http://www.php.net:80/?&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["query"]=> + string(1) "&" +} + +--> http://www.php.net:80/index.php?test=1&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(7) "test=1&" +} + +--> http://www.php.net/index.php?&: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(1) "&" +} + +--> http://www.php.net:80/index.php?foo&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(4) "foo&" +} + +--> http://www.php.net/index.php?&foo: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(4) "&foo" +} + +--> http://www.php.net:80/index.php?test=1&test2=char&test3=mixesCI: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" +} + +--> www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(5) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(6) "secret" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["user"]=> + string(6) "secret" + ["pass"]=> + string(0) "" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(8) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(0) "" + ["pass"]=> + string(7) "hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["user"]=> + string(6) "secret" + ["pass"]=> + string(7) "hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(14) "secret@hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(8) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(6) "secret" + ["pass"]=> + string(7) "hid:out" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> nntp://news.php.net: array(2) { + ["scheme"]=> + string(4) "nntp" + ["host"]=> + string(12) "news.php.net" +} + +--> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz: array(3) { + ["scheme"]=> + string(3) "ftp" + ["host"]=> + string(11) "ftp.gnu.org" + ["path"]=> + string(22) "/gnu/glic/glibc.tar.gz" +} + +--> zlib:http://foo@bar: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(14) "http://foo@bar" +} + +--> zlib:filename.txt: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(12) "filename.txt" +} + +--> zlib:/path/to/my/file/file.txt: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(25) "/path/to/my/file/file.txt" +} + +--> foo://foo@bar: array(3) { + ["scheme"]=> + string(3) "foo" + ["host"]=> + string(3) "bar" + ["user"]=> + string(3) "foo" +} + +--> mailto:me@mydomain.com: array(2) { + ["scheme"]=> + string(6) "mailto" + ["path"]=> + string(15) "me@mydomain.com" +} + +--> /foo.php?a=b&c=d: array(2) { + ["path"]=> + string(8) "/foo.php" + ["query"]=> + string(7) "a=b&c=d" +} + +--> foo.php?a=b&c=d: array(2) { + ["path"]=> + string(7) "foo.php" + ["query"]=> + string(7) "a=b&c=d" +} + +--> http://user:passwd@www.example.com:8080?bar=1&boom=0: array(6) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(15) "www.example.com" + ["port"]=> + int(8080) + ["user"]=> + string(4) "user" + ["pass"]=> + string(6) "passwd" + ["query"]=> + string(12) "bar=1&boom=0" +} + +--> http://user_me-you:my_pas-word@www.example.com:8080?bar=1&boom=0: array(6) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(15) "www.example.com" + ["port"]=> + int(8080) + ["user"]=> + string(11) "user_me-you" + ["pass"]=> + string(11) "my_pas-word" + ["query"]=> + string(12) "bar=1&boom=0" +} + +--> file:///path/to/file: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(13) "/path/to/file" +} + +--> file://path/to/file: array(3) { + ["scheme"]=> + string(4) "file" + ["host"]=> + string(4) "path" + ["path"]=> + string(8) "/to/file" +} + +--> file:/path/to/file: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(13) "/path/to/file" +} + +--> http://1.2.3.4:/abc.asp?a=1&b=2: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "1.2.3.4" + ["path"]=> + string(8) "/abc.asp" + ["query"]=> + string(7) "a=1&b=2" +} + +--> http://foo.com#bar: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "foo.com" + ["fragment"]=> + string(3) "bar" +} + +--> scheme:: array(1) { + ["scheme"]=> + string(6) "scheme" +} + +--> foo+bar://baz@bang/bla: array(4) { + ["scheme"]=> + string(7) "foo+bar" + ["host"]=> + string(4) "bang" + ["user"]=> + string(3) "baz" + ["path"]=> + string(4) "/bla" +} + +--> gg:9130731: array(2) { + ["scheme"]=> + string(2) "gg" + ["path"]=> + string(7) "9130731" +} + +--> http://user:@pass@host/path?argument?value#etc: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(4) "host" + ["user"]=> + string(4) "user" + ["pass"]=> + string(5) "@pass" + ["path"]=> + string(5) "/path" + ["query"]=> + string(14) "argument?value" + ["fragment"]=> + string(3) "etc" +} + +--> http://10.10.10.10/:80: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "10.10.10.10" + ["path"]=> + string(4) "/:80" +} + +--> http://x:?: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) "x" +} + +--> x:blah.com: array(2) { + ["scheme"]=> + string(1) "x" + ["path"]=> + string(8) "blah.com" +} + +--> x:/blah.com: array(2) { + ["scheme"]=> + string(1) "x" + ["path"]=> + string(9) "/blah.com" +} + +--> x://::abc/?: bool(false) + +--> http://::?: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) ":" +} + +--> http://::#: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) ":" +} + +--> x://::6.5: array(3) { + ["scheme"]=> + string(1) "x" + ["host"]=> + string(1) ":" + ["port"]=> + int(6) +} + +--> http://?:/: bool(false) + +--> http://@?:/: bool(false) + +--> file:///:: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(2) "/:" +} + +--> file:///a:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "a:/" +} + +--> file:///ab:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(5) "/ab:/" +} + +--> file:///a:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "a:/" +} + +--> file:///@:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "@:/" +} + +--> file:///:80/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(5) "/:80/" +} + +--> []: array(1) { + ["path"]=> + string(2) "[]" +} + +--> http://[x:80]/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(6) "[x:80]" + ["path"]=> + string(1) "/" +} + +--> : array(1) { + ["path"]=> + string(0) "" +} + +--> /: array(1) { + ["path"]=> + string(1) "/" +} + +--> /rest/Users?filter={"id":"123"}: array(2) { + ["path"]=> + string(11) "/rest/Users" + ["query"]=> + string(19) "filter={"id":"123"}" +} + +--> http:///blah.com: bool(false) + +--> http://:80: bool(false) + +--> http://user@:80: bool(false) + +--> http://user:pass@:80: bool(false) + +--> http://:: bool(false) + +--> http://@/: bool(false) + +--> http://@:/: bool(false) + +--> http://:/: bool(false) + +--> http://?: bool(false) + +--> http://#: bool(false) + +--> http://?:: bool(false) + +--> http://:?: bool(false) + +--> http://blah.com:123456: bool(false) + +--> http://blah.com:abcdef: bool(false) +Done diff --git a/ext/standard/tests/url/parse_url_basic_003.phpt b/ext/standard/tests/url/parse_url_basic_003.phpt index 0ce27b7a2c85b..a766380a37e84 100644 --- a/ext/standard/tests/url/parse_url_basic_003.phpt +++ b/ext/standard/tests/url/parse_url_basic_003.phpt @@ -68,7 +68,7 @@ echo "Done"; --> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" --> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" --> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" ---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(26) "secret@hideout@www.php.net" --> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" --> nntp://news.php.net : string(12) "news.php.net" --> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz : string(11) "ftp.gnu.org" diff --git a/ext/standard/tests/url/parse_url_basic_005.phpt b/ext/standard/tests/url/parse_url_basic_005.phpt index 68162594f1a58..b3df330deb0aa 100644 --- a/ext/standard/tests/url/parse_url_basic_005.phpt +++ b/ext/standard/tests/url/parse_url_basic_005.phpt @@ -68,7 +68,7 @@ echo "Done"; --> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" --> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(0) "" --> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" ---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(14) "secret@hideout" +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : NULL --> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" --> nntp://news.php.net : NULL --> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz : NULL diff --git a/ext/standard/tests/url/parse_url_unterminated.phpt b/ext/standard/tests/url/parse_url_unterminated.phpt index bb0e0968560ae..d6ad18bd08abc 100644 --- a/ext/standard/tests/url/parse_url_unterminated.phpt +++ b/ext/standard/tests/url/parse_url_unterminated.phpt @@ -509,15 +509,13 @@ echo "Done"; string(16) "some_page_ref123" } ---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(6) { ["scheme"]=> string(4) "http" ["host"]=> - string(11) "www.php.net" + string(26) "secret@hideout@www.php.net" ["port"]=> int(80) - ["user"]=> - string(14) "secret@hideout" ["path"]=> string(10) "/index.php" ["query"]=> diff --git a/ext/standard/tests/url/parse_url_unterminated.phpt.orig b/ext/standard/tests/url/parse_url_unterminated.phpt.orig new file mode 100644 index 0000000000000..bb0e0968560ae --- /dev/null +++ b/ext/standard/tests/url/parse_url_unterminated.phpt.orig @@ -0,0 +1,887 @@ +--TEST-- +Test parse_url() function: Parse unterminated string +--SKIPIF-- + +--FILE-- + $url: "; + $str = zend_create_unterminated_string($url); + var_dump(parse_url($str)); + zend_terminate_string($str); +} + +echo "Done"; +?> +--EXPECTF-- + +--> 64.246.30.37: array(1) { + ["path"]=> + string(12) "64.246.30.37" +} + +--> http://64.246.30.37: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(12) "64.246.30.37" +} + +--> http://64.246.30.37/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(12) "64.246.30.37" + ["path"]=> + string(1) "/" +} + +--> 64.246.30.37/: array(1) { + ["path"]=> + string(13) "64.246.30.37/" +} + +--> 64.246.30.37:80/: array(3) { + ["host"]=> + string(12) "64.246.30.37" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> php.net: array(1) { + ["path"]=> + string(7) "php.net" +} + +--> php.net/: array(1) { + ["path"]=> + string(8) "php.net/" +} + +--> http://php.net: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "php.net" +} + +--> http://php.net/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "php.net" + ["path"]=> + string(1) "/" +} + +--> www.php.net: array(1) { + ["path"]=> + string(11) "www.php.net" +} + +--> www.php.net/: array(1) { + ["path"]=> + string(12) "www.php.net/" +} + +--> http://www.php.net: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" +} + +--> http://www.php.net/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" +} + +--> www.php.net:80: array(2) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) +} + +--> http://www.php.net:80: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) +} + +--> http://www.php.net:80/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net/index.php: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" +} + +--> www.php.net/?: array(1) { + ["path"]=> + string(12) "www.php.net/" +} + +--> www.php.net:80/?: array(3) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net/?: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/?: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/foo/bar/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(18) "/foo/bar/index.php" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php?lots=1&of=2¶meters=3&too=4&here=5: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" + ["query"]=> + string(37) "lots=1&of=2¶meters=3&too=4&here=5" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(45) "/this/is/a/very/deep/directory/structure/and/" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/structure/and/file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(53) "/this/is/a/very/deep/directory/structure/and/file.php" +} + +--> http://www.php.net:80/this/../a/../deep/directory: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(28) "/this/../a/../deep/directory" +} + +--> http://www.php.net:80/this/../a/../deep/directory/: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(29) "/this/../a/../deep/directory/" +} + +--> http://www.php.net:80/this/is/a/very/deep/directory/../file.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(42) "/this/is/a/very/deep/directory/../file.php" +} + +--> http://www.php.net:80/index.php: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/index.php?: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" +} + +--> http://www.php.net:80/#foo: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["fragment"]=> + string(3) "foo" +} + +--> http://www.php.net:80/?#: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" +} + +--> http://www.php.net:80/?test=1: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["query"]=> + string(6) "test=1" +} + +--> http://www.php.net/?test=1&: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(1) "/" + ["query"]=> + string(7) "test=1&" +} + +--> http://www.php.net:80/?&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(1) "/" + ["query"]=> + string(1) "&" +} + +--> http://www.php.net:80/index.php?test=1&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(7) "test=1&" +} + +--> http://www.php.net/index.php?&: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(1) "&" +} + +--> http://www.php.net:80/index.php?foo&: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(4) "foo&" +} + +--> http://www.php.net/index.php?&foo: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(4) "&foo" +} + +--> http://www.php.net:80/index.php?test=1&test2=char&test3=mixesCI: array(5) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" +} + +--> www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(5) { + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(6) "secret" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["user"]=> + string(6) "secret" + ["pass"]=> + string(0) "" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(8) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(0) "" + ["pass"]=> + string(7) "hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["user"]=> + string(6) "secret" + ["pass"]=> + string(7) "hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(14) "secret@hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(8) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "www.php.net" + ["port"]=> + int(80) + ["user"]=> + string(6) "secret" + ["pass"]=> + string(7) "hid:out" + ["path"]=> + string(10) "/index.php" + ["query"]=> + string(31) "test=1&test2=char&test3=mixesCI" + ["fragment"]=> + string(16) "some_page_ref123" +} + +--> nntp://news.php.net: array(2) { + ["scheme"]=> + string(4) "nntp" + ["host"]=> + string(12) "news.php.net" +} + +--> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz: array(3) { + ["scheme"]=> + string(3) "ftp" + ["host"]=> + string(11) "ftp.gnu.org" + ["path"]=> + string(22) "/gnu/glic/glibc.tar.gz" +} + +--> zlib:http://foo@bar: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(14) "http://foo@bar" +} + +--> zlib:filename.txt: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(12) "filename.txt" +} + +--> zlib:/path/to/my/file/file.txt: array(2) { + ["scheme"]=> + string(4) "zlib" + ["path"]=> + string(25) "/path/to/my/file/file.txt" +} + +--> foo://foo@bar: array(3) { + ["scheme"]=> + string(3) "foo" + ["host"]=> + string(3) "bar" + ["user"]=> + string(3) "foo" +} + +--> mailto:me@mydomain.com: array(2) { + ["scheme"]=> + string(6) "mailto" + ["path"]=> + string(15) "me@mydomain.com" +} + +--> /foo.php?a=b&c=d: array(2) { + ["path"]=> + string(8) "/foo.php" + ["query"]=> + string(7) "a=b&c=d" +} + +--> foo.php?a=b&c=d: array(2) { + ["path"]=> + string(7) "foo.php" + ["query"]=> + string(7) "a=b&c=d" +} + +--> http://user:passwd@www.example.com:8080?bar=1&boom=0: array(6) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(15) "www.example.com" + ["port"]=> + int(8080) + ["user"]=> + string(4) "user" + ["pass"]=> + string(6) "passwd" + ["query"]=> + string(12) "bar=1&boom=0" +} + +--> http://user_me-you:my_pas-word@www.example.com:8080?bar=1&boom=0: array(6) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(15) "www.example.com" + ["port"]=> + int(8080) + ["user"]=> + string(11) "user_me-you" + ["pass"]=> + string(11) "my_pas-word" + ["query"]=> + string(12) "bar=1&boom=0" +} + +--> file:///path/to/file: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(13) "/path/to/file" +} + +--> file://path/to/file: array(3) { + ["scheme"]=> + string(4) "file" + ["host"]=> + string(4) "path" + ["path"]=> + string(8) "/to/file" +} + +--> file:/path/to/file: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(13) "/path/to/file" +} + +--> http://1.2.3.4:/abc.asp?a=1&b=2: array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "1.2.3.4" + ["path"]=> + string(8) "/abc.asp" + ["query"]=> + string(7) "a=1&b=2" +} + +--> http://foo.com#bar: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(7) "foo.com" + ["fragment"]=> + string(3) "bar" +} + +--> scheme:: array(1) { + ["scheme"]=> + string(6) "scheme" +} + +--> foo+bar://baz@bang/bla: array(4) { + ["scheme"]=> + string(7) "foo+bar" + ["host"]=> + string(4) "bang" + ["user"]=> + string(3) "baz" + ["path"]=> + string(4) "/bla" +} + +--> gg:9130731: array(2) { + ["scheme"]=> + string(2) "gg" + ["path"]=> + string(7) "9130731" +} + +--> http://user:@pass@host/path?argument?value#etc: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(4) "host" + ["user"]=> + string(4) "user" + ["pass"]=> + string(5) "@pass" + ["path"]=> + string(5) "/path" + ["query"]=> + string(14) "argument?value" + ["fragment"]=> + string(3) "etc" +} + +--> http://10.10.10.10/:80: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "10.10.10.10" + ["path"]=> + string(4) "/:80" +} + +--> http://x:?: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) "x" +} + +--> x:blah.com: array(2) { + ["scheme"]=> + string(1) "x" + ["path"]=> + string(8) "blah.com" +} + +--> x:/blah.com: array(2) { + ["scheme"]=> + string(1) "x" + ["path"]=> + string(9) "/blah.com" +} + +--> x://::abc/?: bool(false) + +--> http://::?: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) ":" +} + +--> http://::#: array(2) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(1) ":" +} + +--> x://::6.5: array(3) { + ["scheme"]=> + string(1) "x" + ["host"]=> + string(1) ":" + ["port"]=> + int(6) +} + +--> http://?:/: bool(false) + +--> http://@?:/: bool(false) + +--> file:///:: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(2) "/:" +} + +--> file:///a:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "a:/" +} + +--> file:///ab:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(5) "/ab:/" +} + +--> file:///a:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "a:/" +} + +--> file:///@:/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(3) "@:/" +} + +--> file:///:80/: array(2) { + ["scheme"]=> + string(4) "file" + ["path"]=> + string(5) "/:80/" +} + +--> []: array(1) { + ["path"]=> + string(2) "[]" +} + +--> http://[x:80]/: array(3) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(6) "[x:80]" + ["path"]=> + string(1) "/" +} + +--> : array(1) { + ["path"]=> + string(0) "" +} + +--> /: array(1) { + ["path"]=> + string(1) "/" +} + +--> /rest/Users?filter={"id":"123"}: array(2) { + ["path"]=> + string(11) "/rest/Users" + ["query"]=> + string(19) "filter={"id":"123"}" +} + +--> http:///blah.com: bool(false) + +--> http://:80: bool(false) + +--> http://user@:80: bool(false) + +--> http://user:pass@:80: bool(false) + +--> http://:: bool(false) + +--> http://@/: bool(false) + +--> http://@:/: bool(false) + +--> http://:/: bool(false) + +--> http://?: bool(false) + +--> http://#: bool(false) + +--> http://?:: bool(false) + +--> http://:?: bool(false) + +--> http://blah.com:123456: bool(false) + +--> http://blah.com:abcdef: bool(false) +Done diff --git a/ext/standard/url.c b/ext/standard/url.c index 13938fa2fb9f6..e6d4e224e4ea1 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -92,6 +92,22 @@ PHPAPI php_url *php_url_parse(char const *str) return php_url_parse_ex(str, strlen(str)); } +static int is_userinfo_valid(const char *str, size_t len) +{ + char *valid = "-._~!$&'()*+,;=:"; + char *p = str; + while (p - str < len) { + if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { + p++; + } else if (*p == '%' && p - str <= len - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { + p += 3; + } else { + return 0; + } + } + return 1; +} + /* {{{ php_url_parse */ PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) @@ -235,13 +251,18 @@ PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) ret->pass = estrndup(pp, (p-pp)); php_replace_controlchars_ex(ret->pass, (p-pp)); } else { + if (!is_userinfo_valid(s, p-s)) { + goto check_port; + } ret->user = estrndup(s, (p-s)); php_replace_controlchars_ex(ret->user, (p-s)); + } s = p + 1; } +check_port: /* check for port */ if (s < ue && *s == '[' && *(e-1) == ']') { /* Short circuit portscan, From 4a5f8211a901f17827667e1d694a9bb3cfb18397 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:17 +0000 Subject: [PATCH 21/25] commit patch 22475219 --- ext/dom/domimplementation.c | 5 + ext/dom/domimplementation.c.orig | 274 ++++++ ext/dom/tests/bug79971_2.phpt | 20 + ext/libxml/libxml.c | 9 + ext/libxml/libxml.c.orig | 1367 +++++++++++++++++++++++++++ ext/simplexml/tests/bug79971_1.phpt | 27 + ext/simplexml/tests/bug79971_1.xml | 2 + 7 files changed, 1704 insertions(+) create mode 100644 ext/dom/domimplementation.c.orig create mode 100644 ext/dom/tests/bug79971_2.phpt create mode 100644 ext/libxml/libxml.c.orig create mode 100644 ext/simplexml/tests/bug79971_1.phpt create mode 100644 ext/simplexml/tests/bug79971_1.xml diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index ee050e21fdd95..486a49d52bbb1 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -114,6 +114,11 @@ PHP_METHOD(domimplementation, createDocumentType) pch2 = (xmlChar *) systemid; } + if (strstr(name, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + RETURN_FALSE; + } + uri = xmlParseURI(name); if (uri != NULL && uri->opaque != NULL) { localname = xmlStrdup((xmlChar *) uri->opaque); diff --git a/ext/dom/domimplementation.c.orig b/ext/dom/domimplementation.c.orig new file mode 100644 index 0000000000000..ee050e21fdd95 --- /dev/null +++ b/ext/dom/domimplementation.c.orig @@ -0,0 +1,274 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Christian Stocker | + | Rob Richards | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#if HAVE_LIBXML && HAVE_DOM +#include "php_dom.h" + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_get_feature, 0, 0, 2) + ZEND_ARG_INFO(0, feature) + ZEND_ARG_INFO(0, version) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_has_feature, 0, 0, 0) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_create_documenttype, 0, 0, 3) + ZEND_ARG_INFO(0, qualifiedName) + ZEND_ARG_INFO(0, publicId) + ZEND_ARG_INFO(0, systemId) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_create_document, 0, 0, 3) + ZEND_ARG_INFO(0, namespaceURI) + ZEND_ARG_INFO(0, qualifiedName) + ZEND_ARG_OBJ_INFO(0, docType, DOMDocumentType, 0) +ZEND_END_ARG_INFO(); +/* }}} */ + +/* +* class DOMImplementation +* +* URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-102161490 +* Since: +*/ + +const zend_function_entry php_dom_domimplementation_class_functions[] = { + PHP_ME(domimplementation, getFeature, arginfo_dom_implementation_get_feature, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, hasFeature, arginfo_dom_implementation_has_feature, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, createDocumentType, arginfo_dom_implementation_create_documenttype, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, createDocument, arginfo_dom_implementation_create_document, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_FE_END +}; + +/* {{{ proto boolean dom_domimplementation_has_feature(string feature, string version); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-5CED94D7 +Since: +*/ +PHP_METHOD(domimplementation, hasFeature) +{ + size_t feature_len, version_len; + char *feature, *version; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &feature, &feature_len, &version, &version_len) == FAILURE) { + return; + } + + if (dom_has_feature(feature, version)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} end dom_domimplementation_has_feature */ + +/* {{{ proto DOMDocumentType dom_domimplementation_create_document_type(string qualifiedName, string publicId, string systemId); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocType +Since: DOM Level 2 +*/ +PHP_METHOD(domimplementation, createDocumentType) +{ + xmlDtd *doctype; + int ret; + size_t name_len = 0, publicid_len = 0, systemid_len = 0; + char *name = NULL, *publicid = NULL, *systemid = NULL; + xmlChar *pch1 = NULL, *pch2 = NULL, *localname = NULL; + xmlURIPtr uri; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &name, &name_len, &publicid, &publicid_len, &systemid, &systemid_len) == FAILURE) { + return; + } + + if (name_len == 0) { + php_error_docref(NULL, E_WARNING, "qualifiedName is required"); + RETURN_FALSE; + } + + if (publicid_len > 0) { + pch1 = (xmlChar *) publicid; + } + if (systemid_len > 0) { + pch2 = (xmlChar *) systemid; + } + + uri = xmlParseURI(name); + if (uri != NULL && uri->opaque != NULL) { + localname = xmlStrdup((xmlChar *) uri->opaque); + if (xmlStrchr(localname, (xmlChar) ':') != NULL) { + php_dom_throw_error(NAMESPACE_ERR, 1); + xmlFreeURI(uri); + xmlFree(localname); + RETURN_FALSE; + } + } else { + localname = xmlStrdup((xmlChar *) name); + } + + /* TODO: Test that localname has no invalid chars + php_dom_throw_error(INVALID_CHARACTER_ERR,); + */ + + if (uri) { + xmlFreeURI(uri); + } + + doctype = xmlCreateIntSubset(NULL, localname, pch1, pch2); + xmlFree(localname); + + if (doctype == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to create DocumentType"); + RETURN_FALSE; + } + + DOM_RET_OBJ((xmlNodePtr) doctype, &ret, NULL); +} +/* }}} end dom_domimplementation_create_document_type */ + +/* {{{ proto DOMDocument dom_domimplementation_create_document(string namespaceURI, string qualifiedName, DOMDocumentType doctype); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocument +Since: DOM Level 2 +*/ +PHP_METHOD(domimplementation, createDocument) +{ + zval *node = NULL; + xmlDoc *docp; + xmlNode *nodep; + xmlDtdPtr doctype = NULL; + xmlNsPtr nsptr = NULL; + int ret, errorcode = 0; + size_t uri_len = 0, name_len = 0; + char *uri = NULL, *name = NULL; + char *prefix = NULL, *localname = NULL; + dom_object *doctobj; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ssO", &uri, &uri_len, &name, &name_len, &node, dom_documenttype_class_entry) == FAILURE) { + return; + } + + if (node != NULL) { + DOM_GET_OBJ(doctype, node, xmlDtdPtr, doctobj); + if (doctype->type == XML_DOCUMENT_TYPE_NODE) { + php_error_docref(NULL, E_WARNING, "Invalid DocumentType object"); + RETURN_FALSE; + } + if (doctype->doc != NULL) { + php_dom_throw_error(WRONG_DOCUMENT_ERR, 1); + RETURN_FALSE; + } + } else { + doctobj = NULL; + } + + if (name_len > 0) { + errorcode = dom_check_qname(name, &localname, &prefix, 1, name_len); + if (errorcode == 0 && uri_len > 0 + && ((nsptr = xmlNewNs(NULL, (xmlChar *) uri, (xmlChar *) prefix)) == NULL) + ) { + errorcode = NAMESPACE_ERR; + } + } + + if (prefix != NULL) { + xmlFree(prefix); + } + + if (errorcode != 0) { + if (localname != NULL) { + xmlFree(localname); + } + php_dom_throw_error(errorcode, 1); + RETURN_FALSE; + } + + /* currently letting libxml2 set the version string */ + docp = xmlNewDoc(NULL); + if (!docp) { + if (localname != NULL) { + xmlFree(localname); + } + RETURN_FALSE; + } + + if (doctype != NULL) { + docp->intSubset = doctype; + doctype->parent = docp; + doctype->doc = docp; + docp->children = (xmlNodePtr) doctype; + docp->last = (xmlNodePtr) doctype; + } + + if (localname != NULL) { + nodep = xmlNewDocNode(docp, nsptr, (xmlChar *) localname, NULL); + if (!nodep) { + if (doctype != NULL) { + docp->intSubset = NULL; + doctype->parent = NULL; + doctype->doc = NULL; + docp->children = NULL; + docp->last = NULL; + } + xmlFreeDoc(docp); + xmlFree(localname); + /* Need some type of error here */ + php_error_docref(NULL, E_WARNING, "Unexpected Error"); + RETURN_FALSE; + } + + nodep->nsDef = nsptr; + + xmlDocSetRootElement(docp, nodep); + xmlFree(localname); + } + + DOM_RET_OBJ((xmlNodePtr) docp, &ret, NULL); + + if (doctobj != NULL) { + doctobj->document = ((dom_object *)((php_libxml_node_ptr *)docp->_private)->_private)->document; + php_libxml_increment_doc_ref((php_libxml_node_object *)doctobj, docp); + } +} +/* }}} end dom_domimplementation_create_document */ + +/* {{{ proto DOMNode dom_domimplementation_get_feature(string feature, string version); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#DOMImplementation3-getFeature +Since: DOM Level 3 +*/ +PHP_METHOD(domimplementation, getFeature) +{ + DOM_NOT_IMPLEMENTED(); +} +/* }}} end dom_domimplementation_get_feature */ + +#endif + +/* + * 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 + */ diff --git a/ext/dom/tests/bug79971_2.phpt b/ext/dom/tests/bug79971_2.phpt new file mode 100644 index 0000000000000..c4e6b1e4e0933 --- /dev/null +++ b/ext/dom/tests/bug79971_2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +createDocumentType("$uri%00foo")); +?> +--EXPECTF-- +Warning: DOMImplementation::createDocumentType(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index bfc1224eaa825..54b8d4a9c5b02 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -308,6 +308,10 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char int isescaped=0; xmlURI *uri; + if (strstr(filename, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } uri = xmlParseURI(filename); if (uri && (uri->scheme == NULL || @@ -438,6 +442,11 @@ php_libxml_output_buffer_create_filename(const char *URI, if (URI == NULL) return(NULL); + if (strstr(URI, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } + puri = xmlParseURI(URI); if (puri != NULL) { if (puri->scheme != NULL) diff --git a/ext/libxml/libxml.c.orig b/ext/libxml/libxml.c.orig new file mode 100644 index 0000000000000..bfc1224eaa825 --- /dev/null +++ b/ext/libxml/libxml.c.orig @@ -0,0 +1,1367 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Shane Caraveo | + | Wez Furlong | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define IS_EXT_MODULE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "SAPI.h" + +#define PHP_XML_INTERNAL +#include "zend_variables.h" +#include "ext/standard/php_string.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" + +#if HAVE_LIBXML + +#include +#include +#include +#include +#include +#include +#ifdef LIBXML_SCHEMAS_ENABLED +#include +#include +#endif + +#include "php_libxml.h" + +#define PHP_LIBXML_ERROR 0 +#define PHP_LIBXML_CTX_ERROR 1 +#define PHP_LIBXML_CTX_WARNING 2 + +/* a true global for initialization */ +static int _php_libxml_initialized = 0; +static int _php_libxml_per_request_initialization = 1; +static xmlExternalEntityLoader _php_libxml_default_entity_loader; + +typedef struct _php_libxml_func_handler { + php_libxml_export_node export_func; +} php_libxml_func_handler; + +static HashTable php_libxml_exports; + +static ZEND_DECLARE_MODULE_GLOBALS(libxml) +static PHP_GINIT_FUNCTION(libxml); + +static PHP_FUNCTION(libxml_set_streams_context); +static PHP_FUNCTION(libxml_use_internal_errors); +static PHP_FUNCTION(libxml_get_last_error); +static PHP_FUNCTION(libxml_clear_errors); +static PHP_FUNCTION(libxml_get_errors); +static PHP_FUNCTION(libxml_set_external_entity_loader); +static PHP_FUNCTION(libxml_disable_entity_loader); + +static zend_class_entry *libxmlerror_class_entry; + +/* {{{ dynamically loadable module stuff */ +#ifdef COMPILE_DL_LIBXML +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(libxml) +#endif /* COMPILE_DL_LIBXML */ +/* }}} */ + +/* {{{ function prototypes */ +static PHP_MINIT_FUNCTION(libxml); +static PHP_RINIT_FUNCTION(libxml); +static PHP_RSHUTDOWN_FUNCTION(libxml); +static PHP_MSHUTDOWN_FUNCTION(libxml); +static PHP_MINFO_FUNCTION(libxml); +static int php_libxml_post_deactivate(void); + +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_libxml_set_streams_context, 0) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_use_internal_errors, 0, 0, 0) + ZEND_ARG_INFO(0, use_errors) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_last_error, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_clear_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_disable_entity_loader, 0, 0, 0) + ZEND_ARG_INFO(0, disable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_set_external_entity_loader, 0, 0, 1) + ZEND_ARG_INFO(0, resolver_function) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ extension definition structures */ +static const zend_function_entry libxml_functions[] = { + PHP_FE(libxml_set_streams_context, arginfo_libxml_set_streams_context) + PHP_FE(libxml_use_internal_errors, arginfo_libxml_use_internal_errors) + PHP_FE(libxml_get_last_error, arginfo_libxml_get_last_error) + PHP_FE(libxml_clear_errors, arginfo_libxml_clear_errors) + PHP_FE(libxml_get_errors, arginfo_libxml_get_errors) + PHP_FE(libxml_disable_entity_loader, arginfo_libxml_disable_entity_loader) + PHP_FE(libxml_set_external_entity_loader, arginfo_libxml_set_external_entity_loader) + PHP_FE_END +}; + +zend_module_entry libxml_module_entry = { + STANDARD_MODULE_HEADER, + "libxml", /* extension name */ + libxml_functions, /* extension function list */ + PHP_MINIT(libxml), /* extension-wide startup function */ + PHP_MSHUTDOWN(libxml), /* extension-wide shutdown function */ + PHP_RINIT(libxml), /* per-request startup function */ + PHP_RSHUTDOWN(libxml), /* per-request shutdown function */ + PHP_MINFO(libxml), /* information function */ + PHP_LIBXML_VERSION, + PHP_MODULE_GLOBALS(libxml), /* globals descriptor */ + PHP_GINIT(libxml), /* globals ctor */ + NULL, /* globals dtor */ + php_libxml_post_deactivate, /* post deactivate */ + STANDARD_MODULE_PROPERTIES_EX +}; + +/* }}} */ + +/* {{{ internal functions for interoperability */ +static int php_libxml_clear_object(php_libxml_node_object *object) +{ + if (object->properties) { + object->properties = NULL; + } + php_libxml_decrement_node_ptr(object); + return php_libxml_decrement_doc_ref(object); +} + +static int php_libxml_unregister_node(xmlNodePtr nodep) +{ + php_libxml_node_object *wrapper; + + php_libxml_node_ptr *nodeptr = nodep->_private; + + if (nodeptr != NULL) { + wrapper = nodeptr->_private; + if (wrapper) { + php_libxml_clear_object(wrapper); + } else { + if (nodeptr->node != NULL && nodeptr->node->type != XML_DOCUMENT_NODE) { + nodeptr->node->_private = NULL; + } + nodeptr->node = NULL; + } + } + + return -1; +} + +static void php_libxml_node_free(xmlNodePtr node) +{ + if(node) { + if (node->_private != NULL) { + ((php_libxml_node_ptr *) node->_private)->node = NULL; + } + switch (node->type) { + case XML_ATTRIBUTE_NODE: + xmlFreeProp((xmlAttrPtr) node); + break; + case XML_ENTITY_DECL: + case XML_ELEMENT_DECL: + case XML_ATTRIBUTE_DECL: + break; + case XML_NOTATION_NODE: + /* These require special handling */ + if (node->name != NULL) { + xmlFree((char *) node->name); + } + if (((xmlEntityPtr) node)->ExternalID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->ExternalID); + } + if (((xmlEntityPtr) node)->SystemID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->SystemID); + } + xmlFree(node); + break; + case XML_NAMESPACE_DECL: + if (node->ns) { + xmlFreeNs(node->ns); + node->ns = NULL; + } + node->type = XML_ELEMENT_NODE; + default: + xmlFreeNode(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) +{ + xmlNodePtr curnode; + + if (node != NULL) { + curnode = node; + while (curnode != NULL) { + node = curnode; + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_NOTATION_NODE: + case XML_ENTITY_DECL: + break; + case XML_ENTITY_REF_NODE: + php_libxml_node_free_list((xmlNodePtr) node->properties); + break; + case XML_ATTRIBUTE_NODE: + if ((node->doc != NULL) && (((xmlAttrPtr) node)->atype == XML_ATTRIBUTE_ID)) { + xmlRemoveID(node->doc, (xmlAttrPtr) node); + } + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + php_libxml_node_free_list(node->children); + break; + default: + php_libxml_node_free_list(node->children); + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + + curnode = node->next; + xmlUnlinkNode(node); + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } + } +} + +/* }}} */ + +/* {{{ startup, shutdown and info functions */ +static PHP_GINIT_FUNCTION(libxml) +{ +#if defined(COMPILE_DL_LIBXML) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + ZVAL_UNDEF(&libxml_globals->stream_context); + libxml_globals->error_buffer.s = NULL; + libxml_globals->error_list = NULL; + ZVAL_UNDEF(&libxml_globals->entity_loader.object); + libxml_globals->entity_loader.fci.size = 0; + libxml_globals->entity_loader_disabled = 0; +} + +static void _php_libxml_destroy_fci(zend_fcall_info *fci, zval *object) +{ + if (fci->size > 0) { + zval_ptr_dtor(&fci->function_name); + fci->size = 0; + } + if (!Z_ISUNDEF_P(object)) { + zval_ptr_dtor(object); + ZVAL_UNDEF(object); + } +} + +/* Channel libxml file io layer through the PHP streams subsystem. + * This allows use of ftps:// and https:// urls */ + +static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char *mode, const int read_only) +{ + php_stream_statbuf ssbuf; + php_stream_context *context = NULL; + php_stream_wrapper *wrapper = NULL; + char *resolved_path; + const char *path_to_open = NULL; + void *ret_val = NULL; + int isescaped=0; + xmlURI *uri; + + + uri = xmlParseURI(filename); + if (uri && (uri->scheme == NULL || + (xmlStrncmp(BAD_CAST uri->scheme, BAD_CAST "file", 4) == 0))) { + resolved_path = xmlURIUnescapeString(filename, 0, NULL); + isescaped = 1; +#if LIBXML_VERSION >= 20902 && defined(PHP_WIN32) + /* Libxml 2.9.2 prefixes local paths with file:/ instead of file://, + thus the php stream wrapper will fail on a valid case. For this + reason the prefix is rather better cut off. */ + { + size_t pre_len = sizeof("file:/") - 1; + + if (strncasecmp(resolved_path, "file:/", pre_len) == 0 + && '/' != resolved_path[pre_len]) { + xmlChar *tmp = xmlStrdup(resolved_path + pre_len); + xmlFree(resolved_path); + resolved_path = tmp; + } + } +#endif + } else { + resolved_path = (char *)filename; + } + + if (uri) { + xmlFreeURI(uri); + } + + if (resolved_path == NULL) { + return NULL; + } + + /* logic copied from _php_stream_stat, but we only want to fail + if the wrapper supports stat, otherwise, figure it out from + the open. This logic is only to support hiding warnings + that the streams layer puts out at times, but for libxml we + may try to open files that don't exist, but it is not a failure + in xml processing (eg. DTD files) */ + wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, 0); + if (wrapper && read_only && wrapper->wops->url_stat) { + if (wrapper->wops->url_stat(wrapper, path_to_open, PHP_STREAM_URL_STAT_QUIET, &ssbuf, NULL) == -1) { + if (isescaped) { + xmlFree(resolved_path); + } + return NULL; + } + } + + context = php_stream_context_from_zval(Z_ISUNDEF(LIBXML(stream_context))? NULL : &LIBXML(stream_context), 0); + + ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, REPORT_ERRORS, NULL, context); + if (isescaped) { + xmlFree(resolved_path); + } + return ret_val; +} + +static void *php_libxml_streams_IO_open_read_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "rb", 1); +} + +static void *php_libxml_streams_IO_open_write_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "wb", 0); +} + +static int php_libxml_streams_IO_read(void *context, char *buffer, int len) +{ + return php_stream_read((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_write(void *context, const char *buffer, int len) +{ + if (CG(unclean_shutdown)) { + return -1; + } + return php_stream_write((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_close(void *context) +{ + return php_stream_close((php_stream*)context); +} + +static xmlParserInputBufferPtr +php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) +{ + xmlParserInputBufferPtr ret; + void *context = NULL; + + if (LIBXML(entity_loader_disabled)) { + return NULL; + } + + if (URI == NULL) + return(NULL); + + context = php_libxml_streams_IO_open_read_wrapper(URI); + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Input buffer front-end. */ + ret = xmlAllocParserInputBuffer(enc); + if (ret != NULL) { + ret->context = context; + ret->readcallback = php_libxml_streams_IO_read; + ret->closecallback = php_libxml_streams_IO_close; + } else + php_libxml_streams_IO_close(context); + + return(ret); +} + +static xmlOutputBufferPtr +php_libxml_output_buffer_create_filename(const char *URI, + xmlCharEncodingHandlerPtr encoder, + int compression ATTRIBUTE_UNUSED) +{ + xmlOutputBufferPtr ret; + xmlURIPtr puri; + void *context = NULL; + char *unescaped = NULL; + + if (URI == NULL) + return(NULL); + + puri = xmlParseURI(URI); + if (puri != NULL) { + if (puri->scheme != NULL) + unescaped = xmlURIUnescapeString(URI, 0, NULL); + xmlFreeURI(puri); + } + + if (unescaped != NULL) { + context = php_libxml_streams_IO_open_write_wrapper(unescaped); + xmlFree(unescaped); + } + + /* try with a non-escaped URI this may be a strange filename */ + if (context == NULL) { + context = php_libxml_streams_IO_open_write_wrapper(URI); + } + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Output buffer front-end. */ + ret = xmlAllocOutputBuffer(encoder); + if (ret != NULL) { + ret->context = context; + ret->writecallback = php_libxml_streams_IO_write; + ret->closecallback = php_libxml_streams_IO_close; + } + + return(ret); +} + +static int _php_libxml_free_error(xmlErrorPtr error) +{ + /* This will free the libxml alloc'd memory */ + xmlResetError(error); + return 1; +} + +static void _php_list_set_error_structure(xmlErrorPtr error, const char *msg) +{ + xmlError error_copy; + int ret; + + + memset(&error_copy, 0, sizeof(xmlError)); + + if (error) { + ret = xmlCopyError(error, &error_copy); + } else { + error_copy.domain = 0; + error_copy.code = XML_ERR_INTERNAL_ERROR; + error_copy.level = XML_ERR_ERROR; + error_copy.line = 0; + error_copy.node = NULL; + error_copy.int1 = 0; + error_copy.int2 = 0; + error_copy.ctxt = NULL; + error_copy.message = (char*)xmlStrdup((xmlChar*)msg); + error_copy.file = NULL; + error_copy.str1 = NULL; + error_copy.str2 = NULL; + error_copy.str3 = NULL; + ret = 0; + } + + if (ret == 0) { + zend_llist_add_element(LIBXML(error_list), &error_copy); + } +} + +static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) +{ + xmlParserCtxtPtr parser; + + parser = (xmlParserCtxtPtr) ctx; + + if (parser != NULL && parser->input != NULL) { + if (parser->input->filename) { + php_error_docref(NULL, level, "%s in %s, line: %d", msg, parser->input->filename, parser->input->line); + } else { + php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); + } + } +} + +void php_libxml_issue_error(int level, const char *msg) +{ + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, msg); + } else { + php_error_docref(NULL, level, "%s", msg); + } +} + +static void php_libxml_internal_error_handler(int error_type, void *ctx, const char **msg, va_list ap) +{ + char *buf; + int len, len_iter, output = 0; + + + len = vspprintf(&buf, 0, *msg, ap); + len_iter = len; + + /* remove any trailing \n */ + while (len_iter && buf[--len_iter] == '\n') { + buf[len_iter] = '\0'; + output = 1; + } + + smart_str_appendl(&LIBXML(error_buffer), buf, len); + + efree(buf); + + if (output == 1) { + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, ZSTR_VAL(LIBXML(error_buffer).s)); + } else { + switch (error_type) { + case PHP_LIBXML_CTX_ERROR: + php_libxml_ctx_error_level(E_WARNING, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + case PHP_LIBXML_CTX_WARNING: + php_libxml_ctx_error_level(E_NOTICE, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + default: + php_error_docref(NULL, E_WARNING, "%s", ZSTR_VAL(LIBXML(error_buffer).s)); + } + } + smart_str_free(&LIBXML(error_buffer)); + } +} + +static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + xmlParserInputPtr ret = NULL; + const char *resource = NULL; + zval *ctxzv, retval; + zval params[3]; + int status; + zend_fcall_info *fci; + + fci = &LIBXML(entity_loader).fci; + + if (fci->size == 0) { + /* no custom user-land callback set up; delegate to original loader */ + return _php_libxml_default_entity_loader(URL, ID, context); + } + + if (ID != NULL) { + ZVAL_STRING(¶ms[0], ID); + } else { + ZVAL_UNDEF(¶ms[0]); + } + if (URL != NULL) { + ZVAL_STRING(¶ms[1], URL); + } else { + ZVAL_UNDEF(¶ms[1]); + } + ctxzv = ¶ms[2]; + array_init_size(ctxzv, 4); + +#define ADD_NULL_OR_STRING_KEY(memb) \ + if (context->memb == NULL) { \ + add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \ + } else { \ + add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \ + (char *)context->memb); \ + } + + ADD_NULL_OR_STRING_KEY(directory) + ADD_NULL_OR_STRING_KEY(intSubName) + ADD_NULL_OR_STRING_KEY(extSubURI) + ADD_NULL_OR_STRING_KEY(extSubSystem) + +#undef ADD_NULL_OR_STRING_KEY + + fci->retval = &retval; + fci->params = params; + fci->param_count = sizeof(params)/sizeof(*params); + fci->no_separation = 1; + + status = zend_call_function(fci, &LIBXML(entity_loader).fcc); + if (status != SUCCESS || Z_ISUNDEF(retval)) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed", + Z_STRVAL(fci->function_name)); + } else { + /* + retval_ptr = *fci->retval_ptr_ptr; + if (retval_ptr == NULL) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed; " + "probably it has thrown an exception", + fci->function_name); + } else */ if (Z_TYPE(retval) == IS_STRING) { +is_string: + resource = Z_STRVAL(retval); + } else if (Z_TYPE(retval) == IS_RESOURCE) { + php_stream *stream; + php_stream_from_zval_no_verify(stream, &retval); + if (stream == NULL) { + php_libxml_ctx_error(context, + "The user entity loader callback '%s' has returned a " + "resource, but it is not a stream", + Z_STRVAL(fci->function_name)); + } else { + /* TODO: allow storing the encoding in the stream context? */ + xmlCharEncoding enc = XML_CHAR_ENCODING_NONE; + xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc); + if (pib == NULL) { + php_libxml_ctx_error(context, "Could not allocate parser " + "input buffer"); + } else { + /* make stream not being closed when the zval is freed */ + ++GC_REFCOUNT(stream->res); + pib->context = stream; + pib->readcallback = php_libxml_streams_IO_read; + pib->closecallback = php_libxml_streams_IO_close; + + ret = xmlNewIOInputStream(context, pib, enc); + if (ret == NULL) { + xmlFreeParserInputBuffer(pib); + } + } + } + } else if (Z_TYPE(retval) != IS_NULL) { + /* retval not string nor resource nor null; convert to string */ + convert_to_string(&retval); + goto is_string; + } /* else is null; don't try anything */ + } + + if (ret == NULL) { + if (resource == NULL) { + if (ID == NULL) { + ID = "NULL"; + } + php_libxml_ctx_error(context, + "Failed to load external entity \"%s\"\n", ID); + } else { + /* we got the resource in the form of a string; open it */ + ret = xmlNewInputFromFile(context, resource); + } + } + + zval_ptr_dtor(¶ms[0]); + zval_ptr_dtor(¶ms[1]); + zval_ptr_dtor(¶ms[2]); + zval_ptr_dtor(&retval); + return ret; +} + +static xmlParserInputPtr _php_libxml_pre_ext_ent_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + + /* Check whether we're running in a PHP context, since the entity loader + * we've defined is an application level (true global) setting. + * If we are, we also want to check whether we've finished activating + * the modules (RINIT phase). Using our external entity loader during a + * RINIT should not be problem per se (though during MINIT it is, because + * we don't even have a resource list by then), but then whether one + * extension would be using the custom external entity loader or not + * could depend on extension loading order + * (if _php_libxml_per_request_initialization */ + if (xmlGenericError == php_libxml_error_handler && PG(modules_activated)) { + return _php_libxml_external_entity_loader(URL, ID, context); + } else { + return _php_libxml_default_entity_loader(URL, ID, context); + } +} + +PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_ERROR, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_WARNING, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error) +{ + _php_list_set_error_structure(error, NULL); + + return; +} + +PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_ERROR, ctx, &msg, args); + va_end(args); +} + +static void php_libxml_exports_dtor(zval *zv) +{ + free(Z_PTR_P(zv)); +} + +PHP_LIBXML_API void php_libxml_initialize(void) +{ + if (!_php_libxml_initialized) { + /* we should be the only one's to ever init!! */ + xmlInitParser(); + + _php_libxml_default_entity_loader = xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(_php_libxml_pre_ext_ent_loader); + + zend_hash_init(&php_libxml_exports, 0, NULL, php_libxml_exports_dtor, 1); + + _php_libxml_initialized = 1; + } +} + +PHP_LIBXML_API void php_libxml_shutdown(void) +{ + if (_php_libxml_initialized) { +#if defined(LIBXML_SCHEMAS_ENABLED) + xmlRelaxNGCleanupTypes(); +#endif + /* xmlCleanupParser(); */ + zend_hash_destroy(&php_libxml_exports); + + xmlSetExternalEntityLoader(_php_libxml_default_entity_loader); + _php_libxml_initialized = 0; + } +} + +PHP_LIBXML_API void php_libxml_switch_context(zval *context, zval *oldcontext) +{ + if (oldcontext) { + ZVAL_COPY_VALUE(oldcontext, &LIBXML(stream_context)); + } + if (context) { + ZVAL_COPY_VALUE(&LIBXML(stream_context), context); + } +} + +static PHP_MINIT_FUNCTION(libxml) +{ + zend_class_entry ce; + + php_libxml_initialize(); + + REGISTER_LONG_CONSTANT("LIBXML_VERSION", LIBXML_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_DOTTED_VERSION", LIBXML_DOTTED_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_LOADED_VERSION", (char *)xmlParserVersion, CONST_CS | CONST_PERSISTENT); + + /* For use with loading xml */ + REGISTER_LONG_CONSTANT("LIBXML_NOENT", XML_PARSE_NOENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDLOAD", XML_PARSE_DTDLOAD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDATTR", XML_PARSE_DTDATTR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDVALID", XML_PARSE_DTDVALID, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOERROR", XML_PARSE_NOERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOWARNING", XML_PARSE_NOWARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOBLANKS", XML_PARSE_NOBLANKS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_XINCLUDE", XML_PARSE_XINCLUDE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NSCLEAN", XML_PARSE_NSCLEAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOCDATA", XML_PARSE_NOCDATA, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NONET", XML_PARSE_NONET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_PEDANTIC", XML_PARSE_PEDANTIC, CONST_CS | CONST_PERSISTENT); +#if LIBXML_VERSION >= 20621 + REGISTER_LONG_CONSTANT("LIBXML_COMPACT", XML_PARSE_COMPACT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOXMLDECL", XML_SAVE_NO_DECL, CONST_CS | CONST_PERSISTENT); +#endif +#if LIBXML_VERSION >= 20703 + REGISTER_LONG_CONSTANT("LIBXML_PARSEHUGE", XML_PARSE_HUGE, CONST_CS | CONST_PERSISTENT); +#endif +#if LIBXML_VERSION >= 20900 + REGISTER_LONG_CONSTANT("LIBXML_BIGLINES", XML_PARSE_BIG_LINES, CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("LIBXML_NOEMPTYTAG", LIBXML_SAVE_NOEMPTYTAG, CONST_CS | CONST_PERSISTENT); + + /* Schema validation options */ +#if defined(LIBXML_SCHEMAS_ENABLED) && LIBXML_VERSION >= 20614 + REGISTER_LONG_CONSTANT("LIBXML_SCHEMA_CREATE", XML_SCHEMA_VAL_VC_I_CREATE, CONST_CS | CONST_PERSISTENT); +#endif + + /* Additional constants for use with loading html */ +#if LIBXML_VERSION >= 20707 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NOIMPLIED", HTML_PARSE_NOIMPLIED, CONST_CS | CONST_PERSISTENT); +#endif + +#if LIBXML_VERSION >= 20708 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NODEFDTD", HTML_PARSE_NODEFDTD, CONST_CS | CONST_PERSISTENT); +#endif + + /* Error levels */ + REGISTER_LONG_CONSTANT("LIBXML_ERR_NONE", XML_ERR_NONE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_WARNING", XML_ERR_WARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_ERROR", XML_ERR_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_FATAL", XML_ERR_FATAL, CONST_CS | CONST_PERSISTENT); + + INIT_CLASS_ENTRY(ce, "LibXMLError", NULL); + libxmlerror_class_entry = zend_register_internal_class(&ce); + + if (sapi_module.name) { + static const char * const supported_sapis[] = { + "cgi-fcgi", + "litespeed", + NULL + }; + const char * const *sapi_name; + + for (sapi_name = supported_sapis; *sapi_name; sapi_name++) { + if (strcmp(sapi_module.name, *sapi_name) == 0) { + _php_libxml_per_request_initialization = 0; + break; + } + } + } + + if (!_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + } + + return SUCCESS; +} + + +static PHP_RINIT_FUNCTION(libxml) +{ + if (_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + + /* Enable the entity loader by default. This ensures that + * other threads/requests that might have disabled the loader + * do not affect the current request. + */ + LIBXML(entity_loader_disabled) = 0; + } + return SUCCESS; +} + +static PHP_RSHUTDOWN_FUNCTION(libxml) +{ + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(libxml) +{ + if (!_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + php_libxml_shutdown(); + + return SUCCESS; +} + +static int php_libxml_post_deactivate(void) +{ + /* reset libxml generic error handling */ + if (_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + xmlSetStructuredErrorFunc(NULL, NULL); + + /* the steam_context resource will be released by resource list destructor */ + ZVAL_UNDEF(&LIBXML(stream_context)); + smart_str_free(&LIBXML(error_buffer)); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + xmlResetLastError(); + + return SUCCESS; +} + + +static PHP_MINFO_FUNCTION(libxml) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "libXML support", "active"); + php_info_print_table_row(2, "libXML Compiled Version", LIBXML_DOTTED_VERSION); + php_info_print_table_row(2, "libXML Loaded Version", (char *)xmlParserVersion); + php_info_print_table_row(2, "libXML streams", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ proto void libxml_set_streams_context(resource streams_context) + Set the streams context for the next libxml document load or write */ +static PHP_FUNCTION(libxml_set_streams_context) +{ + zval *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_RESOURCE(arg) + ZEND_PARSE_PARAMETERS_END(); + + if (!Z_ISUNDEF(LIBXML(stream_context))) { + zval_ptr_dtor(&LIBXML(stream_context)); + ZVAL_UNDEF(&LIBXML(stream_context)); + } + ZVAL_COPY(&LIBXML(stream_context), arg); +} +/* }}} */ + +/* {{{ proto bool libxml_use_internal_errors([boolean use_errors]) + Disable libxml errors and allow user to fetch error information as needed */ +static PHP_FUNCTION(libxml_use_internal_errors) +{ + xmlStructuredErrorFunc current_handler; + zend_bool use_errors=0, retval; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(use_errors) + ZEND_PARSE_PARAMETERS_END(); + + current_handler = xmlStructuredError; + if (current_handler && current_handler == php_libxml_structured_error_handler) { + retval = 1; + } else { + retval = 0; + } + + if (ZEND_NUM_ARGS() == 0) { + RETURN_BOOL(retval); + } + + if (use_errors == 0) { + xmlSetStructuredErrorFunc(NULL, NULL); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + } else { + xmlSetStructuredErrorFunc(NULL, php_libxml_structured_error_handler); + if (LIBXML(error_list) == NULL) { + LIBXML(error_list) = (zend_llist *) emalloc(sizeof(zend_llist)); + zend_llist_init(LIBXML(error_list), sizeof(xmlError), (llist_dtor_func_t) _php_libxml_free_error, 0); + } + } + RETURN_BOOL(retval); +} +/* }}} */ + +/* {{{ proto object libxml_get_last_error() + Retrieve last error from libxml */ +static PHP_FUNCTION(libxml_get_last_error) +{ + xmlErrorPtr error; + + error = xmlGetLastError(); + + if (error) { + object_init_ex(return_value, libxmlerror_class_entry); + add_property_long(return_value, "level", error->level); + add_property_long(return_value, "code", error->code); + add_property_long(return_value, "column", error->int2); + if (error->message) { + add_property_string(return_value, "message", error->message); + } else { + add_property_stringl(return_value, "message", "", 0); + } + if (error->file) { + add_property_string(return_value, "file", error->file); + } else { + add_property_stringl(return_value, "file", "", 0); + } + add_property_long(return_value, "line", error->line); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object libxml_get_errors() + Retrieve array of errors */ +static PHP_FUNCTION(libxml_get_errors) +{ + + xmlErrorPtr error; + + if (array_init(return_value) == FAILURE) { + RETURN_FALSE; + } + + if (LIBXML(error_list)) { + + error = zend_llist_get_first(LIBXML(error_list)); + + while (error != NULL) { + zval z_error; + + object_init_ex(&z_error, libxmlerror_class_entry); + add_property_long_ex(&z_error, "level", sizeof("level") - 1, error->level); + add_property_long_ex(&z_error, "code", sizeof("code") - 1, error->code); + add_property_long_ex(&z_error, "column", sizeof("column") - 1, error->int2 ); + if (error->message) { + add_property_string_ex(&z_error, "message", sizeof("message") - 1, error->message); + } else { + add_property_stringl_ex(&z_error, "message", sizeof("message") - 1, "", 0); + } + if (error->file) { + add_property_string_ex(&z_error, "file", sizeof("file") - 1, error->file); + } else { + add_property_stringl_ex(&z_error, "file", sizeof("file") - 1, "", 0); + } + add_property_long_ex(&z_error, "line", sizeof("line") - 1, error->line); + add_next_index_zval(return_value, &z_error); + + error = zend_llist_get_next(LIBXML(error_list)); + } + } +} +/* }}} */ + +/* {{{ proto void libxml_clear_errors() + Clear last error from libxml */ +static PHP_FUNCTION(libxml_clear_errors) +{ + xmlResetLastError(); + if (LIBXML(error_list)) { + zend_llist_clean(LIBXML(error_list)); + } +} +/* }}} */ + +PHP_LIBXML_API zend_bool php_libxml_disable_entity_loader(zend_bool disable) /* {{{ */ +{ + zend_bool old = LIBXML(entity_loader_disabled); + + LIBXML(entity_loader_disabled) = disable; + return old; +} /* }}} */ + +/* {{{ proto bool libxml_disable_entity_loader([boolean disable]) + Disable/Enable ability to load external entities */ +static PHP_FUNCTION(libxml_disable_entity_loader) +{ + zend_bool disable = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(disable) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(php_libxml_disable_entity_loader(disable)); +} +/* }}} */ + +/* {{{ proto void libxml_set_external_entity_loader(callback resolver_function) + Changes the default external entity loader */ +static PHP_FUNCTION(libxml_set_external_entity_loader) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC_EX(fci, fcc, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + if (fci.size > 0) { /* argument not null */ + LIBXML(entity_loader).fci = fci; + Z_ADDREF(fci.function_name); + if (fci.object != NULL) { + ZVAL_OBJ(&LIBXML(entity_loader).object, fci.object); + Z_ADDREF(LIBXML(entity_loader).object); + } + LIBXML(entity_loader).fcc = fcc; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Common functions shared by extensions */ +int php_libxml_xmlCheckUTF8(const unsigned char *s) +{ + int i; + unsigned char c; + + for (i = 0; (c = s[i++]);) { + if ((c & 0x80) == 0) { + } else if ((c & 0xe0) == 0xc0) { + if ((s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf0) == 0xe0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf8) == 0xf0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else { + return 0; + } + } + return 1; +} + +zval *php_libxml_register_export(zend_class_entry *ce, php_libxml_export_node export_function) +{ + php_libxml_func_handler export_hnd; + + /* Initialize in case this module hasn't been loaded yet */ + php_libxml_initialize(); + export_hnd.export_func = export_function; + + return zend_hash_add_mem(&php_libxml_exports, ce->name, &export_hnd, sizeof(export_hnd)); +} + +PHP_LIBXML_API xmlNodePtr php_libxml_import_node(zval *object) +{ + zend_class_entry *ce = NULL; + xmlNodePtr node = NULL; + php_libxml_func_handler *export_hnd; + + if (Z_TYPE_P(object) == IS_OBJECT) { + ce = Z_OBJCE_P(object); + while (ce->parent != NULL) { + ce = ce->parent; + } + if ((export_hnd = zend_hash_find_ptr(&php_libxml_exports, ce->name))) { + node = export_hnd->export_func(object); + } + } + return node; +} + +PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data) +{ + int ret_refcount = -1; + + if (object != NULL && node != NULL) { + if (object->node != NULL) { + if (object->node->node == node) { + return object->node->refcount; + } else { + php_libxml_decrement_node_ptr(object); + } + } + if (node->_private != NULL) { + object->node = node->_private; + ret_refcount = ++object->node->refcount; + /* Only dom uses _private */ + if (object->node->_private == NULL) { + object->node->_private = private_data; + } + } else { + ret_refcount = 1; + object->node = emalloc(sizeof(php_libxml_node_ptr)); + object->node->node = node; + object->node->refcount = 1; + object->node->_private = private_data; + node->_private = object->node; + } + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object) +{ + int ret_refcount = -1; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + ret_refcount = --obj_node->refcount; + if (ret_refcount == 0) { + if (obj_node->node != NULL) { + obj_node->node->_private = NULL; + } + efree(obj_node); + } + object->node = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp) +{ + int ret_refcount = -1; + + if (object->document != NULL) { + object->document->refcount++; + ret_refcount = object->document->refcount; + } else if (docp != NULL) { + ret_refcount = 1; + object->document = emalloc(sizeof(php_libxml_ref_obj)); + object->document->ptr = docp; + object->document->refcount = ret_refcount; + object->document->doc_props = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object) +{ + int ret_refcount = -1; + + if (object != NULL && object->document != NULL) { + ret_refcount = --object->document->refcount; + if (ret_refcount == 0) { + if (object->document->ptr != NULL) { + xmlFreeDoc((xmlDoc *) object->document->ptr); + } + if (object->document->doc_props != NULL) { + if (object->document->doc_props->classmap) { + zend_hash_destroy(object->document->doc_props->classmap); + FREE_HASHTABLE(object->document->doc_props->classmap); + } + efree(object->document->doc_props); + } + efree(object->document); + } + object->document = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API void php_libxml_node_free_resource(xmlNodePtr node) +{ + if (!node) { + return; + } + + switch (node->type) { + case XML_DOCUMENT_NODE: + case XML_HTML_DOCUMENT_NODE: + break; + default: + if (node->parent == NULL || node->type == XML_NAMESPACE_DECL) { + php_libxml_node_free_list((xmlNodePtr) node->children); + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_ENTITY_DECL: + case XML_ATTRIBUTE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + break; + default: + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } else { + php_libxml_unregister_node(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_decrement_resource(php_libxml_node_object *object) +{ + int ret_refcount = -1; + xmlNodePtr nodep; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + nodep = object->node->node; + ret_refcount = php_libxml_decrement_node_ptr(object); + if (ret_refcount == 0) { + php_libxml_node_free_resource(nodep); + } else { + if (obj_node && object == obj_node->_private) { + obj_node->_private = NULL; + } + } + } + if (object != NULL && object->document != NULL) { + /* Safe to call as if the resource were freed then doc pointer is NULL */ + php_libxml_decrement_doc_ref(object); + } +} +/* }}} */ + +#if defined(PHP_WIN32) && defined(COMPILE_DL_LIBXML) +PHP_LIBXML_API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return xmlDllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/simplexml/tests/bug79971_1.phpt b/ext/simplexml/tests/bug79971_1.phpt new file mode 100644 index 0000000000000..197776d82d38d --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +asXML("$uri.out%00foo")); +?> +--EXPECTF-- +Warning: simplexml_load_file(): URI must not contain percent-encoded NUL bytes in %s on line %d + +Warning: simplexml_load_file(): I/O warning : failed to load external entity "%s/bug79971_1.xml%00foo" in %s on line %d +bool(false) + +Warning: SimpleXMLElement::asXML(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/simplexml/tests/bug79971_1.xml b/ext/simplexml/tests/bug79971_1.xml new file mode 100644 index 0000000000000..912bb76d9d7e2 --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.xml @@ -0,0 +1,2 @@ + + From 015ba8ff5035aee699ed09ba0f6916bc6c7b7e4e Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:20 +0000 Subject: [PATCH 22/25] commit patch 17932931 --- ext/standard/url.c | 16 - ext/standard/url.c.orig | 779 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 779 insertions(+), 16 deletions(-) create mode 100644 ext/standard/url.c.orig diff --git a/ext/standard/url.c b/ext/standard/url.c index e6d4e224e4ea1..8cec47d5d9017 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -92,22 +92,6 @@ PHPAPI php_url *php_url_parse(char const *str) return php_url_parse_ex(str, strlen(str)); } -static int is_userinfo_valid(const char *str, size_t len) -{ - char *valid = "-._~!$&'()*+,;=:"; - char *p = str; - while (p - str < len) { - if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { - p++; - } else if (*p == '%' && p - str <= len - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { - p += 3; - } else { - return 0; - } - } - return 1; -} - /* {{{ php_url_parse */ PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) diff --git a/ext/standard/url.c.orig b/ext/standard/url.c.orig new file mode 100644 index 0000000000000..e6d4e224e4ea1 --- /dev/null +++ b/ext/standard/url.c.orig @@ -0,0 +1,779 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include +#include +#include +#include + +#include "php.h" + +#include "url.h" +#include "file.h" +#ifdef _OSD_POSIX +#ifndef APACHE +#error On this EBCDIC platform, PHP is only supported as an Apache module. +#else /*APACHE*/ +#ifndef CHARSET_EBCDIC +#define CHARSET_EBCDIC /* this machine uses EBCDIC, not ASCII! */ +#endif +#include "ebcdic.h" +#endif /*APACHE*/ +#endif /*_OSD_POSIX*/ + +/* {{{ free_url + */ +PHPAPI void php_url_free(php_url *theurl) +{ + if (theurl->scheme) + efree(theurl->scheme); + if (theurl->user) + efree(theurl->user); + if (theurl->pass) + efree(theurl->pass); + if (theurl->host) + efree(theurl->host); + if (theurl->path) + efree(theurl->path); + if (theurl->query) + efree(theurl->query); + if (theurl->fragment) + efree(theurl->fragment); + efree(theurl); +} +/* }}} */ + +/* {{{ php_replace_controlchars + */ +PHPAPI char *php_replace_controlchars_ex(char *str, size_t len) +{ + unsigned char *s = (unsigned char *)str; + unsigned char *e = (unsigned char *)str + len; + + if (!str) { + return (NULL); + } + + while (s < e) { + + if (iscntrl(*s)) { + *s='_'; + } + s++; + } + + return (str); +} +/* }}} */ + +PHPAPI char *php_replace_controlchars(char *str) +{ + return php_replace_controlchars_ex(str, strlen(str)); +} + +PHPAPI php_url *php_url_parse(char const *str) +{ + return php_url_parse_ex(str, strlen(str)); +} + +static int is_userinfo_valid(const char *str, size_t len) +{ + char *valid = "-._~!$&'()*+,;=:"; + char *p = str; + while (p - str < len) { + if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { + p++; + } else if (*p == '%' && p - str <= len - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { + p += 3; + } else { + return 0; + } + } + return 1; +} + +/* {{{ php_url_parse + */ +PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) +{ + char port_buf[6]; + php_url *ret = ecalloc(1, sizeof(php_url)); + char const *s, *e, *p, *pp, *ue; + + s = str; + ue = s + length; + + /* parse scheme */ + if ((e = memchr(s, ':', length)) && e != s) { + /* validate scheme */ + p = s; + while (p < e) { + /* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */ + if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') { + if (e + 1 < ue && e < s + strcspn(s, "?#")) { + goto parse_port; + } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + e = 0; + goto parse_host; + } else { + goto just_path; + } + } + p++; + } + + if (e + 1 == ue) { /* only scheme is available */ + ret->scheme = estrndup(s, (e - s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + return ret; + } + + /* + * certain schemas like mailto: and zlib: may not have any / after them + * this check ensures we support those. + */ + if (*(e+1) != '/') { + /* check if the data we get is a port this allows us to + * correctly parse things like a.com:80 + */ + p = e + 1; + while (p < ue && isdigit(*p)) { + p++; + } + + if ((p == ue || *p == '/') && (p - e) < 7) { + goto parse_port; + } + + ret->scheme = estrndup(s, (e-s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + + s = e + 1; + goto just_path; + } else { + ret->scheme = estrndup(s, (e-s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + + if (e + 2 < ue && *(e + 2) == '/') { + s = e + 3; + if (!strncasecmp("file", ret->scheme, sizeof("file"))) { + if (e + 3 < ue && *(e + 3) == '/') { + /* support windows drive letters as in: + file:///c:/somedir/file.txt + */ + if (e + 5 < ue && *(e + 5) == ':') { + s = e + 4; + } + goto just_path; + } + } + } else { + s = e + 1; + goto just_path; + } + } + } else if (e) { /* no scheme; starts with colon: look for port */ + parse_port: + p = e + 1; + pp = p; + + while (pp < ue && pp - p < 6 && isdigit(*pp)) { + pp++; + } + + if (pp - p > 0 && pp - p < 6 && (pp == ue || *pp == '/')) { + zend_long port; + memcpy(port_buf, p, (pp - p)); + port_buf[pp - p] = '\0'; + port = ZEND_STRTOL(port_buf, NULL, 10); + if (port > 0 && port <= 65535) { + ret->port = (unsigned short) port; + if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } + } else { + if (ret->scheme) efree(ret->scheme); + efree(ret); + return NULL; + } + } else if (p == pp && pp == ue) { + if (ret->scheme) efree(ret->scheme); + efree(ret); + return NULL; + } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } else { + goto just_path; + } + } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } else { + goto just_path; + } + + parse_host: + /* Binary-safe strcspn(s, "/?#") */ + e = ue; + if ((p = memchr(s, '/', e - s))) { + e = p; + } + if ((p = memchr(s, '?', e - s))) { + e = p; + } + if ((p = memchr(s, '#', e - s))) { + e = p; + } + + /* check for login and password */ + if ((p = zend_memrchr(s, '@', (e-s)))) { + if ((pp = memchr(s, ':', (p-s)))) { + ret->user = estrndup(s, (pp-s)); + php_replace_controlchars_ex(ret->user, (pp - s)); + + pp++; + ret->pass = estrndup(pp, (p-pp)); + php_replace_controlchars_ex(ret->pass, (p-pp)); + } else { + if (!is_userinfo_valid(s, p-s)) { + goto check_port; + } + ret->user = estrndup(s, (p-s)); + php_replace_controlchars_ex(ret->user, (p-s)); + + } + + s = p + 1; + } + +check_port: + /* check for port */ + if (s < ue && *s == '[' && *(e-1) == ']') { + /* Short circuit portscan, + we're dealing with an + IPv6 embedded address */ + p = NULL; + } else { + p = zend_memrchr(s, ':', (e-s)); + } + + if (p) { + if (!ret->port) { + p++; + if (e-p > 5) { /* port cannot be longer then 5 characters */ + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } else if (e - p > 0) { + zend_long port; + memcpy(port_buf, p, (e - p)); + port_buf[e - p] = '\0'; + port = ZEND_STRTOL(port_buf, NULL, 10); + if (port > 0 && port <= 65535) { + ret->port = (unsigned short)port; + } else { + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } + } + p--; + } + } else { + p = e; + } + + /* check if we have a valid host, if we don't reject the string as url */ + if ((p-s) < 1) { + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } + + ret->host = estrndup(s, (p-s)); + php_replace_controlchars_ex(ret->host, (p - s)); + + if (e == ue) { + return ret; + } + + s = e; + + just_path: + + e = ue; + p = memchr(s, '#', (e - s)); + if (p) { + p++; + if (p < e) { + ret->fragment = estrndup(p, (e - p)); + php_replace_controlchars_ex(ret->fragment, (e - p)); + } + e = p-1; + } + + p = memchr(s, '?', (e - s)); + if (p) { + p++; + if (p < e) { + ret->query = estrndup(p, (e - p)); + php_replace_controlchars_ex(ret->query, (e - p)); + } + e = p-1; + } + + if (s < e || s == ue) { + ret->path = estrndup(s, (e - s)); + php_replace_controlchars_ex(ret->path, (e - s)); + } + + return ret; +} +/* }}} */ + +/* {{{ proto mixed parse_url(string url, [int url_component]) + Parse a URL and return its components */ +PHP_FUNCTION(parse_url) +{ + char *str; + size_t str_len; + php_url *resource; + zend_long key = -1; + zval tmp; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(key) + ZEND_PARSE_PARAMETERS_END(); + + resource = php_url_parse_ex(str, str_len); + if (resource == NULL) { + /* @todo Find a method to determine why php_url_parse_ex() failed */ + RETURN_FALSE; + } + + if (key > -1) { + switch (key) { + case PHP_URL_SCHEME: + if (resource->scheme != NULL) RETVAL_STRING(resource->scheme); + break; + case PHP_URL_HOST: + if (resource->host != NULL) RETVAL_STRING(resource->host); + break; + case PHP_URL_PORT: + if (resource->port != 0) RETVAL_LONG(resource->port); + break; + case PHP_URL_USER: + if (resource->user != NULL) RETVAL_STRING(resource->user); + break; + case PHP_URL_PASS: + if (resource->pass != NULL) RETVAL_STRING(resource->pass); + break; + case PHP_URL_PATH: + if (resource->path != NULL) RETVAL_STRING(resource->path); + break; + case PHP_URL_QUERY: + if (resource->query != NULL) RETVAL_STRING(resource->query); + break; + case PHP_URL_FRAGMENT: + if (resource->fragment != NULL) RETVAL_STRING(resource->fragment); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid URL component identifier " ZEND_LONG_FMT, key); + RETVAL_FALSE; + } + goto done; + } + + /* allocate an array for return */ + array_init(return_value); + + /* add the various elements to the array */ + if (resource->scheme != NULL) { + ZVAL_STRING(&tmp, resource->scheme); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_SCHEME), &tmp); + } + if (resource->host != NULL) { + ZVAL_STRING(&tmp, resource->host); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_HOST), &tmp); + } + if (resource->port != 0) { + ZVAL_LONG(&tmp, resource->port); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PORT), &tmp); + } + if (resource->user != NULL) { + ZVAL_STRING(&tmp, resource->user); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_USER), &tmp); + } + if (resource->pass != NULL) { + ZVAL_STRING(&tmp, resource->pass); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PASS), &tmp); + } + if (resource->path != NULL) { + ZVAL_STRING(&tmp, resource->path); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PATH), &tmp); + } + if (resource->query != NULL) { + ZVAL_STRING(&tmp, resource->query); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_QUERY), &tmp); + } + if (resource->fragment != NULL) { + ZVAL_STRING(&tmp, resource->fragment); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &tmp); + } +done: + php_url_free(resource); +} +/* }}} */ + +/* {{{ php_htoi + */ +static int php_htoi(char *s) +{ + int value; + int c; + + c = ((unsigned char *)s)[0]; + if (isupper(c)) + c = tolower(c); + value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; + + c = ((unsigned char *)s)[1]; + if (isupper(c)) + c = tolower(c); + value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; + + return (value); +} +/* }}} */ + +/* rfc1738: + + ...The characters ";", + "/", "?", ":", "@", "=" and "&" are the characters which may be + reserved for special meaning within a scheme... + + ...Thus, only alphanumerics, the special characters "$-_.+!*'(),", and + reserved characters used for their reserved purposes may be used + unencoded within a URL... + + For added safety, we only leave -_. unencoded. + */ + +static unsigned char hexchars[] = "0123456789ABCDEF"; + +/* {{{ php_url_encode + */ +PHPAPI zend_string *php_url_encode(char const *s, size_t len) +{ + register unsigned char c; + unsigned char *to; + unsigned char const *from, *end; + zend_string *start; + + from = (unsigned char *)s; + end = (unsigned char *)s + len; + start = zend_string_safe_alloc(3, len, 0, 0); + to = (unsigned char*)ZSTR_VAL(start); + + while (from < end) { + c = *from++; + + if (c == ' ') { + *to++ = '+'; +#ifndef CHARSET_EBCDIC + } else if ((c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z')) { + to[0] = '%'; + to[1] = hexchars[c >> 4]; + to[2] = hexchars[c & 15]; + to += 3; +#else /*CHARSET_EBCDIC*/ + } else if (!isalnum(c) && strchr("_-.", c) == NULL) { + /* Allow only alphanumeric chars and '_', '-', '.'; escape the rest */ + to[0] = '%'; + to[1] = hexchars[os_toascii[c] >> 4]; + to[2] = hexchars[os_toascii[c] & 15]; + to += 3; +#endif /*CHARSET_EBCDIC*/ + } else { + *to++ = c; + } + } + *to = '\0'; + + start = zend_string_truncate(start, to - (unsigned char*)ZSTR_VAL(start), 0); + + return start; +} +/* }}} */ + +/* {{{ proto string urlencode(string str) + URL-encodes string */ +PHP_FUNCTION(urlencode) +{ + zend_string *in_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str))); +} +/* }}} */ + +/* {{{ proto string urldecode(string str) + Decodes URL-encoded string */ +PHP_FUNCTION(urldecode) +{ + zend_string *in_str, *out_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0); + ZSTR_LEN(out_str) = php_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str)); + + RETURN_NEW_STR(out_str); +} +/* }}} */ + +/* {{{ php_url_decode + */ +PHPAPI size_t php_url_decode(char *str, size_t len) +{ + char *dest = str; + char *data = str; + + while (len--) { + if (*data == '+') { + *dest = ' '; + } + else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { +#ifndef CHARSET_EBCDIC + *dest = (char) php_htoi(data + 1); +#else + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; +#endif + data += 2; + len -= 2; + } else { + *dest = *data; + } + data++; + dest++; + } + *dest = '\0'; + return dest - str; +} +/* }}} */ + +/* {{{ php_raw_url_encode + */ +PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len) +{ + register size_t x, y; + zend_string *str; + + str = zend_string_safe_alloc(3, len, 0, 0); + for (x = 0, y = 0; len--; x++, y++) { + ZSTR_VAL(str)[y] = (unsigned char) s[x]; +#ifndef CHARSET_EBCDIC + if ((ZSTR_VAL(str)[y] < '0' && ZSTR_VAL(str)[y] != '-' && ZSTR_VAL(str)[y] != '.') || + (ZSTR_VAL(str)[y] < 'A' && ZSTR_VAL(str)[y] > '9') || + (ZSTR_VAL(str)[y] > 'Z' && ZSTR_VAL(str)[y] < 'a' && ZSTR_VAL(str)[y] != '_') || + (ZSTR_VAL(str)[y] > 'z' && ZSTR_VAL(str)[y] != '~')) { + ZSTR_VAL(str)[y++] = '%'; + ZSTR_VAL(str)[y++] = hexchars[(unsigned char) s[x] >> 4]; + ZSTR_VAL(str)[y] = hexchars[(unsigned char) s[x] & 15]; +#else /*CHARSET_EBCDIC*/ + if (!isalnum(ZSTR_VAL(str)[y]) && strchr("_-.~", ZSTR_VAL(str)[y]) != NULL) { + ZSTR_VAL(str)[y++] = '%'; + ZSTR_VAL(str)[y++] = hexchars[os_toascii[(unsigned char) s[x]] >> 4]; + ZSTR_VAL(str)[y] = hexchars[os_toascii[(unsigned char) s[x]] & 15]; +#endif /*CHARSET_EBCDIC*/ + } + } + ZSTR_VAL(str)[y] = '\0'; + str = zend_string_truncate(str, y, 0); + + return str; +} +/* }}} */ + +/* {{{ proto string rawurlencode(string str) + URL-encodes string */ +PHP_FUNCTION(rawurlencode) +{ + zend_string *in_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_raw_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str))); +} +/* }}} */ + +/* {{{ proto string rawurldecode(string str) + Decodes URL-encodes string */ +PHP_FUNCTION(rawurldecode) +{ + zend_string *in_str, *out_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0); + ZSTR_LEN(out_str) = php_raw_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str)); + + RETURN_NEW_STR(out_str); +} +/* }}} */ + +/* {{{ php_raw_url_decode + */ +PHPAPI size_t php_raw_url_decode(char *str, size_t len) +{ + char *dest = str; + char *data = str; + + while (len--) { + if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { +#ifndef CHARSET_EBCDIC + *dest = (char) php_htoi(data + 1); +#else + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; +#endif + data += 2; + len -= 2; + } else { + *dest = *data; + } + data++; + dest++; + } + *dest = '\0'; + return dest - str; +} +/* }}} */ + +/* {{{ proto array get_headers(string url[, int format[, resource context]]) + fetches all the headers sent by the server in response to a HTTP request */ +PHP_FUNCTION(get_headers) +{ + char *url; + size_t url_len; + php_stream *stream; + zval *prev_val, *hdr = NULL, *h; + HashTable *hashT; + zend_long format = 0; + zval *zcontext = NULL; + php_stream_context *context; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_PATH(url, url_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(format) + Z_PARAM_RESOURCE_EX(zcontext, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + context = php_stream_context_from_zval(zcontext, 0); + + if (!(stream = php_stream_open_wrapper_ex(url, "r", REPORT_ERRORS | STREAM_USE_URL | STREAM_ONLY_GET_HEADERS, NULL, context))) { + RETURN_FALSE; + } + + if (Z_TYPE(stream->wrapperdata) != IS_ARRAY) { + php_stream_close(stream); + RETURN_FALSE; + } + + array_init(return_value); + + /* check for curl-wrappers that provide headers via a special "headers" element */ + if ((h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1)) != NULL && Z_TYPE_P(h) == IS_ARRAY) { + /* curl-wrappers don't load data until the 1st read */ + if (!Z_ARRVAL_P(h)->nNumOfElements) { + php_stream_getc(stream); + } + h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1); + hashT = Z_ARRVAL_P(h); + } else { + hashT = HASH_OF(&stream->wrapperdata); + } + + ZEND_HASH_FOREACH_VAL(hashT, hdr) { + if (Z_TYPE_P(hdr) != IS_STRING) { + continue; + } + if (!format) { +no_name_header: + add_next_index_str(return_value, zend_string_copy(Z_STR_P(hdr))); + } else { + char c; + char *s, *p; + + if ((p = strchr(Z_STRVAL_P(hdr), ':'))) { + c = *p; + *p = '\0'; + s = p + 1; + while (isspace((int)*(unsigned char *)s)) { + s++; + } + + if ((prev_val = zend_hash_str_find(Z_ARRVAL_P(return_value), Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)))) == NULL) { + add_assoc_stringl_ex(return_value, Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)), s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr)))); + } else { /* some headers may occur more than once, therefor we need to remake the string into an array */ + convert_to_array(prev_val); + add_next_index_stringl(prev_val, s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr)))); + } + + *p = c; + } else { + goto no_name_header; + } + } + } ZEND_HASH_FOREACH_END(); + + php_stream_close(stream); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ From 54d42c1a14b6809a06242124690680587fa5e283 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:22 +0000 Subject: [PATCH 23/25] commit patch 23994958 --- NEWS | 3 + NEWS.orig | 4 + ext/soap/php_sdl.c | 26 +- ext/soap/php_sdl.c.orig | 3673 ++++++++++++++++++++++++++++++++++ ext/soap/php_xml.c | 4 +- ext/soap/php_xml.c.orig | 330 +++ ext/soap/tests/bug80672.phpt | 15 + ext/soap/tests/bug80672.xml | 6 + 8 files changed, 4047 insertions(+), 14 deletions(-) create mode 100644 ext/soap/php_sdl.c.orig create mode 100644 ext/soap/php_xml.c.orig create mode 100644 ext/soap/tests/bug80672.phpt create mode 100644 ext/soap/tests/bug80672.xml diff --git a/NEWS b/NEWS index 62bdef871c2a0..c1f9b3ed37f21 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 7.2.1 +- SOAP: + . Fixed bug #80672 (Null Dereference in SoapClient). (CVE-2021-21702) (cmb, Stas) + - Core: . Fixed bug #75573 (Segmentation fault in 7.1.12 and 7.0.26). (Laruence) diff --git a/NEWS.orig b/NEWS.orig index 80e3074f7f28e..62bdef871c2a0 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -1749,6 +1749,10 @@ PHP NEWS . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and unserialize()). (Stas) +- Phar: + . Fixed bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext). + (Stas) + - Soap: . Fixed bug #73538 (SoapClient::__setSoapHeaders doesn't overwrite SOAP headers). (duncan3dc) diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index b8f1911f69722..e38cc76a97ad5 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -314,6 +314,8 @@ void sdl_restore_uri_credentials(sdlCtx *ctx) ctx->context = NULL; } +#define SAFE_STR(a) ((a)?a:"") + static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) { sdlPtr tmpsdl = ctx->sdl; @@ -375,7 +377,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { load_schema(ctx, trav2); } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -436,7 +438,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); } } else if (!node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -546,7 +548,7 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -648,7 +650,7 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -680,14 +682,14 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) sdlParamPtr param; if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", SAFE_STR(trav->name)); } if (node_is_equal(trav,"documentation")) { trav = trav->next; continue; } if (!node_is_equal(trav,"part")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } part = trav; param = emalloc(sizeof(sdlParam)); @@ -696,7 +698,7 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) name = get_attribute(part->properties, "name"); if (name == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", message->name); + soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", SAFE_STR(message->name)); } param->paramName = estrdup((char*)name->children->content); @@ -765,7 +767,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav,"port")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } port = trav; @@ -804,7 +806,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -906,7 +908,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav2,"operation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } operation = trav2; @@ -925,7 +927,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) !node_is_equal(trav3,"output") && !node_is_equal(trav3,"fault") && !node_is_equal(trav3,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav3->name)); } trav3 = trav3->next; } @@ -1103,7 +1105,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } diff --git a/ext/soap/php_sdl.c.orig b/ext/soap/php_sdl.c.orig new file mode 100644 index 0000000000000..b8f1911f69722 --- /dev/null +++ b/ext/soap/php_sdl.c.orig @@ -0,0 +1,3673 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Brad Lafountain | + | Shane Caraveo | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ +/* $Id$ */ + +#include "php_soap.h" +#include "ext/libxml/php_libxml.h" +#include "libxml/uri.h" + +#include "ext/standard/md5.h" +#include "zend_virtual_cwd.h" + +#include +#include +#include + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +static void delete_fault(zval *zv); +static void delete_fault_persistent(zval *zv); +static void delete_binding(zval *zv); +static void delete_binding_persistent(zval *zv); +static void delete_function(zval *zv); +static void delete_function_persistent(zval *zv); +static void delete_parameter(zval *zv); +static void delete_parameter_persistent(zval *zv); +static void delete_header(zval *header); +static void delete_header_int(sdlSoapBindingFunctionHeaderPtr hdr); +static void delete_header_persistent(zval *zv); +static void delete_document(zval *zv); + +encodePtr get_encoder_from_prefix(sdlPtr sdl, xmlNodePtr node, const xmlChar *type) +{ + encodePtr enc = NULL; + xmlNsPtr nsptr; + char *ns, *cptype; + + parse_namespace(type, &cptype, &ns); + nsptr = xmlSearchNs(node->doc, node, BAD_CAST(ns)); + if (nsptr != NULL) { + enc = get_encoder(sdl, (char*)nsptr->href, cptype); + if (enc == NULL) { + enc = get_encoder_ex(sdl, cptype, strlen(cptype)); + } + } else { + enc = get_encoder_ex(sdl, (char*)type, xmlStrlen(type)); + } + efree(cptype); + if (ns) {efree(ns);} + return enc; +} + +static sdlTypePtr get_element(sdlPtr sdl, xmlNodePtr node, const xmlChar *type) +{ + sdlTypePtr ret = NULL; + + if (sdl->elements) { + xmlNsPtr nsptr; + char *ns, *cptype; + sdlTypePtr sdl_type; + + parse_namespace(type, &cptype, &ns); + nsptr = xmlSearchNs(node->doc, node, BAD_CAST(ns)); + if (nsptr != NULL) { + int ns_len = xmlStrlen(nsptr->href); + int type_len = strlen(cptype); + int len = ns_len + type_len + 1; + char *nscat = emalloc(len + 1); + + memcpy(nscat, nsptr->href, ns_len); + nscat[ns_len] = ':'; + memcpy(nscat+ns_len+1, cptype, type_len); + nscat[len] = '\0'; + + if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, nscat, len)) != NULL) { + ret = sdl_type; + } else if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, (char*)type, type_len)) != NULL) { + ret = sdl_type; + } + efree(nscat); + } else { + if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, (char*)type, xmlStrlen(type))) != NULL) { + ret = sdl_type; + } + } + + efree(cptype); + if (ns) {efree(ns);} + } + return ret; +} + +encodePtr get_encoder(sdlPtr sdl, const char *ns, const char *type) +{ + encodePtr enc = NULL; + char *nscat; + int ns_len = ns ? strlen(ns) : 0; + int type_len = strlen(type); + int len = ns_len + type_len + 1; + + nscat = emalloc(len + 1); + memcpy(nscat, ns, ns_len); + nscat[ns_len] = ':'; + memcpy(nscat+ns_len+1, type, type_len); + nscat[len] = '\0'; + + enc = get_encoder_ex(sdl, nscat, len); + + if (enc == NULL && + ((ns_len == sizeof(SOAP_1_1_ENC_NAMESPACE)-1 && + memcmp(ns, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)-1) == 0) || + (ns_len == sizeof(SOAP_1_2_ENC_NAMESPACE)-1 && + memcmp(ns, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)-1) == 0))) { + char *enc_nscat; + int enc_ns_len; + int enc_len; + + enc_ns_len = sizeof(XSD_NAMESPACE)-1; + enc_len = enc_ns_len + type_len + 1; + enc_nscat = emalloc(enc_len + 1); + memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); + enc_nscat[enc_ns_len] = ':'; + memcpy(enc_nscat+enc_ns_len+1, type, type_len); + enc_nscat[enc_len] = '\0'; + + enc = get_encoder_ex(NULL, enc_nscat, enc_len); + efree(enc_nscat); + if (enc && sdl) { + encodePtr new_enc = pemalloc(sizeof(encode), sdl->is_persistent); + memcpy(new_enc, enc, sizeof(encode)); + if (sdl->is_persistent) { + new_enc->details.ns = zend_strndup(ns, ns_len); + new_enc->details.type_str = strdup(new_enc->details.type_str); + } else { + new_enc->details.ns = estrndup(ns, ns_len); + new_enc->details.type_str = estrdup(new_enc->details.type_str); + } + if (sdl->encoders == NULL) { + sdl->encoders = pemalloc(sizeof(HashTable), sdl->is_persistent); + zend_hash_init(sdl->encoders, 0, NULL, delete_encoder, sdl->is_persistent); + } + zend_hash_str_update_ptr(sdl->encoders, nscat, len, new_enc); + enc = new_enc; + } + } + efree(nscat); + return enc; +} + +encodePtr get_encoder_ex(sdlPtr sdl, const char *nscat, int len) +{ + encodePtr enc; + + if ((enc = zend_hash_str_find_ptr(&SOAP_GLOBAL(defEnc), (char*)nscat, len)) != NULL) { + return enc; + } else if (sdl && sdl->encoders && (enc = zend_hash_str_find_ptr(sdl->encoders, (char*)nscat, len)) != NULL) { + return enc; + } + return NULL; +} + +sdlBindingPtr get_binding_from_type(sdlPtr sdl, sdlBindingType type) +{ + sdlBindingPtr binding; + + if (sdl == NULL) { + return NULL; + } + + ZEND_HASH_FOREACH_PTR(sdl->bindings, binding) { + if (binding->bindingType == type) { + return binding; + } + } ZEND_HASH_FOREACH_END(); + return NULL; +} + +sdlBindingPtr get_binding_from_name(sdlPtr sdl, char *name, char *ns) +{ + sdlBindingPtr binding; + smart_str key = {0}; + + smart_str_appends(&key, ns); + smart_str_appendc(&key, ':'); + smart_str_appends(&key, name); + smart_str_0(&key); + + binding = zend_hash_find_ptr(sdl->bindings, key.s); + + smart_str_free(&key); + return binding; +} + +static int is_wsdl_element(xmlNodePtr node) +{ + if (node->ns && strcmp((char*)node->ns->href, WSDL_NAMESPACE) != 0) { + xmlAttrPtr attr; + if ((attr = get_attribute_ex(node->properties, "required", WSDL_NAMESPACE)) != NULL && + attr->children && attr->children->content && + (strcmp((char*)attr->children->content, "1") == 0 || + strcmp((char*)attr->children->content, "true") == 0)) { + soap_error1(E_ERROR, "Parsing WSDL: Unknown required WSDL extension '%s'", node->ns->href); + } + return 0; + } + return 1; +} + +void sdl_set_uri_credentials(sdlCtx *ctx, char *uri) +{ + char *s; + size_t l1, l2; + zval context; + zval *header = NULL; + + /* check if we load xsd from the same server */ + s = strstr(ctx->sdl->source, "://"); + if (!s) return; + s = strchr(s+3, '/'); + l1 = s ? (size_t)(s - ctx->sdl->source) : strlen(ctx->sdl->source); + s = strstr((char*)uri, "://"); + if (!s) return; + s = strchr(s+3, '/'); + l2 = s ? (size_t)(s - (char*)uri) : strlen((char*)uri); + if (l1 != l2) { + /* check for http://...:80/ */ + if (l1 > 11 && + ctx->sdl->source[4] == ':' && + ctx->sdl->source[l1-3] == ':' && + ctx->sdl->source[l1-2] == '8' && + ctx->sdl->source[l1-1] == '0') { + l1 -= 3; + } + if (l2 > 11 && + uri[4] == ':' && + uri[l2-3] == ':' && + uri[l2-2] == '8' && + uri[l2-1] == '0') { + l2 -= 3; + } + /* check for https://...:443/ */ + if (l1 > 13 && + ctx->sdl->source[4] == 's' && + ctx->sdl->source[l1-4] == ':' && + ctx->sdl->source[l1-3] == '4' && + ctx->sdl->source[l1-2] == '4' && + ctx->sdl->source[l1-1] == '3') { + l1 -= 4; + } + if (l2 > 13 && + uri[4] == 's' && + uri[l2-4] == ':' && + uri[l2-3] == '4' && + uri[l2-2] == '4' && + uri[l2-1] == '3') { + l2 -= 4; + } + } + if (l1 != l2 || memcmp(ctx->sdl->source, uri, l1) != 0) { + /* another server. clear authentication credentals */ + php_libxml_switch_context(NULL, &context); + php_libxml_switch_context(&context, NULL); + if (Z_TYPE(context) != IS_UNDEF) { + zval *context_ptr = &context; + ctx->context = php_stream_context_from_zval(context_ptr, 1); + + if (ctx->context && + (header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL) { + s = strstr(Z_STRVAL_P(header), "Authorization: Basic"); + if (s && (s == Z_STRVAL_P(header) || *(s-1) == '\n' || *(s-1) == '\r')) { + char *rest = strstr(s, "\r\n"); + if (rest) { + zval new_header; + + rest += 2; + ZVAL_NEW_STR(&new_header, zend_string_alloc(Z_STRLEN_P(header) - (rest - s), 0)); + memcpy(Z_STRVAL(new_header), Z_STRVAL_P(header), s - Z_STRVAL_P(header)); + memcpy(Z_STRVAL(new_header) + (s - Z_STRVAL_P(header)), rest, Z_STRLEN_P(header) - (rest - Z_STRVAL_P(header)) + 1); + ZVAL_COPY(&ctx->old_header, header); + php_stream_context_set_option(ctx->context, "http", "header", &new_header); + zval_ptr_dtor(&new_header); + } + } + } + } + } +} + +void sdl_restore_uri_credentials(sdlCtx *ctx) +{ + if (Z_TYPE(ctx->old_header) != IS_UNDEF) { + php_stream_context_set_option(ctx->context, "http", "header", &ctx->old_header); + zval_ptr_dtor(&ctx->old_header); + ZVAL_UNDEF(&ctx->old_header); + } + ctx->context = NULL; +} + +static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) +{ + sdlPtr tmpsdl = ctx->sdl; + xmlDocPtr wsdl; + xmlNodePtr root, definitions, trav; + xmlAttrPtr targetNamespace; + + if (zend_hash_str_exists(&ctx->docs, struri, strlen(struri))) { + return; + } + + sdl_set_uri_credentials(ctx, struri); + wsdl = soap_xmlParseFile(struri); + sdl_restore_uri_credentials(ctx); + + if (!wsdl) { + xmlErrorPtr xmlErrorPtr = xmlGetLastError(); + + if (xmlErrorPtr) { + soap_error2(E_ERROR, "Parsing WSDL: Couldn't load from '%s' : %s", struri, xmlErrorPtr->message); + } else { + soap_error1(E_ERROR, "Parsing WSDL: Couldn't load from '%s'", struri); + } + } + + zend_hash_str_add_ptr(&ctx->docs, struri, strlen(struri), wsdl); + + root = wsdl->children; + definitions = get_node_ex(root, "definitions", WSDL_NAMESPACE); + if (!definitions) { + if (include) { + xmlNodePtr schema = get_node_ex(root, "schema", XSD_NAMESPACE); + if (schema) { + load_schema(ctx, schema); + return; + } + } + soap_error1(E_ERROR, "Parsing WSDL: Couldn't find in '%s'", struri); + } + + if (!include) { + targetNamespace = get_attribute(definitions->properties, "targetNamespace"); + if (targetNamespace) { + tmpsdl->target_ns = estrdup((char*)targetNamespace->children->content); + } + } + + trav = definitions->children; + while (trav != NULL) { + if (!is_wsdl_element(trav)) { + trav = trav->next; + continue; + } + if (node_is_equal(trav,"types")) { + /* TODO: Only one "types" is allowed */ + xmlNodePtr trav2 = trav->children; + + while (trav2 != NULL) { + if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { + load_schema(ctx, trav2); + } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + trav2 = trav2->next; + } + } else if (node_is_equal(trav,"import")) { + /* TODO: namespace ??? */ + xmlAttrPtr tmp = get_attribute(trav->properties, "location"); + if (tmp) { + xmlChar *uri; + xmlChar *base = xmlNodeGetBase(trav->doc, trav); + + if (base == NULL) { + uri = xmlBuildURI(tmp->children->content, trav->doc->URL); + } else { + uri = xmlBuildURI(tmp->children->content, base); + xmlFree(base); + } + load_wsdl_ex(this_ptr, (char*)uri, ctx, 1); + xmlFree(uri); + } + + } else if (node_is_equal(trav,"message")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->messages, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + } + + } else if (node_is_equal(trav,"portType")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->portTypes, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + } + + } else if (node_is_equal(trav,"binding")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->bindings, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + } + + } else if (node_is_equal(trav,"service")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->services, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + } + } else if (!node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } +} + +static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xmlNodePtr header, char* wsdl_soap_namespace, int fault) +{ + xmlAttrPtr tmp; + xmlNodePtr message, part; + char *ctype; + sdlSoapBindingFunctionHeaderPtr h; + + tmp = get_attribute(header->properties, "message"); + if (!tmp) { + soap_error0(E_ERROR, "Parsing WSDL: Missing message attribute for
"); + } + + ctype = strrchr((char*)tmp->children->content,':'); + if (ctype == NULL) { + ctype = (char*)tmp->children->content; + } else { + ++ctype; + } + if ((message = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", tmp->children->content); + } + + tmp = get_attribute(header->properties, "part"); + if (!tmp) { + soap_error0(E_ERROR, "Parsing WSDL: Missing part attribute for
"); + } + part = get_node_with_attribute_ex(message->children, "part", WSDL_NAMESPACE, "name", (char*)tmp->children->content, NULL); + if (!part) { + soap_error1(E_ERROR, "Parsing WSDL: Missing part '%s' in ", tmp->children->content); + } + + h = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(h, 0, sizeof(sdlSoapBindingFunctionHeader)); + h->name = estrdup((char*)tmp->children->content); + + tmp = get_attribute(header->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "encoded", sizeof("encoded"))) { + h->use = SOAP_ENCODED; + } else { + h->use = SOAP_LITERAL; + } + + tmp = get_attribute(header->properties, "namespace"); + if (tmp) { + h->ns = estrdup((char*)tmp->children->content); + } + + if (h->use == SOAP_ENCODED) { + tmp = get_attribute(header->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + h->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + h->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + + tmp = get_attribute(part->properties, "type"); + if (tmp != NULL) { + h->encode = get_encoder_from_prefix(ctx->sdl, part, tmp->children->content); + } else { + tmp = get_attribute(part->properties, "element"); + if (tmp != NULL) { + h->element = get_element(ctx->sdl, part, tmp->children->content); + if (h->element) { + h->encode = h->element->encode; + if (!h->ns && h->element->namens) { + h->ns = estrdup(h->element->namens); + } + if (h->element->name) { + efree(h->name); + h->name = estrdup(h->element->name); + } + } + } + } + if (!fault) { + xmlNodePtr trav = header->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "headerfault", wsdl_soap_namespace)) { + sdlSoapBindingFunctionHeaderPtr hf = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, 1); + smart_str key = {0}; + + if (h->headerfaults == NULL) { + h->headerfaults = emalloc(sizeof(HashTable)); + zend_hash_init(h->headerfaults, 0, NULL, delete_header, 0); + } + + if (hf->ns) { + smart_str_appends(&key,hf->ns); + smart_str_appendc(&key,':'); + } + smart_str_appends(&key,hf->name); + smart_str_0(&key); + if (zend_hash_add_ptr(h->headerfaults, key.s, hf) == NULL) { + delete_header_int(hf); + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } + } + return h; +} + +static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap_namespace, sdlSoapBindingFunctionBody *binding, HashTable* params) +{ + xmlNodePtr body, trav; + xmlAttrPtr tmp; + + trav = node->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "body", wsdl_soap_namespace)) { + body = trav; + + tmp = get_attribute(body->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "literal", sizeof("literal"))) { + binding->use = SOAP_LITERAL; + } else { + binding->use = SOAP_ENCODED; + } + + tmp = get_attribute(body->properties, "namespace"); + if (tmp) { + binding->ns = estrdup((char*)tmp->children->content); + } + + tmp = get_attribute(body->properties, "parts"); + if (tmp) { + HashTable ht; + char *parts = (char*)tmp->children->content; + + /* Delete all parts those are not in the "parts" attribute */ + zend_hash_init(&ht, 0, NULL, delete_parameter, 0); + while (*parts) { + sdlParamPtr param; + int found = 0; + char *end; + + while (*parts == ' ') ++parts; + if (*parts == '\0') break; + end = strchr(parts, ' '); + if (end) *end = '\0'; + ZEND_HASH_FOREACH_PTR(params, param) { + if (param->paramName && + strcmp(parts, param->paramName) == 0) { + sdlParamPtr x_param; + x_param = emalloc(sizeof(sdlParam)); + *x_param = *param; + param->paramName = NULL; + zend_hash_next_index_insert_ptr(&ht, x_param); + found = 1; + break; + } + } ZEND_HASH_FOREACH_END(); + if (!found) { + soap_error1(E_ERROR, "Parsing WSDL: Missing part '%s' in ", parts); + } + parts += strlen(parts); + if (end) *end = ' '; + } + zend_hash_destroy(params); + *params = ht; + } + + if (binding->use == SOAP_ENCODED) { + tmp = get_attribute(body->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + } else if (node_is_equal_ex(trav, "header", wsdl_soap_namespace)) { + sdlSoapBindingFunctionHeaderPtr h = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, 0); + smart_str key = {0}; + + if (binding->headers == NULL) { + binding->headers = emalloc(sizeof(HashTable)); + zend_hash_init(binding->headers, 0, NULL, delete_header, 0); + } + + if (h->ns) { + smart_str_appends(&key,h->ns); + smart_str_appendc(&key,':'); + } + smart_str_appends(&key,h->name); + smart_str_0(&key); + if (zend_hash_add_ptr(binding->headers, key.s, h) == NULL) { + delete_header_int(h); + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } +} + +static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) +{ + xmlNodePtr trav, part, message = NULL, tmp; + HashTable* parameters = NULL; + char *ctype; + + ctype = strrchr((char*)message_name,':'); + if (ctype == NULL) { + ctype = (char*)message_name; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", message_name); + } + message = tmp; + + parameters = emalloc(sizeof(HashTable)); + zend_hash_init(parameters, 0, NULL, delete_parameter, 0); + + trav = message->children; + while (trav != NULL) { + xmlAttrPtr element, type, name; + sdlParamPtr param; + + if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); + } + if (node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"part")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + part = trav; + param = emalloc(sizeof(sdlParam)); + memset(param,0,sizeof(sdlParam)); + param->order = 0; + + name = get_attribute(part->properties, "name"); + if (name == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", message->name); + } + + param->paramName = estrdup((char*)name->children->content); + + type = get_attribute(part->properties, "type"); + if (type != NULL) { + param->encode = get_encoder_from_prefix(ctx->sdl, part, type->children->content); + } else { + element = get_attribute(part->properties, "element"); + if (element != NULL) { + param->element = get_element(ctx->sdl, part, element->children->content); + if (param->element) { + param->encode = param->element->encode; + } + } + } + + zend_hash_next_index_insert_ptr(parameters, param); + + trav = trav->next; + } + return parameters; +} + +static sdlPtr load_wsdl(zval *this_ptr, char *struri) +{ + sdlCtx ctx; + int i,n; + + memset(&ctx,0,sizeof(ctx)); + ctx.sdl = emalloc(sizeof(sdl)); + memset(ctx.sdl, 0, sizeof(sdl)); + ctx.sdl->source = estrdup(struri); + zend_hash_init(&ctx.sdl->functions, 0, NULL, delete_function, 0); + + zend_hash_init(&ctx.docs, 0, NULL, delete_document, 0); + zend_hash_init(&ctx.messages, 0, NULL, NULL, 0); + zend_hash_init(&ctx.bindings, 0, NULL, NULL, 0); + zend_hash_init(&ctx.portTypes, 0, NULL, NULL, 0); + zend_hash_init(&ctx.services, 0, NULL, NULL, 0); + + load_wsdl_ex(this_ptr, struri,&ctx, 0); + schema_pass2(&ctx); + + n = zend_hash_num_elements(&ctx.services); + if (n > 0) { + zend_hash_internal_pointer_reset(&ctx.services); + for (i = 0; i < n; i++) { + xmlNodePtr service, tmp; + xmlNodePtr trav, port; + int has_soap_port = 0; + + service = tmp = zend_hash_get_current_data_ptr(&ctx.services); + + trav = service->children; + while (trav != NULL) { + xmlAttrPtr type, name, bindingAttr, location; + xmlNodePtr portType, operation; + xmlNodePtr address, binding, trav2; + char *ctype; + sdlBindingPtr tmpbinding; + char *wsdl_soap_namespace = NULL; + + if (!is_wsdl_element(trav) || node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"port")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + + port = trav; + + tmpbinding = emalloc(sizeof(sdlBinding)); + memset(tmpbinding, 0, sizeof(sdlBinding)); + + bindingAttr = get_attribute(port->properties, "binding"); + if (bindingAttr == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: No binding associated with "); + } + + /* find address and figure out binding type */ + address = NULL; + trav2 = port->children; + while (trav2 != NULL) { + if (node_is_equal(trav2,"address") && trav2->ns) { + if (!strncmp((char*)trav2->ns->href, WSDL_SOAP11_NAMESPACE, sizeof(WSDL_SOAP11_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = WSDL_SOAP11_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_SOAP12_NAMESPACE, sizeof(WSDL_SOAP12_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = WSDL_SOAP12_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, RPC_SOAP12_NAMESPACE, sizeof(RPC_SOAP12_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = RPC_SOAP12_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_HTTP11_NAMESPACE, sizeof(WSDL_HTTP11_NAMESPACE))) { + address = trav2; + tmpbinding->bindingType = BINDING_HTTP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_HTTP12_NAMESPACE, sizeof(WSDL_HTTP12_NAMESPACE))) { + address = trav2; + tmpbinding->bindingType = BINDING_HTTP; + } + } + if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + trav2 = trav2->next; + } + if (!address || tmpbinding->bindingType == BINDING_HTTP) { + if (has_soap_port || trav->next || i < n-1) { + efree(tmpbinding); + trav = trav->next; + continue; + } else if (!address) { + soap_error0(E_ERROR, "Parsing WSDL: No address associated with "); + } + } + has_soap_port = 1; + + location = get_attribute(address->properties, "location"); + if (!location) { + soap_error0(E_ERROR, "Parsing WSDL: No location associated with "); + } + + tmpbinding->location = estrdup((char*)location->children->content); + + ctype = strrchr((char*)bindingAttr->children->content,':'); + if (ctype == NULL) { + ctype = (char*)bindingAttr->children->content; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx.bindings, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: No element with name '%s'", ctype); + } + binding = tmp; + + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapBindingNode; + xmlAttrPtr tmp; + + soapBinding = emalloc(sizeof(sdlSoapBinding)); + memset(soapBinding, 0, sizeof(sdlSoapBinding)); + soapBinding->style = SOAP_DOCUMENT; + + soapBindingNode = get_node_ex(binding->children, "binding", wsdl_soap_namespace); + if (soapBindingNode) { + tmp = get_attribute(soapBindingNode->properties, "style"); + if (tmp && !strncmp((char*)tmp->children->content, "rpc", sizeof("rpc"))) { + soapBinding->style = SOAP_RPC; + } + + tmp = get_attribute(soapBindingNode->properties, "transport"); + if (tmp) { + if (strncmp((char*)tmp->children->content, WSDL_HTTP_TRANSPORT, sizeof(WSDL_HTTP_TRANSPORT)) == 0) { + soapBinding->transport = SOAP_TRANSPORT_HTTP; + } else { + /* try the next binding */ + efree(soapBinding); + efree(tmpbinding->location); + efree(tmpbinding); + trav = trav->next; + continue; + } + } + } + tmpbinding->bindingAttributes = (void *)soapBinding; + } + + name = get_attribute(binding->properties, "name"); + if (name == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for "); + } + tmpbinding->name = estrdup((char*)name->children->content); + + type = get_attribute(binding->properties, "type"); + if (type == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'type' attribute for "); + } + + ctype = strrchr((char*)type->children->content,':'); + if (ctype == NULL) { + ctype = (char*)type->children->content; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx.portTypes, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", name->children->content); + } + portType = tmp; + + trav2 = binding->children; + while (trav2 != NULL) { + sdlFunctionPtr function; + xmlNodePtr input, output, fault, portTypeOperation, trav3; + xmlAttrPtr op_name, paramOrder; + + if ((tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav2, "binding", wsdl_soap_namespace)) || + !is_wsdl_element(trav2) || + node_is_equal(trav2,"documentation")) { + trav2 = trav2->next; + continue; + } + if (!node_is_equal(trav2,"operation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + + operation = trav2; + + op_name = get_attribute(operation->properties, "name"); + if (op_name == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for "); + } + + trav3 = operation->children; + while (trav3 != NULL) { + if (tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav3, "operation", wsdl_soap_namespace)) { + } else if (is_wsdl_element(trav3) && + !node_is_equal(trav3,"input") && + !node_is_equal(trav3,"output") && + !node_is_equal(trav3,"fault") && + !node_is_equal(trav3,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); + } + trav3 = trav3->next; + } + + portTypeOperation = get_node_with_attribute_ex(portType->children, "operation", WSDL_NAMESPACE, "name", (char*)op_name->children->content, NULL); + if (portTypeOperation == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing / with name '%s'", op_name->children->content); + } + + function = emalloc(sizeof(sdlFunction)); + memset(function, 0, sizeof(sdlFunction)); + function->functionName = estrdup((char*)op_name->children->content); + + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunctionBinding; + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapOperation; + xmlAttrPtr tmp; + + soapFunctionBinding = emalloc(sizeof(sdlSoapBindingFunction)); + memset(soapFunctionBinding, 0, sizeof(sdlSoapBindingFunction)); + soapBinding = (sdlSoapBindingPtr)tmpbinding->bindingAttributes; + soapFunctionBinding->style = soapBinding->style; + + soapOperation = get_node_ex(operation->children, "operation", wsdl_soap_namespace); + if (soapOperation) { + tmp = get_attribute(soapOperation->properties, "soapAction"); + if (tmp) { + soapFunctionBinding->soapAction = estrdup((char*)tmp->children->content); + } + + tmp = get_attribute(soapOperation->properties, "style"); + if (tmp) { + if (!strncmp((char*)tmp->children->content, "rpc", sizeof("rpc"))) { + soapFunctionBinding->style = SOAP_RPC; + } else { + soapFunctionBinding->style = SOAP_DOCUMENT; + } + } else { + soapFunctionBinding->style = soapBinding->style; + } + } + + function->bindingAttributes = (void *)soapFunctionBinding; + } + + input = get_node_ex(portTypeOperation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + xmlAttrPtr message; + + message = get_attribute(input->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); + } + function->requestParameters = wsdl_message(&ctx, message->children->content); + +/* FIXME + xmlAttrPtr name = get_attribute(input->properties, "name"); + if (name != NULL) { + function->requestName = estrdup(name->children->content); + } else { +*/ + { + function->requestName = estrdup(function->functionName); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + input = get_node_ex(operation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + wsdl_soap_binding_body(&ctx, input, wsdl_soap_namespace, &soapFunctionBinding->input, function->requestParameters); + } + } + } + + output = get_node_ex(portTypeOperation->children, "output", WSDL_NAMESPACE); + if (output != NULL) { + xmlAttrPtr message; + + message = get_attribute(output->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); + } + function->responseParameters = wsdl_message(&ctx, message->children->content); + +/* FIXME + xmlAttrPtr name = get_attribute(output->properties, "name"); + if (name != NULL) { + function->responseName = estrdup(name->children->content); + } else if (input == NULL) { + function->responseName = estrdup(function->functionName); + } else { +*/ + { + int len = strlen(function->functionName); + function->responseName = emalloc(len + sizeof("Response")); + memcpy(function->responseName, function->functionName, len); + memcpy(function->responseName+len, "Response", sizeof("Response")); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + output = get_node_ex(operation->children, "output", WSDL_NAMESPACE); + if (output != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + wsdl_soap_binding_body(&ctx, output, wsdl_soap_namespace, &soapFunctionBinding->output, function->responseParameters); + } + } + } + + paramOrder = get_attribute(portTypeOperation->properties, "parameterOrder"); + if (paramOrder) { + /* FIXME: */ + } + + fault = portTypeOperation->children; + while (fault != NULL) { + if (node_is_equal_ex(fault, "fault", WSDL_NAMESPACE)) { + xmlAttrPtr message, name; + sdlFaultPtr f; + + name = get_attribute(fault->properties, "name"); + if (name == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); + } + message = get_attribute(fault->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); + } + + f = emalloc(sizeof(sdlFault)); + memset(f, 0, sizeof(sdlFault)); + + f->name = estrdup((char*)name->children->content); + f->details = wsdl_message(&ctx, message->children->content); + if (f->details == NULL || zend_hash_num_elements(f->details) > 1) { + soap_error1(E_ERROR, "Parsing WSDL: The fault message '%s' must have a single part", message->children->content); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + xmlNodePtr soap_fault = get_node_with_attribute_ex(operation->children, "fault", WSDL_NAMESPACE, "name", f->name, NULL); + if (soap_fault != NULL) { + xmlNodePtr trav = soap_fault->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "fault", wsdl_soap_namespace)) { + xmlAttrPtr tmp; + sdlSoapBindingFunctionFaultPtr binding; + + binding = f->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); + memset(f->bindingAttributes, 0, sizeof(sdlSoapBindingFunctionFault)); + + tmp = get_attribute(trav->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "encoded", sizeof("encoded"))) { + binding->use = SOAP_ENCODED; + } else { + binding->use = SOAP_LITERAL; + } + + tmp = get_attribute(trav->properties, "namespace"); + if (tmp) { + binding->ns = estrdup((char*)tmp->children->content); + } + + if (binding->use == SOAP_ENCODED) { + tmp = get_attribute(trav->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } + } + } + if (function->faults == NULL) { + function->faults = emalloc(sizeof(HashTable)); + zend_hash_init(function->faults, 0, NULL, delete_fault, 0); + } + if (zend_hash_str_add_ptr(function->faults, f->name, strlen(f->name), f) == NULL) { + soap_error2(E_ERROR, "Parsing WSDL: with name '%s' already defined in '%s'", f->name, op_name->children->content); + } + } + fault = fault->next; + } + + function->binding = tmpbinding; + + { + char *tmp = estrdup(function->functionName); + int len = strlen(tmp); + + if (zend_hash_str_add_ptr(&ctx.sdl->functions, php_strtolower(tmp, len), len, function) == NULL) { + zend_hash_next_index_insert_ptr(&ctx.sdl->functions, function); + } + efree(tmp); + if (function->requestName != NULL && strcmp(function->requestName,function->functionName) != 0) { + if (ctx.sdl->requests == NULL) { + ctx.sdl->requests = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.sdl->requests, 0, NULL, NULL, 0); + } + tmp = estrdup(function->requestName); + len = strlen(tmp); + zend_hash_str_add_ptr(ctx.sdl->requests, php_strtolower(tmp, len), len, function); + efree(tmp); + } + } + trav2 = trav2->next; + } + + if (!ctx.sdl->bindings) { + ctx.sdl->bindings = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.sdl->bindings, 0, NULL, delete_binding, 0); + } + + if (!zend_hash_str_add_ptr(ctx.sdl->bindings, tmpbinding->name, strlen(tmpbinding->name), tmpbinding)) { + zend_hash_next_index_insert_ptr(ctx.sdl->bindings, tmpbinding); + } + trav= trav->next; + } + + zend_hash_move_forward(&ctx.services); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Couldn't bind to service"); + } + + if (ctx.sdl->bindings == NULL || ctx.sdl->bindings->nNumOfElements == 0) { + soap_error0(E_ERROR, "Parsing WSDL: Could not find any usable binding services in WSDL."); + } + + zend_hash_destroy(&ctx.messages); + zend_hash_destroy(&ctx.bindings); + zend_hash_destroy(&ctx.portTypes); + zend_hash_destroy(&ctx.services); + zend_hash_destroy(&ctx.docs); + + return ctx.sdl; +} + +#define WSDL_CACHE_VERSION 0x0f + +#define WSDL_CACHE_GET(ret,type,buf) memcpy(&ret,*buf,sizeof(type)); *buf += sizeof(type); +#define WSDL_CACHE_GET_INT(ret,buf) ret = ((unsigned char)(*buf)[0])|((unsigned char)(*buf)[1]<<8)|((unsigned char)(*buf)[2]<<16)|((int)(*buf)[3]<<24); *buf += 4; +#define WSDL_CACHE_GET_1(ret,type,buf) ret = (type)(**buf); (*buf)++; +#define WSDL_CACHE_GET_N(ret,n,buf) memcpy(ret,*buf,n); *buf += n; +#define WSDL_CACHE_SKIP(n,buf) *buf += n; + +#define WSDL_CACHE_PUT_INT(val,buf) smart_str_appendc(buf,val & 0xff); \ + smart_str_appendc(buf,(val >> 8) & 0xff); \ + smart_str_appendc(buf,(val >> 16) & 0xff); \ + smart_str_appendc(buf,(val >> 24) & 0xff); +#define WSDL_CACHE_PUT_1(val,buf) smart_str_appendc(buf,val); +#define WSDL_CACHE_PUT_N(val,n,buf) smart_str_appendl(buf,(char*)val,n); + +static char* sdl_deserialize_string(char **in) +{ + char *s; + int len; + + WSDL_CACHE_GET_INT(len, in); + if (len == 0x7fffffff) { + return NULL; + } else { + s = emalloc(len+1); + WSDL_CACHE_GET_N(s, len, in); + s[len] = '\0'; + return s; + } +} + +static void sdl_deserialize_key(HashTable* ht, void* data, char **in) +{ + int len; + + WSDL_CACHE_GET_INT(len, in); + if (len == 0) { + zend_hash_next_index_insert_ptr(ht, data); + } else { + zend_hash_str_add_ptr(ht, *in, len, data); + WSDL_CACHE_SKIP(len, in); + } +} + +static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, char **in) +{ + int i; + + attr->name = sdl_deserialize_string(in); + attr->namens = sdl_deserialize_string(in); + attr->ref = sdl_deserialize_string(in); + attr->def = sdl_deserialize_string(in); + attr->fixed = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(attr->form, sdlForm, in); + WSDL_CACHE_GET_1(attr->use, sdlUse, in); + WSDL_CACHE_GET_INT(i, in); + attr->encode = encoders[i]; + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + attr->extraAttributes = emalloc(sizeof(HashTable)); + zend_hash_init(attr->extraAttributes, i, NULL, delete_extra_attribute, 0); + while (i > 0) { + sdlExtraAttributePtr x = emalloc(sizeof(sdlExtraAttribute)); + sdl_deserialize_key(attr->extraAttributes, x, in); + x->ns = sdl_deserialize_string(in); + x->val = sdl_deserialize_string(in); + --i; + } + } +} + +static sdlRestrictionIntPtr sdl_deserialize_resriction_int(char **in) +{ + if (**in == 1) { + sdlRestrictionIntPtr x = emalloc(sizeof(sdlRestrictionInt)); + WSDL_CACHE_SKIP(1, in); + WSDL_CACHE_GET_INT(x->value, in); + WSDL_CACHE_GET_1(x->fixed, char, in); + return x; + } else { + WSDL_CACHE_SKIP(1, in); + return NULL; + } +} + +static sdlRestrictionCharPtr sdl_deserialize_resriction_char(char **in) +{ + if (**in == 1) { + sdlRestrictionCharPtr x = emalloc(sizeof(sdlRestrictionChar)); + WSDL_CACHE_SKIP(1, in); + x->value = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(x->fixed, char, in); + return x; + } else { + WSDL_CACHE_SKIP(1, in); + return NULL; + } +} + +static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *elements, char **in) +{ + int i; + sdlContentModelPtr model = emalloc(sizeof(sdlContentModel)); + + WSDL_CACHE_GET_1(model->kind, sdlContentKind, in); + WSDL_CACHE_GET_INT(model->min_occurs, in); + WSDL_CACHE_GET_INT(model->max_occurs, in); + switch (model->kind) { + case XSD_CONTENT_ELEMENT: + WSDL_CACHE_GET_INT(i, in); + model->u.element = elements[i]; + break; + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: + WSDL_CACHE_GET_INT(i, in); + model->u.content = emalloc(sizeof(HashTable)); + zend_hash_init(model->u.content, i, NULL, delete_model, 0); + while (i > 0) { + sdlContentModelPtr x = sdl_deserialize_model(types, elements, in); + zend_hash_next_index_insert_ptr(model->u.content, x); + i--; + } + break; + case XSD_CONTENT_GROUP_REF: + model->u.group_ref = sdl_deserialize_string(in); + break; + case XSD_CONTENT_GROUP: + WSDL_CACHE_GET_INT(i, in); + model->u.group = types[i]; + break; + default: + break; + } + return model; +} + +static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr *encoders, char **in) +{ + int i; + sdlTypePtr *elements = NULL; + + WSDL_CACHE_GET_1(type->kind, sdlTypeKind, in); + type->name = sdl_deserialize_string(in); + type->namens = sdl_deserialize_string(in); + type->def = sdl_deserialize_string(in); + type->fixed = sdl_deserialize_string(in); + type->ref = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(type->nillable, char, in); + WSDL_CACHE_GET_1(type->form, sdlForm, in); + + WSDL_CACHE_GET_INT(i, in); + type->encode = encoders[i]; + + if (**in == 1) { + WSDL_CACHE_SKIP(1, in); + type->restrictions = emalloc(sizeof(sdlRestrictions)); + /*memset(type->restrictions, 0, sizeof(sdlRestrictions));*/ + type->restrictions->minExclusive = sdl_deserialize_resriction_int(in); + type->restrictions->minInclusive = sdl_deserialize_resriction_int(in); + type->restrictions->maxExclusive = sdl_deserialize_resriction_int(in); + type->restrictions->maxInclusive = sdl_deserialize_resriction_int(in); + type->restrictions->totalDigits = sdl_deserialize_resriction_int(in); + type->restrictions->fractionDigits = sdl_deserialize_resriction_int(in); + type->restrictions->length = sdl_deserialize_resriction_int(in); + type->restrictions->minLength = sdl_deserialize_resriction_int(in); + type->restrictions->maxLength = sdl_deserialize_resriction_int(in); + type->restrictions->whiteSpace = sdl_deserialize_resriction_char(in); + type->restrictions->pattern = sdl_deserialize_resriction_char(in); + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + type->restrictions->enumeration = emalloc(sizeof(HashTable)); + zend_hash_init(type->restrictions->enumeration, i, NULL, delete_restriction_var_char, 0); + while (i > 0) { + sdlRestrictionCharPtr x = sdl_deserialize_resriction_char(in); + sdl_deserialize_key(type->restrictions->enumeration, x, in); + --i; + } + } else { + type->restrictions->enumeration = NULL; + } + } else { + WSDL_CACHE_SKIP(1, in); + } + + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + elements = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); + elements[0] = NULL; + type->elements = emalloc(sizeof(HashTable)); + zend_hash_init(type->elements, i, NULL, delete_type, 0); + while (i > 0) { + sdlTypePtr t = emalloc(sizeof(sdlType)); + memset(t, 0, sizeof(sdlType)); + sdl_deserialize_key(type->elements, t, in); + sdl_deserialize_type(t, types, encoders, in); + elements[i] = t; + --i; + } + } + + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + type->attributes = emalloc(sizeof(HashTable)); + zend_hash_init(type->attributes, i, NULL, delete_attribute, 0); + while (i > 0) { + sdlAttributePtr attr = emalloc(sizeof(sdlAttribute)); + memset(attr, 0, sizeof(sdlAttribute)); + sdl_deserialize_key(type->attributes, attr, in); + sdl_deserialize_attribute(attr, encoders, in); + --i; + } + } + + if (**in != 0) { + WSDL_CACHE_SKIP(1, in); + type->model = sdl_deserialize_model(types, elements, in); + } else { + WSDL_CACHE_SKIP(1, in); + } + if (elements != NULL) { + efree(elements); + } +} + +static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) +{ + int i; + + WSDL_CACHE_GET_INT(enc->details.type, in); + enc->details.type_str = sdl_deserialize_string(in); + enc->details.ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(i, in); + enc->details.sdl_type = types[i]; + enc->to_xml = sdl_guess_convert_xml; + enc->to_zval = sdl_guess_convert_zval; + + if (enc->details.sdl_type == NULL) { + int ns_len = strlen(enc->details.ns); + int type_len = strlen(enc->details.type_str); + + if (((ns_len == sizeof(SOAP_1_1_ENC_NAMESPACE)-1 && + memcmp(enc->details.ns, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)-1) == 0) || + (ns_len == sizeof(SOAP_1_2_ENC_NAMESPACE)-1 && + memcmp(enc->details.ns, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)-1) == 0))) { + char *enc_nscat; + int enc_ns_len; + int enc_len; + encodePtr real_enc; + + enc_ns_len = sizeof(XSD_NAMESPACE)-1; + enc_len = enc_ns_len + type_len + 1; + enc_nscat = emalloc(enc_len + 1); + memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); + enc_nscat[enc_ns_len] = ':'; + memcpy(enc_nscat+enc_ns_len+1, enc->details.type_str, type_len); + enc_nscat[enc_len] = '\0'; + + real_enc = get_encoder_ex(NULL, enc_nscat, enc_len); + efree(enc_nscat); + if (real_enc) { + enc->to_zval = real_enc->to_zval; + enc->to_xml = real_enc->to_xml; + } + } + } +} + +static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encodePtr *encoders, sdlTypePtr *types, char **in) +{ + int i, j, n; + + WSDL_CACHE_GET_1(body->use, sdlEncodingUse, in); + if (body->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(body->encodingStyle, sdlRpcEncodingStyle, in); + } else { + body->encodingStyle = SOAP_ENCODING_DEFAULT; + } + body->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + body->headers = emalloc(sizeof(HashTable)); + zend_hash_init(body->headers, i, NULL, delete_header, 0); + while (i > 0) { + sdlSoapBindingFunctionHeaderPtr tmp = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(tmp, 0, sizeof(sdlSoapBindingFunctionHeader)); + sdl_deserialize_key(body->headers, tmp, in); + WSDL_CACHE_GET_1(tmp->use, sdlEncodingUse, in); + if (tmp->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(tmp->encodingStyle, sdlRpcEncodingStyle, in); + } else { + tmp->encodingStyle = SOAP_ENCODING_DEFAULT; + } + tmp->name = sdl_deserialize_string(in); + tmp->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(n, in); + tmp->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + tmp->element = types[n]; + --i; + WSDL_CACHE_GET_INT(j, in); + if (j > 0) { + tmp->headerfaults = emalloc(sizeof(HashTable)); + zend_hash_init(tmp->headerfaults, i, NULL, delete_header, 0); + while (j > 0) { + sdlSoapBindingFunctionHeaderPtr tmp2 = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(tmp2, 0, sizeof(sdlSoapBindingFunctionHeader)); + sdl_deserialize_key(tmp->headerfaults, tmp2, in); + WSDL_CACHE_GET_1(tmp2->use, sdlEncodingUse, in); + if (tmp2->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(tmp2->encodingStyle, sdlRpcEncodingStyle, in); + } else { + tmp2->encodingStyle = SOAP_ENCODING_DEFAULT; + } + tmp2->name = sdl_deserialize_string(in); + tmp2->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(n, in); + tmp2->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + tmp2->element = types[n]; + --j; + } + } + } + } +} + +static HashTable* sdl_deserialize_parameters(encodePtr *encoders, sdlTypePtr *types, char **in) +{ + int i, n; + HashTable *ht; + + WSDL_CACHE_GET_INT(i, in); + if (i == 0) {return NULL;} + ht = emalloc(sizeof(HashTable)); + zend_hash_init(ht, i, NULL, delete_parameter, 0); + while (i > 0) { + sdlParamPtr param = emalloc(sizeof(sdlParam)); + sdl_deserialize_key(ht, param, in); + param->paramName = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(param->order, in); + WSDL_CACHE_GET_INT(n, in); + param->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + param->element = types[n]; + --i; + } + return ht; +} + +static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, time_t t, time_t *cached) +{ + sdlPtr sdl; + time_t old_t; + int i, num_groups, num_types, num_elements, num_encoders, num_bindings, num_func; + sdlFunctionPtr *functions = NULL; + sdlBindingPtr *bindings; + sdlTypePtr *types; + encodePtr *encoders; + encodePtr enc; + + int f; + struct stat st; + char *in, *buf; + + f = open(fn, O_RDONLY|O_BINARY); + if (f < 0) { + return NULL; + } + if (fstat(f, &st) != 0) { + close(f); + return NULL; + } + buf = in = emalloc(st.st_size); + if (read(f, in, st.st_size) != st.st_size) { + close(f); + efree(in); + return NULL; + } + close(f); + + if (strncmp(in,"wsdl",4) != 0 || in[4] != WSDL_CACHE_VERSION || in[5] != '\0') { + unlink(fn); + efree(buf); + return NULL; + } + in += 6; + + WSDL_CACHE_GET(old_t, time_t, &in); + if (old_t < t) { + unlink(fn); + efree(buf); + return NULL; + } + *cached = old_t; + + WSDL_CACHE_GET_INT(i, &in); + if (i == 0 && strncmp(in, uri, i) != 0) { + unlink(fn); + efree(buf); + return NULL; + } + WSDL_CACHE_SKIP(i, &in); + + sdl = emalloc(sizeof(*sdl)); + memset(sdl, 0, sizeof(*sdl)); + + sdl->source = sdl_deserialize_string(&in); + sdl->target_ns = sdl_deserialize_string(&in); + + WSDL_CACHE_GET_INT(num_groups, &in); + WSDL_CACHE_GET_INT(num_types, &in); + WSDL_CACHE_GET_INT(num_elements, &in); + WSDL_CACHE_GET_INT(num_encoders, &in); + + i = num_groups+num_types+num_elements; + types = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); + types[0] = NULL; + while (i > 0) { + types[i] = emalloc(sizeof(sdlType)); + memset(types[i], 0, sizeof(sdlType)); + i--; + } + + i = num_encoders; + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + i++; enc++; + } + encoders = safe_emalloc((i+1), sizeof(encodePtr), 0); + i = num_encoders; + encoders[0] = NULL; + while (i > 0) { + encoders[i] = emalloc(sizeof(encode)); + memset(encoders[i], 0, sizeof(encode)); + i--; + } + i = num_encoders; + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + encoders[++i] = enc++; + } + + i = 1; + if (num_groups > 0) { + sdl->groups = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->groups, num_groups, NULL, delete_type, 0); + while (i < num_groups+1) { + sdl_deserialize_key(sdl->groups, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + if (num_types > 0) { + sdl->types = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->types, num_types, NULL, delete_type, 0); + while (i < num_groups+num_types+1) { + sdl_deserialize_key(sdl->types, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + if (num_elements > 0) { + sdl->elements = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->elements, num_elements, NULL, delete_type, 0); + while (i < num_groups+num_types+num_elements+1) { + sdl_deserialize_key(sdl->elements, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + i = 1; + if (num_encoders > 0) { + sdl->encoders = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->encoders, num_encoders, NULL, delete_encoder, 0); + while (i < num_encoders+1) { + sdl_deserialize_key(sdl->encoders, encoders[i], &in); + sdl_deserialize_encoder(encoders[i], types, &in); + i++; + } + } + + /* deserialize bindings */ + WSDL_CACHE_GET_INT(num_bindings, &in); + bindings = safe_emalloc(num_bindings, sizeof(sdlBindingPtr), 0); + if (num_bindings > 0) { + sdl->bindings = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->bindings, num_bindings, NULL, delete_binding, 0); + for (i = 0; i < num_bindings; i++) { + sdlBindingPtr binding = emalloc(sizeof(sdlBinding)); + memset(binding, 0, sizeof(sdlBinding)); + sdl_deserialize_key(sdl->bindings, binding, &in); + binding->name = sdl_deserialize_string(&in); + binding->location = sdl_deserialize_string(&in); + WSDL_CACHE_GET_1(binding->bindingType,sdlBindingType,&in); + if (binding->bindingType == BINDING_SOAP && *in != 0) { + sdlSoapBindingPtr soap_binding = binding->bindingAttributes = emalloc(sizeof(sdlSoapBinding)); + WSDL_CACHE_GET_1(soap_binding->style,sdlEncodingStyle,&in); + WSDL_CACHE_GET_1(soap_binding->transport,sdlTransport,&in); + } else { + WSDL_CACHE_SKIP(1,&in); + } + bindings[i] = binding; + } + } + + /* deserialize functions */ + WSDL_CACHE_GET_INT(num_func, &in); + zend_hash_init(&sdl->functions, num_func, NULL, delete_function, 0); + if (num_func > 0) { + functions = safe_emalloc(num_func, sizeof(sdlFunctionPtr), 0); + for (i = 0; i < num_func; i++) { + int binding_num, num_faults; + sdlFunctionPtr func = emalloc(sizeof(sdlFunction)); + sdl_deserialize_key(&sdl->functions, func, &in); + func->functionName = sdl_deserialize_string(&in); + func->requestName = sdl_deserialize_string(&in); + func->responseName = sdl_deserialize_string(&in); + + WSDL_CACHE_GET_INT(binding_num, &in); + if (binding_num == 0) { + func->binding = NULL; + } else { + func->binding = bindings[binding_num-1]; + } + if (func->binding && func->binding->bindingType == BINDING_SOAP && *in != 0) { + sdlSoapBindingFunctionPtr binding = func->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunction)); + memset(binding, 0, sizeof(sdlSoapBindingFunction)); + WSDL_CACHE_GET_1(binding->style,sdlEncodingStyle,&in); + binding->soapAction = sdl_deserialize_string(&in); + sdl_deserialize_soap_body(&binding->input, encoders, types, &in); + sdl_deserialize_soap_body(&binding->output, encoders, types, &in); + } else { + WSDL_CACHE_SKIP(1, &in); + func->bindingAttributes = NULL; + } + + func->requestParameters = sdl_deserialize_parameters(encoders, types, &in); + func->responseParameters = sdl_deserialize_parameters(encoders, types, &in); + + WSDL_CACHE_GET_INT(num_faults, &in); + if (num_faults > 0) { + int j; + + func->faults = emalloc(sizeof(HashTable)); + zend_hash_init(func->faults, num_faults, NULL, delete_fault, 0); + + for (j = 0; j < num_faults; j++) { + sdlFaultPtr fault = emalloc(sizeof(sdlFault)); + + sdl_deserialize_key(func->faults, fault, &in); + fault->name =sdl_deserialize_string(&in); + fault->details =sdl_deserialize_parameters(encoders, types, &in); + if (*in != 0) { + sdlSoapBindingFunctionFaultPtr binding = fault->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); + memset(binding, 0, sizeof(sdlSoapBindingFunctionFault)); + WSDL_CACHE_GET_1(binding->use,sdlEncodingUse,&in); + if (binding->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(binding->encodingStyle, sdlRpcEncodingStyle, &in); + } else { + binding->encodingStyle = SOAP_ENCODING_DEFAULT; + } + binding->ns = sdl_deserialize_string(&in); + } else { + WSDL_CACHE_SKIP(1, &in); + fault->bindingAttributes = NULL; + } + } + } else { + func->faults = NULL; + } + functions[i] = func; + } + } + + /* deserialize requests */ + WSDL_CACHE_GET_INT(i, &in); + if (i > 0) { + sdl->requests = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->requests, i, NULL, NULL, 0); + while (i > 0) { + int function_num; + + WSDL_CACHE_GET_INT(function_num, &in); + sdl_deserialize_key(sdl->requests, functions[function_num-1], &in); + i--; + } + } + + if (functions) { + efree(functions); + } + efree(bindings); + efree(encoders); + efree(types); + efree(buf); + return sdl; +} + +static void sdl_serialize_string(const char *str, smart_str *out) +{ + int i; + + if (str) { + i = strlen(str); + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + WSDL_CACHE_PUT_N(str, i, out); + } + } else { + WSDL_CACHE_PUT_INT(0x7fffffff, out); + } +} + +// TODO: refactor it +static void sdl_serialize_key(zend_string *key, smart_str *out) +{ + if (key) { + WSDL_CACHE_PUT_INT(ZSTR_LEN(key), out); + WSDL_CACHE_PUT_N(ZSTR_VAL(key), ZSTR_LEN(key), out); + } else { + WSDL_CACHE_PUT_INT(0, out); + } +} + +static void sdl_serialize_encoder_ref(encodePtr enc, HashTable *tmp_encoders, smart_str *out) { + if (enc) { + zval *encoder_num; + if ((encoder_num = zend_hash_str_find(tmp_encoders, (char*)&enc, sizeof(enc))) != 0) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(encoder_num), out); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + } else { + WSDL_CACHE_PUT_INT(0, out); + } +} + +static void sdl_serialize_type_ref(sdlTypePtr type, HashTable *tmp_types, smart_str *out) { + if (type) { + zval *type_num; + if ((type_num = zend_hash_str_find(tmp_types, (char*)&type, sizeof(type))) != NULL) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(type_num), out); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + } else { + WSDL_CACHE_PUT_INT(0,out); + } +} + +static void sdl_serialize_attribute(sdlAttributePtr attr, HashTable *tmp_encoders, smart_str *out) +{ + int i; + + sdl_serialize_string(attr->name, out); + sdl_serialize_string(attr->namens, out); + sdl_serialize_string(attr->ref, out); + sdl_serialize_string(attr->def, out); + sdl_serialize_string(attr->fixed, out); + WSDL_CACHE_PUT_1(attr->form, out); + WSDL_CACHE_PUT_1(attr->use, out); + sdl_serialize_encoder_ref(attr->encode, tmp_encoders, out); + if (attr->extraAttributes) { + i = zend_hash_num_elements(attr->extraAttributes); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlExtraAttributePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(attr->extraAttributes, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->ns, out); + sdl_serialize_string(tmp->val, out); + } ZEND_HASH_FOREACH_END(); + } +} + +static void sdl_serialize_model(sdlContentModelPtr model, HashTable *tmp_types, HashTable *tmp_elements, smart_str *out) +{ + WSDL_CACHE_PUT_1(model->kind, out); + WSDL_CACHE_PUT_INT(model->min_occurs, out); + WSDL_CACHE_PUT_INT(model->max_occurs, out); + switch (model->kind) { + case XSD_CONTENT_ELEMENT: + sdl_serialize_type_ref(model->u.element, tmp_elements, out); + break; + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: { + sdlContentModelPtr tmp; + int i = zend_hash_num_elements(model->u.content); + + WSDL_CACHE_PUT_INT(i, out); + ZEND_HASH_FOREACH_PTR(model->u.content, tmp) { + sdl_serialize_model(tmp, tmp_types, tmp_elements, out); + } ZEND_HASH_FOREACH_END(); + } + break; + case XSD_CONTENT_GROUP_REF: + sdl_serialize_string(model->u.group_ref,out); + break; + case XSD_CONTENT_GROUP: + sdl_serialize_type_ref(model->u.group, tmp_types, out); + break; + default: + break; + } +} + +static void sdl_serialize_resriction_int(sdlRestrictionIntPtr x, smart_str *out) +{ + if (x) { + WSDL_CACHE_PUT_1(1, out); + WSDL_CACHE_PUT_INT(x->value, out); + WSDL_CACHE_PUT_1(x->fixed, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } +} + +static void sdl_serialize_resriction_char(sdlRestrictionCharPtr x, smart_str *out) +{ + if (x) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_string(x->value, out); + WSDL_CACHE_PUT_1(x->fixed, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } +} + +static void sdl_serialize_type(sdlTypePtr type, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i; + HashTable *tmp_elements = NULL; + + WSDL_CACHE_PUT_1(type->kind, out); + sdl_serialize_string(type->name, out); + sdl_serialize_string(type->namens, out); + sdl_serialize_string(type->def, out); + sdl_serialize_string(type->fixed, out); + sdl_serialize_string(type->ref, out); + WSDL_CACHE_PUT_1(type->nillable, out); + WSDL_CACHE_PUT_1(type->form, out); + sdl_serialize_encoder_ref(type->encode, tmp_encoders, out); + + if (type->restrictions) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_resriction_int(type->restrictions->minExclusive,out); + sdl_serialize_resriction_int(type->restrictions->minInclusive,out); + sdl_serialize_resriction_int(type->restrictions->maxExclusive,out); + sdl_serialize_resriction_int(type->restrictions->maxInclusive,out); + sdl_serialize_resriction_int(type->restrictions->totalDigits,out); + sdl_serialize_resriction_int(type->restrictions->fractionDigits,out); + sdl_serialize_resriction_int(type->restrictions->length,out); + sdl_serialize_resriction_int(type->restrictions->minLength,out); + sdl_serialize_resriction_int(type->restrictions->maxLength,out); + sdl_serialize_resriction_char(type->restrictions->whiteSpace,out); + sdl_serialize_resriction_char(type->restrictions->pattern,out); + if (type->restrictions->enumeration) { + i = zend_hash_num_elements(type->restrictions->enumeration); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlRestrictionCharPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->restrictions->enumeration, key, tmp) { + sdl_serialize_resriction_char(tmp, out); + sdl_serialize_key(key, out); + } ZEND_HASH_FOREACH_END(); + } + } else { + WSDL_CACHE_PUT_1(0, out); + } + if (type->elements) { + i = zend_hash_num_elements(type->elements); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zend_string *key; + zval zv; + + tmp_elements = emalloc(sizeof(HashTable)); + zend_hash_init(tmp_elements, i, NULL, NULL, 0); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->elements, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, tmp_encoders, tmp_types, out); + ZVAL_LONG(&zv, i); + zend_hash_str_add(tmp_elements, (char*)&tmp, sizeof(tmp), &zv); + i--; + } ZEND_HASH_FOREACH_END(); + } + + if (type->attributes) { + i = zend_hash_num_elements(type->attributes); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlAttributePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->attributes, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_attribute(tmp, tmp_encoders, out); + } ZEND_HASH_FOREACH_END(); + } + if (type->model) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_model(type->model, tmp_types, tmp_elements, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } + if (tmp_elements != NULL) { + zend_hash_destroy(tmp_elements); + efree(tmp_elements); + } +} + +static void sdl_serialize_encoder(encodePtr enc, HashTable *tmp_types, smart_str *out) +{ + WSDL_CACHE_PUT_INT(enc->details.type, out); + sdl_serialize_string(enc->details.type_str, out); + sdl_serialize_string(enc->details.ns, out); + sdl_serialize_type_ref(enc->details.sdl_type, tmp_types, out); +} + +static void sdl_serialize_parameters(HashTable *ht, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i; + + if (ht) { + i = zend_hash_num_elements(ht); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlParamPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(ht, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->paramName, out); + WSDL_CACHE_PUT_INT(tmp->order, out); + sdl_serialize_encoder_ref(tmp->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp->element, tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } +} + +static void sdl_serialize_soap_body(sdlSoapBindingFunctionBodyPtr body, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i, j; + + WSDL_CACHE_PUT_1(body->use, out); + if (body->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(body->encodingStyle, out); + } + sdl_serialize_string(body->ns, out); + if (body->headers) { + i = zend_hash_num_elements(body->headers); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlSoapBindingFunctionHeaderPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(body->headers, key, tmp) { + sdl_serialize_key(key, out); + WSDL_CACHE_PUT_1(tmp->use, out); + if (tmp->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(tmp->encodingStyle, out); + } + sdl_serialize_string(tmp->name, out); + sdl_serialize_string(tmp->ns, out); + sdl_serialize_encoder_ref(tmp->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp->element, tmp_types, out); + if (tmp->headerfaults) { + j = zend_hash_num_elements(tmp->headerfaults); + } else { + j = 0; + } + WSDL_CACHE_PUT_INT(j, out); + if (j > 0) { + sdlSoapBindingFunctionHeaderPtr tmp2; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(body->headers, key, tmp2) { + sdl_serialize_key(key, out); + WSDL_CACHE_PUT_1(tmp2->use, out); + if (tmp2->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(tmp2->encodingStyle, out); + } + sdl_serialize_string(tmp2->name, out); + sdl_serialize_string(tmp2->ns, out); + sdl_serialize_encoder_ref(tmp2->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp2->element, tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void add_sdl_to_cache(const char *fn, const char *uri, time_t t, sdlPtr sdl) +{ + smart_str buf = {0}; + smart_str *out = &buf; + int i; + int type_num = 1; + int encoder_num = 1; + int f; + encodePtr enc; + HashTable tmp_types; + HashTable tmp_encoders; + HashTable tmp_bindings; + HashTable tmp_functions; + +#ifdef ZEND_WIN32 + f = open(fn,O_CREAT|O_WRONLY|O_EXCL|O_BINARY,S_IREAD|S_IWRITE); +#else + f = open(fn,O_CREAT|O_WRONLY|O_EXCL|O_BINARY,S_IREAD|S_IWRITE); +#endif + if (f < 0) {return;} + + zend_hash_init(&tmp_types, 0, NULL, NULL, 0); + zend_hash_init(&tmp_encoders, 0, NULL, NULL, 0); + zend_hash_init(&tmp_bindings, 0, NULL, NULL, 0); + zend_hash_init(&tmp_functions, 0, NULL, NULL, 0); + + WSDL_CACHE_PUT_N("wsdl", 4, out); + WSDL_CACHE_PUT_1(WSDL_CACHE_VERSION,out); + WSDL_CACHE_PUT_1(0,out); + WSDL_CACHE_PUT_N(&t, sizeof(t), out); + + sdl_serialize_string(uri, out); + sdl_serialize_string(sdl->source, out); + sdl_serialize_string(sdl->target_ns, out); + + if (sdl->groups) { + i = zend_hash_num_elements(sdl->groups); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->groups, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + i = zend_hash_num_elements(sdl->types); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->types, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + i = zend_hash_num_elements(sdl->elements); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->elements, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + i = zend_hash_num_elements(sdl->encoders); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + encodePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->encoders, tmp) { + ZVAL_LONG(&zv, encoder_num); + zend_hash_str_add(&tmp_encoders, (char*)&tmp, sizeof(tmp), &zv); + ++encoder_num; + } ZEND_HASH_FOREACH_END(); + } + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + zval zv; + + ZVAL_LONG(&zv, encoder_num); + zend_hash_str_add(&tmp_encoders, (char*)&enc, sizeof(encodePtr), &zv); + enc++; + ++encoder_num; + } + + if (sdl->groups) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->groups, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->types, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->elements, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + encodePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->encoders, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_encoder(tmp, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + /* serialize bindings */ + if (sdl->bindings) { + i = zend_hash_num_elements(sdl->bindings); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlBindingPtr tmp; + int binding_num = 1; + zval zv; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->bindings, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->name, out); + sdl_serialize_string(tmp->location, out); + WSDL_CACHE_PUT_1(tmp->bindingType,out); + if (tmp->bindingType == BINDING_SOAP && tmp->bindingAttributes != NULL) { + sdlSoapBindingPtr binding = (sdlSoapBindingPtr)tmp->bindingAttributes; + WSDL_CACHE_PUT_1(binding->style, out); + WSDL_CACHE_PUT_1(binding->transport, out); + } else { + WSDL_CACHE_PUT_1(0,out); + } + + ZVAL_LONG(&zv, binding_num); + zend_hash_str_add(&tmp_bindings, (char*)&tmp, sizeof(tmp), &zv); + binding_num++; + } ZEND_HASH_FOREACH_END(); + } + + /* serialize functions */ + i = zend_hash_num_elements(&sdl->functions); + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlFunctionPtr tmp; + zval *binding_num, zv; + int function_num = 1; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&sdl->functions, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->functionName, out); + sdl_serialize_string(tmp->requestName, out); + sdl_serialize_string(tmp->responseName, out); + + if (tmp->binding) { + binding_num = zend_hash_str_find(&tmp_bindings,(char*)&tmp->binding, sizeof(tmp->binding)); + if (binding_num) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(binding_num), out); + if (Z_LVAL_P(binding_num) >= 0) { + if (tmp->binding->bindingType == BINDING_SOAP && tmp->bindingAttributes != NULL) { + sdlSoapBindingFunctionPtr binding = (sdlSoapBindingFunctionPtr)tmp->bindingAttributes; + WSDL_CACHE_PUT_1(binding->style, out); + sdl_serialize_string(binding->soapAction, out); + sdl_serialize_soap_body(&binding->input, &tmp_encoders, &tmp_types, out); + sdl_serialize_soap_body(&binding->output, &tmp_encoders, &tmp_types, out); + } else { + WSDL_CACHE_PUT_1(0,out); + } + } + } + } + sdl_serialize_parameters(tmp->requestParameters, &tmp_encoders, &tmp_types, out); + sdl_serialize_parameters(tmp->responseParameters, &tmp_encoders, &tmp_types, out); + + if (tmp->faults) { + sdlFaultPtr fault; + zend_string *key; + + WSDL_CACHE_PUT_INT(zend_hash_num_elements(tmp->faults), out); + + ZEND_HASH_FOREACH_STR_KEY_PTR(tmp->faults, key, fault) { + sdl_serialize_key(key, out); + sdl_serialize_string(fault->name, out); + sdl_serialize_parameters(fault->details, &tmp_encoders, &tmp_types, out); + if (tmp->binding->bindingType == BINDING_SOAP && fault->bindingAttributes != NULL) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + WSDL_CACHE_PUT_1(binding->use, out); + if (binding->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(binding->encodingStyle, out); + } + sdl_serialize_string(binding->ns, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } + } ZEND_HASH_FOREACH_END(); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + + ZVAL_LONG(&zv, function_num); + zend_hash_str_add(&tmp_functions, (char*)&tmp, sizeof(tmp), &zv); + function_num++; + } ZEND_HASH_FOREACH_END(); + } + + /* serialize requests */ + if (sdl->requests) { + i = zend_hash_num_elements(sdl->requests); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlFunctionPtr tmp; + zval *function_num; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->requests, key, tmp) { + function_num = zend_hash_str_find(&tmp_functions, (char*)&tmp, sizeof(tmp)); + WSDL_CACHE_PUT_INT(Z_LVAL_P(function_num), out); + sdl_serialize_key(key, out); + } ZEND_HASH_FOREACH_END(); + } + + php_ignore_value(write(f, ZSTR_VAL(buf.s), ZSTR_LEN(buf.s))); + close(f); + smart_str_free(&buf); + zend_hash_destroy(&tmp_functions); + zend_hash_destroy(&tmp_bindings); + zend_hash_destroy(&tmp_encoders); + zend_hash_destroy(&tmp_types); +} + + +static void make_persistent_restriction_int(void *data) +{ + sdlRestrictionIntPtr *rest = (sdlRestrictionIntPtr *)data; + sdlRestrictionIntPtr prest = NULL; + + prest = malloc(sizeof(sdlRestrictionInt)); + *prest = **rest; + *rest = prest; +} + + +static void make_persistent_restriction_char_int(sdlRestrictionCharPtr *rest) +{ + sdlRestrictionCharPtr prest = NULL; + + prest = malloc(sizeof(sdlRestrictionChar)); + memset(prest, 0, sizeof(sdlRestrictionChar)); + prest->value = strdup((*rest)->value); + prest->fixed = (*rest)->fixed; + *rest = prest; +} + + +static void make_persistent_restriction_char(zval *zv) +{ + make_persistent_restriction_char_int((sdlRestrictionCharPtr*)&Z_PTR_P(zv)); +} + + +static void make_persistent_sdl_type_ref(sdlTypePtr *type, HashTable *ptr_map, HashTable *bp_types) +{ + sdlTypePtr tmp; + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char *)type, sizeof(sdlTypePtr))) != NULL) { + *type = tmp; + } else { + zend_hash_next_index_insert_ptr(bp_types, *type); + } +} + + +static void make_persistent_sdl_encoder_ref(encodePtr *enc, HashTable *ptr_map, HashTable *bp_encoders) +{ + encodePtr tmp; + + /* do not process defaultEncoding's here */ + if ((*enc) >= defaultEncoding && (*enc) < defaultEncoding + numDefaultEncodings) { + return; + } + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char *)enc, sizeof(encodePtr))) != NULL) { + *enc = tmp; + } else { + zend_hash_next_index_insert_ptr(bp_encoders, enc); + } +} + + +static HashTable* make_persistent_sdl_function_headers(HashTable *headers, HashTable *ptr_map) +{ + HashTable *pheaders; + sdlSoapBindingFunctionHeaderPtr tmp, pheader; + encodePtr penc; + sdlTypePtr ptype; + zend_string *key; + + pheaders = malloc(sizeof(HashTable)); + zend_hash_init(pheaders, zend_hash_num_elements(headers), NULL, delete_header_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(headers, key, tmp) { + pheader = malloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(pheader, 0, sizeof(sdlSoapBindingFunctionHeader)); + *pheader = *tmp; + + if (pheader->name) { + pheader->name = strdup(pheader->name); + } + if (pheader->ns) { + pheader->ns = strdup(pheader->ns); + } + + if (pheader->encode->details.sdl_type) { + if ((penc = zend_hash_str_find_ptr(ptr_map, (char*)&pheader->encode, sizeof(encodePtr))) == NULL) { + assert(0); + } + pheader->encode = penc; + } + if (pheader->element) { + if ((ptype = zend_hash_str_find_ptr(ptr_map, (char*)&pheader->element, sizeof(sdlTypePtr))) == NULL) { + assert(0); + } + pheader->element = ptype; + } + + if (pheader->headerfaults) { + pheader->headerfaults = make_persistent_sdl_function_headers(pheader->headerfaults, ptr_map); + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pheaders, ZSTR_VAL(key), ZSTR_LEN(key), pheader); + } else { + zend_hash_next_index_insert_ptr(pheaders, pheader); + } + } ZEND_HASH_FOREACH_END(); + + return pheaders; +} + + +static void make_persistent_sdl_soap_body(sdlSoapBindingFunctionBodyPtr body, HashTable *ptr_map) +{ + if (body->ns) { + body->ns = strdup(body->ns); + } + + if (body->headers) { + body->headers = make_persistent_sdl_function_headers(body->headers, ptr_map); + } +} + + +static HashTable* make_persistent_sdl_parameters(HashTable *params, HashTable *ptr_map) +{ + HashTable *pparams; + sdlParamPtr tmp, pparam; + sdlTypePtr ptype; + encodePtr penc; + zend_string *key; + + pparams = malloc(sizeof(HashTable)); + zend_hash_init(pparams, zend_hash_num_elements(params), NULL, delete_parameter_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(params, key, tmp) { + pparam = malloc(sizeof(sdlParam)); + memset(pparam, 0, sizeof(sdlParam)); + *pparam = *tmp; + + if (pparam->paramName) { + pparam->paramName = strdup(pparam->paramName); + } + + if (pparam->encode && pparam->encode->details.sdl_type) { + if ((penc = zend_hash_str_find_ptr(ptr_map, (char*)&pparam->encode, sizeof(encodePtr))) == NULL) { + assert(0); + } + pparam->encode = penc; + } + if (pparam->element) { + if ((ptype = zend_hash_str_find_ptr(ptr_map, (char*)&pparam->element, sizeof(sdlTypePtr))) == NULL) { + assert(0); + } + pparam->element = ptype; + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pparams, ZSTR_VAL(key), ZSTR_LEN(key), pparam); + } else { + zend_hash_next_index_insert_ptr(pparams, pparam); + } + } ZEND_HASH_FOREACH_END(); + + return pparams; +} + +static HashTable* make_persistent_sdl_function_faults(sdlFunctionPtr func, HashTable *faults, HashTable *ptr_map) +{ + HashTable *pfaults; + sdlFaultPtr tmp, pfault; + zend_string *key; + + pfaults = malloc(sizeof(HashTable)); + zend_hash_init(pfaults, zend_hash_num_elements(faults), NULL, delete_fault_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(faults, key, tmp) { + pfault = malloc(sizeof(sdlFault)); + memset(pfault, 0, sizeof(sdlFault)); + *pfault = *tmp; + + if (pfault->name) { + pfault->name = strdup(pfault->name); + } + if (pfault->details) { + pfault->details = make_persistent_sdl_parameters(pfault->details, ptr_map); + } + + if (func->binding->bindingType == BINDING_SOAP && pfault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBindingFunctionFault)); + memset(soap_binding, 0, sizeof(sdlSoapBindingFunctionFault)); + *soap_binding = *(sdlSoapBindingFunctionFaultPtr)pfault->bindingAttributes; + if (soap_binding->ns) { + soap_binding->ns = strdup(soap_binding->ns); + } + pfault->bindingAttributes = soap_binding; + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pfaults, ZSTR_VAL(key), ZSTR_LEN(key), pfault); + } else { + zend_hash_next_index_insert_ptr(pfaults, pfault); + } + + } ZEND_HASH_FOREACH_END(); + + return pfaults; +} + + +static sdlAttributePtr make_persistent_sdl_attribute(sdlAttributePtr attr, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + sdlAttributePtr pattr; + zend_string *key; + + pattr = malloc(sizeof(sdlAttribute)); + memset(pattr, 0, sizeof(sdlAttribute)); + + *pattr = *attr; + + if (pattr->name) { + pattr->name = strdup(pattr->name); + } + if (pattr->namens) { + pattr->namens = strdup(pattr->namens); + } + if (pattr->ref) { + pattr->ref = strdup(pattr->ref); + } + if (pattr->def) { + pattr->def = strdup(pattr->def); + } + if (pattr->fixed) { + pattr->fixed = strdup(pattr->fixed); + } + + /* we do not want to process defaultEncoding's here */ + if (pattr->encode) { + make_persistent_sdl_encoder_ref(&pattr->encode, ptr_map, bp_encoders); + } + + if (pattr->extraAttributes) { + sdlExtraAttributePtr tmp, pextra; + + pattr->extraAttributes = malloc(sizeof(HashTable)); + zend_hash_init(pattr->extraAttributes, zend_hash_num_elements(attr->extraAttributes), NULL, delete_extra_attribute_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(attr->extraAttributes, key, tmp) { + if (key) { + pextra = malloc(sizeof(sdlExtraAttribute)); + memset(pextra, 0, sizeof(sdlExtraAttribute)); + + if (tmp->ns) { + pextra->ns = strdup(tmp->ns); + } + if (tmp->val) { + pextra->val = strdup(tmp->val); + } + + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pattr->extraAttributes, ZSTR_VAL(key), ZSTR_LEN(key), pextra); + } + } ZEND_HASH_FOREACH_END(); + } + + return pattr; +} + + +static sdlContentModelPtr make_persistent_sdl_model(sdlContentModelPtr model, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + sdlContentModelPtr pmodel; + sdlContentModelPtr tmp, pcontent; + + pmodel = malloc(sizeof(sdlContentModel)); + memset(pmodel, 0, sizeof(sdlContentModel)); + *pmodel = *model; + + switch (pmodel->kind) { + case XSD_CONTENT_ELEMENT: + if (pmodel->u.element) { + make_persistent_sdl_type_ref(&pmodel->u.element, ptr_map, bp_types); + } + break; + + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: + pmodel->u.content = malloc(sizeof(HashTable)); + zend_hash_init(pmodel->u.content, zend_hash_num_elements(model->u.content), NULL, delete_model_persistent, 1); + + ZEND_HASH_FOREACH_PTR(model->u.content, tmp) { + pcontent = make_persistent_sdl_model(tmp, ptr_map, bp_types, bp_encoders); + zend_hash_next_index_insert_ptr(pmodel->u.content, pcontent); + } ZEND_HASH_FOREACH_END(); + break; + + case XSD_CONTENT_GROUP_REF: + if (pmodel->u.group_ref) { + pmodel->u.group_ref = strdup(pmodel->u.group_ref); + } + break; + + case XSD_CONTENT_GROUP: + if (pmodel->u.group) { + make_persistent_sdl_type_ref(&pmodel->u.group, ptr_map, bp_types); + } + break; + + default: + break; + } + + return pmodel; +} + + +static sdlTypePtr make_persistent_sdl_type(sdlTypePtr type, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + zend_string *key; + sdlTypePtr ptype = NULL; + + ptype = malloc(sizeof(sdlType)); + memset(ptype, 0, sizeof(sdlType)); + + *ptype = *type; + + if (ptype->name) { + ptype->name = strdup(ptype->name); + } + if (ptype->namens) { + ptype->namens = strdup(ptype->namens); + } + if (ptype->def) { + ptype->def = strdup(ptype->def); + } + if (ptype->fixed) { + ptype->fixed = strdup(ptype->fixed); + } + if (ptype->ref) { + ptype->ref = strdup(ptype->ref); + } + + /* we do not want to process defaultEncoding's here */ + if (ptype->encode) { + make_persistent_sdl_encoder_ref(&ptype->encode, ptr_map, bp_encoders); + } + + if (ptype->restrictions) { + ptype->restrictions = malloc(sizeof(sdlRestrictions)); + memset(ptype->restrictions, 0, sizeof(sdlRestrictions)); + *ptype->restrictions = *type->restrictions; + + if (ptype->restrictions->minExclusive) { + make_persistent_restriction_int(&ptype->restrictions->minExclusive); + } + if (ptype->restrictions->maxExclusive) { + make_persistent_restriction_int(&ptype->restrictions->maxExclusive); + } + if (ptype->restrictions->minInclusive) { + make_persistent_restriction_int(&ptype->restrictions->minInclusive); + } + if (ptype->restrictions->maxInclusive) { + make_persistent_restriction_int(&ptype->restrictions->maxInclusive); + } + if (ptype->restrictions->totalDigits) { + make_persistent_restriction_int(&ptype->restrictions->totalDigits); + } + if (ptype->restrictions->fractionDigits) { + make_persistent_restriction_int(&ptype->restrictions->fractionDigits); + } + if (ptype->restrictions->length) { + make_persistent_restriction_int(&ptype->restrictions->length); + } + if (ptype->restrictions->minLength) { + make_persistent_restriction_int(&ptype->restrictions->minLength); + } + if (ptype->restrictions->maxLength) { + make_persistent_restriction_int(&ptype->restrictions->maxLength); + } + if (ptype->restrictions->whiteSpace) { + make_persistent_restriction_char_int(&ptype->restrictions->whiteSpace); + } + if (ptype->restrictions->pattern) { + make_persistent_restriction_char_int(&ptype->restrictions->pattern); + } + + if (type->restrictions->enumeration) { + ptype->restrictions->enumeration = malloc(sizeof(HashTable)); + zend_hash_init(ptype->restrictions->enumeration, zend_hash_num_elements(type->restrictions->enumeration), NULL, delete_restriction_var_char_persistent, 1); + zend_hash_copy(ptype->restrictions->enumeration, type->restrictions->enumeration, make_persistent_restriction_char); + } + } + + if (ptype->elements) { + sdlTypePtr tmp, pelem; + + ptype->elements = malloc(sizeof(HashTable)); + zend_hash_init(ptype->elements, zend_hash_num_elements(type->elements), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->elements, key, tmp) { + pelem = make_persistent_sdl_type(tmp, ptr_map, bp_types, bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(ptype->elements, ZSTR_VAL(key), ZSTR_LEN(key), pelem); + } else { + zend_hash_next_index_insert_ptr(ptype->elements, pelem); + } + zend_hash_str_add_ptr(ptr_map, (char*)&tmp, sizeof(tmp), pelem); + } ZEND_HASH_FOREACH_END(); + } + + if (ptype->attributes) { + sdlAttributePtr tmp, pattr; + + ptype->attributes = malloc(sizeof(HashTable)); + zend_hash_init(ptype->attributes, zend_hash_num_elements(type->attributes), NULL, delete_attribute_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->attributes, key, tmp) { + pattr = make_persistent_sdl_attribute(tmp, ptr_map, bp_types, bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(ptype->attributes, ZSTR_VAL(key), ZSTR_LEN(key), pattr); + } else { + zend_hash_next_index_insert_ptr(ptype->attributes, pattr); + } + } ZEND_HASH_FOREACH_END(); + } + + if (type->model) { + ptype->model = make_persistent_sdl_model(ptype->model, ptr_map, bp_types, bp_encoders); + } + + return ptype; +} + +static encodePtr make_persistent_sdl_encoder(encodePtr enc, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + encodePtr penc = NULL; + + penc = malloc(sizeof(encode)); + memset(penc, 0, sizeof(encode)); + + *penc = *enc; + + if (penc->details.type_str) { + penc->details.type_str = strdup(penc->details.type_str); + } + if (penc->details.ns) { + penc->details.ns = strdup(penc->details.ns); + } + + if (penc->details.sdl_type) { + make_persistent_sdl_type_ref(&penc->details.sdl_type, ptr_map, bp_types); + } + + return penc; +} + +static sdlBindingPtr make_persistent_sdl_binding(sdlBindingPtr bind, HashTable *ptr_map) +{ + sdlBindingPtr pbind = NULL; + + pbind = malloc(sizeof(sdlBinding)); + memset(pbind, 0, sizeof(sdlBinding)); + + *pbind = *bind; + + if (pbind->name) { + pbind->name = strdup(pbind->name); + } + if (pbind->location) { + pbind->location = strdup(pbind->location); + } + + if (pbind->bindingType == BINDING_SOAP && pbind->bindingAttributes) { + sdlSoapBindingPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBinding)); + memset(soap_binding, 0, sizeof(sdlSoapBinding)); + *soap_binding = *(sdlSoapBindingPtr)pbind->bindingAttributes; + pbind->bindingAttributes = soap_binding; + } + + return pbind; +} + +static sdlFunctionPtr make_persistent_sdl_function(sdlFunctionPtr func, HashTable *ptr_map) +{ + sdlFunctionPtr pfunc = NULL; + + pfunc = malloc(sizeof(sdlFunction)); + memset(pfunc, 0, sizeof(sdlFunction)); + + *pfunc = *func; + + if (pfunc->functionName) { + pfunc->functionName = strdup(pfunc->functionName); + } + if (pfunc->requestName) { + pfunc->requestName = strdup(pfunc->requestName); + } + if (pfunc->responseName) { + pfunc->responseName = strdup(pfunc->responseName); + } + + if (pfunc->binding) { + sdlBindingPtr tmp; + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char*)&pfunc->binding, sizeof(pfunc->binding))) == NULL) { + assert(0); + } + pfunc->binding = tmp; + + if (pfunc->binding->bindingType == BINDING_SOAP && pfunc->bindingAttributes) { + sdlSoapBindingFunctionPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBindingFunction)); + memset(soap_binding, 0, sizeof(sdlSoapBindingFunction)); + *soap_binding = *(sdlSoapBindingFunctionPtr)pfunc->bindingAttributes; + if (soap_binding->soapAction) { + soap_binding->soapAction = strdup(soap_binding->soapAction); + } + make_persistent_sdl_soap_body(&soap_binding->input, ptr_map); + make_persistent_sdl_soap_body(&soap_binding->output, ptr_map); + pfunc->bindingAttributes = soap_binding; + } + + if (pfunc->requestParameters) { + pfunc->requestParameters = make_persistent_sdl_parameters(pfunc->requestParameters, ptr_map); + } + if (pfunc->responseParameters) { + pfunc->responseParameters = make_persistent_sdl_parameters(pfunc->responseParameters, ptr_map); + } + if (pfunc->faults) { + pfunc->faults = make_persistent_sdl_function_faults(pfunc, pfunc->faults, ptr_map); + } + } + + return pfunc; +} + +static sdlPtr make_persistent_sdl(sdlPtr sdl) +{ + sdlPtr psdl = NULL; + HashTable ptr_map; + HashTable bp_types, bp_encoders; + zend_string *key; + + zend_hash_init(&bp_types, 0, NULL, NULL, 0); + zend_hash_init(&bp_encoders, 0, NULL, NULL, 0); + zend_hash_init(&ptr_map, 0, NULL, NULL, 0); + + psdl = malloc(sizeof(*sdl)); + memset(psdl, 0, sizeof(*sdl)); + + if (sdl->source) { + psdl->source = strdup(sdl->source); + } + if (sdl->target_ns) { + psdl->target_ns = strdup(sdl->target_ns); + } + + if (sdl->groups) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->groups = malloc(sizeof(HashTable)); + zend_hash_init(psdl->groups, zend_hash_num_elements(sdl->groups), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->groups, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->groups, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->groups, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->types = malloc(sizeof(HashTable)); + zend_hash_init(psdl->types, zend_hash_num_elements(sdl->types), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->types, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->types, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->types, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->elements = malloc(sizeof(HashTable)); + zend_hash_init(psdl->elements, zend_hash_num_elements(sdl->elements), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->elements, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->elements, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->elements, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + encodePtr tmp; + encodePtr penc; + + psdl->encoders = malloc(sizeof(HashTable)); + zend_hash_init(psdl->encoders, zend_hash_num_elements(sdl->encoders), NULL, delete_encoder_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->encoders, key, tmp) { + penc = make_persistent_sdl_encoder(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->encoders, ZSTR_VAL(key), ZSTR_LEN(key), penc); + } else { + zend_hash_next_index_insert_ptr(psdl->encoders, penc); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), penc); + } ZEND_HASH_FOREACH_END(); + } + + /* do backpatching here */ + if (zend_hash_num_elements(&bp_types)) { + sdlTypePtr *tmp, ptype = NULL; + + ZEND_HASH_FOREACH_PTR(&bp_types, tmp) { + if ((ptype = zend_hash_str_find_ptr(&ptr_map, (char*)tmp, sizeof(*tmp))) == NULL) { + assert(0); + } + *tmp = ptype; + } ZEND_HASH_FOREACH_END(); + } + if (zend_hash_num_elements(&bp_encoders)) { + encodePtr *tmp, penc = NULL; + + ZEND_HASH_FOREACH_PTR(&bp_encoders, tmp) { + if ((penc = zend_hash_str_find_ptr(&ptr_map, (char*)tmp, sizeof(*tmp))) == NULL) { + assert(0); + } + *tmp = penc; + } ZEND_HASH_FOREACH_END(); + } + + + if (sdl->bindings) { + sdlBindingPtr tmp; + sdlBindingPtr pbind; + + psdl->bindings = malloc(sizeof(HashTable)); + zend_hash_init(psdl->bindings, zend_hash_num_elements(sdl->bindings), NULL, delete_binding_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->bindings, key, tmp) { + pbind = make_persistent_sdl_binding(tmp, &ptr_map); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->bindings, ZSTR_VAL(key), ZSTR_LEN(key), pbind); + } else { + zend_hash_next_index_insert_ptr(psdl->bindings, pbind); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), pbind); + } ZEND_HASH_FOREACH_END(); + } + + zend_hash_init(&psdl->functions, zend_hash_num_elements(&sdl->functions), NULL, delete_function_persistent, 1); + if (zend_hash_num_elements(&sdl->functions)) { + sdlFunctionPtr tmp; + sdlFunctionPtr pfunc; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&sdl->functions, key, tmp) { + pfunc = make_persistent_sdl_function(tmp, &ptr_map); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(&psdl->functions, ZSTR_VAL(key), ZSTR_LEN(key), pfunc); + } else { + zend_hash_next_index_insert_ptr(&psdl->functions, pfunc); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), pfunc); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->requests) { + zval *zv; + sdlFunctionPtr tmp; + sdlFunctionPtr preq; + + psdl->requests = malloc(sizeof(HashTable)); + zend_hash_init(psdl->requests, zend_hash_num_elements(sdl->requests), NULL, NULL, 1); + + ZEND_HASH_FOREACH_STR_KEY_VAL(sdl->requests, key, zv) { + tmp = Z_PTR_P(zv); + if ((preq = zend_hash_str_find_ptr(&ptr_map, (char*)&tmp, sizeof(tmp))) == NULL) { + assert(0); + } + Z_PTR_P(zv) = preq; + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->requests, ZSTR_VAL(key), ZSTR_LEN(key), preq); + } + } ZEND_HASH_FOREACH_END(); + } + + zend_hash_destroy(&ptr_map); + zend_hash_destroy(&bp_encoders); + zend_hash_destroy(&bp_types); + + return psdl; +} + +typedef struct _sdl_cache_bucket { + sdlPtr sdl; + time_t time; +} sdl_cache_bucket; + +static void delete_psdl_int(sdl_cache_bucket *p) +{ + sdlPtr tmp = p->sdl; + + zend_hash_destroy(&tmp->functions); + if (tmp->source) { + free(tmp->source); + } + if (tmp->target_ns) { + free(tmp->target_ns); + } + if (tmp->elements) { + zend_hash_destroy(tmp->elements); + free(tmp->elements); + } + if (tmp->encoders) { + zend_hash_destroy(tmp->encoders); + free(tmp->encoders); + } + if (tmp->types) { + zend_hash_destroy(tmp->types); + free(tmp->types); + } + if (tmp->groups) { + zend_hash_destroy(tmp->groups); + free(tmp->groups); + } + if (tmp->bindings) { + zend_hash_destroy(tmp->bindings); + free(tmp->bindings); + } + if (tmp->requests) { + zend_hash_destroy(tmp->requests); + free(tmp->requests); + } + free(tmp); +} + +static void delete_psdl(zval *zv) +{ + delete_psdl_int(Z_PTR_P(zv)); + free(Z_PTR_P(zv)); +} + +sdlPtr get_sdl(zval *this_ptr, char *uri, zend_long cache_wsdl) +{ + char fn[MAXPATHLEN]; + sdlPtr sdl = NULL; + char* old_error_code = SOAP_GLOBAL(error_code); + size_t uri_len = 0; + php_stream_context *context=NULL; + zval *tmp, *proxy_host, *proxy_port, orig_context, new_context; + smart_str headers = {0}; + char* key = NULL; + time_t t = time(0); + zend_bool has_proxy_authorization = 0; + zend_bool has_authorization = 0; + + ZVAL_UNDEF(&orig_context); + ZVAL_UNDEF(&new_context); + if (strchr(uri,':') != NULL || IS_ABSOLUTE_PATH(uri, uri_len)) { + uri_len = strlen(uri); + } else if (VCWD_REALPATH(uri, fn) == NULL) { + cache_wsdl = WSDL_CACHE_NONE; + } else { + uri = fn; + uri_len = strlen(uri); + } + + if ((cache_wsdl & WSDL_CACHE_MEMORY) && SOAP_GLOBAL(mem_cache)) { + sdl_cache_bucket *p; + + if (NULL != (p = zend_hash_str_find_ptr(SOAP_GLOBAL(mem_cache), uri, uri_len))) { + if (p->time < t - SOAP_GLOBAL(cache_ttl)) { + /* in-memory cache entry is expired */ + zend_hash_str_del(&EG(persistent_list), uri, uri_len); + } else { + return p->sdl; + } + } + } + + if ((cache_wsdl & WSDL_CACHE_DISK) && (uri_len < MAXPATHLEN)) { + time_t t = time(0); + char md5str[33]; + PHP_MD5_CTX context; + unsigned char digest[16]; + int len = strlen(SOAP_GLOBAL(cache_dir)); + time_t cached; + char *user = php_get_current_user(); + int user_len = user ? strlen(user) + 1 : 0; + + md5str[0] = '\0'; + PHP_MD5Init(&context); + PHP_MD5Update(&context, (unsigned char*)uri, uri_len); + PHP_MD5Final(digest, &context); + make_digest(md5str, digest); + key = emalloc(len+sizeof("/wsdl-")-1+user_len+2+sizeof(md5str)); + memcpy(key,SOAP_GLOBAL(cache_dir),len); + memcpy(key+len,"/wsdl-",sizeof("/wsdl-")-1); + len += sizeof("/wsdl-")-1; + if (user_len) { + memcpy(key+len, user, user_len-1); + len += user_len-1; + key[len++] = '-'; + } + if (WSDL_CACHE_VERSION <= 0x9f) { + key[len++] = (WSDL_CACHE_VERSION >> 8) + '0'; + } else { + key[len++] = (WSDL_CACHE_VERSION >> 8) - 10 + 'a'; + } + if ((WSDL_CACHE_VERSION & 0xf) <= 0x9) { + key[len++] = (WSDL_CACHE_VERSION & 0xf) + '0'; + } else { + key[len++] = (WSDL_CACHE_VERSION & 0xf) - 10 + 'a'; + } + memcpy(key+len,md5str,sizeof(md5str)); + + if ((sdl = get_sdl_from_cache(key, uri, t-SOAP_GLOBAL(cache_ttl), &cached)) != NULL) { + t = cached; + efree(key); + goto cache_in_memory; + } + } + + if (NULL != (tmp = zend_hash_str_find(Z_OBJPROP_P(this_ptr), + "_stream_context", sizeof("_stream_context")-1))) { + context = php_stream_context_from_zval(tmp, 0); + } else { + context = php_stream_context_alloc(); + } + + if ((tmp = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_user_agent", sizeof("_user_agent")-1)) != NULL && + Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp) > 0) { + smart_str_appends(&headers, "User-Agent: "); + smart_str_appends(&headers, Z_STRVAL_P(tmp)); + smart_str_appends(&headers, "\r\n"); + } + + if ((proxy_host = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_proxy_host", sizeof("_proxy_host")-1)) != NULL && + Z_TYPE_P(proxy_host) == IS_STRING && + (proxy_port = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_proxy_port", sizeof("_proxy_port")-1)) != NULL && + Z_TYPE_P(proxy_port) == IS_LONG) { + zval str_port, str_proxy; + smart_str proxy = {0}; + ZVAL_DUP(&str_port, proxy_port); + convert_to_string(&str_port); + smart_str_appends(&proxy,"tcp://"); + smart_str_appends(&proxy,Z_STRVAL_P(proxy_host)); + smart_str_appends(&proxy,":"); + smart_str_appends(&proxy,Z_STRVAL(str_port)); + smart_str_0(&proxy); + zval_dtor(&str_port); + ZVAL_NEW_STR(&str_proxy, proxy.s); + + if (!context) { + context = php_stream_context_alloc(); + } + php_stream_context_set_option(context, "http", "proxy", &str_proxy); + zval_ptr_dtor(&str_proxy); + + if (uri_len < sizeof("https://")-1 || + strncasecmp(uri, "https://", sizeof("https://")-1) != 0) { + ZVAL_TRUE(&str_proxy); + php_stream_context_set_option(context, "http", "request_fulluri", &str_proxy); + } + + has_proxy_authorization = proxy_authentication(this_ptr, &headers); + } + + has_authorization = basic_authentication(this_ptr, &headers); + + /* Use HTTP/1.1 with "Connection: close" by default */ + if ((tmp = php_stream_context_get_option(context, "http", "protocol_version")) == NULL) { + zval http_version; + + ZVAL_DOUBLE(&http_version, 1.1); + php_stream_context_set_option(context, "http", "protocol_version", &http_version); + smart_str_appendl(&headers, "Connection: close\r\n", sizeof("Connection: close\r\n")-1); + } + + if (headers.s && ZSTR_LEN(headers.s) > 0) { + zval str_headers; + + if (!context) { + context = php_stream_context_alloc(); + } else { + http_context_headers(context, has_authorization, has_proxy_authorization, 0, &headers); + } + + smart_str_0(&headers); + ZVAL_NEW_STR(&str_headers, headers.s); + php_stream_context_set_option(context, "http", "header", &str_headers); + zval_ptr_dtor(&str_headers); + } + + if (context) { + php_stream_context_to_zval(context, &new_context); + php_libxml_switch_context(&new_context, &orig_context); + } + + SOAP_GLOBAL(error_code) = "WSDL"; + + sdl = load_wsdl(this_ptr, uri); + if (sdl) { + sdl->is_persistent = 0; + } + + SOAP_GLOBAL(error_code) = old_error_code; + + if (context) { + php_libxml_switch_context(&orig_context, NULL); + zval_ptr_dtor(&new_context); + } + + if ((cache_wsdl & WSDL_CACHE_DISK) && key) { + if (sdl) { + add_sdl_to_cache(key, uri, t, sdl); + } + efree(key); + } + +cache_in_memory: + if (cache_wsdl & WSDL_CACHE_MEMORY) { + if (sdl) { + sdlPtr psdl; + sdl_cache_bucket p; + + if (SOAP_GLOBAL(mem_cache) == NULL) { + SOAP_GLOBAL(mem_cache) = malloc(sizeof(HashTable)); + zend_hash_init(SOAP_GLOBAL(mem_cache), 0, NULL, delete_psdl, 1); + } else if (SOAP_GLOBAL(cache_limit) > 0 && + SOAP_GLOBAL(cache_limit) <= (zend_long)zend_hash_num_elements(SOAP_GLOBAL(mem_cache))) { + /* in-memory cache overflow */ + sdl_cache_bucket *q; + time_t latest = t; + zend_string *latest_key = NULL, *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(SOAP_GLOBAL(mem_cache), key, q) { + if (q->time < latest) { + latest = q->time; + latest_key = key; + } + } ZEND_HASH_FOREACH_END(); + if (latest_key) { + zend_hash_del(SOAP_GLOBAL(mem_cache), latest_key); + } else { + return sdl; + } + } + + psdl = make_persistent_sdl(sdl); + psdl->is_persistent = 1; + p.time = t; + p.sdl = psdl; + + if (NULL != zend_hash_str_update_mem(SOAP_GLOBAL(mem_cache), uri, + uri_len, &p, sizeof(sdl_cache_bucket))) { + /* remove non-persitent sdl structure */ + delete_sdl_impl(sdl); + /* and replace it with persistent one */ + sdl = psdl; + } else { + php_error_docref(NULL, E_WARNING, "Failed to register persistent entry"); + /* clean up persistent sdl */ + delete_psdl_int(&p); + /* keep non-persistent sdl and return it */ + } + } + } + + return sdl; +} + +/* Deletes */ +void delete_sdl_impl(void *handle) +{ + sdlPtr tmp = (sdlPtr)handle; + + zend_hash_destroy(&tmp->functions); + if (tmp->source) { + efree(tmp->source); + } + if (tmp->target_ns) { + efree(tmp->target_ns); + } + if (tmp->elements) { + zend_hash_destroy(tmp->elements); + efree(tmp->elements); + } + if (tmp->encoders) { + zend_hash_destroy(tmp->encoders); + efree(tmp->encoders); + } + if (tmp->types) { + zend_hash_destroy(tmp->types); + efree(tmp->types); + } + if (tmp->groups) { + zend_hash_destroy(tmp->groups); + efree(tmp->groups); + } + if (tmp->bindings) { + zend_hash_destroy(tmp->bindings); + efree(tmp->bindings); + } + if (tmp->requests) { + zend_hash_destroy(tmp->requests); + efree(tmp->requests); + } + efree(tmp); +} + +void delete_sdl(void *handle) +{ + sdlPtr tmp = (sdlPtr)handle; + + if (!tmp->is_persistent) { + delete_sdl_impl(tmp); + } +} + +static void delete_binding(zval *zv) +{ + sdlBindingPtr binding = Z_PTR_P(zv); + + if (binding->location) { + efree(binding->location); + } + if (binding->name) { + efree(binding->name); + } + + if (binding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBind = binding->bindingAttributes; + if (soapBind) { + efree(soapBind); + } + } + efree(binding); +} + +static void delete_binding_persistent(zval *zv) +{ + sdlBindingPtr binding = Z_PTR_P(zv); + + if (binding->location) { + free(binding->location); + } + if (binding->name) { + free(binding->name); + } + + if (binding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBind = binding->bindingAttributes; + if (soapBind) { + free(soapBind); + } + } + free(binding); +} + +static void delete_sdl_soap_binding_function_body(sdlSoapBindingFunctionBody body) +{ + if (body.ns) { + efree(body.ns); + } + if (body.headers) { + zend_hash_destroy(body.headers); + efree(body.headers); + } +} + +static void delete_sdl_soap_binding_function_body_persistent(sdlSoapBindingFunctionBody body) +{ + if (body.ns) { + free(body.ns); + } + if (body.headers) { + zend_hash_destroy(body.headers); + free(body.headers); + } +} + +static void delete_function(zval *zv) +{ + sdlFunctionPtr function = Z_PTR_P(zv); + + if (function->functionName) { + efree(function->functionName); + } + if (function->requestName) { + efree(function->requestName); + } + if (function->responseName) { + efree(function->responseName); + } + if (function->requestParameters) { + zend_hash_destroy(function->requestParameters); + efree(function->requestParameters); + } + if (function->responseParameters) { + zend_hash_destroy(function->responseParameters); + efree(function->responseParameters); + } + if (function->faults) { + zend_hash_destroy(function->faults); + efree(function->faults); + } + + if (function->bindingAttributes && + function->binding && function->binding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunction = function->bindingAttributes; + if (soapFunction->soapAction) { + efree(soapFunction->soapAction); + } + delete_sdl_soap_binding_function_body(soapFunction->input); + delete_sdl_soap_binding_function_body(soapFunction->output); + efree(soapFunction); + } + efree(function); +} + +static void delete_function_persistent(zval *zv) +{ + sdlFunctionPtr function = Z_PTR_P(zv); + + if (function->functionName) { + free(function->functionName); + } + if (function->requestName) { + free(function->requestName); + } + if (function->responseName) { + free(function->responseName); + } + if (function->requestParameters) { + zend_hash_destroy(function->requestParameters); + free(function->requestParameters); + } + if (function->responseParameters) { + zend_hash_destroy(function->responseParameters); + free(function->responseParameters); + } + if (function->faults) { + zend_hash_destroy(function->faults); + free(function->faults); + } + + if (function->bindingAttributes && + function->binding && function->binding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunction = function->bindingAttributes; + if (soapFunction->soapAction) { + free(soapFunction->soapAction); + } + delete_sdl_soap_binding_function_body_persistent(soapFunction->input); + delete_sdl_soap_binding_function_body_persistent(soapFunction->output); + free(soapFunction); + } + free(function); +} + +static void delete_parameter(zval *zv) +{ + sdlParamPtr param = Z_PTR_P(zv); + if (param->paramName) { + efree(param->paramName); + } + efree(param); +} + +static void delete_parameter_persistent(zval *zv) +{ + sdlParamPtr param = Z_PTR_P(zv); + if (param->paramName) { + free(param->paramName); + } + free(param); +} + +static void delete_header_int(sdlSoapBindingFunctionHeaderPtr hdr) +{ + if (hdr->name) { + efree(hdr->name); + } + if (hdr->ns) { + efree(hdr->ns); + } + if (hdr->headerfaults) { + zend_hash_destroy(hdr->headerfaults); + efree(hdr->headerfaults); + } + efree(hdr); +} + +static void delete_header(zval *zv) +{ + delete_header_int(Z_PTR_P(zv)); +} + +static void delete_header_persistent(zval *zv) +{ + sdlSoapBindingFunctionHeaderPtr hdr = Z_PTR_P(zv); + if (hdr->name) { + free(hdr->name); + } + if (hdr->ns) { + free(hdr->ns); + } + if (hdr->headerfaults) { + zend_hash_destroy(hdr->headerfaults); + free(hdr->headerfaults); + } + free(hdr); +} + +static void delete_fault(zval *zv) +{ + sdlFaultPtr fault = Z_PTR_P(zv); + if (fault->name) { + efree(fault->name); + } + if (fault->details) { + zend_hash_destroy(fault->details); + efree(fault->details); + } + if (fault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + + if (binding->ns) { + efree(binding->ns); + } + efree(fault->bindingAttributes); + } + efree(fault); +} + +static void delete_fault_persistent(zval *zv) +{ + sdlFaultPtr fault = Z_PTR_P(zv); + if (fault->name) { + free(fault->name); + } + if (fault->details) { + zend_hash_destroy(fault->details); + free(fault->details); + } + if (fault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + + if (binding->ns) { + free(binding->ns); + } + free(fault->bindingAttributes); + } + free(fault); +} + +static void delete_document(zval *zv) +{ + xmlDocPtr doc = Z_PTR_P(zv); + xmlFreeDoc(doc); +} + diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index fb00c420a6bc6..a9c6a56858d9d 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -204,7 +204,7 @@ xmlNsPtr node_find_ns(xmlNodePtr node) int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = attr_find_ns(node); if (nsPtr) { @@ -220,7 +220,7 @@ int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = node_find_ns(node); if (nsPtr) { diff --git a/ext/soap/php_xml.c.orig b/ext/soap/php_xml.c.orig new file mode 100644 index 0000000000000..fb00c420a6bc6 --- /dev/null +++ b/ext/soap/php_xml.c.orig @@ -0,0 +1,330 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Brad Lafountain | + | Shane Caraveo | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ +/* $Id$ */ + +#include "php_soap.h" +#include "ext/libxml/php_libxml.h" +#include "libxml/parser.h" +#include "libxml/parserInternals.h" + +/* Channel libxml file io layer through the PHP streams subsystem. + * This allows use of ftps:// and https:// urls */ + +static int is_blank(const xmlChar* str) +{ + while (*str != '\0') { + if (*str != ' ' && *str != 0x9 && *str != 0xa && *str != 0xd) { + return 0; + } + str++; + } + return 1; +} + +/* removes all empty text, comments and other insignoficant nodes */ +static void cleanup_xml_node(xmlNodePtr node) +{ + xmlNodePtr trav; + xmlNodePtr del = NULL; + + trav = node->children; + while (trav != NULL) { + if (del != NULL) { + xmlUnlinkNode(del); + xmlFreeNode(del); + del = NULL; + } + if (trav->type == XML_TEXT_NODE) { + if (is_blank(trav->content)) { + del = trav; + } + } else if ((trav->type != XML_ELEMENT_NODE) && + (trav->type != XML_CDATA_SECTION_NODE)) { + del = trav; + } else if (trav->children != NULL) { + cleanup_xml_node(trav); + } + trav = trav->next; + } + if (del != NULL) { + xmlUnlinkNode(del); + xmlFreeNode(del); + } +} + +static void soap_ignorableWhitespace(void *ctx, const xmlChar *ch, int len) +{ +} + +static void soap_Comment(void *ctx, const xmlChar *value) +{ +} + +xmlDocPtr soap_xmlParseFile(const char *filename) +{ + xmlParserCtxtPtr ctxt = NULL; + xmlDocPtr ret; + zend_bool old_allow_url_fopen; + +/* + xmlInitParser(); +*/ + + old_allow_url_fopen = PG(allow_url_fopen); + PG(allow_url_fopen) = 1; + ctxt = xmlCreateFileParserCtxt(filename); + PG(allow_url_fopen) = old_allow_url_fopen; + if (ctxt) { + zend_bool old; + + ctxt->keepBlanks = 0; + ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; + ctxt->sax->comment = soap_Comment; + ctxt->sax->warning = NULL; + ctxt->sax->error = NULL; + /*ctxt->sax->fatalError = NULL;*/ +#if LIBXML_VERSION >= 20703 + ctxt->options |= XML_PARSE_HUGE; +#endif + old = php_libxml_disable_entity_loader(1); + xmlParseDocument(ctxt); + php_libxml_disable_entity_loader(old); + if (ctxt->wellFormed) { + ret = ctxt->myDoc; + if (ret->URL == NULL && ctxt->directory != NULL) { + ret->URL = xmlCharStrdup(ctxt->directory); + } + } else { + ret = NULL; + xmlFreeDoc(ctxt->myDoc); + ctxt->myDoc = NULL; + } + xmlFreeParserCtxt(ctxt); + } else { + ret = NULL; + } + +/* + xmlCleanupParser(); +*/ + + if (ret) { + cleanup_xml_node((xmlNodePtr)ret); + } + return ret; +} + +xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size) +{ + xmlParserCtxtPtr ctxt = NULL; + xmlDocPtr ret; + + +/* + xmlInitParser(); +*/ + ctxt = xmlCreateMemoryParserCtxt(buf, buf_size); + if (ctxt) { + zend_bool old; + + ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; + ctxt->sax->comment = soap_Comment; + ctxt->sax->warning = NULL; + ctxt->sax->error = NULL; + /*ctxt->sax->fatalError = NULL;*/ +#if LIBXML_VERSION >= 20703 + ctxt->options |= XML_PARSE_HUGE; +#endif + old = php_libxml_disable_entity_loader(1); + xmlParseDocument(ctxt); + php_libxml_disable_entity_loader(old); + if (ctxt->wellFormed) { + ret = ctxt->myDoc; + if (ret->URL == NULL && ctxt->directory != NULL) { + ret->URL = xmlCharStrdup(ctxt->directory); + } + } else { + ret = NULL; + xmlFreeDoc(ctxt->myDoc); + ctxt->myDoc = NULL; + } + xmlFreeParserCtxt(ctxt); + } else { + ret = NULL; + } + +/* + xmlCleanupParser(); +*/ + +/* + if (ret) { + cleanup_xml_node((xmlNodePtr)ret); + } +*/ + return ret; +} + +xmlNsPtr attr_find_ns(xmlAttrPtr node) +{ + if (node->ns) { + return node->ns; + } else if (node->parent->ns) { + return node->parent->ns; + } else { + return xmlSearchNs(node->doc, node->parent, NULL); + } +} + +xmlNsPtr node_find_ns(xmlNodePtr node) +{ + if (node->ns) { + return node->ns; + } else { + return xmlSearchNs(node->doc, node, NULL); + } +} + +int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) +{ + if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (ns) { + xmlNsPtr nsPtr = attr_find_ns(node); + if (nsPtr) { + return (strcmp((char*)nsPtr->href, ns) == 0); + } else { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + +int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) +{ + if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (ns) { + xmlNsPtr nsPtr = node_find_ns(node); + if (nsPtr) { + return (strcmp((char*)nsPtr->href, ns) == 0); + } else { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + + +xmlAttrPtr get_attribute_ex(xmlAttrPtr node, char *name, char *ns) +{ + while (node!=NULL) { + if (attr_is_equal_ex(node, name, ns)) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_ex(xmlNodePtr node, char *name, char *ns) +{ + while (node!=NULL) { + if (node_is_equal_ex(node, name, ns)) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_recurisve_ex(xmlNodePtr node, char *name, char *ns) +{ + while (node != NULL) { + if (node_is_equal_ex(node, name, ns)) { + return node; + } else if (node->children != NULL) { + xmlNodePtr tmp = get_node_recurisve_ex(node->children, name, ns); + if (tmp) { + return tmp; + } + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_with_attribute_ex(xmlNodePtr node, char *name, char *name_ns, char *attribute, char *value, char *attr_ns) +{ + xmlAttrPtr attr; + + while (node != NULL) { + if (name != NULL) { + node = get_node_ex(node, name, name_ns); + if (node==NULL) { + return NULL; + } + } + + attr = get_attribute_ex(node->properties, attribute, attr_ns); + if (attr != NULL && strcmp((char*)attr->children->content, value) == 0) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_with_attribute_recursive_ex(xmlNodePtr node, char *name, char *name_ns, char *attribute, char *value, char *attr_ns) +{ + while (node != NULL) { + if (node_is_equal_ex(node, name, name_ns)) { + xmlAttrPtr attr = get_attribute_ex(node->properties, attribute, attr_ns); + if (attr != NULL && strcmp((char*)attr->children->content, value) == 0) { + return node; + } + } + if (node->children != NULL) { + xmlNodePtr tmp = get_node_with_attribute_recursive_ex(node->children, name, name_ns, attribute, value, attr_ns); + if (tmp) { + return tmp; + } + } + node = node->next; + } + return NULL; +} + +int parse_namespace(const xmlChar *inval, char **value, char **namespace) +{ + char *found = strrchr((char*)inval, ':'); + + if (found != NULL && found != (char*)inval) { + (*namespace) = estrndup((char*)inval, found - (char*)inval); + (*value) = estrdup(++found); + } else { + (*value) = estrdup((char*)inval); + (*namespace) = NULL; + } + + return FALSE; +} diff --git a/ext/soap/tests/bug80672.phpt b/ext/soap/tests/bug80672.phpt new file mode 100644 index 0000000000000..71e2b1d84105e --- /dev/null +++ b/ext/soap/tests/bug80672.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #80672 Null Dereference in SoapClient +--SKIPIF-- + +--FILE-- +query(array('sXML' => 'something')); +} catch(SoapFault $e) { + print $e->getMessage(); +} +?> +--EXPECTF-- +SOAP-ERROR: Parsing WSDL: Unexpected WSDL element <> \ No newline at end of file diff --git a/ext/soap/tests/bug80672.xml b/ext/soap/tests/bug80672.xml new file mode 100644 index 0000000000000..0fa185bf1ebba --- /dev/null +++ b/ext/soap/tests/bug80672.xml @@ -0,0 +1,6 @@ + + + + From b53697db12d405b257942aea4084dfbe82653fb6 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:24 +0000 Subject: [PATCH 24/25] commit patch 21270069 --- ext/standard/crypt_blowfish.c | 8 - ext/standard/crypt_blowfish.c.orig | 918 ++++++++++++++++++ .../tests/crypt/bcrypt_salt_dollar.phpt | 82 ++ 3 files changed, 1000 insertions(+), 8 deletions(-) create mode 100644 ext/standard/crypt_blowfish.c.orig create mode 100644 ext/standard/tests/crypt/bcrypt_salt_dollar.phpt diff --git a/ext/standard/crypt_blowfish.c b/ext/standard/crypt_blowfish.c index 5cf306715f139..e923b55ed008b 100644 --- a/ext/standard/crypt_blowfish.c +++ b/ext/standard/crypt_blowfish.c @@ -377,7 +377,6 @@ static unsigned char BF_atoi64[0x60] = { #define BF_safe_atoi64(dst, src) \ { \ tmp = (unsigned char)(src); \ - if (tmp == '$') break; /* PHP hack */ \ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ tmp = BF_atoi64[tmp]; \ if (tmp > 63) return -1; \ @@ -405,13 +404,6 @@ static int BF_decode(BF_word *dst, const char *src, int size) *dptr++ = ((c3 & 0x03) << 6) | c4; } while (dptr < end); - if (end - dptr == size) { - return -1; - } - - while (dptr < end) /* PHP hack */ - *dptr++ = 0; - return 0; } diff --git a/ext/standard/crypt_blowfish.c.orig b/ext/standard/crypt_blowfish.c.orig new file mode 100644 index 0000000000000..5cf306715f139 --- /dev/null +++ b/ext/standard/crypt_blowfish.c.orig @@ -0,0 +1,918 @@ +/* $Id$ */ +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2015. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2015 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_blowfish.h" + +#ifdef __i386__ +#define BF_ASM 0 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if (tmp == '$') break; /* PHP hack */ \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + if (end - dptr == size) { + return -1; + } + + while (dptr < end) /* PHP hack */ + *dptr++ = 0; + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +static int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *php_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +#if 0 +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} +#endif diff --git a/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt new file mode 100644 index 0000000000000..32e335f4b087e --- /dev/null +++ b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt @@ -0,0 +1,82 @@ +--TEST-- +bcrypt correctly rejects salts containing $ +--FILE-- + +--EXPECT-- +string(8) "$2y$04$$" +string(2) "*0" +bool(false) +string(9) "$2y$04$0$" +string(2) "*0" +bool(false) +string(10) "$2y$04$00$" +string(2) "*0" +bool(false) +string(11) "$2y$04$000$" +string(2) "*0" +bool(false) +string(12) "$2y$04$0000$" +string(2) "*0" +bool(false) +string(13) "$2y$04$00000$" +string(2) "*0" +bool(false) +string(14) "$2y$04$000000$" +string(2) "*0" +bool(false) +string(15) "$2y$04$0000000$" +string(2) "*0" +bool(false) +string(16) "$2y$04$00000000$" +string(2) "*0" +bool(false) +string(17) "$2y$04$000000000$" +string(2) "*0" +bool(false) +string(18) "$2y$04$0000000000$" +string(2) "*0" +bool(false) +string(19) "$2y$04$00000000000$" +string(2) "*0" +bool(false) +string(20) "$2y$04$000000000000$" +string(2) "*0" +bool(false) +string(21) "$2y$04$0000000000000$" +string(2) "*0" +bool(false) +string(22) "$2y$04$00000000000000$" +string(2) "*0" +bool(false) +string(23) "$2y$04$000000000000000$" +string(2) "*0" +bool(false) +string(24) "$2y$04$0000000000000000$" +string(2) "*0" +bool(false) +string(25) "$2y$04$00000000000000000$" +string(2) "*0" +bool(false) +string(26) "$2y$04$000000000000000000$" +string(2) "*0" +bool(false) +string(27) "$2y$04$0000000000000000000$" +string(2) "*0" +bool(false) +string(28) "$2y$04$00000000000000000000$" +string(2) "*0" +bool(false) +string(29) "$2y$04$000000000000000000000$" +string(2) "*0" +bool(false) +string(30) "$2y$04$0000000000000000000000$" +string(60) "$2y$04$000000000000000000000u2a2UpVexIt9k3FMJeAVr3c04F5tcI8K" +bool(false) From 996e8f2e70e5b9a68808e8035cb851b27d232b9f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:24:26 +0000 Subject: [PATCH 25/25] commit patch 17574111 --- ext/standard/crypt.c | 1 + ext/standard/crypt.c.orig | 296 ++++++++++++++++++ .../tests/password/password_bcrypt_short.phpt | 8 + 3 files changed, 305 insertions(+) create mode 100644 ext/standard/crypt.c.orig create mode 100644 ext/standard/tests/password/password_bcrypt_short.phpt diff --git a/ext/standard/crypt.c b/ext/standard/crypt.c index f1b713ffafae0..2e7a32a65989e 100644 --- a/ext/standard/crypt.c +++ b/ext/standard/crypt.c @@ -156,6 +156,7 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch } else if ( salt[0] == '$' && salt[1] == '2' && + salt[2] != 0 && salt[3] == '$') { char output[PHP_MAX_SALT_LEN + 1]; diff --git a/ext/standard/crypt.c.orig b/ext/standard/crypt.c.orig new file mode 100644 index 0000000000000..f1b713ffafae0 --- /dev/null +++ b/ext/standard/crypt.c.orig @@ -0,0 +1,296 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 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: Stig Bakken | + | Zeev Suraski | + | Rasmus Lerdorf | + | Pierre Joye | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include + +#include "php.h" + +#if HAVE_UNISTD_H +#include +#endif +#if PHP_USE_PHP_CRYPT_R +# include "php_crypt_r.h" +# include "crypt_freesec.h" +#else +# if HAVE_CRYPT_H +# if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +# endif +# include +# endif +#endif +#if TM_IN_SYS_TIME +#include +#else +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif + +#ifdef PHP_WIN32 +#include +#endif + +#include "php_crypt.h" +#include "php_random.h" + +/* sha512 crypt has the maximal salt length of 123 characters */ +#define PHP_MAX_SALT_LEN 123 + +/* Used to check DES salts to ensure that they contain only valid characters */ +#define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z')) + +#define DES_INVALID_SALT_ERROR "Supplied salt is not valid for DES. Possible bug in provided salt format." + + +PHP_MINIT_FUNCTION(crypt) /* {{{ */ +{ + REGISTER_LONG_CONSTANT("CRYPT_SALT_LENGTH", PHP_MAX_SALT_LEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_STD_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_EXT_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_MD5", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_BLOWFISH", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA256", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA512", 1, CONST_CS | CONST_PERSISTENT); + +#if PHP_USE_PHP_CRYPT_R + php_init_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */ +{ +#if PHP_USE_PHP_CRYPT_R + php_shutdown_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +static unsigned char itoa64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void php_to64(char *s, int n) /* {{{ */ +{ + while (--n >= 0) { + *s = itoa64[*s & 0x3f]; + s++; + } +} +/* }}} */ + +PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, zend_bool quiet) +{ + char *crypt_res; + zend_string *result; +/* Windows (win32/crypt) has a stripped down version of libxcrypt and + a CryptoApi md5_crypt implementation */ +#if PHP_USE_PHP_CRYPT_R + { + struct php_crypt_extended_data buffer; + + if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') { + char output[MD5_HASH_MAX_LEN], *out; + + out = php_md5_crypt_r(password, salt, output); + if (out) { + return zend_string_init(out, strlen(out), 0); + } + return NULL; + } else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if ( + salt[0] == '$' && + salt[1] == '2' && + salt[3] == '$') { + char output[PHP_MAX_SALT_LEN + 1]; + + memset(output, 0, PHP_MAX_SALT_LEN + 1); + + crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output)); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return result; + } + } else if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) { + return NULL; + } else { + /* DES Fallback */ + + /* Only check the salt if it's not EXT_DES */ + if (salt[0] != '_') { + /* DES style hashes */ + if (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1])) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + } + + memset(&buffer, 0, sizeof(buffer)); + _crypt_extended_init_r(); + + crypt_res = _crypt_extended_r(password, salt, &buffer); + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } + } + } +#else + + if (salt[0] != '$' && salt[0] != '_' && (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1]))) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + +# if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE)) + { +# if defined(CRYPT_R_STRUCT_CRYPT_DATA) + struct crypt_data buffer; + memset(&buffer, 0, sizeof(buffer)); +# elif defined(CRYPT_R_CRYPTD) + CRYPTD buffer; +# else +# error Data struct used by crypt_r() is unknown. Please report. +# endif + crypt_res = crypt_r(password, salt, &buffer); + } +# elif defined(HAVE_CRYPT) + crypt_res = crypt(password, salt); +# else +# error No crypt() implementation +# endif +#endif + + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } +} +/* }}} */ + + +/* {{{ proto string crypt(string str [, string salt]) + Hash a string */ +PHP_FUNCTION(crypt) +{ + char salt[PHP_MAX_SALT_LEN + 1]; + char *str, *salt_in = NULL; + size_t str_len, salt_in_len = 0; + zend_string *result; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(salt_in, salt_in_len) + ZEND_PARSE_PARAMETERS_END(); + + salt[0] = salt[PHP_MAX_SALT_LEN] = '\0'; + + /* This will produce suitable results if people depend on DES-encryption + * available (passing always 2-character salt). At least for glibc6.1 */ + memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1); + + if (salt_in) { + memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len)); + } else { + php_error_docref(NULL, E_NOTICE, "No salt parameter was specified. You must use a randomly generated salt and a strong hash function to produce a secure hash."); + } + + /* The automatic salt generation covers standard DES, md5-crypt and Blowfish (simple) */ + if (!*salt) { + strncpy(salt, "$1$", 3); + php_random_bytes_throw(&salt[3], 8); + php_to64(&salt[3], 8); + strncpy(&salt[11], "$", PHP_MAX_SALT_LEN - 11); + salt_in_len = strlen(salt); + } else { + salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len); + } + salt[salt_in_len] = '\0'; + + if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) { + if (salt[0] == '*' && salt[1] == '0') { + RETURN_STRING("*1"); + } else { + RETURN_STRING("*0"); + } + } + RETURN_STR(result); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/standard/tests/password/password_bcrypt_short.phpt b/ext/standard/tests/password/password_bcrypt_short.phpt new file mode 100644 index 0000000000000..085bc8a239045 --- /dev/null +++ b/ext/standard/tests/password/password_bcrypt_short.phpt @@ -0,0 +1,8 @@ +--TEST-- +Test that password_hash() does not overread buffers when a short hash is passed +--FILE-- + +--EXPECT-- +bool(false)