From ab00a2d3cfff996583f06e8db8208e6e46a4aab6 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:04 +0000 Subject: [PATCH 01/46] commit patch 18480209 --- ext/filter/sanitizing_filters.c | 2 +- ext/standard/string.c | 2 +- ext/standard/string.c.orig | 5731 +++++++++++++++++++++++++++++++ ext/xml/xml.c | 2 +- 4 files changed, 5734 insertions(+), 3 deletions(-) create mode 100644 ext/standard/string.c.orig diff --git a/ext/filter/sanitizing_filters.c b/ext/filter/sanitizing_filters.c index ff27bdb1be0bf..0b11ecfc2a3c2 100644 --- a/ext/filter/sanitizing_filters.c +++ b/ext/filter/sanitizing_filters.c @@ -87,7 +87,7 @@ static void php_filter_encode_url(zval *value, const unsigned char* chars, const memset(tmp, 1, 32); } */ - str = zend_string_alloc(3 * Z_STRLEN_P(value), 0); + str = zend_string_safe_alloc(Z_STRLEN_P(value), 3, 0, 0); p = (unsigned char *) ZSTR_VAL(str); s = (unsigned char *) Z_STRVAL_P(value); e = s + Z_STRLEN_P(value); diff --git a/ext/standard/string.c b/ext/standard/string.c index 23003dba72925..0765f63d2454f 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -5356,7 +5356,7 @@ PHP_FUNCTION(str_pad) return; } - result = zend_string_alloc(ZSTR_LEN(input) + num_pad_chars, 0); + result = zend_string_safe_alloc(ZSTR_LEN(input), 1, num_pad_chars, 0); ZSTR_LEN(result) = 0; /* We need to figure out the left/right padding lengths. */ diff --git a/ext/standard/string.c.orig b/ext/standard/string.c.orig new file mode 100644 index 0000000000000..23003dba72925 --- /dev/null +++ b/ext/standard/string.c.orig @@ -0,0 +1,5731 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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$ */ + +/* Synced with php 3.0 revision 1.193 1999-06-16 [ssb] */ + +#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" + +#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) +{ + struct lconv *res; + +# 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(); + + res = cur->locinfo->lconv; + } +#else + /* localeconv doesn't return an error condition */ + res = localeconv(); +#endif + + *out = *res; + +# 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &data) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &data) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|ll", &s11, + &s22, &start, &len) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &item) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &s1, &s2) == FAILURE) { + return; + } + + 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() + * 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) +{ + const char *c = ZSTR_VAL(str); + size_t len = ZSTR_LEN(str); + register size_t i; + size_t trimmed = 0; + char mask[256]; + + if (what) { + if (what_len == 1) { + char p = *what; + if (mode & 1) { + for (i = 0; i < len; i++) { + if (c[i] == p) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if (c[i] == p) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } else { + php_charmask((unsigned char*)what, what_len, mask); + + if (mode & 1) { + for (i = 0; i < len; i++) { + if (mask[(unsigned char)c[i]]) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if (mask[(unsigned char)c[i]]) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } + } else { + if (mode & 1) { + for (i = 0; i < len; i++) { + if ((unsigned char)c[i] <= ' ' && + (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if ((unsigned char)c[i] <= ' ' && + (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } + + if (ZSTR_LEN(str) == len) { + return zend_string_copy(str); + } else { + return zend_string_init(c, len, 0); + } +} +/* }}} */ + +/* {{{ php_do_trim + * Base for trim(), rtrim() and ltrim() functions. + */ +static void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zend_string *str; + zend_string *what = NULL; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|S", &str, &what) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(what) + ZEND_PARSE_PARAMETERS_END(); +#endif + + ZVAL_STR(return_value, php_trim(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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|lsb", &text, &linelength, &breakchar, &breakchar_len, &docut) == FAILURE) { + return; + } + + 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 < 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_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_alloc(ZSTR_LEN(text) * (breakchar_len + 1), 0); + } + + /* now keep track of the actual new text length */ + newtextlen = 0; + + laststart = lastspace = 0; + for (current = 0; current < 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 { + 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &delim, &str, &limit) == FAILURE) { + return; + } +#else + 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(); +#endif + + 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 *delim, zval *arr, 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(arr)); + + 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(arr), 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(arr), tmp) { + if (Z_TYPE_P(tmp) == IS_LONG) { + double val = Z_LVAL_P(tmp); + *++strptr = NULL; + ((zend_long *) (strings + numelems))[strptr - strings] = Z_LVAL_P(tmp); + if (val < 0) { + val = -10 * val; + } + if (val < 10) { + len++; + } else { + len += (int) log10(10 * (double) val); + } + } else { + *++strptr = zval_get_string(tmp); + len += ZSTR_LEN(*strptr); + } + } ZEND_HASH_FOREACH_END(); + + str = zend_string_alloc(len + (numelems - 1) * ZSTR_LEN(delim), 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(delim); + memcpy(cptr, ZSTR_VAL(delim), ZSTR_LEN(delim)); + } 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, *arr; + zend_string *delim; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|z", &arg1, &arg2) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(arg1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(arg2) + ZEND_PARSE_PARAMETERS_END(); +#endif + + if (arg2 == NULL) { + if (Z_TYPE_P(arg1) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Argument must be an array"); + return; + } + + delim = ZSTR_EMPTY_ALLOC(); + arr = arg1; + } else { + if (Z_TYPE_P(arg1) == IS_ARRAY) { + delim = zval_get_string(arg2); + arr = arg1; + } else if (Z_TYPE_P(arg2) == IS_ARRAY) { + delim = zval_get_string(arg1); + arr = arg2; + } else { + php_error_docref(NULL, E_WARNING, "Invalid arguments passed"); + return; + } + } + + php_implode(delim, arr, return_value); + zend_string_release(delim); +} +/* }}} */ + +#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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|S", &str, &tok) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(tok) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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 (!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 = 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(arg) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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 (!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 = 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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) || defined(NETWARE) + if (*c == '/' || *c == '\\') { +#else + if (*c == '/') { +#endif + if (state == 1) { + state = 0; + cend = c; + } +#if defined(PHP_WIN32) || defined(NETWARE) + /* 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &string, &string_len, &suffix, &suffix_len) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &levels) == FAILURE) { + return; + } + + ret = zend_string_init(str, str_len, 0); + + if (levels == 1) { + /* Defaut case */ + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len); + } 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 { + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret)); + } 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &path_len, &opt) == FAILURE) { + return; + } + + 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)) : 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|b", &haystack, &needle, &part) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|b", &haystack, &needle, &part) == FAILURE) { + return; + } + + 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &needle, &offset) == FAILURE) { + return; + } +#else + 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(); +#endif + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &needle, &offset) == FAILURE) { + return; + } + + 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &zneedle, &offset) == FAILURE) { + RETURN_FALSE; + } +#else + 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); +#endif + + 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 (-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); + + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &zneedle, &offset) == FAILURE) { + 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 (-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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz", &haystack, &needle) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", &str, &chunklen, &end, &endlen) == FAILURE) { + return; + } + + 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_alloc(endlen + ZSTR_LEN(str), 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(); + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|l", &str, &f, &l) == FAILURE) { + return; + } +#else + 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(); +#endif + + 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 && -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 ((f + l) > (zend_long)ZSTR_LEN(str)) { + l = ZSTR_LEN(str) - f; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|z/", &str, &repl, &from, &len) == FAILURE) { + return; + } + + 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) { + 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, "'from' and 'len' 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, "'from' and 'len' 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 (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 (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_alloc(Z_STRLEN_P(str) - l + ZSTR_LEN(repl_str), 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 'from' and 'len' 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_alloc(result_len, 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_alloc(result_len, 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_alloc(result_len, 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &old) == FAILURE) { + return; + } + + old_end = ZSTR_VAL(old) + ZSTR_LEN(old); + + if (ZSTR_VAL(old) == old_end) { + RETURN_FALSE; + } + + str = zend_string_alloc(2 * ZSTR_LEN(old), 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 */ +PHP_FUNCTION(ord) +{ + char *str; + size_t str_len; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &str_len) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(str, str_len) + ZEND_PARSE_PARAMETERS_END(); +#endif + + RETURN_LONG((unsigned char) str[0]); +} +/* }}} */ + +/* {{{ proto string chr(int ascii) + Converts ASCII code to a character */ +PHP_FUNCTION(chr) +{ + zend_long c; + + if (ZEND_NUM_ARGS() != 1) { + WRONG_PARAM_COUNT; + } + +#ifndef FAST_ZPP + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "l", &c) == FAILURE) { + c = 0; + } +#else + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 1, 1) + Z_PARAM_LONG(c) + ZEND_PARSE_PARAMETERS_END_EX(c = 0); +#endif + + c &= 0xff; + if (CG(one_char_string)[c]) { + ZVAL_INTERNED_STR(return_value, CG(one_char_string)[c]); + } else { + ZVAL_NEW_STR(return_value, zend_string_alloc(1, 0)); + Z_STRVAL_P(return_value)[0] = (char)c; + Z_STRVAL_P(return_value)[1] = '\0'; + } +} +/* }}} */ + +/* {{{ 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + 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]; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|s", &str, &delims, &delims_len) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(delims, delims_len) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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)) { + 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; + } + 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 { + 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; + } + + 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 { + 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(); + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|s", &str, &from, &to, &to_len) == FAILURE) { + return; + } +#else + 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(); +#endif + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|z/", &t1, &t2, &percent) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &str, &what) == FAILURE) { + return; + } + + 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + 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_alloc(4 * ZSTR_LEN(str), 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_alloc(offset + (2 * (ZSTR_LEN(str) - 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(); + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|z/", &search, &replace, &subject, &zcount) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_ZVAL(search) + Z_PARAM_ZVAL(replace) + Z_PARAM_ZVAL(subject) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_EX(zcount, 0, 1) + ZEND_PARSE_PARAMETERS_END(); +#endif + + /* 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) { + 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; + size_t begin, end, char_count, orig_begin; + size_t str_len; + zend_string *broken_str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &max_chars) == FAILURE) { + return; + } + + 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; + +#ifndef FAST_ZPP + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|b", &str, &is_xhtml) == FAILURE) { + return; + } +#else + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(is_xhtml) + ZEND_PARSE_PARAMETERS_END(); +#endif + + 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_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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|z", &str, &allow) == FAILURE) { + return; + } + + /* 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l+", &cat, &args, &num_args) == FAILURE) { + return; + } + +#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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z/", &arg, &arglen, &arrayArg) == FAILURE) { + return; + } + + res = estrndup(arg, arglen); + + if (arrayArg == NULL) { + zval tmp; + zend_array *symbol_table = zend_rebuild_symbol_table(); + + ZVAL_ARR(&tmp, symbol_table); + sapi_module.treat_data(PARSE_STRING, res, &tmp); + } else { + zval ret; + + /* Clear out the array that was passed in. */ + zval_dtor(arrayArg); + array_init(&ret); + sapi_module.treat_data(PARSE_STRING, res, &ret); + ZVAL_COPY_VALUE(arrayArg, &ret); + } +} +/* }}} */ + +#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, int *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 = '>'; + in_q = state = 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+2 && strncasecmp(p-2, "xm", 2) == 0) { + state = 1; + break; + } + + /* fall-through */ + default: +reg_char: + 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; + } + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|sss", &str, &delim_str, &delim_len, + &enc_str, &enc_len, &esc_str, &esc_len) == FAILURE) { + return; + } + + 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 */ + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl", &input_str, &mult) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &s1, &s2) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ll", &haystack, &haystack_len, &needle, &needle_len, &offset, &length) == FAILURE) { + return; + } + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty substring"); + RETURN_FALSE; + } + + p = haystack; + endp = p + haystack_len; + + if (offset < 0) { + php_error_docref(NULL, E_WARNING, "Offset should be greater than or equal to 0"); + RETURN_FALSE; + } + + if ((size_t)offset > haystack_len) { + php_error_docref(NULL, E_WARNING, "Offset value " ZEND_LONG_FMT " exceeds string length", offset); + RETURN_FALSE; + } + p += offset; + + if (ac == 4) { + + if (length <= 0) { + php_error_docref(NULL, E_WARNING, "Length should be greater than 0"); + RETURN_FALSE; + } + if (length > (haystack_len - offset)) { + php_error_docref(NULL, E_WARNING, "Length value " ZEND_LONG_FMT " exceeds string length", length); + 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 */ + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|sl", &input, &pad_length, &pad_str, &pad_str_len, &pad_type_val) == FAILURE) { + return; + } + + /* 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_alloc(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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss*", &str, &str_len, &format, &format_len, + &args, &num_args) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", &str, &type, &char_list, &char_list_len) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &format, &format_len, &value) == FAILURE) { + return; + } + + 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_alloc(format_len + 1024, 0); + if ((res_len = strfmon(ZSTR_VAL(str), ZSTR_LEN(str), format, value)) < 0) { + zend_string_free(str); + RETURN_FALSE; + } + ZSTR_LEN(str) = (size_t)res_len; + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &str, &split_length) == FAILURE) { + return; + } + + 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; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &haystack, &char_list) == FAILURE) { + 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 cs=0; + size_t cmp_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSl|lb", &s1, &s2, &offset, &len, &cs) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_NUM_ARGS() >= 4 && 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 = (size_t) (len ? 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)); + } +} +/* }}} */ + +/* + * 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/xml/xml.c b/ext/xml/xml.c index d6eae465830a5..bfa1b85b99bbf 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -581,7 +581,7 @@ PHP_XML_API zend_string *xml_utf8_encode(const char *s, size_t len, const XML_Ch } /* This is the theoretical max (will never get beyond len * 2 as long * as we are converting from single-byte characters, though) */ - str = zend_string_alloc(len * 4, 0); + str = zend_string_safe_alloc(len, 4, 0, 0); ZSTR_LEN(str) = 0; while (pos > 0) { c = encoder ? encoder((unsigned char)(*s)) : (unsigned short)(*s); From a3b6b84e1bffcced6a0223a3082eff09ac92d854 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:06 +0000 Subject: [PATCH 02/46] commit patch 25387965 --- ext/soap/php_http.c | 2 ++ ext/soap/tests/bug71610.phpt | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 ext/soap/tests/bug71610.phpt diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 2baa0fa3ff74e..a2d0b6207d7db 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -833,8 +833,10 @@ int make_http_soap_request(zval *this_ptr, Z_TYPE_P(value) == IS_STRING) { zval *tmp; if (((tmp = zend_hash_index_find(Z_ARRVAL_P(data), 1)) == NULL || + Z_TYPE_P(tmp) != IS_STRING || strncmp(phpurl->path?phpurl->path:"/",Z_STRVAL_P(tmp),Z_STRLEN_P(tmp)) == 0) && ((tmp = zend_hash_index_find(Z_ARRVAL_P(data), 2)) == NULL || + Z_TYPE_P(tmp) != IS_STRING || in_domain(phpurl->host,Z_STRVAL_P(tmp))) && (use_ssl || (tmp = zend_hash_index_find(Z_ARRVAL_P(data), 3)) == NULL)) { smart_str_append(&soap_headers, key); diff --git a/ext/soap/tests/bug71610.phpt b/ext/soap/tests/bug71610.phpt new file mode 100644 index 0000000000000..4f1c7162ff633 --- /dev/null +++ b/ext/soap/tests/bug71610.phpt @@ -0,0 +1,15 @@ +--TEST-- +SOAP Bug #71610 - Type Confusion Vulnerability - SOAP / make_http_soap_request() +--SKIPIF-- + +--FILE-- +blahblah(); +} catch(SoapFault $e) { + echo $e->getMessage()."\n"; +} +?> +--EXPECT-- +looks like we got no XML document From 886839f5847f8744b1d03b96f0bb15032b6f0367 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:08 +0000 Subject: [PATCH 03/46] commit patch 20104904 --- ext/snmp/snmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index be8888c348ccd..67e39f1645c28 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -530,7 +530,7 @@ static void php_snmp_error(zval *object, const char *docref, int type, const cha } if (object && (snmp_object->exceptions_enabled & type)) { - zend_throw_exception_ex(php_snmp_exception_ce, type, snmp_object->snmp_errstr); + zend_throw_exception_ex(php_snmp_exception_ce, type, "%s", snmp_object->snmp_errstr); } else { va_start(args, format); php_verror(docref, "", E_WARNING, format, args); From 8cab4d7614ec94e60ba9e27625a5e8c7dba23a00 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:10 +0000 Subject: [PATCH 04/46] commit patch 25916883 --- ext/phar/phar.c | 4 + ext/phar/phar.c.orig | 3616 ++++++++++++++++++ ext/phar/phar_object.c | 40 +- ext/phar/tests/badparameters.phpt | 18 +- ext/phar/tests/bug64931/bug64931.phpt | 5 +- ext/phar/tests/create_path_error.phpt | 3 +- ext/phar/tests/phar_extract.phpt | 2 +- ext/phar/tests/phar_isvalidpharfilename.phpt | 2 +- ext/phar/tests/phar_unlinkarchive.phpt | 2 +- ext/phar/tests/pharfileinfo_construct.phpt | 2 +- 10 files changed, 3657 insertions(+), 37 deletions(-) create mode 100644 ext/phar/phar.c.orig diff --git a/ext/phar/phar.c b/ext/phar/phar.c index 0ed34dbfcd057..cb1e74f0ac177 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -2193,6 +2193,10 @@ int phar_split_fname(const char *filename, int filename_len, char **arch, int *a #endif int ext_len; + if (CHECK_NULL_PATH(filename, filename_len)) { + return FAILURE; + } + if (!strncasecmp(filename, "phar://", 7)) { filename += 7; filename_len -= 7; diff --git a/ext/phar/phar.c.orig b/ext/phar/phar.c.orig new file mode 100644 index 0000000000000..c177c7f7d4b15 --- /dev/null +++ b/ext/phar/phar.c.orig @@ -0,0 +1,3616 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2016 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: 0ed34dbfcd0576adc02983de692e3ad4873155c3 $ */ + +#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; + uint 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)) { + /* close open file handle - allows removal or rename of + the file on windows, which has greedy locking + only close if the archive was not already compressed. If it + was compressed, then the fp does not refer to the original file */ + php_stream_close(phar->fp); + phar->fp = NULL; + } + + if (!zend_hash_num_elements(&phar->manifest)) { + /* this is a new phar that has perhaps had an alias/metadata set, but has never + been flushed */ + if (zend_hash_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 = *(php_uint16*)(buffer); \ + buffer += 2 +#endif +#define PHAR_ZIP_16(var) ((php_uint16)((((php_uint16)var[0]) & 0xff) | \ + (((php_uint16)var[1]) & 0xff) << 8)) +#define PHAR_ZIP_32(var) ((php_uint32)((((php_uint32)var[0]) & 0xff) | \ + (((php_uint32)var[1]) & 0xff) << 8 | \ + (((php_uint32)var[2]) & 0xff) << 16 | \ + (((php_uint32)var[3]) & 0xff) << 24)) + +/** + * Open an already loaded phar + */ +int phar_open_parsed_phar(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error) /* {{{ */ +{ + 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, php_uint32 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, php_uint32 compression, char **error) /* {{{ */ +{ + char b32[4], *buffer, *endbuffer, *savebuf; + phar_archive_data *mydata = NULL; + phar_entry_info entry; + php_uint32 manifest_len, manifest_count, manifest_flags, manifest_index, tmp_len, sig_flags; + php_uint16 manifest_ver; + php_uint32 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: { + php_uint32 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))) + { + buffer[tmp_len] = '\0'; + php_stream_close(fp); + + if (signature) { + efree(signature); + } + + if (error) { + spprintf(error, 0, "cannot load phar \"%s\" with implicit alias \"%s\" under different alias \"%s\"", fname, buffer, alias); + } + + efree(savebuf); + return FAILURE; + } + + alias_len = tmp_len; + alias = buffer; + buffer += tmp_len; + register_alias = 1; + } else if (!alias_len || !alias) { + /* if we neither have an explicit nor an implicit alias, we use the filename */ + alias = NULL; + alias_len = 0; + register_alias = 0; + } else if (alias_len) { + register_alias = 1; + temp_alias = 1; + } + + /* we have 5 32-bit items plus 1 byte at least */ + if (manifest_count > ((manifest_len - 10 - tmp_len) / (5 * 4 + 1))) { + /* prevent serious memory issues */ + MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)") + } + + mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist)); + mydata->is_persistent = PHAR_G(persist); + + /* check whether we have meta data, zero check works regardless of byte order */ + 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 > 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 + 4 > endbuffer) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)") + } + + PHAR_GET_32(buffer, entry.filename_len); + + if (entry.filename_len == 0) { + MAPPHAR_FAIL("zero-length filename encountered in phar \"%s\""); + } + + if (entry.is_persistent) { + entry.manifest_pos = manifest_index; + } + + if (entry.filename_len + 20 > endbuffer - buffer) { + 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 > 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_API_VERSION < 20100412 + if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + return FAILURE; + } +#endif + 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_API_VERSION < 20100412 + if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + return FAILURE; + } +#endif + 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; + php_uint32 compression = PHAR_FILE_COMPRESSED_NONE; + + if (error) { + *error = NULL; + } + + if (-1 == php_stream_rewind(fp)) { + MAPPHAR_ALLOC_FAIL("cannot rewind phar \"%s\"") + } + + buffer[sizeof(buffer)-1] = '\0'; + memset(buffer, 32, sizeof(token)); + halt_offset = 0; + + /* Maybe it's better to compile the file instead of just searching, */ + /* but we only want the offset. So we want a .re scanner to find it. */ + while(!php_stream_eof(fp)) { + if ((got = php_stream_read(fp, buffer+tokenlen, readsize)) < (size_t) tokenlen) { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)") + } + + if (!test) { + test = '\1'; + pos = buffer+tokenlen; + if (!memcmp(pos, gz_magic, 3)) { + char err = 0; + php_stream_filter *filter; + php_stream *temp; + /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */ + zval filterparams; + + if (!PHAR_G(has_zlib)) { + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file, enable zlib extension in php.ini") + } + array_init(&filterparams); +/* this is defined in zlib's zconf.h */ +#ifndef MAX_WBITS +#define MAX_WBITS 15 +#endif + add_assoc_long_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) + ((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) > (uint) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint)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) > (uint) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint)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 (!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_API_VERSION < 20100412 + if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + return FAILURE; + } +#endif + + 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, php_uint32 crc32, char **error, int process_zip) /* {{{ */ +{ + php_uint32 crc = ~0; + int len = idata->internal_file->uncompressed_filesize; + php_stream *fp = idata->fp; + phar_entry_info *entry = idata->internal_file; + + if (error) { + *error = NULL; + } + + if (entry->is_zip && process_zip > 0) { + /* verify local file header */ + phar_zip_file_header local; + phar_zip_data_desc desc; + + if (SUCCESS != phar_open_archive_fp(idata->phar)) { + 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; + php_uint32 manifest_len, mytime, loc, new_manifest_count; + php_uint32 newcrc32; + php_stream *file, *oldfile, *newfile, *stubfile; + php_stream_filter *filter; + php_serialize_data_t metadata_hash; + smart_str main_metadata_str = {0}; + int free_user_stub, free_fp = 1, free_ufp = 1; + 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 = (php_uint32) php_stream_tell(entry->cfp); + /* generate crc on compressed file */ + php_stream_rewind(entry->cfp); + entry->old_flags = entry->flags; + entry->is_modified = 1; + global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK); + } + global_flags |= PHAR_HDR_SIGNATURE; + + /* write out manifest pre-header */ + /* 4: manifest length + * 4: manifest entry count + * 2: phar version + * 4: phar global flags + * 4: alias length + * ?: the alias itself + * 4: phar metadata length + * ?: phar metadata + */ + restore_alias_len = phar->alias_len; + if (phar->is_temporary_alias) { + phar->alias_len = 0; + } + + manifest_len = offset + phar->alias_len + sizeof(manifest) + (main_metadata_str.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, file_handle)) { + efree(name); + name = NULL; + file_handle->filename = f.filename; + if (file_handle->opened_path) { + efree(file_handle->opened_path); + } + file_handle->opened_path = f.opened_path; + file_handle->free_filename = f.free_filename; + } else { + *file_handle = f; + } + } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) { + /* compressed phar */ + 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) /* {{{ */ +{ + int 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: 0ed34dbfcd0576adc02983de692e3ad4873155c3 $"); + 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 +#if HAVE_SPL + ZEND_MOD_REQUIRED("spl") +#endif + ZEND_MOD_END +}; + +zend_module_entry phar_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + phar_deps, + "Phar", + phar_functions, + PHP_MINIT(phar), + PHP_MSHUTDOWN(phar), + NULL, + PHP_RSHUTDOWN(phar), + PHP_MINFO(phar), + PHP_PHAR_VERSION, + PHP_MODULE_GLOBALS(phar), /* globals descriptor */ + PHP_GINIT(phar), /* globals ctor */ + PHP_GSHUTDOWN(phar), /* globals dtor */ + NULL, /* post deactivate */ + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 51b8b13a2ae9f..14cd83fb9d454 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -459,7 +459,7 @@ PHP_METHOD(Phar, mount) size_t path_len, actual_len; phar_archive_data *pphar; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &path, &path_len, &actual, &actual_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &path, &path_len, &actual, &actual_len) == FAILURE) { return; } @@ -938,7 +938,7 @@ PHP_METHOD(Phar, createDefaultStub) zend_string *stub; size_t index_len = 0, webindex_len = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ss", &index, &index_len, &webindex, &webindex_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|pp", &index, &index_len, &webindex, &webindex_len) == FAILURE) { return; } @@ -982,7 +982,7 @@ PHP_METHOD(Phar, loadPhar) char *fname, *alias = NULL, *error; size_t fname_len, alias_len = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s!", &fname, &fname_len, &alias, &alias_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s!", &fname, &fname_len, &alias, &alias_len) == FAILURE) { return; } @@ -1062,7 +1062,7 @@ PHP_METHOD(Phar, isValidPharFilename) int ext_len, is_executable; zend_bool executable = 1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b", &fname, &fname_len, &executable) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &fname, &fname_len, &executable) == FAILURE) { return; } @@ -1134,11 +1134,11 @@ PHP_METHOD(Phar, __construct) is_data = instanceof_function(Z_OBJCE_P(zobj), phar_ce_data); if (is_data) { - if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s|ls!l", &fname, &fname_len, &flags, &alias, &alias_len, &format) == FAILURE) { + 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(), "s|ls!", &fname, &fname_len, &flags, &alias, &alias_len) == FAILURE) { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|ls!", &fname, &fname_len, &flags, &alias, &alias_len) == FAILURE) { return; } } @@ -1307,7 +1307,7 @@ PHP_METHOD(Phar, unlinkArchive) int zname_len, arch_len, entry_len; phar_archive_data *phar; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { RETURN_FALSE; } @@ -1739,7 +1739,7 @@ PHP_METHOD(Phar, buildFromDirectory) return; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &dir, &dir_len, ®ex, ®ex_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &dir, &dir_len, ®ex, ®ex_len) == FAILURE) { RETURN_FALSE; } @@ -2586,7 +2586,7 @@ PHP_METHOD(Phar, delete) return; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { RETURN_FALSE; } @@ -3400,7 +3400,7 @@ PHP_METHOD(Phar, copy) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { return; } @@ -3500,7 +3500,7 @@ PHP_METHOD(Phar, offsetExists) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { return; } @@ -3538,7 +3538,7 @@ PHP_METHOD(Phar, offsetGet) zend_string *sfname; PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { return; } @@ -3685,8 +3685,8 @@ PHP_METHOD(Phar, offsetSet) return; } - if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "sr", &fname, &fname_len, &zresource) == FAILURE - && zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &fname, &fname_len, &cont_str, &cont_len) == FAILURE) { + 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; } @@ -3724,7 +3724,7 @@ PHP_METHOD(Phar, offsetUnset) return; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { return; } @@ -3771,7 +3771,7 @@ PHP_METHOD(Phar, addEmptyDir) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &dirname, &dirname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &dirname, &dirname_len) == FAILURE) { return; } @@ -3796,7 +3796,7 @@ PHP_METHOD(Phar, addFile) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &fname, &fname_len, &localname, &localname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &fname, &fname_len, &localname, &localname_len) == FAILURE) { return; } @@ -3838,7 +3838,7 @@ PHP_METHOD(Phar, addFromString) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &localname, &localname_len, &cont_str, &cont_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &localname, &localname_len, &cont_str, &cont_len) == FAILURE) { return; } @@ -4264,7 +4264,7 @@ PHP_METHOD(Phar, extractTo) PHAR_ARCHIVE_OBJECT(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z!b", &pathto, &pathto_len, &zval_files, &overwrite) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|z!b", &pathto, &pathto_len, &zval_files, &overwrite) == FAILURE) { return; } @@ -4396,7 +4396,7 @@ PHP_METHOD(PharFileInfo, __construct) phar_archive_data *phar_data; zval *zobj = getThis(), arg1; - if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s", &fname, &fname_len) == FAILURE) { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { return; } diff --git a/ext/phar/tests/badparameters.phpt b/ext/phar/tests/badparameters.phpt index a1a9fb78a0f08..4d0887f66f90c 100644 --- a/ext/phar/tests/badparameters.phpt +++ b/ext/phar/tests/badparameters.phpt @@ -147,19 +147,19 @@ echo $e->getMessage() . "\n"; --EXPECTF-- Warning: Phar::mungServer() expects parameter 1 to be array, %string given in %sbadparameters.php on line %d -Warning: Phar::createDefaultStub() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::createDefaultStub() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d -Warning: Phar::loadPhar() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::loadPhar() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Warning: Phar::canCompress() expects parameter 1 to be integer, %string given in %sbadparameters.php on line %d -Exception: Phar::__construct() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Exception: Phar::__construct() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Warning: Phar::convertToExecutable() expects parameter 1 to be integer, array given in %sbadparameters.php on line %d Warning: Phar::convertToData() expects parameter 1 to be integer, array given in %sbadparameters.php on line %d -Warning: PharData::delete() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: PharData::delete() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Cannot write out phar archive, phar is read-only Entry oops does not exist and cannot be deleted %sfiles/frontcontroller10.phar @@ -186,18 +186,18 @@ Phar is readonly, cannot change compression Warning: Phar::copy() expects exactly 2 parameters, 1 given in %sbadparameters.php on line %d Cannot copy "a" to "b", phar is read-only -Warning: Phar::offsetExists() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::offsetExists() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d -Warning: Phar::offsetGet() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::offsetGet() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Warning: Phar::offsetSet() expects exactly 2 parameters, 1 given in %sbadparameters.php on line %d -Warning: PharData::offsetUnset() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: PharData::offsetUnset() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Write operations disabled by the php.ini setting phar.readonly -Warning: Phar::addEmptyDir() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::addEmptyDir() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d -Warning: Phar::addFile() expects parameter 1 to be %string, array given in %sbadparameters.php on line %d +Warning: Phar::addFile() expects parameter 1 to be a valid path, array given in %sbadparameters.php on line %d Warning: Phar::addFromString() expects exactly 2 parameters, 1 given in %sbadparameters.php on line %d Write operations disabled by the php.ini setting phar.readonly diff --git a/ext/phar/tests/bug64931/bug64931.phpt b/ext/phar/tests/bug64931/bug64931.phpt index 9c1f9dcaf1da5..29e0c7b4e325f 100644 --- a/ext/phar/tests/bug64931/bug64931.phpt +++ b/ext/phar/tests/bug64931/bug64931.phpt @@ -48,11 +48,12 @@ try { ---EXPECT-- +--EXPECTF-- Test CAUGHT: Cannot create any files in magic ".phar" directory CAUGHT: Cannot create any files in magic ".phar" directory CAUGHT: Cannot create any files in magic ".phar" directory CAUGHT: Cannot create any files in magic ".phar" directory -CAUGHT: Cannot create any files in magic ".phar" directory + +Warning: Phar::addFromString() expects parameter 1 to be a valid path, string given in %s/bug64931.php on line %d ===DONE=== \ No newline at end of file diff --git a/ext/phar/tests/create_path_error.phpt b/ext/phar/tests/create_path_error.phpt index fe2cd3e22bc3b..3449b07fc6345 100644 --- a/ext/phar/tests/create_path_error.phpt +++ b/ext/phar/tests/create_path_error.phpt @@ -80,6 +80,5 @@ string(5) "query" 11:Error: file_put_contents(phar://%s): failed to open stream: phar error: invalid path "%s" contains illegal character 12:Error: file_put_contents(phar://%s): failed to open stream: phar error: invalid path "%s" contains illegal character 13:Error: file_put_contents(phar://%s): failed to open stream: phar error: invalid path "%s" contains illegal character -Exception: Entry a does not exist and cannot be created: phar error: invalid path "a" contains illegal character -===DONE=== +Error: Phar::offsetSet() expects parameter 1 to be a valid path, string given===DONE=== diff --git a/ext/phar/tests/phar_extract.phpt b/ext/phar/tests/phar_extract.phpt index bc545236fd886..f7d1403d599a3 100644 --- a/ext/phar/tests/phar_extract.phpt +++ b/ext/phar/tests/phar_extract.phpt @@ -138,7 +138,7 @@ string(3) "hi2" bool(false) Invalid argument, expected a filename (string) or array of filenames -Warning: Phar::extractTo() expects parameter 1 to be %string, array given in %sphar_extract.php on line %d +Warning: Phar::extractTo() expects parameter 1 to be a valid path, array given in %sphar_extract.php on line %d Invalid argument, extraction path must be non-zero length Unable to use path "%soops" for extraction, it is a file, must be a directory Invalid argument, array of filenames to extract contains non-string value diff --git a/ext/phar/tests/phar_isvalidpharfilename.phpt b/ext/phar/tests/phar_isvalidpharfilename.phpt index dee9b7dc03e0a..da07bec2876f0 100644 --- a/ext/phar/tests/phar_isvalidpharfilename.phpt +++ b/ext/phar/tests/phar_isvalidpharfilename.phpt @@ -76,7 +76,7 @@ var_dump(Phar::isValidPharFilename('dir.phar.php', false)); " phar archive "%sphar_unlinkarchive.phar" has open file handles or objects. fclose() all file handles, and unset() all objects prior to calling unlinkArchive() diff --git a/ext/phar/tests/pharfileinfo_construct.phpt b/ext/phar/tests/pharfileinfo_construct.phpt index 1f4f6177b07f1..53ee5143cf049 100644 --- a/ext/phar/tests/pharfileinfo_construct.phpt +++ b/ext/phar/tests/pharfileinfo_construct.phpt @@ -50,7 +50,7 @@ echo $e->getMessage() . "\n"; --EXPECTF-- Cannot open phar file 'phar://%spharfileinfo_construct.phar/oops': internal corruption of phar "%spharfileinfo_construct.phar" (truncated entry) -PharFileInfo::__construct() expects parameter 1 to be string, array given +PharFileInfo::__construct() expects parameter 1 to be a valid path, array given Cannot access phar file entry '%s' in archive '%s' Cannot call constructor twice '%s' is not a valid phar archive URL (must have at least phar://filename.phar) From e528678b7d0fed4c8251bf7c94171799889cd955 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:13 +0000 Subject: [PATCH 05/46] commit patch 26445514 --- ext/fileinfo/libmagic/funcs.c | 2 +- ext/fileinfo/libmagic/funcs.c.orig | 532 +++++++++++++++++++++++++++++ ext/fileinfo/tests/bug71527.magic | 1 + ext/fileinfo/tests/bug71527.phpt | 19 ++ 4 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 ext/fileinfo/libmagic/funcs.c.orig create mode 100644 ext/fileinfo/tests/bug71527.magic create mode 100644 ext/fileinfo/tests/bug71527.phpt diff --git a/ext/fileinfo/libmagic/funcs.c b/ext/fileinfo/libmagic/funcs.c index 91e9906dcdce3..575be5ecadb57 100644 --- a/ext/fileinfo/libmagic/funcs.c +++ b/ext/fileinfo/libmagic/funcs.c @@ -402,7 +402,7 @@ file_check_mem(struct magic_set *ms, unsigned int level) size_t len; if (level >= ms->c.len) { - len = (ms->c.len += 20) * sizeof(*ms->c.li); + len = (ms->c.len += 20 + level) * sizeof(*ms->c.li); ms->c.li = CAST(struct level_info *, (ms->c.li == NULL) ? emalloc(len) : erealloc(ms->c.li, len)); diff --git a/ext/fileinfo/libmagic/funcs.c.orig b/ext/fileinfo/libmagic/funcs.c.orig new file mode 100644 index 0000000000000..91e9906dcdce3 --- /dev/null +++ b/ext/fileinfo/libmagic/funcs.c.orig @@ -0,0 +1,532 @@ +/* + * Copyright (c) Christos Zoulas 2003. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "file.h" + +#ifndef lint +FILE_RCSID("@(#)$File: funcs.c,v 1.79 2014/12/16 20:52:49 christos Exp $") +#endif /* lint */ + +#include "magic.h" +#include +#include +#include +#include +#if defined(HAVE_WCHAR_H) +#include +#endif +#if defined(HAVE_WCTYPE_H) +#include +#endif +#if defined(HAVE_LOCALE_H) +#include +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)~0) +#endif + +#include "php.h" +#include "main/php_network.h" + +#ifndef PREG_OFFSET_CAPTURE +# define PREG_OFFSET_CAPTURE (1<<8) +#endif + +extern public void convert_libmagic_pattern(zval *pattern, char *val, int len, int options); + +protected int +file_printf(struct magic_set *ms, const char *fmt, ...) +{ + int rv; + va_list ap; + int len; + char *buf = NULL, *newstr; + + va_start(ap, fmt); + len = vspprintf(&buf, 0, fmt, ap); + va_end(ap); + + if (ms->o.buf != NULL) { + len = spprintf(&newstr, 0, "%s%s", ms->o.buf, (buf ? buf : "")); + if (buf) { + efree(buf); + } + efree(ms->o.buf); + ms->o.buf = newstr; + } else { + ms->o.buf = buf; + } + return 0; +} + +/* + * error - print best error message possible + */ +/*VARARGS*/ +private void +file_error_core(struct magic_set *ms, int error, const char *f, va_list va, + size_t lineno) +{ + char *buf = NULL; + + /* Only the first error is ok */ + if (ms->event_flags & EVENT_HAD_ERR) + return; + if (lineno != 0) { + efree(ms->o.buf); + ms->o.buf = NULL; + file_printf(ms, "line %" SIZE_T_FORMAT "u: ", lineno); + } + + vspprintf(&buf, 0, f, va); + va_end(va); + + if (error > 0) { + file_printf(ms, "%s (%s)", (*buf ? buf : ""), strerror(error)); + } else if (*buf) { + file_printf(ms, "%s", buf); + } + + if (buf) { + efree(buf); + } + + ms->event_flags |= EVENT_HAD_ERR; + ms->error = error; +} + +/*VARARGS*/ +protected void +file_error(struct magic_set *ms, int error, const char *f, ...) +{ + va_list va; + va_start(va, f); + file_error_core(ms, error, f, va, 0); + va_end(va); +} + +/* + * Print an error with magic line number. + */ +/*VARARGS*/ +protected void +file_magerror(struct magic_set *ms, const char *f, ...) +{ + va_list va; + va_start(va, f); + file_error_core(ms, 0, f, va, ms->line); + va_end(va); +} + +protected void +file_oomem(struct magic_set *ms, size_t len) +{ + file_error(ms, errno, "cannot allocate %" SIZE_T_FORMAT "u bytes", + len); +} + +protected void +file_badseek(struct magic_set *ms) +{ + file_error(ms, errno, "error seeking"); +} + +protected void +file_badread(struct magic_set *ms) +{ + file_error(ms, errno, "error reading"); +} + +protected int +file_buffer(struct magic_set *ms, php_stream *stream, const char *inname, const void *buf, + size_t nb) +{ + int m = 0, rv = 0, looks_text = 0; + int mime = ms->flags & MAGIC_MIME; + const unsigned char *ubuf = CAST(const unsigned char *, buf); + unichar *u8buf = NULL; + size_t ulen; + const char *code = NULL; + const char *code_mime = "binary"; + const char *type = "application/octet-stream"; + const char *def = "data"; + const char *ftype = NULL; + + if (nb == 0) { + def = "empty"; + type = "application/x-empty"; + goto simple; + } else if (nb == 1) { + def = "very short file (no magic)"; + goto simple; + } + + if ((ms->flags & MAGIC_NO_CHECK_ENCODING) == 0) { + looks_text = file_encoding(ms, ubuf, nb, &u8buf, &ulen, + &code, &code_mime, &ftype); + } + +#ifdef __EMX__ + if ((ms->flags & MAGIC_NO_CHECK_APPTYPE) == 0 && inname) { + switch (file_os2_apptype(ms, inname, buf, nb)) { + case -1: + return -1; + case 0: + break; + default: + return 1; + } + } +#endif + +#if PHP_FILEINFO_UNCOMPRESS + if ((ms->flags & MAGIC_NO_CHECK_COMPRESS) == 0) + if ((m = file_zmagic(ms, stream, inname, ubuf, nb)) != 0) { + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, "zmagic %d\n", m); + goto done_encoding; + } +#endif + /* Check if we have a tar file */ + if ((ms->flags & MAGIC_NO_CHECK_TAR) == 0) + if ((m = file_is_tar(ms, ubuf, nb)) != 0) { + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, "tar %d\n", m); + goto done; + } + + /* Check if we have a CDF file */ + if ((ms->flags & MAGIC_NO_CHECK_CDF) == 0) { + php_socket_t fd; + if (stream && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, 0)) { + if ((m = file_trycdf(ms, fd, ubuf, nb)) != 0) { + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, "cdf %d\n", m); + goto done; + } + } + } + + /* try soft magic tests */ + if ((ms->flags & MAGIC_NO_CHECK_SOFT) == 0) + if ((m = file_softmagic(ms, ubuf, nb, 0, NULL, BINTEST, + looks_text)) != 0) { + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, "softmagic %d\n", m); +#ifdef BUILTIN_ELF + if ((ms->flags & MAGIC_NO_CHECK_ELF) == 0 && m == 1 && + nb > 5 && fd != -1) { + /* + * We matched something in the file, so this + * *might* be an ELF file, and the file is at + * least 5 bytes long, so if it's an ELF file + * it has at least one byte past the ELF magic + * number - try extracting information from the + * ELF headers that cannot easily * be + * extracted with rules in the magic file. + */ + if ((m = file_tryelf(ms, fd, ubuf, nb)) != 0) + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, + "elf %d\n", m); + } +#endif + goto done; + } + + /* try text properties */ + if ((ms->flags & MAGIC_NO_CHECK_TEXT) == 0) { + + if ((m = file_ascmagic(ms, ubuf, nb, looks_text)) != 0) { + if ((ms->flags & MAGIC_DEBUG) != 0) + (void)fprintf(stderr, "ascmagic %d\n", m); + goto done; + } + } + +simple: + /* give up */ + m = 1; + if ((!mime || (mime & MAGIC_MIME_TYPE)) && + file_printf(ms, "%s", mime ? type : def) == -1) { + rv = -1; + } + done: + if ((ms->flags & MAGIC_MIME_ENCODING) != 0) { + if (ms->flags & MAGIC_MIME_TYPE) + if (file_printf(ms, "; charset=") == -1) + rv = -1; + if (file_printf(ms, "%s", code_mime) == -1) + rv = -1; + } + done_encoding: + free(u8buf); + if (rv) + return rv; + + return m; +} + +protected int +file_reset(struct magic_set *ms) +{ + if (ms->mlist[0] == NULL) { + file_error(ms, 0, "no magic files loaded"); + return -1; + } + if (ms->o.buf) { + efree(ms->o.buf); + ms->o.buf = NULL; + } + if (ms->o.pbuf) { + efree(ms->o.pbuf); + ms->o.pbuf = NULL; + } + ms->event_flags &= ~EVENT_HAD_ERR; + ms->error = -1; + return 0; +} + +#define OCTALIFY(n, o) \ + /*LINTED*/ \ + (void)(*(n)++ = '\\', \ + *(n)++ = (((uint32_t)*(o) >> 6) & 3) + '0', \ + *(n)++ = (((uint32_t)*(o) >> 3) & 7) + '0', \ + *(n)++ = (((uint32_t)*(o) >> 0) & 7) + '0', \ + (o)++) + +protected const char * +file_getbuffer(struct magic_set *ms) +{ + char *op, *np; + size_t psize, len; + + if (ms->event_flags & EVENT_HAD_ERR) + return NULL; + + if (ms->flags & MAGIC_RAW) + return ms->o.buf; + + if (ms->o.buf == NULL) + return NULL; + + /* * 4 is for octal representation, + 1 is for NUL */ + len = strlen(ms->o.buf); + if (len > (SIZE_MAX - 1) / 4) { + file_oomem(ms, len); + return NULL; + } + psize = len * 4 + 1; + if ((ms->o.pbuf = CAST(char *, erealloc(ms->o.pbuf, psize))) == NULL) { + file_oomem(ms, psize); + return NULL; + } + +#if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH) + { + mbstate_t state; + wchar_t nextchar; + int mb_conv = 1; + size_t bytesconsumed; + char *eop; + (void)memset(&state, 0, sizeof(mbstate_t)); + + np = ms->o.pbuf; + op = ms->o.buf; + eop = op + len; + + while (op < eop) { + bytesconsumed = mbrtowc(&nextchar, op, + (size_t)(eop - op), &state); + if (bytesconsumed == (size_t)(-1) || + bytesconsumed == (size_t)(-2)) { + mb_conv = 0; + break; + } + + if (iswprint(nextchar)) { + (void)memcpy(np, op, bytesconsumed); + op += bytesconsumed; + np += bytesconsumed; + } else { + while (bytesconsumed-- > 0) + OCTALIFY(np, op); + } + } + *np = '\0'; + + /* Parsing succeeded as a multi-byte sequence */ + if (mb_conv != 0) + return ms->o.pbuf; + } +#endif + + for (np = ms->o.pbuf, op = ms->o.buf; *op;) { + if (isprint((unsigned char)*op)) { + *np++ = *op++; + } else { + OCTALIFY(np, op); + } + } + *np = '\0'; + return ms->o.pbuf; +} + +protected int +file_check_mem(struct magic_set *ms, unsigned int level) +{ + size_t len; + + if (level >= ms->c.len) { + len = (ms->c.len += 20) * sizeof(*ms->c.li); + ms->c.li = CAST(struct level_info *, (ms->c.li == NULL) ? + emalloc(len) : + erealloc(ms->c.li, len)); + if (ms->c.li == NULL) { + file_oomem(ms, len); + return -1; + } + } + ms->c.li[level].got_match = 0; +#ifdef ENABLE_CONDITIONALS + ms->c.li[level].last_match = 0; + ms->c.li[level].last_cond = COND_NONE; +#endif /* ENABLE_CONDITIONALS */ + return 0; +} + +protected size_t +file_printedlen(const struct magic_set *ms) +{ + return ms->o.buf == NULL ? 0 : strlen(ms->o.buf); +} + +protected int +file_replace(struct magic_set *ms, const char *pat, const char *rep) +{ + zval patt; + int opts = 0; + pcre_cache_entry *pce; + zend_string *res; + zval repl; + int rep_cnt = 0; + + (void)setlocale(LC_CTYPE, "C"); + + opts |= PCRE_MULTILINE; + convert_libmagic_pattern(&patt, pat, strlen(pat), opts); + if ((pce = pcre_get_compiled_regex_cache(Z_STR(patt))) == NULL) { + zval_ptr_dtor(&patt); + rep_cnt = -1; + goto out; + } + zval_ptr_dtor(&patt); + + ZVAL_STRING(&repl, rep); + res = php_pcre_replace_impl(pce, NULL, ms->o.buf, strlen(ms->o.buf), &repl, 0, -1, &rep_cnt); + + zval_ptr_dtor(&repl); + if (NULL == res) { + rep_cnt = -1; + goto out; + } + + strncpy(ms->o.buf, ZSTR_VAL(res), ZSTR_LEN(res)); + ms->o.buf[ZSTR_LEN(res)] = '\0'; + + zend_string_release(res); + +out: + (void)setlocale(LC_CTYPE, ""); + return rep_cnt; +} + +protected file_pushbuf_t * +file_push_buffer(struct magic_set *ms) +{ + file_pushbuf_t *pb; + + if (ms->event_flags & EVENT_HAD_ERR) + return NULL; + + if ((pb = (CAST(file_pushbuf_t *, emalloc(sizeof(*pb))))) == NULL) + return NULL; + + pb->buf = ms->o.buf; + pb->offset = ms->offset; + + ms->o.buf = NULL; + ms->offset = 0; + + return pb; +} + +protected char * +file_pop_buffer(struct magic_set *ms, file_pushbuf_t *pb) +{ + char *rbuf; + + if (ms->event_flags & EVENT_HAD_ERR) { + efree(pb->buf); + efree(pb); + return NULL; + } + + rbuf = ms->o.buf; + + ms->o.buf = pb->buf; + ms->offset = pb->offset; + + efree(pb); + return rbuf; +} + +/* + * convert string to ascii printable format. + */ +protected char * +file_printable(char *buf, size_t bufsiz, const char *str) +{ + char *ptr, *eptr; + const unsigned char *s = (const unsigned char *)str; + + for (ptr = buf, eptr = ptr + bufsiz - 1; ptr < eptr && *s; s++) { + if (isprint(*s)) { + *ptr++ = *s; + continue; + } + if (ptr >= eptr - 3) + break; + *ptr++ = '\\'; + *ptr++ = ((*s >> 6) & 7) + '0'; + *ptr++ = ((*s >> 3) & 7) + '0'; + *ptr++ = ((*s >> 0) & 7) + '0'; + } + *ptr = '\0'; + return buf; +} + diff --git a/ext/fileinfo/tests/bug71527.magic b/ext/fileinfo/tests/bug71527.magic new file mode 100644 index 0000000000000..14d77817be241 --- /dev/null +++ b/ext/fileinfo/tests/bug71527.magic @@ -0,0 +1 @@ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \ No newline at end of file diff --git a/ext/fileinfo/tests/bug71527.phpt b/ext/fileinfo/tests/bug71527.phpt new file mode 100644 index 0000000000000..f5b1d860e809a --- /dev/null +++ b/ext/fileinfo/tests/bug71527.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #71527 Buffer over-write in finfo_open with malformed magic file +--SKIPIF-- + +--EXPECTF-- +Warning: finfo_open(): Failed to load magic database at '%sbug71527.magic'. in %sbug71527.php on line %d + +Warning: finfo_file() expects parameter 1 to be resource, boolean given in %sbug71527.php on line %d +bool(false) From 25ba74f6889aae240b3798b0f336ad5ecdd99e2c Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:15 +0000 Subject: [PATCH 06/46] commit patch 23528025 --- ext/zip/php_zip.c | 4 +- ext/zip/php_zip.c.orig | 3152 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3154 insertions(+), 2 deletions(-) create mode 100644 ext/zip/php_zip.c.orig diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index c31ace1f8dbd6..7ebee116ec2ab 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -1282,7 +1282,7 @@ static PHP_NAMED_FUNCTION(zif_zip_entry_read) } if (zr_rsrc->zf) { - buffer = zend_string_alloc(len, 0); + buffer = zend_string_safe_alloc(1, len, 0, 0); n = zip_fread(zr_rsrc->zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); if (n > 0) { ZSTR_VAL(buffer)[n] = '\0'; @@ -2729,7 +2729,7 @@ static void php_zip_get_from(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ RETURN_FALSE; } - buffer = zend_string_alloc(len, 0); + buffer = zend_string_safe_alloc(1, len, 0, 0); n = zip_fread(zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); if (n < 1) { zend_string_free(buffer); diff --git a/ext/zip/php_zip.c.orig b/ext/zip/php_zip.c.orig new file mode 100644 index 0000000000000..300075861f6d0 --- /dev/null +++ b/ext/zip/php_zip.c.orig @@ -0,0 +1,3152 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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: Piere-Alain Joye | + +----------------------------------------------------------------------+ +*/ + +/* $Id: c31ace1f8dbd6f39bc76c84611423c885aa8e9af $ */ + +#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 "ext/pcre/php_pcre.h" +#include "ext/standard/php_filestat.h" +#include "php_zip.h" + +/* zip_open is a macro for renaming libzip zipopen, so we need to use PHP_NAMED_FUNCTION */ +static PHP_NAMED_FUNCTION(zif_zip_open); +static PHP_NAMED_FUNCTION(zif_zip_read); +static PHP_NAMED_FUNCTION(zif_zip_close); +static PHP_NAMED_FUNCTION(zif_zip_entry_read); +static PHP_NAMED_FUNCTION(zif_zip_entry_filesize); +static PHP_NAMED_FUNCTION(zif_zip_entry_name); +static PHP_NAMED_FUNCTION(zif_zip_entry_compressedsize); +static PHP_NAMED_FUNCTION(zif_zip_entry_compressionmethod); +static PHP_NAMED_FUNCTION(zif_zip_entry_open); +static PHP_NAMED_FUNCTION(zif_zip_entry_close); + +#ifdef HAVE_GLOB +#ifndef PHP_WIN32 +#include +#else +#include "win32/glob.h" +#endif +#endif + +/* {{{ Resource le */ +static int le_zip_dir; +#define le_zip_dir_name "Zip Directory" +static int le_zip_entry; +#define le_zip_entry_name "Zip Entry" +/* }}} */ + +/* {{{ PHP_ZIP_STAT_INDEX(za, index, flags, sb) */ +#define PHP_ZIP_STAT_INDEX(za, index, flags, sb) \ + if (zip_stat_index(za, index, flags, &sb) != 0) { \ + RETURN_FALSE; \ + } +/* }}} */ + +/* {{{ PHP_ZIP_STAT_PATH(za, path, path_len, flags, sb) */ +#define PHP_ZIP_STAT_PATH(za, path, path_len, flags, sb) \ + if (path_len < 1) { \ + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); \ + RETURN_FALSE; \ + } \ + if (zip_stat(za, path, flags, &sb) != 0) { \ + RETURN_FALSE; \ + } +/* }}} */ + +/* {{{ PHP_ZIP_SET_FILE_COMMENT(za, index, comment, comment_len) */ +#define PHP_ZIP_SET_FILE_COMMENT(za, index, comment, comment_len) \ + if (comment_len == 0) { \ + /* Passing NULL remove the existing comment */ \ + if (zip_set_file_comment(za, index, NULL, 0) < 0) { \ + RETURN_FALSE; \ + } \ + } else if (zip_set_file_comment(za, index, comment, comment_len) < 0) { \ + RETURN_FALSE; \ + } \ + RETURN_TRUE; +/* }}} */ + +# define add_ascii_assoc_string add_assoc_string +# define add_ascii_assoc_long add_assoc_long + +/* Flatten a path by making a relative path (to .)*/ +static char * php_zip_make_relative_path(char *path, size_t path_len) /* {{{ */ +{ + char *path_begin = path; + size_t i; + + if (path_len < 1 || path == NULL) { + return NULL; + } + + if (IS_SLASH(path[0])) { + return path + 1; + } + + i = path_len; + + while (1) { + while (i > 0 && !IS_SLASH(path[i])) { + i--; + } + + if (!i) { + return path; + } + + if (i >= 2 && (path[i -1] == '.' || path[i -1] == ':')) { + /* i is the position of . or :, add 1 for / */ + path_begin = path + i + 1; + break; + } + i--; + } + + return path_begin; +} +/* }}} */ + +# define CWD_STATE_ALLOC(l) emalloc(l) +# define CWD_STATE_FREE(s) efree(s) + +/* {{{ php_zip_extract_file */ +static int php_zip_extract_file(struct zip * za, char *dest, char *file, int file_len) +{ + php_stream_statbuf ssb; + struct zip_file *zf; + struct zip_stat sb; + char b[8192]; + int n, len, ret; + php_stream *stream; + char *fullpath; + char *file_dirname_fullpath; + char file_dirname[MAXPATHLEN]; + size_t dir_len; + int is_dir_only = 0; + char *path_cleaned; + size_t path_cleaned_len; + cwd_state new_state; + zend_string *file_basename; + + new_state.cwd = CWD_STATE_ALLOC(1); + new_state.cwd[0] = '\0'; + new_state.cwd_length = 0; + + /* Clean/normlize the path and then transform any path (absolute or relative) + to a path relative to cwd (../../mydir/foo.txt > mydir/foo.txt) + */ + virtual_file_ex(&new_state, file, NULL, CWD_EXPAND); + path_cleaned = php_zip_make_relative_path(new_state.cwd, new_state.cwd_length); + if(!path_cleaned) { + return 0; + } + path_cleaned_len = strlen(path_cleaned); + + if (path_cleaned_len >= MAXPATHLEN || zip_stat(za, file, 0, &sb) != 0) { + return 0; + } + + /* it is a directory only, see #40228 */ + if (path_cleaned_len > 1 && IS_SLASH(path_cleaned[path_cleaned_len - 1])) { + len = spprintf(&file_dirname_fullpath, 0, "%s/%s", dest, path_cleaned); + is_dir_only = 1; + } else { + memcpy(file_dirname, path_cleaned, path_cleaned_len); + dir_len = php_dirname(file_dirname, path_cleaned_len); + + if (dir_len <= 0 || (dir_len == 1 && file_dirname[0] == '.')) { + len = spprintf(&file_dirname_fullpath, 0, "%s", dest); + } else { + len = spprintf(&file_dirname_fullpath, 0, "%s/%s", dest, file_dirname); + } + + file_basename = php_basename(path_cleaned, path_cleaned_len, NULL, 0); + + if (ZIP_OPENBASEDIR_CHECKPATH(file_dirname_fullpath)) { + efree(file_dirname_fullpath); + zend_string_release(file_basename); + CWD_STATE_FREE(new_state.cwd); + return 0; + } + } + + /* let see if the path already exists */ + if (php_stream_stat_path_ex(file_dirname_fullpath, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL) < 0) { + ret = php_stream_mkdir(file_dirname_fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE|REPORT_ERRORS, NULL); + if (!ret) { + efree(file_dirname_fullpath); + if (!is_dir_only) { + zend_string_release(file_basename); + CWD_STATE_FREE(new_state.cwd); + } + return 0; + } + } + + /* it is a standalone directory, job done */ + if (is_dir_only) { + efree(file_dirname_fullpath); + CWD_STATE_FREE(new_state.cwd); + return 1; + } + + len = spprintf(&fullpath, 0, "%s/%s", file_dirname_fullpath, ZSTR_VAL(file_basename)); + if (!len) { + efree(file_dirname_fullpath); + zend_string_release(file_basename); + CWD_STATE_FREE(new_state.cwd); + return 0; + } else if (len > MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "Full extraction path exceed MAXPATHLEN (%i)", MAXPATHLEN); + efree(file_dirname_fullpath); + zend_string_release(file_basename); + CWD_STATE_FREE(new_state.cwd); + return 0; + } + + /* check again the full path, not sure if it + * is required, does a file can have a different + * safemode status as its parent folder? + */ + if (ZIP_OPENBASEDIR_CHECKPATH(fullpath)) { + efree(fullpath); + efree(file_dirname_fullpath); + zend_string_release(file_basename); + CWD_STATE_FREE(new_state.cwd); + return 0; + } + + stream = php_stream_open_wrapper(fullpath, "w+b", REPORT_ERRORS, NULL); + + if (stream == NULL) { + n = -1; + goto done; + } + + zf = zip_fopen(za, file, 0); + if (zf == NULL) { + n = -1; + php_stream_close(stream); + goto done; + } + + n = 0; + + while ((n=zip_fread(zf, b, sizeof(b))) > 0) { + php_stream_write(stream, b, n); + } + + php_stream_close(stream); + n = zip_fclose(zf); + +done: + efree(fullpath); + zend_string_release(file_basename); + efree(file_dirname_fullpath); + CWD_STATE_FREE(new_state.cwd); + + if (n<0) { + return 0; + } else { + return 1; + } +} +/* }}} */ + +static int php_zip_add_file(struct zip *za, const char *filename, size_t filename_len, + char *entry_name, size_t entry_name_len, long offset_start, long offset_len) /* {{{ */ +{ + struct zip_source *zs; + char resolved_path[MAXPATHLEN]; + zval exists_flag; + + + if (ZIP_OPENBASEDIR_CHECKPATH(filename)) { + return -1; + } + + if (!expand_filepath(filename, resolved_path)) { + return -1; + } + + php_stat(resolved_path, strlen(resolved_path), FS_EXISTS, &exists_flag); + if (Z_TYPE(exists_flag) == IS_FALSE) { + return -1; + } + + zs = zip_source_file(za, resolved_path, offset_start, offset_len); + if (!zs) { + return -1; + } + if (zip_file_add(za, entry_name, zs, ZIP_FL_OVERWRITE) < 0) { + zip_source_free(zs); + return -1; + } else { + zip_error_clear(za); + return 1; + } +} +/* }}} */ + +static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char **remove_path, size_t *remove_path_len, char **add_path, size_t *add_path_len) /* {{{ */ +{ + zval *option; + if ((option = zend_hash_str_find(Z_ARRVAL_P(options), "remove_all_path", sizeof("remove_all_path") - 1)) != NULL) { + *remove_all_path = zval_get_long(option); + } + + /* If I add more options, it would make sense to create a nice static struct and loop over it. */ + if ((option = zend_hash_str_find(Z_ARRVAL_P(options), "remove_path", sizeof("remove_path") - 1)) != NULL) { + if (Z_TYPE_P(option) != IS_STRING) { + php_error_docref(NULL, E_WARNING, "remove_path option expected to be a string"); + return -1; + } + + if (Z_STRLEN_P(option) < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string given as remove_path option"); + return -1; + } + + if (Z_STRLEN_P(option) >= MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "remove_path string is too long (max: %i, %i given)", + MAXPATHLEN - 1, Z_STRLEN_P(option)); + return -1; + } + *remove_path_len = Z_STRLEN_P(option); + *remove_path = Z_STRVAL_P(option); + } + + if ((option = zend_hash_str_find(Z_ARRVAL_P(options), "add_path", sizeof("add_path") - 1)) != NULL) { + if (Z_TYPE_P(option) != IS_STRING) { + php_error_docref(NULL, E_WARNING, "add_path option expected to be a string"); + return -1; + } + + if (Z_STRLEN_P(option) < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string given as the add_path option"); + return -1; + } + + if (Z_STRLEN_P(option) >= MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "add_path string too long (max: %i, %i given)", + MAXPATHLEN - 1, Z_STRLEN_P(option)); + return -1; + } + *add_path_len = Z_STRLEN_P(option); + *add_path = Z_STRVAL_P(option); + } + return 1; +} +/* }}} */ + +/* {{{ REGISTER_ZIP_CLASS_CONST_LONG */ +#define REGISTER_ZIP_CLASS_CONST_LONG(const_name, value) \ + zend_declare_class_constant_long(zip_class_entry, const_name, sizeof(const_name)-1, (zend_long)value); +/* }}} */ + +/* {{{ ZIP_FROM_OBJECT */ +#define ZIP_FROM_OBJECT(intern, object) \ + { \ + ze_zip_object *obj = Z_ZIP_P(object); \ + intern = obj->za; \ + if (!intern) { \ + php_error_docref(NULL, E_WARNING, "Invalid or uninitialized Zip object"); \ + RETURN_FALSE; \ + } \ + } +/* }}} */ + +/* {{{ RETURN_SB(sb) */ +#define RETURN_SB(sb) \ + { \ + array_init(return_value); \ + add_ascii_assoc_string(return_value, "name", (char *)(sb)->name); \ + add_ascii_assoc_long(return_value, "index", (zend_long) (sb)->index); \ + add_ascii_assoc_long(return_value, "crc", (zend_long) (sb)->crc); \ + add_ascii_assoc_long(return_value, "size", (zend_long) (sb)->size); \ + add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \ + add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \ + add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \ + } +/* }}} */ + +static int php_zip_status(struct zip *za) /* {{{ */ +{ +#if LIBZIP_VERSION_MAJOR < 1 + int zep, syp; + + zip_error_get(za, &zep, &syp); +#else + int zep; + zip_error_t *err; + + err = zip_get_error(za); + zep = zip_error_code_zip(err); + zip_error_fini(err); +#endif + return zep; +} +/* }}} */ + +static int php_zip_status_sys(struct zip *za) /* {{{ */ +{ +#if LIBZIP_VERSION_MAJOR < 1 + int zep, syp; + + zip_error_get(za, &zep, &syp); +#else + int syp; + zip_error_t *err; + + err = zip_get_error(za); + syp = zip_error_code_system(err); + zip_error_fini(err); +#endif + return syp; +} +/* }}} */ + +static int php_zip_get_num_files(struct zip *za) /* {{{ */ +{ + return zip_get_num_files(za); +} +/* }}} */ + +static char * php_zipobj_get_filename(ze_zip_object *obj) /* {{{ */ +{ + + if (!obj) { + return NULL; + } + + if (obj->filename) { + return obj->filename; + } + return NULL; +} +/* }}} */ + +static char * php_zipobj_get_zip_comment(struct zip *za, int *len) /* {{{ */ +{ + if (za) { + return (char *)zip_get_archive_comment(za, len, 0); + } + return NULL; +} +/* }}} */ + +#ifdef HAVE_GLOB /* {{{ */ +#ifndef GLOB_ONLYDIR +#define GLOB_ONLYDIR (1<<30) +#define GLOB_EMULATE_ONLYDIR +#define GLOB_FLAGMASK (~GLOB_ONLYDIR) +#else +#define GLOB_FLAGMASK (~0) +#endif +#ifndef GLOB_BRACE +# define GLOB_BRACE 0 +#endif +#ifndef GLOB_MARK +# define GLOB_MARK 0 +#endif +#ifndef GLOB_NOSORT +# define GLOB_NOSORT 0 +#endif +#ifndef GLOB_NOCHECK +# define GLOB_NOCHECK 0 +#endif +#ifndef GLOB_NOESCAPE +# define GLOB_NOESCAPE 0 +#endif +#ifndef GLOB_ERR +# define GLOB_ERR 0 +#endif + +/* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */ +#define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR) + +#endif /* }}} */ + +int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_value) /* {{{ */ +{ +#ifdef HAVE_GLOB + char cwd[MAXPATHLEN]; + int cwd_skip = 0; +#ifdef ZTS + char work_pattern[MAXPATHLEN]; + char *result; +#endif + glob_t globbuf; + int n; + int ret; + + if (pattern_len >= MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN); + return -1; + } + + if ((GLOB_AVAILABLE_FLAGS & flags) != flags) { + php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform"); + return -1; + } + +#ifdef ZTS + if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) { + result = VCWD_GETCWD(cwd, MAXPATHLEN); + if (!result) { + cwd[0] = '\0'; + } +#ifdef PHP_WIN32 + if (IS_SLASH(*pattern)) { + cwd[2] = '\0'; + } +#endif + cwd_skip = strlen(cwd)+1; + + snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern); + pattern = work_pattern; + } +#endif + + globbuf.gl_offs = 0; + if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) { +#ifdef GLOB_NOMATCH + if (GLOB_NOMATCH == ret) { + /* Some glob implementation simply return no data if no matches + were found, others return the GLOB_NOMATCH error code. + We don't want to treat GLOB_NOMATCH as an error condition + so that PHP glob() behaves the same on both types of + implementations and so that 'foreach (glob() as ...' + can be used for simple glob() calls without further error + checking. + */ + array_init(return_value); + return 0; + } +#endif + return 0; + } + + /* now catch the FreeBSD style of "no matches" */ + if (!globbuf.gl_pathc || !globbuf.gl_pathv) { + array_init(return_value); + return 0; + } + + /* we assume that any glob pattern will match files from one directory only + so checking the dirname of the first match should be sufficient */ + strncpy(cwd, globbuf.gl_pathv[0], MAXPATHLEN); + if (ZIP_OPENBASEDIR_CHECKPATH(cwd)) { + return -1; + } + + array_init(return_value); + for (n = 0; n < globbuf.gl_pathc; n++) { + /* we need to do this every time since GLOB_ONLYDIR does not guarantee that + * all directories will be filtered. GNU libc documentation states the + * following: + * If the information about the type of the file is easily available + * non-directories will be rejected but no extra work will be done to + * determine the information for each file. I.e., the caller must still be + * able to filter directories out. + */ + if (flags & GLOB_ONLYDIR) { + zend_stat_t s; + + if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) { + continue; + } + + if (S_IFDIR != (s.st_mode & S_IFMT)) { + continue; + } + } + add_next_index_string(return_value, globbuf.gl_pathv[n]+cwd_skip); + } + + globfree(&globbuf); + return globbuf.gl_pathc; +#else + php_error_docref(NULL, E_ERROR, "Glob support is not available"); + return 0; +#endif /* HAVE_GLOB */ +} +/* }}} */ + +int php_zip_pcre(zend_string *regexp, char *path, int path_len, zval *return_value) /* {{{ */ +{ +#ifdef ZTS + char cwd[MAXPATHLEN]; + int cwd_skip = 0; + char work_path[MAXPATHLEN]; + char *result; +#endif + int files_cnt; + zend_string **namelist; + +#ifdef ZTS + if (!IS_ABSOLUTE_PATH(path, path_len)) { + result = VCWD_GETCWD(cwd, MAXPATHLEN); + if (!result) { + cwd[0] = '\0'; + } +#ifdef PHP_WIN32 + if (IS_SLASH(*path)) { + cwd[2] = '\0'; + } +#endif + cwd_skip = strlen(cwd)+1; + + snprintf(work_path, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, path); + path = work_path; + } +#endif + + if (ZIP_OPENBASEDIR_CHECKPATH(path)) { + return -1; + } + + files_cnt = php_stream_scandir(path, &namelist, NULL, (void *) php_stream_dirent_alphasort); + + if (files_cnt > 0) { + pcre *re = NULL; + pcre_extra *pcre_extra = NULL; + int preg_options = 0, i; + + re = pcre_get_compiled_regex(regexp, &pcre_extra, &preg_options); + if (!re) { + php_error_docref(NULL, E_WARNING, "Invalid expression"); + return -1; + } + + array_init(return_value); + + /* only the files, directories are ignored */ + for (i = 0; i < files_cnt; i++) { + zend_stat_t s; + char fullpath[MAXPATHLEN]; + int ovector[3]; + int matches; + int namelist_len = ZSTR_LEN(namelist[i]); + + if ((namelist_len == 1 && ZSTR_VAL(namelist[i])[0] == '.') || + (namelist_len == 2 && ZSTR_VAL(namelist[i])[0] == '.' && ZSTR_VAL(namelist[i])[1] == '.')) { + zend_string_release(namelist[i]); + continue; + } + + if ((path_len + namelist_len + 1) >= MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "add_path string too long (max: %i, %i given)", + MAXPATHLEN - 1, (path_len + namelist_len + 1)); + zend_string_release(namelist[i]); + break; + } + + snprintf(fullpath, MAXPATHLEN, "%s%c%s", path, DEFAULT_SLASH, ZSTR_VAL(namelist[i])); + + if (0 != VCWD_STAT(fullpath, &s)) { + php_error_docref(NULL, E_WARNING, "Cannot read <%s>", fullpath); + zend_string_release(namelist[i]); + continue; + } + + if (S_IFDIR == (s.st_mode & S_IFMT)) { + zend_string_release(namelist[i]); + continue; + } + + matches = pcre_exec(re, NULL, ZSTR_VAL(namelist[i]), ZSTR_LEN(namelist[i]), 0, 0, ovector, 3); + /* 0 means that the vector is too small to hold all the captured substring offsets */ + if (matches < 0) { + zend_string_release(namelist[i]); + continue; + } + + add_next_index_string(return_value, fullpath); + zend_string_release(namelist[i]); + } + efree(namelist); + } + return files_cnt; +} +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_open, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_close, 0, 0, 1) + ZEND_ARG_INFO(0, zip) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_read, 0, 0, 1) + ZEND_ARG_INFO(0, zip) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_open, 0, 0, 2) + ZEND_ARG_INFO(0, zip_dp) + ZEND_ARG_INFO(0, zip_entry) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_close, 0, 0, 1) + ZEND_ARG_INFO(0, zip_ent) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_read, 0, 0, 1) + ZEND_ARG_INFO(0, zip_entry) + ZEND_ARG_INFO(0, len) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_name, 0, 0, 1) + ZEND_ARG_INFO(0, zip_entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_compressedsize, 0, 0, 1) + ZEND_ARG_INFO(0, zip_entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_filesize, 0, 0, 1) + ZEND_ARG_INFO(0, zip_entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_entry_compressionmethod, 0, 0, 1) + ZEND_ARG_INFO(0, zip_entry) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ zend_function_entry */ +static const zend_function_entry zip_functions[] = { + ZEND_RAW_FENTRY("zip_open", zif_zip_open, arginfo_zip_open, 0) + ZEND_RAW_FENTRY("zip_close", zif_zip_close, arginfo_zip_close, 0) + ZEND_RAW_FENTRY("zip_read", zif_zip_read, arginfo_zip_read, 0) + PHP_FE(zip_entry_open, arginfo_zip_entry_open) + PHP_FE(zip_entry_close, arginfo_zip_entry_close) + PHP_FE(zip_entry_read, arginfo_zip_entry_read) + PHP_FE(zip_entry_filesize, arginfo_zip_entry_filesize) + PHP_FE(zip_entry_name, arginfo_zip_entry_name) + PHP_FE(zip_entry_compressedsize, arginfo_zip_entry_compressedsize) + PHP_FE(zip_entry_compressionmethod, arginfo_zip_entry_compressionmethod) +#ifdef PHP_FE_END + PHP_FE_END +#else + {NULL,NULL,NULL} +#endif +}; +/* }}} */ + +/* {{{ ZE2 OO definitions */ +static zend_class_entry *zip_class_entry; +static zend_object_handlers zip_object_handlers; + +static HashTable zip_prop_handlers; + +typedef int (*zip_read_int_t)(struct zip *za); +typedef char *(*zip_read_const_char_t)(struct zip *za, int *len); +typedef char *(*zip_read_const_char_from_ze_t)(ze_zip_object *obj); + +typedef struct _zip_prop_handler { + zip_read_int_t read_int_func; + zip_read_const_char_t read_const_char_func; + zip_read_const_char_from_ze_t read_const_char_from_obj_func; + + int type; +} zip_prop_handler; +/* }}} */ + +static void php_zip_register_prop_handler(HashTable *prop_handler, char *name, zip_read_int_t read_int_func, zip_read_const_char_t read_char_func, zip_read_const_char_from_ze_t read_char_from_obj_func, int rettype) /* {{{ */ +{ + zip_prop_handler hnd; + + hnd.read_const_char_func = read_char_func; + hnd.read_int_func = read_int_func; + hnd.read_const_char_from_obj_func = read_char_from_obj_func; + hnd.type = rettype; + zend_hash_str_add_mem(prop_handler, name, strlen(name), &hnd, sizeof(zip_prop_handler)); +} +/* }}} */ + +static zval *php_zip_property_reader(ze_zip_object *obj, zip_prop_handler *hnd, zval *rv) /* {{{ */ +{ + const char *retchar = NULL; + int retint = 0; + int len = 0; + + if (obj && obj->za != NULL) { + if (hnd->read_const_char_func) { + retchar = hnd->read_const_char_func(obj->za, &len); + } else { + if (hnd->read_int_func) { + retint = hnd->read_int_func(obj->za); + if (retint == -1) { + php_error_docref(NULL, E_WARNING, "Internal zip error returned"); + return NULL; + } + } else { + if (hnd->read_const_char_from_obj_func) { + retchar = hnd->read_const_char_from_obj_func(obj); + len = strlen(retchar); + } + } + } + } + + switch (hnd->type) { + case IS_STRING: + if (retchar) { + ZVAL_STRINGL(rv, (char *) retchar, len); + } else { + ZVAL_EMPTY_STRING(rv); + } + break; + /* case IS_TRUE */ + case IS_FALSE: + ZVAL_BOOL(rv, (long)retint); + break; + case IS_LONG: + ZVAL_LONG(rv, (long)retint); + break; + default: + ZVAL_NULL(rv); + } + + return rv; +} +/* }}} */ + +static zval *php_zip_get_property_ptr_ptr(zval *object, zval *member, int type, void **cache_slot) /* {{{ */ +{ + ze_zip_object *obj; + zval tmp_member; + zval *retval = NULL; + zip_prop_handler *hnd = NULL; + zend_object_handlers *std_hnd; + + if (Z_TYPE_P(member) != IS_STRING) { + ZVAL_COPY(&tmp_member, member); + convert_to_string(&tmp_member); + member = &tmp_member; + cache_slot = NULL; + } + + obj = Z_ZIP_P(object); + + if (obj->prop_handler != NULL) { + hnd = zend_hash_find_ptr(obj->prop_handler, Z_STR_P(member)); + } + + if (hnd == NULL) { + std_hnd = zend_get_std_object_handlers(); + retval = std_hnd->get_property_ptr_ptr(object, member, type, cache_slot); + } + + if (member == &tmp_member) { + zval_dtor(member); + } + + return retval; +} +/* }}} */ + +static zval *php_zip_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) /* {{{ */ +{ + ze_zip_object *obj; + zval tmp_member; + zval *retval = NULL; + zip_prop_handler *hnd = NULL; + zend_object_handlers *std_hnd; + + if (Z_TYPE_P(member) != IS_STRING) { + ZVAL_COPY(&tmp_member, member); + convert_to_string(&tmp_member); + member = &tmp_member; + cache_slot = NULL; + } + + obj = Z_ZIP_P(object); + + if (obj->prop_handler != NULL) { + hnd = zend_hash_find_ptr(obj->prop_handler, Z_STR_P(member)); + } + + if (hnd != NULL) { + retval = php_zip_property_reader(obj, hnd, rv); + if (retval == NULL) { + retval = &EG(uninitialized_zval); + } + } else { + std_hnd = zend_get_std_object_handlers(); + retval = std_hnd->read_property(object, member, type, cache_slot, rv); + } + + if (member == &tmp_member) { + zval_dtor(member); + } + + return retval; +} +/* }}} */ + +static int php_zip_has_property(zval *object, zval *member, int type, void **cache_slot) /* {{{ */ +{ + ze_zip_object *obj; + zval tmp_member; + zip_prop_handler *hnd = NULL; + zend_object_handlers *std_hnd; + int retval = 0; + + if (Z_TYPE_P(member) != IS_STRING) { + ZVAL_COPY(&tmp_member, member); + convert_to_string(&tmp_member); + member = &tmp_member; + cache_slot = NULL; + } + + obj = Z_ZIP_P(object); + + if (obj->prop_handler != NULL) { + hnd = zend_hash_find_ptr(obj->prop_handler, Z_STR_P(member)); + } + + if (hnd != NULL) { + zval tmp, *prop; + + if (type == 2) { + retval = 1; + } else if ((prop = php_zip_property_reader(obj, hnd, &tmp)) != NULL) { + if (type == 1) { + retval = zend_is_true(&tmp); + } else if (type == 0) { + retval = (Z_TYPE(tmp) != IS_NULL); + } + } + + zval_ptr_dtor(&tmp); + } else { + std_hnd = zend_get_std_object_handlers(); + retval = std_hnd->has_property(object, member, type, cache_slot); + } + + if (member == &tmp_member) { + zval_dtor(member); + } + + return retval; +} +/* }}} */ + +static HashTable *php_zip_get_properties(zval *object)/* {{{ */ +{ + ze_zip_object *obj; + HashTable *props; + zip_prop_handler *hnd; + zend_string *key; + + obj = Z_ZIP_P(object); + props = zend_std_get_properties(object); + + if (obj->prop_handler == NULL) { + return NULL; + } + + ZEND_HASH_FOREACH_STR_KEY_PTR(obj->prop_handler, key, hnd) { + zval *ret, val; + ret = php_zip_property_reader(obj, hnd, &val); + if (ret == NULL) { + ret = &EG(uninitialized_zval); + } + zend_hash_update(props, key, ret); + } ZEND_HASH_FOREACH_END(); + + return props; +} +/* }}} */ + +static void php_zip_object_free_storage(zend_object *object) /* {{{ */ +{ + ze_zip_object * intern = php_zip_fetch_object(object); + int i; + + if (!intern) { + return; + } + if (intern->za) { + if (zip_close(intern->za) != 0) { + php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za)); + return; + } + intern->za = NULL; + } + + if (intern->buffers_cnt>0) { + for (i=0; ibuffers_cnt; i++) { + efree(intern->buffers[i]); + } + efree(intern->buffers); + } + + intern->za = NULL; + zend_object_std_dtor(&intern->zo); + + if (intern->filename) { + efree(intern->filename); + } +} +/* }}} */ + +static zend_object *php_zip_object_new(zend_class_entry *class_type) /* {{{ */ +{ + ze_zip_object *intern; + + intern = ecalloc(1, sizeof(ze_zip_object) + zend_object_properties_size(class_type)); + intern->prop_handler = &zip_prop_handlers; + zend_object_std_init(&intern->zo, class_type); + object_properties_init(&intern->zo, class_type); + intern->zo.handlers = &zip_object_handlers; + + return &intern->zo; +} +/* }}} */ + +/* {{{ Resource dtors */ + +/* {{{ php_zip_free_dir */ +static void php_zip_free_dir(zend_resource *rsrc) +{ + zip_rsrc * zip_int = (zip_rsrc *) rsrc->ptr; + + if (zip_int) { + if (zip_int->za) { + if (zip_close(zip_int->za) != 0) { + php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context"); + } + zip_int->za = NULL; + } + + efree(rsrc->ptr); + + rsrc->ptr = NULL; + } +} +/* }}} */ + +/* {{{ php_zip_free_entry */ +static void php_zip_free_entry(zend_resource *rsrc) +{ + zip_read_rsrc *zr_rsrc = (zip_read_rsrc *) rsrc->ptr; + + if (zr_rsrc) { + if (zr_rsrc->zf) { + zip_fclose(zr_rsrc->zf); + zr_rsrc->zf = NULL; + } + efree(zr_rsrc); + rsrc->ptr = NULL; + } +} +/* }}} */ + +/* }}}*/ + +/* reset macro */ + +/* {{{ function prototypes */ +static PHP_MINIT_FUNCTION(zip); +static PHP_MSHUTDOWN_FUNCTION(zip); +static PHP_MINFO_FUNCTION(zip); +/* }}} */ + +/* {{{ zip_module_entry + */ +zend_module_entry zip_module_entry = { + STANDARD_MODULE_HEADER, + "zip", + zip_functions, + PHP_MINIT(zip), + PHP_MSHUTDOWN(zip), + NULL, + NULL, + PHP_MINFO(zip), + PHP_ZIP_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_ZIP +ZEND_GET_MODULE(zip) +#endif +/* set macro */ + +/* {{{ proto resource zip_open(string filename) +Create new zip using source uri for output */ +static PHP_NAMED_FUNCTION(zif_zip_open) +{ + char resolved_path[MAXPATHLEN + 1]; + zip_rsrc *rsrc_int; + int err = 0; + zend_string *filename; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &filename) == FAILURE) { + return; + } + + if (ZSTR_LEN(filename) == 0) { + php_error_docref(NULL, E_WARNING, "Empty string as source"); + RETURN_FALSE; + } + + if (ZIP_OPENBASEDIR_CHECKPATH(ZSTR_VAL(filename))) { + RETURN_FALSE; + } + + if(!expand_filepath(ZSTR_VAL(filename), resolved_path)) { + RETURN_FALSE; + } + + rsrc_int = (zip_rsrc *)emalloc(sizeof(zip_rsrc)); + + rsrc_int->za = zip_open(resolved_path, 0, &err); + if (rsrc_int->za == NULL) { + efree(rsrc_int); + RETURN_LONG((zend_long)err); + } + + rsrc_int->index_current = 0; + rsrc_int->num_files = zip_get_num_files(rsrc_int->za); + + RETURN_RES(zend_register_resource(rsrc_int, le_zip_dir)); +} +/* }}} */ + +/* {{{ proto void zip_close(resource zip) + Close a Zip archive */ +static PHP_NAMED_FUNCTION(zif_zip_close) +{ + zval * zip; + zip_rsrc *z_rsrc = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zip) == FAILURE) { + return; + } + + if ((z_rsrc = (zip_rsrc *)zend_fetch_resource(Z_RES_P(zip), le_zip_dir_name, le_zip_dir)) == NULL) { + RETURN_FALSE; + } + + /* really close the zip will break BC :-D */ + zend_list_close(Z_RES_P(zip)); +} +/* }}} */ + +/* {{{ proto resource zip_read(resource zip) + Returns the next file in the archive */ +static PHP_NAMED_FUNCTION(zif_zip_read) +{ + zval *zip_dp; + zip_read_rsrc *zr_rsrc; + int ret; + zip_rsrc *rsrc_int; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zip_dp) == FAILURE) { + return; + } + + if ((rsrc_int = (zip_rsrc *)zend_fetch_resource(Z_RES_P(zip_dp), le_zip_dir_name, le_zip_dir)) == NULL) { + RETURN_FALSE; + } + + if (rsrc_int && rsrc_int->za) { + if (rsrc_int->index_current >= rsrc_int->num_files) { + RETURN_FALSE; + } + + zr_rsrc = emalloc(sizeof(zip_read_rsrc)); + + ret = zip_stat_index(rsrc_int->za, rsrc_int->index_current, 0, &zr_rsrc->sb); + + if (ret != 0) { + efree(zr_rsrc); + RETURN_FALSE; + } + + zr_rsrc->zf = zip_fopen_index(rsrc_int->za, rsrc_int->index_current, 0); + if (zr_rsrc->zf) { + rsrc_int->index_current++; + RETURN_RES(zend_register_resource(zr_rsrc, le_zip_entry)); + } else { + efree(zr_rsrc); + RETURN_FALSE; + } + + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool zip_entry_open(resource zip_dp, resource zip_entry [, string mode]) + Open a Zip File, pointed by the resource entry */ +/* Dummy function to follow the old API */ +static PHP_NAMED_FUNCTION(zif_zip_entry_open) +{ + zval * zip; + zval * zip_entry; + char *mode = NULL; + size_t mode_len = 0; + zip_read_rsrc * zr_rsrc; + zip_rsrc *z_rsrc; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr|s", &zip, &zip_entry, &mode, &mode_len) == FAILURE) { + return; + } + + if ((zr_rsrc = (zip_read_rsrc *)zend_fetch_resource(Z_RES_P(zip_entry), le_zip_entry_name, le_zip_entry)) == NULL) { + RETURN_FALSE; + } + + if ((z_rsrc = (zip_rsrc *)zend_fetch_resource(Z_RES_P(zip), le_zip_dir_name, le_zip_dir)) == NULL) { + RETURN_FALSE; + } + + if (zr_rsrc->zf != NULL) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool zip_entry_close(resource zip_ent) + Close a zip entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_close) +{ + zval * zip_entry; + zip_read_rsrc * zr_rsrc; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zip_entry) == FAILURE) { + return; + } + + if ((zr_rsrc = (zip_read_rsrc *)zend_fetch_resource(Z_RES_P(zip_entry), le_zip_entry_name, le_zip_entry)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(SUCCESS == zend_list_close(Z_RES_P(zip_entry))); +} +/* }}} */ + +/* {{{ proto mixed zip_entry_read(resource zip_entry [, int len]) + Read from an open directory entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_read) +{ + zval * zip_entry; + zend_long len = 0; + zip_read_rsrc * zr_rsrc; + zend_string *buffer; + int n = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &zip_entry, &len) == FAILURE) { + return; + } + + if ((zr_rsrc = (zip_read_rsrc *)zend_fetch_resource(Z_RES_P(zip_entry), le_zip_entry_name, le_zip_entry)) == NULL) { + RETURN_FALSE; + } + + if (len <= 0) { + len = 1024; + } + + if (zr_rsrc->zf) { + buffer = zend_string_alloc(len, 0); + n = zip_fread(zr_rsrc->zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); + if (n > 0) { + ZSTR_VAL(buffer)[n] = '\0'; + ZSTR_LEN(buffer) = n; + RETURN_NEW_STR(buffer); + } else { + zend_string_free(buffer); + RETURN_EMPTY_STRING() + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +static void php_zip_entry_get_info(INTERNAL_FUNCTION_PARAMETERS, int opt) /* {{{ */ +{ + zval * zip_entry; + zip_read_rsrc * zr_rsrc; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zip_entry) == FAILURE) { + return; + } + + if ((zr_rsrc = (zip_read_rsrc *)zend_fetch_resource(Z_RES_P(zip_entry), le_zip_entry_name, le_zip_entry)) == NULL) { + RETURN_FALSE; + } + + if (!zr_rsrc->zf) { + RETURN_FALSE; + } + + switch (opt) { + case 0: + RETURN_STRING((char *)zr_rsrc->sb.name); + break; + case 1: + RETURN_LONG((zend_long) (zr_rsrc->sb.comp_size)); + break; + case 2: + RETURN_LONG((zend_long) (zr_rsrc->sb.size)); + break; + case 3: + switch (zr_rsrc->sb.comp_method) { + case 0: + RETURN_STRING("stored"); + break; + case 1: + RETURN_STRING("shrunk"); + break; + case 2: + case 3: + case 4: + case 5: + RETURN_STRING("reduced"); + break; + case 6: + RETURN_STRING("imploded"); + break; + case 7: + RETURN_STRING("tokenized"); + break; + case 8: + RETURN_STRING("deflated"); + break; + case 9: + RETURN_STRING("deflatedX"); + break; + case 10: + RETURN_STRING("implodedX"); + break; + default: + RETURN_FALSE; + } + RETURN_LONG((zend_long) (zr_rsrc->sb.comp_method)); + break; + } + +} +/* }}} */ + +/* {{{ proto string zip_entry_name(resource zip_entry) + Return the name given a ZZip entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_name) +{ + php_zip_entry_get_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto int zip_entry_compressedsize(resource zip_entry) + Return the compressed size of a ZZip entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_compressedsize) +{ + php_zip_entry_get_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto int zip_entry_filesize(resource zip_entry) + Return the actual filesize of a ZZip entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_filesize) +{ + php_zip_entry_get_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto string zip_entry_compressionmethod(resource zip_entry) + Return a string containing the compression method used on a particular entry */ +static PHP_NAMED_FUNCTION(zif_zip_entry_compressionmethod) +{ + php_zip_entry_get_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto mixed ZipArchive::open(string source [, int flags]) +Create new zip using source uri for output, return TRUE on success or the error code */ +static ZIPARCHIVE_METHOD(open) +{ + struct zip *intern; + int err = 0; + zend_long flags = 0; + char *resolved_path; + zend_string *filename; + zval *self = getThis(); + ze_zip_object *ze_obj = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|l", &filename, &flags) == FAILURE) { + return; + } + + if (self) { + /* We do not use ZIP_FROM_OBJECT, zip init function here */ + ze_obj = Z_ZIP_P(self); + } + + if (ZSTR_LEN(filename) == 0) { + php_error_docref(NULL, E_WARNING, "Empty string as source"); + RETURN_FALSE; + } + + if (ZIP_OPENBASEDIR_CHECKPATH(ZSTR_VAL(filename))) { + RETURN_FALSE; + } + + if (!(resolved_path = expand_filepath(ZSTR_VAL(filename), NULL))) { + RETURN_FALSE; + } + + if (ze_obj->za) { + /* we already have an opened zip, free it */ + if (zip_close(ze_obj->za) != 0) { + php_error_docref(NULL, E_WARNING, "Empty string as source"); + efree(resolved_path); + RETURN_FALSE; + } + ze_obj->za = NULL; + } + if (ze_obj->filename) { + efree(ze_obj->filename); + ze_obj->filename = NULL; + } + + intern = zip_open(resolved_path, flags, &err); + if (!intern || err) { + efree(resolved_path); + RETURN_LONG((zend_long)err); + } + ze_obj->filename = resolved_path; + ze_obj->filename_len = strlen(resolved_path); + ze_obj->za = intern; + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource ZipArchive::setPassword(string password) +Set the password for the active archive */ +static ZIPARCHIVE_METHOD(setPassword) +{ + struct zip *intern; + zval *self = getThis(); + char *password; + size_t password_len; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &password, &password_len) == FAILURE) { + return; + } + + if (password_len < 1) { + RETURN_FALSE; + } else { + int res = zip_set_default_password(intern, (const char *)password); + if (res == 0) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::close() +close the zip archive */ +static ZIPARCHIVE_METHOD(close) +{ + struct zip *intern; + zval *self = getThis(); + ze_zip_object *ze_obj; + int err; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + ze_obj = Z_ZIP_P(self); + + if ((err = zip_close(intern))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zip_strerror(intern)); + zip_discard(intern); + } + + efree(ze_obj->filename); + ze_obj->filename = NULL; + ze_obj->filename_len = 0; + ze_obj->za = NULL; + + if (!err) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string ZipArchive::getStatusString() + * Returns the status error message, system and/or zip messages */ +static ZIPARCHIVE_METHOD(getStatusString) +{ + struct zip *intern; + zval *self = getThis(); +#if LIBZIP_VERSION_MAJOR < 1 + int zep, syp, len; + char error_string[128]; +#else + zip_error_t *err; +#endif + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + +#if LIBZIP_VERSION_MAJOR < 1 + zip_error_get(intern, &zep, &syp); + + len = zip_error_to_str(error_string, 128, zep, syp); + RETVAL_STRINGL(error_string, len); +#else + err = zip_get_error(intern); + RETVAL_STRING(zip_error_strerror(err)); + zip_error_fini(err); +#endif +} +/* }}} */ + +/* {{{ proto bool ZipArchive::createEmptyDir(string dirname) +Returns the index of the entry named filename in the archive */ +static ZIPARCHIVE_METHOD(addEmptyDir) +{ + struct zip *intern; + zval *self = getThis(); + char *dirname; + size_t dirname_len; + int idx; + struct zip_stat sb; + char *s; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &dirname, &dirname_len) == FAILURE) { + return; + } + + if (dirname_len<1) { + RETURN_FALSE; + } + + if (dirname[dirname_len-1] != '/') { + s=(char *)emalloc(dirname_len+2); + strcpy(s, dirname); + s[dirname_len] = '/'; + s[dirname_len+1] = '\0'; + } else { + s = dirname; + } + + idx = zip_stat(intern, s, 0, &sb); + if (idx >= 0) { + RETVAL_FALSE; + } else { + if (zip_add_dir(intern, (const char *)s) == -1) { + RETVAL_FALSE; + } + zip_error_clear(intern); + RETVAL_TRUE; + } + + if (s != dirname) { + efree(s); + } +} +/* }}} */ + +static void php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ +{ + struct zip *intern; + zval *self = getThis(); + char *path = NULL; + char *remove_path = NULL; + char *add_path = NULL; + size_t add_path_len, remove_path_len = 0, path_len = 0; + zend_long remove_all_path = 0; + zend_long flags = 0; + zval *options = NULL; + int found; + zend_string *pattern; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + /* 1 == glob, 2 == pcre */ + if (type == 1) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|la", + &pattern, &flags, &options) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|sa", + &pattern, &path, &path_len, &options) == FAILURE) { + return; + } + } + + if (ZSTR_LEN(pattern) == 0) { + php_error_docref(NULL, E_NOTICE, "Empty string as pattern"); + RETURN_FALSE; + } + if (options && (php_zip_parse_options(options, &remove_all_path, &remove_path, &remove_path_len, + &add_path, &add_path_len) < 0)) { + RETURN_FALSE; + } + + if (remove_path && remove_path_len > 1) { + size_t real_len = strlen(remove_path); + if ((real_len > 1) && ((remove_path[real_len - 1] == '/') || (remove_path[real_len - 1] == '\\'))) { + remove_path[real_len - 1] = '\0'; + } + } + + if (type == 1) { + found = php_zip_glob(ZSTR_VAL(pattern), ZSTR_LEN(pattern), flags, return_value); + } else { + found = php_zip_pcre(pattern, path, path_len, return_value); + } + + if (found > 0) { + int i; + zval *zval_file; + + for (i = 0; i < found; i++) { + char *file_stripped, *entry_name; + size_t entry_name_len, file_stripped_len; + char entry_name_buf[MAXPATHLEN]; + zend_string *basename = NULL; + + if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(return_value), i)) != NULL) { + if (remove_all_path) { + basename = php_basename(Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), NULL, 0); + file_stripped = ZSTR_VAL(basename); + file_stripped_len = ZSTR_LEN(basename); + } else if (remove_path && strstr(Z_STRVAL_P(zval_file), remove_path) != NULL) { + file_stripped = Z_STRVAL_P(zval_file) + remove_path_len + 1; + file_stripped_len = Z_STRLEN_P(zval_file) - remove_path_len - 1; + } else { + file_stripped = Z_STRVAL_P(zval_file); + file_stripped_len = Z_STRLEN_P(zval_file); + } + + if (add_path) { + if ((add_path_len + file_stripped_len) > MAXPATHLEN) { + php_error_docref(NULL, E_WARNING, "Entry name too long (max: %d, %pd given)", + MAXPATHLEN - 1, (add_path_len + file_stripped_len)); + zval_ptr_dtor(return_value); + RETURN_FALSE; + } + + snprintf(entry_name_buf, MAXPATHLEN, "%s%s", add_path, file_stripped); + entry_name = entry_name_buf; + entry_name_len = strlen(entry_name); + } else { + entry_name = Z_STRVAL_P(zval_file); + entry_name_len = Z_STRLEN_P(zval_file); + } + if (basename) { + zend_string_release(basename); + basename = NULL; + } + if (php_zip_add_file(intern, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), + entry_name, entry_name_len, 0, 0) < 0) { + zval_dtor(return_value); + RETURN_FALSE; + } + } + } + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::addGlob(string pattern[,int flags [, array options]]) +Add files matching the glob pattern. See php's glob for the pattern syntax. */ +static ZIPARCHIVE_METHOD(addGlob) +{ + php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto bool ZipArchive::addPattern(string pattern[, string path [, array options]]) +Add files matching the pcre pattern. See php's pcre for the pattern syntax. */ +static ZIPARCHIVE_METHOD(addPattern) +{ + php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto bool ZipArchive::addFile(string filepath[, string entryname[, int start [, int length]]]) +Add a file in a Zip archive using its path and the name to use. */ +static ZIPARCHIVE_METHOD(addFile) +{ + struct zip *intern; + zval *self = getThis(); + char *entry_name = NULL; + size_t entry_name_len = 0; + zend_long offset_start = 0, offset_len = 0; + zend_string *filename; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|sll", + &filename, &entry_name, &entry_name_len, &offset_start, &offset_len) == FAILURE) { + return; + } + + if (ZSTR_LEN(filename) == 0) { + php_error_docref(NULL, E_NOTICE, "Empty string as filename"); + RETURN_FALSE; + } + + if (entry_name_len == 0) { + entry_name = ZSTR_VAL(filename); + entry_name_len = ZSTR_LEN(filename); + } + + if (php_zip_add_file(intern, ZSTR_VAL(filename), ZSTR_LEN(filename), entry_name, entry_name_len, 0, 0) < 0) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::addFromString(string name, string content) +Add a file using content and the entry name */ +static ZIPARCHIVE_METHOD(addFromString) +{ + struct zip *intern; + zval *self = getThis(); + zend_string *buffer; + char *name; + size_t name_len; + ze_zip_object *ze_obj; + struct zip_source *zs; + int pos = 0; + int cur_idx; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", + &name, &name_len, &buffer) == FAILURE) { + return; + } + + ze_obj = Z_ZIP_P(self); + if (ze_obj->buffers_cnt) { + ze_obj->buffers = (char **)erealloc(ze_obj->buffers, sizeof(char *) * (ze_obj->buffers_cnt+1)); + pos = ze_obj->buffers_cnt++; + } else { + ze_obj->buffers = (char **)emalloc(sizeof(char *)); + ze_obj->buffers_cnt++; + pos = 0; + } + ze_obj->buffers[pos] = (char *)emalloc(ZSTR_LEN(buffer) + 1); + memcpy(ze_obj->buffers[pos], ZSTR_VAL(buffer), ZSTR_LEN(buffer) + 1); + + zs = zip_source_buffer(intern, ze_obj->buffers[pos], ZSTR_LEN(buffer), 0); + + if (zs == NULL) { + RETURN_FALSE; + } + + cur_idx = zip_name_locate(intern, (const char *)name, 0); + /* TODO: fix _zip_replace */ + if (cur_idx >= 0) { + if (zip_delete(intern, cur_idx) == -1) { + zip_source_free(zs); + RETURN_FALSE; + } + } + + if (zip_add(intern, name, zs) == -1) { + zip_source_free(zs); + RETURN_FALSE; + } else { + zip_error_clear(intern); + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto array ZipArchive::statName(string filename[, int flags]) +Returns the information about a the zip entry filename */ +static ZIPARCHIVE_METHOD(statName) +{ + struct zip *intern; + zval *self = getThis(); + zend_long flags = 0; + struct zip_stat sb; + zend_string *name; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|l", &name, &flags) == FAILURE) { + return; + } + + PHP_ZIP_STAT_PATH(intern, ZSTR_VAL(name), ZSTR_LEN(name), flags, sb); + + RETURN_SB(&sb); +} +/* }}} */ + +/* {{{ proto resource ZipArchive::statIndex(int index[, int flags]) +Returns the zip entry informations using its index */ +static ZIPARCHIVE_METHOD(statIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index, flags = 0; + + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", + &index, &flags) == FAILURE) { + return; + } + + if (zip_stat_index(intern, index, flags, &sb) != 0) { + RETURN_FALSE; + } + RETURN_SB(&sb); +} +/* }}} */ + +/* {{{ proto int ZipArchive::locateName(string filename[, int flags]) +Returns the index of the entry named filename in the archive */ +static ZIPARCHIVE_METHOD(locateName) +{ + struct zip *intern; + zval *self = getThis(); + zend_long flags = 0; + zend_long idx = -1; + zend_string *name; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|l", &name, &flags) == FAILURE) { + return; + } + + if (ZSTR_LEN(name) < 1) { + RETURN_FALSE; + } + + idx = (zend_long)zip_name_locate(intern, (const char *)ZSTR_VAL(name), flags); + + if (idx >= 0) { + RETURN_LONG(idx); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string ZipArchive::getNameIndex(int index [, int flags]) +Returns the name of the file at position index */ +static ZIPARCHIVE_METHOD(getNameIndex) +{ + struct zip *intern; + zval *self = getThis(); + const char *name; + zend_long flags = 0, index = 0; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", + &index, &flags) == FAILURE) { + return; + } + + name = zip_get_name(intern, (int) index, flags); + + if (name) { + RETVAL_STRING((char *)name); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setArchiveComment(string comment) +Set or remove (NULL/'') the comment of the archive */ +static ZIPARCHIVE_METHOD(setArchiveComment) +{ + struct zip *intern; + zval *self = getThis(); + size_t comment_len; + char * comment; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &comment, &comment_len) == FAILURE) { + return; + } + if (zip_set_archive_comment(intern, (const char *)comment, (int)comment_len)) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto string ZipArchive::getArchiveComment([int flags]) +Returns the comment of an entry using its index */ +static ZIPARCHIVE_METHOD(getArchiveComment) +{ + struct zip *intern; + zval *self = getThis(); + zend_long flags = 0; + const char * comment; + int comment_len = 0; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags) == FAILURE) { + return; + } + + comment = zip_get_archive_comment(intern, &comment_len, (int)flags); + if(comment==NULL) { + RETURN_FALSE; + } + RETURN_STRINGL((char *)comment, (zend_long)comment_len); +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setCommentName(string name, string comment) +Set or remove (NULL/'') the comment of an entry using its Name */ +static ZIPARCHIVE_METHOD(setCommentName) +{ + struct zip *intern; + zval *self = getThis(); + size_t comment_len, name_len; + char * comment, *name; + int idx; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &name, &name_len, &comment, &comment_len) == FAILURE) { + return; + } + + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + PHP_ZIP_SET_FILE_COMMENT(intern, idx, comment, comment_len); +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setCommentIndex(int index, string comment) +Set or remove (NULL/'') the comment of an entry using its index */ +static ZIPARCHIVE_METHOD(setCommentIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index; + size_t comment_len; + char * comment; + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", + &index, &comment, &comment_len) == FAILURE) { + return; + } + + PHP_ZIP_STAT_INDEX(intern, index, 0, sb); + PHP_ZIP_SET_FILE_COMMENT(intern, index, comment, comment_len); +} +/* }}} */ + +/* those constants/functions are only available in libzip since 0.11.2 */ +#ifdef ZIP_OPSYS_DEFAULT + +/* {{{ proto bool ZipArchive::setExternalAttributesName(string name, int opsys, int attr [, int flags]) +Set external attributes for file in zip, using its name */ +static ZIPARCHIVE_METHOD(setExternalAttributesName) +{ + struct zip *intern; + zval *self = getThis(); + size_t name_len; + char *name; + zend_long flags=0, opsys, attr; + zip_int64_t idx; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|l", + &name, &name_len, &opsys, &attr, &flags) == FAILURE) { + return; + } + + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + if (zip_file_set_external_attributes(intern, idx, (zip_flags_t)flags, + (zip_uint8_t)(opsys&0xff), (zip_uint32_t)attr) < 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setExternalAttributesIndex(int index, int opsys, int attr [, int flags]) +Set external attributes for file in zip, using its index */ +static ZIPARCHIVE_METHOD(setExternalAttributesIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index, flags=0, opsys, attr; + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll|l", + &index, &opsys, &attr, &flags) == FAILURE) { + return; + } + + PHP_ZIP_STAT_INDEX(intern, index, 0, sb); + if (zip_file_set_external_attributes(intern, (zip_uint64_t)index, + (zip_flags_t)flags, (zip_uint8_t)(opsys&0xff), (zip_uint32_t)attr) < 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::getExternalAttributesName(string name, int &opsys, int &attr [, int flags]) +Get external attributes for file in zip, using its name */ +static ZIPARCHIVE_METHOD(getExternalAttributesName) +{ + struct zip *intern; + zval *self = getThis(), *z_opsys, *z_attr; + size_t name_len; + char *name; + zend_long flags=0; + zip_uint8_t opsys; + zip_uint32_t attr; + zip_int64_t idx; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z/|l", + &name, &name_len, &z_opsys, &z_attr, &flags) == FAILURE) { + return; + } + + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + if (zip_file_get_external_attributes(intern, idx, + (zip_flags_t)flags, &opsys, &attr) < 0) { + RETURN_FALSE; + } + zval_ptr_dtor(z_opsys); + ZVAL_LONG(z_opsys, opsys); + zval_ptr_dtor(z_attr); + ZVAL_LONG(z_attr, attr); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::getExternalAttributesIndex(int index, int &opsys, int &attr [, int flags]) +Get external attributes for file in zip, using its index */ +static ZIPARCHIVE_METHOD(getExternalAttributesIndex) +{ + struct zip *intern; + zval *self = getThis(), *z_opsys, *z_attr; + zend_long index, flags=0; + zip_uint8_t opsys; + zip_uint32_t attr; + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz/z/|l", + &index, &z_opsys, &z_attr, &flags) == FAILURE) { + return; + } + + PHP_ZIP_STAT_INDEX(intern, index, 0, sb); + if (zip_file_get_external_attributes(intern, (zip_uint64_t)index, + (zip_flags_t)flags, &opsys, &attr) < 0) { + RETURN_FALSE; + } + zval_dtor(z_opsys); + ZVAL_LONG(z_opsys, opsys); + zval_dtor(z_attr); + ZVAL_LONG(z_attr, attr); + RETURN_TRUE; +} +/* }}} */ +#endif /* ifdef ZIP_OPSYS_DEFAULT */ + +/* {{{ proto string ZipArchive::getCommentName(string name[, int flags]) +Returns the comment of an entry using its name */ +static ZIPARCHIVE_METHOD(getCommentName) +{ + struct zip *intern; + zval *self = getThis(); + size_t name_len; + int idx; + zend_long flags = 0; + int comment_len = 0; + const char * comment; + char *name; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", + &name, &name_len, &flags) == FAILURE) { + return; + } + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + RETURN_FALSE; + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + + comment = zip_get_file_comment(intern, idx, &comment_len, (int)flags); + RETURN_STRINGL((char *)comment, (zend_long)comment_len); +} +/* }}} */ + +/* {{{ proto string ZipArchive::getCommentIndex(int index[, int flags]) +Returns the comment of an entry using its index */ +static ZIPARCHIVE_METHOD(getCommentIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index, flags = 0; + const char * comment; + int comment_len = 0; + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", + &index, &flags) == FAILURE) { + return; + } + + PHP_ZIP_STAT_INDEX(intern, index, 0, sb); + comment = zip_get_file_comment(intern, index, &comment_len, (int)flags); + RETURN_STRINGL((char *)comment, (zend_long)comment_len); +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setCompressionName(string name, int comp_method[, int comp_flags]) +Set the compression of a file in zip, using its name */ +static ZIPARCHIVE_METHOD(setCompressionName) + { + struct zip *intern; + zval *this = getThis(); + size_t name_len; + char *name; + zip_int64_t idx; + zend_long comp_method, comp_flags = 0; + + if (!this) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, this); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|l", + &name, &name_len, &comp_method, &comp_flags) == FAILURE) { + return; + } + + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + + if (zip_set_file_compression(intern, (zip_uint64_t)idx, + (zip_int32_t)comp_method, (zip_uint32_t)comp_flags) != 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setCompressionIndex(int index, int comp_method[, int comp_flags]) +Set the compression of a file in zip, using its index */ +static ZIPARCHIVE_METHOD(setCompressionIndex) +{ + struct zip *intern; + zval *this = getThis(); + zend_long index; + zend_long comp_method, comp_flags = 0; + + if (!this) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, this); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l", + &index, &comp_method, &comp_flags) == FAILURE) { + return; + } + + if (zip_set_file_compression(intern, (zip_uint64_t)index, + (zip_int32_t)comp_method, (zip_uint32_t)comp_flags) != 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::deleteIndex(int index) +Delete a file using its index */ +static ZIPARCHIVE_METHOD(deleteIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + return; + } + + if (index < 0) { + RETURN_FALSE; + } + + if (zip_delete(intern, index) < 0) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::deleteName(string name) +Delete a file using its index */ +static ZIPARCHIVE_METHOD(deleteName) +{ + struct zip *intern; + zval *self = getThis(); + size_t name_len; + char *name; + struct zip_stat sb; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { + return; + } + if (name_len < 1) { + RETURN_FALSE; + } + + PHP_ZIP_STAT_PATH(intern, name, name_len, 0, sb); + if (zip_delete(intern, sb.index)) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::renameIndex(int index, string new_name) +Rename an entry selected by its index to new_name */ +static ZIPARCHIVE_METHOD(renameIndex) +{ + struct zip *intern; + zval *self = getThis(); + + char *new_name; + size_t new_name_len; + zend_long index; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", &index, &new_name, &new_name_len) == FAILURE) { + return; + } + + if (index < 0) { + RETURN_FALSE; + } + + if (new_name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as new entry name"); + RETURN_FALSE; + } + if (zip_rename(intern, index, (const char *)new_name) != 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::renameName(string name, string new_name) +Rename an entry selected by its name to new_name */ +static ZIPARCHIVE_METHOD(renameName) +{ + struct zip *intern; + zval *self = getThis(); + struct zip_stat sb; + char *name, *new_name; + size_t name_len, new_name_len; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &name, &name_len, &new_name, &new_name_len) == FAILURE) { + return; + } + + if (new_name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as new entry name"); + RETURN_FALSE; + } + + PHP_ZIP_STAT_PATH(intern, name, name_len, 0, sb); + + if (zip_rename(intern, sb.index, (const char *)new_name)) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::unchangeIndex(int index) +Changes to the file at position index are reverted */ +static ZIPARCHIVE_METHOD(unchangeIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + return; + } + + if (index < 0) { + RETURN_FALSE; + } + + if (zip_unchange(intern, index) != 0) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::unchangeName(string name) +Changes to the file named 'name' are reverted */ +static ZIPARCHIVE_METHOD(unchangeName) +{ + struct zip *intern; + zval *self = getThis(); + struct zip_stat sb; + char *name; + size_t name_len; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { + return; + } + + if (name_len < 1) { + RETURN_FALSE; + } + + PHP_ZIP_STAT_PATH(intern, name, name_len, 0, sb); + + if (zip_unchange(intern, sb.index) != 0) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::unchangeAll() +All changes to files and global information in archive are reverted */ +static ZIPARCHIVE_METHOD(unchangeAll) +{ + struct zip *intern; + zval *self = getThis(); + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zip_unchange_all(intern) != 0) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::unchangeArchive() +Revert all global changes to the archive archive. For now, this only reverts archive comment changes. */ +static ZIPARCHIVE_METHOD(unchangeArchive) +{ + struct zip *intern; + zval *self = getThis(); + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zip_unchange_archive(intern) != 0) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto bool ZipArchive::extractTo(string pathto[, mixed files]) +Extract one or more file from a zip archive */ +/* TODO: + * - allow index or array of indeces + * - replace path + * - patterns + */ +static ZIPARCHIVE_METHOD(extractTo) +{ + struct zip *intern; + + zval *self = getThis(); + zval *zval_files = NULL; + zval *zval_file = NULL; + php_stream_statbuf ssb; + char *pathto; + size_t pathto_len; + int ret, i; + + int nelems; + + if (!self) { + RETURN_FALSE; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z", &pathto, &pathto_len, &zval_files) == FAILURE) { + return; + } + + if (pathto_len < 1) { + RETURN_FALSE; + } + + if (php_stream_stat_path_ex(pathto, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL) < 0) { + ret = php_stream_mkdir(pathto, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL); + if (!ret) { + RETURN_FALSE; + } + } + + ZIP_FROM_OBJECT(intern, self); + if (zval_files && (Z_TYPE_P(zval_files) != IS_NULL)) { + switch (Z_TYPE_P(zval_files)) { + case IS_STRING: + if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_files), Z_STRLEN_P(zval_files))) { + RETURN_FALSE; + } + 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++) { + if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(zval_files), i)) != NULL) { + switch (Z_TYPE_P(zval_file)) { + case IS_LONG: + break; + case IS_STRING: + if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file))) { + RETURN_FALSE; + } + break; + } + } + } + break; + case IS_LONG: + default: + php_error_docref(NULL, E_WARNING, "Invalid argument, expect string or array of strings"); + break; + } + } else { + /* Extract all files */ + int filecount = zip_get_num_files(intern); + + if (filecount == -1) { + php_error_docref(NULL, E_WARNING, "Illegal archive"); + RETURN_FALSE; + } + + for (i = 0; i < filecount; i++) { + char *file = (char*)zip_get_name(intern, i, ZIP_FL_UNCHANGED); + if (!php_zip_extract_file(intern, pathto, file, strlen(file))) { + RETURN_FALSE; + } + } + } + RETURN_TRUE; +} +/* }}} */ + +static void php_zip_get_from(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ +{ + struct zip *intern; + zval *self = getThis(); + + struct zip_stat sb; + struct zip_file *zf; + + zend_long index = -1; + zend_long flags = 0; + zend_long len = 0; + + zend_string *filename; + zend_string *buffer; + + int n = 0; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (type == 1) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|ll", &filename, &len, &flags) == FAILURE) { + return; + } + PHP_ZIP_STAT_PATH(intern, ZSTR_VAL(filename), ZSTR_LEN(filename), flags, sb); + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|ll", &index, &len, &flags) == FAILURE) { + return; + } + PHP_ZIP_STAT_INDEX(intern, index, 0, sb); + } + + if (sb.size < 1) { + RETURN_EMPTY_STRING(); + } + + if (len < 1) { + len = sb.size; + } + if (index >= 0) { + zf = zip_fopen_index(intern, index, flags); + } else { + zf = zip_fopen(intern, ZSTR_VAL(filename), flags); + } + + if (zf == NULL) { + RETURN_FALSE; + } + + buffer = zend_string_alloc(len, 0); + n = zip_fread(zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); + if (n < 1) { + zend_string_free(buffer); + RETURN_EMPTY_STRING(); + } + + zip_fclose(zf); + ZSTR_VAL(buffer)[n] = '\0'; + ZSTR_LEN(buffer) = n; + RETURN_NEW_STR(buffer); +} +/* }}} */ + +/* {{{ proto string ZipArchive::getFromName(string entryname[, int len [, int flags]]) +get the contents of an entry using its name */ +static ZIPARCHIVE_METHOD(getFromName) +{ + php_zip_get_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string ZipArchive::getFromIndex(int index[, int len [, int flags]]) +get the contents of an entry using its index */ +static ZIPARCHIVE_METHOD(getFromIndex) +{ + php_zip_get_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto resource ZipArchive::getStream(string entryname) +get a stream for an entry using its name */ +static ZIPARCHIVE_METHOD(getStream) +{ + struct zip *intern; + zval *self = getThis(); + struct zip_stat sb; + char *mode = "rb"; + zend_string *filename; + php_stream *stream; + ze_zip_object *obj; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &filename) == FAILURE) { + return; + } + + if (zip_stat(intern, ZSTR_VAL(filename), 0, &sb) != 0) { + RETURN_FALSE; + } + + obj = Z_ZIP_P(self); + + stream = php_stream_zip_open(obj->filename, ZSTR_VAL(filename), mode STREAMS_CC); + if (stream) { + php_stream_to_zval(stream, return_value); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_open, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setpassword, 0, 0, 1) + ZEND_ARG_INFO(0, password) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_ziparchive__void, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_addemptydir, 0, 0, 1) + ZEND_ARG_INFO(0, dirname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_addglob, 0, 0, 1) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_addpattern, 0, 0, 1) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, path) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_addfile, 0, 0, 1) + ZEND_ARG_INFO(0, filepath) + ZEND_ARG_INFO(0, entryname) + ZEND_ARG_INFO(0, start) + ZEND_ARG_INFO(0, length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_addfromstring, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, content) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_statname, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_statindex, 0, 0, 1) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setarchivecomment, 0, 0, 1) + ZEND_ARG_INFO(0, comment) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcommentindex, 0, 0, 2) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, comment) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getcommentname, 0, 0, 1) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getcommentindex, 0, 0, 1) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_renameindex, 0, 0, 2) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, new_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_renamename, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, new_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_unchangeindex, 0, 0, 1) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_unchangename, 0, 0, 1) + ZEND_ARG_INFO(0, name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_extractto, 0, 0, 1) + ZEND_ARG_INFO(0, pathto) + ZEND_ARG_INFO(0, files) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getfromname, 0, 0, 1) + ZEND_ARG_INFO(0, entryname) + ZEND_ARG_INFO(0, len) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getfromindex, 0, 0, 1) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, len) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getarchivecomment, 0, 0, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcommentname, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, comment) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getstream, 0, 0, 1) + ZEND_ARG_INFO(0, entryname) +ZEND_END_ARG_INFO() + +#ifdef ZIP_OPSYS_DEFAULT +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setextattrname, 0, 0, 3) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, opsys) + ZEND_ARG_INFO(0, attr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setextattrindex, 0, 0, 3) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, opsys) + ZEND_ARG_INFO(0, attr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getextattrname, 0, 0, 3) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(1, opsys) + ZEND_ARG_INFO(1, attr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_getextattrindex, 0, 0, 3) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(1, opsys) + ZEND_ARG_INFO(1, attr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() +#endif /* ifdef ZIP_OPSYS_DEFAULT */ +/* }}} */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcompname, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, compflags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcompindex, 0, 0, 2) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, compflags) +ZEND_END_ARG_INFO() + +/* {{{ ze_zip_object_class_functions */ +static const zend_function_entry zip_class_functions[] = { + ZIPARCHIVE_ME(open, arginfo_ziparchive_open, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setPassword, arginfo_ziparchive_setpassword, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(close, arginfo_ziparchive__void, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getStatusString, arginfo_ziparchive__void, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(addEmptyDir, arginfo_ziparchive_addemptydir, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(addFromString, arginfo_ziparchive_addfromstring, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(addFile, arginfo_ziparchive_addfile, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(addGlob, arginfo_ziparchive_addglob, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(addPattern, arginfo_ziparchive_addpattern, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(renameIndex, arginfo_ziparchive_renameindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(renameName, arginfo_ziparchive_renamename, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setArchiveComment, arginfo_ziparchive_setarchivecomment, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getArchiveComment, arginfo_ziparchive_getarchivecomment, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setCommentIndex, arginfo_ziparchive_setcommentindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setCommentName, arginfo_ziparchive_setcommentname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getCommentIndex, arginfo_ziparchive_getcommentindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getCommentName, arginfo_ziparchive_getcommentname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(deleteIndex, arginfo_ziparchive_unchangeindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(deleteName, arginfo_ziparchive_unchangename, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(statName, arginfo_ziparchive_statname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(statIndex, arginfo_ziparchive_statindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(locateName, arginfo_ziparchive_statname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getNameIndex, arginfo_ziparchive_statindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(unchangeArchive, arginfo_ziparchive__void, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(unchangeAll, arginfo_ziparchive__void, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(unchangeIndex, arginfo_ziparchive_unchangeindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(unchangeName, arginfo_ziparchive_unchangename, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(extractTo, arginfo_ziparchive_extractto, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getFromName, arginfo_ziparchive_getfromname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getFromIndex, arginfo_ziparchive_getfromindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getStream, arginfo_ziparchive_getstream, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setExternalAttributesName, arginfo_ziparchive_setextattrname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setExternalAttributesIndex, arginfo_ziparchive_setextattrindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getExternalAttributesName, arginfo_ziparchive_getextattrname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(getExternalAttributesIndex, arginfo_ziparchive_getextattrindex, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setCompressionName, arginfo_ziparchive_setcompname, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setCompressionIndex, arginfo_ziparchive_setcompindex, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; +/* }}} */ + +static void php_zip_free_prop_handler(zval *el) /* {{{ */ { + pefree(Z_PTR_P(el), 1); +} /* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +static PHP_MINIT_FUNCTION(zip) +{ + zend_class_entry ce; + + memcpy(&zip_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + zip_object_handlers.offset = XtOffsetOf(ze_zip_object, zo); + zip_object_handlers.free_obj = php_zip_object_free_storage; + zip_object_handlers.clone_obj = NULL; + zip_object_handlers.get_property_ptr_ptr = php_zip_get_property_ptr_ptr; + + zip_object_handlers.get_properties = php_zip_get_properties; + zip_object_handlers.read_property = php_zip_read_property; + zip_object_handlers.has_property = php_zip_has_property; + + INIT_CLASS_ENTRY(ce, "ZipArchive", zip_class_functions); + ce.create_object = php_zip_object_new; + zip_class_entry = zend_register_internal_class(&ce); + + zend_hash_init(&zip_prop_handlers, 0, NULL, php_zip_free_prop_handler, 1); + php_zip_register_prop_handler(&zip_prop_handlers, "status", php_zip_status, NULL, NULL, IS_LONG); + php_zip_register_prop_handler(&zip_prop_handlers, "statusSys", php_zip_status_sys, NULL, NULL, IS_LONG); + php_zip_register_prop_handler(&zip_prop_handlers, "numFiles", php_zip_get_num_files, NULL, NULL, IS_LONG); + php_zip_register_prop_handler(&zip_prop_handlers, "filename", NULL, NULL, php_zipobj_get_filename, IS_STRING); + php_zip_register_prop_handler(&zip_prop_handlers, "comment", NULL, php_zipobj_get_zip_comment, NULL, IS_STRING); + + REGISTER_ZIP_CLASS_CONST_LONG("CREATE", ZIP_CREATE); + REGISTER_ZIP_CLASS_CONST_LONG("EXCL", ZIP_EXCL); + REGISTER_ZIP_CLASS_CONST_LONG("CHECKCONS", ZIP_CHECKCONS); + REGISTER_ZIP_CLASS_CONST_LONG("OVERWRITE", ZIP_OVERWRITE); + + REGISTER_ZIP_CLASS_CONST_LONG("FL_NOCASE", ZIP_FL_NOCASE); + REGISTER_ZIP_CLASS_CONST_LONG("FL_NODIR", ZIP_FL_NODIR); + REGISTER_ZIP_CLASS_CONST_LONG("FL_COMPRESSED", ZIP_FL_COMPRESSED); + REGISTER_ZIP_CLASS_CONST_LONG("FL_UNCHANGED", ZIP_FL_UNCHANGED); + REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFAULT", ZIP_CM_DEFAULT); + REGISTER_ZIP_CLASS_CONST_LONG("CM_STORE", ZIP_CM_STORE); + REGISTER_ZIP_CLASS_CONST_LONG("CM_SHRINK", ZIP_CM_SHRINK); + REGISTER_ZIP_CLASS_CONST_LONG("CM_REDUCE_1", ZIP_CM_REDUCE_1); + REGISTER_ZIP_CLASS_CONST_LONG("CM_REDUCE_2", ZIP_CM_REDUCE_2); + REGISTER_ZIP_CLASS_CONST_LONG("CM_REDUCE_3", ZIP_CM_REDUCE_3); + REGISTER_ZIP_CLASS_CONST_LONG("CM_REDUCE_4", ZIP_CM_REDUCE_4); + REGISTER_ZIP_CLASS_CONST_LONG("CM_IMPLODE", ZIP_CM_IMPLODE); + REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFLATE", ZIP_CM_DEFLATE); + REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFLATE64", ZIP_CM_DEFLATE64); + REGISTER_ZIP_CLASS_CONST_LONG("CM_PKWARE_IMPLODE", ZIP_CM_PKWARE_IMPLODE); + REGISTER_ZIP_CLASS_CONST_LONG("CM_BZIP2", ZIP_CM_BZIP2); + REGISTER_ZIP_CLASS_CONST_LONG("CM_LZMA", ZIP_CM_LZMA); + REGISTER_ZIP_CLASS_CONST_LONG("CM_TERSE", ZIP_CM_TERSE); + REGISTER_ZIP_CLASS_CONST_LONG("CM_LZ77", ZIP_CM_LZ77); + REGISTER_ZIP_CLASS_CONST_LONG("CM_WAVPACK", ZIP_CM_WAVPACK); + REGISTER_ZIP_CLASS_CONST_LONG("CM_PPMD", ZIP_CM_PPMD); + + /* Error code */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_OK", ZIP_ER_OK); /* N No error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_MULTIDISK", ZIP_ER_MULTIDISK); /* N Multi-disk zip archives not supported */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_RENAME", ZIP_ER_RENAME); /* S Renaming temporary file failed */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_CLOSE", ZIP_ER_CLOSE); /* S Closing zip archive failed */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_SEEK", ZIP_ER_SEEK); /* S Seek error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_READ", ZIP_ER_READ); /* S Read error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_WRITE", ZIP_ER_WRITE); /* S Write error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_CRC", ZIP_ER_CRC); /* N CRC error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_ZIPCLOSED", ZIP_ER_ZIPCLOSED); /* N Containing zip archive was closed */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_NOENT", ZIP_ER_NOENT); /* N No such file */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_EXISTS", ZIP_ER_EXISTS); /* N File already exists */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_OPEN", ZIP_ER_OPEN); /* S Can't open file */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_TMPOPEN", ZIP_ER_TMPOPEN); /* S Failure to create temporary file */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_ZLIB", ZIP_ER_ZLIB); /* Z Zlib error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_MEMORY", ZIP_ER_MEMORY); /* N Malloc failure */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_CHANGED", ZIP_ER_CHANGED); /* N Entry has been changed */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_COMPNOTSUPP", ZIP_ER_COMPNOTSUPP);/* N Compression method not supported */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_EOF", ZIP_ER_EOF); /* N Premature EOF */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_INVAL", ZIP_ER_INVAL); /* N Invalid argument */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_NOZIP", ZIP_ER_NOZIP); /* N Not a zip archive */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_INTERNAL", ZIP_ER_INTERNAL); /* N Internal error */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_INCONS", ZIP_ER_INCONS); /* N Zip archive inconsistent */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_REMOVE", ZIP_ER_REMOVE); /* S Can't remove file */ + REGISTER_ZIP_CLASS_CONST_LONG("ER_DELETED", ZIP_ER_DELETED); /* N Entry has been deleted */ + +#ifdef ZIP_OPSYS_DEFAULT + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_DOS", ZIP_OPSYS_DOS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_AMIGA", ZIP_OPSYS_AMIGA); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_OPENVMS", ZIP_OPSYS_OPENVMS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_UNIX", ZIP_OPSYS_UNIX); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_VM_CMS", ZIP_OPSYS_VM_CMS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_ATARI_ST", ZIP_OPSYS_ATARI_ST); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_OS_2", ZIP_OPSYS_OS_2); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_MACINTOSH", ZIP_OPSYS_MACINTOSH); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_Z_SYSTEM", ZIP_OPSYS_Z_SYSTEM); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_Z_CPM", ZIP_OPSYS_CPM); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_WINDOWS_NTFS", ZIP_OPSYS_WINDOWS_NTFS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_MVS", ZIP_OPSYS_MVS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_VSE", ZIP_OPSYS_VSE); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_ACORN_RISC", ZIP_OPSYS_ACORN_RISC); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_VFAT", ZIP_OPSYS_VFAT); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_ALTERNATE_MVS", ZIP_OPSYS_ALTERNATE_MVS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_BEOS", ZIP_OPSYS_BEOS); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_TANDEM", ZIP_OPSYS_TANDEM); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_OS_400", ZIP_OPSYS_OS_400); + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_OS_X", ZIP_OPSYS_OS_X); + + REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_DEFAULT", ZIP_OPSYS_DEFAULT); +#endif /* ifdef ZIP_OPSYS_DEFAULT */ + + php_register_url_stream_wrapper("zip", &php_stream_zip_wrapper); + + le_zip_dir = zend_register_list_destructors_ex(php_zip_free_dir, NULL, le_zip_dir_name, module_number); + le_zip_entry = zend_register_list_destructors_ex(php_zip_free_entry, NULL, le_zip_entry_name, module_number); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(zip) +{ + zend_hash_destroy(&zip_prop_handlers); + php_unregister_url_stream_wrapper("zip"); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +static PHP_MINFO_FUNCTION(zip) +{ + php_info_print_table_start(); + + php_info_print_table_row(2, "Zip", "enabled"); + php_info_print_table_row(2, "Extension Version","$Id: c31ace1f8dbd6f39bc76c84611423c885aa8e9af $"); + php_info_print_table_row(2, "Zip version", PHP_ZIP_VERSION); + php_info_print_table_row(2, "Libzip version", LIBZIP_VERSION); + + php_info_print_table_end(); +} +/* }}} */ + +/* + * 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 + */ From 08b01df2ae9625004bed50cba08ba0deee57ca2c Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:18 +0000 Subject: [PATCH 07/46] commit patch 26403199 --- ext/intl/locale/locale_methods.c | 18 + ext/intl/locale/locale_methods.c.orig | 1618 +++++++++++++++++++++++++ ext/intl/tests/bug72533.phpt | 30 + 3 files changed, 1666 insertions(+) create mode 100644 ext/intl/locale/locale_methods.c.orig create mode 100644 ext/intl/tests/bug72533.phpt diff --git a/ext/intl/locale/locale_methods.c b/ext/intl/locale/locale_methods.c index c47f283201556..c48322eea0e64 100644 --- a/ext/intl/locale/locale_methods.c +++ b/ext/intl/locale/locale_methods.c @@ -1593,6 +1593,24 @@ PHP_FUNCTION(locale_accept_from_http) "locale_accept_from_http: unable to parse input parameters", 0 ); RETURN_FALSE; } + if(http_accept_len > ULOC_FULLNAME_CAPACITY) { + /* check each fragment, if any bigger than capacity, can't do it due to bug #72533 */ + char *start = http_accept; + char *end; + size_t len; + do { + end = strchr(start, ','); + len = end ? end-start : http_accept_len-(start-http_accept); + if(len > ULOC_FULLNAME_CAPACITY) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_accept_from_http: locale string too long", 0 TSRMLS_CC ); + RETURN_FALSE; + } + if(end) { + start = end+1; + } + } while(end != NULL); + } available = ures_openAvailableLocales(NULL, &status); INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list"); diff --git a/ext/intl/locale/locale_methods.c.orig b/ext/intl/locale/locale_methods.c.orig new file mode 100644 index 0000000000000..c47f283201556 --- /dev/null +++ b/ext/intl/locale/locale_methods.c.orig @@ -0,0 +1,1618 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | 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: Kirti Velankar | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "php_intl.h" +#include "locale.h" +#include "locale_class.h" +#include "locale_methods.h" +#include "intl_convert.h" +#include "intl_data.h" + +#include +#include +#include +#include "main/php_ini.h" +#include "zend_smart_str.h" + +ZEND_EXTERN_MODULE_GLOBALS( intl ) + +/* Sizes required for the strings "variant15" , "extlang11", "private12" etc. */ +#define SEPARATOR "_" +#define SEPARATOR1 "-" +#define DELIMITER "-_" +#define EXTLANG_PREFIX "a" +#define PRIVATE_PREFIX "x" +#define DISP_NAME "name" + +#define MAX_NO_VARIANT 15 +#define MAX_NO_EXTLANG 3 +#define MAX_NO_PRIVATE 15 +#define MAX_NO_LOOKUP_LANG_TAG 100 + +#define LOC_NOT_FOUND 1 + +/* Sizes required for the strings "variant15" , "extlang3", "private12" etc. */ +#define VARIANT_KEYNAME_LEN 11 +#define EXTLANG_KEYNAME_LEN 10 +#define PRIVATE_KEYNAME_LEN 11 + +/* Based on IANA registry at the time of writing this code +* +*/ +static const char * const LOC_GRANDFATHERED[] = { + "art-lojban", "i-klingon", "i-lux", "i-navajo", "no-bok", "no-nyn", + "cel-gaulish", "en-GB-oed", "i-ami", + "i-bnn", "i-default", "i-enochian", + "i-mingo", "i-pwn", "i-tao", + "i-tay", "i-tsu", "sgn-BE-fr", + "sgn-BE-nl", "sgn-CH-de", "zh-cmn", + "zh-cmn-Hans", "zh-cmn-Hant", "zh-gan" , + "zh-guoyu", "zh-hakka", "zh-min", + "zh-min-nan", "zh-wuu", "zh-xiang", + "zh-yue", NULL +}; + +/* Based on IANA registry at the time of writing this code +* This array lists the preferred values for the grandfathered tags if applicable +* This is in sync with the array LOC_GRANDFATHERED +* e.g. the offsets of the grandfathered tags match the offset of the preferred value +*/ +static const int LOC_PREFERRED_GRANDFATHERED_LEN = 6; +static const char * const LOC_PREFERRED_GRANDFATHERED[] = { + "jbo", "tlh", "lb", + "nv", "nb", "nn", + NULL +}; + +/*returns TRUE if a is an ID separator FALSE otherwise*/ +#define isIDSeparator(a) (a == '_' || a == '-') +#define isKeywordSeparator(a) (a == '@' ) +#define isEndOfTag(a) (a == '\0' ) + +#define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I')) + +/*returns TRUE if one of the special prefixes is here (s=string) + 'x-' or 'i-' */ +#define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1])) +#define isKeywordPrefix(s) ( isKeywordSeparator(s[0]) ) + +/* Dot terminates it because of POSIX form where dot precedes the codepage + * except for variant */ +#define isTerminator(a) ((a==0)||(a=='.')||(a=='@')) + +/* {{{ return the offset of 'key' in the array 'list'. + * returns -1 if not present */ +static int16_t findOffset(const char* const* list, const char* key) +{ + const char* const* anchor = list; + while (*list != NULL) { + if (strcmp(key, *list) == 0) { + return (int16_t)(list - anchor); + } + list++; + } + + return -1; + +} +/*}}}*/ + +static char* getPreferredTag(const char* gf_tag) +{ + char* result = NULL; + int grOffset = 0; + + grOffset = findOffset( LOC_GRANDFATHERED ,gf_tag); + if(grOffset < 0) { + return NULL; + } + if( grOffset < LOC_PREFERRED_GRANDFATHERED_LEN ){ + /* return preferred tag */ + result = estrdup( LOC_PREFERRED_GRANDFATHERED[grOffset] ); + } else { + /* Return correct grandfathered language tag */ + result = estrdup( LOC_GRANDFATHERED[grOffset] ); + } + return result; +} + +/* {{{ +* returns the position of next token for lookup +* or -1 if no token +* strtokr equivalent search for token in reverse direction +*/ +static int getStrrtokenPos(char* str, int savedPos) +{ + int result =-1; + int i; + + for(i=savedPos-1; i>=0; i--) { + if(isIDSeparator(*(str+i)) ){ + /* delimiter found; check for singleton */ + if(i>=2 && isIDSeparator(*(str+i-2)) ){ + /* a singleton; so send the position of token before the singleton */ + result = i-2; + } else { + result = i; + } + break; + } + } + if(result < 1){ + /* Just in case inavlid locale e.g. '-x-xyz' or '-sl_Latn' */ + result =-1; + } + return result; +} +/* }}} */ + +/* {{{ +* returns the position of a singleton if present +* returns -1 if no singleton +* strtok equivalent search for singleton +*/ +static int getSingletonPos(const char* str) +{ + int result =-1; + int i=0; + int len = 0; + + if( str && ((len=strlen(str))>0) ){ + for( i=0; i= 0 ){ + if( strcmp(tag_name , LOC_LANG_TAG)==0 ){ + return zend_string_init(loc_name, strlen(loc_name), 0); + } else { + /* Since Grandfathered , no value , do nothing , retutn NULL */ + return NULL; + } + } + + if( fromParseLocale==1 ){ + /* Handle singletons */ + if( strcmp(tag_name , LOC_LANG_TAG)==0 ){ + if( strlen(loc_name)>1 && (isIDPrefix(loc_name) == 1) ){ + return zend_string_init(loc_name, strlen(loc_name), 0); + } + } + + singletonPos = getSingletonPos( loc_name ); + if( singletonPos == 0){ + /* singleton at start of script, region , variant etc. + * or invalid singleton at start of language */ + return NULL; + } else if( singletonPos > 0 ){ + /* singleton at some position except at start + * strip off the singleton and rest of the loc_name */ + mod_loc_name = estrndup ( loc_name , singletonPos-1); + } + } /* end of if fromParse */ + + } /* end of if != LOC_CANONICAL_TAG */ + + if( mod_loc_name == NULL){ + mod_loc_name = estrdup(loc_name ); + } + + /* Proceed to ICU */ + do{ + if (tag_value) { + tag_value = zend_string_realloc( tag_value , buflen, 0); + } else { + tag_value = zend_string_alloc( buflen, 0); + } + tag_value_len = buflen; + + if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){ + buflen = uloc_getScript ( mod_loc_name , tag_value->val , tag_value_len , &status); + } + if( strcmp(tag_name , LOC_LANG_TAG )==0 ){ + buflen = uloc_getLanguage ( mod_loc_name , tag_value->val , tag_value_len , &status); + } + if( strcmp(tag_name , LOC_REGION_TAG)==0 ){ + buflen = uloc_getCountry ( mod_loc_name , tag_value->val , tag_value_len , &status); + } + if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){ + buflen = uloc_getVariant ( mod_loc_name , tag_value->val , tag_value_len , &status); + } + if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){ + buflen = uloc_canonicalize ( mod_loc_name , tag_value->val , tag_value_len , &status); + } + + if( U_FAILURE( status ) ) { + if( status == U_BUFFER_OVERFLOW_ERROR ) { + status = U_ZERO_ERROR; + continue; + } + + /* Error in retriving data */ + *result = 0; + if( tag_value ){ + zend_string_release( tag_value ); + } + if( mod_loc_name ){ + efree( mod_loc_name); + } + return NULL; + } + } while( buflen > tag_value_len ); + + if( buflen ==0 ){ + /* No value found */ + *result = -1; + if( tag_value ){ + zend_string_release( tag_value ); + } + if( mod_loc_name ){ + efree( mod_loc_name); + } + return NULL; + } else { + *result = 1; + } + + if( mod_loc_name ){ + efree( mod_loc_name); + } + + tag_value->len = strlen(tag_value->val); + return tag_value; +} +/* }}} */ + +/* {{{ +* Gets the value from ICU , called when PHP userspace function is called +* common code shared by get_primary_language,get_script or get_region or get_variant +*/ +static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) +{ + + const char* loc_name = NULL; + size_t loc_name_len = 0; + + zend_string* tag_value = NULL; + char* empty_result = ""; + + int result = 0; + char* msg = NULL; + + UErrorCode status = U_ZERO_ERROR; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", + &loc_name ,&loc_name_len ) == FAILURE) { + spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name ); + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 ); + efree(msg); + + RETURN_FALSE; + } + + if(loc_name_len == 0) { + loc_name = intl_locale_get_default(); + } + + /* Call ICU get */ + tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0); + + /* No value found */ + if( result == -1 ) { + if( tag_value){ + zend_string_release( tag_value); + } + RETURN_STRING( empty_result); + } + + /* value found */ + if( tag_value){ + RETVAL_STR( tag_value ); + return; + } + + /* Error encountered while fetching the value */ + if( result ==0) { + spprintf(&msg , 0, "locale_get_%s : unable to get locale %s", tag_name , tag_name ); + intl_error_set( NULL, status, msg , 1 ); + efree(msg); + RETURN_NULL(); + } + +} +/* }}} */ + +/* {{{ proto static string Locale::getScript($locale) + * gets the script for the $locale + }}} */ +/* {{{ proto static string locale_get_script($locale) + * gets the script for the $locale + */ +PHP_FUNCTION( locale_get_script ) +{ + get_icu_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ proto static string Locale::getRegion($locale) + * gets the region for the $locale + }}} */ +/* {{{ proto static string locale_get_region($locale) + * gets the region for the $locale + */ +PHP_FUNCTION( locale_get_region ) +{ + get_icu_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ proto static string Locale::getPrimaryLanguage($locale) + * gets the primary language for the $locale + }}} */ +/* {{{ proto static string locale_get_primary_language($locale) + * gets the primary language for the $locale + */ +PHP_FUNCTION(locale_get_primary_language ) +{ + get_icu_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + + +/* {{{ + * common code shared by display_xyz functions to get the value from ICU + }}} */ +static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) +{ + const char* loc_name = NULL; + size_t loc_name_len = 0; + + const char* disp_loc_name = NULL; + size_t disp_loc_name_len = 0; + int free_loc_name = 0; + + UChar* disp_name = NULL; + int32_t disp_name_len = 0; + + char* mod_loc_name = NULL; + + int32_t buflen = 512; + UErrorCode status = U_ZERO_ERROR; + + zend_string* u8str; + + char* msg = NULL; + int grOffset = 0; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s|s", + &loc_name, &loc_name_len , + &disp_loc_name ,&disp_loc_name_len ) == FAILURE) + { + spprintf(&msg , 0, "locale_get_display_%s : unable to parse input params", tag_name ); + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 ); + efree(msg); + RETURN_FALSE; + } + + if(loc_name_len > ULOC_FULLNAME_CAPACITY) { + /* See bug 67397: overlong locale names cause trouble in uloc_getDisplayName */ + spprintf(&msg , 0, "locale_get_display_%s : name too long", tag_name ); + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 ); + efree(msg); + RETURN_FALSE; + } + + if(loc_name_len == 0) { + loc_name = intl_locale_get_default(); + } + + if( strcmp(tag_name, DISP_NAME) != 0 ){ + /* Handle grandfathered languages */ + grOffset = findOffset( LOC_GRANDFATHERED , loc_name ); + if( grOffset >= 0 ){ + if( strcmp(tag_name , LOC_LANG_TAG)==0 ){ + mod_loc_name = getPreferredTag( loc_name ); + } else { + /* Since Grandfathered, no value, do nothing, retutn NULL */ + RETURN_FALSE; + } + } + } /* end of if != LOC_CANONICAL_TAG */ + + if( mod_loc_name==NULL ){ + mod_loc_name = estrdup( loc_name ); + } + + /* Check if disp_loc_name passed , if not use default locale */ + if( !disp_loc_name){ + disp_loc_name = estrdup(intl_locale_get_default()); + free_loc_name = 1; + } + + /* Get the disp_value for the given locale */ + do{ + disp_name = erealloc( disp_name , buflen * sizeof(UChar) ); + disp_name_len = buflen; + + if( strcmp(tag_name , LOC_LANG_TAG)==0 ){ + buflen = uloc_getDisplayLanguage ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status); + } else if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){ + buflen = uloc_getDisplayScript ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status); + } else if( strcmp(tag_name , LOC_REGION_TAG)==0 ){ + buflen = uloc_getDisplayCountry ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status); + } else if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){ + buflen = uloc_getDisplayVariant ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status); + } else if( strcmp(tag_name , DISP_NAME)==0 ){ + buflen = uloc_getDisplayName ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status); + } + + /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */ + if( U_FAILURE( status ) ) + { + if( status == U_BUFFER_OVERFLOW_ERROR ) + { + status = U_ZERO_ERROR; + continue; + } + + spprintf(&msg, 0, "locale_get_display_%s : unable to get locale %s", tag_name , tag_name ); + intl_error_set( NULL, status, msg , 1 ); + efree(msg); + if( disp_name){ + efree( disp_name ); + } + if( mod_loc_name){ + efree( mod_loc_name ); + } + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + RETURN_FALSE; + } + } while( buflen > disp_name_len ); + + if( mod_loc_name){ + efree( mod_loc_name ); + } + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + /* Convert display locale name from UTF-16 to UTF-8. */ + u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status ); + efree( disp_name ); + if( !u8str ) + { + spprintf(&msg, 0, "locale_get_display_%s :error converting display name for %s to UTF-8", tag_name , tag_name ); + intl_error_set( NULL, status, msg , 1 ); + efree(msg); + RETURN_FALSE; + } + + RETVAL_NEW_STR( u8str ); +} +/* }}} */ + +/* {{{ proto static string Locale::getDisplayName($locale[, $in_locale = null]) +* gets the name for the $locale in $in_locale or default_locale + }}} */ +/* {{{ proto static string get_display_name($locale[, $in_locale = null]) +* gets the name for the $locale in $in_locale or default_locale +*/ +PHP_FUNCTION(locale_get_display_name) +{ + get_icu_disp_value_src_php( DISP_NAME , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ proto static string Locale::getDisplayLanguage($locale[, $in_locale = null]) +* gets the language for the $locale in $in_locale or default_locale + }}} */ +/* {{{ proto static string get_display_language($locale[, $in_locale = null]) +* gets the language for the $locale in $in_locale or default_locale +*/ +PHP_FUNCTION(locale_get_display_language) +{ + get_icu_disp_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ proto static string Locale::getDisplayScript($locale, $in_locale = null) +* gets the script for the $locale in $in_locale or default_locale + }}} */ +/* {{{ proto static string get_display_script($locale, $in_locale = null) +* gets the script for the $locale in $in_locale or default_locale +*/ +PHP_FUNCTION(locale_get_display_script) +{ + get_icu_disp_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ proto static string Locale::getDisplayRegion($locale, $in_locale = null) +* gets the region for the $locale in $in_locale or default_locale + }}} */ +/* {{{ proto static string get_display_region($locale, $in_locale = null) +* gets the region for the $locale in $in_locale or default_locale +*/ +PHP_FUNCTION(locale_get_display_region) +{ + get_icu_disp_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ +* proto static string Locale::getDisplayVariant($locale, $in_locale = null) +* gets the variant for the $locale in $in_locale or default_locale + }}} */ +/* {{{ +* proto static string get_display_variant($locale, $in_locale = null) +* gets the variant for the $locale in $in_locale or default_locale +*/ +PHP_FUNCTION(locale_get_display_variant) +{ + get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + + /* {{{ proto static array getKeywords(string $locale) { + * return an associative array containing keyword-value + * pairs for this locale. The keys are keys to the array (doh!) + * }}}*/ + /* {{{ proto static array locale_get_keywords(string $locale) { + * return an associative array containing keyword-value + * pairs for this locale. The keys are keys to the array (doh!) + */ +PHP_FUNCTION( locale_get_keywords ) +{ + UEnumeration* e = NULL; + UErrorCode status = U_ZERO_ERROR; + + const char* kw_key = NULL; + int32_t kw_key_len = 0; + + const char* loc_name = NULL; + size_t loc_name_len = 0; + +/* + ICU expects the buffer to be allocated before calling the function + and so the buffer size has been explicitly specified + ICU uloc.h #define ULOC_KEYWORD_AND_VALUES_CAPACITY 100 + hence the kw_value buffer size is 100 +*/ + zend_string *kw_value_str; + int32_t kw_value_len = 100; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", + &loc_name, &loc_name_len ) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_get_keywords: unable to parse input params", 0 ); + + RETURN_FALSE; + } + + if(loc_name_len == 0) { + loc_name = intl_locale_get_default(); + } + + /* Get the keywords */ + e = uloc_openKeywords( loc_name, &status ); + if( e != NULL ) + { + /* Traverse it, filling the return array. */ + array_init( return_value ); + + while( ( kw_key = uenum_next( e, &kw_key_len, &status ) ) != NULL ){ + kw_value_len = 100; + kw_value_str = zend_string_alloc(kw_value_len, 0); + + /* Get the keyword value for each keyword */ + kw_value_len=uloc_getKeywordValue( loc_name, kw_key, ZSTR_VAL(kw_value_str), kw_value_len, &status ); + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + kw_value_str = zend_string_extend(kw_value_str, kw_value_len, 0); + kw_value_len=uloc_getKeywordValue( loc_name,kw_key, ZSTR_VAL(kw_value_str), kw_value_len+1, &status ); + } else if(!U_FAILURE(status)) { + kw_value_str = zend_string_truncate(kw_value_str, kw_value_len, 0); + } + if (U_FAILURE(status)) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_get_keywords: Error encountered while getting the keyword value for the keyword", 0 ); + if( kw_value_str){ + zend_string_free( kw_value_str ); + } + zval_dtor(return_value); + RETURN_FALSE; + } + + add_assoc_str( return_value, (char *)kw_key, kw_value_str); + } /* end of while */ + + } /* end of if e!=NULL */ + + uenum_close( e ); +} +/* }}} */ + + /* {{{ proto static string Locale::canonicalize($locale) + * @return string the canonicalized locale + * }}} */ + /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale) + * @param string $locale The locale string to canonicalize + */ +PHP_FUNCTION(locale_canonicalize) +{ + get_icu_value_src_php( LOC_CANONICALIZE_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); +} +/* }}} */ + +/* {{{ append_key_value +* Internal function which is called from locale_compose +* gets the value for the key_name and appends to the loc_name +* returns 1 if successful , -1 if not found , +* 0 if array element is not a string , -2 if buffer-overflow +*/ +static int append_key_value(smart_str* loc_name, HashTable* hash_arr, char* key_name) +{ + zval *ele_value; + + if ((ele_value = zend_hash_str_find(hash_arr , key_name, strlen(key_name))) != NULL ) { + if(Z_TYPE_P(ele_value)!= IS_STRING ){ + /* element value is not a string */ + return FAILURE; + } + if(strcmp(key_name, LOC_LANG_TAG) != 0 && + strcmp(key_name, LOC_GRANDFATHERED_LANG_TAG)!=0 ) { + /* not lang or grandfathered tag */ + smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1); + } + smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value)); + return SUCCESS; + } + + return LOC_NOT_FOUND; +} +/* }}} */ + +/* {{{ append_prefix , appends the prefix needed +* e.g. private adds 'x' +*/ +static void add_prefix(smart_str* loc_name, char* key_name) +{ + if( strncmp(key_name , LOC_PRIVATE_TAG , 7) == 0 ){ + smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1); + smart_str_appendl(loc_name, PRIVATE_PREFIX , sizeof(PRIVATE_PREFIX)-1); + } +} +/* }}} */ + +/* {{{ append_multiple_key_values +* Internal function which is called from locale_compose +* gets the multiple values for the key_name and appends to the loc_name +* used for 'variant','extlang','private' +* returns 1 if successful , -1 if not found , +* 0 if array element is not a string , -2 if buffer-overflow +*/ +static int append_multiple_key_values(smart_str* loc_name, HashTable* hash_arr, char* key_name) +{ + zval *ele_value; + int i = 0; + int isFirstSubtag = 0; + int max_value = 0; + + /* Variant/ Extlang/Private etc. */ + if ((ele_value = zend_hash_str_find( hash_arr , key_name , strlen(key_name))) != NULL) { + if( Z_TYPE_P(ele_value) == IS_STRING ){ + add_prefix( loc_name , key_name); + + smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1); + smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value)); + return SUCCESS; + } else if(Z_TYPE_P(ele_value) == IS_ARRAY ) { + HashTable *arr = Z_ARRVAL_P(ele_value); + zval *data; + + ZEND_HASH_FOREACH_VAL(arr, data) { + if(Z_TYPE_P(data) != IS_STRING) { + return FAILURE; + } + if (isFirstSubtag++ == 0){ + add_prefix(loc_name , key_name); + } + smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1); + smart_str_appendl(loc_name, Z_STRVAL_P(data) , Z_STRLEN_P(data)); + } ZEND_HASH_FOREACH_END(); + return SUCCESS; + } else { + return FAILURE; + } + } else { + char cur_key_name[31]; + /* Decide the max_value: the max. no. of elements allowed */ + if( strcmp(key_name , LOC_VARIANT_TAG) ==0 ){ + max_value = MAX_NO_VARIANT; + } + if( strcmp(key_name , LOC_EXTLANG_TAG) ==0 ){ + max_value = MAX_NO_EXTLANG; + } + if( strcmp(key_name , LOC_PRIVATE_TAG) ==0 ){ + max_value = MAX_NO_PRIVATE; + } + + /* Multiple variant values as variant0, variant1 ,variant2 */ + isFirstSubtag = 0; + for( i=0 ; i< max_value; i++ ){ + snprintf( cur_key_name , 30, "%s%d", key_name , i); + if ((ele_value = zend_hash_str_find( hash_arr , cur_key_name , strlen(cur_key_name))) != NULL) { + if( Z_TYPE_P(ele_value)!= IS_STRING ){ + /* variant is not a string */ + return FAILURE; + } + /* Add the contents */ + if (isFirstSubtag++ == 0){ + add_prefix(loc_name , cur_key_name); + } + smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1); + smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value)); + } + } /* end of for */ + } /* end of else */ + + return SUCCESS; +} +/* }}} */ + +/*{{{ +* If applicable sets error message and aborts locale_compose gracefully +* returns 0 if locale_compose needs to be aborted +* otherwise returns 1 +*/ +static int handleAppendResult( int result, smart_str* loc_name) +{ + intl_error_reset( NULL ); + if( result == FAILURE) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_compose: parameter array element is not a string", 0 ); + smart_str_free(loc_name); + return 0; + } + return 1; +} +/* }}} */ + +#define RETURN_SMART_STR(str) smart_str_0((str)); RETURN_NEW_STR((str)->s) +/* {{{ proto static string Locale::composeLocale($array) +* Creates a locale by combining the parts of locale-ID passed +* }}} */ +/* {{{ proto static string compose_locale($array) +* Creates a locale by combining the parts of locale-ID passed +* }}} */ +PHP_FUNCTION(locale_compose) +{ + smart_str loc_name_s = {0}; + smart_str *loc_name = &loc_name_s; + zval* arr = NULL; + HashTable* hash_arr = NULL; + int result = 0; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "a", + &arr) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_compose: unable to parse input params", 0 ); + RETURN_FALSE; + } + + hash_arr = Z_ARRVAL_P( arr ); + + if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) + RETURN_FALSE; + + /* Check for grandfathered first */ + result = append_key_value(loc_name, hash_arr, LOC_GRANDFATHERED_LANG_TAG); + if( result == SUCCESS){ + RETURN_SMART_STR(loc_name); + } + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Not grandfathered */ + result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG); + if( result == LOC_NOT_FOUND ){ + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_compose: parameter array does not contain 'language' tag.", 0 ); + smart_str_free(loc_name); + RETURN_FALSE; + } + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Extlang */ + result = append_multiple_key_values(loc_name, hash_arr , LOC_EXTLANG_TAG); + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Script */ + result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG); + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Region */ + result = append_key_value( loc_name, hash_arr , LOC_REGION_TAG); + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Variant */ + result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG); + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + /* Private */ + result = append_multiple_key_values( loc_name, hash_arr , LOC_PRIVATE_TAG); + if( !handleAppendResult( result, loc_name)){ + RETURN_FALSE; + } + + RETURN_SMART_STR(loc_name); +} +/* }}} */ + + +/*{{{ +* Parses the locale and returns private subtags if existing +* else returns NULL +* e.g. for locale='en_US-x-prv1-prv2-prv3' +* returns a pointer to the string 'prv1-prv2-prv3' +*/ +static zend_string* get_private_subtags(const char* loc_name) +{ + zend_string* result =NULL; + int singletonPos = 0; + int len =0; + const char* mod_loc_name =NULL; + + if( loc_name && (len = strlen(loc_name)>0 ) ){ + mod_loc_name = loc_name ; + len = strlen(mod_loc_name); + while( (singletonPos = getSingletonPos(mod_loc_name))!= -1){ + + if( singletonPos!=-1){ + if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){ + /* private subtag start found */ + if( singletonPos + 2 == len){ + /* loc_name ends with '-x-' ; return NULL */ + } + else{ + /* result = mod_loc_name + singletonPos +2; */ + result = zend_string_init(mod_loc_name + singletonPos+2 , (len -( singletonPos +2) ), 0); + } + break; + } + else{ + if( singletonPos + 1 >= len){ + /* String end */ + break; + } else { + /* singleton found but not a private subtag , hence check further in the string for the private subtag */ + mod_loc_name = mod_loc_name + singletonPos +1; + len = strlen(mod_loc_name); + } + } + } + + } /* end of while */ + } + + return result; +} +/* }}} */ + +/* {{{ code used by locale_parse +*/ +static int add_array_entry(const char* loc_name, zval* hash_arr, char* key_name) +{ + zend_string* key_value = NULL; + char* cur_key_name = NULL; + char* token = NULL; + char* last_ptr = NULL; + + int result = 0; + int cur_result = 0; + int cnt = 0; + + + if( strcmp(key_name , LOC_PRIVATE_TAG)==0 ){ + key_value = get_private_subtags( loc_name ); + result = 1; + } else { + key_value = get_icu_value_internal( loc_name , key_name , &result,1 ); + } + if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) || + ( strcmp(key_name , LOC_VARIANT_TAG)==0) ){ + if( result > 0 && key_value){ + /* Tokenize on the "_" or "-" */ + token = php_strtok_r( key_value->val , DELIMITER ,&last_ptr); + if( cur_key_name ){ + efree( cur_key_name); + } + cur_key_name = (char*)ecalloc( 25, 25); + sprintf( cur_key_name , "%s%d", key_name , cnt++); + add_assoc_string( hash_arr, cur_key_name , token); + /* tokenize on the "_" or "-" and stop at singleton if any */ + while( (token = php_strtok_r(NULL , DELIMITER , &last_ptr)) && (strlen(token)>1) ){ + sprintf( cur_key_name , "%s%d", key_name , cnt++); + add_assoc_string( hash_arr, cur_key_name , token); + } +/* + if( strcmp(key_name, LOC_PRIVATE_TAG) == 0 ){ + } +*/ + } + if (key_value) { + zend_string_release(key_value); + } + } else { + if( result == 1 ){ + add_assoc_str( hash_arr, key_name , key_value); + cur_result = 1; + } else if (key_value) { + zend_string_release(key_value); + } + } + + if( cur_key_name ){ + efree( cur_key_name); + } + /*if( key_name != LOC_PRIVATE_TAG && key_value){*/ + return cur_result; +} +/* }}} */ + +/* {{{ proto static array Locale::parseLocale($locale) +* parses a locale-id into an array the different parts of it + }}} */ +/* {{{ proto static array parse_locale($locale) +* parses a locale-id into an array the different parts of it +*/ +PHP_FUNCTION(locale_parse) +{ + const char* loc_name = NULL; + size_t loc_name_len = 0; + int grOffset = 0; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", + &loc_name, &loc_name_len ) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_parse: unable to parse input params", 0 ); + + RETURN_FALSE; + } + + if(loc_name_len == 0) { + loc_name = intl_locale_get_default(); + } + + array_init( return_value ); + + grOffset = findOffset( LOC_GRANDFATHERED , loc_name ); + if( grOffset >= 0 ){ + add_assoc_string( return_value , LOC_GRANDFATHERED_LANG_TAG, (char *)loc_name); + } + else{ + /* Not grandfathered */ + add_array_entry( loc_name , return_value , LOC_LANG_TAG); + add_array_entry( loc_name , return_value , LOC_SCRIPT_TAG); + add_array_entry( loc_name , return_value , LOC_REGION_TAG); + add_array_entry( loc_name , return_value , LOC_VARIANT_TAG); + add_array_entry( loc_name , return_value , LOC_PRIVATE_TAG); + } +} +/* }}} */ + +/* {{{ proto static array Locale::getAllVariants($locale) +* gets an array containing the list of variants, or null + }}} */ +/* {{{ proto static array locale_get_all_variants($locale) +* gets an array containing the list of variants, or null +*/ +PHP_FUNCTION(locale_get_all_variants) +{ + const char* loc_name = NULL; + size_t loc_name_len = 0; + + int result = 0; + char* token = NULL; + zend_string* variant = NULL; + char* saved_ptr = NULL; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", + &loc_name, &loc_name_len ) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_parse: unable to parse input params", 0 ); + + RETURN_FALSE; + } + + if(loc_name_len == 0) { + loc_name = intl_locale_get_default(); + } + + + array_init( return_value ); + + /* If the locale is grandfathered, stop, no variants */ + if( findOffset( LOC_GRANDFATHERED , loc_name ) >= 0 ){ + /* ("Grandfathered Tag. No variants."); */ + } + else { + /* Call ICU variant */ + variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0); + if( result > 0 && variant){ + /* Tokenize on the "_" or "-" */ + token = php_strtok_r( variant->val , DELIMITER , &saved_ptr); + add_next_index_stringl( return_value, token , strlen(token)); + /* tokenize on the "_" or "-" and stop at singleton if any */ + while( (token = php_strtok_r(NULL , DELIMITER, &saved_ptr)) && (strlen(token)>1) ){ + add_next_index_stringl( return_value, token , strlen(token)); + } + } + if( variant ){ + zend_string_release( variant ); + } + } + + +} +/* }}} */ + +/*{{{ +* Converts to lower case and also replaces all hyphens with the underscore +*/ +static int strToMatch(const char* str ,char *retstr) +{ + char* anchor = NULL; + const char* anchor1 = NULL; + int result = 0; + + if( (!str) || str[0] == '\0'){ + return result; + } else { + anchor = retstr; + anchor1 = str; + while( (*str)!='\0' ){ + if( *str == '-' ){ + *retstr = '_'; + } else { + *retstr = tolower(*str); + } + str++; + retstr++; + } + *retstr = '\0'; + retstr= anchor; + str= anchor1; + result = 1; + } + + return(result); +} +/* }}} */ + +/* {{{ proto static boolean Locale::filterMatches(string $langtag, string $locale[, bool $canonicalize]) +* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm +*/ +/* }}} */ +/* {{{ proto boolean locale_filter_matches(string $langtag, string $locale[, bool $canonicalize]) +* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm +*/ +PHP_FUNCTION(locale_filter_matches) +{ + char* lang_tag = NULL; + size_t lang_tag_len = 0; + const char* loc_range = NULL; + size_t loc_range_len = 0; + + int result = 0; + char* token = 0; + char* chrcheck = NULL; + + zend_string* can_lang_tag = NULL; + zend_string* can_loc_range = NULL; + + char* cur_lang_tag = NULL; + char* cur_loc_range = NULL; + + zend_bool boolCanonical = 0; + UErrorCode status = U_ZERO_ERROR; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "ss|b", + &lang_tag, &lang_tag_len , &loc_range , &loc_range_len , + &boolCanonical) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_filter_matches: unable to parse input params", 0 ); + + RETURN_FALSE; + } + + if(loc_range_len == 0) { + loc_range = intl_locale_get_default(); + } + + if( strcmp(loc_range,"*")==0){ + RETURN_TRUE; + } + + if( boolCanonical ){ + /* canonicalize loc_range */ + can_loc_range=get_icu_value_internal( loc_range , LOC_CANONICALIZE_TAG , &result , 0); + if( result ==0) { + intl_error_set( NULL, status, + "locale_filter_matches : unable to canonicalize loc_range" , 0 ); + RETURN_FALSE; + } + + /* canonicalize lang_tag */ + can_lang_tag = get_icu_value_internal( lang_tag , LOC_CANONICALIZE_TAG , &result , 0); + if( result ==0) { + intl_error_set( NULL, status, + "locale_filter_matches : unable to canonicalize lang_tag" , 0 ); + RETURN_FALSE; + } + + /* Convert to lower case for case-insensitive comparison */ + cur_lang_tag = ecalloc( 1, can_lang_tag->len + 1); + + /* Convert to lower case for case-insensitive comparison */ + result = strToMatch( can_lang_tag->val , cur_lang_tag); + if( result == 0) { + efree( cur_lang_tag ); + zend_string_release( can_lang_tag ); + RETURN_FALSE; + } + + cur_loc_range = ecalloc( 1, can_loc_range->len + 1); + result = strToMatch( can_loc_range->val , cur_loc_range ); + if( result == 0) { + efree( cur_lang_tag ); + zend_string_release( can_lang_tag ); + efree( cur_loc_range ); + zend_string_release( can_loc_range ); + RETURN_FALSE; + } + + /* check if prefix */ + token = strstr( cur_lang_tag , cur_loc_range ); + + if( token && (token==cur_lang_tag) ){ + /* check if the char. after match is SEPARATOR */ + chrcheck = token + (strlen(cur_loc_range)); + if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ + if( cur_lang_tag){ + efree( cur_lang_tag ); + } + if( cur_loc_range){ + efree( cur_loc_range ); + } + if( can_lang_tag){ + zend_string_release( can_lang_tag ); + } + if( can_loc_range){ + zend_string_release( can_loc_range ); + } + RETURN_TRUE; + } + } + + /* No prefix as loc_range */ + if( cur_lang_tag){ + efree( cur_lang_tag ); + } + if( cur_loc_range){ + efree( cur_loc_range ); + } + if( can_lang_tag){ + zend_string_release( can_lang_tag ); + } + if( can_loc_range){ + zend_string_release( can_loc_range ); + } + RETURN_FALSE; + + } /* end of if isCanonical */ + else{ + /* Convert to lower case for case-insensitive comparison */ + cur_lang_tag = ecalloc( 1, strlen(lang_tag ) + 1); + + result = strToMatch( lang_tag , cur_lang_tag); + if( result == 0) { + efree( cur_lang_tag ); + RETURN_FALSE; + } + cur_loc_range = ecalloc( 1, strlen(loc_range ) + 1); + result = strToMatch( loc_range , cur_loc_range ); + if( result == 0) { + efree( cur_lang_tag ); + efree( cur_loc_range ); + RETURN_FALSE; + } + + /* check if prefix */ + token = strstr( cur_lang_tag , cur_loc_range ); + + if( token && (token==cur_lang_tag) ){ + /* check if the char. after match is SEPARATOR */ + chrcheck = token + (strlen(cur_loc_range)); + if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ + if( cur_lang_tag){ + efree( cur_lang_tag ); + } + if( cur_loc_range){ + efree( cur_loc_range ); + } + RETURN_TRUE; + } + } + + /* No prefix as loc_range */ + if( cur_lang_tag){ + efree( cur_lang_tag ); + } + if( cur_loc_range){ + efree( cur_loc_range ); + } + RETURN_FALSE; + + } +} +/* }}} */ + +static void array_cleanup( char* arr[] , int arr_size) +{ + int i=0; + for( i=0; i< arr_size; i++ ){ + if( arr[i*2] ){ + efree( arr[i*2]); + } + } + efree(arr); +} + +#define LOOKUP_CLEAN_RETURN(value) array_cleanup(cur_arr, cur_arr_len); return (value) +/* {{{ +* returns the lookup result to lookup_loc_range_src_php +* internal function +*/ +static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr, int canonicalize ) +{ + int i = 0; + int cur_arr_len = 0; + int result = 0; + + zend_string* lang_tag = NULL; + zval* ele_value = NULL; + char** cur_arr = NULL; + + char* cur_loc_range = NULL; + zend_string* can_loc_range = NULL; + int saved_pos = 0; + + zend_string* return_value = NULL; + + cur_arr = ecalloc(zend_hash_num_elements(hash_arr)*2, sizeof(char *)); + ZEND_HASH_FOREACH_VAL(hash_arr, ele_value) { + /* convert the array to lowercase , also replace hyphens with the underscore and store it in cur_arr */ + if(Z_TYPE_P(ele_value)!= IS_STRING) { + /* element value is not a string */ + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: locale array element is not a string", 0); + LOOKUP_CLEAN_RETURN(NULL); + } + cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_P(ele_value), Z_STRLEN_P(ele_value)); + result = strToMatch(Z_STRVAL_P(ele_value), cur_arr[cur_arr_len*2]); + if(result == 0) { + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0); + LOOKUP_CLEAN_RETURN(NULL); + } + cur_arr[cur_arr_len*2+1] = Z_STRVAL_P(ele_value); + cur_arr_len++ ; + } ZEND_HASH_FOREACH_END(); /* end of for */ + + /* Canonicalize array elements */ + if(canonicalize) { + for(i=0; ival[0]) { + if(lang_tag) { + zend_string_release(lang_tag); + } + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0); + LOOKUP_CLEAN_RETURN(NULL); + } + cur_arr[i*2] = erealloc(cur_arr[i*2], lang_tag->len+1); + result = strToMatch(lang_tag->val, cur_arr[i*2]); + zend_string_release(lang_tag); + if(result == 0) { + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0); + LOOKUP_CLEAN_RETURN(NULL); + } + } + + } + + if(canonicalize) { + /* Canonicalize the loc_range */ + can_loc_range = get_icu_value_internal(loc_range, LOC_CANONICALIZE_TAG, &result , 0); + if( result != 1 || can_loc_range == NULL || !can_loc_range->val[0]) { + /* Error */ + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize loc_range" , 0 ); + if(can_loc_range) { + zend_string_release(can_loc_range); + } + LOOKUP_CLEAN_RETURN(NULL); + } else { + loc_range = can_loc_range->val; + } + } + + cur_loc_range = ecalloc(1, strlen(loc_range)+1); + /* convert to lower and replace hyphens */ + result = strToMatch(loc_range, cur_loc_range); + if(can_loc_range) { + zend_string_release(can_loc_range); + } + if(result == 0) { + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0); + LOOKUP_CLEAN_RETURN(NULL); + } + + /* Lookup for the lang_tag match */ + saved_pos = strlen(cur_loc_range); + while(saved_pos > 0) { + for(i=0; i< cur_arr_len; i++){ + if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) { + /* Match found */ + char *str = canonicalize ? cur_arr[i*2] : cur_arr[i*2+1]; + return_value = zend_string_init(str, strlen(str), 0); + efree(cur_loc_range); + LOOKUP_CLEAN_RETURN(return_value); + } + } + saved_pos = getStrrtokenPos(cur_loc_range, saved_pos); + } + + /* Match not found */ + efree(cur_loc_range); + LOOKUP_CLEAN_RETURN(NULL); +} +/* }}} */ + +/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]]) +* Searchs the items in $langtag for the best match to the language +* range +*/ +/* }}} */ +/* {{{ proto string locale_lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]]) +* Searchs the items in $langtag for the best match to the language +* range +*/ +PHP_FUNCTION(locale_lookup) +{ + zend_string* fallback_loc_str = NULL; + const char* loc_range = NULL; + size_t loc_range_len = 0; + + zval* arr = NULL; + HashTable* hash_arr = NULL; + zend_bool boolCanonical = 0; + zend_string* result_str = NULL; + + intl_error_reset( NULL ); + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "as|bS", &arr, &loc_range, &loc_range_len, + &boolCanonical, &fallback_loc_str) == FAILURE) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_lookup: unable to parse input params", 0 ); + RETURN_FALSE; + } + + if(loc_range_len == 0) { + loc_range = intl_locale_get_default(); + } + + hash_arr = Z_ARRVAL_P(arr); + + if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) { + RETURN_EMPTY_STRING(); + } + + result_str = lookup_loc_range(loc_range, hash_arr, boolCanonical); + if(result_str == NULL || ZSTR_VAL(result_str)[0] == '\0') { + if( fallback_loc_str ) { + result_str = zend_string_copy(fallback_loc_str); + } else { + RETURN_EMPTY_STRING(); + } + } + + RETURN_STR(result_str); +} +/* }}} */ + +/* {{{ proto string Locale::acceptFromHttp(string $http_accept) +* Tries to find out best available locale based on HTTP �Accept-Language� header +*/ +/* }}} */ +/* {{{ proto string locale_accept_from_http(string $http_accept) +* Tries to find out best available locale based on HTTP �Accept-Language� header +*/ +PHP_FUNCTION(locale_accept_from_http) +{ + UEnumeration *available; + char *http_accept = NULL; + size_t http_accept_len; + UErrorCode status = 0; + int len; + char resultLocale[INTL_MAX_LOCALE_LEN+1]; + UAcceptResult outResult; + + if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", &http_accept, &http_accept_len) == FAILURE) + { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + "locale_accept_from_http: unable to parse input parameters", 0 ); + RETURN_FALSE; + } + + available = ures_openAvailableLocales(NULL, &status); + INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list"); + len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN, + &outResult, http_accept, available, &status); + uenum_close(available); + INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale"); + if (len < 0 || outResult == ULOC_ACCEPT_FAILED) { + RETURN_FALSE; + } + RETURN_STRINGL(resultLocale, 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 + *can_loc_len +*/ diff --git a/ext/intl/tests/bug72533.phpt b/ext/intl/tests/bug72533.phpt new file mode 100644 index 0000000000000..c7fcba39d029b --- /dev/null +++ b/ext/intl/tests/bug72533.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug #72533 (locale_accept_from_http out-of-bounds access) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +false +'locale_accept_from_http: locale string too long: U_ILLEGAL_ARGUMENT_ERROR' +'en' \ No newline at end of file From e89c59f33fd1228d9c1c169ec556414f54eae05b Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:21 +0000 Subject: [PATCH 08/46] commit patch 17614012 --- ext/exif/exif.c | 14 +- ext/exif/exif.c.orig | 4187 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4199 insertions(+), 2 deletions(-) create mode 100644 ext/exif/exif.c.orig diff --git a/ext/exif/exif.c b/ext/exif/exif.c index ebac695b2fc5b..9a1643a72060c 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -1702,6 +1702,10 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c if (!length) break; case TAG_FMT_UNDEFINED: + if (tag == TAG_MAKER_NOTE) { + length = MIN(length, strlen(value)); + } + if (value) { /* do not recompute length here */ info_value->s = estrndup(value, length); @@ -2709,8 +2713,14 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu 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)) - return FALSE; + 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:"");*/ diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig new file mode 100644 index 0000000000000..c37f2beb94a1e --- /dev/null +++ b/ext/exif/exif.c.orig @@ -0,0 +1,4187 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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: ebac695b2fc5b2488443366e73b673df0022004e $ */ + +/* 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 100 + +/* {{{ 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_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"); + 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; + } + efree(return_list); + } + 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; + } + efree(return_list); + } + 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) + Get the size of an image as 4-element array */ +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 +}; + +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, + MN_OFFSET_GUESS +} 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; + +static const maker_note_type maker_note_array[] = { + { tag_table_VND_CANON, "Canon", NULL, NULL, 0, 0, MN_ORDER_INTEL, MN_OFFSET_GUESS}, +/* { tag_table_VND_CANON, "Canon", NULL, NULL, 0, 0, MN_ORDER_NORMAL, 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}, +}; +/* }}} */ + +/* {{{ 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 "\n"; + } + 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_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 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->FileName?ImageInfo->FileName:"", type, buf, args); + efree(buf); + } +#else + php_verror(docref, 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) { + /* 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); +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) { + 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; + } + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + if (zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + zend_multibyte_fetch_encoding(ImageInfo->encode_unicode), + zend_multibyte_fetch_encoding(decode) + ) == (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 */ + if (zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + zend_multibyte_fetch_encoding(ImageInfo->encode_jis), + zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_jis_be : ImageInfo->decode_jis_le) + ) == (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) +{ + int de, i=0, section_index = SECTION_MAKERNOTE; + int NumDirEntries, old_motorola_intel, offset_diff; + 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)) + return FALSE; + 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; + } + + 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; + case MN_OFFSET_GUESS: + 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 + offset_base = value_ptr + offset_diff; + break; + 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 + x%04X*12 = x%04X > x%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(%ld)", 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, offset_val, SEEK_SET); + fgot = php_stream_tell(ImageInfo->infile); + if (fgot!=offset_val) { + EFREE_IF(outside); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, 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, format, ImageInfo->motorola_intel); + break; + + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Thumbnail.height = exif_convert_any_to_int(value_ptr, 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, 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, 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, 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", value_ptr, value_ptr+length+1); + /* 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, 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: + exif_process_IFD_in_MAKERNOTE(ImageInfo, value_ptr, byte_count, offset_base, IFDlength, displacement); + 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)) { + 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 de; + int NumDirEntries; + int NextDirOffset; + +#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; + + 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;demotorola_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)) { +#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. */ + 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); + +#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)) == EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + if ((ll = php_stream_getc(ImageInfo->infile)) == 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) + } + 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) + } + 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 = php_stream_open_wrapper(FileName, "rb", STREAM_MUST_SEEK|IGNORE_PATH, NULL); + if (!ImageInfo->infile) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Unable to open file"); + return FALSE; + } + + if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) { + if (VCWD_STAT(FileName, &st) >= 0) { + 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 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); + } + } + + base = php_basename(FileName, strlen(FileName), NULL, 0); + ImageInfo->FileName = estrndup(ZSTR_VAL(base), ZSTR_LEN(base)); + zend_string_release(base); + 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 JPEG headers. */ + ret = exif_scan_FILE_header(ImageInfo); + + php_stream_close(ImageInfo->infile); + return ret; +} +/* }}} */ + +/* {{{ proto array exif_read_data(string filename [, string sections_needed [, bool sub_arrays[, bool read_thumbnail]]]) + Reads header data from the JPEG/TIFF image filename and optionally reads the internal thumbnails */ +PHP_FUNCTION(exif_read_data) +{ + char *p_name, *p_sections_needed = NULL; + size_t p_name_len, p_sections_needed_len = 0; + zend_bool sub_arrays=0, read_thumbnail=0, read_all=0; + + int i, ret, sections_needed=0; + image_info_type ImageInfo; + char tmp[64], *sections_str, *s; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|sbb", &p_name, &p_name_len, &p_sections_needed, &p_sections_needed_len, &sub_arrays, &read_thumbnail) == FAILURE) { + return; + } + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (p_sections_needed) { + spprintf(§ions_str, 0, ",%s,", p_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= 3) { + if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) { + exif_scan_thumbnail(&ImageInfo); + } + zval_dtor(p_width); + zval_dtor(p_height); + ZVAL_LONG(p_width, ImageInfo.Thumbnail.width); + ZVAL_LONG(p_height, ImageInfo.Thumbnail.height); + } + if (arg_c >= 4) { + zval_dtor(p_imagetype); + ZVAL_LONG(p_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, p_name, 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(), "s", &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 6e34f82ece27dd7357328f81ac7a15fb78edd0f0 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:23 +0000 Subject: [PATCH 09/46] commit patch 21965670 --- ext/exif/exif.c | 8 ++++---- ext/exif/exif.c.orig | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 9a1643a72060c..82508785ca763 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -1702,11 +1702,11 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c if (!length) break; case TAG_FMT_UNDEFINED: - if (tag == TAG_MAKER_NOTE) { - length = MIN(length, strlen(value)); - } - if (value) { + if (tag == TAG_MAKER_NOTE) { + length = MIN(length, strlen(value)); + } + /* do not recompute length here */ info_value->s = estrndup(value, length); info_data->length = length; diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig index c37f2beb94a1e..48358b8ddb629 100644 --- a/ext/exif/exif.c.orig +++ b/ext/exif/exif.c.orig @@ -1702,6 +1702,10 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c if (!length) break; case TAG_FMT_UNDEFINED: + if (tag == TAG_MAKER_NOTE) { + length = MIN(length, strlen(value)); + } + if (value) { /* do not recompute length here */ info_value->s = estrndup(value, length); @@ -2709,8 +2713,14 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu 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)) - return FALSE; + 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:"");*/ From 095295e1d98aad3daf5b1089b78c7d3491f2407b Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:25 +0000 Subject: [PATCH 10/46] commit patch 27566587 --- Zend/zend_alloc.c | 24 +- Zend/zend_alloc.c.orig | 2881 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2893 insertions(+), 12 deletions(-) create mode 100644 Zend/zend_alloc.c.orig diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index cfc277f136af3..016ff841b01c9 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -1540,21 +1540,21 @@ static void *zend_mm_realloc_heap(zend_mm_heap *heap, void *ptr, size_t size, si ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); if (info & ZEND_MM_IS_SRUN) { - int old_bin_num, bin_num; - - old_bin_num = ZEND_MM_SRUN_BIN_NUM(info); + int old_bin_num = ZEND_MM_SRUN_BIN_NUM(info); old_size = bin_data_size[old_bin_num]; - bin_num = ZEND_MM_SMALL_SIZE_TO_BIN(size); - if (old_bin_num == bin_num) { + if (size <= ZEND_MM_MAX_SMALL_SIZE) { + int bin_num = ZEND_MM_SMALL_SIZE_TO_BIN(size); + if (old_bin_num == bin_num) { #if ZEND_DEBUG - dbg = zend_mm_get_debug_info(heap, ptr); - dbg->size = real_size; - dbg->filename = __zend_filename; - dbg->orig_filename = __zend_orig_filename; - dbg->lineno = __zend_lineno; - dbg->orig_lineno = __zend_orig_lineno; + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; #endif - return ptr; + return ptr; + } } } else /* if (info & ZEND_MM_IS_LARGE_RUN) */ { ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); diff --git a/Zend/zend_alloc.c.orig b/Zend/zend_alloc.c.orig new file mode 100644 index 0000000000000..cfc277f136af3 --- /dev/null +++ b/Zend/zend_alloc.c.orig @@ -0,0 +1,2881 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andi Gutmans | + | Zeev Suraski | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +/* + * zend_alloc is designed to be a modern CPU cache friendly memory manager + * for PHP. Most ideas are taken from jemalloc and tcmalloc implementations. + * + * All allocations are split into 3 categories: + * + * Huge - the size is greater than CHUNK size (~2M by default), allocation is + * performed using mmap(). The result is aligned on 2M boundary. + * + * Large - a number of 4096K pages inside a CHUNK. Large blocks + * are always aligned on page boundary. + * + * Small - less than 3/4 of page size. Small sizes are rounded up to nearest + * greater predefined small size (there are 30 predefined sizes: + * 8, 16, 24, 32, ... 3072). Small blocks are allocated from + * RUNs. Each RUN is allocated as a single or few following pages. + * Allocation inside RUNs implemented using linked list of free + * elements. The result is aligned to 8 bytes. + * + * zend_alloc allocates memory from OS by CHUNKs, these CHUNKs and huge memory + * blocks are always aligned to CHUNK boundary. So it's very easy to determine + * the CHUNK owning the certain pointer. Regular CHUNKs reserve a single + * page at start for special purpose. It contains bitset of free pages, + * few bitset for available runs of predefined small sizes, map of pages that + * keeps information about usage of each page in this CHUNK, etc. + * + * zend_alloc provides familiar emalloc/efree/erealloc API, but in addition it + * provides specialized and optimized routines to allocate blocks of predefined + * sizes (e.g. emalloc_2(), emallc_4(), ..., emalloc_large(), etc) + * The library uses C preprocessor tricks that substitute calls to emalloc() + * with more specialized routines when the requested size is known. + */ + +#include "zend.h" +#include "zend_alloc.h" +#include "zend_globals.h" +#include "zend_operators.h" +#include "zend_multiply.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef ZEND_WIN32 +# include +# include +#endif + +#include +#include +#include + +#include +#include +#if HAVE_LIMITS_H +#include +#endif +#include +#include + +#ifndef _WIN32 +# ifdef HAVE_MREMAP +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +# ifndef __USE_GNU +# define __USE_GNU +# endif +# endif +# include +# ifndef MAP_ANON +# ifdef MAP_ANONYMOUS +# define MAP_ANON MAP_ANONYMOUS +# endif +# endif +# ifndef MREMAP_MAYMOVE +# define MREMAP_MAYMOVE 0 +# endif +# ifndef MAP_FAILED +# define MAP_FAILED ((void*)-1) +# endif +# ifndef MAP_POPULATE +# define MAP_POPULATE 0 +# endif +# if defined(_SC_PAGESIZE) || (_SC_PAGE_SIZE) +# define REAL_PAGE_SIZE _real_page_size +static size_t _real_page_size = ZEND_MM_PAGE_SIZE; +# endif +#endif + +#ifndef REAL_PAGE_SIZE +# define REAL_PAGE_SIZE ZEND_MM_PAGE_SIZE +#endif + +#ifndef ZEND_MM_STAT +# define ZEND_MM_STAT 1 /* track current and peak memory usage */ +#endif +#ifndef ZEND_MM_LIMIT +# define ZEND_MM_LIMIT 1 /* support for user-defined memory limit */ +#endif +#ifndef ZEND_MM_CUSTOM +# define ZEND_MM_CUSTOM 1 /* support for custom memory allocator */ + /* USE_ZEND_ALLOC=0 may switch to system malloc() */ +#endif +#ifndef ZEND_MM_STORAGE +# define ZEND_MM_STORAGE 1 /* support for custom memory storage */ +#endif +#ifndef ZEND_MM_ERROR +# define ZEND_MM_ERROR 1 /* report system errors */ +#endif + +#ifndef ZEND_MM_CHECK +# define ZEND_MM_CHECK(condition, message) do { \ + if (UNEXPECTED(!(condition))) { \ + zend_mm_panic(message); \ + } \ + } while (0) +#endif + +typedef uint32_t zend_mm_page_info; /* 4-byte integer */ +typedef zend_ulong zend_mm_bitset; /* 4-byte or 8-byte integer */ + +#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \ + (((size_t)(size)) & ((alignment) - 1)) +#define ZEND_MM_ALIGNED_BASE(size, alignment) \ + (((size_t)(size)) & ~((alignment) - 1)) +#define ZEND_MM_SIZE_TO_NUM(size, alignment) \ + (((size_t)(size) + ((alignment) - 1)) / (alignment)) + +#define ZEND_MM_BITSET_LEN (sizeof(zend_mm_bitset) * 8) /* 32 or 64 */ +#define ZEND_MM_PAGE_MAP_LEN (ZEND_MM_PAGES / ZEND_MM_BITSET_LEN) /* 16 or 8 */ + +typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ + +#define ZEND_MM_IS_FRUN 0x00000000 +#define ZEND_MM_IS_LRUN 0x40000000 +#define ZEND_MM_IS_SRUN 0x80000000 + +#define ZEND_MM_LRUN_PAGES_MASK 0x000003ff +#define ZEND_MM_LRUN_PAGES_OFFSET 0 + +#define ZEND_MM_SRUN_BIN_NUM_MASK 0x0000001f +#define ZEND_MM_SRUN_BIN_NUM_OFFSET 0 + +#define ZEND_MM_SRUN_FREE_COUNTER_MASK 0x01ff0000 +#define ZEND_MM_SRUN_FREE_COUNTER_OFFSET 16 + +#define ZEND_MM_NRUN_OFFSET_MASK 0x01ff0000 +#define ZEND_MM_NRUN_OFFSET_OFFSET 16 + +#define ZEND_MM_LRUN_PAGES(info) (((info) & ZEND_MM_LRUN_PAGES_MASK) >> ZEND_MM_LRUN_PAGES_OFFSET) +#define ZEND_MM_SRUN_BIN_NUM(info) (((info) & ZEND_MM_SRUN_BIN_NUM_MASK) >> ZEND_MM_SRUN_BIN_NUM_OFFSET) +#define ZEND_MM_SRUN_FREE_COUNTER(info) (((info) & ZEND_MM_SRUN_FREE_COUNTER_MASK) >> ZEND_MM_SRUN_FREE_COUNTER_OFFSET) +#define ZEND_MM_NRUN_OFFSET(info) (((info) & ZEND_MM_NRUN_OFFSET_MASK) >> ZEND_MM_NRUN_OFFSET_OFFSET) + +#define ZEND_MM_FRUN() ZEND_MM_IS_FRUN +#define ZEND_MM_LRUN(count) (ZEND_MM_IS_LRUN | ((count) << ZEND_MM_LRUN_PAGES_OFFSET)) +#define ZEND_MM_SRUN(bin_num) (ZEND_MM_IS_SRUN | ((bin_num) << ZEND_MM_SRUN_BIN_NUM_OFFSET)) +#define ZEND_MM_SRUN_EX(bin_num, count) (ZEND_MM_IS_SRUN | ((bin_num) << ZEND_MM_SRUN_BIN_NUM_OFFSET) | ((count) << ZEND_MM_SRUN_FREE_COUNTER_OFFSET)) +#define ZEND_MM_NRUN(bin_num, offset) (ZEND_MM_IS_SRUN | ZEND_MM_IS_LRUN | ((bin_num) << ZEND_MM_SRUN_BIN_NUM_OFFSET) | ((offset) << ZEND_MM_NRUN_OFFSET_OFFSET)) + +#define ZEND_MM_BINS 30 + +typedef struct _zend_mm_page zend_mm_page; +typedef struct _zend_mm_bin zend_mm_bin; +typedef struct _zend_mm_free_slot zend_mm_free_slot; +typedef struct _zend_mm_chunk zend_mm_chunk; +typedef struct _zend_mm_huge_list zend_mm_huge_list; + +#ifdef _WIN64 +# define PTR_FMT "0x%0.16I64x" +#elif SIZEOF_LONG == 8 +# define PTR_FMT "0x%0.16lx" +#else +# define PTR_FMT "0x%0.8lx" +#endif + +/* + * Memory is retrived from OS by chunks of fixed size 2MB. + * Inside chunk it's managed by pages of fixed size 4096B. + * So each chunk consists from 512 pages. + * The first page of each chunk is reseved for chunk header. + * It contains service information about all pages. + * + * free_pages - current number of free pages in this chunk + * + * free_tail - number of continuous free pages at the end of chunk + * + * free_map - bitset (a bit for each page). The bit is set if the corresponding + * page is allocated. Allocator for "lage sizes" may easily find a + * free page (or a continuous number of pages) searching for zero + * bits. + * + * map - contains service information for each page. (32-bits for each + * page). + * usage: + * (2 bits) + * FRUN - free page, + * LRUN - first page of "large" allocation + * SRUN - first page of a bin used for "small" allocation + * + * lrun_pages: + * (10 bits) number of allocated pages + * + * srun_bin_num: + * (5 bits) bin number (e.g. 0 for sizes 0-2, 1 for 3-4, + * 2 for 5-8, 3 for 9-16 etc) see zend_alloc_sizes.h + */ + +struct _zend_mm_heap { +#if ZEND_MM_CUSTOM + int use_custom_heap; +#endif +#if ZEND_MM_STORAGE + zend_mm_storage *storage; +#endif +#if ZEND_MM_STAT + size_t size; /* current memory usage */ + size_t peak; /* peak memory usage */ +#endif + zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */ +#if ZEND_MM_STAT || ZEND_MM_LIMIT + size_t real_size; /* current size of allocated pages */ +#endif +#if ZEND_MM_STAT + size_t real_peak; /* peak size of allocated pages */ +#endif +#if ZEND_MM_LIMIT + size_t limit; /* memory limit */ + int overflow; /* memory overflow flag */ +#endif + + zend_mm_huge_list *huge_list; /* list of huge allocated blocks */ + + zend_mm_chunk *main_chunk; + zend_mm_chunk *cached_chunks; /* list of unused chunks */ + int chunks_count; /* number of alocated chunks */ + int peak_chunks_count; /* peak number of allocated chunks for current request */ + int cached_chunks_count; /* number of cached chunks */ + double avg_chunks_count; /* average number of chunks allocated per request */ +#if ZEND_MM_CUSTOM + union { + struct { + void *(*_malloc)(size_t); + void (*_free)(void*); + void *(*_realloc)(void*, size_t); + } std; + struct { + void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + } debug; + } custom_heap; +#endif +}; + +struct _zend_mm_chunk { + zend_mm_heap *heap; + zend_mm_chunk *next; + zend_mm_chunk *prev; + int free_pages; /* number of free pages */ + int free_tail; /* number of free pages at the end of chunk */ + int num; + char reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)]; + zend_mm_heap heap_slot; /* used only in main chunk */ + zend_mm_page_map free_map; /* 512 bits or 64 bytes */ + zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */ +}; + +struct _zend_mm_page { + char bytes[ZEND_MM_PAGE_SIZE]; +}; + +/* + * bin - is one or few continuous pages (up to 8) used for allocation of + * a particular "small size". + */ +struct _zend_mm_bin { + char bytes[ZEND_MM_PAGE_SIZE * 8]; +}; + +struct _zend_mm_free_slot { + zend_mm_free_slot *next_free_slot; +}; + +struct _zend_mm_huge_list { + void *ptr; + size_t size; + zend_mm_huge_list *next; +#if ZEND_DEBUG + zend_mm_debug_info dbg; +#endif +}; + +#define ZEND_MM_PAGE_ADDR(chunk, page_num) \ + ((void*)(((zend_mm_page*)(chunk)) + (page_num))) + +#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size, +static const unsigned int bin_data_size[] = { + ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y) +}; + +#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) elements, +static const int bin_elements[] = { + ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS, x, y) +}; + +#define _BIN_DATA_PAGES(num, size, elements, pages, x, y) pages, +static const int bin_pages[] = { + ZEND_MM_BINS_INFO(_BIN_DATA_PAGES, x, y) +}; + +#if ZEND_DEBUG +ZEND_COLD void zend_debug_alloc_output(char *format, ...) +{ + char output_buf[256]; + va_list args; + + va_start(args, format); + vsprintf(output_buf, format, args); + va_end(args); + +#ifdef ZEND_WIN32 + OutputDebugString(output_buf); +#else + fprintf(stderr, "%s", output_buf); +#endif +} +#endif + +static ZEND_COLD ZEND_NORETURN void zend_mm_panic(const char *message) +{ + fprintf(stderr, "%s\n", message); +/* See http://support.microsoft.com/kb/190351 */ +#ifdef ZEND_WIN32 + fflush(stderr); +#endif +#if ZEND_DEBUG && defined(HAVE_KILL) && defined(HAVE_GETPID) + kill(getpid(), SIGSEGV); +#endif + exit(1); +} + +static ZEND_COLD ZEND_NORETURN void zend_mm_safe_error(zend_mm_heap *heap, + const char *format, + size_t limit, +#if ZEND_DEBUG + const char *filename, + uint lineno, +#endif + size_t size) +{ + + heap->overflow = 1; + zend_try { + zend_error_noreturn(E_ERROR, + format, + limit, +#if ZEND_DEBUG + filename, + lineno, +#endif + size); + } zend_catch { + } zend_end_try(); + heap->overflow = 0; + zend_bailout(); + exit(1); +} + +#ifdef _WIN32 +void +stderr_last_error(char *msg) +{ + LPSTR buf = NULL; + DWORD err = GetLastError(); + + if (!FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buf, + 0, NULL)) { + fprintf(stderr, "\n%s: [0x%08lx]\n", msg, err); + } + else { + fprintf(stderr, "\n%s: [0x%08lx] %s\n", msg, err, buf); + } +} +#endif + +/*****************/ +/* OS Allocation */ +/*****************/ + +static void *zend_mm_mmap_fixed(void *addr, size_t size) +{ +#ifdef _WIN32 + return VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#else + /* MAP_FIXED leads to discarding of the old mapping, so it can't be used. */ + void *ptr = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON /*| MAP_POPULATE | MAP_HUGETLB*/, -1, 0); + + if (ptr == MAP_FAILED) { +#if ZEND_MM_ERROR + fprintf(stderr, "\nmmap() failed: [%d] %s\n", errno, strerror(errno)); +#endif + return NULL; + } else if (ptr != addr) { + if (munmap(ptr, size) != 0) { +#if ZEND_MM_ERROR + fprintf(stderr, "\nmunmap() failed: [%d] %s\n", errno, strerror(errno)); +#endif + } + return NULL; + } + return ptr; +#endif +} + +static void *zend_mm_mmap(size_t size) +{ +#ifdef _WIN32 + void *ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + if (ptr == NULL) { +#if ZEND_MM_ERROR + stderr_last_error("VirtualAlloc() failed"); +#endif + return NULL; + } + return ptr; +#else + void *ptr; + +#ifdef MAP_HUGETLB + if (size == ZEND_MM_CHUNK_SIZE) { + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); + if (ptr != MAP_FAILED) { + return ptr; + } + } +#endif + + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + + if (ptr == MAP_FAILED) { +#if ZEND_MM_ERROR + fprintf(stderr, "\nmmap() failed: [%d] %s\n", errno, strerror(errno)); +#endif + return NULL; + } + return ptr; +#endif +} + +static void zend_mm_munmap(void *addr, size_t size) +{ +#ifdef _WIN32 + if (VirtualFree(addr, 0, MEM_RELEASE) == 0) { +#if ZEND_MM_ERROR + stderr_last_error("VirtualFree() failed"); +#endif + } +#else + if (munmap(addr, size) != 0) { +#if ZEND_MM_ERROR + fprintf(stderr, "\nmunmap() failed: [%d] %s\n", errno, strerror(errno)); +#endif + } +#endif +} + +/***********/ +/* Bitmask */ +/***********/ + +/* number of trailing set (1) bits */ +static zend_always_inline int zend_mm_bitset_nts(zend_mm_bitset bitset) +{ +#if (defined(__GNUC__) || __has_builtin(__builtin_ctzl)) && SIZEOF_ZEND_LONG == SIZEOF_LONG && defined(PHP_HAVE_BUILTIN_CTZL) + return __builtin_ctzl(~bitset); +#elif (defined(__GNUC__) || __has_builtin(__builtin_ctzll)) && defined(PHP_HAVE_BUILTIN_CTZLL) + return __builtin_ctzll(~bitset); +#elif defined(_WIN32) + unsigned long index; + +#if defined(_WIN64) + if (!BitScanForward64(&index, ~bitset)) { +#else + if (!BitScanForward(&index, ~bitset)) { +#endif + /* undefined behavior */ + return 32; + } + + return (int)index; +#else + int n; + + if (bitset == (zend_mm_bitset)-1) return ZEND_MM_BITSET_LEN; + + n = 0; +#if SIZEOF_ZEND_LONG == 8 + if (sizeof(zend_mm_bitset) == 8) { + if ((bitset & 0xffffffff) == 0xffffffff) {n += 32; bitset = bitset >> Z_UL(32);} + } +#endif + if ((bitset & 0x0000ffff) == 0x0000ffff) {n += 16; bitset = bitset >> 16;} + if ((bitset & 0x000000ff) == 0x000000ff) {n += 8; bitset = bitset >> 8;} + if ((bitset & 0x0000000f) == 0x0000000f) {n += 4; bitset = bitset >> 4;} + if ((bitset & 0x00000003) == 0x00000003) {n += 2; bitset = bitset >> 2;} + return n + (bitset & 1); +#endif +} + +/* number of trailing zero bits (0x01 -> 1; 0x40 -> 6; 0x00 -> LEN) */ +static zend_always_inline int zend_mm_bitset_ntz(zend_mm_bitset bitset) +{ +#if (defined(__GNUC__) || __has_builtin(__builtin_ctzl)) && SIZEOF_ZEND_LONG == SIZEOF_LONG && defined(PHP_HAVE_BUILTIN_CTZL) + return __builtin_ctzl(bitset); +#elif (defined(__GNUC__) || __has_builtin(__builtin_ctzll)) && defined(PHP_HAVE_BUILTIN_CTZLL) + return __builtin_ctzll(bitset); +#elif defined(_WIN32) + unsigned long index; + +#if defined(_WIN64) + if (!BitScanForward64(&index, bitset)) { +#else + if (!BitScanForward(&index, bitset)) { +#endif + /* undefined behavior */ + return 32; + } + + return (int)index; +#else + int n; + + if (bitset == (zend_mm_bitset)0) return ZEND_MM_BITSET_LEN; + + n = 1; +#if SIZEOF_ZEND_LONG == 8 + if (sizeof(zend_mm_bitset) == 8) { + if ((bitset & 0xffffffff) == 0) {n += 32; bitset = bitset >> Z_UL(32);} + } +#endif + if ((bitset & 0x0000ffff) == 0) {n += 16; bitset = bitset >> 16;} + if ((bitset & 0x000000ff) == 0) {n += 8; bitset = bitset >> 8;} + if ((bitset & 0x0000000f) == 0) {n += 4; bitset = bitset >> 4;} + if ((bitset & 0x00000003) == 0) {n += 2; bitset = bitset >> 2;} + return n - (bitset & 1); +#endif +} + +static zend_always_inline int zend_mm_bitset_find_zero(zend_mm_bitset *bitset, int size) +{ + int i = 0; + + do { + zend_mm_bitset tmp = bitset[i]; + if (tmp != (zend_mm_bitset)-1) { + return i * ZEND_MM_BITSET_LEN + zend_mm_bitset_nts(tmp); + } + i++; + } while (i < size); + return -1; +} + +static zend_always_inline int zend_mm_bitset_find_one(zend_mm_bitset *bitset, int size) +{ + int i = 0; + + do { + zend_mm_bitset tmp = bitset[i]; + if (tmp != 0) { + return i * ZEND_MM_BITSET_LEN + zend_mm_bitset_ntz(tmp); + } + i++; + } while (i < size); + return -1; +} + +static zend_always_inline int zend_mm_bitset_find_zero_and_set(zend_mm_bitset *bitset, int size) +{ + int i = 0; + + do { + zend_mm_bitset tmp = bitset[i]; + if (tmp != (zend_mm_bitset)-1) { + int n = zend_mm_bitset_nts(tmp); + bitset[i] |= Z_UL(1) << n; + return i * ZEND_MM_BITSET_LEN + n; + } + i++; + } while (i < size); + return -1; +} + +static zend_always_inline int zend_mm_bitset_is_set(zend_mm_bitset *bitset, int bit) +{ + return (bitset[bit / ZEND_MM_BITSET_LEN] & (Z_L(1) << (bit & (ZEND_MM_BITSET_LEN-1)))) != 0; +} + +static zend_always_inline void zend_mm_bitset_set_bit(zend_mm_bitset *bitset, int bit) +{ + bitset[bit / ZEND_MM_BITSET_LEN] |= (Z_L(1) << (bit & (ZEND_MM_BITSET_LEN-1))); +} + +static zend_always_inline void zend_mm_bitset_reset_bit(zend_mm_bitset *bitset, int bit) +{ + bitset[bit / ZEND_MM_BITSET_LEN] &= ~(Z_L(1) << (bit & (ZEND_MM_BITSET_LEN-1))); +} + +static zend_always_inline void zend_mm_bitset_set_range(zend_mm_bitset *bitset, int start, int len) +{ + if (len == 1) { + zend_mm_bitset_set_bit(bitset, start); + } else { + int pos = start / ZEND_MM_BITSET_LEN; + int end = (start + len - 1) / ZEND_MM_BITSET_LEN; + int bit = start & (ZEND_MM_BITSET_LEN - 1); + zend_mm_bitset tmp; + + if (pos != end) { + /* set bits from "bit" to ZEND_MM_BITSET_LEN-1 */ + tmp = (zend_mm_bitset)-1 << bit; + bitset[pos++] |= tmp; + while (pos != end) { + /* set all bits */ + bitset[pos++] = (zend_mm_bitset)-1; + } + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* set bits from "0" to "end" */ + tmp = (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + bitset[pos] |= tmp; + } else { + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* set bits from "bit" to "end" */ + tmp = (zend_mm_bitset)-1 << bit; + tmp &= (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + bitset[pos] |= tmp; + } + } +} + +static zend_always_inline void zend_mm_bitset_reset_range(zend_mm_bitset *bitset, int start, int len) +{ + if (len == 1) { + zend_mm_bitset_reset_bit(bitset, start); + } else { + int pos = start / ZEND_MM_BITSET_LEN; + int end = (start + len - 1) / ZEND_MM_BITSET_LEN; + int bit = start & (ZEND_MM_BITSET_LEN - 1); + zend_mm_bitset tmp; + + if (pos != end) { + /* reset bits from "bit" to ZEND_MM_BITSET_LEN-1 */ + tmp = ~((Z_L(1) << bit) - 1); + bitset[pos++] &= ~tmp; + while (pos != end) { + /* set all bits */ + bitset[pos++] = 0; + } + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* reset bits from "0" to "end" */ + tmp = (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + bitset[pos] &= ~tmp; + } else { + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* reset bits from "bit" to "end" */ + tmp = (zend_mm_bitset)-1 << bit; + tmp &= (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + bitset[pos] &= ~tmp; + } + } +} + +static zend_always_inline int zend_mm_bitset_is_free_range(zend_mm_bitset *bitset, int start, int len) +{ + if (len == 1) { + return !zend_mm_bitset_is_set(bitset, start); + } else { + int pos = start / ZEND_MM_BITSET_LEN; + int end = (start + len - 1) / ZEND_MM_BITSET_LEN; + int bit = start & (ZEND_MM_BITSET_LEN - 1); + zend_mm_bitset tmp; + + if (pos != end) { + /* set bits from "bit" to ZEND_MM_BITSET_LEN-1 */ + tmp = (zend_mm_bitset)-1 << bit; + if ((bitset[pos++] & tmp) != 0) { + return 0; + } + while (pos != end) { + /* set all bits */ + if (bitset[pos++] != 0) { + return 0; + } + } + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* set bits from "0" to "end" */ + tmp = (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + return (bitset[pos] & tmp) == 0; + } else { + end = (start + len - 1) & (ZEND_MM_BITSET_LEN - 1); + /* set bits from "bit" to "end" */ + tmp = (zend_mm_bitset)-1 << bit; + tmp &= (zend_mm_bitset)-1 >> ((ZEND_MM_BITSET_LEN - 1) - end); + return (bitset[pos] & tmp) == 0; + } + } +} + +/**********/ +/* Chunks */ +/**********/ + +static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment) +{ + void *ptr = zend_mm_mmap(size); + + if (ptr == NULL) { + return NULL; + } else if (ZEND_MM_ALIGNED_OFFSET(ptr, alignment) == 0) { +#ifdef MADV_HUGEPAGE + madvise(ptr, size, MADV_HUGEPAGE); +#endif + return ptr; + } else { + size_t offset; + + /* chunk has to be aligned */ + zend_mm_munmap(ptr, size); + ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE); +#ifdef _WIN32 + offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment); + zend_mm_munmap(ptr, size + alignment - REAL_PAGE_SIZE); + ptr = zend_mm_mmap_fixed((void*)((char*)ptr + (alignment - offset)), size); + offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment); + if (offset != 0) { + zend_mm_munmap(ptr, size); + return NULL; + } + return ptr; +#else + offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment); + if (offset != 0) { + offset = alignment - offset; + zend_mm_munmap(ptr, offset); + ptr = (char*)ptr + offset; + alignment -= offset; + } + if (alignment > REAL_PAGE_SIZE) { + zend_mm_munmap((char*)ptr + size, alignment - REAL_PAGE_SIZE); + } +# ifdef MADV_HUGEPAGE + madvise(ptr, size, MADV_HUGEPAGE); +# endif +#endif + return ptr; + } +} + +static void *zend_mm_chunk_alloc(zend_mm_heap *heap, size_t size, size_t alignment) +{ +#if ZEND_MM_STORAGE + if (UNEXPECTED(heap->storage)) { + void *ptr = heap->storage->handlers.chunk_alloc(heap->storage, size, alignment); + ZEND_ASSERT(((zend_uintptr_t)((char*)ptr + (alignment-1)) & (alignment-1)) == (zend_uintptr_t)ptr); + return ptr; + } +#endif + return zend_mm_chunk_alloc_int(size, alignment); +} + +static void zend_mm_chunk_free(zend_mm_heap *heap, void *addr, size_t size) +{ +#if ZEND_MM_STORAGE + if (UNEXPECTED(heap->storage)) { + heap->storage->handlers.chunk_free(heap->storage, addr, size); + return; + } +#endif + zend_mm_munmap(addr, size); +} + +static int zend_mm_chunk_truncate(zend_mm_heap *heap, void *addr, size_t old_size, size_t new_size) +{ +#if ZEND_MM_STORAGE + if (UNEXPECTED(heap->storage)) { + if (heap->storage->handlers.chunk_truncate) { + return heap->storage->handlers.chunk_truncate(heap->storage, addr, old_size, new_size); + } else { + return 0; + } + } +#endif +#ifndef _WIN32 + zend_mm_munmap((char*)addr + new_size, old_size - new_size); + return 1; +#else + return 0; +#endif +} + +static int zend_mm_chunk_extend(zend_mm_heap *heap, void *addr, size_t old_size, size_t new_size) +{ +#if ZEND_MM_STORAGE + if (UNEXPECTED(heap->storage)) { + if (heap->storage->handlers.chunk_extend) { + return heap->storage->handlers.chunk_extend(heap->storage, addr, old_size, new_size); + } else { + return 0; + } + } +#endif +#ifndef _WIN32 + return (zend_mm_mmap_fixed((char*)addr + old_size, new_size - old_size) != NULL); +#else + return 0; +#endif +} + +static zend_always_inline void zend_mm_chunk_init(zend_mm_heap *heap, zend_mm_chunk *chunk) +{ + chunk->heap = heap; + chunk->next = heap->main_chunk; + chunk->prev = heap->main_chunk->prev; + chunk->prev->next = chunk; + chunk->next->prev = chunk; + /* mark first pages as allocated */ + chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; + chunk->free_tail = ZEND_MM_FIRST_PAGE; + /* the younger chunks have bigger number */ + chunk->num = chunk->prev->num + 1; + /* mark first pages as allocated */ + chunk->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; + chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); +} + +/***********************/ +/* Huge Runs (forward) */ +/***********************/ + +static size_t zend_mm_get_huge_block_size(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); +static void *zend_mm_alloc_huge(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); +static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + +#if ZEND_DEBUG +static void zend_mm_change_huge_block_size(zend_mm_heap *heap, void *ptr, size_t size, size_t dbg_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); +#else +static void zend_mm_change_huge_block_size(zend_mm_heap *heap, void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); +#endif + +/**************/ +/* Large Runs */ +/**************/ + +#if ZEND_DEBUG +static void *zend_mm_alloc_pages(zend_mm_heap *heap, int pages_count, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#else +static void *zend_mm_alloc_pages(zend_mm_heap *heap, int pages_count ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#endif +{ + zend_mm_chunk *chunk = heap->main_chunk; + int page_num, len; + + while (1) { + if (UNEXPECTED(chunk->free_pages < pages_count)) { + goto not_found; +#if 0 + } else if (UNEXPECTED(chunk->free_pages + chunk->free_tail == ZEND_MM_PAGES)) { + if (UNEXPECTED(ZEND_MM_PAGES - chunk->free_tail < pages_count)) { + goto not_found; + } else { + page_num = chunk->free_tail; + goto found; + } + } else if (0) { + /* First-Fit Search */ + int free_tail = chunk->free_tail; + zend_mm_bitset *bitset = chunk->free_map; + zend_mm_bitset tmp = *(bitset++); + int i = 0; + + while (1) { + /* skip allocated blocks */ + while (tmp == (zend_mm_bitset)-1) { + i += ZEND_MM_BITSET_LEN; + if (i == ZEND_MM_PAGES) { + goto not_found; + } + tmp = *(bitset++); + } + /* find first 0 bit */ + page_num = i + zend_mm_bitset_nts(tmp); + /* reset bits from 0 to "bit" */ + tmp &= tmp + 1; + /* skip free blocks */ + while (tmp == 0) { + i += ZEND_MM_BITSET_LEN; + len = i - page_num; + if (len >= pages_count) { + goto found; + } else if (i >= free_tail) { + goto not_found; + } + tmp = *(bitset++); + } + /* find first 1 bit */ + len = (i + zend_mm_bitset_ntz(tmp)) - page_num; + if (len >= pages_count) { + goto found; + } + /* set bits from 0 to "bit" */ + tmp |= tmp - 1; + } +#endif + } else { + /* Best-Fit Search */ + int best = -1; + int best_len = ZEND_MM_PAGES; + int free_tail = chunk->free_tail; + zend_mm_bitset *bitset = chunk->free_map; + zend_mm_bitset tmp = *(bitset++); + int i = 0; + + while (1) { + /* skip allocated blocks */ + while (tmp == (zend_mm_bitset)-1) { + i += ZEND_MM_BITSET_LEN; + if (i == ZEND_MM_PAGES) { + if (best > 0) { + page_num = best; + goto found; + } else { + goto not_found; + } + } + tmp = *(bitset++); + } + /* find first 0 bit */ + page_num = i + zend_mm_bitset_nts(tmp); + /* reset bits from 0 to "bit" */ + tmp &= tmp + 1; + /* skip free blocks */ + while (tmp == 0) { + i += ZEND_MM_BITSET_LEN; + if (i >= free_tail || i == ZEND_MM_PAGES) { + len = ZEND_MM_PAGES - page_num; + if (len >= pages_count && len < best_len) { + chunk->free_tail = page_num + pages_count; + goto found; + } else { + /* set accurate value */ + chunk->free_tail = page_num; + if (best > 0) { + page_num = best; + goto found; + } else { + goto not_found; + } + } + } + tmp = *(bitset++); + } + /* find first 1 bit */ + len = i + zend_mm_bitset_ntz(tmp) - page_num; + if (len >= pages_count) { + if (len == pages_count) { + goto found; + } else if (len < best_len) { + best_len = len; + best = page_num; + } + } + /* set bits from 0 to "bit" */ + tmp |= tmp - 1; + } + } + +not_found: + if (chunk->next == heap->main_chunk) { +get_chunk: + if (heap->cached_chunks) { + heap->cached_chunks_count--; + chunk = heap->cached_chunks; + heap->cached_chunks = chunk->next; + } else { +#if ZEND_MM_LIMIT + if (UNEXPECTED(heap->real_size + ZEND_MM_CHUNK_SIZE > heap->limit)) { + if (zend_mm_gc(heap)) { + goto get_chunk; + } else if (heap->overflow == 0) { +#if ZEND_DEBUG + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted at %s:%d (tried to allocate %zu bytes)", heap->limit, __zend_filename, __zend_lineno, size); +#else + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted (tried to allocate %zu bytes)", heap->limit, ZEND_MM_PAGE_SIZE * pages_count); +#endif + return NULL; + } + } +#endif + chunk = (zend_mm_chunk*)zend_mm_chunk_alloc(heap, ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE); + if (UNEXPECTED(chunk == NULL)) { + /* insufficient memory */ + if (zend_mm_gc(heap) && + (chunk = (zend_mm_chunk*)zend_mm_chunk_alloc(heap, ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE)) != NULL) { + /* pass */ + } else { +#if !ZEND_MM_LIMIT + zend_mm_safe_error(heap, "Out of memory"); +#elif ZEND_DEBUG + zend_mm_safe_error(heap, "Out of memory (allocated %zu) at %s:%d (tried to allocate %zu bytes)", heap->real_size, __zend_filename, __zend_lineno, size); +#else + zend_mm_safe_error(heap, "Out of memory (allocated %zu) (tried to allocate %zu bytes)", heap->real_size, ZEND_MM_PAGE_SIZE * pages_count); +#endif + return NULL; + } + } +#if ZEND_MM_STAT + do { + size_t size = heap->real_size + ZEND_MM_CHUNK_SIZE; + size_t peak = MAX(heap->real_peak, size); + heap->real_size = size; + heap->real_peak = peak; + } while (0); +#elif ZEND_MM_LIMIT + heap->real_size += ZEND_MM_CHUNK_SIZE; + +#endif + } + heap->chunks_count++; + if (heap->chunks_count > heap->peak_chunks_count) { + heap->peak_chunks_count = heap->chunks_count; + } + zend_mm_chunk_init(heap, chunk); + page_num = ZEND_MM_FIRST_PAGE; + len = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; + goto found; + } else { + chunk = chunk->next; + } + } + +found: + /* mark run as allocated */ + chunk->free_pages -= pages_count; + zend_mm_bitset_set_range(chunk->free_map, page_num, pages_count); + chunk->map[page_num] = ZEND_MM_LRUN(pages_count); + if (page_num == chunk->free_tail) { + chunk->free_tail = page_num + pages_count; + } + return ZEND_MM_PAGE_ADDR(chunk, page_num); +} + +static zend_always_inline void *zend_mm_alloc_large(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + int pages_count = (int)ZEND_MM_SIZE_TO_NUM(size, ZEND_MM_PAGE_SIZE); +#if ZEND_DEBUG + void *ptr = zend_mm_alloc_pages(heap, pages_count, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + void *ptr = zend_mm_alloc_pages(heap, pages_count ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif +#if ZEND_MM_STAT + do { + size_t size = heap->size + pages_count * ZEND_MM_PAGE_SIZE; + size_t peak = MAX(heap->peak, size); + heap->size = size; + heap->peak = peak; + } while (0); +#endif + return ptr; +} + +static zend_always_inline void zend_mm_delete_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk) +{ + chunk->next->prev = chunk->prev; + chunk->prev->next = chunk->next; + heap->chunks_count--; + if (heap->chunks_count + heap->cached_chunks_count < heap->avg_chunks_count + 0.1) { + /* delay deletion */ + heap->cached_chunks_count++; + chunk->next = heap->cached_chunks; + heap->cached_chunks = chunk; + } else { +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size -= ZEND_MM_CHUNK_SIZE; +#endif + if (!heap->cached_chunks || chunk->num > heap->cached_chunks->num) { + zend_mm_chunk_free(heap, chunk, ZEND_MM_CHUNK_SIZE); + } else { +//TODO: select the best chunk to delete??? + chunk->next = heap->cached_chunks->next; + zend_mm_chunk_free(heap, heap->cached_chunks, ZEND_MM_CHUNK_SIZE); + heap->cached_chunks = chunk; + } + } +} + +static zend_always_inline void zend_mm_free_pages_ex(zend_mm_heap *heap, zend_mm_chunk *chunk, int page_num, int pages_count, int free_chunk) +{ + chunk->free_pages += pages_count; + zend_mm_bitset_reset_range(chunk->free_map, page_num, pages_count); + chunk->map[page_num] = 0; + if (chunk->free_tail == page_num + pages_count) { + /* this setting may be not accurate */ + chunk->free_tail = page_num; + } + if (free_chunk && chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE) { + zend_mm_delete_chunk(heap, chunk); + } +} + +static void zend_mm_free_pages(zend_mm_heap *heap, zend_mm_chunk *chunk, int page_num, int pages_count) +{ + zend_mm_free_pages_ex(heap, chunk, page_num, pages_count, 1); +} + +static zend_always_inline void zend_mm_free_large(zend_mm_heap *heap, zend_mm_chunk *chunk, int page_num, int pages_count) +{ +#if ZEND_MM_STAT + heap->size -= pages_count * ZEND_MM_PAGE_SIZE; +#endif + zend_mm_free_pages(heap, chunk, page_num, pages_count); +} + +/**************/ +/* Small Runs */ +/**************/ + +/* higher set bit number (0->N/A, 1->1, 2->2, 4->3, 8->4, 127->7, 128->8 etc) */ +static zend_always_inline int zend_mm_small_size_to_bit(int size) +{ +#if (defined(__GNUC__) || __has_builtin(__builtin_clz)) && defined(PHP_HAVE_BUILTIN_CLZ) + return (__builtin_clz(size) ^ 0x1f) + 1; +#elif defined(_WIN32) + unsigned long index; + + if (!BitScanReverse(&index, (unsigned long)size)) { + /* undefined behavior */ + return 64; + } + + return (((31 - (int)index) ^ 0x1f) + 1); +#else + int n = 16; + if (size <= 0x00ff) {n -= 8; size = size << 8;} + if (size <= 0x0fff) {n -= 4; size = size << 4;} + if (size <= 0x3fff) {n -= 2; size = size << 2;} + if (size <= 0x7fff) {n -= 1;} + return n; +#endif +} + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +static zend_always_inline int zend_mm_small_size_to_bin(size_t size) +{ +#if 0 + int n; + /*0, 1, 2, 3, 4, 5, 6, 7, 8, 9 10, 11, 12*/ + static const int f1[] = { 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9}; + static const int f2[] = { 0, 0, 0, 0, 0, 0, 0, 4, 8, 12, 16, 20, 24}; + + if (UNEXPECTED(size <= 2)) return 0; + n = zend_mm_small_size_to_bit(size - 1); + return ((size-1) >> f1[n]) + f2[n]; +#else + unsigned int t1, t2; + + if (size <= 64) { + /* we need to support size == 0 ... */ + return (size - !!size) >> 3; + } else { + t1 = size - 1; + t2 = zend_mm_small_size_to_bit(t1) - 3; + t1 = t1 >> t2; + t2 = t2 - 3; + t2 = t2 << 2; + return (int)(t1 + t2); + } +#endif +} + +#define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size) + +static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + zend_mm_chunk *chunk; + int page_num; + zend_mm_bin *bin; + zend_mm_free_slot *p, *end; + +#if ZEND_DEBUG + bin = (zend_mm_bin*)zend_mm_alloc_pages(heap, bin_pages[bin_num], bin_data_size[bin_num] ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + bin = (zend_mm_bin*)zend_mm_alloc_pages(heap, bin_pages[bin_num] ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif + if (UNEXPECTED(bin == NULL)) { + /* insufficient memory */ + return NULL; + } + + chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(bin, ZEND_MM_CHUNK_SIZE); + page_num = ZEND_MM_ALIGNED_OFFSET(bin, ZEND_MM_CHUNK_SIZE) / ZEND_MM_PAGE_SIZE; + chunk->map[page_num] = ZEND_MM_SRUN(bin_num); + if (bin_pages[bin_num] > 1) { + int i = 1; + do { + chunk->map[page_num+i] = ZEND_MM_NRUN(bin_num, i); + i++; + } while (i < bin_pages[bin_num]); + } + + /* create a linked list of elements from 1 to last */ + end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1))); + heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]); + do { + p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);; +#if ZEND_DEBUG + do { + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + dbg->size = 0; + } while (0); +#endif + p = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]); + } while (p != end); + + /* terminate list using NULL */ + p->next_free_slot = NULL; +#if ZEND_DEBUG + do { + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + dbg->size = 0; + } while (0); +#endif + + /* return first element */ + return (char*)bin; +} + +static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, size_t size, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ +#if ZEND_MM_STAT + do { + size_t size = heap->size + bin_data_size[bin_num]; + size_t peak = MAX(heap->peak, size); + heap->size = size; + heap->peak = peak; + } while (0); +#endif + + if (EXPECTED(heap->free_slot[bin_num] != NULL)) { + zend_mm_free_slot *p = heap->free_slot[bin_num]; + heap->free_slot[bin_num] = p->next_free_slot; + return (void*)p; + } else { + return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } +} + +static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, int bin_num) +{ + zend_mm_free_slot *p; + +#if ZEND_MM_STAT + heap->size -= bin_data_size[bin_num]; +#endif + +#if ZEND_DEBUG + do { + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)ptr + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + dbg->size = 0; + } while (0); +#endif + + p = (zend_mm_free_slot*)ptr; + p->next_free_slot = heap->free_slot[bin_num]; + heap->free_slot[bin_num] = p; +} + +/********/ +/* Heap */ +/********/ + +#if ZEND_DEBUG +static zend_always_inline zend_mm_debug_info *zend_mm_get_debug_info(zend_mm_heap *heap, void *ptr) +{ + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + zend_mm_chunk *chunk; + int page_num; + zend_mm_page_info info; + + ZEND_MM_CHECK(page_offset != 0, "zend_mm_heap corrupted"); + chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + info = chunk->map[page_num]; + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + if (EXPECTED(info & ZEND_MM_IS_SRUN)) { + int bin_num = ZEND_MM_SRUN_BIN_NUM(info); + return (zend_mm_debug_info*)((char*)ptr + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + } else /* if (info & ZEND_MM_IS_LRUN) */ { + int pages_count = ZEND_MM_LRUN_PAGES(info); + + return (zend_mm_debug_info*)((char*)ptr + ZEND_MM_PAGE_SIZE * pages_count - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + } +} +#endif + +static zend_always_inline void *zend_mm_alloc_heap(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + void *ptr; +#if ZEND_DEBUG + size_t real_size = size; + zend_mm_debug_info *dbg; + + /* special handling for zero-size allocation */ + size = MAX(size, 1); + size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); +#endif + if (size <= ZEND_MM_MAX_SMALL_SIZE) { + ptr = zend_mm_alloc_small(heap, size, ZEND_MM_SMALL_SIZE_TO_BIN(size) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } else if (size <= ZEND_MM_MAX_LARGE_SIZE) { + ptr = zend_mm_alloc_large(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } else { +#if ZEND_DEBUG + size = real_size; +#endif + return zend_mm_alloc_huge(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } +} + +static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + + if (UNEXPECTED(page_offset == 0)) { + if (ptr != NULL) { + zend_mm_free_huge(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } + } else { + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + zend_mm_page_info info = chunk->map[page_num]; + + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + if (EXPECTED(info & ZEND_MM_IS_SRUN)) { + zend_mm_free_small(heap, ptr, ZEND_MM_SRUN_BIN_NUM(info)); + } else /* if (info & ZEND_MM_IS_LRUN) */ { + int pages_count = ZEND_MM_LRUN_PAGES(info); + + ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); + zend_mm_free_large(heap, chunk, page_num, pages_count); + } + } +} + +static size_t zend_mm_size(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + + if (UNEXPECTED(page_offset == 0)) { + return zend_mm_get_huge_block_size(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + zend_mm_chunk *chunk; +#if 0 && ZEND_DEBUG + zend_mm_debug_info *dbg = zend_mm_get_debug_info(heap, ptr); + return dbg->size; +#else + int page_num; + zend_mm_page_info info; + + chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + info = chunk->map[page_num]; + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + if (EXPECTED(info & ZEND_MM_IS_SRUN)) { + return bin_data_size[ZEND_MM_SRUN_BIN_NUM(info)]; + } else /* if (info & ZEND_MM_IS_LARGE_RUN) */ { + return ZEND_MM_LRUN_PAGES(info) * ZEND_MM_PAGE_SIZE; + } +#endif + } +} + +static void *zend_mm_realloc_heap(zend_mm_heap *heap, void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t page_offset; + size_t old_size; + size_t new_size; + void *ret; +#if ZEND_DEBUG + size_t real_size; + zend_mm_debug_info *dbg; +#endif + + page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + if (UNEXPECTED(page_offset == 0)) { + if (UNEXPECTED(ptr == NULL)) { + return zend_mm_alloc_heap(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } + old_size = zend_mm_get_huge_block_size(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#if ZEND_DEBUG + real_size = size; + size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); +#endif + if (size > ZEND_MM_MAX_LARGE_SIZE) { +#if ZEND_DEBUG + size = real_size; +#endif +#ifdef ZEND_WIN32 + /* On Windows we don't have ability to extend huge blocks in-place. + * We allocate them with 2MB size granularity, to avoid many + * reallocations when they are extended by small pieces + */ + new_size = ZEND_MM_ALIGNED_SIZE_EX(size, MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE)); +#else + new_size = ZEND_MM_ALIGNED_SIZE_EX(size, REAL_PAGE_SIZE); +#endif + if (new_size == old_size) { +#if ZEND_DEBUG + zend_mm_change_huge_block_size(heap, ptr, new_size, real_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + zend_mm_change_huge_block_size(heap, ptr, new_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif + return ptr; + } else if (new_size < old_size) { + /* unmup tail */ + if (zend_mm_chunk_truncate(heap, ptr, old_size, new_size)) { +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size -= old_size - new_size; +#endif +#if ZEND_MM_STAT + heap->size -= old_size - new_size; +#endif +#if ZEND_DEBUG + zend_mm_change_huge_block_size(heap, ptr, new_size, real_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + zend_mm_change_huge_block_size(heap, ptr, new_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif + return ptr; + } + } else /* if (new_size > old_size) */ { +#if ZEND_MM_LIMIT + if (UNEXPECTED(heap->real_size + (new_size - old_size) > heap->limit)) { + if (zend_mm_gc(heap) && heap->real_size + (new_size - old_size) <= heap->limit) { + /* pass */ + } else if (heap->overflow == 0) { +#if ZEND_DEBUG + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted at %s:%d (tried to allocate %zu bytes)", heap->limit, __zend_filename, __zend_lineno, size); +#else + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted (tried to allocate %zu bytes)", heap->limit, size); +#endif + return NULL; + } + } +#endif + /* try to map tail right after this block */ + if (zend_mm_chunk_extend(heap, ptr, old_size, new_size)) { +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size += new_size - old_size; +#endif +#if ZEND_MM_STAT + heap->real_peak = MAX(heap->real_peak, heap->real_size); + heap->size += new_size - old_size; + heap->peak = MAX(heap->peak, heap->size); +#endif +#if ZEND_DEBUG + zend_mm_change_huge_block_size(heap, ptr, new_size, real_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + zend_mm_change_huge_block_size(heap, ptr, new_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif + return ptr; + } + } + } + } else { + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + zend_mm_page_info info = chunk->map[page_num]; +#if ZEND_DEBUG + size_t real_size = size; + + size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); +#endif + + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + if (info & ZEND_MM_IS_SRUN) { + int old_bin_num, bin_num; + + old_bin_num = ZEND_MM_SRUN_BIN_NUM(info); + old_size = bin_data_size[old_bin_num]; + bin_num = ZEND_MM_SMALL_SIZE_TO_BIN(size); + if (old_bin_num == bin_num) { +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } + } else /* if (info & ZEND_MM_IS_LARGE_RUN) */ { + ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); + old_size = ZEND_MM_LRUN_PAGES(info) * ZEND_MM_PAGE_SIZE; + if (size > ZEND_MM_MAX_SMALL_SIZE && size <= ZEND_MM_MAX_LARGE_SIZE) { + new_size = ZEND_MM_ALIGNED_SIZE_EX(size, ZEND_MM_PAGE_SIZE); + if (new_size == old_size) { +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } else if (new_size < old_size) { + /* free tail pages */ + int new_pages_count = (int)(new_size / ZEND_MM_PAGE_SIZE); + int rest_pages_count = (int)((old_size - new_size) / ZEND_MM_PAGE_SIZE); + +#if ZEND_MM_STAT + heap->size -= rest_pages_count * ZEND_MM_PAGE_SIZE; +#endif + chunk->map[page_num] = ZEND_MM_LRUN(new_pages_count); + chunk->free_pages += rest_pages_count; + zend_mm_bitset_reset_range(chunk->free_map, page_num + new_pages_count, rest_pages_count); +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } else /* if (new_size > old_size) */ { + int new_pages_count = (int)(new_size / ZEND_MM_PAGE_SIZE); + int old_pages_count = (int)(old_size / ZEND_MM_PAGE_SIZE); + + /* try to allocate tail pages after this block */ + if (page_num + new_pages_count <= ZEND_MM_PAGES && + zend_mm_bitset_is_free_range(chunk->free_map, page_num + old_pages_count, new_pages_count - old_pages_count)) { +#if ZEND_MM_STAT + do { + size_t size = heap->size + (new_size - old_size); + size_t peak = MAX(heap->peak, size); + heap->size = size; + heap->peak = peak; + } while (0); +#endif + chunk->free_pages -= new_pages_count - old_pages_count; + zend_mm_bitset_set_range(chunk->free_map, page_num + old_pages_count, new_pages_count - old_pages_count); + chunk->map[page_num] = ZEND_MM_LRUN(new_pages_count); +#if ZEND_DEBUG + dbg = zend_mm_get_debug_info(heap, ptr); + dbg->size = real_size; + dbg->filename = __zend_filename; + dbg->orig_filename = __zend_orig_filename; + dbg->lineno = __zend_lineno; + dbg->orig_lineno = __zend_orig_lineno; +#endif + return ptr; + } + } + } + } +#if ZEND_DEBUG + size = real_size; +#endif + } + + /* Naive reallocation */ +#if ZEND_MM_STAT + do { + size_t orig_peak = heap->peak; + size_t orig_real_peak = heap->real_peak; +#endif + ret = zend_mm_alloc_heap(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + memcpy(ret, ptr, MIN(old_size, copy_size)); + zend_mm_free_heap(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#if ZEND_MM_STAT + heap->peak = MAX(orig_peak, heap->size); + heap->real_peak = MAX(orig_real_peak, heap->real_size); + } while (0); +#endif + return ret; +} + +/*********************/ +/* Huge Runs (again) */ +/*********************/ + +#if ZEND_DEBUG +static void zend_mm_add_huge_block(zend_mm_heap *heap, void *ptr, size_t size, size_t dbg_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#else +static void zend_mm_add_huge_block(zend_mm_heap *heap, void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#endif +{ + zend_mm_huge_list *list = (zend_mm_huge_list*)zend_mm_alloc_heap(heap, sizeof(zend_mm_huge_list) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + list->ptr = ptr; + list->size = size; + list->next = heap->huge_list; +#if ZEND_DEBUG + list->dbg.size = dbg_size; + list->dbg.filename = __zend_filename; + list->dbg.orig_filename = __zend_orig_filename; + list->dbg.lineno = __zend_lineno; + list->dbg.orig_lineno = __zend_orig_lineno; +#endif + heap->huge_list = list; +} + +static size_t zend_mm_del_huge_block(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + zend_mm_huge_list *prev = NULL; + zend_mm_huge_list *list = heap->huge_list; + while (list != NULL) { + if (list->ptr == ptr) { + size_t size; + + if (prev) { + prev->next = list->next; + } else { + heap->huge_list = list->next; + } + size = list->size; + zend_mm_free_heap(heap, list ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + return size; + } + prev = list; + list = list->next; + } + ZEND_MM_CHECK(0, "zend_mm_heap corrupted"); + return 0; +} + +static size_t zend_mm_get_huge_block_size(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + zend_mm_huge_list *list = heap->huge_list; + while (list != NULL) { + if (list->ptr == ptr) { + return list->size; + } + list = list->next; + } + ZEND_MM_CHECK(0, "zend_mm_heap corrupted"); + return 0; +} + +#if ZEND_DEBUG +static void zend_mm_change_huge_block_size(zend_mm_heap *heap, void *ptr, size_t size, size_t dbg_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#else +static void zend_mm_change_huge_block_size(zend_mm_heap *heap, void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +#endif +{ + zend_mm_huge_list *list = heap->huge_list; + while (list != NULL) { + if (list->ptr == ptr) { + list->size = size; +#if ZEND_DEBUG + list->dbg.size = dbg_size; + list->dbg.filename = __zend_filename; + list->dbg.orig_filename = __zend_orig_filename; + list->dbg.lineno = __zend_lineno; + list->dbg.orig_lineno = __zend_orig_lineno; +#endif + return; + } + list = list->next; + } +} + +static void *zend_mm_alloc_huge(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ +#ifdef ZEND_WIN32 + /* On Windows we don't have ability to extend huge blocks in-place. + * We allocate them with 2MB size granularity, to avoid many + * reallocations when they are extended by small pieces + */ + size_t new_size = ZEND_MM_ALIGNED_SIZE_EX(size, MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE)); +#else + size_t new_size = ZEND_MM_ALIGNED_SIZE_EX(size, REAL_PAGE_SIZE); +#endif + void *ptr; + +#if ZEND_MM_LIMIT + if (UNEXPECTED(heap->real_size + new_size > heap->limit)) { + if (zend_mm_gc(heap) && heap->real_size + new_size <= heap->limit) { + /* pass */ + } else if (heap->overflow == 0) { +#if ZEND_DEBUG + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted at %s:%d (tried to allocate %zu bytes)", heap->limit, __zend_filename, __zend_lineno, size); +#else + zend_mm_safe_error(heap, "Allowed memory size of %zu bytes exhausted (tried to allocate %zu bytes)", heap->limit, size); +#endif + return NULL; + } + } +#endif + ptr = zend_mm_chunk_alloc(heap, new_size, ZEND_MM_CHUNK_SIZE); + if (UNEXPECTED(ptr == NULL)) { + /* insufficient memory */ + if (zend_mm_gc(heap) && + (ptr = zend_mm_chunk_alloc(heap, new_size, ZEND_MM_CHUNK_SIZE)) != NULL) { + /* pass */ + } else { +#if !ZEND_MM_LIMIT + zend_mm_safe_error(heap, "Out of memory"); +#elif ZEND_DEBUG + zend_mm_safe_error(heap, "Out of memory (allocated %zu) at %s:%d (tried to allocate %zu bytes)", heap->real_size, __zend_filename, __zend_lineno, size); +#else + zend_mm_safe_error(heap, "Out of memory (allocated %zu) (tried to allocate %zu bytes)", heap->real_size, size); +#endif + return NULL; + } + } +#if ZEND_DEBUG + zend_mm_add_huge_block(heap, ptr, new_size, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#else + zend_mm_add_huge_block(heap, ptr, new_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +#endif +#if ZEND_MM_STAT + do { + size_t size = heap->real_size + new_size; + size_t peak = MAX(heap->real_peak, size); + heap->real_size = size; + heap->real_peak = peak; + } while (0); + do { + size_t size = heap->size + new_size; + size_t peak = MAX(heap->peak, size); + heap->size = size; + heap->peak = peak; + } while (0); +#elif ZEND_MM_LIMIT + heap->real_size += new_size; +#endif + return ptr; +} + +static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t size; + + ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE) == 0, "zend_mm_heap corrupted"); + size = zend_mm_del_huge_block(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + zend_mm_chunk_free(heap, ptr, size); +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size -= size; +#endif +#if ZEND_MM_STAT + heap->size -= size; +#endif +} + +/******************/ +/* Initialization */ +/******************/ + +static zend_mm_heap *zend_mm_init(void) +{ + zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE); + zend_mm_heap *heap; + + if (UNEXPECTED(chunk == NULL)) { +#if ZEND_MM_ERROR +#ifdef _WIN32 + stderr_last_error("Can't initialize heap"); +#else + fprintf(stderr, "\nCan't initialize heap: [%d] %s\n", errno, strerror(errno)); +#endif +#endif + return NULL; + } + heap = &chunk->heap_slot; + chunk->heap = heap; + chunk->next = chunk; + chunk->prev = chunk; + chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; + chunk->free_tail = ZEND_MM_FIRST_PAGE; + chunk->num = 0; + chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1; + chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); + heap->main_chunk = chunk; + heap->cached_chunks = NULL; + heap->chunks_count = 1; + heap->peak_chunks_count = 1; + heap->cached_chunks_count = 0; + heap->avg_chunks_count = 1.0; +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size = ZEND_MM_CHUNK_SIZE; +#endif +#if ZEND_MM_STAT + heap->real_peak = ZEND_MM_CHUNK_SIZE; + heap->size = 0; + heap->peak = 0; +#endif +#if ZEND_MM_LIMIT + heap->limit = (Z_L(-1) >> Z_L(1)); + heap->overflow = 0; +#endif +#if ZEND_MM_CUSTOM + heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE; +#endif +#if ZEND_MM_STORAGE + heap->storage = NULL; +#endif + heap->huge_list = NULL; + return heap; +} + +ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) +{ + zend_mm_free_slot *p, **q; + zend_mm_chunk *chunk; + size_t page_offset; + int page_num; + zend_mm_page_info info; + int i, has_free_pages, free_counter; + size_t collected = 0; + +#if ZEND_MM_CUSTOM + if (heap->use_custom_heap) { + return 0; + } +#endif + + for (i = 0; i < ZEND_MM_BINS; i++) { + has_free_pages = 0; + p = heap->free_slot[i]; + while (p != NULL) { + chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE); + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + page_offset = ZEND_MM_ALIGNED_OFFSET(p, ZEND_MM_CHUNK_SIZE); + ZEND_ASSERT(page_offset != 0); + page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + info = chunk->map[page_num]; + ZEND_ASSERT(info & ZEND_MM_IS_SRUN); + if (info & ZEND_MM_IS_LRUN) { + page_num -= ZEND_MM_NRUN_OFFSET(info); + info = chunk->map[page_num]; + ZEND_ASSERT(info & ZEND_MM_IS_SRUN); + ZEND_ASSERT(!(info & ZEND_MM_IS_LRUN)); + } + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i); + free_counter = ZEND_MM_SRUN_FREE_COUNTER(info) + 1; + if (free_counter == bin_elements[i]) { + has_free_pages = 1; + } + chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter);; + p = p->next_free_slot; + } + + if (!has_free_pages) { + continue; + } + + q = &heap->free_slot[i]; + p = *q; + while (p != NULL) { + chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE); + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); + page_offset = ZEND_MM_ALIGNED_OFFSET(p, ZEND_MM_CHUNK_SIZE); + ZEND_ASSERT(page_offset != 0); + page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); + info = chunk->map[page_num]; + ZEND_ASSERT(info & ZEND_MM_IS_SRUN); + if (info & ZEND_MM_IS_LRUN) { + page_num -= ZEND_MM_NRUN_OFFSET(info); + info = chunk->map[page_num]; + ZEND_ASSERT(info & ZEND_MM_IS_SRUN); + ZEND_ASSERT(!(info & ZEND_MM_IS_LRUN)); + } + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i); + if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) { + /* remove from cache */ + p = p->next_free_slot;; + *q = p; + } else { + q = &p->next_free_slot; + p = *q; + } + } + } + + chunk = heap->main_chunk; + do { + i = ZEND_MM_FIRST_PAGE; + while (i < chunk->free_tail) { + if (zend_mm_bitset_is_set(chunk->free_map, i)) { + info = chunk->map[i]; + if (info & ZEND_MM_IS_SRUN) { + int bin_num = ZEND_MM_SRUN_BIN_NUM(info); + int pages_count = bin_pages[bin_num]; + + if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[bin_num]) { + /* all elemens are free */ + zend_mm_free_pages_ex(heap, chunk, i, pages_count, 0); + collected += pages_count; + } else { + /* reset counter */ + chunk->map[i] = ZEND_MM_SRUN(bin_num); + } + i += bin_pages[bin_num]; + } else /* if (info & ZEND_MM_IS_LRUN) */ { + i += ZEND_MM_LRUN_PAGES(info); + } + } else { + i++; + } + } + if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE) { + zend_mm_chunk *next_chunk = chunk->next; + + zend_mm_delete_chunk(heap, chunk); + chunk = next_chunk; + } else { + chunk = chunk->next; + } + } while (chunk != heap->main_chunk); + + return collected * ZEND_MM_PAGE_SIZE; +} + +#if ZEND_DEBUG +/******************/ +/* Leak detection */ +/******************/ + +static zend_long zend_mm_find_leaks_small(zend_mm_chunk *p, int i, int j, zend_leak_info *leak) +{ + int empty = 1; + zend_long count = 0; + int bin_num = ZEND_MM_SRUN_BIN_NUM(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] * (j + 1) - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + while (j < bin_elements[bin_num]) { + if (dbg->size != 0) { + if (dbg->filename == leak->filename && dbg->lineno == leak->lineno) { + count++; + dbg->size = 0; + dbg->filename = NULL; + dbg->lineno = 0; + } else { + empty = 0; + } + } + j++; + dbg = (zend_mm_debug_info*)((char*)dbg + bin_data_size[bin_num]); + } + if (empty) { + zend_mm_bitset_reset_range(p->free_map, i, bin_pages[bin_num]); + } + return count; +} + +static zend_long zend_mm_find_leaks(zend_mm_heap *heap, zend_mm_chunk *p, int i, zend_leak_info *leak) +{ + zend_long count = 0; + + do { + while (i < p->free_tail) { + if (zend_mm_bitset_is_set(p->free_map, i)) { + if (p->map[i] & ZEND_MM_IS_SRUN) { + int bin_num = ZEND_MM_SRUN_BIN_NUM(p->map[i]); + count += zend_mm_find_leaks_small(p, i, 0, leak); + i += bin_pages[bin_num]; + } else /* if (p->map[i] & ZEND_MM_IS_LRUN) */ { + int pages_count = ZEND_MM_LRUN_PAGES(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * (i + pages_count) - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + if (dbg->filename == leak->filename && dbg->lineno == leak->lineno) { + count++; + } + zend_mm_bitset_reset_range(p->free_map, i, pages_count); + i += pages_count; + } + } else { + i++; + } + } + p = p->next; + } while (p != heap->main_chunk); + return count; +} + +static zend_long zend_mm_find_leaks_huge(zend_mm_heap *heap, zend_mm_huge_list *list) +{ + zend_long count = 0; + zend_mm_huge_list *prev = list; + zend_mm_huge_list *p = list->next; + + while (p) { + if (p->dbg.filename == list->dbg.filename && p->dbg.lineno == list->dbg.lineno) { + prev->next = p->next; + zend_mm_chunk_free(heap, p->ptr, p->size); + zend_mm_free_heap(heap, p, NULL, 0, NULL, 0); + count++; + } else { + prev = p; + } + p = prev->next; + } + + return count; +} + +static void zend_mm_check_leaks(zend_mm_heap *heap) +{ + zend_mm_huge_list *list; + zend_mm_chunk *p; + zend_leak_info leak; + zend_long repeated = 0; + uint32_t total = 0; + int i, j; + + /* find leaked huge blocks and free them */ + list = heap->huge_list; + while (list) { + zend_mm_huge_list *q = list; + + leak.addr = list->ptr; + leak.size = list->dbg.size; + leak.filename = list->dbg.filename; + leak.orig_filename = list->dbg.orig_filename; + leak.lineno = list->dbg.lineno; + leak.orig_lineno = list->dbg.orig_lineno; + + zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); + repeated = zend_mm_find_leaks_huge(heap, list); + total += 1 + repeated; + if (repeated) { + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(zend_uintptr_t)repeated); + } + + heap->huge_list = list = list->next; + zend_mm_chunk_free(heap, q->ptr, q->size); + zend_mm_free_heap(heap, q, NULL, 0, NULL, 0); + } + + /* for each chunk */ + p = heap->main_chunk; + do { + i = ZEND_MM_FIRST_PAGE; + while (i < p->free_tail) { + if (zend_mm_bitset_is_set(p->free_map, i)) { + if (p->map[i] & ZEND_MM_IS_SRUN) { + int bin_num = ZEND_MM_SRUN_BIN_NUM(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + j = 0; + while (j < bin_elements[bin_num]) { + if (dbg->size != 0) { + leak.addr = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] * j); + leak.size = dbg->size; + leak.filename = dbg->filename; + leak.orig_filename = dbg->orig_filename; + leak.lineno = dbg->lineno; + leak.orig_lineno = dbg->orig_lineno; + + zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); + + dbg->size = 0; + dbg->filename = NULL; + dbg->lineno = 0; + + repeated = zend_mm_find_leaks_small(p, i, j + 1, &leak) + + zend_mm_find_leaks(heap, p, i + bin_pages[bin_num], &leak); + total += 1 + repeated; + if (repeated) { + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(zend_uintptr_t)repeated); + } + } + dbg = (zend_mm_debug_info*)((char*)dbg + bin_data_size[bin_num]); + j++; + } + i += bin_pages[bin_num]; + } else /* if (p->map[i] & ZEND_MM_IS_LRUN) */ { + int pages_count = ZEND_MM_LRUN_PAGES(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * (i + pages_count) - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + leak.addr = (void*)((char*)p + ZEND_MM_PAGE_SIZE * i); + leak.size = dbg->size; + leak.filename = dbg->filename; + leak.orig_filename = dbg->orig_filename; + leak.lineno = dbg->lineno; + leak.orig_lineno = dbg->orig_lineno; + + zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); + + zend_mm_bitset_reset_range(p->free_map, i, pages_count); + + repeated = zend_mm_find_leaks(heap, p, i + pages_count, &leak); + total += 1 + repeated; + if (repeated) { + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(zend_uintptr_t)repeated); + } + i += pages_count; + } + } else { + i++; + } + } + p = p->next; + } while (p != heap->main_chunk); + if (total) { + zend_message_dispatcher(ZMSG_MEMORY_LEAKS_GRAND_TOTAL, &total); + } +} +#endif + +void zend_mm_shutdown(zend_mm_heap *heap, int full, int silent) +{ + zend_mm_chunk *p; + zend_mm_huge_list *list; + +#if ZEND_MM_CUSTOM + if (heap->use_custom_heap) { + if (full) { + if (ZEND_DEBUG && heap->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + heap->custom_heap.debug._free(heap ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC); + } else { + heap->custom_heap.std._free(heap); + } + } + return; + } +#endif + +#if ZEND_DEBUG + if (!silent) { + zend_mm_check_leaks(heap); + } +#endif + + /* free huge blocks */ + list = heap->huge_list; + heap->huge_list = NULL; + while (list) { + zend_mm_huge_list *q = list; + list = list->next; + zend_mm_chunk_free(heap, q->ptr, q->size); + } + + /* move all chunks except of the first one into the cache */ + p = heap->main_chunk->next; + while (p != heap->main_chunk) { + zend_mm_chunk *q = p->next; + p->next = heap->cached_chunks; + heap->cached_chunks = p; + p = q; + heap->chunks_count--; + heap->cached_chunks_count++; + } + + if (full) { + /* free all cached chunks */ + while (heap->cached_chunks) { + p = heap->cached_chunks; + heap->cached_chunks = p->next; + zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + } + /* free the first chunk */ + zend_mm_chunk_free(heap, heap->main_chunk, ZEND_MM_CHUNK_SIZE); + } else { + zend_mm_heap old_heap; + + /* free some cached chunks to keep average count */ + heap->avg_chunks_count = (heap->avg_chunks_count + (double)heap->peak_chunks_count) / 2.0; + while ((double)heap->cached_chunks_count + 0.9 > heap->avg_chunks_count && + heap->cached_chunks) { + p = heap->cached_chunks; + heap->cached_chunks = p->next; + zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + heap->cached_chunks_count--; + } + /* clear cached chunks */ + p = heap->cached_chunks; + while (p != NULL) { + zend_mm_chunk *q = p->next; + memset(p, 0, sizeof(zend_mm_chunk)); + p->next = q; + p = q; + } + + /* reinitialize the first chunk and heap */ + old_heap = *heap; + p = heap->main_chunk; + memset(p, 0, ZEND_MM_FIRST_PAGE * ZEND_MM_PAGE_SIZE); + *heap = old_heap; + memset(heap->free_slot, 0, sizeof(heap->free_slot)); + heap->main_chunk = p; + p->heap = &p->heap_slot; + p->next = p; + p->prev = p; + p->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; + p->free_tail = ZEND_MM_FIRST_PAGE; + p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; + p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); + heap->chunks_count = 1; + heap->peak_chunks_count = 1; +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size = ZEND_MM_CHUNK_SIZE; +#endif +#if ZEND_MM_STAT + heap->real_peak = ZEND_MM_CHUNK_SIZE; + heap->size = heap->peak = 0; +#endif + } +} + +/**************/ +/* PUBLIC API */ +/**************/ + +ZEND_API void* ZEND_FASTCALL _zend_mm_alloc(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return zend_mm_alloc_heap(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API void ZEND_FASTCALL _zend_mm_free(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + zend_mm_free_heap(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +void* ZEND_FASTCALL _zend_mm_realloc(zend_mm_heap *heap, void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return zend_mm_realloc_heap(heap, ptr, size, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +void* ZEND_FASTCALL _zend_mm_realloc2(zend_mm_heap *heap, void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return zend_mm_realloc_heap(heap, ptr, size, copy_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API size_t ZEND_FASTCALL _zend_mm_block_size(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return zend_mm_size(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +/**********************/ +/* Allocation Manager */ +/**********************/ + +typedef struct _zend_alloc_globals { + zend_mm_heap *mm_heap; +} zend_alloc_globals; + +#ifdef ZTS +static int alloc_globals_id; +# define AG(v) ZEND_TSRMG(alloc_globals_id, zend_alloc_globals *, v) +#else +# define AG(v) (alloc_globals.v) +static zend_alloc_globals alloc_globals; +#endif + +ZEND_API int is_zend_mm(void) +{ +#if ZEND_MM_CUSTOM + return !AG(mm_heap)->use_custom_heap; +#else + return 1; +#endif +} + +#if !ZEND_DEBUG && !defined(_WIN32) +#undef _emalloc + +#if ZEND_MM_CUSTOM +# define ZEND_MM_CUSTOM_ALLOCATOR(size) do { \ + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \ + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { \ + return AG(mm_heap)->custom_heap.debug._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } else { \ + return AG(mm_heap)->custom_heap.std._malloc(size); \ + } \ + } \ + } while (0) +# define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) do { \ + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \ + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { \ + AG(mm_heap)->custom_heap.debug._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } else { \ + AG(mm_heap)->custom_heap.std._free(ptr); \ + } \ + return; \ + } \ + } while (0) +#else +# define ZEND_MM_CUSTOM_ALLOCATOR(size) +# define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) +#endif + +# define _ZEND_BIN_ALLOCATOR(_num, _size, _elements, _pages, x, y) \ + ZEND_API void* ZEND_FASTCALL _emalloc_ ## _size(void) { \ + ZEND_MM_CUSTOM_ALLOCATOR(_size); \ + return zend_mm_alloc_small(AG(mm_heap), _size, _num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } + +ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR, x, y) + +ZEND_API void* ZEND_FASTCALL _emalloc_large(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + + ZEND_MM_CUSTOM_ALLOCATOR(size); + return zend_mm_alloc_large(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) +{ + + ZEND_MM_CUSTOM_ALLOCATOR(size); + return zend_mm_alloc_huge(AG(mm_heap), size); +} + +#if ZEND_DEBUG +# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, x, y) \ + ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ + ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + { \ + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); \ + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ + int page_num = page_offset / ZEND_MM_PAGE_SIZE; \ + ZEND_MM_CHECK(chunk->heap == AG(mm_heap), "zend_mm_heap corrupted"); \ + ZEND_ASSERT(chunk->map[page_num] & ZEND_MM_IS_SRUN); \ + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(chunk->map[page_num]) == _num); \ + zend_mm_free_small(AG(mm_heap), ptr, _num); \ + } \ + } +#else +# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, x, y) \ + ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ + ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + { \ + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ + ZEND_MM_CHECK(chunk->heap == AG(mm_heap), "zend_mm_heap corrupted"); \ + zend_mm_free_small(AG(mm_heap), ptr, _num); \ + } \ + } +#endif + +ZEND_MM_BINS_INFO(_ZEND_BIN_FREE, x, y) + +ZEND_API void ZEND_FASTCALL _efree_large(void *ptr, size_t size) +{ + + ZEND_MM_CUSTOM_DEALLOCATOR(ptr); + { + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + int page_num = page_offset / ZEND_MM_PAGE_SIZE; + int pages_count = ZEND_MM_ALIGNED_SIZE_EX(size, ZEND_MM_PAGE_SIZE) / ZEND_MM_PAGE_SIZE; + + ZEND_MM_CHECK(chunk->heap == AG(mm_heap) && ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); + ZEND_ASSERT(chunk->map[page_num] & ZEND_MM_IS_LRUN); + ZEND_ASSERT(ZEND_MM_LRUN_PAGES(chunk->map[page_num]) == pages_count); + zend_mm_free_large(AG(mm_heap), chunk, page_num, pages_count); + } +} + +ZEND_API void ZEND_FASTCALL _efree_huge(void *ptr, size_t size) +{ + + ZEND_MM_CUSTOM_DEALLOCATOR(ptr); + zend_mm_free_huge(AG(mm_heap), ptr); +} +#endif + +ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + +#if ZEND_MM_CUSTOM + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + return AG(mm_heap)->custom_heap.debug._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + return AG(mm_heap)->custom_heap.std._malloc(size); + } + } +#endif + return zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + +#if ZEND_MM_CUSTOM + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + AG(mm_heap)->custom_heap.debug._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + AG(mm_heap)->custom_heap.std._free(ptr); + } + return; + } +#endif + zend_mm_free_heap(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API void* ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + return AG(mm_heap)->custom_heap.debug._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + return AG(mm_heap)->custom_heap.std._realloc(ptr, size); + } + } + return zend_mm_realloc_heap(AG(mm_heap), ptr, size, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API void* ZEND_FASTCALL _erealloc2(void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { + if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + return AG(mm_heap)->custom_heap.debug._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + return AG(mm_heap)->custom_heap.std._realloc(ptr, size); + } + } + return zend_mm_realloc_heap(AG(mm_heap), ptr, size, copy_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +ZEND_API size_t ZEND_FASTCALL _zend_mem_block_size(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { + return 0; + } + return zend_mm_size(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} + +static zend_always_inline size_t safe_address(size_t nmemb, size_t size, size_t offset) +{ + int overflow; + size_t ret = zend_safe_address(nmemb, size, offset, &overflow); + + if (UNEXPECTED(overflow)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset); + return 0; + } + return ret; +} + + +ZEND_API void* ZEND_FASTCALL _safe_emalloc(size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return emalloc_rel(safe_address(nmemb, size, offset)); +} + +ZEND_API void* ZEND_FASTCALL _safe_malloc(size_t nmemb, size_t size, size_t offset) +{ + return pemalloc(safe_address(nmemb, size, offset), 1); +} + +ZEND_API void* ZEND_FASTCALL _safe_erealloc(void *ptr, size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + return erealloc_rel(ptr, safe_address(nmemb, size, offset)); +} + +ZEND_API void* ZEND_FASTCALL _safe_realloc(void *ptr, size_t nmemb, size_t size, size_t offset) +{ + return perealloc(ptr, safe_address(nmemb, size, offset), 1); +} + + +ZEND_API void* ZEND_FASTCALL _ecalloc(size_t nmemb, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + void *p; + + p = _safe_emalloc(nmemb, size, 0 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + if (UNEXPECTED(p == NULL)) { + return p; + } + memset(p, 0, size * nmemb); + return p; +} + +ZEND_API char* ZEND_FASTCALL _estrdup(const char *s ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t length; + char *p; + + length = strlen(s); + if (UNEXPECTED(length + 1 == 0)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", 1, length, 1); + } + p = (char *) _emalloc(length + 1 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + if (UNEXPECTED(p == NULL)) { + return p; + } + memcpy(p, s, length+1); + return p; +} + +ZEND_API char* ZEND_FASTCALL _estrndup(const char *s, size_t length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + char *p; + + if (UNEXPECTED(length + 1 == 0)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", 1, length, 1); + } + p = (char *) _emalloc(length + 1 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + if (UNEXPECTED(p == NULL)) { + return p; + } + memcpy(p, s, length); + p[length] = 0; + return p; +} + + +ZEND_API char* ZEND_FASTCALL zend_strndup(const char *s, size_t length) +{ + char *p; + + if (UNEXPECTED(length + 1 == 0)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", 1, length, 1); + } + p = (char *) malloc(length + 1); + if (UNEXPECTED(p == NULL)) { + return p; + } + if (EXPECTED(length)) { + memcpy(p, s, length); + } + p[length] = 0; + return p; +} + + +ZEND_API int zend_set_memory_limit(size_t memory_limit) +{ +#if ZEND_MM_LIMIT + AG(mm_heap)->limit = (memory_limit >= ZEND_MM_CHUNK_SIZE) ? memory_limit : ZEND_MM_CHUNK_SIZE; +#endif + return SUCCESS; +} + +ZEND_API size_t zend_memory_usage(int real_usage) +{ +#if ZEND_MM_STAT + if (real_usage) { + return AG(mm_heap)->real_size; + } else { + size_t usage = AG(mm_heap)->size; + return usage; + } +#endif + return 0; +} + +ZEND_API size_t zend_memory_peak_usage(int real_usage) +{ +#if ZEND_MM_STAT + if (real_usage) { + return AG(mm_heap)->real_peak; + } else { + return AG(mm_heap)->peak; + } +#endif + return 0; +} + +ZEND_API void shutdown_memory_manager(int silent, int full_shutdown) +{ + zend_mm_shutdown(AG(mm_heap), full_shutdown, silent); +} + +static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) +{ +#if ZEND_MM_CUSTOM + char *tmp = getenv("USE_ZEND_ALLOC"); + + if (tmp && !zend_atoi(tmp, 0)) { + alloc_globals->mm_heap = malloc(sizeof(zend_mm_heap)); + memset(alloc_globals->mm_heap, 0, sizeof(zend_mm_heap)); + alloc_globals->mm_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD; + alloc_globals->mm_heap->custom_heap.std._malloc = malloc; + alloc_globals->mm_heap->custom_heap.std._free = free; + alloc_globals->mm_heap->custom_heap.std._realloc = realloc; + return; + } +#endif + ZEND_TSRMLS_CACHE_UPDATE(); + alloc_globals->mm_heap = zend_mm_init(); +} + +#ifdef ZTS +static void alloc_globals_dtor(zend_alloc_globals *alloc_globals) +{ + zend_mm_shutdown(alloc_globals->mm_heap, 1, 1); +} +#endif + +ZEND_API void start_memory_manager(void) +{ +#ifdef ZTS + ts_allocate_id(&alloc_globals_id, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); +#else + alloc_globals_ctor(&alloc_globals); +#endif +#ifndef _WIN32 +# if defined(_SC_PAGESIZE) + REAL_PAGE_SIZE = sysconf(_SC_PAGESIZE); +# elif defined(_SC_PAGE_SIZE) + REAL_PAGE_SIZE = sysconf(_SC_PAGE_SIZE); +# endif +#endif +} + +ZEND_API zend_mm_heap *zend_mm_set_heap(zend_mm_heap *new_heap) +{ + zend_mm_heap *old_heap; + + old_heap = AG(mm_heap); + AG(mm_heap) = (zend_mm_heap*)new_heap; + return (zend_mm_heap*)old_heap; +} + +ZEND_API zend_mm_heap *zend_mm_get_heap(void) +{ + return AG(mm_heap); +} + +ZEND_API int zend_mm_is_custom_heap(zend_mm_heap *new_heap) +{ +#if ZEND_MM_CUSTOM + return AG(mm_heap)->use_custom_heap; +#else + return 0; +#endif +} + +ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap, + void* (*_malloc)(size_t), + void (*_free)(void*), + void* (*_realloc)(void*, size_t)) +{ +#if ZEND_MM_CUSTOM + zend_mm_heap *_heap = (zend_mm_heap*)heap; + + _heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD; + _heap->custom_heap.std._malloc = _malloc; + _heap->custom_heap.std._free = _free; + _heap->custom_heap.std._realloc = _realloc; +#endif +} + +ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap, + void* (**_malloc)(size_t), + void (**_free)(void*), + void* (**_realloc)(void*, size_t)) +{ +#if ZEND_MM_CUSTOM + zend_mm_heap *_heap = (zend_mm_heap*)heap; + + if (heap->use_custom_heap) { + *_malloc = _heap->custom_heap.std._malloc; + *_free = _heap->custom_heap.std._free; + *_realloc = _heap->custom_heap.std._realloc; + } else { + *_malloc = NULL; + *_free = NULL; + *_realloc = NULL; + } +#else + *_malloc = NULL; + *_free = NULL; + *_realloc = NULL; +#endif +} + +#if ZEND_DEBUG +ZEND_API void zend_mm_set_custom_debug_handlers(zend_mm_heap *heap, + void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void* (*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)) +{ +#if ZEND_MM_CUSTOM + zend_mm_heap *_heap = (zend_mm_heap*)heap; + + _heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_DEBUG; + _heap->custom_heap.debug._malloc = _malloc; + _heap->custom_heap.debug._free = _free; + _heap->custom_heap.debug._realloc = _realloc; +#endif +} +#endif + +ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap) +{ +#if ZEND_MM_STORAGE + return heap->storage; +#else + return NULL +#endif +} + +ZEND_API zend_mm_heap *zend_mm_startup(void) +{ + return zend_mm_init(); +} + +ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void *data, size_t data_size) +{ +#if ZEND_MM_STORAGE + zend_mm_storage tmp_storage, *storage; + zend_mm_chunk *chunk; + zend_mm_heap *heap; + + memcpy((zend_mm_handlers*)&tmp_storage.handlers, handlers, sizeof(zend_mm_handlers)); + tmp_storage.data = data; + chunk = (zend_mm_chunk*)handlers->chunk_alloc(&tmp_storage, ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE); + if (UNEXPECTED(chunk == NULL)) { +#if ZEND_MM_ERROR +#ifdef _WIN32 + stderr_last_error("Can't initialize heap"); +#else + fprintf(stderr, "\nCan't initialize heap: [%d] %s\n", errno, strerror(errno)); +#endif +#endif + return NULL; + } + heap = &chunk->heap_slot; + chunk->heap = heap; + chunk->next = chunk; + chunk->prev = chunk; + chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; + chunk->free_tail = ZEND_MM_FIRST_PAGE; + chunk->num = 0; + chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1; + chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); + heap->main_chunk = chunk; + heap->cached_chunks = NULL; + heap->chunks_count = 1; + heap->peak_chunks_count = 1; + heap->cached_chunks_count = 0; + heap->avg_chunks_count = 1.0; +#if ZEND_MM_STAT || ZEND_MM_LIMIT + heap->real_size = ZEND_MM_CHUNK_SIZE; +#endif +#if ZEND_MM_STAT + heap->real_peak = ZEND_MM_CHUNK_SIZE; + heap->size = 0; + heap->peak = 0; +#endif +#if ZEND_MM_LIMIT + heap->limit = (Z_L(-1) >> Z_L(1)); + heap->overflow = 0; +#endif +#if ZEND_MM_CUSTOM + heap->use_custom_heap = 0; +#endif + heap->storage = &tmp_storage; + heap->huge_list = NULL; + memset(heap->free_slot, 0, sizeof(heap->free_slot)); + storage = _zend_mm_alloc(heap, sizeof(zend_mm_storage) + data_size ZEND_FILE_LINE_CC ZEND_FILE_LINE_CC); + if (!storage) { + handlers->chunk_free(&tmp_storage, chunk, ZEND_MM_CHUNK_SIZE); +#if ZEND_MM_ERROR +#ifdef _WIN32 + stderr_last_error("Can't initialize heap"); +#else + fprintf(stderr, "\nCan't initialize heap: [%d] %s\n", errno, strerror(errno)); +#endif +#endif + return NULL; + } + memcpy(storage, &tmp_storage, sizeof(zend_mm_storage)); + if (data) { + storage->data = (void*)(((char*)storage + sizeof(zend_mm_storage))); + memcpy(storage->data, data, data_size); + } + heap->storage = storage; + return heap; +#else + return NULL; +#endif +} + +static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void) +{ + fprintf(stderr, "Out of memory\n"); + exit(1); +} + +ZEND_API void * __zend_malloc(size_t len) +{ + void *tmp = malloc(len); + if (EXPECTED(tmp)) { + return tmp; + } + zend_out_of_memory(); +} + +ZEND_API void * __zend_calloc(size_t nmemb, size_t len) +{ + void *tmp = _safe_malloc(nmemb, len, 0); + memset(tmp, 0, nmemb * len); + return tmp; +} + +ZEND_API void * __zend_realloc(void *p, size_t len) +{ + p = realloc(p, len); + if (EXPECTED(p)) { + return p; + } + zend_out_of_memory(); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ From c7e2f35edb9ed0dc3dad29731868aff458bac763 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:27 +0000 Subject: [PATCH 11/46] commit patch 21437787 --- ext/gd/gd.c | 6 +- ext/gd/gd.c.orig | 4960 ++++++++++++++++++++++++++++++++++++ ext/gd/tests/bug72697.phpt | 17 + 3 files changed, 4980 insertions(+), 3 deletions(-) create mode 100644 ext/gd/gd.c.orig create mode 100644 ext/gd/tests/bug72697.phpt diff --git a/ext/gd/gd.c b/ext/gd/gd.c index d86352385285d..091e72d3060e2 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1514,11 +1514,11 @@ PHP_FUNCTION(imagetruecolortopalette) RETURN_FALSE; } - if (ncolors <= 0) { - php_error_docref(NULL, E_WARNING, "Number of colors has to be greater than zero"); + if (ncolors <= 0 || ZEND_LONG_INT_OVFL(ncolors)) { + php_error_docref(NULL, E_WARNING, "Number of colors has to be greater than zero and no more than %d", INT_MAX); RETURN_FALSE; } - gdImageTrueColorToPalette(im, dither, ncolors); + gdImageTrueColorToPalette(im, dither, (int)ncolors); RETURN_TRUE; } diff --git a/ext/gd/gd.c.orig b/ext/gd/gd.c.orig new file mode 100644 index 0000000000000..d86352385285d --- /dev/null +++ b/ext/gd/gd.c.orig @@ -0,0 +1,4960 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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 Bakken | + | Jim Winstead | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +/* gd 1.2 is copyright 1994, 1995, Quest Protein Database Center, + Cold Spring Harbor Labs. */ + +/* Note that there is no code from the gd package in this file */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/head.h" +#include +#include "SAPI.h" +#include "php_gd.h" +#include "ext/standard/info.h" +#include "php_open_temporary_file.h" + + +#if HAVE_SYS_WAIT_H +# include +#endif +#if HAVE_UNISTD_H +# include +#endif +#ifdef PHP_WIN32 +# include +# include +# include +# include +# include +#endif + +#ifdef HAVE_GD_XPM +# include +#endif + +# include "gd_compat.h" + + +static int le_gd, le_gd_font; + +#include +#include /* 1 Tiny font */ +#include /* 2 Small font */ +#include /* 3 Medium bold font */ +#include /* 4 Large font */ +#include /* 5 Giant font */ + +#ifdef ENABLE_GD_TTF +# ifdef HAVE_LIBFREETYPE +# include +# include FT_FREETYPE_H +# endif +#endif + +#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED) +# include "X11/xpm.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef ENABLE_GD_TTF +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int, int); +#endif + +#include "gd_ctx.c" + +/* as it is not really public, duplicate declaration here to avoid + pointless warnings */ +int overflow2(int a, int b); + +/* Section Filters Declarations */ +/* IMPORTANT NOTE FOR NEW FILTER + * Do not forget to update: + * IMAGE_FILTER_MAX: define the last filter index + * IMAGE_FILTER_MAX_ARGS: define the biggest amount of arguments + * image_filter array in PHP_FUNCTION(imagefilter) + * */ +#define IMAGE_FILTER_NEGATE 0 +#define IMAGE_FILTER_GRAYSCALE 1 +#define IMAGE_FILTER_BRIGHTNESS 2 +#define IMAGE_FILTER_CONTRAST 3 +#define IMAGE_FILTER_COLORIZE 4 +#define IMAGE_FILTER_EDGEDETECT 5 +#define IMAGE_FILTER_EMBOSS 6 +#define IMAGE_FILTER_GAUSSIAN_BLUR 7 +#define IMAGE_FILTER_SELECTIVE_BLUR 8 +#define IMAGE_FILTER_MEAN_REMOVAL 9 +#define IMAGE_FILTER_SMOOTH 10 +#define IMAGE_FILTER_PIXELATE 11 +#define IMAGE_FILTER_MAX 11 +#define IMAGE_FILTER_MAX_ARGS 6 +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS); + +/* End Section filters declarations */ +static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)()); +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()); +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()); +static int _php_image_type(char data[8]); +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type); +static void _php_image_bw_convert(gdImagePtr im_org, gdIOCtx *out, int threshold); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_gd_info, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageloadfont, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetstyle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, styles) /* ARRAY_INFO(0, styles, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatetruecolor, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageistruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetruecolortopalette, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, ditherFlag) + ZEND_ARG_INFO(0, colorsWanted) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettetotruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolormatch, 0) + ZEND_ARG_INFO(0, im1) + ZEND_ARG_INFO(0, im2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetthickness, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, thickness) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledarc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, style) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagealphablending, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, blend) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesavealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, save) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagelayereffect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, effect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocatealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolvealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosestalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexactalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresampled, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +#ifdef PHP_WIN32 +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegrabwindow, 0, 0, 1) + ZEND_ARG_INFO(0, handle) + ZEND_ARG_INFO(0, client_area) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegrabscreen, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagerotate, 0, 0, 3) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, bgdcolor) + ZEND_ARG_INFO(0, ignoretransparent) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesettile, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, tile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetbrush, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, brush) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreate, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetypes, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromstring, 0) + ZEND_ARG_INFO(0, image) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgif, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromjpeg, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefrompng, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwebp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxbm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_XPM) +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxpm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwbmp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2part, 0) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, srcX) + ZEND_ARG_INFO(0, srcY) + ZEND_ARG_INFO(0, width) + ZEND_ARG_INFO(0, height) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagexbm, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegif, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagepng, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewebp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagejpeg, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, quality) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd2, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, chunk_size) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagedestroy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettecopy, 0) + ZEND_ARG_INFO(0, dst) + ZEND_ARG_INFO(0, src) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorat, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosest, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosesthwb, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolordeallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolve, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexact, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolorset, 0, 0, 5) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, color) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorsforindex, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegammacorrect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, inputgamma) + ZEND_ARG_INFO(0, outputgamma) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetpixel, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagedashedline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagerectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledrectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagearc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilltoborder, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, border) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefill, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorstotal, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolortransparent, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageinterlace, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, interlace) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledpolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontwidth, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontheight, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagechar, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecharup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestring, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestringup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopy, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymerge, 0) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymergegray, 0) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresized, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesx, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +#ifdef ENABLE_GD_TTF +#if HAVE_LIBFREETYPE +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageftbbox, 0, 0, 4) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefttext, 0, 0, 8) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagettfbbox, 0) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagettftext, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_image2wbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, threshold) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_JPG) +ZEND_BEGIN_ARG_INFO(arginfo_jpeg2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_GD_PNG) +ZEND_BEGIN_ARG_INFO(arginfo_png2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefilter, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filtertype) + ZEND_ARG_INFO(0, arg1) + ZEND_ARG_INFO(0, arg2) + ZEND_ARG_INFO(0, arg3) + ZEND_ARG_INFO(0, arg4) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageconvolution, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, matrix3x3) /* ARRAY_INFO(0, matrix3x3, 0) */ + ZEND_ARG_INFO(0, div) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageflip, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_BUNDLED +ZEND_BEGIN_ARG_INFO(arginfo_imageantialias, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, on) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecrop, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, rect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecropauto, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, threshold) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagescale, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, new_width) + ZEND_ARG_INFO(0, new_height) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffine, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, affine) + ZEND_ARG_INFO(0, clip) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffinematrixget, 0, 0, 1) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageaffinematrixconcat, 0) + ZEND_ARG_INFO(0, m1) + ZEND_ARG_INFO(0, m2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetinterpolation, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ gd_functions[] + */ +const zend_function_entry gd_functions[] = { + PHP_FE(gd_info, arginfo_gd_info) + PHP_FE(imagearc, arginfo_imagearc) + PHP_FE(imageellipse, arginfo_imageellipse) + PHP_FE(imagechar, arginfo_imagechar) + PHP_FE(imagecharup, arginfo_imagecharup) + PHP_FE(imagecolorat, arginfo_imagecolorat) + PHP_FE(imagecolorallocate, arginfo_imagecolorallocate) + PHP_FE(imagepalettecopy, arginfo_imagepalettecopy) + PHP_FE(imagecreatefromstring, arginfo_imagecreatefromstring) + PHP_FE(imagecolorclosest, arginfo_imagecolorclosest) + PHP_FE(imagecolorclosesthwb, arginfo_imagecolorclosesthwb) + PHP_FE(imagecolordeallocate, arginfo_imagecolordeallocate) + PHP_FE(imagecolorresolve, arginfo_imagecolorresolve) + PHP_FE(imagecolorexact, arginfo_imagecolorexact) + PHP_FE(imagecolorset, arginfo_imagecolorset) + PHP_FE(imagecolortransparent, arginfo_imagecolortransparent) + PHP_FE(imagecolorstotal, arginfo_imagecolorstotal) + PHP_FE(imagecolorsforindex, arginfo_imagecolorsforindex) + PHP_FE(imagecopy, arginfo_imagecopy) + PHP_FE(imagecopymerge, arginfo_imagecopymerge) + PHP_FE(imagecopymergegray, arginfo_imagecopymergegray) + PHP_FE(imagecopyresized, arginfo_imagecopyresized) + PHP_FE(imagecreate, arginfo_imagecreate) + PHP_FE(imagecreatetruecolor, arginfo_imagecreatetruecolor) + PHP_FE(imageistruecolor, arginfo_imageistruecolor) + PHP_FE(imagetruecolortopalette, arginfo_imagetruecolortopalette) + PHP_FE(imagepalettetotruecolor, arginfo_imagepalettetotruecolor) + PHP_FE(imagesetthickness, arginfo_imagesetthickness) + PHP_FE(imagefilledarc, arginfo_imagefilledarc) + PHP_FE(imagefilledellipse, arginfo_imagefilledellipse) + PHP_FE(imagealphablending, arginfo_imagealphablending) + PHP_FE(imagesavealpha, arginfo_imagesavealpha) + PHP_FE(imagecolorallocatealpha, arginfo_imagecolorallocatealpha) + PHP_FE(imagecolorresolvealpha, arginfo_imagecolorresolvealpha) + PHP_FE(imagecolorclosestalpha, arginfo_imagecolorclosestalpha) + PHP_FE(imagecolorexactalpha, arginfo_imagecolorexactalpha) + PHP_FE(imagecopyresampled, arginfo_imagecopyresampled) + +#ifdef PHP_WIN32 + PHP_FE(imagegrabwindow, arginfo_imagegrabwindow) + PHP_FE(imagegrabscreen, arginfo_imagegrabscreen) +#endif + + PHP_FE(imagerotate, arginfo_imagerotate) + PHP_FE(imageflip, arginfo_imageflip) + +#ifdef HAVE_GD_BUNDLED + PHP_FE(imageantialias, arginfo_imageantialias) +#endif + PHP_FE(imagecrop, arginfo_imagecrop) + PHP_FE(imagecropauto, arginfo_imagecropauto) + PHP_FE(imagescale, arginfo_imagescale) + PHP_FE(imageaffine, arginfo_imageaffine) + PHP_FE(imageaffinematrixconcat, arginfo_imageaffinematrixconcat) + PHP_FE(imageaffinematrixget, arginfo_imageaffinematrixget) + PHP_FE(imagesetinterpolation, arginfo_imagesetinterpolation) + PHP_FE(imagesettile, arginfo_imagesettile) + PHP_FE(imagesetbrush, arginfo_imagesetbrush) + PHP_FE(imagesetstyle, arginfo_imagesetstyle) + +#ifdef HAVE_GD_PNG + PHP_FE(imagecreatefrompng, arginfo_imagecreatefrompng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagecreatefromwebp, arginfo_imagecreatefromwebp) +#endif + PHP_FE(imagecreatefromgif, arginfo_imagecreatefromgif) +#ifdef HAVE_GD_JPG + PHP_FE(imagecreatefromjpeg, arginfo_imagecreatefromjpeg) +#endif + PHP_FE(imagecreatefromwbmp, arginfo_imagecreatefromwbmp) + PHP_FE(imagecreatefromxbm, arginfo_imagecreatefromxbm) +#if defined(HAVE_GD_XPM) + PHP_FE(imagecreatefromxpm, arginfo_imagecreatefromxpm) +#endif + PHP_FE(imagecreatefromgd, arginfo_imagecreatefromgd) + PHP_FE(imagecreatefromgd2, arginfo_imagecreatefromgd2) + PHP_FE(imagecreatefromgd2part, arginfo_imagecreatefromgd2part) +#ifdef HAVE_GD_PNG + PHP_FE(imagepng, arginfo_imagepng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagewebp, arginfo_imagewebp) +#endif + PHP_FE(imagegif, arginfo_imagegif) +#ifdef HAVE_GD_JPG + PHP_FE(imagejpeg, arginfo_imagejpeg) +#endif + PHP_FE(imagewbmp, arginfo_imagewbmp) + PHP_FE(imagegd, arginfo_imagegd) + PHP_FE(imagegd2, arginfo_imagegd2) + + PHP_FE(imagedestroy, arginfo_imagedestroy) + PHP_FE(imagegammacorrect, arginfo_imagegammacorrect) + PHP_FE(imagefill, arginfo_imagefill) + PHP_FE(imagefilledpolygon, arginfo_imagefilledpolygon) + PHP_FE(imagefilledrectangle, arginfo_imagefilledrectangle) + PHP_FE(imagefilltoborder, arginfo_imagefilltoborder) + PHP_FE(imagefontwidth, arginfo_imagefontwidth) + PHP_FE(imagefontheight, arginfo_imagefontheight) + PHP_FE(imageinterlace, arginfo_imageinterlace) + PHP_FE(imageline, arginfo_imageline) + PHP_FE(imageloadfont, arginfo_imageloadfont) + PHP_FE(imagepolygon, arginfo_imagepolygon) + PHP_FE(imagerectangle, arginfo_imagerectangle) + PHP_FE(imagesetpixel, arginfo_imagesetpixel) + PHP_FE(imagestring, arginfo_imagestring) + PHP_FE(imagestringup, arginfo_imagestringup) + PHP_FE(imagesx, arginfo_imagesx) + PHP_FE(imagesy, arginfo_imagesy) + PHP_FE(imagedashedline, arginfo_imagedashedline) + +#ifdef ENABLE_GD_TTF + PHP_FE(imagettfbbox, arginfo_imagettfbbox) + PHP_FE(imagettftext, arginfo_imagettftext) +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE + PHP_FE(imageftbbox, arginfo_imageftbbox) + PHP_FE(imagefttext, arginfo_imagefttext) +#endif +#endif + + PHP_FE(imagetypes, arginfo_imagetypes) + +#if defined(HAVE_GD_JPG) + PHP_FE(jpeg2wbmp, arginfo_jpeg2wbmp) +#endif +#if defined(HAVE_GD_PNG) + PHP_FE(png2wbmp, arginfo_png2wbmp) +#endif + PHP_FE(image2wbmp, arginfo_image2wbmp) + PHP_FE(imagelayereffect, arginfo_imagelayereffect) + PHP_FE(imagexbm, arginfo_imagexbm) + + PHP_FE(imagecolormatch, arginfo_imagecolormatch) + +/* gd filters */ + PHP_FE(imagefilter, arginfo_imagefilter) + PHP_FE(imageconvolution, arginfo_imageconvolution) + + PHP_FE_END +}; +/* }}} */ + +zend_module_entry gd_module_entry = { + STANDARD_MODULE_HEADER, + "gd", + gd_functions, + PHP_MINIT(gd), + NULL, + NULL, +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE + PHP_RSHUTDOWN(gd), +#else + NULL, +#endif + PHP_MINFO(gd), + PHP_GD_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_GD +ZEND_GET_MODULE(gd) +#endif + +/* {{{ PHP_INI_BEGIN */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("gd.jpeg_ignore_warning", "0", PHP_INI_ALL, NULL) +PHP_INI_END() +/* }}} */ + +/* {{{ php_free_gd_image + */ +static void php_free_gd_image(zend_resource *rsrc) +{ + gdImageDestroy((gdImagePtr) rsrc->ptr); +} +/* }}} */ + +/* {{{ php_free_gd_font + */ +static void php_free_gd_font(zend_resource *rsrc) +{ + gdFontPtr fp = (gdFontPtr) rsrc->ptr; + + if (fp->data) { + efree(fp->data); + } + + efree(fp); +} +/* }}} */ + +#ifndef HAVE_GD_BUNDLED +/* {{{ php_gd_error_method + */ +void php_gd_error_method(int type, const char *format, va_list args) +{ + + php_verror(NULL, "", type, format, args); +} +/* }}} */ +#endif + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(gd) +{ + le_gd = zend_register_list_destructors_ex(php_free_gd_image, NULL, "gd", module_number); + le_gd_font = zend_register_list_destructors_ex(php_free_gd_font, NULL, "gd font", module_number); + +#if HAVE_GD_BUNDLED && HAVE_LIBFREETYPE + gdFontCacheMutexSetup(); +#endif +#ifndef HAVE_GD_BUNDLED + gdSetErrorMethod(php_gd_error_method); +#endif + REGISTER_INI_ENTRIES(); + + REGISTER_LONG_CONSTANT("IMG_GIF", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPG", 2, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPEG", 2, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_PNG", 4, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WBMP", 8, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_XPM", 16, CONST_CS | CONST_PERSISTENT); + + /* special colours for gd */ + REGISTER_LONG_CONSTANT("IMG_COLOR_TILED", gdTiled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLED", gdStyled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_BRUSHED", gdBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLEDBRUSHED", gdStyledBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_TRANSPARENT", gdTransparent, CONST_CS | CONST_PERSISTENT); + + /* for imagefilledarc */ + REGISTER_LONG_CONSTANT("IMG_ARC_ROUNDED", gdArc, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_PIE", gdPie, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_CHORD", gdChord, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_NOFILL", gdNoFill, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_EDGED", gdEdged, CONST_CS | CONST_PERSISTENT); + + /* GD2 image format types */ + REGISTER_LONG_CONSTANT("IMG_GD2_RAW", GD2_FMT_RAW, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GD2_COMPRESSED", GD2_FMT_COMPRESSED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_HORIZONTAL", GD_FLIP_HORINZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_VERTICAL", GD_FLIP_VERTICAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_BOTH", GD_FLIP_BOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_REPLACE", gdEffectReplace, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_ALPHABLEND", gdEffectAlphaBlend, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_NORMAL", gdEffectNormal, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_OVERLAY", gdEffectOverlay, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_CROP_DEFAULT", GD_CROP_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_TRANSPARENT", GD_CROP_TRANSPARENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_BLACK", GD_CROP_BLACK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_WHITE", GD_CROP_WHITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_SIDES", GD_CROP_SIDES, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_THRESHOLD", GD_CROP_THRESHOLD, CONST_CS | CONST_PERSISTENT); + + + REGISTER_LONG_CONSTANT("IMG_BELL", GD_BELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BESSEL", GD_BESSEL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BILINEAR_FIXED", GD_BILINEAR_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC", GD_BICUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC_FIXED", GD_BICUBIC_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BLACKMAN", GD_BLACKMAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BOX", GD_BOX, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BSPLINE", GD_BSPLINE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CATMULLROM", GD_CATMULLROM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GAUSSIAN", GD_GAUSSIAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GENERALIZED_CUBIC", GD_GENERALIZED_CUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HERMITE", GD_HERMITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HAMMING", GD_HAMMING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HANNING", GD_HANNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_MITCHELL", GD_MITCHELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_POWER", GD_POWER, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_QUADRATIC", GD_QUADRATIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_SINC", GD_SINC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_NEAREST_NEIGHBOUR", GD_NEAREST_NEIGHBOUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WEIGHTED4", GD_WEIGHTED4, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_TRIANGLE", GD_TRIANGLE, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_AFFINE_TRANSLATE", GD_AFFINE_TRANSLATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SCALE", GD_AFFINE_SCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_ROTATE", GD_AFFINE_ROTATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_HORIZONTAL", GD_AFFINE_SHEAR_HORIZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_VERTICAL", GD_AFFINE_SHEAR_VERTICAL, CONST_CS | CONST_PERSISTENT); + +#if defined(HAVE_GD_BUNDLED) + REGISTER_LONG_CONSTANT("GD_BUNDLED", 1, CONST_CS | CONST_PERSISTENT); +#else + REGISTER_LONG_CONSTANT("GD_BUNDLED", 0, CONST_CS | CONST_PERSISTENT); +#endif + + /* Section Filters */ + REGISTER_LONG_CONSTANT("IMG_FILTER_NEGATE", IMAGE_FILTER_NEGATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GRAYSCALE", IMAGE_FILTER_GRAYSCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_BRIGHTNESS", IMAGE_FILTER_BRIGHTNESS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_CONTRAST", IMAGE_FILTER_CONTRAST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_COLORIZE", IMAGE_FILTER_COLORIZE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EDGEDETECT", IMAGE_FILTER_EDGEDETECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GAUSSIAN_BLUR", IMAGE_FILTER_GAUSSIAN_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SELECTIVE_BLUR", IMAGE_FILTER_SELECTIVE_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EMBOSS", IMAGE_FILTER_EMBOSS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_MEAN_REMOVAL", IMAGE_FILTER_MEAN_REMOVAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SMOOTH", IMAGE_FILTER_SMOOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_PIXELATE", IMAGE_FILTER_PIXELATE, CONST_CS | CONST_PERSISTENT); + /* End Section Filters */ + +#ifdef GD_VERSION_STRING + REGISTER_STRING_CONSTANT("GD_VERSION", GD_VERSION_STRING, CONST_CS | CONST_PERSISTENT); +#endif + +#if defined(GD_MAJOR_VERSION) && defined(GD_MINOR_VERSION) && defined(GD_RELEASE_VERSION) && defined(GD_EXTRA_VERSION) + REGISTER_LONG_CONSTANT("GD_MAJOR_VERSION", GD_MAJOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_MINOR_VERSION", GD_MINOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_RELEASE_VERSION", GD_RELEASE_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("GD_EXTRA_VERSION", GD_EXTRA_VERSION, CONST_CS | CONST_PERSISTENT); +#endif + + +#ifdef HAVE_GD_PNG + + /* + * cannot include #include "png.h" + * /usr/include/pngconf.h:310:2: error: #error png.h already includes setjmp.h with some additional fixup. + * as error, use the values for now... + */ + REGISTER_LONG_CONSTANT("PNG_NO_FILTER", 0x00, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_NONE", 0x08, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_SUB", 0x10, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_UP", 0x20, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_AVG", 0x40, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_PAETH", 0x80, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_ALL_FILTERS", 0x08 | 0x10 | 0x20 | 0x40 | 0x80, CONST_CS | CONST_PERSISTENT); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE +PHP_RSHUTDOWN_FUNCTION(gd) +{ + gdFontCacheShutdown(); + return SUCCESS; +} +#endif +/* }}} */ + +#if defined(HAVE_GD_BUNDLED) +#define PHP_GD_VERSION_STRING "bundled (2.1.0 compatible)" +#else +# define PHP_GD_VERSION_STRING GD_VERSION_STRING +#endif + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(gd) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "GD Support", "enabled"); + + /* need to use a PHPAPI function here because it is external module in windows */ + +#if defined(HAVE_GD_BUNDLED) + php_info_print_table_row(2, "GD Version", PHP_GD_VERSION_STRING); +#else + php_info_print_table_row(2, "GD headers Version", PHP_GD_VERSION_STRING); +#if defined(HAVE_GD_LIBVERSION) + php_info_print_table_row(2, "GD library Version", gdVersionString()); +#endif +#endif + +#ifdef ENABLE_GD_TTF + php_info_print_table_row(2, "FreeType Support", "enabled"); +#if HAVE_LIBFREETYPE + php_info_print_table_row(2, "FreeType Linkage", "with freetype"); + { + char tmp[256]; + +#ifdef FREETYPE_PATCH + snprintf(tmp, sizeof(tmp), "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); +#elif defined(FREETYPE_MAJOR) + snprintf(tmp, sizeof(tmp), "%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR); +#else + snprintf(tmp, sizeof(tmp), "1.x"); +#endif + php_info_print_table_row(2, "FreeType Version", tmp); + } +#else + php_info_print_table_row(2, "FreeType Linkage", "with unknown library"); +#endif +#endif + + php_info_print_table_row(2, "GIF Read Support", "enabled"); + php_info_print_table_row(2, "GIF Create Support", "enabled"); + +#ifdef HAVE_GD_JPG + { + php_info_print_table_row(2, "JPEG Support", "enabled"); + php_info_print_table_row(2, "libJPEG Version", gdJpegGetVersionString()); + } +#endif + +#ifdef HAVE_GD_PNG + php_info_print_table_row(2, "PNG Support", "enabled"); + php_info_print_table_row(2, "libPNG Version", gdPngGetVersionString()); +#endif + php_info_print_table_row(2, "WBMP Support", "enabled"); +#if defined(HAVE_GD_XPM) + php_info_print_table_row(2, "XPM Support", "enabled"); + { + char tmp[12]; + snprintf(tmp, sizeof(tmp), "%d", XpmLibraryVersion()); + php_info_print_table_row(2, "libXpm Version", tmp); + } +#endif + php_info_print_table_row(2, "XBM Support", "enabled"); +#if defined(USE_GD_JISX0208) + php_info_print_table_row(2, "JIS-mapped Japanese Font Support", "enabled"); +#endif +#ifdef HAVE_GD_WEBP + php_info_print_table_row(2, "WebP Support", "enabled"); +#endif + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ proto array gd_info() + */ +PHP_FUNCTION(gd_info) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_FALSE; + } + + array_init(return_value); + + add_assoc_string(return_value, "GD Version", PHP_GD_VERSION_STRING); + +#ifdef ENABLE_GD_TTF + add_assoc_bool(return_value, "FreeType Support", 1); +#if HAVE_LIBFREETYPE + add_assoc_string(return_value, "FreeType Linkage", "with freetype"); +#else + add_assoc_string(return_value, "FreeType Linkage", "with unknown library"); +#endif +#else + add_assoc_bool(return_value, "FreeType Support", 0); +#endif + add_assoc_bool(return_value, "GIF Read Support", 1); + add_assoc_bool(return_value, "GIF Create Support", 1); +#ifdef HAVE_GD_JPG + add_assoc_bool(return_value, "JPEG Support", 1); +#else + add_assoc_bool(return_value, "JPEG Support", 0); +#endif +#ifdef HAVE_GD_PNG + add_assoc_bool(return_value, "PNG Support", 1); +#else + add_assoc_bool(return_value, "PNG Support", 0); +#endif + add_assoc_bool(return_value, "WBMP Support", 1); +#if defined(HAVE_GD_XPM) + add_assoc_bool(return_value, "XPM Support", 1); +#else + add_assoc_bool(return_value, "XPM Support", 0); +#endif + add_assoc_bool(return_value, "XBM Support", 1); +#ifdef HAVE_GD_WEBP + add_assoc_bool(return_value, "WebP Support", 1); +#else + add_assoc_bool(return_value, "WebP Support", 0); +#endif +#if defined(USE_GD_JISX0208) + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 1); +#else + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 0); +#endif +} +/* }}} */ + +/* Need this for cpdf. See also comment in file.c php3i_get_le_fp() */ +PHP_GD_API int phpi_get_le_gd(void) +{ + return le_gd; +} +/* }}} */ + +#define FLIPWORD(a) (((a & 0xff000000) >> 24) | ((a & 0x00ff0000) >> 8) | ((a & 0x0000ff00) << 8) | ((a & 0x000000ff) << 24)) + +/* {{{ proto int imageloadfont(string filename) + Load a new font */ +PHP_FUNCTION(imageloadfont) +{ + zval *ind; + zend_string *file; + int hdr_size = sizeof(gdFont) - sizeof(char *); + int body_size, n = 0, b, i, body_size_check; + gdFontPtr font; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &file) == FAILURE) { + return; + } + + stream = php_stream_open_wrapper(ZSTR_VAL(file), "rb", IGNORE_PATH | IGNORE_URL_WIN | REPORT_ERRORS, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* Only supports a architecture-dependent binary dump format + * at the moment. + * The file format is like this on machines with 32-byte integers: + * + * byte 0-3: (int) number of characters in the font + * byte 4-7: (int) value of first character in the font (often 32, space) + * byte 8-11: (int) pixel width of each character + * byte 12-15: (int) pixel height of each character + * bytes 16-: (char) array with character data, one byte per pixel + * in each character, for a total of + * (nchars*width*height) bytes. + */ + font = (gdFontPtr) emalloc(sizeof(gdFont)); + b = 0; + while (b < hdr_size && (n = php_stream_read(stream, (char*)&font[b], hdr_size - b))) { + b += n; + } + + if (!n) { + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading header"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading header"); + } + php_stream_close(stream); + RETURN_FALSE; + } + i = php_stream_tell(stream); + php_stream_seek(stream, 0, SEEK_END); + body_size_check = php_stream_tell(stream) - hdr_size; + php_stream_seek(stream, i, SEEK_SET); + + body_size = font->w * font->h * font->nchars; + if (body_size != body_size_check) { + font->w = FLIPWORD(font->w); + font->h = FLIPWORD(font->h); + font->nchars = FLIPWORD(font->nchars); + body_size = font->w * font->h * font->nchars; + } + + if (overflow2(font->nchars, font->h) || overflow2(font->nchars * font->h, font->w )) { + php_error_docref(NULL, E_WARNING, "Error reading font, invalid font header"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + if (body_size != body_size_check) { + php_error_docref(NULL, E_WARNING, "Error reading font"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + font->data = emalloc(body_size); + b = 0; + while (b < body_size && (n = php_stream_read(stream, &font->data[b], body_size - b))) { + b += n; + } + + if (!n) { + efree(font->data); + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading body"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading body"); + } + php_stream_close(stream); + RETURN_FALSE; + } + php_stream_close(stream); + + ind = zend_list_insert(font, le_gd_font); + + /* Adding 5 to the font index so we will never have font indices + * that overlap with the old fonts (with indices 1-5). The first + * list index given out is always 1. + */ + RETURN_LONG(Z_RES_HANDLE_P(ind) + 5); +} +/* }}} */ + +/* {{{ proto bool imagesetstyle(resource im, array styles) + Set the line drawing styles for use with imageline and IMG_COLOR_STYLED. */ +PHP_FUNCTION(imagesetstyle) +{ + zval *IM, *styles, *item; + gdImagePtr im; + int *stylearr; + int index = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &styles) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + /* copy the style values in the stylearr */ + stylearr = safe_emalloc(sizeof(int), zend_hash_num_elements(Z_ARRVAL_P(styles)), 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(styles), item) { + stylearr[index++] = zval_get_long(item); + } ZEND_HASH_FOREACH_END(); + + gdImageSetStyle(im, stylearr, index); + + efree(stylearr); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreatetruecolor(int x_size, int y_size) + Create a new true color image */ +PHP_FUNCTION(imagecreatetruecolor) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreateTrueColor(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto bool imageistruecolor(resource im) + return true if the image uses truecolor */ +PHP_FUNCTION(imageistruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(im->trueColor); +} +/* }}} */ + +/* {{{ proto void imagetruecolortopalette(resource im, bool ditherFlag, int colorsWanted) + Convert a true colour image to a palette based image with a number of colours, optionally using dithering. */ +PHP_FUNCTION(imagetruecolortopalette) +{ + zval *IM; + zend_bool dither; + zend_long ncolors; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rbl", &IM, &dither, &ncolors) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (ncolors <= 0) { + php_error_docref(NULL, E_WARNING, "Number of colors has to be greater than zero"); + RETURN_FALSE; + } + gdImageTrueColorToPalette(im, dither, ncolors); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto void imagetruecolortopalette(resource im, bool ditherFlag, int colorsWanted) + Convert a true colour image to a palette based image with a number of colours, optionally using dithering. */ +PHP_FUNCTION(imagepalettetotruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImagePaletteToTrueColor(im) == 0) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecolormatch(resource im1, resource im2) + Makes the colors of the palette version of an image more closely match the true color version */ +PHP_FUNCTION(imagecolormatch) +{ + zval *IM1, *IM2; + gdImagePtr im1, im2; + int result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM1, &IM2) == FAILURE) { + return; + } + + if ((im1 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM1), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + if ((im2 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM2), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + result = gdImageColorMatch(im1, im2); + switch (result) { + case -1: + php_error_docref(NULL, E_WARNING, "Image1 must be TrueColor" ); + RETURN_FALSE; + break; + case -2: + php_error_docref(NULL, E_WARNING, "Image2 must be Palette" ); + RETURN_FALSE; + break; + case -3: + php_error_docref(NULL, E_WARNING, "Image1 and Image2 must be the same size" ); + RETURN_FALSE; + break; + case -4: + php_error_docref(NULL, E_WARNING, "Image2 must have at least one color" ); + RETURN_FALSE; + break; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetthickness(resource im, int thickness) + Set line thickness for drawing lines, ellipses, rectangles, polygons etc. */ +PHP_FUNCTION(imagesetthickness) +{ + zval *IM; + zend_long thick; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &thick) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetThickness(im, thick); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imagefilledellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFilledEllipse(im, cx, cy, w, h, color); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledarc(resource im, int cx, int cy, int w, int h, int s, int e, int col, int style) + Draw a filled partial ellipse */ +PHP_FUNCTION(imagefilledarc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col, style; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col, &style) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageFilledArc(im, cx, cy, w, h, st, e, col, style); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagealphablending(resource im, bool on) + Turn alpha blending mode on or off for the given image */ +PHP_FUNCTION(imagealphablending) +{ + zval *IM; + zend_bool blend; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &blend) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, blend); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesavealpha(resource im, bool on) + Include alpha channel to a saved image */ +PHP_FUNCTION(imagesavealpha) +{ + zval *IM; + zend_bool save; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &save) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSaveAlpha(im, save); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagelayereffect(resource im, int effect) + Set the alpha blending flag to use the bundled libgd layering effects */ +PHP_FUNCTION(imagelayereffect) +{ + zval *IM; + zend_long effect; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &effect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, effect); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorallocatealpha(resource im, int red, int green, int blue, int alpha) + Allocate a color with an alpha level. Works for true color and palette based images */ +PHP_FUNCTION(imagecolorallocatealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + ct = gdImageColorAllocateAlpha(im, red, green, blue, alpha); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG((zend_long)ct); +} +/* }}} */ + +/* {{{ proto int imagecolorresolvealpha(resource im, int red, int green, int blue, int alpha) + Resolve/Allocate a colour with an alpha level. Works for true colour and palette based images */ +PHP_FUNCTION(imagecolorresolvealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorResolveAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosestalpha(resource im, int red, int green, int blue, int alpha) + Find the closest matching colour with alpha transparency */ +PHP_FUNCTION(imagecolorclosestalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosestAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorexactalpha(resource im, int red, int green, int blue, int alpha) + Find exact match for colour with transparency */ +PHP_FUNCTION(imagecolorexactalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorExactAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto bool imagecopyresampled(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image using resampling to help ensure clarity */ +PHP_FUNCTION(imagecopyresampled) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + gdImageCopyResampled(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + + RETURN_TRUE; +} +/* }}} */ + +#ifdef PHP_WIN32 +/* {{{ proto resource imagegrabwindow(int window_handle [, int client_area]) + Grab a window or its client area using a windows handle (HWND property in COM instance) */ +PHP_FUNCTION(imagegrabwindow) +{ + HWND window; + zend_long client_area = 0; + RECT rc = {0}; + RECT rc_win = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + HINSTANCE handle; + zend_long lwindow_handle; + typedef BOOL (WINAPI *tPrintWindow)(HWND, HDC,UINT); + tPrintWindow pPrintWindow = 0; + gdImagePtr im = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &lwindow_handle, &client_area) == FAILURE) { + RETURN_FALSE; + } + + window = (HWND) lwindow_handle; + + if (!IsWindow(window)) { + php_error_docref(NULL, E_NOTICE, "Invalid window handle"); + RETURN_FALSE; + } + + hdc = GetDC(0); + + if (client_area) { + GetClientRect(window, &rc); + Width = rc.right; + Height = rc.bottom; + } else { + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + } + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + + + handle = LoadLibrary("User32.dll"); + if ( handle == 0 ) { + goto clean; + } + pPrintWindow = (tPrintWindow) GetProcAddress(handle, "PrintWindow"); + + if ( pPrintWindow ) { + pPrintWindow(window, memDC, (UINT) client_area); + } else { + php_error_docref(NULL, E_WARNING, "Windows API too old"); + goto clean; + } + + FreeLibrary(handle); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + +clean: + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagegrabscreen() + Grab a screenshot */ +PHP_FUNCTION(imagegrabscreen) +{ + HWND window = GetDesktopWindow(); + RECT rc = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + typedef BOOL (WINAPI *tPrintWindow)(HWND, HDC,UINT); + tPrintWindow pPrintWindow = 0; + gdImagePtr im; + hdc = GetDC(0); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!hdc) { + RETURN_FALSE; + } + + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + BitBlt( memDC, 0, 0, Width, Height , hdc, rc.left, rc.top , SRCCOPY ); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ +#endif /* PHP_WIN32 */ + +/* {{{ proto resource imagerotate(resource src_im, float angle, int bgdcolor [, int ignoretransparent]) + Rotate an image using a custom angle */ +PHP_FUNCTION(imagerotate) +{ + zval *SIM; + gdImagePtr im_dst, im_src; + double degrees; + zend_long color; + zend_long ignoretransparent = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdl|l", &SIM, °rees, &color, &ignoretransparent) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + im_dst = gdImageRotateInterpolated(im_src, (const float)degrees, color); + + if (im_dst != NULL) { + RETURN_RES(zend_register_resource(im_dst, le_gd)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagesettile(resource image, resource tile) + Set the tile image to $tile when filling $image with the "IMG_COLOR_TILED" color */ +PHP_FUNCTION(imagesettile) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetTile(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetbrush(resource image, resource brush) + Set the brush image to $brush when filling $image with the "IMG_COLOR_BRUSHED" color */ +PHP_FUNCTION(imagesetbrush) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetBrush(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreate(int x_size, int y_size) + Create a new image */ +PHP_FUNCTION(imagecreate) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreate(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto int imagetypes(void) + Return the types of images supported in a bitfield - 1=GIF, 2=JPEG, 4=PNG, 8=WBMP, 16=XPM */ +PHP_FUNCTION(imagetypes) +{ + int ret=0; + ret = 1; +#ifdef HAVE_GD_JPG + ret |= 2; +#endif +#ifdef HAVE_GD_PNG + ret |= 4; +#endif + ret |= 8; +#if defined(HAVE_GD_XPM) + ret |= 16; +#endif + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ _php_ctx_getmbi + */ + +static int _php_ctx_getmbi(gdIOCtx *ctx) +{ + int i, mbi = 0; + + do { + i = (ctx->getC)(ctx); + if (i < 0) { + return -1; + } + mbi = (mbi << 7) | (i & 0x7f); + } while (i & 0x80); + + return mbi; +} +/* }}} */ + +/* {{{ _php_image_type + */ +static const char php_sig_gd2[3] = {'g', 'd', '2'}; + +static int _php_image_type (char data[8]) +{ + /* Based on ext/standard/image.c */ + + if (data == NULL) { + return -1; + } + + if (!memcmp(data, php_sig_gd2, 3)) { + return PHP_GDIMG_TYPE_GD2; + } else if (!memcmp(data, php_sig_jpg, 3)) { + return PHP_GDIMG_TYPE_JPG; + } else if (!memcmp(data, php_sig_png, 3)) { + if (!memcmp(data, php_sig_png, 8)) { + return PHP_GDIMG_TYPE_PNG; + } + } else if (!memcmp(data, php_sig_gif, 3)) { + return PHP_GDIMG_TYPE_GIF; + } + else { + gdIOCtx *io_ctx; + io_ctx = gdNewDynamicCtxEx(8, data, 0); + if (io_ctx) { + if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) { + io_ctx->gd_free(io_ctx); + return PHP_GDIMG_TYPE_WBM; + } else { + io_ctx->gd_free(io_ctx); + } + } + } + return -1; +} +/* }}} */ + +/* {{{ _php_image_create_from_string + */ +gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)()) +{ + gdImagePtr im; + gdIOCtx *io_ctx; + + io_ctx = gdNewDynamicCtxEx(Z_STRLEN_P(data), Z_STRVAL_P(data), 0); + + if (!io_ctx) { + return NULL; + } + + im = (*ioctx_func_p)(io_ctx); + if (!im) { + php_error_docref(NULL, E_WARNING, "Passed data is not in '%s' format", tn); + io_ctx->gd_free(io_ctx); + return NULL; + } + + io_ctx->gd_free(io_ctx); + + return im; +} +/* }}} */ + +/* {{{ proto resource imagecreatefromstring(string image) + Create a new image from the image stream in the string */ +PHP_FUNCTION(imagecreatefromstring) +{ + zval *data; + gdImagePtr im; + int imtype; + char sig[8]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &data) == FAILURE) { + return; + } + + convert_to_string_ex(data); + if (Z_STRLEN_P(data) < 8) { + php_error_docref(NULL, E_WARNING, "Empty string or invalid image"); + RETURN_FALSE; + } + + memcpy(sig, Z_STRVAL_P(data), 8); + + imtype = _php_image_type(sig); + + switch (imtype) { + case PHP_GDIMG_TYPE_JPG: +#ifdef HAVE_GD_JPG + im = _php_image_create_from_string(data, "JPEG", gdImageCreateFromJpegCtx); +#else + php_error_docref(NULL, E_WARNING, "No JPEG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_PNG: +#ifdef HAVE_GD_PNG + im = _php_image_create_from_string(data, "PNG", gdImageCreateFromPngCtx); +#else + php_error_docref(NULL, E_WARNING, "No PNG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_GIF: + im = _php_image_create_from_string(data, "GIF", gdImageCreateFromGifCtx); + break; + + case PHP_GDIMG_TYPE_WBM: + im = _php_image_create_from_string(data, "WBMP", gdImageCreateFromWBMPCtx); + break; + + case PHP_GDIMG_TYPE_GD2: + im = _php_image_create_from_string(data, "GD2", gdImageCreateFromGd2Ctx); + break; + + default: + php_error_docref(NULL, E_WARNING, "Data is not in a recognized format"); + RETURN_FALSE; + } + + if (!im) { + php_error_docref(NULL, E_WARNING, "Couldn't create GD Image Stream out of Data"); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ _php_image_create_from + */ +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()) +{ + char *file; + size_t file_len; + zend_long srcx, srcy, width, height; + gdImagePtr im = NULL; + php_stream *stream; + FILE * fp = NULL; +#ifdef HAVE_GD_JPG + long ignore_warning; +#endif + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pllll", &file, &file_len, &srcx, &srcy, &width, &height) == FAILURE) { + return; + } + if (width < 1 || height < 1) { + php_error_docref(NULL, E_WARNING, "Zero width or height not allowed"); + RETURN_FALSE; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &file, &file_len) == FAILURE) { + return; + } + } + + + stream = php_stream_open_wrapper(file, "rb", REPORT_ERRORS|IGNORE_PATH|IGNORE_URL_WIN, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* try and avoid allocating a FILE* if the stream is not naturally a FILE* */ + if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS)) { + goto out_err; + } + } else if (ioctx_func_p) { + /* we can create an io context */ + gdIOCtx* io_ctx; + zend_string *buff; + char *pstr; + + buff = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0); + + if (!buff) { + php_error_docref(NULL, E_WARNING,"Cannot read image data"); + goto out_err; + } + + /* needs to be malloc (persistent) - GD will free() it later */ + pstr = pestrndup(ZSTR_VAL(buff), ZSTR_LEN(buff), 1); + io_ctx = gdNewDynamicCtxEx(ZSTR_LEN(buff), pstr, 0); + if (!io_ctx) { + pefree(pstr, 1); + zend_string_release(buff); + php_error_docref(NULL, E_WARNING,"Cannot allocate GD IO context"); + goto out_err; + } + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + im = (*ioctx_func_p)(io_ctx, srcx, srcy, width, height); + } else { + im = (*ioctx_func_p)(io_ctx); + } + io_ctx->gd_free(io_ctx); + pefree(pstr, 1); + zend_string_release(buff); + } + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + /* try and force the stream to be FILE* */ + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { + goto out_err; + } + } + + if (!im && fp) { + switch (image_type) { + case PHP_GDIMG_TYPE_GD2PART: + im = (*func_p)(fp, srcx, srcy, width, height); + break; +#if defined(HAVE_GD_XPM) + case PHP_GDIMG_TYPE_XPM: + im = gdImageCreateFromXpm(file); + break; +#endif + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im = gdImageCreateFromJpegEx(fp, ignore_warning); + break; +#endif + + default: + im = (*func_p)(fp); + break; + } + + fflush(fp); + } + +/* register_im: */ + if (im) { + RETVAL_RES(zend_register_resource(im, le_gd)); + php_stream_close(stream); + return; + } + + php_error_docref(NULL, E_WARNING, "'%s' is not a valid %s file", file, tn); +out_err: + php_stream_close(stream); + RETURN_FALSE; + +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgif(string filename) + Create a new image from GIF file or URL */ +PHP_FUNCTION(imagecreatefromgif) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageCreateFromGif, gdImageCreateFromGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_JPG +/* {{{ proto resource imagecreatefromjpeg(string filename) + Create a new image from JPEG file or URL */ +PHP_FUNCTION(imagecreatefromjpeg) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageCreateFromJpeg, gdImageCreateFromJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG +/* {{{ proto resource imagecreatefrompng(string filename) + Create a new image from PNG file or URL */ +PHP_FUNCTION(imagecreatefrompng) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImageCreateFromPng, gdImageCreateFromPngCtx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + +#ifdef HAVE_GD_WEBP +/* {{{ proto resource imagecreatefromwebp(string filename) + Create a new image from WEBP file or URL */ +PHP_FUNCTION(imagecreatefromwebp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageCreateFromWebp, gdImageCreateFromWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + +/* {{{ proto resource imagecreatefromxbm(string filename) + Create a new image from XBM file or URL */ +PHP_FUNCTION(imagecreatefromxbm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageCreateFromXbm, NULL); +} +/* }}} */ + +#if defined(HAVE_GD_XPM) +/* {{{ proto resource imagecreatefromxpm(string filename) + Create a new image from XPM file or URL */ +PHP_FUNCTION(imagecreatefromxpm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XPM, "XPM", gdImageCreateFromXpm, NULL); +} +/* }}} */ +#endif + +/* {{{ proto resource imagecreatefromwbmp(string filename) + Create a new image from WBMP file or URL */ +PHP_FUNCTION(imagecreatefromwbmp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageCreateFromWBMP, gdImageCreateFromWBMPCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd(string filename) + Create a new image from GD file or URL */ +PHP_FUNCTION(imagecreatefromgd) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageCreateFromGd, gdImageCreateFromGdCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2(string filename) + Create a new image from GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageCreateFromGd2, gdImageCreateFromGd2Ctx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2part(string filename, int srcX, int srcY, int width, int height) + Create a new image from a given part of GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2part) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2PART, "GD2", gdImageCreateFromGd2Part, gdImageCreateFromGd2PartCtx); +} +/* }}} */ + +/* {{{ _php_image_output + */ +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()) +{ + zval *imgind; + char *file = NULL; + zend_long quality = 0, type = 0; + gdImagePtr im; + char *fn = NULL; + FILE *fp; + size_t file_len = 0; + int argc = ZEND_NUM_ARGS(); + int q = -1, i, t = 1; + + /* The quality parameter for Wbmp stands for the threshold when called from image2wbmp() */ + /* When called from imagewbmp() the quality parameter stands for the foreground color. Default: black. */ + /* The quality parameter for gd2 stands for chunk size */ + + if (zend_parse_parameters(argc, "r|pll", &imgind, &file, &file_len, &quality, &type) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(imgind), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + fn = file; + if (argc == 3) { + q = quality; + } + if (argc == 4) { + t = type; + } + } + + if (argc >= 2 && file_len) { + PHP_GD_CHECK_OPEN_BASEDIR(fn, "Invalid filename"); + + fp = VCWD_FOPEN(fn, "wb"); + if (!fp) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, fp); + break; + case PHP_GDIMG_TYPE_JPG: + (*func_p)(im, fp, q); + break; + case PHP_GDIMG_TYPE_WBM: + for (i = 0; i < gdImageColorsTotal(im); i++) { + if (gdImageRed(im, i) == 0) break; + } + (*func_p)(im, i, fp); + break; + case PHP_GDIMG_TYPE_GD: + if (im->trueColor){ + gdImageTrueColorToPalette(im,1,256); + } + (*func_p)(im, fp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, fp, q, t); + break; + default: + if (q == -1) { + q = 128; + } + (*func_p)(im, fp, q, t); + break; + } + fflush(fp); + fclose(fp); + } else { + int b; + FILE *tmp; + char buf[4096]; + zend_string *path; + + tmp = php_open_temporary_file(NULL, NULL, &path); + if (tmp == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open temporary file"); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, tmp); + break; + case PHP_GDIMG_TYPE_JPG: + (*func_p)(im, tmp, q); + break; + case PHP_GDIMG_TYPE_WBM: + for (i = 0; i < gdImageColorsTotal(im); i++) { + if (gdImageRed(im, i) == 0) { + break; + } + } + (*func_p)(im, q, tmp); + break; + case PHP_GDIMG_TYPE_GD: + if (im->trueColor) { + gdImageTrueColorToPalette(im,1,256); + } + (*func_p)(im, tmp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, tmp, q, t); + break; + default: + (*func_p)(im, tmp); + break; + } + + fseek(tmp, 0, SEEK_SET); + +#if APACHE && defined(CHARSET_EBCDIC) + /* XXX this is unlikely to work any more thies@thieso.net */ + + /* This is a binary file already: avoid EBCDIC->ASCII conversion */ + ap_bsetflag(php3_rqst->connection->client, B_EBCDIC2ASCII, 0); +#endif + while ((b = fread(buf, 1, sizeof(buf), tmp)) > 0) { + php_write(buf, b); + } + + fclose(tmp); + VCWD_UNLINK((const char *)ZSTR_VAL(path)); /* make sure that the temporary file is removed */ + zend_string_release(path); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagexbm(int im, string filename [, int foreground]) + Output XBM image to browser or file */ +PHP_FUNCTION(imagexbm) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageXbmCtx); +} +/* }}} */ + +/* {{{ proto bool imagegif(resource im [, string filename]) + Output GIF image to browser or file */ +PHP_FUNCTION(imagegif) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_PNG +/* {{{ proto bool imagepng(resource im [, string filename]) + Output PNG image to browser or file */ +PHP_FUNCTION(imagepng) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImagePngCtxEx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + + +#ifdef HAVE_GD_WEBP +/* {{{ proto bool imagewebp(resource im [, string filename[, int quality]] ) + Output WEBP image to browser or file */ +PHP_FUNCTION(imagewebp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + + +#ifdef HAVE_GD_JPG +/* {{{ proto bool imagejpeg(resource im [, string filename [, int quality]]) + Output JPEG image to browser or file */ +PHP_FUNCTION(imagejpeg) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +/* {{{ proto bool imagewbmp(resource im [, string filename [, int foreground]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(imagewbmp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageWBMPCtx); +} +/* }}} */ + +/* {{{ proto bool imagegd(resource im [, string filename]) + Output GD image to browser or file */ +PHP_FUNCTION(imagegd) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageGd); +} +/* }}} */ + +/* {{{ proto bool imagegd2(resource im [, string filename [, int chunk_size [, int type]]]) + Output GD2 image to browser or file */ +PHP_FUNCTION(imagegd2) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageGd2); +} +/* }}} */ + +/* {{{ proto bool imagedestroy(resource im) + Destroy an image */ +PHP_FUNCTION(imagedestroy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + zend_list_close(Z_RES_P(IM)); + + RETURN_TRUE; +} +/* }}} */ + + +/* {{{ proto int imagecolorallocate(resource im, int red, int green, int blue) + Allocate a color for an image */ +PHP_FUNCTION(imagecolorallocate) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + ct = gdImageColorAllocate(im, red, green, blue); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG(ct); +} +/* }}} */ + +/* {{{ proto void imagepalettecopy(resource dst, resource src) + Copy the palette from the src image onto the dst image */ +PHP_FUNCTION(imagepalettecopy) +{ + zval *dstim, *srcim; + gdImagePtr dst, src; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &dstim, &srcim) == FAILURE) { + return; + } + + if ((dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(dstim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(srcim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImagePaletteCopy(dst, src); +} +/* }}} */ + +/* {{{ proto int imagecolorat(resource im, int x, int y) + Get the index of the color of a pixel */ +PHP_FUNCTION(imagecolorat) +{ + zval *IM; + zend_long x, y; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll", &IM, &x, &y) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + if (im->tpixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(gdImageTrueColorPixel(im, x, y)); + } else { + php_error_docref(NULL, E_NOTICE, "%pd,%pd is out of bounds", x, y); + RETURN_FALSE; + } + } else { + if (im->pixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(im->pixels[y][x]); + } else { + php_error_docref(NULL, E_NOTICE, "%pd,%pd is out of bounds", x, y); + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto int imagecolorclosest(resource im, int red, int green, int blue) + Get the index of the closest color to the specified color */ +PHP_FUNCTION(imagecolorclosest) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosest(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosesthwb(resource im, int red, int green, int blue) + Get the index of the color which has the hue, white and blackness nearest to the given color */ +PHP_FUNCTION(imagecolorclosesthwb) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosestHWB(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto bool imagecolordeallocate(resource im, int index) + De-allocate a color for an image */ +PHP_FUNCTION(imagecolordeallocate) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + /* We can return right away for a truecolor image as deallocating colours is meaningless here */ + if (gdImageTrueColor(im)) { + RETURN_TRUE; + } + + col = index; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + gdImageColorDeallocate(im, col); + RETURN_TRUE; + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imagecolorresolve(resource im, int red, int green, int blue) + Get the index of the specified color or its closest possible alternative */ +PHP_FUNCTION(imagecolorresolve) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorResolve(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorexact(resource im, int red, int green, int blue) + Get the index of the specified color */ +PHP_FUNCTION(imagecolorexact) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorExact(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto void imagecolorset(resource im, int col, int red, int green, int blue) + Set the color for the specified palette index */ +PHP_FUNCTION(imagecolorset) +{ + zval *IM; + zend_long color, red, green, blue, alpha = 0; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &IM, &color, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = color; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + im->red[col] = red; + im->green[col] = green; + im->blue[col] = blue; + im->alpha[col] = alpha; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imagecolorsforindex(resource im, int col) + Get the colors for an index */ +PHP_FUNCTION(imagecolorsforindex) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = index; + + if ((col >= 0 && gdImageTrueColor(im)) || (!gdImageTrueColor(im) && col >= 0 && col < gdImageColorsTotal(im))) { + array_init(return_value); + + add_assoc_long(return_value,"red", gdImageRed(im,col)); + add_assoc_long(return_value,"green", gdImageGreen(im,col)); + add_assoc_long(return_value,"blue", gdImageBlue(im,col)); + add_assoc_long(return_value,"alpha", gdImageAlpha(im,col)); + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagegammacorrect(resource im, float inputgamma, float outputgamma) + Apply a gamma correction to a GD image */ +PHP_FUNCTION(imagegammacorrect) +{ + zval *IM; + gdImagePtr im; + int i; + double input, output; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &IM, &input, &output) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + int x, y, c; + + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + c = gdImageGetPixel(im, x, y); + gdImageSetPixel(im, x, y, + gdTrueColorAlpha( + (int) ((pow((pow((gdTrueColorGetRed(c) / 255.0), input)), 1.0 / output) * 255) + .5), + (int) ((pow((pow((gdTrueColorGetGreen(c) / 255.0), input)), 1.0 / output) * 255) + .5), + (int) ((pow((pow((gdTrueColorGetBlue(c) / 255.0), input)), 1.0 / output) * 255) + .5), + gdTrueColorGetAlpha(c) + ) + ); + } + } + RETURN_TRUE; + } + + for (i = 0; i < gdImageColorsTotal(im); i++) { + im->red[i] = (int)((pow((pow((im->red[i] / 255.0), input)), 1.0 / output) * 255) + .5); + im->green[i] = (int)((pow((pow((im->green[i] / 255.0), input)), 1.0 / output) * 255) + .5); + im->blue[i] = (int)((pow((pow((im->blue[i] / 255.0), input)), 1.0 / output) * 255) + .5); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetpixel(resource im, int x, int y, int col) + Set a single pixel */ +PHP_FUNCTION(imagesetpixel) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &x, &y, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetPixel(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a line */ +PHP_FUNCTION(imageline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + +#ifdef HAVE_GD_BUNDLED + if (im->antialias) { + gdImageAALine(im, x1, y1, x2, y2, col); + } else +#endif + { + gdImageLine(im, x1, y1, x2, y2, col); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagedashedline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a dashed line */ +PHP_FUNCTION(imagedashedline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageDashedLine(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagerectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a rectangle */ +PHP_FUNCTION(imagerectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledrectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a filled rectangle */ +PHP_FUNCTION(imagefilledrectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + gdImageFilledRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagearc(resource im, int cx, int cy, int w, int h, int s, int e, int col) + Draw a partial ellipse */ +PHP_FUNCTION(imagearc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageArc(im, cx, cy, w, h, st, e, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imageellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageEllipse(im, cx, cy, w, h, color); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilltoborder(resource im, int x, int y, int border, int col) + Flood fill to specific color */ +PHP_FUNCTION(imagefilltoborder) +{ + zval *IM; + zend_long x, y, border, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &x, &y, &border, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFillToBorder(im, x, y, border, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefill(resource im, int x, int y, int col) + Flood fill */ +PHP_FUNCTION(imagefill) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &x, &y, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFill(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorstotal(resource im) + Find out the number of colors in an image's palette */ +PHP_FUNCTION(imagecolorstotal) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorsTotal(im)); +} +/* }}} */ + +/* {{{ proto int imagecolortransparent(resource im [, int col]) + Define a color as transparent */ +PHP_FUNCTION(imagecolortransparent) +{ + zval *IM; + zend_long COL = 0; + gdImagePtr im; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &IM, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageColorTransparent(im, COL); + } + + RETURN_LONG(gdImageGetTransparent(im)); +} +/* }}} */ + +/* {{{ proto int imageinterlace(resource im [, int interlace]) + Enable or disable interlace */ +PHP_FUNCTION(imageinterlace) +{ + zval *IM; + int argc = ZEND_NUM_ARGS(); + zend_long INT = 0; + gdImagePtr im; + + if (zend_parse_parameters(argc, "r|l", &IM, &INT) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageInterlace(im, INT); + } + + RETURN_LONG(gdImageGetInterlaced(im)); +} +/* }}} */ + +/* {{{ php_imagepolygon + arg = 0 normal polygon + arg = 1 filled polygon */ +/* im, points, num_points, col */ +static void php_imagepolygon(INTERNAL_FUNCTION_PARAMETERS, int filled) +{ + zval *IM, *POINTS; + zend_long NPOINTS, COL; + zval *var = NULL; + gdImagePtr im; + gdPointPtr points; + int npoints, col, nelem, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rall", &IM, &POINTS, &NPOINTS, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + npoints = NPOINTS; + col = COL; + + nelem = zend_hash_num_elements(Z_ARRVAL_P(POINTS)); + if (nelem < 6) { + php_error_docref(NULL, E_WARNING, "You must have at least 3 points in your array"); + RETURN_FALSE; + } + if (npoints <= 0) { + php_error_docref(NULL, E_WARNING, "You must give a positive number of points"); + RETURN_FALSE; + } + if (nelem < npoints * 2) { + php_error_docref(NULL, E_WARNING, "Trying to use %d points in array with only %d points", npoints, nelem/2); + RETURN_FALSE; + } + + points = (gdPointPtr) safe_emalloc(npoints, sizeof(gdPoint), 0); + + for (i = 0; i < npoints; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2))) != NULL) { + points[i].x = zval_get_long(var); + } + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2) + 1)) != NULL) { + points[i].y = zval_get_long(var); + } + } + + if (filled) { + gdImageFilledPolygon(im, points, npoints, col); + } else { + gdImagePolygon(im, points, npoints, col); + } + + efree(points); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagepolygon(resource im, array point, int num_points, int col) + Draw a polygon */ +PHP_FUNCTION(imagepolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imagefilledpolygon(resource im, array point, int num_points, int col) + Draw a filled polygon */ +PHP_FUNCTION(imagefilledpolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_find_gd_font + */ +static gdFontPtr php_find_gd_font(int size) +{ + gdFontPtr font; + + switch (size) { + case 1: + font = gdFontTiny; + break; + case 2: + font = gdFontSmall; + break; + case 3: + font = gdFontMediumBold; + break; + case 4: + font = gdFontLarge; + break; + case 5: + font = gdFontGiant; + break; + default: { + zval *zv = zend_hash_index_find(&EG(regular_list), size - 5); + if (!zv || (Z_RES_P(zv))->type != le_gd_font) { + if (size < 1) { + font = gdFontTiny; + } else { + font = gdFontGiant; + } + } else { + font = (gdFontPtr)Z_RES_P(zv)->ptr; + } + } + break; + } + + return font; +} +/* }}} */ + +/* {{{ php_imagefontsize + * arg = 0 ImageFontWidth + * arg = 1 ImageFontHeight + */ +static void php_imagefontsize(INTERNAL_FUNCTION_PARAMETERS, int arg) +{ + zend_long SIZE; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &SIZE) == FAILURE) { + return; + } + + font = php_find_gd_font(SIZE); + RETURN_LONG(arg ? font->h : font->w); +} +/* }}} */ + +/* {{{ proto int imagefontwidth(int font) + Get font width */ +PHP_FUNCTION(imagefontwidth) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto int imagefontheight(int font) + Get font height */ +PHP_FUNCTION(imagefontheight) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_gdimagecharup + * workaround for a bug in gd 1.2 */ +static void php_gdimagecharup(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) +{ + int cx, cy, px, py, fline; + cx = 0; + cy = 0; + + if ((c < f->offset) || (c >= (f->offset + f->nchars))) { + return; + } + + fline = (c - f->offset) * f->h * f->w; + for (py = y; (py > (y - f->w)); py--) { + for (px = x; (px < (x + f->h)); px++) { + if (f->data[fline + cy * f->w + cx]) { + gdImageSetPixel(im, px, py, color); + } + cy++; + } + cy = 0; + cx++; + } +} +/* }}} */ + +/* {{{ php_imagechar + * arg = 0 ImageChar + * arg = 1 ImageCharUp + * arg = 2 ImageString + * arg = 3 ImageStringUp + */ +static void php_imagechar(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zval *IM; + zend_long SIZE, X, Y, COL; + char *C; + size_t C_len; + gdImagePtr im; + int ch = 0, col, x, y, size, i, l = 0; + unsigned char *str = NULL; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllsl", &IM, &SIZE, &X, &Y, &C, &C_len, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = COL; + + if (mode < 2) { + ch = (int)((unsigned char)*C); + } else { + str = (unsigned char *) estrndup(C, C_len); + l = strlen((char *)str); + } + + y = Y; + x = X; + size = SIZE; + + font = php_find_gd_font(size); + + switch (mode) { + case 0: + gdImageChar(im, font, x, y, ch, col); + break; + case 1: + php_gdimagecharup(im, font, x, y, ch, col); + break; + case 2: + for (i = 0; (i < l); i++) { + gdImageChar(im, font, x, y, (int) ((unsigned char) str[i]), col); + x += font->w; + } + break; + case 3: { + for (i = 0; (i < l); i++) { + /* php_gdimagecharup(im, font, x, y, (int) str[i], col); */ + gdImageCharUp(im, font, x, y, (int) str[i], col); + y -= font->w; + } + break; + } + } + if (str) { + efree(str); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagechar(resource im, int font, int x, int y, string c, int col) + Draw a character */ +PHP_FUNCTION(imagechar) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imagecharup(resource im, int font, int x, int y, string c, int col) + Draw a character rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagecharup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto bool imagestring(resource im, int font, int x, int y, string str, int col) + Draw a string horizontally */ +PHP_FUNCTION(imagestring) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto bool imagestringup(resource im, int font, int x, int y, string str, int col) + Draw a string vertically - rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagestringup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto bool imagecopy(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h) + Copy part of an image */ +PHP_FUNCTION(imagecopy) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + + gdImageCopy(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymerge(resource src_im, resource dst_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymerge) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMerge(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymergegray(resource src_im, resource dst_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymergegray) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMergeGray(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopyresized(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image */ +PHP_FUNCTION(imagecopyresized) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + if (dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + gdImageCopyResized(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagesx(resource im) + Get image width */ +PHP_FUNCTION(imagesx) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSX(im)); +} +/* }}} */ + +/* {{{ proto int imagesy(resource im) + Get image height */ +PHP_FUNCTION(imagesy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSY(im)); +} +/* }}} */ + +#ifdef ENABLE_GD_TTF +#define TTFTEXT_DRAW 0 +#define TTFTEXT_BBOX 1 +#endif + +#ifdef ENABLE_GD_TTF + +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE +/* {{{ proto array imageftbbox(float size, float angle, string font_file, string text [, array extrainfo]) + Give the bounding box of a text using fonts via freetype2 */ +PHP_FUNCTION(imageftbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 1); +} +/* }}} */ + +/* {{{ proto array imagefttext(resource im, float size, float angle, int x, int y, int col, string font_file, string text [, array extrainfo]) + Write text to the image using fonts via freetype2 */ +PHP_FUNCTION(imagefttext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 1); +} +/* }}} */ +#endif /* HAVE_GD_FREETYPE && HAVE_LIBFREETYPE */ + +/* {{{ proto array imagettfbbox(float size, float angle, string font_file, string text) + Give the bounding box of a text using TrueType fonts */ +PHP_FUNCTION(imagettfbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 0); +} +/* }}} */ + +/* {{{ proto array imagettftext(resource im, float size, float angle, int x, int y, int col, string font_file, string text) + Write text to the image using a TrueType font */ +PHP_FUNCTION(imagettftext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 0); +} +/* }}} */ + +/* {{{ php_imagettftext_common + */ +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int mode, int extended) +{ + zval *IM, *EXT = NULL; + gdImagePtr im=NULL; + zend_long col = -1, x = -1, y = -1; + size_t str_len, fontname_len; + int i, brect[8]; + double ptsize, angle; + char *str = NULL, *fontname = NULL; + char *error = NULL; + int argc = ZEND_NUM_ARGS(); + gdFTStringExtra strex = {0}; + + if (mode == TTFTEXT_BBOX) { + if (argc < 4 || argc > ((extended) ? 5 : 4)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "ddss|a", &ptsize, &angle, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + } else { + if (argc < 8 || argc > ((extended) ? 9 : 8)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "rddlllss|a", &IM, &ptsize, &angle, &x, &y, &col, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + } + + /* convert angle to radians */ + angle = angle * (M_PI/180); + + if (extended && EXT) { /* parse extended info */ + zval *item; + zend_string *key; + + /* walk the assoc array */ + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(EXT), key, item) { + if (key == NULL) { + continue; + } + if (strcmp("linespacing", ZSTR_VAL(key)) == 0) { + strex.flags |= gdFTEX_LINESPACE; + strex.linespacing = zval_get_double(item); + } + } ZEND_HASH_FOREACH_END(); + } + +#ifdef VIRTUAL_DIR + { + char tmp_font_path[MAXPATHLEN]; + + if (!VCWD_REALPATH(fontname, tmp_font_path)) { + fontname = NULL; + } + } +#endif /* VIRTUAL_DIR */ + + PHP_GD_CHECK_OPEN_BASEDIR(fontname, "Invalid font filename"); + +#ifdef HAVE_GD_FREETYPE + if (extended) { + error = gdImageStringFTEx(im, brect, col, fontname, ptsize, angle, x, y, str, &strex); + } + else + error = gdImageStringFT(im, brect, col, fontname, ptsize, angle, x, y, str); + +#endif /* HAVE_GD_FREETYPE */ + + if (error) { + php_error_docref(NULL, E_WARNING, "%s", error); + RETURN_FALSE; + } + + array_init(return_value); + + /* return array with the text's bounding box */ + for (i = 0; i < 8; i++) { + add_next_index_long(return_value, brect[i]); + } +} +/* }}} */ +#endif /* ENABLE_GD_TTF */ + +/* {{{ proto bool image2wbmp(resource im [, string filename [, int threshold]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(image2wbmp) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_CONVERT_WBM, "WBMP", _php_image_bw_convert); +} +/* }}} */ + +#if defined(HAVE_GD_JPG) +/* {{{ proto bool jpeg2wbmp (string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert JPEG image to WBMP image */ +PHP_FUNCTION(jpeg2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG); +} +/* }}} */ +#endif + +#if defined(HAVE_GD_PNG) +/* {{{ proto bool png2wbmp (string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert PNG image to WBMP image */ +PHP_FUNCTION(png2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG); +} +/* }}} */ +#endif + +/* {{{ _php_image_bw_convert + * It converts a gd Image to bw using a threshold value */ +static void _php_image_bw_convert(gdImagePtr im_org, gdIOCtx *out, int threshold) +{ + gdImagePtr im_dest; + int white, black; + int color, color_org, median; + int dest_height = gdImageSY(im_org); + int dest_width = gdImageSX(im_org); + int x, y; + + im_dest = gdImageCreate(dest_width, dest_height); + if (im_dest == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to allocate temporary buffer"); + return; + } + + white = gdImageColorAllocate(im_dest, 255, 255, 255); + if (white == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + return; + } + + black = gdImageColorAllocate(im_dest, 0, 0, 0); + if (black == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + return; + } + + if (im_org->trueColor) { + gdImageTrueColorToPalette(im_org, 1, 256); + } + + for (y = 0; y < dest_height; y++) { + for (x = 0; x < dest_width; x++) { + color_org = gdImageGetPixel(im_org, x, y); + median = (im_org->red[color_org] + im_org->green[color_org] + im_org->blue[color_org]) / 3; + if (median < threshold) { + color = black; + } else { + color = white; + } + gdImageSetPixel (im_dest, x, y, color); + } + } + gdImageWBMPCtx (im_dest, black, out); + +} +/* }}} */ + +/* {{{ _php_image_convert + * _php_image_convert converts jpeg/png images to wbmp and resizes them as needed */ +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type ) +{ + char *f_org, *f_dest; + size_t f_org_len, f_dest_len; + zend_long height, width, threshold; + gdImagePtr im_org, im_dest, im_tmp; + char *fn_org = NULL; + char *fn_dest = NULL; + FILE *org, *dest; + int dest_height = -1; + int dest_width = -1; + int org_height, org_width; + int white, black; + int color, color_org, median; + int int_threshold; + int x, y; + float x_ratio, y_ratio; +#ifdef HAVE_GD_JPG + zend_long ignore_warning; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pplll", &f_org, &f_org_len, &f_dest, &f_dest_len, &height, &width, &threshold) == FAILURE) { + return; + } + + fn_org = f_org; + fn_dest = f_dest; + dest_height = height; + dest_width = width; + int_threshold = threshold; + + /* Check threshold value */ + if (int_threshold < 0 || int_threshold > 8) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'", int_threshold); + RETURN_FALSE; + } + + /* Check origin file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_org, "Invalid origin filename"); + + /* Check destination file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_dest, "Invalid destination filename"); + + /* Open origin file */ + org = VCWD_FOPEN(fn_org, "rb"); + if (!org) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for reading", fn_org); + RETURN_FALSE; + } + + /* Open destination file */ + dest = VCWD_FOPEN(fn_dest, "wb"); + if (!dest) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn_dest); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_TYPE_GIF: + im_org = gdImageCreateFromGif(org); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid GIF file", fn_dest); + RETURN_FALSE; + } + break; + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im_org = gdImageCreateFromJpegEx(org, ignore_warning); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid JPEG file", fn_dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG + case PHP_GDIMG_TYPE_PNG: + im_org = gdImageCreateFromPng(org); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid PNG file", fn_dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_PNG */ + + default: + php_error_docref(NULL, E_WARNING, "Format not supported"); + RETURN_FALSE; + break; + } + + org_width = gdImageSX (im_org); + org_height = gdImageSY (im_org); + + x_ratio = (float) org_width / (float) dest_width; + y_ratio = (float) org_height / (float) dest_height; + + if (x_ratio > 1 && y_ratio > 1) { + if (y_ratio > x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width / x_ratio); + dest_height = (int) (org_height / y_ratio); + } else { + x_ratio = (float) dest_width / (float) org_width; + y_ratio = (float) dest_height / (float) org_height; + + if (y_ratio < x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width * x_ratio); + dest_height = (int) (org_height * y_ratio); + } + + im_tmp = gdImageCreate (dest_width, dest_height); + if (im_tmp == NULL ) { + php_error_docref(NULL, E_WARNING, "Unable to allocate temporary buffer"); + RETURN_FALSE; + } + + gdImageCopyResized (im_tmp, im_org, 0, 0, 0, 0, dest_width, dest_height, org_width, org_height); + + gdImageDestroy(im_org); + + fclose(org); + + im_dest = gdImageCreate(dest_width, dest_height); + if (im_dest == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to allocate destination buffer"); + RETURN_FALSE; + } + + white = gdImageColorAllocate(im_dest, 255, 255, 255); + if (white == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + RETURN_FALSE; + } + + black = gdImageColorAllocate(im_dest, 0, 0, 0); + if (black == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + RETURN_FALSE; + } + + int_threshold = int_threshold * 32; + + for (y = 0; y < dest_height; y++) { + for (x = 0; x < dest_width; x++) { + color_org = gdImageGetPixel (im_tmp, x, y); + median = (im_tmp->red[color_org] + im_tmp->green[color_org] + im_tmp->blue[color_org]) / 3; + if (median < int_threshold) { + color = black; + } else { + color = white; + } + gdImageSetPixel (im_dest, x, y, color); + } + } + + gdImageDestroy (im_tmp ); + + gdImageWBMP(im_dest, black , dest); + + fflush(dest); + fclose(dest); + + gdImageDestroy(im_dest); + + RETURN_TRUE; +} +/* }}} */ + +/* Section Filters */ +#define PHP_GD_SINGLE_RES \ + zval *SIM; \ + gdImagePtr im_src; \ + if (zend_parse_parameters(1, "r", &SIM) == FAILURE) { \ + RETURN_FALSE; \ + } \ + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { \ + RETURN_FALSE; \ + } + +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageNegate(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGrayScale(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long brightness, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zll", &SIM, &tmp, &brightness) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageBrightness(im_src, (int)brightness) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long contrast, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll", &SIM, &tmp, &contrast) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageContrast(im_src, (int)contrast) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long r,g,b,tmp; + zend_long a = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &SIM, &tmp, &r, &g, &b, &a) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageColor(im_src, (int) r, (int) g, (int) b, (int) a) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEdgeDetectQuick(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEmboss(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGaussianBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageSelectiveBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageMeanRemoval(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + zend_long tmp; + gdImagePtr im_src; + double weight; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rld", &SIM, &tmp, &weight) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageSmooth(im_src, (float)weight)==1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *IM; + gdImagePtr im; + zend_long tmp, blocksize; + zend_bool mode = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll|b", &IM, &tmp, &blocksize, &mode) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im == NULL) { + RETURN_FALSE; + } + + if (gdImagePixelate(im, (int) blocksize, (const unsigned int) mode)) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +/* {{{ proto bool imagefilter(resource src_im, int filtertype[, int arg1 [, int arg2 [, int arg3 [, int arg4 ]]]] ) + Applies Filter an image using a custom angle */ +PHP_FUNCTION(imagefilter) +{ + zval *tmp; + + typedef void (*image_filter)(INTERNAL_FUNCTION_PARAMETERS); + zend_long filtertype; + image_filter filters[] = + { + php_image_filter_negate , + php_image_filter_grayscale, + php_image_filter_brightness, + php_image_filter_contrast, + php_image_filter_colorize, + php_image_filter_edgedetect, + php_image_filter_emboss, + php_image_filter_gaussian_blur, + php_image_filter_selective_blur, + php_image_filter_mean_removal, + php_image_filter_smooth, + php_image_filter_pixelate + }; + + if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > IMAGE_FILTER_MAX_ARGS) { + WRONG_PARAM_COUNT; + } else if (zend_parse_parameters(2, "rl", &tmp, &filtertype) == FAILURE) { + return; + } + + if (filtertype >= 0 && filtertype <= IMAGE_FILTER_MAX) { + filters[filtertype](INTERNAL_FUNCTION_PARAM_PASSTHRU); + } +} +/* }}} */ + +/* {{{ proto resource imageconvolution(resource src_im, array matrix3x3, double div, double offset) + Apply a 3x3 convolution matrix, using coefficient div and offset */ +PHP_FUNCTION(imageconvolution) +{ + zval *SIM, *hash_matrix; + zval *var = NULL, *var2 = NULL; + gdImagePtr im_src = NULL; + double div, offset; + int nelem, i, j, res; + float matrix[3][3] = {{0,0,0}, {0,0,0}, {0,0,0}}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "radd", &SIM, &hash_matrix, &div, &offset) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + nelem = zend_hash_num_elements(Z_ARRVAL_P(hash_matrix)); + if (nelem != 3) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (i=0; i<3; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(hash_matrix), (i))) != NULL && Z_TYPE_P(var) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(var)) != 3 ) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (j=0; j<3; j++) { + if ((var2 = zend_hash_index_find(Z_ARRVAL_P(var), j)) != NULL) { + matrix[i][j] = (float) zval_get_double(var2); + } else { + php_error_docref(NULL, E_WARNING, "You must have a 3x3 matrix"); + RETURN_FALSE; + } + } + } + } + res = gdImageConvolution(im_src, matrix, (float)div, (float)offset); + + if (res) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ +/* End section: Filters */ + +/* {{{ proto void imageflip(resource im, int mode) + Flip an image (in place) horizontally, vertically or both directions. */ +PHP_FUNCTION(imageflip) +{ + zval *IM; + zend_long mode; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &mode) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case GD_FLIP_VERTICAL: + gdImageFlipVertical(im); + break; + + case GD_FLIP_HORINZONTAL: + gdImageFlipHorizontal(im); + break; + + case GD_FLIP_BOTH: + gdImageFlipBoth(im); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown flip mode"); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +#ifdef HAVE_GD_BUNDLED +/* {{{ proto bool imageantialias(resource im, bool on) + Should antialiased functions used or not*/ +PHP_FUNCTION(imageantialias) +{ + zval *IM; + zend_bool alias; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &alias) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + gdImageAntialias(im, alias); + RETURN_TRUE; +} +/* }}} */ +#endif + +/* {{{ proto void imagecrop(resource im, array rect) + Crop an image using the given coordinates and size, x, y, width and height. */ +PHP_FUNCTION(imagecrop) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_crop; + gdRect rect; + zval *z_rect; + zval *tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &z_rect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") -1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + + im_crop = gdImageCrop(im, &rect); + + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto void imagecropauto(resource im [, int mode [, float threshold [, int color]]]) + Crop an image automatically using one of the available modes. */ +PHP_FUNCTION(imagecropauto) +{ + zval *IM; + zend_long mode = -1; + zend_long color = -1; + double threshold = 0.5f; + gdImagePtr im; + gdImagePtr im_crop; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|ldl", &IM, &mode, &threshold, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case -1: + mode = GD_CROP_DEFAULT; + case GD_CROP_DEFAULT: + case GD_CROP_TRANSPARENT: + case GD_CROP_BLACK: + case GD_CROP_WHITE: + case GD_CROP_SIDES: + im_crop = gdImageCropAuto(im, mode); + break; + + case GD_CROP_THRESHOLD: + if (color < 0) { + php_error_docref(NULL, E_WARNING, "Color argument missing with threshold mode"); + RETURN_FALSE; + } + im_crop = gdImageCropThreshold(im, color, (float) threshold); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown crop mode"); + RETURN_FALSE; + } + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagescale(resource im, int new_width[, int new_height[, int method]]) + Scale an image using the given new width and height. */ +PHP_FUNCTION(imagescale) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_scaled = NULL; + int new_width, new_height; + zend_long tmp_w, tmp_h=-1, tmp_m = GD_BILINEAR_FIXED; + gdInterpolationMethod method; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|ll", &IM, &tmp_w, &tmp_h, &tmp_m) == FAILURE) { + return; + } + method = tmp_m; + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (tmp_h < 0) { + /* preserve ratio */ + long src_x, src_y; + + src_x = gdImageSX(im); + src_y = gdImageSY(im); + if (src_x) { + tmp_h = tmp_w * src_y / src_x; + } + } + + new_width = tmp_w; + new_height = tmp_h; + + if (gdImageSetInterpolationMethod(im, method)) { + im_scaled = gdImageScale(im, new_width, new_height); + } + + if (im_scaled == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_scaled, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imageaffine(resource src, array affine[, array clip]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffine) +{ + zval *IM; + gdImagePtr src; + gdImagePtr dst; + gdRect rect; + gdRectPtr pRect = NULL; + zval *z_rect = NULL; + zval *z_affine; + zval *tmp; + double affine[6]; + int i, nelems; + zval *zval_affine_elem = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra|a", &IM, &z_affine, &z_rect) == FAILURE) { + return; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_affine))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine array must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < nelems; i++) { + if ((zval_affine_elem = zend_hash_index_find(Z_ARRVAL_P(z_affine), i)) != NULL) { + switch (Z_TYPE_P(zval_affine_elem)) { + case IS_LONG: + affine[i] = Z_LVAL_P(zval_affine_elem); + break; + case IS_DOUBLE: + affine[i] = Z_DVAL_P(zval_affine_elem); + break; + case IS_STRING: + affine[i] = zval_get_double(zval_affine_elem); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (z_rect != NULL) { + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") - 1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + pRect = ▭ + } else { + rect.x = -1; + rect.y = -1; + rect.width = gdImageSX(src); + rect.height = gdImageSY(src); + pRect = NULL; + } + + if (gdTransformAffineGetImage(&dst, src, pRect, affine) != GD_TRUE) { + RETURN_FALSE; + } + + if (dst == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(dst, le_gd)); + } +} +/* }}} */ + +/* {{{ proto array imageaffinematrixget(int type[, array options]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffinematrixget) +{ + double affine[6]; + zend_long type; + zval *options = NULL; + zval *tmp; + int res = GD_FALSE, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z", &type, &options) == FAILURE) { + return; + } + + switch((gdAffineStandardMatrix)type) { + case GD_AFFINE_TRANSLATE: + case GD_AFFINE_SCALE: { + double x, y; + if (!options || Z_TYPE_P(options) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Array expected as options"); + RETURN_FALSE; + } + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "x", sizeof("x") - 1)) != NULL) { + x = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "y", sizeof("y") - 1)) != NULL) { + y = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if (type == GD_AFFINE_TRANSLATE) { + res = gdAffineTranslate(affine, x, y); + } else { + res = gdAffineScale(affine, x, y); + } + break; + } + + case GD_AFFINE_ROTATE: + case GD_AFFINE_SHEAR_HORIZONTAL: + case GD_AFFINE_SHEAR_VERTICAL: { + double angle; + + if (!options) { + php_error_docref(NULL, E_WARNING, "Number is expected as option"); + RETURN_FALSE; + } + + angle = zval_get_double(options); + + if (type == GD_AFFINE_SHEAR_HORIZONTAL) { + res = gdAffineShearHorizontal(affine, angle); + } else if (type == GD_AFFINE_SHEAR_VERTICAL) { + res = gdAffineShearVertical(affine, angle); + } else { + res = gdAffineRotate(affine, angle); + } + break; + } + + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %li", type); + RETURN_FALSE; + } + + if (res == GD_FALSE) { + RETURN_FALSE; + } else { + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, affine[i]); + } + } +} /* }}} */ + +/* {{{ proto array imageaffineconcat(array m1, array m2) + Concat two matrices (as in doing many ops in one go) */ +PHP_FUNCTION(imageaffinematrixconcat) +{ + double m1[6]; + double m2[6]; + double mr[6]; + + zval *tmp; + zval *z_m1; + zval *z_m2; + int i, nelems; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &z_m1, &z_m2) == FAILURE) { + return; + } + + if (((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m1))) != 6) || (nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m2))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine arrays must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < 6; i++) { + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m1), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m1[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m1[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m1[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m2), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m2[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m2[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m2[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (gdAffineConcat (mr, m1, m2) != GD_TRUE) { + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, mr[i]); + } +} /* }}} */ + +/* {{{ proto resource imagesetinterpolation(resource im [, int method]]) + Set the default interpolation method, passing -1 or 0 sets it to the libgd default (bilinear). */ +PHP_FUNCTION(imagesetinterpolation) +{ + zval *IM; + gdImagePtr im; + zend_long method = GD_BILINEAR_FIXED; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &IM, &method) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (method == -1) { + method = GD_BILINEAR_FIXED; + } + RETURN_BOOL(gdImageSetInterpolationMethod(im, (gdInterpolationMethod) method)); +} +/* }}} */ + +/* + * 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/gd/tests/bug72697.phpt b/ext/gd/tests/bug72697.phpt new file mode 100644 index 0000000000000..6110385fcb807 --- /dev/null +++ b/ext/gd/tests/bug72697.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #72697: select_colors write out-of-bounds +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +Warning: imagetruecolortopalette(): Number of colors has to be greater than zero and no more than 2147483647 in %sbug72697.php on line %d +DONE \ No newline at end of file From 10c6f8a212c08877d95943931f4aa32a54a421d1 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:30 +0000 Subject: [PATCH 12/46] commit patch 17972441 --- ext/standard/tests/url/bug73192.phpt | 30 +++++++++++++++++++ .../tests/url/parse_url_basic_001.phpt | 20 ++----------- .../tests/url/parse_url_basic_002.phpt | 4 +-- .../tests/url/parse_url_basic_003.phpt | 4 +-- .../tests/url/parse_url_basic_004.phpt | 4 +-- .../tests/url/parse_url_basic_005.phpt | 4 +-- .../tests/url/parse_url_basic_006.phpt | 4 +-- .../tests/url/parse_url_basic_007.phpt | 4 +-- .../tests/url/parse_url_basic_008.phpt | 4 +-- .../tests/url/parse_url_basic_009.phpt | 4 +-- ext/standard/url.c | 23 +------------- 11 files changed, 49 insertions(+), 56 deletions(-) create mode 100644 ext/standard/tests/url/bug73192.phpt diff --git a/ext/standard/tests/url/bug73192.phpt b/ext/standard/tests/url/bug73192.phpt new file mode 100644 index 0000000000000..6ecb47718d04e --- /dev/null +++ b/ext/standard/tests/url/bug73192.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug #73192: parse_url return wrong hostname +--FILE-- + +--EXPECT-- +array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "example.com" + ["port"]=> + int(80) + ["fragment"]=> + string(12) "@google.com/" +} +array(4) { + ["scheme"]=> + string(4) "http" + ["host"]=> + string(11) "example.com" + ["port"]=> + int(80) + ["query"]=> + string(12) "@google.com/" +} diff --git a/ext/standard/tests/url/parse_url_basic_001.phpt b/ext/standard/tests/url/parse_url_basic_001.phpt index 0708691fe3ac8..e468066a421a7 100644 --- a/ext/standard/tests/url/parse_url_basic_001.phpt +++ b/ext/standard/tests/url/parse_url_basic_001.phpt @@ -763,25 +763,9 @@ echo "Done"; int(6) } ---> http://?:/: array(3) { - ["scheme"]=> - string(4) "http" - ["host"]=> - string(1) "?" - ["path"]=> - string(1) "/" -} +--> http://?:/: bool(false) ---> http://@?:/: array(4) { - ["scheme"]=> - string(4) "http" - ["host"]=> - string(1) "?" - ["user"]=> - string(0) "" - ["path"]=> - string(1) "/" -} +--> http://@?:/: bool(false) --> file:///:: array(2) { ["scheme"]=> diff --git a/ext/standard/tests/url/parse_url_basic_002.phpt b/ext/standard/tests/url/parse_url_basic_002.phpt index c05d1f487ab2b..f222ffccf3853 100644 --- a/ext/standard/tests/url/parse_url_basic_002.phpt +++ b/ext/standard/tests/url/parse_url_basic_002.phpt @@ -98,8 +98,8 @@ echo "Done"; --> http://::? : string(4) "http" --> http://::# : string(4) "http" --> x://::6.5 : string(1) "x" ---> http://?:/ : string(4) "http" ---> http://@?:/ : string(4) "http" +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : string(4) "file" --> file:///a:/ : string(4) "file" --> file:///ab:/ : string(4) "file" diff --git a/ext/standard/tests/url/parse_url_basic_003.phpt b/ext/standard/tests/url/parse_url_basic_003.phpt index 88eda504d561a..70dc4bb90b6b6 100644 --- a/ext/standard/tests/url/parse_url_basic_003.phpt +++ b/ext/standard/tests/url/parse_url_basic_003.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : string(1) ":" --> http://::# : string(1) ":" --> x://::6.5 : string(1) ":" ---> http://?:/ : string(1) "?" ---> http://@?:/ : string(1) "?" +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/tests/url/parse_url_basic_004.phpt b/ext/standard/tests/url/parse_url_basic_004.phpt index e3b9abd91ccf5..7ddddaf716183 100644 --- a/ext/standard/tests/url/parse_url_basic_004.phpt +++ b/ext/standard/tests/url/parse_url_basic_004.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : int(6) ---> http://?:/ : NULL ---> http://@?:/ : NULL +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/tests/url/parse_url_basic_005.phpt b/ext/standard/tests/url/parse_url_basic_005.phpt index 5b2cb98f8bfc8..b2ca06ff9603e 100644 --- a/ext/standard/tests/url/parse_url_basic_005.phpt +++ b/ext/standard/tests/url/parse_url_basic_005.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : NULL ---> http://?:/ : NULL ---> http://@?:/ : string(0) "" +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/tests/url/parse_url_basic_006.phpt b/ext/standard/tests/url/parse_url_basic_006.phpt index 79af6b8b62674..f0c251bb55629 100644 --- a/ext/standard/tests/url/parse_url_basic_006.phpt +++ b/ext/standard/tests/url/parse_url_basic_006.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : NULL ---> http://?:/ : NULL ---> http://@?:/ : NULL +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/tests/url/parse_url_basic_007.phpt b/ext/standard/tests/url/parse_url_basic_007.phpt index 8e04553983602..1b362bb01367c 100644 --- a/ext/standard/tests/url/parse_url_basic_007.phpt +++ b/ext/standard/tests/url/parse_url_basic_007.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : NULL ---> http://?:/ : string(1) "/" ---> http://@?:/ : string(1) "/" +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : string(2) "/:" --> file:///a:/ : string(3) "a:/" --> file:///ab:/ : string(5) "/ab:/" diff --git a/ext/standard/tests/url/parse_url_basic_008.phpt b/ext/standard/tests/url/parse_url_basic_008.phpt index 0c77221465e78..1271f3838c559 100644 --- a/ext/standard/tests/url/parse_url_basic_008.phpt +++ b/ext/standard/tests/url/parse_url_basic_008.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : NULL ---> http://?:/ : NULL ---> http://@?:/ : NULL +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/tests/url/parse_url_basic_009.phpt b/ext/standard/tests/url/parse_url_basic_009.phpt index 487b271149737..72f172a55b510 100644 --- a/ext/standard/tests/url/parse_url_basic_009.phpt +++ b/ext/standard/tests/url/parse_url_basic_009.phpt @@ -97,8 +97,8 @@ echo "Done"; --> http://::? : NULL --> http://::# : NULL --> x://::6.5 : NULL ---> http://?:/ : NULL ---> http://@?:/ : NULL +--> http://?:/ : bool(false) +--> http://@?:/ : bool(false) --> file:///: : NULL --> file:///a:/ : NULL --> file:///ab:/ : NULL diff --git a/ext/standard/url.c b/ext/standard/url.c index 0138e4203f636..6488767bc6608 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -217,28 +217,7 @@ PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) goto nohost; } - e = ue; - - if (!(p = memchr(s, '/', (ue - s)))) { - char *query, *fragment; - - query = memchr(s, '?', (ue - s)); - fragment = memchr(s, '#', (ue - s)); - - if (query && fragment) { - if (query > fragment) { - e = fragment; - } else { - e = query; - } - } else if (query) { - e = query; - } else if (fragment) { - e = fragment; - } - } else { - e = p; - } + e = s + strcspn(s, "/?#"); /* check for login and password */ if ((p = zend_memrchr(s, '@', (e-s)))) { From b9a484347918c22fba5dd8b7a0f3254f9269987f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:32 +0000 Subject: [PATCH 13/46] commit patch 22620615 --- ext/spl/spl_observer.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index e8d6074653175..4aa5c5198c1a3 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -772,6 +772,9 @@ SPL_METHOD(SplObjectStorage, unserialize) --p; /* for ';' */ count = Z_LVAL_P(pcount); + ZVAL_UNDEF(&entry); + ZVAL_UNDEF(&inf); + while (count-- > 0) { spl_SplObjectStorageElement *pelement; zend_string *hash; @@ -787,18 +790,17 @@ SPL_METHOD(SplObjectStorage, unserialize) if (!php_var_unserialize(&entry, &p, s + buf_len, &var_hash)) { goto outexcept; } - if (Z_TYPE(entry) != IS_OBJECT) { - zval_ptr_dtor(&entry); - goto outexcept; - } if (*p == ',') { /* new version has inf */ ++p; if (!php_var_unserialize(&inf, &p, s + buf_len, &var_hash)) { zval_ptr_dtor(&entry); goto outexcept; } - } else { - ZVAL_UNDEF(&inf); + } + if (Z_TYPE(entry) != IS_OBJECT) { + zval_ptr_dtor(&entry); + zval_ptr_dtor(&inf); + goto outexcept; } hash = spl_object_storage_get_hash(intern, getThis(), &entry); From ea04ef0d8cf61459b284a617424ac1d726690291 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:34 +0000 Subject: [PATCH 14/46] commit patch 26192028 --- ext/gd/libgd/gd.c | 2 +- ext/gd/libgd/gd.c.orig | 3087 ++++++++++++++++++++++++++++++++++++ ext/gd/tests/bug72696.phpt | 14 + 3 files changed, 3102 insertions(+), 1 deletion(-) create mode 100644 ext/gd/libgd/gd.c.orig create mode 100644 ext/gd/tests/bug72696.phpt diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index b4278316726cf..2b919a35dc8e4 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -1762,7 +1762,7 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) int leftLimit = -1, rightLimit; int i, restoreAlphaBlending = 0; - if (border < 0) { + if (border < 0 || color < 0) { /* Refuse to fill to a non-solid border */ return; } diff --git a/ext/gd/libgd/gd.c.orig b/ext/gd/libgd/gd.c.orig new file mode 100644 index 0000000000000..b4278316726cf --- /dev/null +++ b/ext/gd/libgd/gd.c.orig @@ -0,0 +1,3087 @@ + +#include +#include +#include +#include "gd.h" +#include "gdhelpers.h" + +#include "php.h" + +#ifdef _MSC_VER +# if _MSC_VER >= 1300 +/* in MSVC.NET these are available but only for __cplusplus and not _MSC_EXTENSIONS */ +# if !defined(_MSC_EXTENSIONS) && defined(__cplusplus) +# define HAVE_FABSF 1 +extern float fabsf(float x); +# define HAVE_FLOORF 1 +extern float floorf(float x); +# endif /*MSVC.NET */ +# endif /* MSC */ +#endif +#ifndef HAVE_FABSF +# define HAVE_FABSF 0 +#endif +#ifndef HAVE_FLOORF +# define HAVE_FLOORF 0 +#endif +#if HAVE_FABSF == 0 +/* float fabsf(float x); */ +# ifndef fabsf +# define fabsf(x) ((float)(fabs(x))) +# endif +#endif +#if HAVE_FLOORF == 0 +# ifndef floorf +/* float floorf(float x);*/ +# define floorf(x) ((float)(floor(x))) +# endif +#endif + +#ifdef _OSD_POSIX /* BS2000 uses the EBCDIC char set instead of ASCII */ +#define CHARSET_EBCDIC +#define __attribute__(any) /*nothing */ +#endif +/*_OSD_POSIX*/ + +#ifndef CHARSET_EBCDIC +#define ASC(ch) ch +#else /*CHARSET_EBCDIC */ +#define ASC(ch) gd_toascii[(unsigned char)ch] +static const unsigned char gd_toascii[256] = +{ +/*00 */ 0x00, 0x01, 0x02, 0x03, 0x85, 0x09, 0x86, 0x7f, + 0x87, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /*................ */ +/*10 */ 0x10, 0x11, 0x12, 0x13, 0x8f, 0x0a, 0x08, 0x97, + 0x18, 0x19, 0x9c, 0x9d, 0x1c, 0x1d, 0x1e, 0x1f, /*................ */ +/*20 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x92, 0x17, 0x1b, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07, /*................ */ +/*30 */ 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, + 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, /*................ */ +/*40 */ 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, + 0xe7, 0xf1, 0x60, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, /* .........`.<(+| */ +/*50 */ 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, + 0xec, 0xdf, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x9f, /*&.........!$*);. */ +/*60 */ 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, + 0xc7, 0xd1, 0x5e, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, +/*-/........^,%_>?*/ +/*70 */ 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, + 0xcc, 0xa8, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, /*..........:#@'=" */ +/*80 */ 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0xab, 0xbb, 0xf0, 0xfd, 0xfe, 0xb1, /*.abcdefghi...... */ +/*90 */ 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0xaa, 0xba, 0xe6, 0xb8, 0xc6, 0xa4, /*.jklmnopqr...... */ +/*a0 */ 0xb5, 0xaf, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0xa1, 0xbf, 0xd0, 0xdd, 0xde, 0xae, /*..stuvwxyz...... */ +/*b0 */ 0xa2, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, + 0xbd, 0xbe, 0xac, 0x5b, 0x5c, 0x5d, 0xb4, 0xd7, /*...........[\].. */ +/*c0 */ 0xf9, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0xad, 0xf4, 0xf6, 0xf2, 0xf3, 0xf5, /*.ABCDEFGHI...... */ +/*d0 */ 0xa6, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0xb9, 0xfb, 0xfc, 0xdb, 0xfa, 0xff, /*.JKLMNOPQR...... */ +/*e0 */ 0xd9, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0xb2, 0xd4, 0xd6, 0xd2, 0xd3, 0xd5, /*..STUVWXYZ...... */ +/*f0 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0xb3, 0x7b, 0xdc, 0x7d, 0xda, 0x7e /*0123456789.{.}.~ */ +}; +#endif /*CHARSET_EBCDIC */ + +/* 2.0.10: cast instead of floor() yields 35% performance improvement. Thanks to John Buckman. */ +#define floor_cast(exp) ((long) exp) + +extern int gdCosT[]; +extern int gdSinT[]; + +static void gdImageBrushApply(gdImagePtr im, int x, int y); +static void gdImageTileApply(gdImagePtr im, int x, int y); +static void gdImageAntiAliasedApply(gdImagePtr im, int x, int y); +static int gdLayerOverlay(int dst, int src); +static int gdAlphaOverlayColor(int src, int dst, int max); +int gdImageGetTrueColorPixel(gdImagePtr im, int x, int y); + +void php_gd_error_ex(int type, const char *format, ...) +{ + va_list args; + + + va_start(args, format); + php_verror(NULL, "", type, format, args); + va_end(args); +} + +void php_gd_error(const char *format, ...) +{ + va_list args; + + + va_start(args, format); + php_verror(NULL, "", E_WARNING, format, args); + va_end(args); +} + +gdImagePtr gdImageCreate (int sx, int sy) +{ + int i; + gdImagePtr im; + + if (overflow2(sx, sy)) { + return NULL; + } + + if (overflow2(sizeof(unsigned char *), sy)) { + return NULL; + } + + im = (gdImage *) gdCalloc(1, sizeof(gdImage)); + + /* Row-major ever since gd 1.3 */ + im->pixels = (unsigned char **) gdMalloc(sizeof(unsigned char *) * sy); + im->AA_opacity = (unsigned char **) gdMalloc(sizeof(unsigned char *) * sy); + im->polyInts = 0; + im->polyAllocated = 0; + im->brush = 0; + im->tile = 0; + im->style = 0; + for (i = 0; i < sy; i++) { + /* Row-major ever since gd 1.3 */ + im->pixels[i] = (unsigned char *) gdCalloc(sx, sizeof(unsigned char)); + im->AA_opacity[i] = (unsigned char *) gdCalloc(sx, sizeof(unsigned char)); + } + im->sx = sx; + im->sy = sy; + im->colorsTotal = 0; + im->transparent = (-1); + im->interlace = 0; + im->thick = 1; + im->AA = 0; + im->AA_polygon = 0; + for (i = 0; i < gdMaxColors; i++) { + im->open[i] = 1; + im->red[i] = 0; + im->green[i] = 0; + im->blue[i] = 0; + } + im->trueColor = 0; + im->tpixels = 0; + im->cx1 = 0; + im->cy1 = 0; + im->cx2 = im->sx - 1; + im->cy2 = im->sy - 1; + im->interpolation = NULL; + im->interpolation_id = GD_BILINEAR_FIXED; + return im; +} + +gdImagePtr gdImageCreateTrueColor (int sx, int sy) +{ + int i; + gdImagePtr im; + + if (overflow2(sx, sy)) { + return NULL; + } + + if (overflow2(sizeof(unsigned char *), sy)) { + return NULL; + } + + if (overflow2(sizeof(int), sx)) { + return NULL; + } + + im = (gdImage *) gdMalloc(sizeof(gdImage)); + memset(im, 0, sizeof(gdImage)); + im->tpixels = (int **) gdMalloc(sizeof(int *) * sy); + im->AA_opacity = (unsigned char **) gdMalloc(sizeof(unsigned char *) * sy); + im->polyInts = 0; + im->polyAllocated = 0; + im->brush = 0; + im->tile = 0; + im->style = 0; + for (i = 0; i < sy; i++) { + im->tpixels[i] = (int *) gdCalloc(sx, sizeof(int)); + im->AA_opacity[i] = (unsigned char *) gdCalloc(sx, sizeof(unsigned char)); + } + im->sx = sx; + im->sy = sy; + im->transparent = (-1); + im->interlace = 0; + im->trueColor = 1; + /* 2.0.2: alpha blending is now on by default, and saving of alpha is + * off by default. This allows font antialiasing to work as expected + * on the first try in JPEGs -- quite important -- and also allows + * for smaller PNGs when saving of alpha channel is not really + * desired, which it usually isn't! + */ + im->saveAlphaFlag = 0; + im->alphaBlendingFlag = 1; + im->thick = 1; + im->AA = 0; + im->AA_polygon = 0; + im->cx1 = 0; + im->cy1 = 0; + im->cx2 = im->sx - 1; + im->cy2 = im->sy - 1; + im->interpolation = NULL; + im->interpolation_id = GD_BILINEAR_FIXED; + return im; +} + +void gdImageDestroy (gdImagePtr im) +{ + int i; + if (im->pixels) { + for (i = 0; i < im->sy; i++) { + gdFree(im->pixels[i]); + } + gdFree(im->pixels); + } + if (im->tpixels) { + for (i = 0; i < im->sy; i++) { + gdFree(im->tpixels[i]); + } + gdFree(im->tpixels); + } + if (im->AA_opacity) { + for (i = 0; i < im->sy; i++) { + gdFree(im->AA_opacity[i]); + } + gdFree(im->AA_opacity); + } + if (im->polyInts) { + gdFree(im->polyInts); + } + if (im->style) { + gdFree(im->style); + } + gdFree(im); +} + +int gdImageColorClosest (gdImagePtr im, int r, int g, int b) +{ + return gdImageColorClosestAlpha (im, r, g, b, gdAlphaOpaque); +} + +int gdImageColorClosestAlpha (gdImagePtr im, int r, int g, int b, int a) +{ + int i; + long rd, gd, bd, ad; + int ct = (-1); + int first = 1; + long mindist = 0; + + if (im->trueColor) { + return gdTrueColorAlpha(r, g, b, a); + } + for (i = 0; i < im->colorsTotal; i++) { + long dist; + if (im->open[i]) { + continue; + } + rd = im->red[i] - r; + gd = im->green[i] - g; + bd = im->blue[i] - b; + /* gd 2.02: whoops, was - b (thanks to David Marwood) */ + ad = im->alpha[i] - a; + dist = rd * rd + gd * gd + bd * bd + ad * ad; + if (first || (dist < mindist)) { + mindist = dist; + ct = i; + first = 0; + } + } + return ct; +} + +/* This code is taken from http://www.acm.org/jgt/papers/SmithLyons96/hwb_rgb.html, an article + * on colour conversion to/from RBG and HWB colour systems. + * It has been modified to return the converted value as a * parameter. + */ + +#define RETURN_HWB(h, w, b) {HWB->H = h; HWB->W = w; HWB->B = b; return HWB;} +#define RETURN_RGB(r, g, b) {RGB->R = r; RGB->G = g; RGB->B = b; return RGB;} +#define HWB_UNDEFINED -1 +#define SETUP_RGB(s, r, g, b) {s.R = r/255.0f; s.G = g/255.0f; s.B = b/255.0f;} + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#define MIN3(a,b,c) ((a)<(b)?(MIN(a,c)):(MIN(b,c))) +#ifndef MAX +#define MAX(a,b) ((a)<(b)?(b):(a)) +#endif +#define MAX3(a,b,c) ((a)<(b)?(MAX(b,c)):(MAX(a,c))) + + +/* + * Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms. Pure + * red always maps to 6 in this implementation. Therefore UNDEFINED can be + * defined as 0 in situations where only unsigned numbers are desired. + */ +typedef struct +{ + float R, G, B; +} +RGBType; +typedef struct +{ + float H, W, B; +} +HWBType; + +static HWBType * RGB_to_HWB (RGBType RGB, HWBType * HWB) +{ + /* + * RGB are each on [0, 1]. W and B are returned on [0, 1] and H is + * returned on [0, 6]. Exception: H is returned UNDEFINED if W == 1 - B. + */ + + float R = RGB.R, G = RGB.G, B = RGB.B, w, v, b, f; + int i; + + w = MIN3 (R, G, B); + v = MAX3 (R, G, B); + b = 1 - v; + if (v == w) { + RETURN_HWB(HWB_UNDEFINED, w, b); + } + f = (R == w) ? G - B : ((G == w) ? B - R : R - G); + i = (R == w) ? 3 : ((G == w) ? 5 : 1); + + RETURN_HWB(i - f / (v - w), w, b); +} + +static float HWB_Diff (int r1, int g1, int b1, int r2, int g2, int b2) +{ + RGBType RGB1, RGB2; + HWBType HWB1, HWB2; + float diff; + + SETUP_RGB(RGB1, r1, g1, b1); + SETUP_RGB(RGB2, r2, g2, b2); + + RGB_to_HWB(RGB1, &HWB1); + RGB_to_HWB(RGB2, &HWB2); + + /* + * I made this bit up; it seems to produce OK results, and it is certainly + * more visually correct than the current RGB metric. (PJW) + */ + + if ((HWB1.H == HWB_UNDEFINED) || (HWB2.H == HWB_UNDEFINED)) { + diff = 0.0f; /* Undefined hues always match... */ + } else { + diff = fabsf(HWB1.H - HWB2.H); + if (diff > 3.0f) { + diff = 6.0f - diff; /* Remember, it's a colour circle */ + } + } + + diff = diff * diff + (HWB1.W - HWB2.W) * (HWB1.W - HWB2.W) + (HWB1.B - HWB2.B) * (HWB1.B - HWB2.B); + + return diff; +} + + +#if 0 +/* + * This is not actually used, but is here for completeness, in case someone wants to + * use the HWB stuff for anything else... + */ +static RGBType * HWB_to_RGB (HWBType HWB, RGBType * RGB) +{ + /* + * H is given on [0, 6] or UNDEFINED. W and B are given on [0, 1]. + * RGB are each returned on [0, 1]. + */ + + float h = HWB.H, w = HWB.W, b = HWB.B, v, n, f; + int i; + + v = 1 - b; + if (h == HWB_UNDEFINED) { + RETURN_RGB(v, v, v); + } + i = floor(h); + f = h - i; + if (i & 1) { + f = 1 - f; /* if i is odd */ + } + n = w + f * (v - w); /* linear interpolation between w and v */ + switch (i) { + case 6: + case 0: + RETURN_RGB(v, n, w); + case 1: + RETURN_RGB(n, v, w); + case 2: + RETURN_RGB(w, v, n); + case 3: + RETURN_RGB(w, n, v); + case 4: + RETURN_RGB(n, w, v); + case 5: + RETURN_RGB(v, w, n); + } + + return RGB; +} +#endif + +int gdImageColorClosestHWB (gdImagePtr im, int r, int g, int b) +{ + int i; + /* long rd, gd, bd; */ + int ct = (-1); + int first = 1; + float mindist = 0; + if (im->trueColor) { + return gdTrueColor(r, g, b); + } + for (i = 0; i < im->colorsTotal; i++) { + float dist; + if (im->open[i]) { + continue; + } + dist = HWB_Diff(im->red[i], im->green[i], im->blue[i], r, g, b); + if (first || (dist < mindist)) { + mindist = dist; + ct = i; + first = 0; + } + } + return ct; +} + +int gdImageColorExact (gdImagePtr im, int r, int g, int b) +{ + return gdImageColorExactAlpha (im, r, g, b, gdAlphaOpaque); +} + +int gdImageColorExactAlpha (gdImagePtr im, int r, int g, int b, int a) +{ + int i; + if (im->trueColor) { + return gdTrueColorAlpha(r, g, b, a); + } + for (i = 0; i < im->colorsTotal; i++) { + if (im->open[i]) { + continue; + } + if ((im->red[i] == r) && (im->green[i] == g) && (im->blue[i] == b) && (im->alpha[i] == a)) { + return i; + } + } + return -1; +} + +int gdImageColorAllocate (gdImagePtr im, int r, int g, int b) +{ + return gdImageColorAllocateAlpha (im, r, g, b, gdAlphaOpaque); +} + +int gdImageColorAllocateAlpha (gdImagePtr im, int r, int g, int b, int a) +{ + int i; + int ct = (-1); + if (im->trueColor) { + return gdTrueColorAlpha(r, g, b, a); + } + for (i = 0; i < im->colorsTotal; i++) { + if (im->open[i]) { + ct = i; + break; + } + } + if (ct == (-1)) { + ct = im->colorsTotal; + if (ct == gdMaxColors) { + return -1; + } + im->colorsTotal++; + } + im->red[ct] = r; + im->green[ct] = g; + im->blue[ct] = b; + im->alpha[ct] = a; + im->open[ct] = 0; + + return ct; +} + +/* + * gdImageColorResolve is an alternative for the code fragment: + * + * if ((color=gdImageColorExact(im,R,G,B)) < 0) + * if ((color=gdImageColorAllocate(im,R,G,B)) < 0) + * color=gdImageColorClosest(im,R,G,B); + * + * in a single function. Its advantage is that it is guaranteed to + * return a color index in one search over the color table. + */ + +int gdImageColorResolve (gdImagePtr im, int r, int g, int b) +{ + return gdImageColorResolveAlpha(im, r, g, b, gdAlphaOpaque); +} + +int gdImageColorResolveAlpha (gdImagePtr im, int r, int g, int b, int a) +{ + int c; + int ct = -1; + int op = -1; + long rd, gd, bd, ad, dist; + long mindist = 4 * 255 * 255; /* init to max poss dist */ + if (im->trueColor) + { + return gdTrueColorAlpha (r, g, b, a); + } + + for (c = 0; c < im->colorsTotal; c++) + { + if (im->open[c]) + { + op = c; /* Save open slot */ + continue; /* Color not in use */ + } + if (c == im->transparent) + { + /* don't ever resolve to the color that has + * been designated as the transparent color */ + continue; + } + rd = (long) (im->red[c] - r); + gd = (long) (im->green[c] - g); + bd = (long) (im->blue[c] - b); + ad = (long) (im->alpha[c] - a); + dist = rd * rd + gd * gd + bd * bd + ad * ad; + if (dist < mindist) + { + if (dist == 0) + { + return c; /* Return exact match color */ + } + mindist = dist; + ct = c; + } + } + /* no exact match. We now know closest, but first try to allocate exact */ + if (op == -1) + { + op = im->colorsTotal; + if (op == gdMaxColors) + { /* No room for more colors */ + return ct; /* Return closest available color */ + } + im->colorsTotal++; + } + im->red[op] = r; + im->green[op] = g; + im->blue[op] = b; + im->alpha[op] = a; + im->open[op] = 0; + return op; /* Return newly allocated color */ +} + +void gdImageColorDeallocate (gdImagePtr im, int color) +{ + if (im->trueColor) { + return; + } + /* Mark it open. */ + im->open[color] = 1; +} + +void gdImageColorTransparent (gdImagePtr im, int color) +{ + if (!im->trueColor) { + if (im->transparent != -1) { + im->alpha[im->transparent] = gdAlphaOpaque; + } + if (color > -1 && color < im->colorsTotal && color < gdMaxColors) { + im->alpha[color] = gdAlphaTransparent; + } else { + return; + } + } + im->transparent = color; +} + +void gdImagePaletteCopy (gdImagePtr to, gdImagePtr from) +{ + int i; + int x, y, p; + int xlate[256]; + if (to->trueColor || from->trueColor) { + return; + } + + for (i = 0; i < 256; i++) { + xlate[i] = -1; + } + + for (y = 0; y < to->sy; y++) { + for (x = 0; x < to->sx; x++) { + p = gdImageGetPixel(to, x, y); + if (xlate[p] == -1) { + /* This ought to use HWB, but we don't have an alpha-aware version of that yet. */ + xlate[p] = gdImageColorClosestAlpha (from, to->red[p], to->green[p], to->blue[p], to->alpha[p]); + } + gdImageSetPixel(to, x, y, xlate[p]); + } + } + + for (i = 0; i < from->colorsTotal; i++) { + to->red[i] = from->red[i]; + to->blue[i] = from->blue[i]; + to->green[i] = from->green[i]; + to->alpha[i] = from->alpha[i]; + to->open[i] = 0; + } + + for (i = from->colorsTotal; i < to->colorsTotal; i++) { + to->open[i] = 1; + } + + to->colorsTotal = from->colorsTotal; +} + +/* 2.0.10: before the drawing routines, some code to clip points that are + * outside the drawing window. Nick Atty (nick@canalplan.org.uk) + * + * This is the Sutherland Hodgman Algorithm, as implemented by + * Duvanenko, Robbins and Gyurcsik - SH(DRG) for short. See Dr Dobb's + * Journal, January 1996, pp107-110 and 116-117 + * + * Given the end points of a line, and a bounding rectangle (which we + * know to be from (0,0) to (SX,SY)), adjust the endpoints to be on + * the edges of the rectangle if the line should be drawn at all, + * otherwise return a failure code + */ + +/* this does "one-dimensional" clipping: note that the second time it + * is called, all the x parameters refer to height and the y to width + * - the comments ignore this (if you can understand it when it's + * looking at the X parameters, it should become clear what happens on + * the second call!) The code is simplified from that in the article, + * as we know that gd images always start at (0,0) + */ + +static int clip_1d(int *x0, int *y0, int *x1, int *y1, int maxdim) { + double m; /* gradient of line */ + + if (*x0 < 0) { /* start of line is left of window */ + if(*x1 < 0) { /* as is the end, so the line never cuts the window */ + return 0; + } + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + /* adjust x0 to be on the left boundary (ie to be zero), and y0 to match */ + *y0 -= (int)(m * *x0); + *x0 = 0; + /* now, perhaps, adjust the far end of the line as well */ + if (*x1 > maxdim) { + *y1 += (int)(m * (maxdim - *x1)); + *x1 = maxdim; + } + return 1; + } + if (*x0 > maxdim) { /* start of line is right of window - complement of above */ + if (*x1 > maxdim) { /* as is the end, so the line misses the window */ + return 0; + } + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + *y0 += (int)(m * (maxdim - *x0)); /* adjust so point is on the right boundary */ + *x0 = maxdim; + /* now, perhaps, adjust the end of the line */ + if (*x1 < 0) { + *y1 -= (int)(m * *x1); + *x1 = 0; + } + return 1; + } + /* the final case - the start of the line is inside the window */ + if (*x1 > maxdim) { /* other end is outside to the right */ + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + *y1 += (int)(m * (maxdim - *x1)); + *x1 = maxdim; + return 1; + } + if (*x1 < 0) { /* other end is outside to the left */ + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + *y1 -= (int)(m * *x1); + *x1 = 0; + return 1; + } + /* only get here if both points are inside the window */ + return 1; +} + +void gdImageSetPixel (gdImagePtr im, int x, int y, int color) +{ + int p; + switch (color) { + case gdStyled: + if (!im->style) { + /* Refuse to draw if no style is set. */ + return; + } else { + p = im->style[im->stylePos++]; + } + if (p != gdTransparent) { + gdImageSetPixel(im, x, y, p); + } + im->stylePos = im->stylePos % im->styleLength; + break; + case gdStyledBrushed: + if (!im->style) { + /* Refuse to draw if no style is set. */ + return; + } + p = im->style[im->stylePos++]; + if (p != gdTransparent && p != 0) { + gdImageSetPixel(im, x, y, gdBrushed); + } + im->stylePos = im->stylePos % im->styleLength; + break; + case gdBrushed: + gdImageBrushApply(im, x, y); + break; + case gdTiled: + gdImageTileApply(im, x, y); + break; + case gdAntiAliased: + gdImageAntiAliasedApply(im, x, y); + break; + default: + if (gdImageBoundsSafe(im, x, y)) { + if (im->trueColor) { + switch (im->alphaBlendingFlag) { + default: + case gdEffectReplace: + im->tpixels[y][x] = color; + break; + case gdEffectAlphaBlend: + im->tpixels[y][x] = gdAlphaBlend(im->tpixels[y][x], color); + break; + case gdEffectNormal: + im->tpixels[y][x] = gdAlphaBlend(im->tpixels[y][x], color); + break; + case gdEffectOverlay : + im->tpixels[y][x] = gdLayerOverlay(im->tpixels[y][x], color); + break; + } + } else { + im->pixels[y][x] = color; + } + } + break; + } +} + +int gdImageGetTrueColorPixel (gdImagePtr im, int x, int y) +{ + int p = gdImageGetPixel(im, x, y); + + if (!im->trueColor) { + return gdTrueColorAlpha(im->red[p], im->green[p], im->blue[p], (im->transparent == p) ? gdAlphaTransparent : im->alpha[p]); + } else { + return p; + } +} + +static void gdImageBrushApply (gdImagePtr im, int x, int y) +{ + int lx, ly; + int hy, hx; + int x1, y1, x2, y2; + int srcx, srcy; + + if (!im->brush) { + return; + } + + hy = gdImageSY(im->brush) / 2; + y1 = y - hy; + y2 = y1 + gdImageSY(im->brush); + hx = gdImageSX(im->brush) / 2; + x1 = x - hx; + x2 = x1 + gdImageSX(im->brush); + srcy = 0; + + if (im->trueColor) { + if (im->brush->trueColor) { + for (ly = y1; ly < y2; ly++) { + srcx = 0; + for (lx = x1; (lx < x2); lx++) { + int p; + p = gdImageGetTrueColorPixel(im->brush, srcx, srcy); + /* 2.0.9, Thomas Winzig: apply simple full transparency */ + if (p != gdImageGetTransparent(im->brush)) { + gdImageSetPixel(im, lx, ly, p); + } + srcx++; + } + srcy++; + } + } else { + /* 2.0.12: Brush palette, image truecolor (thanks to Thorben Kundinger for pointing out the issue) */ + for (ly = y1; ly < y2; ly++) { + srcx = 0; + for (lx = x1; lx < x2; lx++) { + int p, tc; + p = gdImageGetPixel(im->brush, srcx, srcy); + tc = gdImageGetTrueColorPixel(im->brush, srcx, srcy); + /* 2.0.9, Thomas Winzig: apply simple full transparency */ + if (p != gdImageGetTransparent(im->brush)) { + gdImageSetPixel(im, lx, ly, tc); + } + srcx++; + } + srcy++; + } + } + } else { + for (ly = y1; ly < y2; ly++) { + srcx = 0; + for (lx = x1; lx < x2; lx++) { + int p; + p = gdImageGetPixel(im->brush, srcx, srcy); + /* Allow for non-square brushes! */ + if (p != gdImageGetTransparent(im->brush)) { + /* Truecolor brush. Very slow on a palette destination. */ + if (im->brush->trueColor) { + gdImageSetPixel(im, lx, ly, gdImageColorResolveAlpha(im, gdTrueColorGetRed(p), + gdTrueColorGetGreen(p), + gdTrueColorGetBlue(p), + gdTrueColorGetAlpha(p))); + } else { + gdImageSetPixel(im, lx, ly, im->brushColorMap[p]); + } + } + srcx++; + } + srcy++; + } + } +} + +static void gdImageTileApply (gdImagePtr im, int x, int y) +{ + gdImagePtr tile = im->tile; + int srcx, srcy; + int p; + if (!tile) { + return; + } + srcx = x % gdImageSX(tile); + srcy = y % gdImageSY(tile); + if (im->trueColor) { + p = gdImageGetPixel(tile, srcx, srcy); + if (p != gdImageGetTransparent (tile)) { + if (!tile->trueColor) { + p = gdTrueColorAlpha(tile->red[p], tile->green[p], tile->blue[p], tile->alpha[p]); + } + gdImageSetPixel(im, x, y, p); + } + } else { + p = gdImageGetPixel(tile, srcx, srcy); + /* Allow for transparency */ + if (p != gdImageGetTransparent(tile)) { + if (tile->trueColor) { + /* Truecolor tile. Very slow on a palette destination. */ + gdImageSetPixel(im, x, y, gdImageColorResolveAlpha(im, + gdTrueColorGetRed(p), + gdTrueColorGetGreen(p), + gdTrueColorGetBlue(p), + gdTrueColorGetAlpha(p))); + } else { + gdImageSetPixel(im, x, y, im->tileColorMap[p]); + } + } + } +} + + +static int gdImageTileGet (gdImagePtr im, int x, int y) +{ + int srcx, srcy; + int tileColor,p; + if (!im->tile) { + return -1; + } + srcx = x % gdImageSX(im->tile); + srcy = y % gdImageSY(im->tile); + p = gdImageGetPixel(im->tile, srcx, srcy); + + if (im->trueColor) { + if (im->tile->trueColor) { + tileColor = p; + } else { + tileColor = gdTrueColorAlpha( gdImageRed(im->tile,p), gdImageGreen(im->tile,p), gdImageBlue (im->tile,p), gdImageAlpha (im->tile,p)); + } + } else { + if (im->tile->trueColor) { + tileColor = gdImageColorResolveAlpha(im, gdTrueColorGetRed (p), gdTrueColorGetGreen (p), gdTrueColorGetBlue (p), gdTrueColorGetAlpha (p)); + } else { + tileColor = p; + tileColor = gdImageColorResolveAlpha(im, gdImageRed (im->tile,p), gdImageGreen (im->tile,p), gdImageBlue (im->tile,p), gdImageAlpha (im->tile,p)); + } + } + return tileColor; +} + + +static void gdImageAntiAliasedApply (gdImagePtr im, int px, int py) +{ + float p_dist, p_alpha; + unsigned char opacity; + + /* + * Find the perpendicular distance from point C (px, py) to the line + * segment AB that is being drawn. (Adapted from an algorithm from the + * comp.graphics.algorithms FAQ.) + */ + + int LAC_2, LBC_2; + + int Ax_Cx = im->AAL_x1 - px; + int Ay_Cy = im->AAL_y1 - py; + + int Bx_Cx = im->AAL_x2 - px; + int By_Cy = im->AAL_y2 - py; + + /* 2.0.13: bounds check! AA_opacity is just as capable of + * overflowing as the main pixel array. Arne Jorgensen. + * 2.0.14: typo fixed. 2.0.15: moved down below declarations + * to satisfy non-C++ compilers. + */ + if (!gdImageBoundsSafe(im, px, py)) { + return; + } + + /* Get the squares of the lengths of the segemnts AC and BC. */ + LAC_2 = (Ax_Cx * Ax_Cx) + (Ay_Cy * Ay_Cy); + LBC_2 = (Bx_Cx * Bx_Cx) + (By_Cy * By_Cy); + + if (((im->AAL_LAB_2 + LAC_2) >= LBC_2) && ((im->AAL_LAB_2 + LBC_2) >= LAC_2)) { + /* The two angles are acute. The point lies inside the portion of the + * plane spanned by the line segment. + */ + p_dist = fabs ((float) ((Ay_Cy * im->AAL_Bx_Ax) - (Ax_Cx * im->AAL_By_Ay)) / im->AAL_LAB); + } else { + /* The point is past an end of the line segment. It's length from the + * segment is the shorter of the lengths from the endpoints, but call + * the distance -1, so as not to compute the alpha nor draw the pixel. + */ + p_dist = -1; + } + + if ((p_dist >= 0) && (p_dist <= (float) (im->thick))) { + p_alpha = pow (1.0 - (p_dist / 1.5), 2); + + if (p_alpha > 0) { + if (p_alpha >= 1) { + opacity = 255; + } else { + opacity = (unsigned char) (p_alpha * 255.0); + } + if (!im->AA_polygon || (im->AA_opacity[py][px] < opacity)) { + im->AA_opacity[py][px] = opacity; + } + } + } +} + + +int gdImageGetPixel (gdImagePtr im, int x, int y) +{ + if (gdImageBoundsSafe(im, x, y)) { + if (im->trueColor) { + return im->tpixels[y][x]; + } else { + return im->pixels[y][x]; + } + } else { + return 0; + } +} + +void gdImageAABlend (gdImagePtr im) +{ + float p_alpha, old_alpha; + int color = im->AA_color, color_red, color_green, color_blue; + int old_color, old_red, old_green, old_blue; + int p_color, p_red, p_green, p_blue; + int px, py; + + color_red = gdImageRed(im, color); + color_green = gdImageGreen(im, color); + color_blue = gdImageBlue(im, color); + + /* Impose the anti-aliased drawing on the image. */ + for (py = 0; py < im->sy; py++) { + for (px = 0; px < im->sx; px++) { + if (im->AA_opacity[py][px] != 0) { + old_color = gdImageGetPixel(im, px, py); + + if ((old_color != color) && ((old_color != im->AA_dont_blend) || (im->AA_opacity[py][px] == 255))) { + /* Only blend with different colors that aren't the dont_blend color. */ + p_alpha = (float) (im->AA_opacity[py][px]) / 255.0; + old_alpha = 1.0 - p_alpha; + + if (p_alpha >= 1.0) { + p_color = color; + } else { + old_red = gdImageRed(im, old_color); + old_green = gdImageGreen(im, old_color); + old_blue = gdImageBlue(im, old_color); + + p_red = (int) (((float) color_red * p_alpha) + ((float) old_red * old_alpha)); + p_green = (int) (((float) color_green * p_alpha) + ((float) old_green * old_alpha)); + p_blue = (int) (((float) color_blue * p_alpha) + ((float) old_blue * old_alpha)); + p_color = gdImageColorResolve(im, p_red, p_green, p_blue); + } + gdImageSetPixel(im, px, py, p_color); + } + } + } + /* Clear the AA_opacity array behind us. */ + memset(im->AA_opacity[py], 0, im->sx); + } +} + +static void gdImageHLine(gdImagePtr im, int y, int x1, int x2, int col) +{ + if (im->thick > 1) { + int thickhalf = im->thick >> 1; + gdImageFilledRectangle(im, x1, y - thickhalf, x2, y + im->thick - thickhalf - 1, col); + } else { + if (x2 < x1) { + int t = x2; + x2 = x1; + x1 = t; + } + + for (;x1 <= x2; x1++) { + gdImageSetPixel(im, x1, y, col); + } + } + return; +} + +static void gdImageVLine(gdImagePtr im, int x, int y1, int y2, int col) +{ + if (im->thick > 1) { + int thickhalf = im->thick >> 1; + gdImageFilledRectangle(im, x - thickhalf, y1, x + im->thick - thickhalf - 1, y2, col); + } else { + if (y2 < y1) { + int t = y1; + y1 = y2; + y2 = t; + } + + for (;y1 <= y2; y1++) { + gdImageSetPixel(im, x, y1, col); + } + } + return; +} + +/* Bresenham as presented in Foley & Van Dam */ +void gdImageLine (gdImagePtr im, int x1, int y1, int x2, int y2, int color) +{ + int dx, dy, incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag; + int wid; + int w, wstart; + int thick = im->thick; + + if (color == gdAntiAliased) { + /* + gdAntiAliased passed as color: use the much faster, much cheaper + and equally attractive gdImageAALine implementation. That + clips too, so don't clip twice. + */ + gdImageAALine(im, x1, y1, x2, y2, im->AA_color); + return; + } + + /* 2.0.10: Nick Atty: clip to edges of drawing rectangle, return if no points need to be drawn */ + if (!clip_1d(&x1,&y1,&x2,&y2,gdImageSX(im)) || !clip_1d(&y1,&x1,&y2,&x2,gdImageSY(im))) { + return; + } + + dx = abs (x2 - x1); + dy = abs (y2 - y1); + + if (dx == 0) { + gdImageVLine(im, x1, y1, y2, color); + return; + } else if (dy == 0) { + gdImageHLine(im, y1, x1, x2, color); + return; + } + + if (dy <= dx) { + /* More-or-less horizontal. use wid for vertical stroke */ + /* Doug Claar: watch out for NaN in atan2 (2.0.5) */ + if ((dx == 0) && (dy == 0)) { + wid = 1; + } else { + /* 2.0.12: Michael Schwartz: divide rather than multiply; +TBB: but watch out for /0! */ + double ac = cos (atan2 (dy, dx)); + if (ac != 0) { + wid = thick / ac; + } else { + wid = 1; + } + if (wid == 0) { + wid = 1; + } + } + d = 2 * dy - dx; + incr1 = 2 * dy; + incr2 = 2 * (dy - dx); + if (x1 > x2) { + x = x2; + y = y2; + ydirflag = (-1); + xend = x1; + } else { + x = x1; + y = y1; + ydirflag = 1; + xend = x2; + } + + /* Set up line thickness */ + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel(im, x, w, color); + } + + if (((y2 - y1) * ydirflag) > 0) { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y++; + d += incr2; + } + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel (im, x, w, color); + } + } + } else { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y--; + d += incr2; + } + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel (im, x, w, color); + } + } + } + } else { + /* More-or-less vertical. use wid for horizontal stroke */ + /* 2.0.12: Michael Schwartz: divide rather than multiply; + TBB: but watch out for /0! */ + double as = sin (atan2 (dy, dx)); + if (as != 0) { + wid = thick / as; + } else { + wid = 1; + } + if (wid == 0) { + wid = 1; + } + + d = 2 * dx - dy; + incr1 = 2 * dx; + incr2 = 2 * (dx - dy); + if (y1 > y2) { + y = y2; + x = x2; + yend = y1; + xdirflag = (-1); + } else { + y = y1; + x = x1; + yend = y2; + xdirflag = 1; + } + + /* Set up line thickness */ + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel (im, w, y, color); + } + + if (((x2 - x1) * xdirflag) > 0) { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x++; + d += incr2; + } + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel (im, w, y, color); + } + } + } else { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x--; + d += incr2; + } + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel (im, w, y, color); + } + } + } + } +} + + +/* + * Added on 2003/12 by Pierre-Alain Joye (pajoye@pearfr.org) + * */ +#define BLEND_COLOR(a, nc, c, cc) \ +nc = (cc) + (((((c) - (cc)) * (a)) + ((((c) - (cc)) * (a)) >> 8) + 0x80) >> 8); + +inline static void gdImageSetAAPixelColor(gdImagePtr im, int x, int y, int color, int t) +{ + int dr,dg,db,p,r,g,b; + dr = gdTrueColorGetRed(color); + dg = gdTrueColorGetGreen(color); + db = gdTrueColorGetBlue(color); + + p = gdImageGetPixel(im,x,y); + r = gdTrueColorGetRed(p); + g = gdTrueColorGetGreen(p); + b = gdTrueColorGetBlue(p); + + BLEND_COLOR(t, dr, r, dr); + BLEND_COLOR(t, dg, g, dg); + BLEND_COLOR(t, db, b, db); + im->tpixels[y][x]=gdTrueColorAlpha(dr, dg, db, gdAlphaOpaque); +} + +/* + * Added on 2003/12 by Pierre-Alain Joye (pajoye@pearfr.org) + **/ +void gdImageAALine (gdImagePtr im, int x1, int y1, int x2, int y2, int col) +{ + /* keep them as 32bits */ + long x, y, inc; + long dx, dy,tmp; + + if (y1 < 0 && y2 < 0) { + return; + } + if (y1 < 0) { + x1 += (y1 * (x1 - x2)) / (y2 - y1); + y1 = 0; + } + if (y2 < 0) { + x2 += (y2 * (x1 - x2)) / (y2 - y1); + y2 = 0; + } + + /* bottom edge */ + if (y1 >= im->sy && y2 >= im->sy) { + return; + } + if (y1 >= im->sy) { + x1 -= ((im->sy - y1) * (x1 - x2)) / (y2 - y1); + y1 = im->sy - 1; + } + if (y2 >= im->sy) { + x2 -= ((im->sy - y2) * (x1 - x2)) / (y2 - y1); + y2 = im->sy - 1; + } + + /* left edge */ + if (x1 < 0 && x2 < 0) { + return; + } + if (x1 < 0) { + y1 += (x1 * (y1 - y2)) / (x2 - x1); + x1 = 0; + } + if (x2 < 0) { + y2 += (x2 * (y1 - y2)) / (x2 - x1); + x2 = 0; + } + /* right edge */ + if (x1 >= im->sx && x2 >= im->sx) { + return; + } + if (x1 >= im->sx) { + y1 -= ((im->sx - x1) * (y1 - y2)) / (x2 - x1); + x1 = im->sx - 1; + } + if (x2 >= im->sx) { + y2 -= ((im->sx - x2) * (y1 - y2)) / (x2 - x1); + x2 = im->sx - 1; + } + + dx = x2 - x1; + dy = y2 - y1; + + if (dx == 0 && dy == 0) { + return; + } + if (abs(dx) > abs(dy)) { + if (dx < 0) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + dx = x2 - x1; + dy = y2 - y1; + } + x = x1 << 16; + y = y1 << 16; + inc = (dy * 65536) / dx; + while ((x >> 16) <= x2) { + gdImageSetAAPixelColor(im, x >> 16, y >> 16, col, (y >> 8) & 0xFF); + if ((y >> 16) + 1 < im->sy) { + gdImageSetAAPixelColor(im, x >> 16, (y >> 16) + 1,col, (~y >> 8) & 0xFF); + } + x += (1 << 16); + y += inc; + } + } else { + if (dy < 0) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + dx = x2 - x1; + dy = y2 - y1; + } + x = x1 << 16; + y = y1 << 16; + inc = (dx * 65536) / dy; + while ((y>>16) <= y2) { + gdImageSetAAPixelColor(im, x >> 16, y >> 16, col, (x >> 8) & 0xFF); + if ((x >> 16) + 1 < im->sx) { + gdImageSetAAPixelColor(im, (x >> 16) + 1, (y >> 16),col, (~x >> 8) & 0xFF); + } + x += inc; + y += (1<<16); + } + } +} + +static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert); + +void gdImageDashedLine (gdImagePtr im, int x1, int y1, int x2, int y2, int color) +{ + int dx, dy, incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag; + int dashStep = 0; + int on = 1; + int wid; + int vert; + int thick = im->thick; + + dx = abs(x2 - x1); + dy = abs(y2 - y1); + if (dy <= dx) { + /* More-or-less horizontal. use wid for vertical stroke */ + /* 2.0.12: Michael Schwartz: divide rather than multiply; + TBB: but watch out for /0! */ + double as = sin(atan2(dy, dx)); + if (as != 0) { + wid = thick / as; + } else { + wid = 1; + } + wid = (int)(thick * sin(atan2(dy, dx))); + vert = 1; + + d = 2 * dy - dx; + incr1 = 2 * dy; + incr2 = 2 * (dy - dx); + if (x1 > x2) { + x = x2; + y = y2; + ydirflag = (-1); + xend = x1; + } else { + x = x1; + y = y1; + ydirflag = 1; + xend = x2; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + if (((y2 - y1) * ydirflag) > 0) { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y++; + d += incr2; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + } + } else { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y--; + d += incr2; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + } + } + } else { + /* 2.0.12: Michael Schwartz: divide rather than multiply; + TBB: but watch out for /0! */ + double as = sin (atan2 (dy, dx)); + if (as != 0) { + wid = thick / as; + } else { + wid = 1; + } + vert = 0; + + d = 2 * dx - dy; + incr1 = 2 * dx; + incr2 = 2 * (dx - dy); + if (y1 > y2) { + y = y2; + x = x2; + yend = y1; + xdirflag = (-1); + } else { + y = y1; + x = x1; + yend = y2; + xdirflag = 1; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + if (((x2 - x1) * xdirflag) > 0) { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x++; + d += incr2; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + } + } else { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x--; + d += incr2; + } + dashedSet(im, x, y, color, &on, &dashStep, wid, vert); + } + } + } +} + +static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert) +{ + int dashStep = *dashStepP; + int on = *onP; + int w, wstart; + + dashStep++; + if (dashStep == gdDashSize) { + dashStep = 0; + on = !on; + } + if (on) { + if (vert) { + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel(im, x, w, color); + } + } else { + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetPixel(im, w, y, color); + } + } + } + *dashStepP = dashStep; + *onP = on; +} + +void gdImageChar (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) +{ + int cx, cy; + int px, py; + int fline; + cx = 0; + cy = 0; +#ifdef CHARSET_EBCDIC + c = ASC (c); +#endif /*CHARSET_EBCDIC */ + if ((c < f->offset) || (c >= (f->offset + f->nchars))) { + return; + } + fline = (c - f->offset) * f->h * f->w; + for (py = y; (py < (y + f->h)); py++) { + for (px = x; (px < (x + f->w)); px++) { + if (f->data[fline + cy * f->w + cx]) { + gdImageSetPixel(im, px, py, color); + } + cx++; + } + cx = 0; + cy++; + } +} + +void gdImageCharUp (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) +{ + int cx, cy; + int px, py; + int fline; + cx = 0; + cy = 0; +#ifdef CHARSET_EBCDIC + c = ASC (c); +#endif /*CHARSET_EBCDIC */ + if ((c < f->offset) || (c >= (f->offset + f->nchars))) { + return; + } + fline = (c - f->offset) * f->h * f->w; + for (py = y; py > (y - f->w); py--) { + for (px = x; px < (x + f->h); px++) { + if (f->data[fline + cy * f->w + cx]) { + gdImageSetPixel(im, px, py, color); + } + cy++; + } + cy = 0; + cx++; + } +} + +void gdImageString (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) +{ + int i; + int l; + l = strlen ((char *) s); + for (i = 0; (i < l); i++) { + gdImageChar(im, f, x, y, s[i], color); + x += f->w; + } +} + +void gdImageStringUp (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) +{ + int i; + int l; + l = strlen ((char *) s); + for (i = 0; (i < l); i++) { + gdImageCharUp(im, f, x, y, s[i], color); + y -= f->w; + } +} + +static int strlen16 (unsigned short *s); + +void gdImageString16 (gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) +{ + int i; + int l; + l = strlen16(s); + for (i = 0; (i < l); i++) { + gdImageChar(im, f, x, y, s[i], color); + x += f->w; + } +} + +void gdImageStringUp16 (gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) +{ + int i; + int l; + l = strlen16(s); + for (i = 0; i < l; i++) { + gdImageCharUp(im, f, x, y, s[i], color); + y -= f->w; + } +} + +static int strlen16 (unsigned short *s) +{ + int len = 0; + while (*s) { + s++; + len++; + } + return len; +} + +#ifndef HAVE_LSQRT +/* If you don't have a nice square root function for longs, you can use + ** this hack + */ +long lsqrt (long n) +{ + long result = (long) sqrt ((double) n); + return result; +} +#endif + +/* s and e are integers modulo 360 (degrees), with 0 degrees + being the rightmost extreme and degrees changing clockwise. + cx and cy are the center in pixels; w and h are the horizontal + and vertical diameter in pixels. Nice interface, but slow. + See gd_arc_f_buggy.c for a better version that doesn't + seem to be bug-free yet. */ + +void gdImageArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color) +{ + if ((s % 360) == (e % 360)) { + gdImageEllipse(im, cx, cy, w, h, color); + } else { + gdImageFilledArc(im, cx, cy, w, h, s, e, color, gdNoFill); + } +} + +void gdImageFilledArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color, int style) +{ + gdPoint pts[3]; + int i; + int lx = 0, ly = 0; + int fx = 0, fy = 0; + + + if ((s % 360) == (e % 360)) { + s = 0; e = 360; + } else { + if (s > 360) { + s = s % 360; + } + + if (e > 360) { + e = e % 360; + } + + while (s < 0) { + s += 360; + } + + while (e < s) { + e += 360; + } + if (s == e) { + s = 0; e = 360; + } + } + + for (i = s; i <= e; i++) { + int x, y; + x = ((long) gdCosT[i % 360] * (long) w / (2 * 1024)) + cx; + y = ((long) gdSinT[i % 360] * (long) h / (2 * 1024)) + cy; + if (i != s) { + if (!(style & gdChord)) { + if (style & gdNoFill) { + gdImageLine(im, lx, ly, x, y, color); + } else { + /* This is expensive! */ + pts[0].x = lx; + pts[0].y = ly; + pts[1].x = x; + pts[1].y = y; + pts[2].x = cx; + pts[2].y = cy; + gdImageFilledPolygon(im, pts, 3, color); + } + } + } else { + fx = x; + fy = y; + } + lx = x; + ly = y; + } + if (style & gdChord) { + if (style & gdNoFill) { + if (style & gdEdged) { + gdImageLine(im, cx, cy, lx, ly, color); + gdImageLine(im, cx, cy, fx, fy, color); + } + gdImageLine(im, fx, fy, lx, ly, color); + } else { + pts[0].x = fx; + pts[0].y = fy; + pts[1].x = lx; + pts[1].y = ly; + pts[2].x = cx; + pts[2].y = cy; + gdImageFilledPolygon(im, pts, 3, color); + } + } else { + if (style & gdNoFill) { + if (style & gdEdged) { + gdImageLine(im, cx, cy, lx, ly, color); + gdImageLine(im, cx, cy, fx, fy, color); + } + } + } +} + +void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) +{ + int lastBorder; + /* Seek left */ + int leftLimit = -1, rightLimit; + int i, restoreAlphaBlending = 0; + + if (border < 0) { + /* Refuse to fill to a non-solid border */ + return; + } + + restoreAlphaBlending = im->alphaBlendingFlag; + im->alphaBlendingFlag = 0; + + if (x >= im->sx) { + x = im->sx - 1; + } else if (x < 0) { + x = 0; + } + if (y >= im->sy) { + y = im->sy - 1; + } else if (y < 0) { + y = 0; + } + + for (i = x; i >= 0; i--) { + if (gdImageGetPixel(im, i, y) == border) { + break; + } + gdImageSetPixel(im, i, y, color); + leftLimit = i; + } + if (leftLimit == -1) { + im->alphaBlendingFlag = restoreAlphaBlending; + return; + } + /* Seek right */ + rightLimit = x; + for (i = (x + 1); i < im->sx; i++) { + if (gdImageGetPixel(im, i, y) == border) { + break; + } + gdImageSetPixel(im, i, y, color); + rightLimit = i; + } + /* Look at lines above and below and start paints */ + /* Above */ + if (y > 0) { + lastBorder = 1; + for (i = leftLimit; i <= rightLimit; i++) { + int c = gdImageGetPixel(im, i, y - 1); + if (lastBorder) { + if ((c != border) && (c != color)) { + gdImageFillToBorder(im, i, y - 1, border, color); + lastBorder = 0; + } + } else if ((c == border) || (c == color)) { + lastBorder = 1; + } + } + } + + /* Below */ + if (y < ((im->sy) - 1)) { + lastBorder = 1; + for (i = leftLimit; i <= rightLimit; i++) { + int c = gdImageGetPixel(im, i, y + 1); + + if (lastBorder) { + if ((c != border) && (c != color)) { + gdImageFillToBorder(im, i, y + 1, border, color); + lastBorder = 0; + } + } else if ((c == border) || (c == color)) { + lastBorder = 1; + } + } + } + im->alphaBlendingFlag = restoreAlphaBlending; +} + +/* + * set the pixel at (x,y) and its 4-connected neighbors + * with the same pixel value to the new pixel value nc (new color). + * A 4-connected neighbor: pixel above, below, left, or right of a pixel. + * ideas from comp.graphics discussions. + * For tiled fill, the use of a flag buffer is mandatory. As the tile image can + * contain the same color as the color to fill. To do not bloat normal filling + * code I added a 2nd private function. + */ + +/* horizontal segment of scan line y */ +struct seg {int y, xl, xr, dy;}; + +/* max depth of stack */ +#define FILL_MAX ((int)(im->sy*im->sx)/4) +#define FILL_PUSH(Y, XL, XR, DY) \ + if (sp=0 && Y+(DY)y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; sp++;} + +#define FILL_POP(Y, XL, XR, DY) \ + {sp--; Y = sp->y+(DY = sp->dy); XL = sp->xl; XR = sp->xr;} + +static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc); + +void gdImageFill(gdImagePtr im, int x, int y, int nc) +{ + int l, x1, x2, dy; + int oc; /* old pixel value */ + int wx2,wy2; + + int alphablending_bak; + + /* stack of filled segments */ + /* struct seg stack[FILL_MAX],*sp = stack;; */ + struct seg *stack = NULL; + struct seg *sp; + + if (!im->trueColor && nc > (im->colorsTotal -1)) { + return; + } + + alphablending_bak = im->alphaBlendingFlag; + im->alphaBlendingFlag = 0; + + if (nc==gdTiled){ + _gdImageFillTiled(im,x,y,nc); + im->alphaBlendingFlag = alphablending_bak; + return; + } + + wx2=im->sx;wy2=im->sy; + oc = gdImageGetPixel(im, x, y); + if (oc==nc || x<0 || x>wx2 || y<0 || y>wy2) { + im->alphaBlendingFlag = alphablending_bak; + return; + } + + /* Do not use the 4 neighbors implementation with + * small images + */ + if (im->sx < 4) { + int ix = x, iy = y, c; + do { + do { + c = gdImageGetPixel(im, ix, iy); + if (c != oc) { + goto done; + } + gdImageSetPixel(im, ix, iy, nc); + } while(ix++ < (im->sx -1)); + ix = x; + } while(iy++ < (im->sy -1)); + goto done; + } + + stack = (struct seg *)safe_emalloc(sizeof(struct seg), ((int)(im->sy*im->sx)/4), 1); + sp = stack; + + /* required! */ + FILL_PUSH(y,x,x,1); + /* seed segment (popped 1st) */ + FILL_PUSH(y+1, x, x, -1); + while (sp>stack) { + FILL_POP(y, x1, x2, dy); + + for (x=x1; x>=0 && gdImageGetPixel(im,x, y)==oc; x--) { + gdImageSetPixel(im,x, y, nc); + } + if (x>=x1) { + goto skip; + } + l = x+1; + + /* leak on left? */ + if (lx2+1) { + FILL_PUSH(y, x2+1, x-1, -dy); + } +skip: for (x++; x<=x2 && (gdImageGetPixel(im, x, y)!=oc); x++); + + l = x; + } while (x<=x2); + } + + efree(stack); + +done: + im->alphaBlendingFlag = alphablending_bak; +} + +static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) +{ + int i, l, x1, x2, dy; + int oc; /* old pixel value */ + int wx2,wy2; + /* stack of filled segments */ + struct seg *stack; + struct seg *sp; + char **pts; + + if (!im->tile) { + return; + } + + wx2=im->sx;wy2=im->sy; + + nc = gdImageTileGet(im,x,y); + + pts = (char **) ecalloc(im->sy + 1, sizeof(char *)); + for (i = 0; i < im->sy + 1; i++) { + pts[i] = (char *) ecalloc(im->sx + 1, sizeof(char)); + } + + stack = (struct seg *)safe_emalloc(sizeof(struct seg), ((int)(im->sy*im->sx)/4), 1); + sp = stack; + + oc = gdImageGetPixel(im, x, y); + + /* required! */ + FILL_PUSH(y,x,x,1); + /* seed segment (popped 1st) */ + FILL_PUSH(y+1, x, x, -1); + while (sp>stack) { + FILL_POP(y, x1, x2, dy); + for (x=x1; x>=0 && (!pts[y][x] && gdImageGetPixel(im,x,y)==oc); x--) { + nc = gdImageTileGet(im,x,y); + pts[y][x] = 1; + gdImageSetPixel(im,x, y, nc); + } + if (x>=x1) { + goto skip; + } + l = x+1; + + /* leak on left? */ + if (lx2+1) { + FILL_PUSH(y, x2+1, x-1, -dy); + } +skip: for(x++; x<=x2 && (pts[y][x] || gdImageGetPixel(im,x, y)!=oc); x++); + l = x; + } while (x<=x2); + } + + for(i = 0; i < im->sy + 1; i++) { + efree(pts[i]); + } + + efree(pts); + efree(stack); +} + + + +void gdImageRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) +{ + int x1h = x1, x1v = x1, y1h = y1, y1v = y1, x2h = x2, x2v = x2, y2h = y2, y2v = y2; + int thick = im->thick; + int t; + + if (x1 == x2 && y1 == y2 && thick == 1) { + gdImageSetPixel(im, x1, y1, color); + return; + } + + if (y2 < y1) { + t=y1; + y1 = y2; + y2 = t; + } + + if (x2 < x1) { + t = x1; + x1 = x2; + x2 = t; + } + + x1h = x1; x1v = x1; y1h = y1; y1v = y1; x2h = x2; x2v = x2; y2h = y2; y2v = y2; + if (thick > 1) { + int cx, cy, x1ul, y1ul, x2lr, y2lr; + int half = thick >> 1; + + x1ul = x1 - half; + y1ul = y1 - half; + + x2lr = x2 + half; + y2lr = y2 + half; + + cy = y1ul + thick; + while (cy-- > y1ul) { + cx = x1ul - 1; + while (cx++ < x2lr) { + gdImageSetPixel(im, cx, cy, color); + } + } + + cy = y2lr - thick; + while (cy++ < y2lr) { + cx = x1ul - 1; + while (cx++ < x2lr) { + gdImageSetPixel(im, cx, cy, color); + } + } + + cy = y1ul + thick - 1; + while (cy++ < y2lr -thick) { + cx = x1ul - 1; + while (cx++ < x1ul + thick) { + gdImageSetPixel(im, cx, cy, color); + } + } + + cy = y1ul + thick - 1; + while (cy++ < y2lr -thick) { + cx = x2lr - thick - 1; + while (cx++ < x2lr) { + gdImageSetPixel(im, cx, cy, color); + } + } + + return; + } else { + if (x1 == x2 || y1 == y2) { + gdImageLine(im, x1, y1, x2, y2, color); + } else { + y1v = y1h + 1; + y2v = y2h - 1; + gdImageLine(im, x1h, y1h, x2h, y1h, color); + gdImageLine(im, x1h, y2h, x2h, y2h, color); + gdImageLine(im, x1v, y1v, x1v, y2v, color); + gdImageLine(im, x2v, y1v, x2v, y2v, color); + } + } +} + +void gdImageFilledRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) +{ + int x, y; + + + if (x1 == x2 && y1 == y2) { + gdImageSetPixel(im, x1, y1, color); + return; + } + + if (x1 > x2) { + x = x1; + x1 = x2; + x2 = x; + } + + if (y1 > y2) { + y = y1; + y1 = y2; + y2 = y; + } + + if (x1 < 0) { + x1 = 0; + } + + if (x2 >= gdImageSX(im)) { + x2 = gdImageSX(im) - 1; + } + + if (y1 < 0) { + y1 = 0; + } + + if (y2 >= gdImageSY(im)) { + y2 = gdImageSY(im) - 1; + } + + for (y = y1; (y <= y2); y++) { + for (x = x1; (x <= x2); x++) { + gdImageSetPixel (im, x, y, color); + } + } +} + +void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) +{ + int c; + int x, y; + int tox, toy; + int i; + int colorMap[gdMaxColors]; + + if (dst->trueColor) { + /* 2.0: much easier when the destination is truecolor. */ + /* 2.0.10: needs a transparent-index check that is still valid if + * the source is not truecolor. Thanks to Frank Warmerdam. + */ + + if (src->trueColor) { + for (y = 0; (y < h); y++) { + for (x = 0; (x < w); x++) { + int c = gdImageGetTrueColorPixel (src, srcX + x, srcY + y); + gdImageSetPixel (dst, dstX + x, dstY + y, c); + } + } + } else { + /* source is palette based */ + for (y = 0; (y < h); y++) { + for (x = 0; (x < w); x++) { + int c = gdImageGetPixel (src, srcX + x, srcY + y); + if (c != src->transparent) { + gdImageSetPixel(dst, dstX + x, dstY + y, gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c])); + } + } + } + } + return; + } + + /* Destination is palette based */ + if (src->trueColor) { /* But source is truecolor (Ouch!) */ + toy = dstY; + for (y = srcY; (y < (srcY + h)); y++) { + tox = dstX; + for (x = srcX; x < (srcX + w); x++) { + int nc; + c = gdImageGetPixel (src, x, y); + + /* Get best match possible. */ + nc = gdImageColorResolveAlpha(dst, gdTrueColorGetRed(c), gdTrueColorGetGreen(c), gdTrueColorGetBlue(c), gdTrueColorGetAlpha(c)); + + gdImageSetPixel(dst, tox, toy, nc); + tox++; + } + toy++; + } + return; + } + + /* Palette based to palette based */ + for (i = 0; i < gdMaxColors; i++) { + colorMap[i] = (-1); + } + toy = dstY; + for (y = srcY; y < (srcY + h); y++) { + tox = dstX; + for (x = srcX; x < (srcX + w); x++) { + int nc; + int mapTo; + c = gdImageGetPixel (src, x, y); + /* Added 7/24/95: support transparent copies */ + if (gdImageGetTransparent (src) == c) { + tox++; + continue; + } + /* Have we established a mapping for this color? */ + if (src->trueColor) { + /* 2.05: remap to the palette available in the destination image. This is slow and + * works badly, but it beats crashing! Thanks to Padhrig McCarthy. + */ + mapTo = gdImageColorResolveAlpha (dst, gdTrueColorGetRed (c), gdTrueColorGetGreen (c), gdTrueColorGetBlue (c), gdTrueColorGetAlpha (c)); + } else if (colorMap[c] == (-1)) { + /* If it's the same image, mapping is trivial */ + if (dst == src) { + nc = c; + } else { + /* Get best match possible. This function never returns error. */ + nc = gdImageColorResolveAlpha (dst, src->red[c], src->green[c], src->blue[c], src->alpha[c]); + } + colorMap[c] = nc; + mapTo = colorMap[c]; + } else { + mapTo = colorMap[c]; + } + gdImageSetPixel (dst, tox, toy, mapTo); + tox++; + } + toy++; + } +} + +/* This function is a substitute for real alpha channel operations, + so it doesn't pay attention to the alpha channel. */ +void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) +{ + int c, dc; + int x, y; + int tox, toy; + int ncR, ncG, ncB; + toy = dstY; + + for (y = srcY; y < (srcY + h); y++) { + tox = dstX; + for (x = srcX; x < (srcX + w); x++) { + int nc; + c = gdImageGetPixel(src, x, y); + /* Added 7/24/95: support transparent copies */ + if (gdImageGetTransparent(src) == c) { + tox++; + continue; + } + /* If it's the same image, mapping is trivial */ + if (dst == src) { + nc = c; + } else { + dc = gdImageGetPixel(dst, tox, toy); + + ncR = (int)(gdImageRed (src, c) * (pct / 100.0) + gdImageRed (dst, dc) * ((100 - pct) / 100.0)); + ncG = (int)(gdImageGreen (src, c) * (pct / 100.0) + gdImageGreen (dst, dc) * ((100 - pct) / 100.0)); + ncB = (int)(gdImageBlue (src, c) * (pct / 100.0) + gdImageBlue (dst, dc) * ((100 - pct) / 100.0)); + + /* Find a reasonable color */ + nc = gdImageColorResolve (dst, ncR, ncG, ncB); + } + gdImageSetPixel (dst, tox, toy, nc); + tox++; + } + toy++; + } +} + +/* This function is a substitute for real alpha channel operations, + so it doesn't pay attention to the alpha channel. */ +void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) +{ + int c, dc; + int x, y; + int tox, toy; + int ncR, ncG, ncB; + float g; + toy = dstY; + + for (y = srcY; (y < (srcY + h)); y++) { + tox = dstX; + for (x = srcX; (x < (srcX + w)); x++) { + int nc; + c = gdImageGetPixel (src, x, y); + /* Added 7/24/95: support transparent copies */ + if (gdImageGetTransparent(src) == c) { + tox++; + continue; + } + + /* + * If it's the same image, mapping is NOT trivial since we + * merge with greyscale target, but if pct is 100, the grey + * value is not used, so it becomes trivial. pjw 2.0.12. + */ + if (dst == src && pct == 100) { + nc = c; + } else { + dc = gdImageGetPixel(dst, tox, toy); + g = (0.29900f * gdImageRed(dst, dc)) + (0.58700f * gdImageGreen(dst, dc)) + (0.11400f * gdImageBlue(dst, dc)); + + ncR = (int)(gdImageRed (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); + ncG = (int)(gdImageGreen (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); + ncB = (int)(gdImageBlue (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); + + + /* First look for an exact match */ + nc = gdImageColorExact(dst, ncR, ncG, ncB); + if (nc == (-1)) { + /* No, so try to allocate it */ + nc = gdImageColorAllocate(dst, ncR, ncG, ncB); + /* If we're out of colors, go for the closest color */ + if (nc == (-1)) { + nc = gdImageColorClosest(dst, ncR, ncG, ncB); + } + } + } + gdImageSetPixel(dst, tox, toy, nc); + tox++; + } + toy++; + } +} + +void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) +{ + int c; + int x, y; + int tox, toy; + int ydest; + int i; + int colorMap[gdMaxColors]; + /* Stretch vectors */ + int *stx, *sty; + + if (overflow2(sizeof(int), srcW)) { + return; + } + if (overflow2(sizeof(int), srcH)) { + return; + } + + stx = (int *) gdMalloc (sizeof (int) * srcW); + sty = (int *) gdMalloc (sizeof (int) * srcH); + + /* Fixed by Mao Morimoto 2.0.16 */ + for (i = 0; (i < srcW); i++) { + stx[i] = dstW * (i+1) / srcW - dstW * i / srcW ; + } + for (i = 0; (i < srcH); i++) { + sty[i] = dstH * (i+1) / srcH - dstH * i / srcH ; + } + for (i = 0; (i < gdMaxColors); i++) { + colorMap[i] = (-1); + } + toy = dstY; + for (y = srcY; (y < (srcY + srcH)); y++) { + for (ydest = 0; (ydest < sty[y - srcY]); ydest++) { + tox = dstX; + for (x = srcX; (x < (srcX + srcW)); x++) { + int nc = 0; + int mapTo; + if (!stx[x - srcX]) { + continue; + } + if (dst->trueColor) { + /* 2.0.9: Thorben Kundinger: Maybe the source image is not a truecolor image */ + if (!src->trueColor) { + int tmp = gdImageGetPixel (src, x, y); + mapTo = gdImageGetTrueColorPixel (src, x, y); + if (gdImageGetTransparent (src) == tmp) { + /* 2.0.21, TK: not tox++ */ + tox += stx[x - srcX]; + continue; + } + } else { + /* TK: old code follows */ + mapTo = gdImageGetTrueColorPixel (src, x, y); + /* Added 7/24/95: support transparent copies */ + if (gdImageGetTransparent (src) == mapTo) { + /* 2.0.21, TK: not tox++ */ + tox += stx[x - srcX]; + continue; + } + } + } else { + c = gdImageGetPixel (src, x, y); + /* Added 7/24/95: support transparent copies */ + if (gdImageGetTransparent (src) == c) { + tox += stx[x - srcX]; + continue; + } + if (src->trueColor) { + /* Remap to the palette available in the destination image. This is slow and works badly. */ + mapTo = gdImageColorResolveAlpha(dst, gdTrueColorGetRed(c), + gdTrueColorGetGreen(c), + gdTrueColorGetBlue(c), + gdTrueColorGetAlpha (c)); + } else { + /* Have we established a mapping for this color? */ + if (colorMap[c] == (-1)) { + /* If it's the same image, mapping is trivial */ + if (dst == src) { + nc = c; + } else { + /* Find or create the best match */ + /* 2.0.5: can't use gdTrueColorGetRed, etc with palette */ + nc = gdImageColorResolveAlpha(dst, gdImageRed(src, c), + gdImageGreen(src, c), + gdImageBlue(src, c), + gdImageAlpha(src, c)); + } + colorMap[c] = nc; + } + mapTo = colorMap[c]; + } + } + for (i = 0; (i < stx[x - srcX]); i++) { + gdImageSetPixel (dst, tox, toy, mapTo); + tox++; + } + } + toy++; + } + } + gdFree (stx); + gdFree (sty); +} + +/* When gd 1.x was first created, floating point was to be avoided. + These days it is often faster than table lookups or integer + arithmetic. The routine below is shamelessly, gloriously + floating point. TBB */ + +void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) +{ + int x, y; + double sy1, sy2, sx1, sx2; + + if (!dst->trueColor) { + gdImageCopyResized (dst, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + return; + } + for (y = dstY; (y < dstY + dstH); y++) { + sy1 = ((double) y - (double) dstY) * (double) srcH / (double) dstH; + sy2 = ((double) (y + 1) - (double) dstY) * (double) srcH / (double) dstH; + for (x = dstX; (x < dstX + dstW); x++) { + double sx, sy; + double spixels = 0; + double red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0; + double alpha_factor, alpha_sum = 0.0, contrib_sum = 0.0; + sx1 = ((double) x - (double) dstX) * (double) srcW / dstW; + sx2 = ((double) (x + 1) - (double) dstX) * (double) srcW / dstW; + sy = sy1; + do { + double yportion; + if (floor_cast(sy) == floor_cast(sy1)) { + yportion = 1.0f - (sy - floor_cast(sy)); + if (yportion > sy2 - sy1) { + yportion = sy2 - sy1; + } + sy = floor_cast(sy); + } else if (sy == floorf(sy2)) { + yportion = sy2 - floor_cast(sy2); + } else { + yportion = 1.0f; + } + sx = sx1; + do { + double xportion; + double pcontribution; + int p; + if (floorf(sx) == floor_cast(sx1)) { + xportion = 1.0f - (sx - floor_cast(sx)); + if (xportion > sx2 - sx1) { + xportion = sx2 - sx1; + } + sx = floor_cast(sx); + } else if (sx == floorf(sx2)) { + xportion = sx2 - floor_cast(sx2); + } else { + xportion = 1.0f; + } + pcontribution = xportion * yportion; + p = gdImageGetTrueColorPixel(src, (int) sx + srcX, (int) sy + srcY); + + alpha_factor = ((gdAlphaMax - gdTrueColorGetAlpha(p))) * pcontribution; + red += gdTrueColorGetRed (p) * alpha_factor; + green += gdTrueColorGetGreen (p) * alpha_factor; + blue += gdTrueColorGetBlue (p) * alpha_factor; + alpha += gdTrueColorGetAlpha (p) * pcontribution; + alpha_sum += alpha_factor; + contrib_sum += pcontribution; + spixels += xportion * yportion; + sx += 1.0f; + } + while (sx < sx2); + + sy += 1.0f; + } + + while (sy < sy2); + + if (spixels != 0.0f) { + red /= spixels; + green /= spixels; + blue /= spixels; + alpha /= spixels; + alpha += 0.5; + } + if ( alpha_sum != 0.0f) { + if( contrib_sum != 0.0f) { + alpha_sum /= contrib_sum; + } + red /= alpha_sum; + green /= alpha_sum; + blue /= alpha_sum; + } + /* Clamping to allow for rounding errors above */ + if (red > 255.0f) { + red = 255.0f; + } + if (green > 255.0f) { + green = 255.0f; + } + if (blue > 255.0f) { + blue = 255.0f; + } + if (alpha > gdAlphaMax) { + alpha = gdAlphaMax; + } + gdImageSetPixel(dst, x, y, gdTrueColorAlpha ((int) red, (int) green, (int) blue, (int) alpha)); + } + } +} + +void gdImagePolygon (gdImagePtr im, gdPointPtr p, int n, int c) +{ + int i; + int lx, ly; + typedef void (*image_line)(gdImagePtr im, int x1, int y1, int x2, int y2, int color); + image_line draw_line; + + if (n <= 0) { + return; + } + + /* Let it be known that we are drawing a polygon so that the opacity + * mask doesn't get cleared after each line. + */ + if (c == gdAntiAliased) { + im->AA_polygon = 1; + } + + if ( im->antialias) { + draw_line = gdImageAALine; + } else { + draw_line = gdImageLine; + } + lx = p->x; + ly = p->y; + draw_line(im, lx, ly, p[n - 1].x, p[n - 1].y, c); + for (i = 1; i < n; i++) { + p++; + draw_line(im, lx, ly, p->x, p->y, c); + lx = p->x; + ly = p->y; + } + + if (c == gdAntiAliased) { + im->AA_polygon = 0; + gdImageAABlend(im); + } +} + +int gdCompareInt (const void *a, const void *b); + +/* THANKS to Kirsten Schulz for the polygon fixes! */ + +/* The intersection finding technique of this code could be improved + * by remembering the previous intertersection, and by using the slope. + * That could help to adjust intersections to produce a nice + * interior_extrema. + */ + +void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) +{ + int i; + int y; + int miny, maxy, pmaxy; + int x1, y1; + int x2, y2; + int ind1, ind2; + int ints; + int fill_color; + + if (n <= 0) { + return; + } + + if (overflow2(sizeof(int), n)) { + return; + } + + if (c == gdAntiAliased) { + fill_color = im->AA_color; + } else { + fill_color = c; + } + + if (!im->polyAllocated) { + im->polyInts = (int *) gdMalloc(sizeof(int) * n); + im->polyAllocated = n; + } + if (im->polyAllocated < n) { + while (im->polyAllocated < n) { + im->polyAllocated *= 2; + } + if (overflow2(sizeof(int), im->polyAllocated)) { + return; + } + im->polyInts = (int *) gdRealloc(im->polyInts, sizeof(int) * im->polyAllocated); + } + miny = p[0].y; + maxy = p[0].y; + for (i = 1; i < n; i++) { + if (p[i].y < miny) { + miny = p[i].y; + } + if (p[i].y > maxy) { + maxy = p[i].y; + } + } + pmaxy = maxy; + /* 2.0.16: Optimization by Ilia Chipitsine -- don't waste time offscreen */ + if (miny < 0) { + miny = 0; + } + if (maxy >= gdImageSY(im)) { + maxy = gdImageSY(im) - 1; + } + + /* Fix in 1.3: count a vertex only once */ + for (y = miny; y <= maxy; y++) { + /*1.4 int interLast = 0; */ + /* int dirLast = 0; */ + /* int interFirst = 1; */ + ints = 0; + for (i = 0; i < n; i++) { + if (!i) { + ind1 = n - 1; + ind2 = 0; + } else { + ind1 = i - 1; + ind2 = i; + } + y1 = p[ind1].y; + y2 = p[ind2].y; + if (y1 < y2) { + x1 = p[ind1].x; + x2 = p[ind2].x; + } else if (y1 > y2) { + y2 = p[ind1].y; + y1 = p[ind2].y; + x2 = p[ind1].x; + x1 = p[ind2].x; + } else { + continue; + } + /* Do the following math as float intermediately, and round to ensure + * that Polygon and FilledPolygon for the same set of points have the + * same footprint. + */ + if (y >= y1 && y < y2) { + im->polyInts[ints++] = (float) ((y - y1) * (x2 - x1)) / (float) (y2 - y1) + 0.5 + x1; + } else if (y == pmaxy && y == y2) { + im->polyInts[ints++] = x2; + } + } + qsort(im->polyInts, ints, sizeof(int), gdCompareInt); + + for (i = 0; i < ints - 1; i += 2) { + gdImageLine(im, im->polyInts[i], y, im->polyInts[i + 1], y, fill_color); + } + } + + /* If we are drawing this AA, then redraw the border with AA lines. */ + if (c == gdAntiAliased) { + gdImagePolygon(im, p, n, c); + } +} + +int gdCompareInt (const void *a, const void *b) +{ + return (*(const int *) a) - (*(const int *) b); +} + +void gdImageSetStyle (gdImagePtr im, int *style, int noOfPixels) +{ + if (im->style) { + gdFree(im->style); + } + im->style = (int *) gdMalloc(sizeof(int) * noOfPixels); + memcpy(im->style, style, sizeof(int) * noOfPixels); + im->styleLength = noOfPixels; + im->stylePos = 0; +} + +void gdImageSetThickness (gdImagePtr im, int thickness) +{ + im->thick = thickness; +} + +void gdImageSetBrush (gdImagePtr im, gdImagePtr brush) +{ + int i; + im->brush = brush; + if (!im->trueColor && !im->brush->trueColor) { + for (i = 0; i < gdImageColorsTotal(brush); i++) { + int index; + index = gdImageColorResolveAlpha(im, gdImageRed(brush, i), gdImageGreen(brush, i), gdImageBlue(brush, i), gdImageAlpha(brush, i)); + im->brushColorMap[i] = index; + } + } +} + +void gdImageSetTile (gdImagePtr im, gdImagePtr tile) +{ + int i; + im->tile = tile; + if (!im->trueColor && !im->tile->trueColor) { + for (i = 0; i < gdImageColorsTotal(tile); i++) { + int index; + index = gdImageColorResolveAlpha(im, gdImageRed(tile, i), gdImageGreen(tile, i), gdImageBlue(tile, i), gdImageAlpha(tile, i)); + im->tileColorMap[i] = index; + } + } +} + +void gdImageSetAntiAliased (gdImagePtr im, int c) +{ + im->AA = 1; + im->AA_color = c; + im->AA_dont_blend = -1; +} + +void gdImageSetAntiAliasedDontBlend (gdImagePtr im, int c, int dont_blend) +{ + im->AA = 1; + im->AA_color = c; + im->AA_dont_blend = dont_blend; +} + + +void gdImageInterlace (gdImagePtr im, int interlaceArg) +{ + im->interlace = interlaceArg; +} + +int gdImageCompare (gdImagePtr im1, gdImagePtr im2) +{ + int x, y; + int p1, p2; + int cmpStatus = 0; + int sx, sy; + + if (im1->interlace != im2->interlace) { + cmpStatus |= GD_CMP_INTERLACE; + } + + if (im1->transparent != im2->transparent) { + cmpStatus |= GD_CMP_TRANSPARENT; + } + + if (im1->trueColor != im2->trueColor) { + cmpStatus |= GD_CMP_TRUECOLOR; + } + + sx = im1->sx; + if (im1->sx != im2->sx) { + cmpStatus |= GD_CMP_SIZE_X + GD_CMP_IMAGE; + if (im2->sx < im1->sx) { + sx = im2->sx; + } + } + + sy = im1->sy; + if (im1->sy != im2->sy) { + cmpStatus |= GD_CMP_SIZE_Y + GD_CMP_IMAGE; + if (im2->sy < im1->sy) { + sy = im2->sy; + } + } + + if (im1->colorsTotal != im2->colorsTotal) { + cmpStatus |= GD_CMP_NUM_COLORS; + } + + for (y = 0; y < sy; y++) { + for (x = 0; x < sx; x++) { + p1 = im1->trueColor ? gdImageTrueColorPixel(im1, x, y) : gdImagePalettePixel(im1, x, y); + p2 = im2->trueColor ? gdImageTrueColorPixel(im2, x, y) : gdImagePalettePixel(im2, x, y); + + if (gdImageRed(im1, p1) != gdImageRed(im2, p2)) { + cmpStatus |= GD_CMP_COLOR + GD_CMP_IMAGE; + break; + } + if (gdImageGreen(im1, p1) != gdImageGreen(im2, p2)) { + cmpStatus |= GD_CMP_COLOR + GD_CMP_IMAGE; + break; + } + if (gdImageBlue(im1, p1) != gdImageBlue(im2, p2)) { + cmpStatus |= GD_CMP_COLOR + GD_CMP_IMAGE; + break; + } +#if 0 + /* Soon we'll add alpha channel to palettes */ + if (gdImageAlpha(im1, p1) != gdImageAlpha(im2, p2)) { + cmpStatus |= GD_CMP_COLOR + GD_CMP_IMAGE; + break; + } +#endif + } + if (cmpStatus & GD_CMP_COLOR) { + break; + } + } + + return cmpStatus; +} + +int +gdAlphaBlendOld (int dst, int src) +{ + /* 2.0.12: TBB: alpha in the destination should be a + * component of the result. Thanks to Frank Warmerdam for + * pointing out the issue. + */ + return ((((gdTrueColorGetAlpha (src) * + gdTrueColorGetAlpha (dst)) / gdAlphaMax) << 24) + + ((((gdAlphaTransparent - gdTrueColorGetAlpha (src)) * + gdTrueColorGetRed (src) / gdAlphaMax) + + (gdTrueColorGetAlpha (src) * + gdTrueColorGetRed (dst)) / gdAlphaMax) << 16) + + ((((gdAlphaTransparent - gdTrueColorGetAlpha (src)) * + gdTrueColorGetGreen (src) / gdAlphaMax) + + (gdTrueColorGetAlpha (src) * + gdTrueColorGetGreen (dst)) / gdAlphaMax) << 8) + + (((gdAlphaTransparent - gdTrueColorGetAlpha (src)) * + gdTrueColorGetBlue (src) / gdAlphaMax) + + (gdTrueColorGetAlpha (src) * + gdTrueColorGetBlue (dst)) / gdAlphaMax)); +} + +int gdAlphaBlend (int dst, int src) { + int src_alpha = gdTrueColorGetAlpha(src); + int dst_alpha, alpha, red, green, blue; + int src_weight, dst_weight, tot_weight; + +/* -------------------------------------------------------------------- */ +/* Simple cases we want to handle fast. */ +/* -------------------------------------------------------------------- */ + if( src_alpha == gdAlphaOpaque ) + return src; + + dst_alpha = gdTrueColorGetAlpha(dst); + if( src_alpha == gdAlphaTransparent ) + return dst; + if( dst_alpha == gdAlphaTransparent ) + return src; + +/* -------------------------------------------------------------------- */ +/* What will the source and destination alphas be? Note that */ +/* the destination weighting is substantially reduced as the */ +/* overlay becomes quite opaque. */ +/* -------------------------------------------------------------------- */ + src_weight = gdAlphaTransparent - src_alpha; + dst_weight = (gdAlphaTransparent - dst_alpha) * src_alpha / gdAlphaMax; + tot_weight = src_weight + dst_weight; + +/* -------------------------------------------------------------------- */ +/* What red, green and blue result values will we use? */ +/* -------------------------------------------------------------------- */ + alpha = src_alpha * dst_alpha / gdAlphaMax; + + red = (gdTrueColorGetRed(src) * src_weight + + gdTrueColorGetRed(dst) * dst_weight) / tot_weight; + green = (gdTrueColorGetGreen(src) * src_weight + + gdTrueColorGetGreen(dst) * dst_weight) / tot_weight; + blue = (gdTrueColorGetBlue(src) * src_weight + + gdTrueColorGetBlue(dst) * dst_weight) / tot_weight; + +/* -------------------------------------------------------------------- */ +/* Return merged result. */ +/* -------------------------------------------------------------------- */ + return ((alpha << 24) + (red << 16) + (green << 8) + blue); + +} + +void gdImageAlphaBlending (gdImagePtr im, int alphaBlendingArg) +{ + im->alphaBlendingFlag = alphaBlendingArg; +} + +void gdImageAntialias (gdImagePtr im, int antialias) +{ + if (im->trueColor){ + im->antialias = antialias; + } +} + +void gdImageSaveAlpha (gdImagePtr im, int saveAlphaArg) +{ + im->saveAlphaFlag = saveAlphaArg; +} + +static int gdLayerOverlay (int dst, int src) +{ + int a1, a2; + a1 = gdAlphaMax - gdTrueColorGetAlpha(dst); + a2 = gdAlphaMax - gdTrueColorGetAlpha(src); + return ( ((gdAlphaMax - a1*a2/gdAlphaMax) << 24) + + (gdAlphaOverlayColor( gdTrueColorGetRed(src), gdTrueColorGetRed(dst), gdRedMax ) << 16) + + (gdAlphaOverlayColor( gdTrueColorGetGreen(src), gdTrueColorGetGreen(dst), gdGreenMax ) << 8) + + (gdAlphaOverlayColor( gdTrueColorGetBlue(src), gdTrueColorGetBlue(dst), gdBlueMax )) + ); +} + +static int gdAlphaOverlayColor (int src, int dst, int max ) +{ + /* this function implements the algorithm + * + * for dst[rgb] < 0.5, + * c[rgb] = 2.src[rgb].dst[rgb] + * and for dst[rgb] > 0.5, + * c[rgb] = -2.src[rgb].dst[rgb] + 2.dst[rgb] + 2.src[rgb] - 1 + * + */ + + dst = dst << 1; + if( dst > max ) { + /* in the "light" zone */ + return dst + (src << 1) - (dst * src / max) - max; + } else { + /* in the "dark" zone */ + return dst * src / max; + } +} + +void gdImageSetClip (gdImagePtr im, int x1, int y1, int x2, int y2) +{ + if (x1 < 0) { + x1 = 0; + } + if (x1 >= im->sx) { + x1 = im->sx - 1; + } + if (x2 < 0) { + x2 = 0; + } + if (x2 >= im->sx) { + x2 = im->sx - 1; + } + if (y1 < 0) { + y1 = 0; + } + if (y1 >= im->sy) { + y1 = im->sy - 1; + } + if (y2 < 0) { + y2 = 0; + } + if (y2 >= im->sy) { + y2 = im->sy - 1; + } + im->cx1 = x1; + im->cy1 = y1; + im->cx2 = x2; + im->cy2 = y2; +} + +void gdImageGetClip (gdImagePtr im, int *x1P, int *y1P, int *x2P, int *y2P) +{ + *x1P = im->cx1; + *y1P = im->cy1; + *x2P = im->cx2; + *y2P = im->cy2; +} + +/* convert a palette image to true color */ +int gdImagePaletteToTrueColor(gdImagePtr src) +{ + unsigned int y; + unsigned int yy; + + if (src == NULL) { + return 0; + } + + if (src->trueColor == 1) { + return 1; + } else { + unsigned int x; + const unsigned int sy = gdImageSY(src); + const unsigned int sx = gdImageSX(src); + + src->tpixels = (int **) gdMalloc(sizeof(int *) * sy); + if (src->tpixels == NULL) { + return 0; + } + + for (y = 0; y < sy; y++) { + const unsigned char *src_row = src->pixels[y]; + int * dst_row; + + /* no need to calloc it, we overwrite all pxl anyway */ + src->tpixels[y] = (int *) gdMalloc(sx * sizeof(int)); + if (src->tpixels[y] == NULL) { + goto clean_on_error; + } + + dst_row = src->tpixels[y]; + for (x = 0; x < sx; x++) { + const unsigned char c = *(src_row + x); + if (c == src->transparent) { + *(dst_row + x) = gdTrueColorAlpha(0, 0, 0, 127); + } else { + *(dst_row + x) = gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c]); + } + } + } + } + + /* free old palette buffer (y is sy) */ + for (yy = 0; yy < y; yy++) { + gdFree(src->pixels[yy]); + } + gdFree(src->pixels); + src->trueColor = 1; + src->pixels = NULL; + src->alphaBlendingFlag = 0; + src->saveAlphaFlag = 1; + + if (src->transparent >= 0) { + const unsigned char c = src->transparent; + src->transparent = gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c]); + } + + return 1; + +clean_on_error: + /* free new true color buffer (y is not allocated, have failed) */ + for (yy = 0; yy < y; yy++) { + gdFree(src->tpixels[yy]); + } + gdFree(src->tpixels); + return 0; +} + diff --git a/ext/gd/tests/bug72696.phpt b/ext/gd/tests/bug72696.phpt new file mode 100644 index 0000000000000..4f0d9e7f1d256 --- /dev/null +++ b/ext/gd/tests/bug72696.phpt @@ -0,0 +1,14 @@ +--TEST-- +Bug #72696 (imagefilltoborder stackoverflow on truecolor images) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== From c6b7e52521ff511ed52a4629c52b08534cb97ae6 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:36 +0000 Subject: [PATCH 15/46] commit patch 27270549 --- ext/wddx/tests/bug73631.phpt | 19 + ext/wddx/wddx.c | 5 + ext/wddx/wddx.c.orig | 1287 ++++++++++++++++++++++++++++++++++ 3 files changed, 1311 insertions(+) create mode 100644 ext/wddx/tests/bug73631.phpt create mode 100644 ext/wddx/wddx.c.orig diff --git a/ext/wddx/tests/bug73631.phpt b/ext/wddx/tests/bug73631.phpt new file mode 100644 index 0000000000000..5e37ae826921a --- /dev/null +++ b/ext/wddx/tests/bug73631.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #73631 (Memory leak due to invalid wddx stack processing) +--SKIPIF-- + +--FILE-- + + +1234 + + +EOF; +$wddx = wddx_deserialize($xml); +var_dump($wddx); +?> +--EXPECTF-- +int(1234) + diff --git a/ext/wddx/wddx.c b/ext/wddx/wddx.c index ca7b7116821f1..e9644e1c5dcaf 100644 --- a/ext/wddx/wddx.c +++ b/ext/wddx/wddx.c @@ -818,6 +818,11 @@ static void php_wddx_push_element(void *user_data, const XML_Char *name, const X break; } + } else { + ent.type = ST_BOOLEAN; + SET_STACK_VARNAME; + ZVAL_FALSE(&ent.data); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); } wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); diff --git a/ext/wddx/wddx.c.orig b/ext/wddx/wddx.c.orig new file mode 100644 index 0000000000000..ca7b7116821f1 --- /dev/null +++ b/ext/wddx/wddx.c.orig @@ -0,0 +1,1287 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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: Andrei Zmievski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" + +#if HAVE_WDDX + +#include "ext/xml/expat_compat.h" +#include "php_wddx.h" +#include "php_wddx_api.h" + +#define PHP_XML_INTERNAL +#include "ext/xml/php_xml.h" +#include "ext/standard/php_incomplete_class.h" +#include "ext/standard/base64.h" +#include "ext/standard/info.h" +#include "zend_smart_str.h" +#include "ext/standard/html.h" +#include "ext/standard/php_string.h" +#include "ext/date/php_date.h" +#include "zend_globals.h" + +#define WDDX_BUF_LEN 256 +#define PHP_CLASS_NAME_VAR "php_class_name" + +#define EL_ARRAY "array" +#define EL_BINARY "binary" +#define EL_BOOLEAN "boolean" +#define EL_CHAR "char" +#define EL_CHAR_CODE "code" +#define EL_NULL "null" +#define EL_NUMBER "number" +#define EL_PACKET "wddxPacket" +#define EL_STRING "string" +#define EL_STRUCT "struct" +#define EL_VALUE "value" +#define EL_VAR "var" +#define EL_NAME "name" +#define EL_VERSION "version" +#define EL_RECORDSET "recordset" +#define EL_FIELD "field" +#define EL_DATETIME "dateTime" + +#define php_wddx_deserialize(a,b) \ + php_wddx_deserialize_ex(Z_STRVAL_P(a), Z_STRLEN_P(a), (b)) + +#define SET_STACK_VARNAME \ + if (stack->varname) { \ + ent.varname = estrdup(stack->varname); \ + efree(stack->varname); \ + stack->varname = NULL; \ + } else \ + ent.varname = NULL; \ + +static int le_wddx; + +typedef struct { + zval data; + enum { + ST_ARRAY, + ST_BOOLEAN, + ST_NULL, + ST_NUMBER, + ST_STRING, + ST_BINARY, + ST_STRUCT, + ST_RECORDSET, + ST_FIELD, + ST_DATETIME + } type; + char *varname; +} st_entry; + +typedef struct { + int top, max; + char *varname; + zend_bool done; + void **elements; +} wddx_stack; + + +static void php_wddx_process_data(void *user_data, const XML_Char *s, int len); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_value, 0, 0, 1) + ZEND_ARG_INFO(0, var) + ZEND_ARG_INFO(0, comment) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_vars, 0, 0, 1) + ZEND_ARG_VARIADIC_INFO(0, var_names) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_start, 0, 0, 0) + ZEND_ARG_INFO(0, comment) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_packet_end, 0, 0, 1) + ZEND_ARG_INFO(0, packet_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_add_vars, 0, 0, 2) + ZEND_ARG_INFO(0, packet_id) + ZEND_ARG_VARIADIC_INFO(0, var_names) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_deserialize, 0, 0, 1) + ZEND_ARG_INFO(0, packet) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ wddx_functions[] + */ +const zend_function_entry wddx_functions[] = { + PHP_FE(wddx_serialize_value, arginfo_wddx_serialize_value) + PHP_FE(wddx_serialize_vars, arginfo_wddx_serialize_vars) + PHP_FE(wddx_packet_start, arginfo_wddx_serialize_start) + PHP_FE(wddx_packet_end, arginfo_wddx_packet_end) + PHP_FE(wddx_add_vars, arginfo_wddx_add_vars) + PHP_FE(wddx_deserialize, arginfo_wddx_deserialize) + PHP_FE_END +}; +/* }}} */ + +PHP_MINIT_FUNCTION(wddx); +PHP_MINFO_FUNCTION(wddx); + +/* {{{ dynamically loadable module stuff */ +#ifdef COMPILE_DL_WDDX +ZEND_GET_MODULE(wddx) +#endif /* COMPILE_DL_WDDX */ +/* }}} */ + +/* {{{ wddx_module_entry + */ +zend_module_entry wddx_module_entry = { + STANDARD_MODULE_HEADER, + "wddx", + wddx_functions, + PHP_MINIT(wddx), + NULL, + NULL, + NULL, + PHP_MINFO(wddx), + PHP_WDDX_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ wddx_stack_init + */ +static int wddx_stack_init(wddx_stack *stack) +{ + stack->top = 0; + stack->elements = (void **) safe_emalloc(sizeof(void **), STACK_BLOCK_SIZE, 0); + stack->max = STACK_BLOCK_SIZE; + stack->varname = NULL; + stack->done = 0; + + return SUCCESS; +} +/* }}} */ + +/* {{{ wddx_stack_push + */ +static int wddx_stack_push(wddx_stack *stack, void *element, int size) +{ + if (stack->top >= stack->max) { /* we need to allocate more memory */ + stack->elements = (void **) erealloc(stack->elements, + (sizeof(void **) * (stack->max += STACK_BLOCK_SIZE))); + } + stack->elements[stack->top] = (void *) emalloc(size); + memcpy(stack->elements[stack->top], element, size); + return stack->top++; +} +/* }}} */ + +/* {{{ wddx_stack_top + */ +static int wddx_stack_top(wddx_stack *stack, void **element) +{ + if (stack->top > 0) { + *element = stack->elements[stack->top - 1]; + return SUCCESS; + } else { + *element = NULL; + return FAILURE; + } +} +/* }}} */ + +/* {{{ wddx_stack_is_empty + */ +static int wddx_stack_is_empty(wddx_stack *stack) +{ + if (stack->top == 0) { + return 1; + } else { + return 0; + } +} +/* }}} */ + +/* {{{ wddx_stack_destroy + */ +static int wddx_stack_destroy(wddx_stack *stack) +{ + register int i; + + if (stack->elements) { + for (i = 0; i < stack->top; i++) { + zval_ptr_dtor(&((st_entry *)stack->elements[i])->data); + if (((st_entry *)stack->elements[i])->varname) { + efree(((st_entry *)stack->elements[i])->varname); + } + efree(stack->elements[i]); + } + efree(stack->elements); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ release_wddx_packet_rsrc + */ +static void release_wddx_packet_rsrc(zend_resource *rsrc) +{ + smart_str *str = (smart_str *)rsrc->ptr; + smart_str_free(str); + efree(str); +} +/* }}} */ + +#include "ext/session/php_session.h" + +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) +/* {{{ PS_SERIALIZER_ENCODE_FUNC + */ +PS_SERIALIZER_ENCODE_FUNC(wddx) +{ + wddx_packet *packet; + zend_string *str; + PS_ENCODE_VARS; + + packet = php_wddx_constructor(); + + php_wddx_packet_start(packet, NULL, 0); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + + PS_ENCODE_LOOP( + php_wddx_serialize_var(packet, struc, key); + ); + + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + php_wddx_packet_end(packet); + smart_str_0(packet); + str = zend_string_copy(packet->s); + php_wddx_destructor(packet); + + return str; +} +/* }}} */ + +/* {{{ PS_SERIALIZER_DECODE_FUNC + */ +PS_SERIALIZER_DECODE_FUNC(wddx) +{ + zval retval; + zval *ent; + zend_string *key; + zend_ulong idx; + int ret; + + if (vallen == 0) { + return SUCCESS; + } + + ZVAL_UNDEF(&retval); + if ((ret = php_wddx_deserialize_ex(val, vallen, &retval)) == SUCCESS) { + if (Z_TYPE(retval) != IS_ARRAY) { + zval_dtor(&retval); + return FAILURE; + } + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(retval), idx, key, ent) { + if (key == NULL) { + key = zend_long_to_str(idx); + } else { + zend_string_addref(key); + } + if (php_set_session_var(key, ent, NULL)) { + if (Z_REFCOUNTED_P(ent)) Z_ADDREF_P(ent); + } + PS_ADD_VAR(key); + zend_string_release(key); + } ZEND_HASH_FOREACH_END(); + } + + zval_ptr_dtor(&retval); + + return ret; +} +/* }}} */ +#endif + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(wddx) +{ + le_wddx = zend_register_list_destructors_ex(release_wddx_packet_rsrc, NULL, "wddx", module_number); + +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) + php_session_register_serializer("wddx", + PS_SERIALIZER_ENCODE_NAME(wddx), + PS_SERIALIZER_DECODE_NAME(wddx)); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(wddx) +{ + php_info_print_table_start(); +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) + php_info_print_table_header(2, "WDDX Support", "enabled" ); + php_info_print_table_row(2, "WDDX Session Serializer", "enabled" ); +#else + php_info_print_table_row(2, "WDDX Support", "enabled" ); +#endif + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ php_wddx_packet_start + */ +void php_wddx_packet_start(wddx_packet *packet, char *comment, size_t comment_len) +{ + php_wddx_add_chunk_static(packet, WDDX_PACKET_S); + if (comment) { + php_wddx_add_chunk_static(packet, WDDX_HEADER_S); + php_wddx_add_chunk_static(packet, WDDX_COMMENT_S); + php_wddx_add_chunk_ex(packet, comment, comment_len); + php_wddx_add_chunk_static(packet, WDDX_COMMENT_E); + php_wddx_add_chunk_static(packet, WDDX_HEADER_E); + } else { + php_wddx_add_chunk_static(packet, WDDX_HEADER); + } + php_wddx_add_chunk_static(packet, WDDX_DATA_S); +} +/* }}} */ + +/* {{{ php_wddx_packet_end + */ +void php_wddx_packet_end(wddx_packet *packet) +{ + php_wddx_add_chunk_static(packet, WDDX_DATA_E); + php_wddx_add_chunk_static(packet, WDDX_PACKET_E); +} +/* }}} */ + +#define FLUSH_BUF() \ + if (l > 0) { \ + php_wddx_add_chunk_ex(packet, buf, l); \ + l = 0; \ + } + +/* {{{ php_wddx_serialize_string + */ +static void php_wddx_serialize_string(wddx_packet *packet, zval *var) +{ + php_wddx_add_chunk_static(packet, WDDX_STRING_S); + + if (Z_STRLEN_P(var) > 0) { + zend_string *buf = php_escape_html_entities( + (unsigned char *) Z_STRVAL_P(var), Z_STRLEN_P(var), 0, ENT_QUOTES, NULL); + + php_wddx_add_chunk_ex(packet, ZSTR_VAL(buf), ZSTR_LEN(buf)); + + zend_string_release(buf); + } + php_wddx_add_chunk_static(packet, WDDX_STRING_E); +} +/* }}} */ + +/* {{{ php_wddx_serialize_number + */ +static void php_wddx_serialize_number(wddx_packet *packet, zval *var) +{ + char tmp_buf[WDDX_BUF_LEN]; + zend_string *str = zval_get_string(var); + snprintf(tmp_buf, sizeof(tmp_buf), WDDX_NUMBER, ZSTR_VAL(str)); + zend_string_release(str); + + php_wddx_add_chunk(packet, tmp_buf); +} +/* }}} */ + +/* {{{ php_wddx_serialize_boolean + */ +static void php_wddx_serialize_boolean(wddx_packet *packet, zval *var) +{ + php_wddx_add_chunk(packet, Z_TYPE_P(var) == IS_TRUE ? WDDX_BOOLEAN_TRUE : WDDX_BOOLEAN_FALSE); +} +/* }}} */ + +/* {{{ php_wddx_serialize_unset + */ +static void php_wddx_serialize_unset(wddx_packet *packet) +{ + php_wddx_add_chunk_static(packet, WDDX_NULL); +} +/* }}} */ + +/* {{{ php_wddx_serialize_object + */ +static void php_wddx_serialize_object(wddx_packet *packet, zval *obj) +{ +/* OBJECTS_FIXME */ + zval *ent, fname, *varname; + zval retval; + zend_string *key; + zend_ulong idx; + char tmp_buf[WDDX_BUF_LEN]; + HashTable *objhash, *sleephash; + + ZVAL_STRING(&fname, "__sleep"); + /* + * We try to call __sleep() method on object. It's supposed to return an + * array of property names to be serialized. + */ + if (call_user_function_ex(CG(function_table), obj, &fname, &retval, 0, 0, 1, NULL) == SUCCESS) { + if (!Z_ISUNDEF(retval) && (sleephash = HASH_OF(&retval))) { + PHP_CLASS_ATTRIBUTES; + + PHP_SET_CLASS_ATTRIBUTES(obj); + + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); + php_wddx_add_chunk(packet, tmp_buf); + php_wddx_add_chunk_static(packet, WDDX_STRING_S); + php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); + php_wddx_add_chunk_static(packet, WDDX_STRING_E); + php_wddx_add_chunk_static(packet, WDDX_VAR_E); + + PHP_CLEANUP_CLASS_ATTRIBUTES(); + + objhash = Z_OBJPROP_P(obj); + + ZEND_HASH_FOREACH_VAL(sleephash, varname) { + if (Z_TYPE_P(varname) != IS_STRING) { + php_error_docref(NULL, E_NOTICE, "__sleep should return an array only containing the names of instance-variables to serialize."); + continue; + } + + if ((ent = zend_hash_find(objhash, Z_STR_P(varname))) != NULL) { + php_wddx_serialize_var(packet, ent, Z_STR_P(varname)); + } + } ZEND_HASH_FOREACH_END(); + + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + } + } else { + PHP_CLASS_ATTRIBUTES; + + PHP_SET_CLASS_ATTRIBUTES(obj); + + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); + php_wddx_add_chunk(packet, tmp_buf); + php_wddx_add_chunk_static(packet, WDDX_STRING_S); + php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); + php_wddx_add_chunk_static(packet, WDDX_STRING_E); + php_wddx_add_chunk_static(packet, WDDX_VAR_E); + + PHP_CLEANUP_CLASS_ATTRIBUTES(); + + objhash = Z_OBJPROP_P(obj); + ZEND_HASH_FOREACH_KEY_VAL(objhash, idx, key, ent) { + if (ent == obj) { + continue; + } + if (key) { + const char *class_name, *prop_name; + size_t prop_name_len; + zend_string *tmp; + + zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len); + tmp = zend_string_init(prop_name, prop_name_len, 0); + php_wddx_serialize_var(packet, ent, tmp); + zend_string_release(tmp); + } else { + key = zend_long_to_str(idx); + php_wddx_serialize_var(packet, ent, key); + zend_string_release(key); + } + } ZEND_HASH_FOREACH_END(); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + } + + zval_ptr_dtor(&fname); + zval_ptr_dtor(&retval); +} +/* }}} */ + +/* {{{ php_wddx_serialize_array + */ +static void php_wddx_serialize_array(wddx_packet *packet, zval *arr) +{ + zval *ent; + zend_string *key; + int is_struct = 0; + zend_ulong idx; + HashTable *target_hash; + char tmp_buf[WDDX_BUF_LEN]; + zend_ulong ind = 0; + + target_hash = Z_ARRVAL_P(arr); + ZEND_HASH_FOREACH_KEY(target_hash, idx, key) { + if (key) { + is_struct = 1; + break; + } + + if (idx != ind) { + is_struct = 1; + break; + } + ind++; + } ZEND_HASH_FOREACH_END(); + + if (is_struct) { + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + } else { + snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash)); + php_wddx_add_chunk(packet, tmp_buf); + } + + ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) { + if (ent == arr) { + continue; + } + + if (is_struct) { + if (key) { + php_wddx_serialize_var(packet, ent, key); + } else { + key = zend_long_to_str(idx); + php_wddx_serialize_var(packet, ent, key); + zend_string_release(key); + } + } else { + php_wddx_serialize_var(packet, ent, NULL); + } + } ZEND_HASH_FOREACH_END(); + + if (is_struct) { + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + } else { + php_wddx_add_chunk_static(packet, WDDX_ARRAY_E); + } +} +/* }}} */ + +/* {{{ php_wddx_serialize_var + */ +void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name) +{ + HashTable *ht; + + if (name) { + char *tmp_buf; + zend_string *name_esc = php_escape_html_entities((unsigned char *) ZSTR_VAL(name), ZSTR_LEN(name), 0, ENT_QUOTES, NULL); + tmp_buf = emalloc(ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S)); + snprintf(tmp_buf, ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S), WDDX_VAR_S, ZSTR_VAL(name_esc)); + php_wddx_add_chunk(packet, tmp_buf); + efree(tmp_buf); + zend_string_release(name_esc); + } + + if (Z_TYPE_P(var) == IS_INDIRECT) { + var = Z_INDIRECT_P(var); + } + ZVAL_DEREF(var); + switch (Z_TYPE_P(var)) { + case IS_STRING: + php_wddx_serialize_string(packet, var); + break; + + case IS_LONG: + case IS_DOUBLE: + php_wddx_serialize_number(packet, var); + break; + + case IS_TRUE: + case IS_FALSE: + php_wddx_serialize_boolean(packet, var); + break; + + case IS_NULL: + php_wddx_serialize_unset(packet); + break; + + case IS_ARRAY: + ht = Z_ARRVAL_P(var); + if (ht->u.v.nApplyCount > 1) { + php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references"); + return; + } + if (ZEND_HASH_APPLY_PROTECTION(ht)) { + ht->u.v.nApplyCount++; + } + php_wddx_serialize_array(packet, var); + if (ZEND_HASH_APPLY_PROTECTION(ht)) { + ht->u.v.nApplyCount--; + } + break; + + case IS_OBJECT: + ht = Z_OBJPROP_P(var); + if (ht->u.v.nApplyCount > 1) { + php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references"); + return; + } + ht->u.v.nApplyCount++; + php_wddx_serialize_object(packet, var); + ht->u.v.nApplyCount--; + break; + } + + if (name) { + php_wddx_add_chunk_static(packet, WDDX_VAR_E); + } +} +/* }}} */ + +/* {{{ php_wddx_add_var + */ +static void php_wddx_add_var(wddx_packet *packet, zval *name_var) +{ + zval *val; + HashTable *target_hash; + + if (Z_TYPE_P(name_var) == IS_STRING) { + zend_array *symbol_table = zend_rebuild_symbol_table(); + if ((val = zend_hash_find(symbol_table, Z_STR_P(name_var))) != NULL) { + if (Z_TYPE_P(val) == IS_INDIRECT) { + val = Z_INDIRECT_P(val); + } + php_wddx_serialize_var(packet, val, Z_STR_P(name_var)); + } + } else if (Z_TYPE_P(name_var) == IS_ARRAY || Z_TYPE_P(name_var) == IS_OBJECT) { + int is_array = Z_TYPE_P(name_var) == IS_ARRAY; + + target_hash = HASH_OF(name_var); + + if (is_array && target_hash->u.v.nApplyCount > 1) { + php_error_docref(NULL, E_WARNING, "recursion detected"); + return; + } + + if (Z_IMMUTABLE_P(name_var)) { + ZEND_HASH_FOREACH_VAL(target_hash, val) { + php_wddx_add_var(packet, val); + } ZEND_HASH_FOREACH_END(); + } else { + ZEND_HASH_FOREACH_VAL(target_hash, val) { + if (is_array) { + target_hash->u.v.nApplyCount++; + } + + ZVAL_DEREF(val); + php_wddx_add_var(packet, val); + + if (is_array) { + target_hash->u.v.nApplyCount--; + } + } ZEND_HASH_FOREACH_END(); + } + } +} +/* }}} */ + +/* {{{ php_wddx_push_element + */ +static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts) +{ + st_entry ent; + wddx_stack *stack = (wddx_stack *)user_data; + if (!strcmp((char *)name, EL_PACKET)) { + int i; + + if (atts) for (i=0; atts[i]; i++) { + if (!strcmp((char *)atts[i], EL_VERSION)) { + /* nothing for now */ + } + } + } else if (!strcmp((char *)name, EL_STRING)) { + ent.type = ST_STRING; + SET_STACK_VARNAME; + + ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC()); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_BINARY)) { + ent.type = ST_BINARY; + SET_STACK_VARNAME; + + ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC()); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_CHAR)) { + int i; + + if (atts) for (i = 0; atts[i]; i++) { + if (!strcmp((char *)atts[i], EL_CHAR_CODE) && atts[++i] && atts[i][0]) { + char tmp_buf[2]; + + snprintf(tmp_buf, sizeof(tmp_buf), "%c", (char)strtol((char *)atts[i], NULL, 16)); + php_wddx_process_data(user_data, (XML_Char *) tmp_buf, strlen(tmp_buf)); + break; + } + } + } else if (!strcmp((char *)name, EL_NUMBER)) { + ent.type = ST_NUMBER; + SET_STACK_VARNAME; + + ZVAL_LONG(&ent.data, 0); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_BOOLEAN)) { + int i; + + if (atts) for (i = 0; atts[i]; i++) { + if (!strcmp((char *)atts[i], EL_VALUE) && atts[++i] && atts[i][0]) { + ent.type = ST_BOOLEAN; + SET_STACK_VARNAME; + + ZVAL_TRUE(&ent.data); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + php_wddx_process_data(user_data, atts[i], strlen((char *)atts[i])); + break; + } + } + } else if (!strcmp((char *)name, EL_NULL)) { + ent.type = ST_NULL; + SET_STACK_VARNAME; + + ZVAL_NULL(&ent.data); + + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_ARRAY)) { + ent.type = ST_ARRAY; + SET_STACK_VARNAME; + + array_init(&ent.data); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_STRUCT)) { + ent.type = ST_STRUCT; + SET_STACK_VARNAME; + array_init(&ent.data); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_VAR)) { + int i; + + if (atts) for (i = 0; atts[i]; i++) { + if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) { + stack->varname = estrdup((char *)atts[i]); + break; + } + } + } else if (!strcmp((char *)name, EL_RECORDSET)) { + int i; + + ent.type = ST_RECORDSET; + SET_STACK_VARNAME; + array_init(&ent.data); + + if (atts) for (i = 0; atts[i]; i++) { + if (!strcmp((char *)atts[i], "fieldNames") && atts[++i] && atts[i][0]) { + zval tmp; + char *key; + const char *p1, *p2, *endp; + + endp = (char *)atts[i] + strlen((char *)atts[i]); + p1 = (char *)atts[i]; + while ((p2 = php_memnstr(p1, ",", sizeof(",")-1, endp)) != NULL) { + key = estrndup(p1, p2 - p1); + array_init(&tmp); + add_assoc_zval_ex(&ent.data, key, p2 - p1, &tmp); + p1 = p2 + sizeof(",")-1; + efree(key); + } + + if (p1 <= endp) { + array_init(&tmp); + add_assoc_zval_ex(&ent.data, p1, endp - p1, &tmp); + } + + break; + } + } + + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_FIELD)) { + int i; + st_entry ent; + + ent.type = ST_FIELD; + ent.varname = NULL; + ZVAL_UNDEF(&ent.data); + + if (atts) for (i = 0; atts[i]; i++) { + if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) { + st_entry *recordset; + zval *field; + + if (wddx_stack_top(stack, (void**)&recordset) == SUCCESS && + recordset->type == ST_RECORDSET && + (field = zend_hash_str_find(Z_ARRVAL(recordset->data), (char*)atts[i], strlen((char *)atts[i]))) != NULL) { + ZVAL_COPY_VALUE(&ent.data, field); + } + + break; + } + } + + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } else if (!strcmp((char *)name, EL_DATETIME)) { + ent.type = ST_DATETIME; + SET_STACK_VARNAME; + + ZVAL_LONG(&ent.data, 0); + wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry)); + } +} +/* }}} */ + +/* {{{ php_wddx_pop_element + */ +static void php_wddx_pop_element(void *user_data, const XML_Char *name) +{ + st_entry *ent1, *ent2; + wddx_stack *stack = (wddx_stack *)user_data; + HashTable *target_hash; + zend_class_entry *pce; + zval obj; + +/* OBJECTS_FIXME */ + if (stack->top == 0) { + return; + } + + if (!strcmp((char *)name, EL_STRING) || !strcmp((char *)name, EL_NUMBER) || + !strcmp((char *)name, EL_BOOLEAN) || !strcmp((char *)name, EL_NULL) || + !strcmp((char *)name, EL_ARRAY) || !strcmp((char *)name, EL_STRUCT) || + !strcmp((char *)name, EL_RECORDSET) || !strcmp((char *)name, EL_BINARY) || + !strcmp((char *)name, EL_DATETIME)) { + wddx_stack_top(stack, (void**)&ent1); + + if (!strcmp((char *)name, EL_BINARY)) { + zend_string *new_str = php_base64_decode( + (unsigned char *)Z_STRVAL(ent1->data), Z_STRLEN(ent1->data)); + zval_ptr_dtor(&ent1->data); + ZVAL_STR(&ent1->data, new_str); + } + + /* Call __wakeup() method on the object. */ + if (Z_TYPE(ent1->data) == IS_OBJECT) { + zval fname, retval; + + ZVAL_STRING(&fname, "__wakeup"); + + call_user_function_ex(NULL, &ent1->data, &fname, &retval, 0, 0, 0, NULL); + + zval_ptr_dtor(&fname); + zval_ptr_dtor(&retval); + } + + if (stack->top > 1) { + stack->top--; + wddx_stack_top(stack, (void**)&ent2); + + /* if non-existent field */ + if (ent2->type == ST_FIELD && Z_ISUNDEF(ent2->data)) { + zval_ptr_dtor(&ent1->data); + efree(ent1); + return; + } + + if (Z_TYPE(ent2->data) == IS_ARRAY || Z_TYPE(ent2->data) == IS_OBJECT) { + target_hash = HASH_OF(&ent2->data); + + if (ent1->varname) { + if (!strcmp(ent1->varname, PHP_CLASS_NAME_VAR) && + Z_TYPE(ent1->data) == IS_STRING && Z_STRLEN(ent1->data) && + ent2->type == ST_STRUCT && Z_TYPE(ent2->data) == IS_ARRAY) { + zend_bool incomplete_class = 0; + + zend_str_tolower(Z_STRVAL(ent1->data), Z_STRLEN(ent1->data)); + zend_string_forget_hash_val(Z_STR(ent1->data)); + if ((pce = zend_hash_find_ptr(EG(class_table), Z_STR(ent1->data))) == NULL) { + incomplete_class = 1; + pce = PHP_IC_ENTRY; + } + + /* Initialize target object */ + object_init_ex(&obj, pce); + + /* Merge current hashtable with object's default properties */ + zend_hash_merge(Z_OBJPROP(obj), + Z_ARRVAL(ent2->data), + zval_add_ref, 0); + + if (incomplete_class) { + php_store_class_name(&obj, Z_STRVAL(ent1->data), Z_STRLEN(ent1->data)); + } + + /* Clean up old array entry */ + zval_ptr_dtor(&ent2->data); + + /* Set stack entry to point to the newly created object */ + ZVAL_COPY_VALUE(&ent2->data, &obj); + + /* Clean up class name var entry */ + zval_ptr_dtor(&ent1->data); + } else if (Z_TYPE(ent2->data) == IS_OBJECT) { + zend_class_entry *old_scope = EG(scope); + + EG(scope) = Z_OBJCE(ent2->data); + add_property_zval(&ent2->data, ent1->varname, &ent1->data); + if Z_REFCOUNTED(ent1->data) Z_DELREF(ent1->data); + EG(scope) = old_scope; + } else { + zend_symtable_str_update(target_hash, ent1->varname, strlen(ent1->varname), &ent1->data); + } + efree(ent1->varname); + } else { + zend_hash_next_index_insert(target_hash, &ent1->data); + } + } + efree(ent1); + } else { + stack->done = 1; + } + } else if (!strcmp((char *)name, EL_VAR) && stack->varname) { + efree(stack->varname); + } else if (!strcmp((char *)name, EL_FIELD)) { + st_entry *ent; + wddx_stack_top(stack, (void **)&ent); + efree(ent); + stack->top--; + } +} +/* }}} */ + +/* {{{ php_wddx_process_data + */ +static void php_wddx_process_data(void *user_data, const XML_Char *s, int len) +{ + st_entry *ent; + wddx_stack *stack = (wddx_stack *)user_data; + + if (!wddx_stack_is_empty(stack) && !stack->done) { + wddx_stack_top(stack, (void**)&ent); + switch (ent->type) { + case ST_BINARY: + case ST_STRING: + if (Z_STRLEN(ent->data) == 0) { + zval_ptr_dtor(&ent->data); + ZVAL_STRINGL(&ent->data, (char *)s, len); + } else { + Z_STR(ent->data) = zend_string_extend(Z_STR(ent->data), Z_STRLEN(ent->data) + len, 0); + memcpy(Z_STRVAL(ent->data) + Z_STRLEN(ent->data) - len, (char *)s, len); + Z_STRVAL(ent->data)[Z_STRLEN(ent->data)] = '\0'; + } + break; + case ST_NUMBER: + ZVAL_STRINGL(&ent->data, (char *)s, len); + convert_scalar_to_number(&ent->data); + break; + + case ST_BOOLEAN: + if (!strcmp((char *)s, "true")) { + Z_LVAL(ent->data) = 1; + } else if (!strcmp((char *)s, "false")) { + Z_LVAL(ent->data) = 0; + } else { + stack->top--; + zval_ptr_dtor(&ent->data); + if (ent->varname) + efree(ent->varname); + efree(ent); + } + break; + + case ST_DATETIME: { + char *tmp; + + tmp = emalloc(len + 1); + memcpy(tmp, (char *)s, len); + tmp[len] = '\0'; + + Z_LVAL(ent->data) = php_parse_date(tmp, NULL); + /* date out of range < 1969 or > 2038 */ + if (Z_LVAL(ent->data) == -1) { + ZVAL_STRINGL(&ent->data, (char *)s, len); + } + efree(tmp); + } + break; + + default: + break; + } + } +} +/* }}} */ + +/* {{{ php_wddx_deserialize_ex + */ +int php_wddx_deserialize_ex(const char *value, size_t vallen, zval *return_value) +{ + wddx_stack stack; + XML_Parser parser; + st_entry *ent; + int retval; + + wddx_stack_init(&stack); + parser = XML_ParserCreate((XML_Char *) "UTF-8"); + + XML_SetUserData(parser, &stack); + XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element); + XML_SetCharacterDataHandler(parser, php_wddx_process_data); + + /* XXX value should be parsed in the loop to exhaust size_t */ + XML_Parse(parser, (const XML_Char *) value, (int)vallen, 1); + + XML_ParserFree(parser); + + if (stack.top == 1) { + wddx_stack_top(&stack, (void**)&ent); + ZVAL_COPY(return_value, &ent->data); + retval = SUCCESS; + } else { + retval = FAILURE; + } + + wddx_stack_destroy(&stack); + + return retval; +} +/* }}} */ + +/* {{{ proto string wddx_serialize_value(mixed var [, string comment]) + Creates a new packet and serializes the given value */ +PHP_FUNCTION(wddx_serialize_value) +{ + zval *var; + char *comment = NULL; + size_t comment_len = 0; + wddx_packet *packet; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &var, &comment, &comment_len) == FAILURE) { + return; + } + + packet = php_wddx_constructor(); + + php_wddx_packet_start(packet, comment, comment_len); + php_wddx_serialize_var(packet, var, NULL); + php_wddx_packet_end(packet); + smart_str_0(packet); + + RETVAL_STR_COPY(packet->s); + php_wddx_destructor(packet); +} +/* }}} */ + +/* {{{ proto string wddx_serialize_vars(mixed var_name [, mixed ...]) + Creates a new packet and serializes given variables into a struct */ +PHP_FUNCTION(wddx_serialize_vars) +{ + int num_args, i; + wddx_packet *packet; + zval *args = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) { + return; + } + + packet = php_wddx_constructor(); + + php_wddx_packet_start(packet, NULL, 0); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + + for (i=0; is); + php_wddx_destructor(packet); +} +/* }}} */ + +/* {{{ php_wddx_constructor + */ +wddx_packet *php_wddx_constructor(void) +{ + smart_str *packet; + + packet = ecalloc(1, sizeof(smart_str)); + + return packet; +} +/* }}} */ + +/* {{{ php_wddx_destructor + */ +void php_wddx_destructor(wddx_packet *packet) +{ + smart_str_free(packet); + efree(packet); +} +/* }}} */ + +/* {{{ proto resource wddx_packet_start([string comment]) + Starts a WDDX packet with optional comment and returns the packet id */ +PHP_FUNCTION(wddx_packet_start) +{ + char *comment = NULL; + size_t comment_len = 0; + wddx_packet *packet; + + comment = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &comment, &comment_len) == FAILURE) { + return; + } + + packet = php_wddx_constructor(); + + php_wddx_packet_start(packet, comment, comment_len); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + + RETURN_RES(zend_register_resource(packet, le_wddx)); +} +/* }}} */ + +/* {{{ proto string wddx_packet_end(resource packet_id) + Ends specified WDDX packet and returns the string containing the packet */ +PHP_FUNCTION(wddx_packet_end) +{ + zval *packet_id; + wddx_packet *packet = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &packet_id) == FAILURE) { + return; + } + + if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) { + RETURN_FALSE; + } + + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + + php_wddx_packet_end(packet); + smart_str_0(packet); + + RETVAL_STR_COPY(packet->s); + + zend_list_close(Z_RES_P(packet_id)); +} +/* }}} */ + +/* {{{ proto bool wddx_add_vars(resource packet_id, mixed var_names [, mixed ...]) + Serializes given variables and adds them to packet given by packet_id */ +PHP_FUNCTION(wddx_add_vars) +{ + int num_args, i; + zval *args = NULL; + zval *packet_id; + wddx_packet *packet = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r+", &packet_id, &args, &num_args) == FAILURE) { + return; + } + + if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) { + RETURN_FALSE; + } + + for (i=0; i Date: Wed, 11 Dec 2024 17:44:38 +0000 Subject: [PATCH 16/46] commit patch 20591208 --- Zend/zend_hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 11034fe0288e9..7595d3f9d4d2c 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -175,7 +175,6 @@ ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_ GC_REFCOUNT(ht) = 1; GC_TYPE_INFO(ht) = IS_ARRAY; ht->u.flags = (persistent ? HASH_FLAG_PERSISTENT : 0) | HASH_FLAG_APPLY_PROTECTION | HASH_FLAG_STATIC_KEYS; - ht->nTableSize = zend_hash_check_size(nSize); ht->nTableMask = HT_MIN_MASK; HT_SET_DATA_ADDR(ht, &uninitialized_bucket); ht->nNumUsed = 0; @@ -183,6 +182,7 @@ ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_ ht->nInternalPointer = HT_INVALID_IDX; ht->nNextFreeElement = 0; ht->pDestructor = pDestructor; + ht->nTableSize = zend_hash_check_size(nSize); } static void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht) From 7985029181e23db076f01c3018322ef2445c2243 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:40 +0000 Subject: [PATCH 17/46] commit patch 28074068 --- NEWS | 4 + NEWS.orig | 958 +++++++++++++++++++++++++++++++++++++++++++ main/php_variables.c | 10 +- 3 files changed, 970 insertions(+), 2 deletions(-) create mode 100644 NEWS.orig diff --git a/NEWS b/NEWS index ffc3792b29f5e..0993dd011cc00 100644 --- a/NEWS +++ b/NEWS @@ -43,6 +43,10 @@ PHP NEWS - mbstring: . Fixed bug #71397 (mb_send_mail segmentation fault). (Andrea, Yasuo) +- Core: + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + - OpenSSL: . Fixed bug #71475 (openssl_seal() uninitialized memory usage). (Stas) diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..ffc3792b29f5e --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,958 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +04 Feb 2016 PHP 7.0.3 + +- Core: + . Added support for new HTTP 451 code. (Julien) + . Fixed bug #71039 (exec functions ignore length but look for NULL termination). + (Anatol) + . Fixed bug #71089 (No check to duplicate zend_extension). (Remi) + . Fixed bug #71201 (round() segfault on 64-bit builds). (Anatol) + . Fixed bug #71221 (Null pointer deref (segfault) in get_defined_vars via + ob_start). (hugh at allthethings dot co dot nz) + . Fixed bug #71248 (Wrong interface is enforced). (Dmitry) + . Fixed bug #71273 (A wrong ext directory setup in php.ini leads to crash). + (Anatol) + . Fixed Bug #71275 (Bad method called on cloning an object having a trait). + (Bob) + . Fixed bug #71297 (Memory leak with consecutive yield from). (Bob) + . Fixed bug #71300 (Segfault in zend_fetch_string_offset). (Laruence) + . Fixed bug #71314 (var_export(INF) prints INF.0). (Andrea) + . Fixed bug #71323 (Output of stream_get_meta_data can be falsified by its + input). (Leo Gaspard) + . Fixed bug #71336 (Wrong is_ref on properties as exposed via + get_object_vars()). (Laruence) + . Fixed bug #71459 (Integer overflow in iptcembed()). (Stas) + +- Apache2handler: + . Fix >2G Content-Length headers in apache2handler. (Adam Harvey) + +- CURL: + . Fixed bug #71227 (Can't compile php_curl statically). (Anatol) + . Fixed bug #71225 (curl_setopt() fails to set CURLOPT_POSTFIELDS with + reference to CURLFile). (Laruence) + +- Interbase: + . Fixed Bug #71305 (Crash when optional resource is omitted). + (Laruence, Anatol) + +- LDAP: + . Fixed bug #71249 (ldap_mod_replace/ldap_mod_add store value as string + "Array"). (Laruence) + +- mbstring: + . Fixed bug #71397 (mb_send_mail segmentation fault). (Andrea, Yasuo) + +- OpenSSL: + . Fixed bug #71475 (openssl_seal() uninitialized memory usage). (Stas) + +- Phar: + . Fixed bug #71354 (Heap corruption in tar/zip/phar parser). (Stas) + . Fixed bug #71391 (NULL Pointer Dereference in phar_tar_setupmetadata()). + (Stas) + . Fixed bug #71488 (Stack overflow when decompressing tar archives). (Stas) + +- SOAP: + . Fixed bug #70979 (crash with bad soap request). (Anatol) + +- SPL: + . Fixed bug #71204 (segfault if clean spl_autoload_funcs while autoloading). + (Laruence) + . Fixed bug #71202 (Autoload function registered by another not activated + immediately). (Laruence) + . Fixed bug #71311 (Use-after-free vulnerability in SPL(ArrayObject, + unserialize)). (Sean Heelan) + . Fixed bug #71313 (Use-after-free vulnerability in SPL(SplObjectStorage, + unserialize)). (Sean Heelan) + +- Standard: + . Fixed bug #71287 (Error message contains hexadecimal instead of decimal + number). (Laruence) + . Fixed bug #71264 (file_put_contents() returns unexpected value when + filesystem runs full). (Laruence) + . Fixed bug #71245 (file_get_contents() ignores "header" context option if + it's a reference). (Laruence) + . Fixed bug #71220 (Null pointer deref (segfault) in compact via ob_start). + (hugh at allthethings dot co dot nz) + . Fixed bug #71190 (substr_replace converts integers in original $search + array to strings). (Laruence) + . Fixed bug #71188 (str_replace converts integers in original $search array + to strings). (Laruence) + . Fixed bug #71132, #71197 (range() segfaults). (Thomas Punt) + +- WDDX: + . Fixed bug #71335 (Type Confusion in WDDX Packet Deserialization). (Stas) + +07 Jan 2016 PHP 7.0.2 + +- Core: + . Fixed bug #71165 (-DGC_BENCH=1 doesn't work on PHP7). + (y dot uchiyama dot 1015 at gmail dot com) + . Fixed bug #71163 (Segmentation Fault: cleanup_unfinished_calls). (Laruence) + . Fixed bug #71109 (ZEND_MOD_CONFLICTS("xdebug") doesn't work). (Laruence) + . Fixed bug #71092 (Segmentation fault with return type hinting). (Laruence) + . Fixed bug memleak in header_register_callback. (Laruence) + . Fixed bug #71067 (Local object in class method stays in memory for each + call). (Laruence) + . Fixed bug #66909 (configure fails utf8_to_mutf7 test). (Michael Orlitzky) + . Fixed bug #70781 (Extension tests fail on dynamic ext dependency). + (Francois Laupretre) + . Fixed bug #71089 (No check to duplicate zend_extension). (Remi) + . Fixed bug #71086 (Invalid numeric literal parse error within + highlight_string() function). (Nikita) + . Fixed bug #71154 (Incorrect HT iterator invalidation causes iterator reuse). + (Nikita) + . Fixed bug #52355 (Negating zero does not produce negative zero). (Andrea) + . Fixed bug #66179 (var_export() exports float as integer). (Andrea) + . Fixed bug #70804 (Unary add on negative zero produces positive zero). + (Andrea) + +- CURL: + . Fixed bug #71144 (Sementation fault when using cURL with ZTS). + (Michael Maroszek, Laruence) + +- DBA: + . Fixed key leak with invalid resource. (Laruence) + +- Filter: + . Fixed bug #71063 (filter_input(INPUT_ENV, ..) does not work). (Reeze Xia) + +- GD: + . Fixed bug #70976 (Memory Read via gdImageRotateInterpolated Array Index + Out of Bounds). (emmanuel dot law at gmail dot com). + +- FPM: + . Fixed bug #70755 (fpm_log.c memory leak and buffer overflow). (Stas) + +- FTP: + . Implemented FR #55651 (Option to ignore the returned FTP PASV address). + (abrender at elitehosts dot com) + +- Mbstring: + . Fixed bug #71066 (mb_send_mail: Program terminated with signal SIGSEGV, + Segmentation fault). (Laruence) + +- Opcache: + . Fixed bug #71127 (Define in auto_prepend_file is overwrite). (Laruence) + +- PCRE: + . Fixed bug #71178 (preg_replace with arrays creates [0] in replace array + if not already set). (Laruence) + +- Readline: + . Fixed bug #71094 (readline_completion_function corrupts static array on + second TAB). (Nikita) + +- Session: + . Fixed bug #71122 (Session GC may not remove obsolete session data). (Yasuo) + +- SPL: + . Fixed bug #71077 (ReflectionMethod for ArrayObject constructor returns + wrong number of parameters). (Laruence) + . Fixed bug #71153 (Performance Degradation in ArrayIterator with large + arrays). (Nikita) + +- Standard: + . Fixed bug #71270 (Heap BufferOver Flow in escapeshell functions). + (emmanuel dot law at gmail dot com) + +- WDDX: + . Fixed bug #70661 (Use After Free Vulnerability in WDDX Packet Deserialization). + (taoguangchen at icloud dot com) + . Fixed bug #70741 (Session WDDX Packet Deserialization Type Confusion + Vulnerability). (taoguangchen at icloud dot com) + +- XMLRPC + . Fixed bug #70728 (Type Confusion Vulnerability in PHP_to_XMLRPC_worker). + (Julien) + +17 Dec 2015, PHP 7.0.1 + +- Core: + . Fixed bug #71105 (Format String Vulnerability in Class Name Error Message). + (CVE-2015-8617) (andrew at jmpesp dot org) + . Fixed bug #70831 (Compile fails on system with 160 CPUs). (Daniel Axtens) + . Fixed bug #71006 (symbol referencing errors on Sparc/Solaris). (Dmitry) + . Fixed bug #70997 (When using parentClass:: instead of parent::, static + context changed). (Dmitry) + . Fixed bug #70970 (Segfault when combining error handler with output + buffering). (Laruence) + . Fixed bug #70967 (Weird error handling for __toString when Error is + thrown). (Laruence) + . Fixed bug #70958 (Invalid opcode while using ::class as trait method + paramater default value). (Laruence) + . Fixed bug #70944 (try{ } finally{} can create infinite chains of + exceptions). (Laruence) + . Fixed bug #70931 (Two errors messages are in conflict). (dams, Laruence) + . Fixed bug #70904 (yield from incorrectly marks valid generator as + finished). (Bob) + . Fixed bug #70899 (buildconf failure in extensions). (Bob, Reeze) + . Fixed bug #61751 (SAPI build problem on AIX: Undefined symbol: + php_register_internal_extensions). (Lior Kaplan) + . Fixed \int (or generally every scalar type name with leading backslash) + to not be accepted as type name. (Bob) + . Fixed exception not being thrown immediately into a generator yielding + from an array. (Bob) + . Fixed bug #70987 (static::class within Closure::call() causes segfault). + (Andrea) + . Fixed bug #71013 (Incorrect exception handler with yield from). (Bob) + . Fixed double free in error condition of format printer. (Bob) + +- CLI server: + . Fixed bug #71005 (Segfault in php_cli_server_dispatch_router()). (Adam) + +- Intl: + . Fixed bug #71020 (Use after free in Collator::sortWithSortKeys). + (CVE-2015-8616) (emmanuel dot law at gmail dot com, Laruence) + +- Mysqlnd: + . Fixed bug #68077 (LOAD DATA LOCAL INFILE / open_basedir restriction). + (Laruence) + . Fixed bug #68344 (MySQLi does not provide way to disable peer certificate + validation) by introducing MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT + connection flag. (Andrey) + +- OCI8: + . Fixed LOB implementation size_t/zend_long mismatch reported by gcov. + (Senthil) + +- Opcache: + . Fixed bug #71024 (Unable to use PHP 7.0 x64 side-by-side with PHP 5.6 x32 + on the same server). (Anatol) + . Fixed bug #70991 (zend_file_cache.c:710: error: array type has incomplete + element type). (Laruence) + . Fixed bug #70977 (Segmentation fault with opcache.huge_code_pages=1). + (Laruence) + +- PDO_Firebird: + . Fixed bug #60052 (Integer returned as a 64bit integer on X64_86). (Mariuz) + +- Phpdbg: + . Fixed stderr being written to stdout. (Bob) + +- Reflection: + . Fixed bug #71018 (ReflectionProperty::setValue() behavior changed). + (Laruence) + . Fixed bug #70982 (setStaticPropertyValue behaviors inconsistently with + 5.6). (Laruence) + +- Soap: + . Fixed bug #70993 (Array key references break argument processing). + (Laruence) + +- SPL: + . Fixed bug #71028 (Undefined index with ArrayIterator). (Laruence) + +- SQLite3: + . Fixed bug #71049 (SQLite3Stmt::execute() releases bound parameter instead + of internal buffer). (Laruence) + +- Standard: + . Fixed bug #70999 (php_random_bytes: called object is not a function). + (Scott) + . Fixed bug #70960 (ReflectionFunction for array_unique returns wrong number + of parameters). (Laruence) + +- Streams/Socket: + . Add IPV6_V6ONLY constant / make it usable in stream contexts. (Bob) + +03 Dec 2015, PHP 7.0.0 + +- Core: + . Fixed bug #70947 (INI parser segfault with INI_SCANNER_TYPED). (Laruence) + . Fixed bug #70914 (zend_throw_or_error() format string vulnerability). + (Taoguang Chen) + . Fixed bug #70912 (Null ptr dereference instantiating class with invalid + array property). (Laruence) + . Fixed bug #70895, #70898 (null ptr deref and segfault with crafted calable). + (Anatol, Laruence) + . Fixed bug #70249 (Segmentation fault while running PHPUnit tests on + phpBB 3.2-dev). (Laruence) + . Fixed bug #70805 (Segmentation faults whilst running Drupal 8 test suite). + (Dmitry, Laruence) + . Fixed bug #70842 (Persistent Stream Segmentation Fault). (Caleb Champlin) + . Fixed bug #70862 (Several functions do not check return code of + php_stream_copy_to_mem()). (Anatol) + . Fixed bug #70863 (Incorect logic to increment_function for proxy objects). + (Anatol) + . Fixed bug #70323 (Regression in zend_fetch_debug_backtrace() can cause + segfaults). (Aharvey, Laruence) + . Fixed bug #70873 (Regression on private static properties access). + (Laruence) + . Fixed bug #70748 (Segfault in ini_lex () at Zend/zend_ini_scanner.l). + (Laruence) + . Fixed bug #70689 (Exception handler does not work as expected). (Laruence) + . Fixed bug #70430 (Stack buffer overflow in zend_language_parser()). (Nikita) + . Fixed bug #70782 (null ptr deref and segfault (zend_get_class_fetch_type)). + (Nikita) + . Fixed bug #70785 (Infinite loop due to exception during identical + comparison). (Laruence) + . Fixed bug #70630 (Closure::call/bind() crash with ReflectionFunction-> + getClosure()). (Dmitry, Bob) + . Fixed bug #70662 (Duplicate array key via undefined index error handler). + (Nikita) + . Fixed bug #70681 (Segfault when binding $this of internal instance method + to null). (Nikita) + . Fixed bug #70685 (Segfault for getClosure() internal method rebind with + invalid $this). (Nikita) + . Added zend_internal_function.reserved[] fields. (Dmitry) + . Fixed bug #70557 (Memleak on return type verifying failed). (Laruence) + . Fixed bug #70555 (fun_get_arg() on unsetted vars return UNKNOW). (Laruence) + . Fixed bug #70548 (Redundant information printed in case of uncaught engine + exception). (Laruence) + . Fixed bug #70547 (unsetting function variables corrupts backtrace). + (Laruence) + . Fixed bug #70528 (assert() with instanceof adds apostrophes around class + name). (Laruence) + . Fixed bug #70481 (Memory leak in auto_global_copy_ctor() in ZTS build). + (Laruence) + . Fixed bug #70431 (Memory leak in php_ini.c). (Senthil, Laruence) + . Fixed bug #70478 (**= does no longer work). (Bob) + . Fixed bug #70398 (SIGSEGV, Segmentation fault zend_ast_destroy_ex). + (Dmitry, Bob, Laruence) + . Fixed bug #70332 (Wrong behavior while returning reference on object). + (Laruence, Dmitry) + . Fixed bug #70300 (Syntactical inconsistency with new group use syntax). + (marcio dot web2 at gmail dot com) + . Fixed bug #70321 (Magic getter breaks reference to array property). + (Laruence) + . Fixed bug #70187 (Notice: unserialize(): Unexpected end of serialized + data). (Dmitry) + . Fixed bug #70145 (From field incorrectly parsed from headers). (Anatol) + . Fixed bug #70370 (Bundled libtool.m4 doesn't handle FreeBSD 10 when + building extensions). (Adam) + . Fixed bug causing exception traces with anon classes to be truncated. (Bob) + . Fixed bug #70397 (Segmentation fault when using Closure::call and yield). + (Bob) + . Fixed bug #70299 (Memleak while assigning object offsetGet result). + (Laruence) + . Fixed bug #70288 (Apache crash related to ZEND_SEND_REF). (Laruence) + . Fixed bug #70262 (Accessing array crashes PHP 7.0beta3). + (Laruence, Dmitry) + . Fixed bug #70258 (Segfault if do_resize fails to allocated memory). + (Laruence) + . Fixed bug #70253 (segfault at _efree () in zend_alloc.c:1389). (Laruence) + . Fixed bug #70240 (Segfault when doing unset($var());). (Laruence) + . Fixed bug #70223 (Incrementing value returned by magic getter). (Laruence) + . Fixed bug #70215 (Segfault when __invoke is static). (Bob) + . Fixed bug #70207 (Finally is broken with opcache). (Laruence, Dmitry) + . Fixed bug #70173 (ZVAL_COPY_VALUE_EX broken for 32bit Solaris Sparc). + (Laruence, cmb) + . Fixed bug #69487 (SAPI may truncate POST data). (cmb) + . Fixed bug #70198 (Checking liveness does not work as expected). + (Shafreeck Sea, Anatol Belski) + . Fixed bug #70241,#70293 (Skipped assertions affect Generator returns). (Bob) + . Fixed bug #70239 (Creating a huge array doesn't result in exhausted, + but segfault). (Laruence, Anatol) + . Fixed "finally" issues. (Nikita, Dmitry) + . Fixed bug #70098 (Real memory usage doesn't decrease). (Dmitry) + . Fixed bug #70159 (__CLASS__ is lost in closures). (Julien) + . Fixed bug #70156 (Segfault in zend_find_alias_name). (Laruence) + . Fixed bug #70124 (null ptr deref / seg fault in ZEND_HANDLE_EXCEPTION). + (Laruence) + . Fixed bug #70117 (Unexpected return type error). (Laruence) + . Fixed bug #70106 (Inheritance by anonymous class). (Bob) + . Fixed bug #69674 (SIGSEGV array.c:953). (cmb) + . Fixed bug #70164 (__COMPILER_HALT_OFFSET__ under namespace is not defined). + (Bob) + . Fixed bug #70108 (sometimes empty $_SERVER['QUERY_STRING']). (Anatol) + . Fixed bug #70179 ($this refcount issue). (Bob) + . Fixed bug #69896 ('asm' operand has impossible constraints). (Anatol) + . Fixed bug #70183 (null pointer deref (segfault) in zend_eval_const_expr). + (Hugh Davenport) + . Fixed bug #70182 (Segfault in ZEND_ASSIGN_DIV_SPEC_CV_UNUSED_HANDLER). + (Hugh Davenport) + . Fixed bug #69793 (Remotely triggerable stack exhaustion via recursive + method calls). (Stas) + . Fixed bug #69892 (Different arrays compare indentical due to integer key + truncation). (Nikita) + . Fixed bug #70121 (unserialize() could lead to unexpected methods execution + / NULL pointer deref). (Stas) + . Fixed bug #70089 (segfault at ZEND_FETCH_DIM_W_SPEC_VAR_CONST_HANDLER ()). + (Laruence) + . Fixed bug #70057 (Build failure on 32-bit Mac OS X 10.6.8: recursive + inlining). (Laruence) + . Fixed bug #70012 (Exception lost with nested finally block). (Laruence) + . Fixed bug #69996 (Changing the property of a cloned object affects the + original). (Dmitry, Laruence) + . Fixed bug #70083 (Use after free with assign by ref to overloaded objects). + (Bob) + . Fixed bug #70006 (cli - function with default arg = STDOUT crash output). + (Laruence) + . Fixed bug #69521 (Segfault in gc_collect_cycles()). + (arjen at react dot com, Laruence) + . Improved zend_string API. (Francois Laupretre) + . Fixed bug #69955 (Segfault when trying to combine [] and assign-op on + ArrayAccess object). (Laruence) + . Fixed bug #69957 (Different ways of handling div/mod/intdiv). (Bob) + . Fixed bug #69900 (Too long timeout on pipes). (Anatol) + . Fixed bug #69872 (uninitialised value in strtr with array). (Laruence) + . Fixed bug #69868 (Invalid read of size 1 in zend_compile_short_circuiting). + (Laruence) + . Fixed bug #69849 (Broken output of apache_request_headers). (Kalle) + . Fixed bug #69840 (iconv_substr() doesn't work with UTF-16BE). (Kalle) + . Fixed bug #69823 (PHP 7.0.0alpha1 segmentation fault when exactly 33 + extensions are loaded). (Laruence) + . Fixed bug #69805 (null ptr deref and seg fault in zend_resolve_class_name). + (Laruence) + . Fixed bug #69802 (Reflection on Closure::__invoke borks type hint class + name). (Dmitry) + . Fixed bug #69761 (Serialization of anonymous classes should be prevented). + (Laruence) + . Fixed bug #69551 (parse_ini_file() and parse_ini_string() segmentation + fault). (Christoph M. Becker) + . Fixed bug #69781 (phpinfo() reports Professional Editions of Windows + 7/8/8.1/10 as "Business"). (Christian Wenz) + . Fixed bug #69835 (phpinfo() does not report many Windows SKUs). + (Christian Wenz) + . Fixed bug #69889 (Null coalesce operator doesn't work for string offsets). + (Nikita) + . Fixed bug #69891 (Unexpected array comparison result). (Nikita) + . Fixed bug #69892 (Different arrays compare indentical due to integer key + truncation). (Nikita) + . Fixed bug #69893 (Strict comparison between integer and empty string keys + crashes). (Nikita) + . Fixed bug #69767 (Default parameter value with wrong type segfaults). + (cmb, Laruence) + . Fixed bug #69756 (Fatal error: Nesting level too deep - recursive dependency + ? with ===). (Dmitry, Laruence) + . Fixed bug #69758 (Item added to array not being removed by array_pop/shift + ). (Laruence) + . Fixed bug #68475 (Add support for $callable() sytnax with 'Class::method'). + (Julien, Aaron Piotrowski) + . Fixed bug #69485 (Double free on zend_list_dtor). (Laruence) + . Fixed bug #69427 (Segfault on magic method __call of private method in + superclass). (Laruence) + . Improved __call() and __callStatic() magic method handling. Now they are + called in a stackless way using ZEND_CALL_TRAMPOLINE opcode, without + additional stack frame. (Laruence, Dmitry) + . Optimized strings concatenation. (Dmitry, Laruence) + . Fixed weird operators behavior. Division by zero now emits warning and + returns +/-INF, modulo by zero and intdid() throws an exception, shifts + by negative offset throw exceptions. Compile-time evaluation of division + by zero is disabled. (Dmitry, Andrea, Nikita) + . Fixed bug #69371 (Hash table collision leads to inaccessible array keys). + (Laruence) + . Fixed bug #68933 (Invalid read of size 8 in zend_std_read_property). + (Laruence, arjen at react dot com) + . Fixed bug #68252 (segfault in Zend/zend_hash.c in function + _zend_hash_del_el). (Laruence) + . Fixed bug #65598 (Closure executed via static autoload incorrectly marked as + static). (Nikita) + . Fixed bug #66811 (Cannot access static::class in lambda, writen outside of a + class). (Nikita) + . Fixed bug #69568 (call a private function in closure failed). (Nikita) + . Added PHP_INT_MIN constant. (Andrea) + . Added Closure::call() method. (Andrea) + . Fixed bug #67959 (Segfault when calling phpversion('spl')). (Florian) + . Implemented the RFC `Catchable "Call to a member function bar() on a + non-object"`. (Timm) + . Added options parameter for unserialize allowing to specify acceptable + classes (https://wiki.php.net/rfc/secure_unserialize). (Stas) + . Fixed bug #63734 (Garbage collector can free zvals that are still + referenced). (Dmitry) + . Removed ZEND_ACC_FINAL_CLASS, promoting ZEND_ACC_FINAL as final class + modifier. (Guilherme Blanco) + . is_long() & is_integer() is now an alias of is_int(). (Kalle) + . Implemented FR #55467 (phpinfo: PHP Variables with $ and single quotes). (Kalle) + . Added ?? operator. (Andrea) + . Added <=> operator. (Andrea) + . Added \u{xxxxx} Unicode Codepoint Escape Syntax. (Andrea) + . Fixed oversight where define() did not support arrays yet const syntax did. + (Andrea, Dmitry) + . Use "integer" and "float" instead of "long" and "double" in ZPP, type hint + and conversion error messages. (Andrea) + . Implemented FR #55428 (E_RECOVERABLE_ERROR when output buffering in output + buffering handler). (Kalle) + . Removed scoped calls of non-static methods from an incompatible $this + context. (Nikita) + . Removed support for #-style comments in ini files. (Nikita) + . Removed support for assigning the result of new by reference. (Nikita) + . Invalid octal literals in source code now produce compile errors, fixes + PHPSadness #31. (Andrea) + . Removed dl() function on fpm-fcgi. (Nikita) + . Removed support for hexadecimal numeric strings. (Nikita) + . Removed obsolete extensions and SAPIs. See the full list in UPGRADING. (Anatol) + . Added NULL byte protection to exec, system and passthru. (Yasuo) + . Added error_clear_last() function. (Reeze Xia) + . Fixed bug #68797 (Number 2.2250738585072012e-308 converted incorrectly). + (Anatol) + . Improved zend_qsort(using hybrid sorting algo) for better performance, + and also renamed zend_qsort to zend_sort. (Laruence) + . Added stable sorting algo zend_insert_sort. (Laruence) + . Improved zend_memnchr(using sunday algo) for better performance. (Laruence) + . Implemented the RFC `Scalar Type Decalarations v0.5`. (Anthony) + . Implemented the RFC `Group Use Declarations`. (Marcio) + . Implemented the RFC `Continue Output Buffering`. (Mike) + . Implemented the RFC `Constructor behaviour of internal classes`. (Dan, Dmitry) + . Implemented the RFC `Fix "foreach" behavior`. (Dmitry) + . Implemented the RFC `Generator Delegation`. (Bob) + . Implemented the RFC `Anonymous Class Support`. (Joe, Nikita, Dmitry) + . Implemented the RFC `Context Sensitive Lexer`. (Marcio Almada) + . Fixed bug #69511 (Off-by-one buffer overflow in php_sys_readlink). + (Jan Starke, Anatol) + +- CLI server: + . Fixed bug #68291 (404 on urls with '+'). (cmb) + . Fixed bug #66606 (Sets HTTP_CONTENT_TYPE but not CONTENT_TYPE). + (wusuopu, cmb) + . Fixed bug #70264 (CLI server directory traversal). (cmb) + . Fixed bug #69655 (php -S changes MKCALENDAR request method to MKCOL). (cmb) + . Fixed bug #64878 (304 responses return Content-Type header). (cmb) + . Refactor MIME type handling to use a hash table instead of linear search. + (Adam) + . Update the MIME type list from the one shipped by Apache HTTPD. (Adam) + . Added support for SEARCH WebDav method. (Mats Lindh) + +- COM: + . Fixed bug #69939 (Casting object to bool returns false). (Kalle) + +- Curl: + . Fixed bug #70330 (Segmentation Fault with multiple "curl_copy_handle"). + (Laruence) + . Fixed bug #70163 (curl_setopt_array() type confusion). (Laruence) + . Fixed bug #70065 (curl_getinfo() returns corrupted values). (Anatol) + . Fixed bug #69831 (Segmentation fault in curl_getinfo). (im dot denisenko at + yahoo dot com) + . Fixed bug #68937 (Segfault in curl_multi_exec). (Laruence) + . Removed support for unsafe file uploads. (Nikita) + +- Date: + . Fixed bug #70245 (strtotime does not emit warning when 2nd parameter is + object or string). (cmb) + . Fixed bug #70266 (DateInterval::__construct.interval_spec is not supposed to + be optional). (cmb) + . Fixed bug #70277 (new DateTimeZone($foo) is ignoring text after null byte). + (cmb) + . Fixed day_of_week function as it could sometimes return negative values + internally. (Derick) + . Removed $is_dst parameter from mktime() and gmmktime(). (Nikita) + . Removed date.timezone warning + (https://wiki.php.net/rfc/date.timezone_warning_removal). (Bob) + . Added "v" DateTime format modifier to get the 3-digit version of fraction + of seconds. (Mariano Iglesias) + . Implemented FR #69089 (Added DateTime::RFC3339_EXTENDED to output in + RFC3339 Extended format which includes fraction of seconds). (Mariano + Iglesias) + +- DBA: + . Fixed bug #62490 (dba_delete returns true on missing item (inifile)). (Mike) + . Fixed bug #68711 (useless comparisons). (bugreports at internot dot info) + +- DOM: + . Fixed bug #70558 ("Couldn't fetch" error in + DOMDocument::registerNodeClass()). (Laruence) + . Fixed bug #70001 (Assigning to DOMNode::textContent does additional entity + encoding). (cmb) + . Fixed bug #69846 (Segmenation fault (access violation) when iterating over + DOMNodeList). (Anatol Belski) + . Made DOMNode::textContent writeable. (Tjerk) + +- EXIF: + . Fixed bug #70385 (Buffer over-read in exif_read_data with TIFF IFD tag byte + value of 32 bytes). (Stas) + +- Fileinfo: + . Fixed bug #66242 (libmagic: don't assume char is signed). (ArdB) + +- Filter: + . New FILTER_VALIDATE_DOMAIN and better RFC conformance for FILTER_VALIDATE_URL. (Kevin Dunglas) + +- FPM: + . Fixed bug #70538 ("php-fpm -i" crashes). (rainer dot jung at + kippdata dot de) + . Fixed bug #70279 (HTTP Authorization Header is sometimes passed to newer + reqeusts). (Laruence) + . Fixed bug #68945 (Unknown admin values segfault pools). (Laruence) + . Fixed bug #65933 (Cannot specify config lines longer than 1024 bytes). (Chris Wright) + . Implemented FR #67106 (Split main fpm config). (Elan Ruusamäe, Remi) + +- FTP: + . Fixed bug #69082 (FTPS support on Windows). (Anatol) + +- GD: + . Fixed bug #53156 (imagerectangle problem with point ordering). (cmb) + . Fixed bug #66387 (Stack overflow with imagefilltoborder). (cmb) + . Fixed bug #70102 (imagecreatefromwebm() shifts colors). (cmb) + . Fixed bug #66590 (imagewebp() doesn't pad to even length). (cmb) + . Fixed bug #66882 (imagerotate by -90 degrees truncates image by 1px). (cmb) + . Fixed bug #70064 (imagescale(..., IMG_BICUBIC) leaks memory). (cmb) + . Fixed bug #69024 (imagescale segfault with palette based image). (cmb) + . Fixed bug #53154 (Zero-height rectangle has whiskers). (cmb) + . Fixed bug #67447 (imagecrop() add a black line when cropping). (cmb) + . Fixed bug #68714 (copy 'n paste error). (cmb) + . Fixed bug #66339 (PHP segfaults in imagexbm). (cmb) + . Fixed bug #70047 (gd_info() doesn't report WebP support). (cmb) + . Replace libvpx with libwebp for bundled libgd. (cmb, Anatol) + . Fixed bug #61221 (imagegammacorrect function loses alpha channel). (cmb) + . Made fontFetch's path parser thread-safe. (Sara) + . Removed T1Lib support. (Kalle) + +- GMP: + . Fixed bug #70284 (Use after free vulnerability in unserialize() with GMP). + (stas) + +- hash: + . Fixed bug #70312 (HAVAL gives wrong hashes in specific cases). (letsgolee + at naver dot com) + +- IMAP: + . Fixed bug #70158 (Building with static imap fails). (cmb) + . Fixed bug #69998 (curl multi leaking memory). (Pierrick) + +- Intl: + . Fixed bug #70453 (IntlChar::foldCase() incorrect arguments and missing + constants). (cmb) + . Fixed bug #70454 (IntlChar::forDigit second parameter should be optional). + (cmb, colinodell) + . Removed deprecated aliases datefmt_set_timezone_id() and + IntlDateFormatter::setTimeZoneID(). (Nikita) + +- JSON: + . Fixed bug #62010 (json_decode produces invalid byte-sequences). + (Jakub Zelenka) + . Fixed bug #68546 (json_decode() Fatal error: Cannot access property + started with '\0'). (Jakub Zelenka) + . Replace non-free JSON parser with a parser from Jsond extension, fixes #63520 + (JSON extension includes a problematic license statement). (Jakub Zelenka) + . Fixed bug #68938 (json_decode() decodes empty string without error). + (jeremy at bat-country dot us) + +- LDAP: + . Fixed bug #47222 (Implement LDAP_OPT_DIAGNOSTIC_MESSAGE). (Andreas Heigl) + +- LiteSpeed: + . Updated LiteSpeed SAPI code from V5.5 to V6.6. (George Wang) + +- libxml: + . Fixed handling of big lines in error messages with libxml >= 2.9.0. + (Christoph M. Becker) + +- Mcrypt: + . Fixed bug #70625 (mcrypt_encrypt() won't return data when no IV was + specified under RC4). (Nikita) + . Fixed bug #69833 (mcrypt fd caching not working). (Anatol) + . Fixed possible read after end of buffer and use after free. (Dmitry) + . Removed mcrypt_generic_end() alias. (Nikita) + . Removed mcrypt_ecb(), mcrypt_cbc(), mcrypt_cfb(), mcrypt_ofb(). (Nikita) + +- Mysqli: + . Fixed bug #32490 (constructor of mysqli has wrong name). (cmb) + +- Mysqlnd: + . Fixed bug #70949 (SQL Result Sets With NULL Can Cause Fatal Memory Errors). + (Laruence) + . Fixed bug #70384 (mysqli_real_query():Unknown type 245 sent by the server). + (Andrey) + . Fixed bug #70456 (mysqlnd doesn't activate TCP keep-alive when connecting to + a server). (Sergei Turchanov) + . Fixed bug #70572 segfault in mysqlnd_connect. (Andrey, Remi) + . Fixed Bug #69796 (mysqli_stmt::fetch doesn't assign null values to + bound variables). (Laruence) + +- OCI8: + . Fixed memory leak with LOBs. (Senthil) + . Fixed bug #68298 (OCI int overflow) (Senthil). + . Corrected oci8 hash destructors to prevent segfaults, and a few other fixes. + (Cameron Porter) + +- ODBC: + . Fixed bug #69975 (PHP segfaults when accessing nvarchar(max) defined + columns). (cmb) + +- Opcache: + . Fixed bug #70656 (require() statement broken after opcache_reset() or a + few hours of use). (Laruence) + . Fixed bug #70843 (Segmentation fault on MacOSX with + opcache.file_cache_only=1). (Laruence) + . Fixed bug #70724 (Undefined Symbols from opcache.so on Mac OS X 10.10). + (Laruence) + . Fixed compatibility with Windows 10 (see also bug #70652). (Anatol) + . Attmpt to fix "Unable to reattach to base address" problem. (Matt Ficken) + . Fixed bug #70423 (Warning Internal error: wrong size calculation). (Anatol) + . Fixed bug #70237 (Empty while and do-while segmentation fault with opcode + on CLI enabled). (Dmitry, Laruence) + . Fixed bug #70111 (Segfault when a function uses both an explicit return + type and an explicit cast). (Laruence) + . Fixed bug #70058 (Build fails when building for i386). (Laruence) + . Fixed bug #70022 (Crash with opcache using opcache.file_cache_only=1). + (Anatol) + . Removed opcache.load_comments configuration directive. Now doc comments + loading costs nothing and always enabled. (Dmitry) + . Fixed bug #69838 (Wrong size calculation for function table). (Anatol) + . Fixed bug #69688 (segfault with eval and opcache fast shutdown). + (Laruence) + . Added experimental (disabled by default) file based opcode cache. + (Dmitry, Laruence, Anatol) + . Fixed bug with try blocks being removed when extended_info opcode + generation is turned on. (Laruence) + . Fixed bug #68644 (strlen incorrect : mbstring + func_overload=2 +UTF-8 + + Opcache). (Laruence) + +- OpenSSL: + . Require at least OpenSSL version 0.9.8. (Jakub Zelenka) + . Fixed bug #68312 (Lookup for openssl.cnf causes a message box). (Anatol) + . Fixed bug #55259 (openssl extension does not get the DH parameters from + DH key resource). (Jakub Zelenka) + . Fixed bug #70395 (Missing ARG_INFO for openssl_seal()). (cmb) + . Fixed bug #60632 (openssl_seal fails with AES). (Jakub Zelenka) + . Implemented FR #70438 (Add IV parameter for openssl_seal and openssl_open) + (Jakub Zelenka) + . Fixed bug #70014 (openssl_random_pseudo_bytes() is not cryptographically + secure). (Stas) + . Fixed bug #69882 (OpenSSL error "key values mismatch" after + openssl_pkcs12_read with extra cert). (Tomasz Sawicki) + . Added "alpn_protocols" SSL context option allowing encrypted client/server + streams to negotiate alternative protocols using the ALPN TLS extension when + built against OpenSSL 1.0.2 or newer. Negotiated protocol information is + accessible through stream_get_meta_data() output. + . Removed "CN_match" and "SNI_server_name" SSL context options. Use automatic + detection or the "peer_name" option instead. (Nikita) + +- Pcntl: + . Fixed bug #70386 (Can't compile on NetBSD because of missing WCONTINUED + and WIFCONTINUED). (Matteo) + . Fixed bug #60509 (pcntl_signal doesn't decrease ref-count of old handler + when setting SIG_DFL). (Julien) + . Implemented FR #68505 (Added wifcontinued and wcontinued). (xilon-jul) + . Added rusage support to pcntl_wait() and pcntl_waitpid(). (Anton Stepanenko, + Tony) + +- PCRE: + . Fixed bug #70232 (Incorrect bump-along behavior with \K and empty string + match). (cmb) + . Fixed bug #70345 (Multiple vulnerabilities related to PCRE functions). + (Anatol Belski) + . Fixed bug #70232 (Incorrect bump-along behavior with \K and empty string + match). (cmb) + . Fixed bug #53823 (preg_replace: * qualifier on unicode replace garbles the + string). (cmb) + . Fixed bug #69864 (Segfault in preg_replace_callback). (cmb, ab) + +- PDO: + . Fixed bug #70861 (Segmentation fault in pdo_parse_params() during Drupal 8 + test suite). (Anatol) + . Fixed bug #70389 (PDO constructor changes unrelated variables). (Laruence) + . Fixed bug #70272 (Segfault in pdo_mysql). (Laruence) + . Fixed bug #70221 (persistent sqlite connection + custom function + segfaults). (Laruence) + . Removed support for the /e (PREG_REPLACE_EVAL) modifier. (Nikita) + . Fixed bug #59450 (./configure fails with "Cannot find php_pdo_driver.h"). + (maxime dot besson at smile dot fr) + +- PDO_DBlib: + . Fixed bug #69757 (Segmentation fault on nextRowset). + (miracle at rpz dot name) + +- PDO_mysql: + . Fixed bug #68424 (Add new PDO mysql connection attr to control multi + statements option). (peter dot wolanin at acquia dot com) + +- PDO_OCI: + . Fixed bug #70308 (PDO::ATTR_PREFETCH is ignored). (Chris Jones) + +- PDO_pgsql: + . Fixed bug #69752 (PDOStatement::execute() leaks memory with DML + Statements when closeCuror() is u). (Philip Hofstetter) + . Removed PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT attribute in favor of + ATTR_EMULATE_PREPARES). (Nikita) + +- Phar: + . Fixed bug #69720 (Null pointer dereference in phar_get_fp_offset()). (Stas) + . FIxed bug #70433 (Uninitialized pointer in phar_make_dirstream when zip + entry filename is "/"). (Stas) + . Improved fix for bug #69441. (Anatol Belski) + . Fixed bug #70019 (Files extracted from archive may be placed outside of + destination directory). (Anatol Belski) + +- Phpdbg: + . Fixed bug #70614 (incorrect exit code in -rr mode with Exceptions). (Bob) + . Fixed bug #70532 (phpdbg must respect set_exception_handler). (Bob) + . Fixed bug #70531 (Run and quit mode (-qrr) should not fallback to + interactive mode). (Bob) + . Fixed bug #70533 (Help overview (-h) does not rpint anything under Windows). + (Anatol) + . Fixed bug #70449 (PHP won't compile on 10.4 and 10.5 because of missing + constants). (Bob) + . Fixed bug #70214 (FASYNC not defined, needs sys/file.h include). (Bob) + . Fixed bug #70138 (Segfault when displaying memory leaks). (Bob) + +- Reflection: + . Fixed bug #70650 (Wrong docblock assignment). (Marcio) + . Fixed bug #70674 (ReflectionFunction::getClosure() leaks memory when used + for internal functions). (Dmitry, Bob) + . Fixed bug causing bogus traces for ReflectionGenerator::getTrace(). (Bob) + . Fixed inheritance chain of Reflector interface. (Tjerk) + . Added ReflectionGenerator class. (Bob) + . Added reflection support for return types and type declarations. (Sara, + Matteo) + +- Session: + . Fixed bug #70876 (Segmentation fault when regenerating session id with + strict mode). (Laruence) + . Fixed bug #70529 (Session read causes "String is not zero-terminated" error). + (Yasuo) + . Fixed bug #70013 (Reference to $_SESSION is lost after a call to + session_regenerate_id()). (Yasuo) + . Fixed bug #69952 (Data integrity issues accessing superglobals by + reference). (Bob) + . Fixed bug #67694 (Regression in session_regenerate_id()). (Tjerk) + . Fixed bug #68941 (mod_files.sh is a bash-script). (bugzilla at ii.nl, Yasuo) + +- SOAP: + . Fixed bug #70940 (Segfault in soap / type_to_string). (Remi) + . Fixed bug #70900 (SoapClient systematic out of memory error). (Dmitry) + . Fixed bug #70875 (Segmentation fault if wsdl has no targetNamespace + attribute). (Matteo) + . Fixed bug #70715 (Segmentation fault inside soap client). (Laruence) + . Fixed bug #70709 (SOAP Client generates Segfault). (Laruence) + . Fixed bug #70388 (SOAP serialize_function_call() type confusion / RCE). + (Stas) + . Fixed bug #70081 (SoapClient info leak / null pointer dereference via + multiple type confusions). (Stas) + . Fixed bug #70079 (Segmentation fault after more than 100 SoapClient + calls). (Laruence) + . Fixed bug #70032 (make_http_soap_request calls + zend_hash_get_current_key_ex(,,,NULL). (Laruence) + . Fixed bug #68361 (Segmentation fault on SoapClient::__getTypes). (Laruence) + +- SPL: + . Fixed bug #70959 (ArrayObject unserialize does not restore protected + fields). (Laruence) + . Fixed bug #70853 (SplFixedArray throws exception when using ref variable + as index). (Laruence) + . Fixed bug #70868 (PCRE JIT and pattern reuse segfault). (Laruence) + . Fixed bug #70730 (Incorrect ArrayObject serialization if unset is called + in serialize()). (Laruence) + . Fixed bug #70573 (Cloning SplPriorityQueue leads to memory leaks). (Dmitry) + . Fixed bug #70303 (Incorrect constructor reflection for ArrayObject). (cmb) + . Fixed bug #70068 (Dangling pointer in the unserialization of ArrayObject + items). (sean.heelan) + . Fixed bug #70166 (Use After Free Vulnerability in unserialize() with + SPLArrayObject). (taoguangchen at icloud dot com) + . Fixed bug #70168 (Use After Free Vulnerability in unserialize() with + SplObjectStorage). (taoguangchen at icloud dot com) + . Fixed bug #70169 (Use After Free Vulnerability in unserialize() with + SplDoublyLinkedList). (taoguangchen at icloud dot com) + . Fixed bug #70053 (MutlitpleIterator array-keys incompatible change in + PHP 7). (Tjerk) + . Fixed bug #69970 (Use-after-free vulnerability in + spl_recursive_it_move_forward_ex()). (Laruence) + . Fixed bug #69845 (ArrayObject with ARRAY_AS_PROPS broken). (Dmitry) + . Changed ArrayIterator implementation using zend_hash_iterator_... API. + Allowed modification of iterated ArrayObject using the same behavior + as proposed in `Fix "foreach" behavior`. Removed "Array was modified + outside object and internal position is no longer valid" hack. (Dmitry) + . Implemented FR #67886 (SplPriorityQueue/SplHeap doesn't expose extractFlags + nor curruption state). (Julien) + . Fixed bug #66405 (RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + breaks the RecursiveIterator). (Paul Garvin) + +- SQLite3: + . Fixed bug #70571 (Memory leak in sqlite3_do_callback). (Adam) + . Fixed bug #69972 (Use-after-free vulnerability in + sqlite3SafetyCheckSickOrOk()). (Laruence) + . Fixed bug #69897 (segfault when manually constructing SQLite3Result). + (Kalle) + . Fixed bug #68260 (SQLite3Result::fetchArray declares wrong + required_num_args). (Julien) + +- Standard: + . Fixed count on symbol tables. (Laruence) + . Fixed bug #70963 (Unserialize shows UNKNOWN in result). (Laruence) + . Fixed bug #70910 (extract() breaks variable references). (Laruence) + . Fixed bug #70808 (array_merge_recursive corrupts memory of unset items). + (Laruence) + . Fixed bug #70667 (strtr() causes invalid writes and a crashes). (Dmitry) + . Fixed bug #70668 (array_keys() doesn't respect references when $strict is + true). (Bob, Dmitry) + . Implemented the RFC `Random Functions Throwing Exceptions in PHP 7`. + (Sammy Kaye Powers, Anthony) + . Fixed bug #70487 (pack('x') produces an error). (Nikita) + . Fixed bug #70342 (changing configuration with ignore_user_abort(true) isn't + working). (Laruence) + . Fixed bug #70295 (Segmentation fault with setrawcookie). (Bob) + . Fixed bug #67131 (setcookie() conditional for empty values not met). (cmb) + . Fixed bug #70365 (Use-after-free vulnerability in unserialize() with + SplObjectStorage). (taoguangchen at icloud dot com) + . Fixed bug #70366 (Use-after-free vulnerability in unserialize() with + SplDoublyLinkedList). (taoguangchen at icloud dot com) + . Fixed bug #70250 (extract() turns array elements to references). + (Laruence) + . Fixed bug #70211 (php 7 ZEND_HASH_IF_FULL_DO_RESIZE use after free). + (Laruence) + . Fixed bug #70208 (Assert breaking access on objects). (Bob) + . Fixed bug #70140 (str_ireplace/php_string_tolower - Arbitrary Code + Execution). (CVE-2015-6527) (Laruence) + . Implemented FR #70112 (Allow "dirname" to go up various times). (Remi) + . Fixed bug #36365 (scandir duplicates file name at every 65535th file). (cmb) + . Fixed bug #70096 (Repeated iptcembed() adds superfluous FF bytes). (cmb) + . Fixed bug #70018 (exec does not strip all whitespace). (Laruence) + . Fixed bug #69983 (get_browser fails with user agent of null). + (Kalle, cmb, Laruence) + . Fixed bug #69976 (Unable to parse "all" urls with colon char). (cmb) + . Fixed bug #69768 (escapeshell*() doesn't cater to !). (cmb) + . Fixed bug #62922 (Truncating entire string should result in string). + (Nikita) + . Fixed bug #69723 (Passing parameters by reference and array_column). + (Laruence) + . Fixed bug #69523 (Cookie name cannot be empty). (Christoph M. Becker) + . Fixed bug #69325 (php_copy_file_ex does not pass the argument). + (imbolk at gmail dot com) + . Fixed bug #69299 (Regression in array_filter's $flag argument in PHP 7). + (Laruence) + . Removed call_user_method() and call_user_method_array() functions. (Kalle) + . Fixed user session handlers (See rfc:session.user.return-value). (Sara) + . Added intdiv() function. (Andrea) + . Improved precision of log() function for base 2 and 10. (Marc Bennewitz) + . Remove string category support in setlocale(). (Nikita) + . Remove set_magic_quotes_runtime() and its alias magic_quotes_runtime(). + (Nikita) + . Fixed bug #65272 (flock() out parameter not set correctly in windows). + (Daniel Lowrey) + . Added preg_replace_callback_array function. (Wei Dai) + . Deprecated salt option to password_hash. (Anthony) + . Fixed bug #69686 (password_verify reports back error on PHP7 will null + string). (Anthony) + . Added Windows support for getrusage(). (Kalle) + . Removed hardcoded limit on number of pipes in proc_open(). (Tony) + +- Streams: + . Fixed bug #70361 (HTTP stream wrapper doesn't close keep-alive connections). + (Niklas Keller) + . Fixed bug #68532 (convert.base64-encode omits padding bytes). + (blaesius at krumedia dot de) + . Removed set_socket_blocking() in favor of its alias stream_set_blocking(). + (Nikita) + +- Tokenizer: + . Fixed bug #69430 (token_get_all has new irrecoverable errors). (Nikita) + +- XMLReader: + . Fixed bug #70309 (XmlReader read generates extra output). (Anatol) + +- XMLRPC + . Fixed bug #70526 (xmlrpc_set_type returns false on success). (Laruence) + +- XSL: + . Fixed bug #70678 (PHP7 returns true when false is expected). (Felipe) + . Fixed bug #70535 (XSLT: free(): invalid pointer). (Laruence) + . Fixed bug #69782 (NULL pointer dereference). (Stas) + . Fixed bug #64776 (The XSLT extension is not thread safe). (Mike) + . Removed xsl.security_prefs ini option. (Nikita) + +- Zlib: + . Added deflate_init(), deflate_add(), inflate_init(), inflate_add() + functions allowing incremental/streaming compression/decompression. + (Daniel Lowrey & Bob Weinand) + +- Zip: + . Fixed bug #70322 (ZipArchive::close() doesn't indicate errors). (cmb) + . Fixed bug #70350 (ZipArchive::extractTo allows for directory traversal when + creating directories). (neal at fb dot com) + . Added ZipArchive::setCompressionName and ZipArchive::setCompressionIndex + methods. (Remi, Cedric Delmas) + . Update bundled libzip to 1.0.1. (Remi, Anatol) + . Fixed bug #67161 (ZipArchive::getStream() returns NULL for certain file). + (Christoph M. Becker) + diff --git a/main/php_variables.c b/main/php_variables.c index 73274d7695015..43b047fde8823 100644 --- a/main/php_variables.c +++ b/main/php_variables.c @@ -239,11 +239,14 @@ typedef struct post_var_data { char *ptr; char *end; uint64_t cnt; + + /* Bytes in ptr that have already been scanned for '&' */ + size_t already_scanned; } post_var_data_t; static zend_bool add_post_var(zval *arr, post_var_data_t *var, zend_bool eof) { - char *ksep, *vsep, *val; + char *start, *ksep, *vsep, *val; size_t klen, vlen; size_t new_vlen; @@ -251,9 +254,11 @@ static zend_bool add_post_var(zval *arr, post_var_data_t *var, zend_bool eof) return 0; } - vsep = memchr(var->ptr, '&', var->end - var->ptr); + start = var->ptr + var->already_scanned; + vsep = memchr(start, '&', var->end - start); if (!vsep) { if (!eof) { + var->already_scanned = var->end - var->ptr; return 0; } else { vsep = var->end; @@ -286,6 +291,7 @@ static zend_bool add_post_var(zval *arr, post_var_data_t *var, zend_bool eof) efree(val); var->ptr = vsep + (vsep != var->end); + var->already_scanned = 0; return 1; } From 25eb1da55304a1bd4bbfe386a49a3ce99ed8581f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:42 +0000 Subject: [PATCH 18/46] commit patch 18839167 --- ext/standard/tests/streams/parseip-001.phpt | 37 + main/streams/xp_socket.c | 29 +- main/streams/xp_socket.c.orig | 916 ++++++++++++++++++++ 3 files changed, 971 insertions(+), 11 deletions(-) create mode 100644 ext/standard/tests/streams/parseip-001.phpt create mode 100644 main/streams/xp_socket.c.orig 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) diff --git a/main/streams/xp_socket.c.orig b/main/streams/xp_socket.c.orig new file mode 100644 index 0000000000000..7a21fbef4458d --- /dev/null +++ b/main/streams/xp_socket.c.orig @@ -0,0 +1,916 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "ext/standard/file.h" +#include "streams/php_streams_int.h" +#include "php_network.h" + +#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) +# undef AF_UNIX +#endif + +#if defined(AF_UNIX) +#include +#endif + +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0 +#endif + +#ifndef MSG_PEEK +# define MSG_PEEK 0 +#endif + +#ifdef PHP_WIN32 +/* send/recv family on windows expects int */ +# define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz)) +#else +# define XP_SOCK_BUF_SIZE(sz) (sz) +#endif + +php_stream_ops php_stream_generic_socket_ops; +PHPAPI php_stream_ops php_stream_socket_ops; +php_stream_ops php_stream_udp_socket_ops; +#ifdef AF_UNIX +php_stream_ops php_stream_unix_socket_ops; +php_stream_ops php_stream_unixdg_socket_ops; +#endif + + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam); + +/* {{{ Generic socket stream operations */ +static size_t php_sockop_write(php_stream *stream, const char *buf, size_t count) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + int didwrite; + struct timeval *ptimeout; + + if (!sock || sock->socket == -1) { + return 0; + } + + if (sock->timeout.tv_sec == -1) + ptimeout = NULL; + else + ptimeout = &sock->timeout; + +retry: + didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0); + + if (didwrite <= 0) { + int err = php_socket_errno(); + char *estr; + + if (sock->is_blocked && (err == EWOULDBLOCK || err == EAGAIN)) { + int retval; + + sock->timeout_event = 0; + + do { + retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); + + if (retval == 0) { + sock->timeout_event = 1; + break; + } + + if (retval > 0) { + /* writable now; retry */ + goto retry; + } + + err = php_socket_errno(); + } while (err == EINTR); + } + estr = php_socket_strerror(err, NULL, 0); + php_error_docref(NULL, E_NOTICE, "send of " ZEND_LONG_FMT " bytes failed with errno=%ld %s", + (zend_long)count, err, estr); + efree(estr); + } + + if (didwrite > 0) { + php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); + } + + if (didwrite < 0) { + didwrite = 0; + } + + return didwrite; +} + +static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock) +{ + int retval; + struct timeval *ptimeout; + + if (!sock || sock->socket == -1) { + return; + } + + sock->timeout_event = 0; + + if (sock->timeout.tv_sec == -1) + ptimeout = NULL; + else + ptimeout = &sock->timeout; + + while(1) { + retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout); + + if (retval == 0) + sock->timeout_event = 1; + + if (retval >= 0) + break; + + if (php_socket_errno() != EINTR) + break; + } +} + +static size_t php_sockop_read(php_stream *stream, char *buf, size_t count) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + ssize_t nr_bytes = 0; + int err; + + if (!sock || sock->socket == -1) { + return 0; + } + + if (sock->is_blocked) { + php_sock_stream_wait_for_data(stream, sock); + if (sock->timeout_event) + return 0; + } + + nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && sock->timeout.tv_sec != -1) ? MSG_DONTWAIT : 0); + err = php_socket_errno(); + + stream->eof = (nr_bytes == 0 || (nr_bytes == -1 && err != EWOULDBLOCK && err != EAGAIN)); + + if (nr_bytes > 0) { + php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); + } + + if (nr_bytes < 0) { + nr_bytes = 0; + } + + return nr_bytes; +} + + +static int php_sockop_close(php_stream *stream, int close_handle) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; +#ifdef PHP_WIN32 + int n; +#endif + + if (!sock) { + return 0; + } + + if (close_handle) { + +#ifdef PHP_WIN32 + if (sock->socket == -1) + sock->socket = SOCK_ERR; +#endif + if (sock->socket != SOCK_ERR) { +#ifdef PHP_WIN32 + /* prevent more data from coming in */ + shutdown(sock->socket, SHUT_RD); + + /* try to make sure that the OS sends all data before we close the connection. + * Essentially, we are waiting for the socket to become writeable, which means + * that all pending data has been sent. + * We use a small timeout which should encourage the OS to send the data, + * but at the same time avoid hanging indefinitely. + * */ + do { + n = php_pollfd_for_ms(sock->socket, POLLOUT, 500); + } while (n == -1 && php_socket_errno() == EINTR); +#endif + closesocket(sock->socket); + sock->socket = SOCK_ERR; + } + + } + + pefree(sock, php_stream_is_persistent(stream)); + + return 0; +} + +static int php_sockop_flush(php_stream *stream) +{ +#if 0 + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + return fsync(sock->socket); +#endif + return 0; +} + +static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb) +{ +#if ZEND_WIN32 + return 0; +#else + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + + return zend_fstat(sock->socket, &ssb->sb); +#endif +} + +static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags, + struct sockaddr *addr, socklen_t addrlen + ) +{ + int ret; + if (addr) { + ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen)); + + return (ret == SOCK_CONN_ERR) ? -1 : ret; + } +#ifdef PHP_WIN32 + return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; +#else + return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; +#endif +} + +static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags, + zend_string **textaddr, + struct sockaddr **addr, socklen_t *addrlen + ) +{ + php_sockaddr_storage sa; + socklen_t sl = sizeof(sa); + int ret; + int want_addr = textaddr || addr; + + if (want_addr) { + ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl); + ret = (ret == SOCK_CONN_ERR) ? -1 : ret; + php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl, + textaddr, addr, addrlen); + } else { + ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags); + ret = (ret == SOCK_CONN_ERR) ? -1 : ret; + } + + return ret; +} + +static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) +{ + int oldmode, flags; + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + if (!sock) { + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } + + switch(option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + { + struct timeval tv; + char buf; + int alive = 1; + + if (value == -1) { + if (sock->timeout.tv_sec == -1) { + tv.tv_sec = FG(default_socket_timeout); + tv.tv_usec = 0; + } else { + tv = sock->timeout; + } + } else { + tv.tv_sec = value; + tv.tv_usec = 0; + } + + if (sock->socket == -1) { + alive = 0; + } else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { +#ifdef PHP_WIN32 + int ret; +#else + ssize_t ret; +#endif + int err; + + ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK); + err = php_socket_errno(); + if (0 == ret || /* the counterpart did properly shutdown*/ + (0 > ret && err != EWOULDBLOCK && err != EAGAIN)) { /* there was an unrecoverable error */ + alive = 0; + } + } + return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + + case PHP_STREAM_OPTION_BLOCKING: + oldmode = sock->is_blocked; + if (SUCCESS == php_set_sock_blocking(sock->socket, value)) { + sock->is_blocked = value; + return oldmode; + } + return PHP_STREAM_OPTION_RETURN_ERR; + + case PHP_STREAM_OPTION_READ_TIMEOUT: + sock->timeout = *(struct timeval*)ptrparam; + sock->timeout_event = 0; + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_OPTION_META_DATA_API: + add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event); + add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked); + add_assoc_bool((zval *)ptrparam, "eof", stream->eof); + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch (xparam->op) { + case STREAM_XPORT_OP_LISTEN: + xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1; + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_GET_NAME: + xparam->outputs.returncode = php_network_get_sock_name(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_GET_PEER_NAME: + xparam->outputs.returncode = php_network_get_peer_name(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_SEND: + flags = 0; + if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { + flags |= MSG_OOB; + } + xparam->outputs.returncode = sock_sendto(sock, + xparam->inputs.buf, xparam->inputs.buflen, + flags, + xparam->inputs.addr, + xparam->inputs.addrlen); + if (xparam->outputs.returncode == -1) { + char *err = php_socket_strerror(php_socket_errno(), NULL, 0); + php_error_docref(NULL, E_WARNING, + "%s\n", err); + efree(err); + } + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_RECV: + flags = 0; + if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { + flags |= MSG_OOB; + } + if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) { + flags |= MSG_PEEK; + } + xparam->outputs.returncode = sock_recvfrom(sock, + xparam->inputs.buf, xparam->inputs.buflen, + flags, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + +#ifdef HAVE_SHUTDOWN +# ifndef SHUT_RD +# define SHUT_RD 0 +# endif +# ifndef SHUT_WR +# define SHUT_WR 1 +# endif +# ifndef SHUT_RDWR +# define SHUT_RDWR 2 +# endif + case STREAM_XPORT_OP_SHUTDOWN: { + static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; + + xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]); + return PHP_STREAM_OPTION_RETURN_OK; + } +#endif + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } +} + +static int php_sockop_cast(php_stream *stream, int castas, void **ret) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + + if (!sock) { + return FAILURE; + } + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (ret) { + *(FILE**)ret = fdopen(sock->socket, stream->mode); + if (*ret) + return SUCCESS; + return FAILURE; + } + return SUCCESS; + case PHP_STREAM_AS_FD_FOR_SELECT: + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (ret) + *(php_socket_t *)ret = sock->socket; + return SUCCESS; + default: + return FAILURE; + } +} +/* }}} */ + +/* These may look identical, but we need them this way so that + * we can determine which type of socket we are dealing with + * by inspecting stream->ops. + * A "useful" side-effect is that the user's scripts can then + * make similar decisions using stream_get_meta_data. + * */ +php_stream_ops php_stream_generic_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "generic_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_sockop_set_option, +}; + + +php_stream_ops php_stream_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "tcp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +php_stream_ops php_stream_udp_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +#ifdef AF_UNIX +php_stream_ops php_stream_unix_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "unix_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +php_stream_ops php_stream_unixdg_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udg_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +#endif + + +/* network socket operations */ + +#ifdef AF_UNIX +static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr) +{ + memset(unix_addr, 0, sizeof(*unix_addr)); + unix_addr->sun_family = AF_UNIX; + + /* we need to be binary safe on systems that support an abstract + * namespace */ + if (xparam->inputs.namelen >= sizeof(unix_addr->sun_path)) { + /* On linux, when the path begins with a NUL byte we are + * referring to an abstract namespace. In theory we should + * allow an extra byte below, since we don't need the NULL. + * BUT, to get into this branch of code, the name is too long, + * so we don't care. */ + xparam->inputs.namelen = sizeof(unix_addr->sun_path) - 1; + php_error_docref(NULL, E_NOTICE, + "socket path exceeded the maximum allowed length of %lu bytes " + "and was truncated", (unsigned long)sizeof(unix_addr->sun_path)); + } + + memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); + + return 1; +} +#endif + +static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) +{ + char *colon; + char *host = NULL; + +#ifdef HAVE_IPV6 + char *p; + + if (*(str) == '[' && str_len > 1) { + /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ + p = memchr(str + 1, ']', str_len - 2); + if (!p || *(p + 1) != ':') { + if (get_err) { + *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); + } + return NULL; + } + *portno = atoi(p + 2); + 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); + } + return NULL; + } + + return host; +} + +static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) +{ + return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text); +} + +static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam) +{ + char *host = NULL; + int portno, err; + long sockopts = STREAM_SOCKOP_NONE; + zval *tmpzval = NULL; + +#ifdef AF_UNIX + if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { + struct sockaddr_un unix_addr; + + sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (sock->socket == SOCK_ERR) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", + stream->ops == &php_stream_unix_socket_ops ? "" : "datagram", + strerror(errno)); + } + return -1; + } + + parse_unix_address(xparam, &unix_addr); + + return bind(sock->socket, (const struct sockaddr *)&unix_addr, + (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen); + } +#endif + + host = parse_ip_address(xparam, &portno); + + if (host == NULL) { + return -1; + } + +#ifdef IPV6_V6ONLY + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL + && Z_TYPE_P(tmpzval) != IS_NULL + ) { + sockopts |= STREAM_SOCKOP_IPV6_V6ONLY; + sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval); + } +#endif + +#ifdef SO_REUSEPORT + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_REUSEPORT; + } +#endif + +#ifdef SO_BROADCAST + if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_BROADCAST; + } +#endif + + sock->socket = php_network_bind_socket_to_local_addr(host, portno, + stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, + sockopts, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err + ); + + if (host) { + efree(host); + } + + return sock->socket == -1 ? -1 : 0; +} + +static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam) +{ + char *host = NULL, *bindto = NULL; + int portno, bindport = 0; + int err = 0; + int ret; + zval *tmpzval = NULL; + long sockopts = STREAM_SOCKOP_NONE; + +#ifdef AF_UNIX + if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { + struct sockaddr_un unix_addr; + + sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (sock->socket == SOCK_ERR) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket"); + } + return -1; + } + + parse_unix_address(xparam, &unix_addr); + + ret = php_network_connect_socket(sock->socket, + (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen, + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err); + + xparam->outputs.error_code = err; + + goto out; + } +#endif + + host = parse_ip_address(xparam, &portno); + + if (host == NULL) { + return -1; + } + + if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) { + if (Z_TYPE_P(tmpzval) != IS_STRING) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); + } + efree(host); + return -1; + } + bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); + } + +#ifdef SO_BROADCAST + if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_BROADCAST; + } +#endif + + /* Note: the test here for php_stream_udp_socket_ops is important, because we + * want the default to be TCP sockets so that the openssl extension can + * re-use this code. */ + + sock->socket = php_network_connect_socket_to_host(host, portno, + stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err, + bindto, + bindport, + sockopts + ); + + ret = sock->socket == -1 ? -1 : 0; + xparam->outputs.error_code = err; + + if (host) { + efree(host); + } + if (bindto) { + efree(bindto); + } + +#ifdef AF_UNIX +out: +#endif + + if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { + /* indicates pending connection */ + return 1; + } + + return ret; +} + +static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam STREAMS_DC) +{ + int clisock; + + xparam->outputs.client = NULL; + + clisock = php_network_accept_incoming(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code + ); + + if (clisock >= 0) { + php_netstream_data_t *clisockdata; + + clisockdata = emalloc(sizeof(*clisockdata)); + + if (clisockdata == NULL) { + close(clisock); + /* technically a fatal error */ + } else { + memcpy(clisockdata, sock, sizeof(*clisockdata)); + clisockdata->socket = clisock; + + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->ctx = stream->ctx; + if (stream->ctx) { + GC_REFCOUNT(stream->ctx)++; + } + } + } + } + + return xparam->outputs.client == NULL ? -1 : 0; +} + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + switch(option) { + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch(xparam->op) { + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_BIND: + xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam); + return PHP_STREAM_OPTION_RETURN_OK; + + + case STREAM_XPORT_OP_ACCEPT: + xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + default: + /* fall through */ + ; + } + } + return php_sockop_set_option(stream, option, value, ptrparam); +} + + +PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen, + const char *resourcename, size_t resourcenamelen, + const char *persistent_id, int options, int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC) +{ + php_stream *stream = NULL; + php_netstream_data_t *sock; + php_stream_ops *ops; + + /* which type of socket ? */ + if (strncmp(proto, "tcp", protolen) == 0) { + ops = &php_stream_socket_ops; + } else if (strncmp(proto, "udp", protolen) == 0) { + ops = &php_stream_udp_socket_ops; + } +#ifdef AF_UNIX + else if (strncmp(proto, "unix", protolen) == 0) { + ops = &php_stream_unix_socket_ops; + } else if (strncmp(proto, "udg", protolen) == 0) { + ops = &php_stream_unixdg_socket_ops; + } +#endif + else { + /* should never happen */ + return NULL; + } + + sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); + memset(sock, 0, sizeof(php_netstream_data_t)); + + sock->is_blocked = 1; + sock->timeout.tv_sec = FG(default_socket_timeout); + sock->timeout.tv_usec = 0; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sock->socket = -1; + + stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sock, persistent_id ? 1 : 0); + return NULL; + } + + if (flags == 0) { + return stream; + } + + return stream; +} + + +/* + * 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 + */ From 83b311b9d61a828024ed243f0b127ed935f4f76f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:44 +0000 Subject: [PATCH 19/46] commit patch 28074067 --- NEWS | 3 +++ NEWS.orig | 4 ++++ ext/intl/msgformat/msgformat_parse.c | 1 + 3 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 0993dd011cc00..1081c40c94202 100644 --- a/NEWS +++ b/NEWS @@ -132,6 +132,9 @@ PHP NEWS . Implemented FR #55651 (Option to ignore the returned FTP PASV address). (abrender at elitehosts dot com) +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + - Mbstring: . Fixed bug #71066 (mb_send_mail: Program terminated with signal SIGSEGV, Segmentation fault). (Laruence) diff --git a/NEWS.orig b/NEWS.orig index ffc3792b29f5e..0993dd011cc00 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -43,6 +43,10 @@ PHP NEWS - mbstring: . Fixed bug #71397 (mb_send_mail segmentation fault). (Andrea, Yasuo) +- Core: + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + - OpenSSL: . Fixed bug #71475 (openssl_seal() uninitialized memory usage). (Stas) diff --git a/ext/intl/msgformat/msgformat_parse.c b/ext/intl/msgformat/msgformat_parse.c index 349633912b73c..8562a76e92acc 100644 --- a/ext/intl/msgformat/msgformat_parse.c +++ b/ext/intl/msgformat/msgformat_parse.c @@ -110,6 +110,7 @@ PHP_FUNCTION( msgfmt_parse_message ) RETURN_FALSE; } + INTL_CHECK_LOCALE_LEN(slocale_len); memset(mfo, 0, sizeof(*mfo)); msgformat_data_init(&mfo->mf_data); From 310da1a4b1e76d4623f8b82cad34f5e0a84003a1 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:46 +0000 Subject: [PATCH 20/46] commit patch 19872485 --- Zend/tests/bug74603.ini | 1 + Zend/tests/bug74603.phpt | 15 ++ Zend/zend_ini_parser.y | 2 +- Zend/zend_ini_parser.y.orig | 403 ++++++++++++++++++++++++++++++++++++ 4 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/bug74603.ini create mode 100644 Zend/tests/bug74603.phpt create mode 100644 Zend/zend_ini_parser.y.orig diff --git a/Zend/tests/bug74603.ini b/Zend/tests/bug74603.ini new file mode 100644 index 0000000000000..8d74a570ec88a --- /dev/null +++ b/Zend/tests/bug74603.ini @@ -0,0 +1 @@ +0=0&~2000000000 diff --git a/Zend/tests/bug74603.phpt b/Zend/tests/bug74603.phpt new file mode 100644 index 0000000000000..b3194ecd48f7d --- /dev/null +++ b/Zend/tests/bug74603.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #74603 (PHP INI Parsing Stack Buffer Overflow Vulnerability) +--SKIPIF-- + +--EXPECT-- +array(1) { + [0]=> + string(1) "0" +} diff --git a/Zend/zend_ini_parser.y b/Zend/zend_ini_parser.y index ae04d2b70c235..ee4d11a89d772 100644 --- a/Zend/zend_ini_parser.y +++ b/Zend/zend_ini_parser.y @@ -53,7 +53,7 @@ static void zend_ini_do_op(char type, zval *result, zval *op1, zval *op2) int i_result; int i_op1, i_op2; int str_len; - char str_result[MAX_LENGTH_OF_LONG]; + char str_result[MAX_LENGTH_OF_LONG+1]; i_op1 = atoi(Z_STRVAL_P(op1)); zend_string_free(Z_STR_P(op1)); diff --git a/Zend/zend_ini_parser.y.orig b/Zend/zend_ini_parser.y.orig new file mode 100644 index 0000000000000..ae04d2b70c235 --- /dev/null +++ b/Zend/zend_ini_parser.y.orig @@ -0,0 +1,403 @@ +%{ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + | Jani Taskinen | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#define DEBUG_CFG_PARSER 0 + +#include "zend.h" +#include "zend_API.h" +#include "zend_ini.h" +#include "zend_constants.h" +#include "zend_ini_scanner.h" +#include "zend_extensions.h" + +#ifdef ZEND_WIN32 +#include "win32/syslog.h" +#endif + +#define YYERROR_VERBOSE +#define YYSTYPE zval + +int ini_parse(void); + +#define ZEND_INI_PARSER_CB (CG(ini_parser_param))->ini_parser_cb +#define ZEND_INI_PARSER_ARG (CG(ini_parser_param))->arg + +#ifdef _MSC_VER +#define YYMALLOC malloc +#define YYFREE free +#endif + +/* {{{ zend_ini_do_op() +*/ +static void zend_ini_do_op(char type, zval *result, zval *op1, zval *op2) +{ + int i_result; + int i_op1, i_op2; + int str_len; + char str_result[MAX_LENGTH_OF_LONG]; + + i_op1 = atoi(Z_STRVAL_P(op1)); + zend_string_free(Z_STR_P(op1)); + if (op2) { + i_op2 = atoi(Z_STRVAL_P(op2)); + zend_string_free(Z_STR_P(op2)); + } else { + i_op2 = 0; + } + + switch (type) { + case '|': + i_result = i_op1 | i_op2; + break; + case '&': + i_result = i_op1 & i_op2; + break; + case '^': + i_result = i_op1 ^ i_op2; + break; + case '~': + i_result = ~i_op1; + break; + case '!': + i_result = !i_op1; + break; + default: + i_result = 0; + break; + } + + str_len = zend_sprintf(str_result, "%d", i_result); + ZVAL_PSTRINGL(result, str_result, str_len); +} +/* }}} */ + +/* {{{ zend_ini_init_string() +*/ +static void zend_ini_init_string(zval *result) +{ + ZVAL_EMPTY_PSTRING(result); +} +/* }}} */ + +/* {{{ zend_ini_add_string() +*/ +static void zend_ini_add_string(zval *result, zval *op1, zval *op2) +{ + int length, op1_len; + + if (Z_TYPE_P(op1) != IS_STRING) { + zend_string *str = zval_get_string(op1); + /* ZEND_ASSERT(!Z_REFCOUNTED_P(op1)); */ + ZVAL_PSTRINGL(op1, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_string_release(str); + } + op1_len = (int)Z_STRLEN_P(op1); + + if (Z_TYPE_P(op2) != IS_STRING) { + convert_to_string(op2); + } + length = op1_len + (int)Z_STRLEN_P(op2); + + ZVAL_NEW_STR(result, zend_string_extend(Z_STR_P(op1), length, 1)); + memcpy(Z_STRVAL_P(result) + op1_len, Z_STRVAL_P(op2), Z_STRLEN_P(op2) + 1); +} +/* }}} */ + +/* {{{ zend_ini_get_constant() +*/ +static void zend_ini_get_constant(zval *result, zval *name) +{ + zval *c, tmp; + + /* If name contains ':' it is not a constant. Bug #26893. */ + if (!memchr(Z_STRVAL_P(name), ':', Z_STRLEN_P(name)) + && (c = zend_get_constant(Z_STR_P(name))) != 0) { + if (Z_TYPE_P(c) != IS_STRING) { + ZVAL_COPY_VALUE(&tmp, c); + if (Z_OPT_CONSTANT(tmp)) { + zval_update_constant_ex(&tmp, 1, NULL); + } + zval_opt_copy_ctor(&tmp); + convert_to_string(&tmp); + c = &tmp; + } + ZVAL_PSTRINGL(result, Z_STRVAL_P(c), Z_STRLEN_P(c)); + if (c == &tmp) { + zend_string_release(Z_STR(tmp)); + } + zend_string_free(Z_STR_P(name)); + } else { + *result = *name; + } +} +/* }}} */ + +/* {{{ zend_ini_get_var() +*/ +static void zend_ini_get_var(zval *result, zval *name) +{ + zval *curval; + char *envvar; + + /* Fetch configuration option value */ + if ((curval = zend_get_configuration_directive(Z_STR_P(name))) != NULL) { + ZVAL_PSTRINGL(result, Z_STRVAL_P(curval), Z_STRLEN_P(curval)); + /* ..or if not found, try ENV */ + } else if ((envvar = zend_getenv(Z_STRVAL_P(name), Z_STRLEN_P(name))) != NULL || + (envvar = getenv(Z_STRVAL_P(name))) != NULL) { + ZVAL_PSTRING(result, envvar); + } else { + zend_ini_init_string(result); + } +} +/* }}} */ + +/* {{{ ini_error() +*/ +static ZEND_COLD void ini_error(const char *msg) +{ + char *error_buf; + int error_buf_len; + char *currently_parsed_filename; + + currently_parsed_filename = zend_ini_scanner_get_filename(); + if (currently_parsed_filename) { + error_buf_len = 128 + (int)strlen(msg) + (int)strlen(currently_parsed_filename); /* should be more than enough */ + error_buf = (char *) emalloc(error_buf_len); + + sprintf(error_buf, "%s in %s on line %d\n", msg, currently_parsed_filename, zend_ini_scanner_get_lineno()); + } else { + error_buf = estrdup("Invalid configuration directive\n"); + } + + if (CG(ini_parser_unbuffered_errors)) { +#ifdef ZEND_WIN32 + syslog(LOG_ALERT, "PHP: %s (%s)", error_buf, GetCommandLine()); +#endif + fprintf(stderr, "PHP: %s", error_buf); + } else { + zend_error(E_WARNING, "%s", error_buf); + } + efree(error_buf); +} +/* }}} */ + +/* {{{ zend_parse_ini_file() +*/ +ZEND_API int zend_parse_ini_file(zend_file_handle *fh, zend_bool unbuffered_errors, int scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg) +{ + int retval; + zend_ini_parser_param ini_parser_param; + + ini_parser_param.ini_parser_cb = ini_parser_cb; + ini_parser_param.arg = arg; + CG(ini_parser_param) = &ini_parser_param; + + if (zend_ini_open_file_for_scanning(fh, scanner_mode) == FAILURE) { + return FAILURE; + } + + CG(ini_parser_unbuffered_errors) = unbuffered_errors; + retval = ini_parse(); + zend_file_handle_dtor(fh); + + shutdown_ini_scanner(); + + if (retval == 0) { + return SUCCESS; + } else { + return FAILURE; + } +} +/* }}} */ + +/* {{{ zend_parse_ini_string() +*/ +ZEND_API int zend_parse_ini_string(char *str, zend_bool unbuffered_errors, int scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg) +{ + int retval; + zend_ini_parser_param ini_parser_param; + + ini_parser_param.ini_parser_cb = ini_parser_cb; + ini_parser_param.arg = arg; + CG(ini_parser_param) = &ini_parser_param; + + if (zend_ini_prepare_string_for_scanning(str, scanner_mode) == FAILURE) { + return FAILURE; + } + + CG(ini_parser_unbuffered_errors) = unbuffered_errors; + retval = ini_parse(); + + shutdown_ini_scanner(); + + if (retval == 0) { + return SUCCESS; + } else { + return FAILURE; + } +} +/* }}} */ + +%} + +%expect 0 +%pure_parser + +%token TC_SECTION +%token TC_RAW +%token TC_CONSTANT +%token TC_NUMBER +%token TC_STRING +%token TC_WHITESPACE +%token TC_LABEL +%token TC_OFFSET +%token TC_DOLLAR_CURLY +%token TC_VARNAME +%token TC_QUOTED_STRING +%token BOOL_TRUE +%token BOOL_FALSE +%token NULL_NULL +%token END_OF_LINE +%token '=' ':' ',' '.' '"' '\'' '^' '+' '-' '/' '*' '%' '$' '~' '<' '>' '?' '@' '{' '}' +%left '|' '&' '^' +%right '~' '!' + +%% + +statement_list: + statement_list statement + | /* empty */ +; + +statement: + TC_SECTION section_string_or_value ']' { +#if DEBUG_CFG_PARSER + printf("SECTION: [%s]\n", Z_STRVAL($2)); +#endif + ZEND_INI_PARSER_CB(&$2, NULL, NULL, ZEND_INI_PARSER_SECTION, ZEND_INI_PARSER_ARG); + zend_string_release(Z_STR($2)); + } + | TC_LABEL '=' string_or_value { +#if DEBUG_CFG_PARSER + printf("NORMAL: '%s' = '%s'\n", Z_STRVAL($1), Z_STRVAL($3)); +#endif + ZEND_INI_PARSER_CB(&$1, &$3, NULL, ZEND_INI_PARSER_ENTRY, ZEND_INI_PARSER_ARG); + zend_string_release(Z_STR($1)); + zval_ptr_dtor(&$3); + } + | TC_OFFSET option_offset ']' '=' string_or_value { +#if DEBUG_CFG_PARSER + printf("OFFSET: '%s'[%s] = '%s'\n", Z_STRVAL($1), Z_STRVAL($2), Z_STRVAL($5)); +#endif + ZEND_INI_PARSER_CB(&$1, &$5, &$2, ZEND_INI_PARSER_POP_ENTRY, ZEND_INI_PARSER_ARG); + zend_string_release(Z_STR($1)); + if (Z_TYPE($2) == IS_STRING) { + zend_string_release(Z_STR($2)); + } else { + zval_dtor(&$2); + } + zval_ptr_dtor(&$5); + } + | TC_LABEL { ZEND_INI_PARSER_CB(&$1, NULL, NULL, ZEND_INI_PARSER_ENTRY, ZEND_INI_PARSER_ARG); zend_string_release(Z_STR($1)); } + | END_OF_LINE +; + +section_string_or_value: + var_string_list_section { $$ = $1; } + | /* empty */ { zend_ini_init_string(&$$); } +; + +string_or_value: + expr { $$ = $1; } + | BOOL_TRUE { $$ = $1; } + | BOOL_FALSE { $$ = $1; } + | NULL_NULL { $$ = $1; } + | END_OF_LINE { zend_ini_init_string(&$$); } +; + +option_offset: + var_string_list { $$ = $1; } + | /* empty */ { zend_ini_init_string(&$$); } +; + +encapsed_list: + encapsed_list cfg_var_ref { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | encapsed_list TC_QUOTED_STRING { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | /* empty */ { zend_ini_init_string(&$$); } +; + +var_string_list_section: + cfg_var_ref { $$ = $1; } + | constant_literal { $$ = $1; } + | '"' encapsed_list '"' { $$ = $2; } + | var_string_list_section cfg_var_ref { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | var_string_list_section constant_literal { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | var_string_list_section '"' encapsed_list '"' { zend_ini_add_string(&$$, &$1, &$3); zend_string_free(Z_STR($3)); } +; + +var_string_list: + cfg_var_ref { $$ = $1; } + | constant_string { $$ = $1; } + | '"' encapsed_list '"' { $$ = $2; } + | var_string_list cfg_var_ref { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | var_string_list constant_string { zend_ini_add_string(&$$, &$1, &$2); zend_string_free(Z_STR($2)); } + | var_string_list '"' encapsed_list '"' { zend_ini_add_string(&$$, &$1, &$3); zend_string_free(Z_STR($3)); } +; + +expr: + var_string_list { $$ = $1; } + | expr '|' expr { zend_ini_do_op('|', &$$, &$1, &$3); } + | expr '&' expr { zend_ini_do_op('&', &$$, &$1, &$3); } + | expr '^' expr { zend_ini_do_op('^', &$$, &$1, &$3); } + | '~' expr { zend_ini_do_op('~', &$$, &$2, NULL); } + | '!' expr { zend_ini_do_op('!', &$$, &$2, NULL); } + | '(' expr ')' { $$ = $2; } +; + +cfg_var_ref: + TC_DOLLAR_CURLY TC_VARNAME '}' { zend_ini_get_var(&$$, &$2); zend_string_free(Z_STR($2)); } +; + +constant_literal: + TC_CONSTANT { $$ = $1; } + | TC_RAW { $$ = $1; /*printf("TC_RAW: '%s'\n", Z_STRVAL($1));*/ } + | TC_NUMBER { $$ = $1; /*printf("TC_NUMBER: '%s'\n", Z_STRVAL($1));*/ } + | TC_STRING { $$ = $1; /*printf("TC_STRING: '%s'\n", Z_STRVAL($1));*/ } + | TC_WHITESPACE { $$ = $1; /*printf("TC_WHITESPACE: '%s'\n", Z_STRVAL($1));*/ } +; + +constant_string: + TC_CONSTANT { zend_ini_get_constant(&$$, &$1); } + | TC_RAW { $$ = $1; /*printf("TC_RAW: '%s'\n", Z_STRVAL($1));*/ } + | TC_NUMBER { $$ = $1; /*printf("TC_NUMBER: '%s'\n", Z_STRVAL($1));*/ } + | TC_STRING { $$ = $1; /*printf("TC_STRING: '%s'\n", Z_STRVAL($1));*/ } + | TC_WHITESPACE { $$ = $1; /*printf("TC_WHITESPACE: '%s'\n", Z_STRVAL($1));*/ } +; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ From c7188e729ffbb0542e5b13217fc2380b047e2bda Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:50 +0000 Subject: [PATCH 21/46] commit patch 19071073 --- ext/standard/http_fopen_wrapper.c | 4 +- ext/standard/http_fopen_wrapper.c.orig | 991 +++++++++++++++++++++++++ ext/standard/tests/http/bug75981.phpt | 32 + 3 files changed, 1025 insertions(+), 2 deletions(-) create mode 100644 ext/standard/http_fopen_wrapper.c.orig create mode 100644 ext/standard/tests/http/bug75981.phpt diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 141ba0fc9c5e6..b59b2d60c9252 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -720,9 +720,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, tmp_line, response_code); } } - if (tmp_line[tmp_line_len - 1] == '\n') { + if (tmp_line_len >= 1 && tmp_line[tmp_line_len - 1] == '\n') { --tmp_line_len; - if (tmp_line[tmp_line_len - 1] == '\r') { + if (tmp_line_len >= 1 &&tmp_line[tmp_line_len - 1] == '\r') { --tmp_line_len; } } diff --git a/ext/standard/http_fopen_wrapper.c.orig b/ext/standard/http_fopen_wrapper.c.orig new file mode 100644 index 0000000000000..141ba0fc9c5e6 --- /dev/null +++ b/ext/standard/http_fopen_wrapper.c.orig @@ -0,0 +1,991 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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 | + | Jim Winstead | + | Hartmut Holzgraefe | + | Wez Furlong | + | Sara Golemon | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_streams.h" +#include "php_network.h" +#include "php_ini.h" +#include "ext/standard/basic_functions.h" +#include "zend_smart_str.h" + +#include +#include +#include +#include +#include +#include + +#ifdef PHP_WIN32 +#define O_RDONLY _O_RDONLY +#include "win32/param.h" +#else +#include +#endif + +#include "php_standard.h" + +#include +#if HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef PHP_WIN32 +#include +#elif defined(NETWARE) && defined(USE_WINSOCK) +#include +#else +#include +#include +#if HAVE_ARPA_INET_H +#include +#endif +#endif + +#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) +#undef AF_UNIX +#endif + +#if defined(AF_UNIX) +#include +#endif + +#include "php_fopen_wrappers.h" + +#define HTTP_HEADER_BLOCK_SIZE 1024 +#define PHP_URL_REDIRECT_MAX 20 +#define HTTP_HEADER_USER_AGENT 1 +#define HTTP_HEADER_HOST 2 +#define HTTP_HEADER_AUTH 4 +#define HTTP_HEADER_FROM 8 +#define HTTP_HEADER_CONTENT_LENGTH 16 +#define HTTP_HEADER_TYPE 32 +#define HTTP_HEADER_CONNECTION 64 + +#define HTTP_WRAPPER_HEADER_INIT 1 +#define HTTP_WRAPPER_REDIRECTED 2 + +static inline void strip_header(char *header_bag, char *lc_header_bag, + const char *lc_header_name) +{ + char *lc_header_start = strstr(lc_header_bag, lc_header_name); + char *header_start = header_bag + (lc_header_start - lc_header_bag); + + if (lc_header_start + && (lc_header_start == lc_header_bag || *(lc_header_start-1) == '\n') + ) { + char *lc_eol = strchr(lc_header_start, '\n'); + char *eol = header_start + (lc_eol - lc_header_start); + + if (lc_eol) { + size_t eollen = strlen(lc_eol); + + memmove(lc_header_start, lc_eol+1, eollen); + memmove(header_start, eol+1, eollen); + } else { + *lc_header_start = '\0'; + *header_start = '\0'; + } + } +} + +php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, + const char *path, const char *mode, int options, zend_string **opened_path, + php_stream_context *context, int redirect_max, int flags STREAMS_DC) /* {{{ */ +{ + php_stream *stream = NULL; + php_url *resource = NULL; + int use_ssl; + int use_proxy = 0; + char *scratch = NULL; + zend_string *tmp = NULL; + char *ua_str = NULL; + zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; + size_t scratch_len = 0; + int body = 0; + char location[HTTP_HEADER_BLOCK_SIZE]; + zval response_header; + int reqok = 0; + char *http_header_line = NULL; + char tmp_line[128]; + size_t chunk_size = 0, file_size = 0; + int eol_detect = 0; + char *transport_string; + zend_string *errstr = NULL; + size_t transport_len; + int have_header = 0; + zend_bool request_fulluri = 0, ignore_errors = 0; + char *protocol_version = NULL; + int protocol_version_len = 3; /* Default: "1.0" */ + struct timeval timeout; + char *user_headers = NULL; + int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); + int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); + zend_bool follow_location = 1; + php_stream_filter *transfer_encoding = NULL; + int response_code; + zend_array *symbol_table; + + ZVAL_UNDEF(&response_header); + tmp_line[0] = '\0'; + + if (redirect_max < 1) { + php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting"); + return NULL; + } + + resource = php_url_parse(path); + if (resource == NULL) { + return NULL; + } + + if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) { + if (!context || + (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) == NULL || + Z_TYPE_P(tmpzval) != IS_STRING || + Z_STRLEN_P(tmpzval) <= 0) { + php_url_free(resource); + return php_stream_open_wrapper_ex(path, mode, REPORT_ERRORS, NULL, context); + } + /* Called from a non-http wrapper with http proxying requested (i.e. ftp) */ + request_fulluri = 1; + use_ssl = 0; + use_proxy = 1; + + transport_len = Z_STRLEN_P(tmpzval); + transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + /* Normal http request (possibly with proxy) */ + + if (strpbrk(mode, "awx+")) { + php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections"); + php_url_free(resource); + return NULL; + } + + use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's'; + /* choose default ports */ + if (use_ssl && resource->port == 0) + resource->port = 443; + else if (resource->port == 0) + resource->port = 80; + + if (context && + (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && + Z_STRLEN_P(tmpzval) > 0) { + use_proxy = 1; + transport_len = Z_STRLEN_P(tmpzval); + transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port); + } + } + + if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) { + double d = zval_get_double(tmpzval); +#ifndef PHP_WIN32 + timeout.tv_sec = (time_t) d; + timeout.tv_usec = (size_t) ((d - timeout.tv_sec) * 1000000); +#else + timeout.tv_sec = (long) d; + timeout.tv_usec = (long) ((d - timeout.tv_sec) * 1000000); +#endif + } else { +#ifndef PHP_WIN32 + timeout.tv_sec = FG(default_socket_timeout); +#else + timeout.tv_sec = (long)FG(default_socket_timeout); +#endif + timeout.tv_usec = 0; + } + + stream = php_stream_xport_create(transport_string, transport_len, options, + STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, + NULL, &timeout, context, &errstr, NULL); + + if (stream) { + php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout); + } + + if (errstr) { + php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr)); + zend_string_release(errstr); + errstr = NULL; + } + + efree(transport_string); + + if (stream && use_proxy && use_ssl) { + smart_str header = {0}; + + /* Set peer_name or name verification will try to use the proxy server name */ + if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) { + ZVAL_STRING(&ssl_proxy_peer_name, resource->host); + php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name); + zval_ptr_dtor(&ssl_proxy_peer_name); + } + + smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1); + smart_str_appends(&header, resource->host); + smart_str_appendc(&header, ':'); + smart_str_append_unsigned(&header, resource->port); + smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1); + + /* check if we have Proxy-Authorization header */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) { + char *s, *p; + + if (Z_TYPE_P(tmpzval) == IS_ARRAY) { + zval *tmpheader = NULL; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) { + if (Z_TYPE_P(tmpheader) == IS_STRING) { + s = Z_STRVAL_P(tmpheader); + do { + while (*s == ' ' || *s == '\t') s++; + p = s; + while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++; + if (*p == ':') { + p++; + if (p - s == sizeof("Proxy-Authorization:") - 1 && + zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1, + "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + smart_str_appendl(&header, s, p - s); + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + goto finish; + } else { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + } + } + s = p; + while (*s == '\r' || *s == '\n') s++; + } while (*s != 0); + } + } ZEND_HASH_FOREACH_END(); + } else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) { + s = Z_STRVAL_P(tmpzval); + do { + while (*s == ' ' || *s == '\t') s++; + p = s; + while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++; + if (*p == ':') { + p++; + if (p - s == sizeof("Proxy-Authorization:") - 1 && + zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1, + "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + smart_str_appendl(&header, s, p - s); + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + goto finish; + } else { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + } + } + s = p; + while (*s == '\r' || *s == '\n') s++; + } while (*s != 0); + } + } +finish: + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + + if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) { + php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_close(stream); + stream = NULL; + } + smart_str_free(&header); + + if (stream) { + char header_line[HTTP_HEADER_BLOCK_SIZE]; + + /* get response header */ + while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) { + if (header_line[0] == '\n' || + header_line[0] == '\r' || + header_line[0] == '\0') { + break; + } + } + } + + /* enable SSL transport layer */ + if (stream) { + if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || + php_stream_xport_crypto_enable(stream, 1) < 0) { + php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_close(stream); + stream = NULL; + } + } + } + + if (stream == NULL) + goto out; + + /* avoid buffering issues while reading header */ + if (options & STREAM_WILL_CAST) + chunk_size = php_stream_set_chunk_size(stream, 1); + + /* avoid problems with auto-detecting when reading the headers -> the headers + * are always in canonical \r\n format */ + eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC); + stream->flags &= ~(PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC); + + php_stream_context_set(stream, context); + + php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); + + if (header_init && context && (tmpzval = php_stream_context_get_option(context, "http", "max_redirects")) != NULL) { + redirect_max = (int)zval_get_long(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) { + if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { + /* As per the RFC, automatically redirected requests MUST NOT use other methods than + * GET and HEAD unless it can be confirmed by the user */ + if (!redirected + || (Z_STRLEN_P(tmpzval) == 3 && memcmp("GET", Z_STRVAL_P(tmpzval), 3) == 0) + || (Z_STRLEN_P(tmpzval) == 4 && memcmp("HEAD",Z_STRVAL_P(tmpzval), 4) == 0) + ) { + scratch_len = strlen(path) + 29 + Z_STRLEN_P(tmpzval); + scratch = emalloc(scratch_len); + strlcpy(scratch, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) + 1); + strncat(scratch, " ", 1); + } + } + } + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "protocol_version")) != NULL) { + protocol_version_len = (int)spprintf(&protocol_version, 0, "%.1F", zval_get_double(tmpzval)); + } + + if (!scratch) { + scratch_len = strlen(path) + 29 + protocol_version_len; + scratch = emalloc(scratch_len); + strncpy(scratch, "GET ", scratch_len); + } + + /* Should we send the entire path in the request line, default to no. */ + if (!request_fulluri && context && + (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { + request_fulluri = zend_is_true(tmpzval); + } + + if (request_fulluri) { + /* Ask for everything */ + strcat(scratch, path); + } else { + /* Send the traditional /path/to/file?query_string */ + + /* file */ + if (resource->path && *resource->path) { + strlcat(scratch, resource->path, scratch_len); + } else { + strlcat(scratch, "/", scratch_len); + } + + /* query string */ + if (resource->query) { + strlcat(scratch, "?", scratch_len); + strlcat(scratch, resource->query, scratch_len); + } + } + + /* protocol version we are speaking */ + if (protocol_version) { + strlcat(scratch, " HTTP/", scratch_len); + strlcat(scratch, protocol_version, scratch_len); + strlcat(scratch, "\r\n", scratch_len); + } else { + strlcat(scratch, " HTTP/1.0\r\n", scratch_len); + } + + /* send it */ + php_stream_write(stream, scratch, strlen(scratch)); + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) { + tmp = NULL; + + if (Z_TYPE_P(tmpzval) == IS_ARRAY) { + zval *tmpheader = NULL; + smart_str tmpstr = {0}; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) { + if (Z_TYPE_P(tmpheader) == IS_STRING) { + smart_str_append(&tmpstr, Z_STR_P(tmpheader)); + smart_str_appendl(&tmpstr, "\r\n", sizeof("\r\n") - 1); + } + } ZEND_HASH_FOREACH_END(); + smart_str_0(&tmpstr); + /* Remove newlines and spaces from start and end. there's at least one extra \r\n at the end that needs to go. */ + if (tmpstr.s) { + tmp = php_trim(tmpstr.s, NULL, 0, 3); + smart_str_free(&tmpstr); + } + } else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) { + /* Remove newlines and spaces from start and end php_trim will estrndup() */ + tmp = php_trim(Z_STR_P(tmpzval), NULL, 0, 3); + } + if (tmp && ZSTR_LEN(tmp)) { + char *s; + char *t; + + user_headers = estrndup(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); + + if (ZSTR_IS_INTERNED(tmp)) { + tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0); + } else if (GC_REFCOUNT(tmp) > 1) { + GC_REFCOUNT(tmp)--; + tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0); + } + + /* Make lowercase for easy comparison against 'standard' headers */ + php_strtolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); + t = ZSTR_VAL(tmp); + + if (!header_init) { + /* strip POST headers on redirect */ + strip_header(user_headers, t, "content-length:"); + strip_header(user_headers, t, "content-type:"); + } + + if ((s = strstr(t, "user-agent:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_USER_AGENT; + } + if ((s = strstr(t, "host:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_HOST; + } + if ((s = strstr(t, "from:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_FROM; + } + if ((s = strstr(t, "authorization:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_AUTH; + } + if ((s = strstr(t, "content-length:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_CONTENT_LENGTH; + } + if ((s = strstr(t, "content-type:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_TYPE; + } + if ((s = strstr(t, "connection:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_CONNECTION; + } + /* remove Proxy-Authorization header */ + if (use_proxy && use_ssl && (s = strstr(t, "proxy-authorization:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + char *p = s + sizeof("proxy-authorization:") - 1; + + while (s > t && (*(s-1) == ' ' || *(s-1) == '\t')) s--; + while (*p != 0 && *p != '\r' && *p != '\n') p++; + while (*p == '\r' || *p == '\n') p++; + if (*p == 0) { + if (s == t) { + efree(user_headers); + user_headers = NULL; + } else { + while (s > t && (*(s-1) == '\r' || *(s-1) == '\n')) s--; + user_headers[s - t] = 0; + } + } else { + memmove(user_headers + (s - t), user_headers + (p - t), strlen(p) + 1); + } + } + + } + if (tmp) { + zend_string_release(tmp); + } + } + + /* auth header if it was specified */ + if (((have_header & HTTP_HEADER_AUTH) == 0) && resource->user) { + zend_string *stmp; + /* decode the strings first */ + php_url_decode(resource->user, strlen(resource->user)); + + /* scratch is large enough, since it was made large enough for the whole URL */ + strcpy(scratch, resource->user); + strcat(scratch, ":"); + + /* Note: password is optional! */ + if (resource->pass) { + php_url_decode(resource->pass, strlen(resource->pass)); + strcat(scratch, resource->pass); + } + + stmp = php_base64_encode((unsigned char*)scratch, strlen(scratch)); + + if (snprintf(scratch, scratch_len, "Authorization: Basic %s\r\n", ZSTR_VAL(stmp)) > 0) { + php_stream_write(stream, scratch, strlen(scratch)); + php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0); + } + + zend_string_free(stmp); + } + + /* if the user has configured who they are, send a From: line */ + if (((have_header & HTTP_HEADER_FROM) == 0) && FG(from_address)) { + if (snprintf(scratch, scratch_len, "From: %s\r\n", FG(from_address)) > 0) + php_stream_write(stream, scratch, strlen(scratch)); + } + + /* Send Host: header so name-based virtual hosts work */ + if ((have_header & HTTP_HEADER_HOST) == 0) { + if ((use_ssl && resource->port != 443 && resource->port != 0) || + (!use_ssl && resource->port != 80 && resource->port != 0)) { + if (snprintf(scratch, scratch_len, "Host: %s:%i\r\n", resource->host, resource->port) > 0) + php_stream_write(stream, scratch, strlen(scratch)); + } else { + if (snprintf(scratch, scratch_len, "Host: %s\r\n", resource->host) > 0) { + php_stream_write(stream, scratch, strlen(scratch)); + } + } + } + + /* Send a Connection: close header to avoid hanging when the server + * interprets the RFC literally and establishes a keep-alive connection, + * unless the user specifically requests something else by specifying a + * Connection header in the context options. Send that header even for + * HTTP/1.0 to avoid issues when the server respond with a HTTP/1.1 + * keep-alive response, which is the preferred response type. */ + if ((have_header & HTTP_HEADER_CONNECTION) == 0) { + php_stream_write_string(stream, "Connection: close\r\n"); + } + + if (context && + (ua_zval = php_stream_context_get_option(context, "http", "user_agent")) != NULL && + Z_TYPE_P(ua_zval) == IS_STRING) { + ua_str = Z_STRVAL_P(ua_zval); + } else if (FG(user_agent)) { + ua_str = FG(user_agent); + } + + if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) { +#define _UA_HEADER "User-Agent: %s\r\n" + char *ua; + size_t ua_len; + + ua_len = sizeof(_UA_HEADER) + strlen(ua_str); + + /* ensure the header is only sent if user_agent is not blank */ + if (ua_len > sizeof(_UA_HEADER)) { + ua = emalloc(ua_len + 1); + if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) { + ua[ua_len] = 0; + php_stream_write(stream, ua, ua_len); + } else { + php_error_docref(NULL, E_WARNING, "Cannot construct User-agent header"); + } + + if (ua) { + efree(ua); + } + } + } + + if (user_headers) { + /* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST + * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first. + */ + if ( + header_init && + context && + !(have_header & HTTP_HEADER_CONTENT_LENGTH) && + (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0 + ) { + scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_P(tmpzval)); + php_stream_write(stream, scratch, scratch_len); + have_header |= HTTP_HEADER_CONTENT_LENGTH; + } + + php_stream_write(stream, user_headers, strlen(user_headers)); + php_stream_write(stream, "\r\n", sizeof("\r\n")-1); + efree(user_headers); + } + + /* Request content, such as for POST requests */ + if (header_init && context && + (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { + if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) { + scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_P(tmpzval)); + php_stream_write(stream, scratch, scratch_len); + } + if (!(have_header & HTTP_HEADER_TYPE)) { + php_stream_write(stream, "Content-Type: application/x-www-form-urlencoded\r\n", + sizeof("Content-Type: application/x-www-form-urlencoded\r\n") - 1); + php_error_docref(NULL, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded"); + } + php_stream_write(stream, "\r\n", sizeof("\r\n")-1); + php_stream_write(stream, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + php_stream_write(stream, "\r\n", sizeof("\r\n")-1); + } + + location[0] = '\0'; + + symbol_table = zend_rebuild_symbol_table(); + + if (header_init) { + zval ztmp; + array_init(&ztmp); + zend_set_local_var_str("http_response_header", sizeof("http_response_header")-1, &ztmp, 0); + } + + { + zval *response_header_ptr = zend_hash_str_find_ind(symbol_table, "http_response_header", sizeof("http_response_header")-1); + if (!response_header_ptr || Z_TYPE_P(response_header_ptr) != IS_ARRAY) { + ZVAL_UNDEF(&response_header); + goto out; + } else { + ZVAL_COPY(&response_header, response_header_ptr); + } + } + + if (!php_stream_eof(stream)) { + size_t tmp_line_len; + /* get response header */ + + if (php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL) { + zval http_response; + + if (tmp_line_len > 9) { + response_code = atoi(tmp_line + 9); + } else { + response_code = 0; + } + if (context && NULL != (tmpzval = php_stream_context_get_option(context, "http", "ignore_errors"))) { + ignore_errors = zend_is_true(tmpzval); + } + /* when we request only the header, don't fail even on error codes */ + if ((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) { + reqok = 1; + } + /* all status codes in the 2xx range are defined by the specification as successful; + * all status codes in the 3xx range are for redirection, and so also should never + * fail */ + if (response_code >= 200 && response_code < 400) { + reqok = 1; + } else { + switch(response_code) { + case 403: + php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, + tmp_line, response_code); + break; + default: + /* safety net in the event tmp_line == NULL */ + if (!tmp_line_len) { + tmp_line[0] = '\0'; + } + php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, + tmp_line, response_code); + } + } + if (tmp_line[tmp_line_len - 1] == '\n') { + --tmp_line_len; + if (tmp_line[tmp_line_len - 1] == '\r') { + --tmp_line_len; + } + } + ZVAL_STRINGL(&http_response, tmp_line, tmp_line_len); + zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_response); + } + } else { + php_stream_wrapper_log_error(wrapper, options, "HTTP request failed, unexpected end of socket!"); + goto out; + } + + /* read past HTTP headers */ + + http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE); + + while (!body && !php_stream_eof(stream)) { + size_t http_header_line_length; + if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) && *http_header_line != '\n' && *http_header_line != '\r') { + char *e = http_header_line + http_header_line_length - 1; + if (*e != '\n') { + do { /* partial header */ + if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) == NULL) { + php_stream_wrapper_log_error(wrapper, options, "Failed to read HTTP headers"); + goto out; + } + e = http_header_line + http_header_line_length - 1; + } while (*e != '\n'); + continue; + } + while (*e == '\n' || *e == '\r') { + e--; + } + http_header_line_length = e - http_header_line + 1; + http_header_line[http_header_line_length] = '\0'; + + if (!strncasecmp(http_header_line, "Location: ", 10)) { + if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { + follow_location = zval_is_true(tmpzval); + } else if (!((response_code >= 300 && response_code < 304) || 307 == response_code || 308 == response_code)) { + /* we shouldn't redirect automatically + if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307) + see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 + RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ + follow_location = 0; + } + strlcpy(location, http_header_line + 10, sizeof(location)); + } else if (!strncasecmp(http_header_line, "Content-Type: ", 14)) { + php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_line + 14, 0); + } else if (!strncasecmp(http_header_line, "Content-Length: ", 16)) { + file_size = atoi(http_header_line + 16); + php_stream_notify_file_size(context, file_size, http_header_line, 0); + } else if (!strncasecmp(http_header_line, "Transfer-Encoding: chunked", sizeof("Transfer-Encoding: chunked"))) { + + /* create filter to decode response body */ + if (!(options & STREAM_ONLY_GET_HEADERS)) { + zend_long decode = 1; + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { + decode = zend_is_true(tmpzval); + } + if (decode) { + transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream)); + if (transfer_encoding) { + /* don't store transfer-encodeing header */ + continue; + } + } + } + } + + if (http_header_line[0] == '\0') { + body = 1; + } else { + zval http_header; + + ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length); + + zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_header); + } + } else { + break; + } + } + + if (!reqok || (location[0] != '\0' && follow_location)) { + if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { + goto out; + } + + if (location[0] != '\0') + php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0); + + php_stream_close(stream); + stream = NULL; + + if (location[0] != '\0') { + + char new_path[HTTP_HEADER_BLOCK_SIZE]; + char loc_path[HTTP_HEADER_BLOCK_SIZE]; + + *new_path='\0'; + if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) && + strncasecmp(location, "https://", sizeof("https://")-1) && + strncasecmp(location, "ftp://", sizeof("ftp://")-1) && + strncasecmp(location, "ftps://", sizeof("ftps://")-1))) + { + if (*location != '/') { + if (*(location+1) != '\0' && resource->path) { + char *s = strrchr(resource->path, '/'); + if (!s) { + s = resource->path; + if (!s[0]) { + efree(s); + s = resource->path = estrdup("/"); + } else { + *s = '/'; + } + } + s[1] = '\0'; + if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') { + snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location); + } else { + snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", resource->path, location); + } + } else { + snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location); + } + } else { + strlcpy(loc_path, location, sizeof(loc_path)); + } + if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) { + snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", resource->scheme, resource->host, resource->port, loc_path); + } else { + snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", resource->scheme, resource->host, loc_path); + } + } else { + strlcpy(new_path, location, sizeof(new_path)); + } + + php_url_free(resource); + /* check for invalid redirection URLs */ + if ((resource = php_url_parse(new_path)) == NULL) { + php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); + goto out; + } + +#define CHECK_FOR_CNTRL_CHARS(val) { \ + if (val) { \ + unsigned char *s, *e; \ + size_t l; \ + l = php_url_decode(val, strlen(val)); \ + s = (unsigned char*)val; e = s + l; \ + while (s < e) { \ + if (iscntrl(*s)) { \ + php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \ + goto out; \ + } \ + s++; \ + } \ + } \ +} + /* check for control characters in login, password & path */ + if (strncasecmp(new_path, "http://", sizeof("http://") - 1) || strncasecmp(new_path, "https://", sizeof("https://") - 1)) { + CHECK_FOR_CNTRL_CHARS(resource->user) + CHECK_FOR_CNTRL_CHARS(resource->pass) + CHECK_FOR_CNTRL_CHARS(resource->path) + } + stream = php_stream_url_wrap_http_ex(wrapper, new_path, mode, options, opened_path, context, --redirect_max, HTTP_WRAPPER_REDIRECTED STREAMS_CC); + } else { + php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); + } + } +out: + if (protocol_version) { + efree(protocol_version); + } + + if (http_header_line) { + efree(http_header_line); + } + + if (scratch) { + efree(scratch); + } + + if (resource) { + php_url_free(resource); + } + + if (stream) { + if (header_init) { + ZVAL_COPY(&stream->wrapperdata, &response_header); + } + php_stream_notify_progress_init(context, 0, file_size); + + /* Restore original chunk size now that we're done with headers */ + if (options & STREAM_WILL_CAST) + php_stream_set_chunk_size(stream, (int)chunk_size); + + /* restore the users auto-detect-line-endings setting */ + stream->flags |= eol_detect; + + /* as far as streams are concerned, we are now at the start of + * the stream */ + stream->position = 0; + + /* restore mode */ + strlcpy(stream->mode, mode, sizeof(stream->mode)); + + if (transfer_encoding) { + php_stream_filter_append(&stream->readfilters, transfer_encoding); + } + } else { + if (transfer_encoding) { + php_stream_filter_free(transfer_encoding); + } + } + + zval_ptr_dtor(&response_header); + + return stream; +} +/* }}} */ + +php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */ +{ + return php_stream_url_wrap_http_ex(wrapper, path, mode, options, opened_path, context, PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT STREAMS_CC); +} +/* }}} */ + +static int php_stream_http_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb) /* {{{ */ +{ + /* one day, we could fill in the details based on Date: and Content-Length: + * headers. For now, we return with a failure code to prevent the underlying + * file's details from being used instead. */ + return -1; +} +/* }}} */ + +static php_stream_wrapper_ops http_stream_wops = { + php_stream_url_wrap_http, + NULL, /* stream_close */ + php_stream_http_stream_stat, + NULL, /* stat_url */ + NULL, /* opendir */ + "http", + NULL, /* unlink */ + NULL, /* rename */ + NULL, /* mkdir */ + NULL /* rmdir */ +}; + +PHPAPI php_stream_wrapper php_stream_http_wrapper = { + &http_stream_wops, + NULL, + 1 /* is_url */ +}; + +/* + * 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/http/bug75981.phpt b/ext/standard/tests/http/bug75981.phpt new file mode 100644 index 0000000000000..d415de66b9007 --- /dev/null +++ b/ext/standard/tests/http/bug75981.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #75981 (stack-buffer-overflow while parsing HTTP response) +--INI-- +allow_url_fopen=1 +--SKIPIF-- + +--FILE-- + [ + 'protocol_version' => '1.1', + 'header' => 'Connection: Close' + ], +]; + +$ctx = stream_context_create($options); + +$responses = [ + "data://text/plain,000000000100\xA\xA" +]; +$pid = http_server('tcp://127.0.0.1:12342', $responses); + +echo @file_get_contents('http://127.0.0.1:12342/', false, $ctx); + +http_server_kill($pid); + +?> +DONE +--EXPECT-- +DONE From e7a517b0e21d0a56b04132f9c74d6302c3284127 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:52 +0000 Subject: [PATCH 22/46] commit patch 18374824 --- ext/phar/phar_object.c | 6 +- ext/phar/phar_object.c.orig | 5395 +++++++++++++++++ .../tests/cache_list/frontcontroller10.phpt | 2 +- .../tests/cache_list/frontcontroller6.phpt | 2 +- .../tests/cache_list/frontcontroller8.phpt | 2 +- ext/phar/tests/frontcontroller10.phpt | 2 +- ext/phar/tests/frontcontroller6.phpt | 2 +- ext/phar/tests/frontcontroller8.phpt | 2 +- .../tests/tar/frontcontroller10.phar.phpt | 2 +- ext/phar/tests/tar/frontcontroller6.phar.phpt | 2 +- ext/phar/tests/tar/frontcontroller8.phar.phpt | 2 +- .../tests/zip/frontcontroller10.phar.phpt | 2 +- ext/phar/tests/zip/frontcontroller6.phar.phpt | 2 +- ext/phar/tests/zip/frontcontroller8.phar.phpt | 2 +- 14 files changed, 5409 insertions(+), 16 deletions(-) create mode 100644 ext/phar/phar_object.c.orig diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 14cd83fb9d454..581f4cfc80db5 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -317,8 +317,7 @@ static void phar_do_403(char *entry, int entry_len) /* {{{ */ 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); + PHPWRITE("Access Denied\n \n", sizeof("Access Denied\n \n") - 1); } /* }}} */ @@ -342,8 +341,7 @@ static void phar_do_404(phar_archive_data *phar, char *fname, int fname_len, cha 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); + PHPWRITE("Not Found\n \n", sizeof("Not Found\n \n") - 1); } /* }}} */ diff --git a/ext/phar/phar_object.c.orig b/ext/phar/phar_object.c.orig new file mode 100644 index 0000000000000..14cd83fb9d454 --- /dev/null +++ b/ext/phar/phar_object.c.orig @@ -0,0 +1,5395 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2016 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; + +#if HAVE_SPL +static zend_class_entry *phar_ce_entry; +#endif + +#if PHP_VERSION_ID >= 50300 +# define PHAR_ARG_INFO +#else +# define PHAR_ARG_INFO static +#endif + +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; + int basename_len = strlen(basename); + int 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; + int 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] == '/') { + name_len = spprintf(&name, 4096, "phar://%s%s", arch, entry); + } else { + name_len = 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) = cwd - (entry + 1); + PHAR_G(cwd) = estrndup(entry + 1, PHAR_G(cwd_len)); + } else { + PHAR_G(cwd_len) = 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, size_t 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 = 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 = 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; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = 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, actual_len, path, 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, 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 (phar_open_executed_filename(alias, 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")))) { + 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 = 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 = 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 = 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); + } + + 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); + } + + 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"); + return; + } + + switch (Z_TYPE(retval)) { + case IS_STRING: + efree(entry); + entry = estrndup(Z_STRVAL_P(fci.retval), Z_STRLEN_P(fci.retval)); + entry_len = Z_STRLEN_P(fci.retval); + break; + case IS_TRUE: + case IS_FALSE: + phar_do_403(entry, entry_len); + + if (free_pathinfo) { + efree(path_info); + } + + zend_bailout(); + return; + default: + if (free_pathinfo) { + efree(path_info); + } + + 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, 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 = 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, fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, fname_len, f404, 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, fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, fname_len, f404, 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 = 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; + } + + phar_request_initialize(); + + RETVAL_BOOL(phar_open_executed_filename(alias, 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; + } + + phar_request_initialize(); + + RETVAL_BOOL(phar_open_from_filename(fname, fname_len, alias, 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; + } + + is_executable = executable; + RETVAL_BOOL(phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, is_executable, 2, 1) == SUCCESS); +} +/* }}} */ + +#if HAVE_SPL +/** + * 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 +}; +#endif /* HAVE_SPL */ + +/* {{{ 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) +{ +#if !HAVE_SPL + zend_throw_exception_ex(zend_ce_exception, 0, "Cannot instantiate Phar object without SPL extension"); +#else + 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 (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, fname_len, alias, 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); +#endif /* HAVE_SPL */ +} +/* }}} */ + +/* {{{ 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 (!fname_len) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"\""); + return; + } + + if (FAILURE == phar_open_from_filename(fname, 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 = 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 (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; +} +/* }}} */ + +#if HAVE_SPL + +#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; + uint 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; + uint str_key_len, base_len = p_obj->l, fname_len; + phar_entry_data *data; + php_stream *fp; + 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 %v 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 %v 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 %v returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = 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 %v 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 %v 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 %v 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 = 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 %v 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 %v returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = 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 %v returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + } +#if PHP_API_VERSION < 20100412 + if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %v returned a path \"%s\" that safe mode prevents opening", ZSTR_VAL(ce->name), fname); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } +#endif + + if (php_check_open_basedir(fname)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %v 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 %v 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 (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 = 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 (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 = 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, zend_bool compress) /* {{{ */ +{ + 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; + int oldname_len; + phar_archive_data *pphar = NULL; + php_stream_statbuf ssb; + + 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; + } + + if (ext[0] == '.') { + ++ext; + } + + 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); + + basename = estrndup(oldname, oldname_len); + spprintf(&newname, 0, "%s.%s", strtok(basename, "."), ext); + efree(basename); + + + + basepath = estrndup(oldpath, (strlen(oldpath) - oldname_len)); + phar->fname_len = spprintf(&newpath, 0, "%s%s", basepath, newname); + 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 = 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, php_uint32 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, 0))) { + 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; + php_uint32 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, 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; + php_uint32 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, 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, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) 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 (alias_len == 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, 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, 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 = 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 = 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; + } + + 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 = algo; + phar_obj->archive->is_modified = 1; + PHAR_G(openssl_privatekey) = key; + PHAR_G(openssl_privatekey_len) = 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); + php_uint32 compress = *(php_uint32 *)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, php_uint32 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; + php_uint32 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; + php_uint32 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 (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, (uint) oldfile_len) || NULL == (oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint) 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, (uint) newfile_len)) { + if (NULL != (temp = zend_hash_str_find_ptr(&phar_obj->archive->manifest, newfile, (uint) 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, (uint) 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 = 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_hash_str_exists(&phar_obj->archive->manifest, fname, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) 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, (uint) 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; + } + + /* 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, 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", phar_obj->archive->fname); + 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 >= 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", (*pphar)->fname); + 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 (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", phar_obj->archive->fname); + return; + } + + phar_add_file(&(phar_obj->archive), fname, 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_hash_str_exists(&phar_obj->archive->manifest, fname, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) 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, (uint) 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 (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, 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 PHP_API_VERSION < 20100412 + if (PG(safe_mode) && (!php_checkuid(fname, NULL, CHECKUID_ALLOW_ONLY_FILE))) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "phar error: unable to open file \"%s\" to add to phar archive, safe_mode restrictions prevent this", fname); + return; + } +#endif + + 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, 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; + } + + phar_add_file(&(phar_obj->archive), localname, 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; + } +} +/* }}} */ +#if PHP_API_VERSION < 20100412 +#define PHAR_OPENBASEDIR_CHECKPATH(filename) \ + (PG(safe_mode) && (!php_checkuid(filename, NULL, CHECKUID_CHECK_FILE_AND_DIR))) || php_check_open_basedir(filename) +#else +#define PHAR_OPENBASEDIR_CHECKPATH(filename) \ + php_check_open_basedir(filename) +#endif + +static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *dest, int dest_len, char **error) /* {{{ */ +{ + php_stream_statbuf ssb; + int 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 TSRMLS_CC) != 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 (PHAR_OPENBASEDIR_CHECKPATH(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); + free(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); + free(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; + } + +#if PHP_API_VERSION < 20100412 + fp = php_stream_open_wrapper(fullpath, "w+b", REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL); +#else + fp = php_stream_open_wrapper(fullpath, "w+b", REPORT_ERRORS, NULL); +#endif + + 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, 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, 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, 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; +} +/* }}} */ + +#endif /* HAVE_SPL */ + +/* {{{ phar methods */ +PHAR_ARG_INFO +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_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_cancompress, 0, 0, 0) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_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() + +PHAR_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() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mungServer, 0, 0, 1) + ZEND_ARG_INFO(0, munglist) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_running, 0, 0, 1) + ZEND_ARG_INFO(0, retphar) +ZEND_END_ARG_INFO() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_ua, 0, 0, 1) + ZEND_ARG_INFO(0, archive) +ZEND_END_ARG_INFO() + +#if HAVE_SPL +PHAR_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() + +PHAR_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() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_decomp, 0, 0, 0) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_comp, 0, 0, 1) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_compo, 0, 0, 0) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_delete, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetExists, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setAlias, 0, 0, 1) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setMetadata, 0, 0, 1) + ZEND_ARG_INFO(0, metadata) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_emptydir, 0, 0, 0) + ZEND_ARG_INFO(0, dirname) +ZEND_END_ARG_INFO() + +PHAR_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() + +PHAR_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() + +PHAR_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() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_isff, 0, 0, 1) + ZEND_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO(arginfo_phar__void, 0) +ZEND_END_ARG_INFO() + + +#endif /* HAVE_SPL */ + +zend_function_entry php_archive_methods[] = { +#if !HAVE_SPL + PHP_ME(Phar, __construct, arginfo_phar___construct, ZEND_ACC_PRIVATE) +#else + 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) +#endif + /* 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 +}; + +#if HAVE_SPL +PHAR_ARG_INFO +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +PHAR_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 +}; +#endif /* HAVE_SPL */ + +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); + +#if HAVE_SPL + 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, spl_ce_Countable, zend_ce_arrayaccess); + + INIT_CLASS_ENTRY(ce, "PharData", php_archive_methods); + phar_ce_data = zend_register_internal_class_ex(&ce, spl_ce_RecursiveDirectoryIterator); + + zend_class_implements(phar_ce_data, 2, spl_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); +#else + INIT_CLASS_ENTRY(ce, "Phar", php_archive_methods); + phar_ce_archive = zend_register_internal_class(&ce); + phar_ce_archive->ce_flags |= ZEND_ACC_FINAL; + + INIT_CLASS_ENTRY(ce, "PharData", php_archive_methods); + phar_ce_data = zend_register_internal_class(&ce); + phar_ce_data->ce_flags |= ZEND_ACC_FINAL; +#endif + + 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/cache_list/frontcontroller10.phpt b/ext/phar/tests/cache_list/frontcontroller10.phpt index 00177d4ff5580..5fd986895d569 100644 --- a/ext/phar/tests/cache_list/frontcontroller10.phpt +++ b/ext/phar/tests/cache_list/frontcontroller10.phpt @@ -20,6 +20,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

diff --git a/ext/phar/tests/cache_list/frontcontroller6.phpt b/ext/phar/tests/cache_list/frontcontroller6.phpt index 2480be41293a6..a79c95851712b 100644 --- a/ext/phar/tests/cache_list/frontcontroller6.phpt +++ b/ext/phar/tests/cache_list/frontcontroller6.phpt @@ -18,6 +18,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/cache_list/frontcontroller8.phpt b/ext/phar/tests/cache_list/frontcontroller8.phpt index bf9b390defc43..e04f9e5a3afe4 100644 --- a/ext/phar/tests/cache_list/frontcontroller8.phpt +++ b/ext/phar/tests/cache_list/frontcontroller8.phpt @@ -18,6 +18,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/frontcontroller10.phpt b/ext/phar/tests/frontcontroller10.phpt index 667d5c243cd4e..b3f5e640dd7ba 100644 --- a/ext/phar/tests/frontcontroller10.phpt +++ b/ext/phar/tests/frontcontroller10.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

diff --git a/ext/phar/tests/frontcontroller6.phpt b/ext/phar/tests/frontcontroller6.phpt index 1a2cc2cd23d12..c5dd382b10acb 100644 --- a/ext/phar/tests/frontcontroller6.phpt +++ b/ext/phar/tests/frontcontroller6.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/frontcontroller8.phpt b/ext/phar/tests/frontcontroller8.phpt index 36e3206d66879..77d33dac38b77 100644 --- a/ext/phar/tests/frontcontroller8.phpt +++ b/ext/phar/tests/frontcontroller8.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller10.phar.phpt b/ext/phar/tests/tar/frontcontroller10.phar.phpt index f1fc6e3d0fd00..23ce6f37e2554 100644 --- a/ext/phar/tests/tar/frontcontroller10.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller10.phar.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller6.phar.phpt b/ext/phar/tests/tar/frontcontroller6.phar.phpt index 5375beef8cc8b..b811f00f75d9f 100644 --- a/ext/phar/tests/tar/frontcontroller6.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller6.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller8.phar.phpt b/ext/phar/tests/tar/frontcontroller8.phar.phpt index 19844cb19942d..a180e2010aec9 100644 --- a/ext/phar/tests/tar/frontcontroller8.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller8.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/zip/frontcontroller10.phar.phpt b/ext/phar/tests/zip/frontcontroller10.phar.phpt index 56d16c2064ab5..5bbe9e1affba7 100644 --- a/ext/phar/tests/zip/frontcontroller10.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller10.phar.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

\ No newline at end of file diff --git a/ext/phar/tests/zip/frontcontroller6.phar.phpt b/ext/phar/tests/zip/frontcontroller6.phar.phpt index 15489f6ca7280..63f7c62e88d22 100644 --- a/ext/phar/tests/zip/frontcontroller6.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller6.phar.phpt @@ -17,6 +17,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/zip/frontcontroller8.phar.phpt b/ext/phar/tests/zip/frontcontroller8.phar.phpt index 1b0d133bc7170..d4c3a3f9ea05c 100644 --- a/ext/phar/tests/zip/frontcontroller8.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller8.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file From 0b1cab84f4a2d65ced7b7f48a84eb30db72b0466 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:54 +0000 Subject: [PATCH 23/46] commit patch 20147412 --- ext/iconv/iconv.c | 3 + ext/iconv/iconv.c.orig | 2944 +++++++++++++++++++++++++++++++++ ext/iconv/tests/bug76249.phpt | 16 + 3 files changed, 2963 insertions(+) create mode 100644 ext/iconv/iconv.c.orig create mode 100644 ext/iconv/tests/bug76249.phpt diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index 309258b4c7a0a..9367d3728805d 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -2648,6 +2648,9 @@ static int php_iconv_stream_filter_append_bucket( tcnt = 0; break; } + } else { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; } break; diff --git a/ext/iconv/iconv.c.orig b/ext/iconv/iconv.c.orig new file mode 100644 index 0000000000000..309258b4c7a0a --- /dev/null +++ b/ext/iconv/iconv.c.orig @@ -0,0 +1,2944 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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: Rui Hirokawa | + | Stig Bakken | + | Moriyoshi Koizumi | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "main/php_output.h" +#include "SAPI.h" +#include "php_ini.h" + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include + +#include "php_iconv.h" + +#ifdef HAVE_ICONV + +#ifdef PHP_ICONV_H_PATH +#include PHP_ICONV_H_PATH +#else +#include +#endif + +#ifdef HAVE_GLIBC_ICONV +#include +#endif + +#ifdef HAVE_LIBICONV +#undef iconv +#endif + +#include "zend_smart_str.h" +#include "ext/standard/base64.h" +#include "ext/standard/quot_print.h" + +#define _php_iconv_memequal(a, b, c) \ + ((c) == sizeof(zend_ulong) ? *((zend_ulong *)(a)) == *((zend_ulong *)(b)) : ((c) == sizeof(unsigned int) ? *((unsigned int *)(a)) == *((unsigned int *)(b)) : memcmp(a, b, c) == 0)) + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strlen, 0, 0, 1) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_substr, 0, 0, 2) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, length) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strpos, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strrpos, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_encode, 0, 0, 2) + ZEND_ARG_INFO(0, field_name) + ZEND_ARG_INFO(0, field_value) + ZEND_ARG_INFO(0, preference) /* ZEND_ARG_ARRAY_INFO(0, preference, 1) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode, 0, 0, 1) + ZEND_ARG_INFO(0, encoded_string) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode_headers, 0, 0, 1) + ZEND_ARG_INFO(0, headers) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_iconv, 0) + ZEND_ARG_INFO(0, in_charset) + ZEND_ARG_INFO(0, out_charset) + ZEND_ARG_INFO(0, str) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_iconv_set_encoding, 0) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_get_encoding, 0, 0, 0) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ iconv_functions[] + */ +const zend_function_entry iconv_functions[] = { + PHP_RAW_NAMED_FE(iconv,php_if_iconv, arginfo_iconv) + PHP_FE(iconv_get_encoding, arginfo_iconv_get_encoding) + PHP_FE(iconv_set_encoding, arginfo_iconv_set_encoding) + PHP_FE(iconv_strlen, arginfo_iconv_strlen) + PHP_FE(iconv_substr, arginfo_iconv_substr) + PHP_FE(iconv_strpos, arginfo_iconv_strpos) + PHP_FE(iconv_strrpos, arginfo_iconv_strrpos) + PHP_FE(iconv_mime_encode, arginfo_iconv_mime_encode) + PHP_FE(iconv_mime_decode, arginfo_iconv_mime_decode) + PHP_FE(iconv_mime_decode_headers, arginfo_iconv_mime_decode_headers) + PHP_FE_END +}; +/* }}} */ + +ZEND_DECLARE_MODULE_GLOBALS(iconv) +static PHP_GINIT_FUNCTION(iconv); + +/* {{{ iconv_module_entry + */ +zend_module_entry iconv_module_entry = { + STANDARD_MODULE_HEADER, + "iconv", + iconv_functions, + PHP_MINIT(miconv), + PHP_MSHUTDOWN(miconv), + NULL, + NULL, + PHP_MINFO(miconv), + PHP_ICONV_VERSION, + PHP_MODULE_GLOBALS(iconv), + PHP_GINIT(iconv), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_ICONV +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(iconv) +#endif + +/* {{{ PHP_GINIT_FUNCTION */ +static PHP_GINIT_FUNCTION(iconv) +{ +#if defined(COMPILE_DL_ICONV) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + iconv_globals->input_encoding = NULL; + iconv_globals->output_encoding = NULL; + iconv_globals->internal_encoding = NULL; +} +/* }}} */ + +#if defined(HAVE_LIBICONV) && defined(ICONV_ALIASED_LIBICONV) +#define iconv libiconv +#endif + +/* {{{ typedef enum php_iconv_enc_scheme_t */ +typedef enum _php_iconv_enc_scheme_t { + PHP_ICONV_ENC_SCHEME_BASE64, + PHP_ICONV_ENC_SCHEME_QPRINT +} php_iconv_enc_scheme_t; +/* }}} */ + +#define PHP_ICONV_MIME_DECODE_STRICT (1<<0) +#define PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR (1<<1) + +/* {{{ prototypes */ +static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd); +static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd); + +static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset); + +static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_t nbytes, const char *enc); + +static php_iconv_err_t _php_iconv_substr(smart_str *pretval, const char *str, size_t nbytes, zend_long offset, zend_long len, const char *enc); + +static php_iconv_err_t _php_iconv_strpos(size_t *pretval, const char *haystk, size_t haystk_nbytes, const char *ndl, size_t ndl_nbytes, zend_long offset, const char *enc); + +static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, size_t max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc); + +static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode); + +static php_iconv_err_t php_iconv_stream_filter_register_factory(void); +static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void); + +static int php_iconv_output_conflict(const char *handler_name, size_t handler_name_len); +static php_output_handler *php_iconv_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags); +static int php_iconv_output_handler(void **nothing, php_output_context *output_context); +/* }}} */ + +/* {{{ static globals */ +static char _generic_superset_name[] = ICONV_UCS4_ENCODING; +#define GENERIC_SUPERSET_NAME _generic_superset_name +#define GENERIC_SUPERSET_NBYTES 4 +/* }}} */ + + +static PHP_INI_MH(OnUpdateInputEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.input_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +static PHP_INI_MH(OnUpdateOutputEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.output_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +static PHP_INI_MH(OnUpdateInternalEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.internal_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("iconv.input_encoding", "", PHP_INI_ALL, OnUpdateInputEncoding, input_encoding, zend_iconv_globals, iconv_globals) + STD_PHP_INI_ENTRY("iconv.output_encoding", "", PHP_INI_ALL, OnUpdateOutputEncoding, output_encoding, zend_iconv_globals, iconv_globals) + STD_PHP_INI_ENTRY("iconv.internal_encoding", "", PHP_INI_ALL, OnUpdateInternalEncoding, internal_encoding, zend_iconv_globals, iconv_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(miconv) +{ + char *version = "unknown"; + + REGISTER_INI_ENTRIES(); + +#if HAVE_LIBICONV + { + static char buf[16]; + snprintf(buf, sizeof(buf), "%d.%d", + ((_libiconv_version >> 8) & 0x0f), (_libiconv_version & 0x0f)); + version = buf; + } +#elif HAVE_GLIBC_ICONV + version = (char *)gnu_get_libc_version(); +#elif defined(NETWARE) + version = "OS built-in"; +#endif + +#ifdef PHP_ICONV_IMPL + REGISTER_STRING_CONSTANT("ICONV_IMPL", PHP_ICONV_IMPL, CONST_CS | CONST_PERSISTENT); +#elif HAVE_LIBICONV + REGISTER_STRING_CONSTANT("ICONV_IMPL", "libiconv", CONST_CS | CONST_PERSISTENT); +#elif defined(NETWARE) + REGISTER_STRING_CONSTANT("ICONV_IMPL", "Novell", CONST_CS | CONST_PERSISTENT); +#else + REGISTER_STRING_CONSTANT("ICONV_IMPL", "unknown", CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_STRING_CONSTANT("ICONV_VERSION", version, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_STRICT", PHP_ICONV_MIME_DECODE_STRICT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_CONTINUE_ON_ERROR", PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR, CONST_CS | CONST_PERSISTENT); + + if (php_iconv_stream_filter_register_factory() != PHP_ICONV_ERR_SUCCESS) { + return FAILURE; + } + + php_output_handler_alias_register(ZEND_STRL("ob_iconv_handler"), php_iconv_output_handler_init); + php_output_handler_conflict_register(ZEND_STRL("ob_iconv_handler"), php_iconv_output_conflict); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(miconv) +{ + php_iconv_stream_filter_unregister_factory(); + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(miconv) +{ + zval *iconv_impl, *iconv_ver; + + iconv_impl = zend_get_constant_str("ICONV_IMPL", sizeof("ICONV_IMPL")-1); + iconv_ver = zend_get_constant_str("ICONV_VERSION", sizeof("ICONV_VERSION")-1); + + php_info_print_table_start(); + php_info_print_table_row(2, "iconv support", "enabled"); + php_info_print_table_row(2, "iconv implementation", Z_STRVAL_P(iconv_impl)); + php_info_print_table_row(2, "iconv library version", Z_STRVAL_P(iconv_ver)); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +static char *get_internal_encoding(void) { + if (ICONVG(internal_encoding) && ICONVG(internal_encoding)[0]) { + return ICONVG(internal_encoding); + } else if (PG(internal_encoding) && PG(internal_encoding)[0]) { + return PG(internal_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + +static char *get_input_encoding(void) { + if (ICONVG(input_encoding) && ICONVG(input_encoding)[0]) { + return ICONVG(input_encoding); + } else if (PG(input_encoding) && PG(input_encoding)[0]) { + return PG(input_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + +static char *get_output_encoding(void) { + if (ICONVG(output_encoding) && ICONVG(output_encoding)[0]) { + return ICONVG(output_encoding); + } else if (PG(output_encoding) && PG(output_encoding)[0]) { + return PG(output_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + + +static int php_iconv_output_conflict(const char *handler_name, size_t handler_name_len) +{ + if (php_output_get_level()) { + if (php_output_handler_conflict(handler_name, handler_name_len, ZEND_STRL("ob_iconv_handler")) + || php_output_handler_conflict(handler_name, handler_name_len, ZEND_STRL("mb_output_handler"))) { + return FAILURE; + } + } + return SUCCESS; +} + +static php_output_handler *php_iconv_output_handler_init(const char *handler_name, size_t handler_name_len, size_t chunk_size, int flags) +{ + return php_output_handler_create_internal(handler_name, handler_name_len, php_iconv_output_handler, chunk_size, flags); +} + +static int php_iconv_output_handler(void **nothing, php_output_context *output_context) +{ + char *s, *content_type, *mimetype = NULL; + int output_status, mimetype_len = 0; + + if (output_context->op & PHP_OUTPUT_HANDLER_START) { + output_status = php_output_get_status(); + if (output_status & PHP_OUTPUT_SENT) { + return FAILURE; + } + + if (SG(sapi_headers).mimetype && !strncasecmp(SG(sapi_headers).mimetype, "text/", 5)) { + if ((s = strchr(SG(sapi_headers).mimetype,';')) == NULL){ + mimetype = SG(sapi_headers).mimetype; + } else { + mimetype = SG(sapi_headers).mimetype; + mimetype_len = (int)(s - SG(sapi_headers).mimetype); + } + } else if (SG(sapi_headers).send_default_content_type) { + mimetype = SG(default_mimetype) ? SG(default_mimetype) : SAPI_DEFAULT_MIMETYPE; + } + + if (mimetype != NULL && !(output_context->op & PHP_OUTPUT_HANDLER_CLEAN)) { + size_t len; + char *p = strstr(get_output_encoding(), "//"); + + if (p) { + len = spprintf(&content_type, 0, "Content-Type:%.*s; charset=%.*s", mimetype_len ? mimetype_len : (size_t) strlen(mimetype), mimetype, (size_t)(p - get_output_encoding()), get_output_encoding()); + } else { + len = spprintf(&content_type, 0, "Content-Type:%.*s; charset=%s", mimetype_len ? mimetype_len : (size_t) strlen(mimetype), mimetype, get_output_encoding()); + } + if (content_type && SUCCESS == sapi_add_header(content_type, (uint)len, 0)) { + SG(sapi_headers).send_default_content_type = 0; + php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE, NULL); + } + } + } + + if (output_context->in.used) { + zend_string *out; + output_context->out.free = 1; + _php_iconv_show_error(php_iconv_string(output_context->in.data, output_context->in.used, &out, get_output_encoding(), get_internal_encoding()), get_output_encoding(), get_internal_encoding()); + if (out) { + output_context->out.data = estrndup(ZSTR_VAL(out), ZSTR_LEN(out)); + output_context->out.used = ZSTR_LEN(out); + zend_string_free(out); + } else { + output_context->out.data = NULL; + output_context->out.used = 0; + } + } + + return SUCCESS; +} + +/* {{{ _php_iconv_appendl() */ +static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd) +{ + const char *in_p = s; + size_t in_left = l; + char *out_p; + size_t out_left = 0; + size_t buf_growth = 128; +#if !ICONV_SUPPORTS_ERRNO + size_t prev_in_left = in_left; +#endif + + if (in_p != NULL) { + while (in_left > 0) { + out_left = buf_growth - out_left; + smart_str_alloc(d, out_left, 0); + + out_p = ZSTR_VAL((d)->s) + ZSTR_LEN((d)->s); + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + return PHP_ICONV_ERR_ILLEGAL_CHAR; + + case EILSEQ: + return PHP_ICONV_ERR_ILLEGAL_SEQ; + + case E2BIG: + break; + + default: + return PHP_ICONV_ERR_UNKNOWN; + } +#else + if (prev_in_left == in_left) { + return PHP_ICONV_ERR_UNKNOWN; + } +#endif + } +#if !ICONV_SUPPORTS_ERRNO + prev_in_left = in_left; +#endif + ZSTR_LEN((d)->s) += (buf_growth - out_left); + buf_growth <<= 1; + } + } else { + for (;;) { + out_left = buf_growth - out_left; + smart_str_alloc(d, out_left, 0); + + out_p = ZSTR_VAL((d)->s) + ZSTR_LEN((d)->s); + + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)0) { + ZSTR_LEN((d)->s) += (buf_growth - out_left); + break; + } else { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + return PHP_ICONV_ERR_UNKNOWN; + } +#else + if (out_left != 0) { + return PHP_ICONV_ERR_UNKNOWN; + } +#endif + } + ZSTR_LEN((d)->s) += (buf_growth - out_left); + buf_growth <<= 1; + } + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ _php_iconv_appendc() */ +static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd) +{ + return _php_iconv_appendl(d, &c, 1, cd); +} +/* }}} */ + +/* {{{ */ +#if ICONV_BROKEN_IGNORE +static int _php_check_ignore(const char *charset) +{ + size_t clen = strlen(charset); + if (clen >= 9 && strcmp("//IGNORE", charset+clen-8) == 0) { + return 1; + } + if (clen >= 19 && strcmp("//IGNORE//TRANSLIT", charset+clen-18) == 0) { + return 1; + } + return 0; +} +#else +#define _php_check_ignore(x) (0) +#endif +/* }}} */ + +/* {{{ php_iconv_string() + */ +PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len, zend_string **out, const char *out_charset, const char *in_charset) +{ +#if !ICONV_SUPPORTS_ERRNO + size_t in_size, out_size, out_left; + char *out_p; + iconv_t cd; + size_t result; + zend_string *ret, *out_buffer; + + /* + This is not the right way to get output size... + This is not space efficient for large text. + This is also problem for encoding like UTF-7/UTF-8/ISO-2022 which + a single char can be more than 4 bytes. + I added 15 extra bytes for safety. + */ + out_size = in_len * sizeof(int) + 15; + out_left = out_size; + + in_size = in_len; + + cd = iconv_open(out_charset, in_charset); + + if (cd == (iconv_t)(-1)) { + return PHP_ICONV_ERR_UNKNOWN; + } + + out_buffer = zend_string_alloc(out_size, 0); + out_p = ZSTR_VAL(out_buffer); + +#ifdef NETWARE + result = iconv(cd, (char **) &in_p, &in_size, (char **) +#else + result = iconv(cd, (const char **) &in_p, &in_size, (char **) +#endif + &out_p, &out_left); + + if (result == (size_t)(-1)) { + zend_string_free(out_buffer); + return PHP_ICONV_ERR_UNKNOWN; + } + + if (out_left < 8) { + size_t pos = out_p - ZSTR_VAL(out_buffer); + out_buffer = zend_string_extend(out_buffer, out_size + 8, 0); + out_p = ZSTR_VAL(out_buffer) + pos; + out_size += 7; + out_left += 7; + } + + /* flush the shift-out sequences */ + result = iconv(cd, NULL, NULL, &out_p, &out_left); + + if (result == (size_t)(-1)) { + zend_string_free(out_buffer); + return PHP_ICONV_ERR_UNKNOWN; + } + + ZSTR_VAL(out_buffer)[out_size - out_left] = '\0'; + ZSTR_LEN(out_buffer) = out_size - out_left; + + iconv_close(cd); + + *out = out_buffer; + return PHP_ICONV_ERR_SUCCESS; + +#else + /* + iconv supports errno. Handle it better way. + */ + iconv_t cd; + size_t in_left, out_size, out_left; + char *out_p; + size_t bsz, result = 0; + php_iconv_err_t retval = PHP_ICONV_ERR_SUCCESS; + zend_string *out_buf; + int ignore_ilseq = _php_check_ignore(out_charset); + + *out = NULL; + + cd = iconv_open(out_charset, in_charset); + + if (cd == (iconv_t)(-1)) { + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } + } + in_left= in_len; + out_left = in_len + 32; /* Avoid realloc() most cases */ + out_size = 0; + bsz = out_left; + out_buf = zend_string_alloc(bsz, 0); + out_p = ZSTR_VAL(out_buf); + + while (in_left > 0) { + result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left); + out_size = bsz - out_left; + if (result == (size_t)(-1)) { + if (ignore_ilseq && errno == EILSEQ) { + if (in_left <= 1) { + result = 0; + } else { + errno = 0; + in_p++; + in_left--; + continue; + } + } + + if (errno == E2BIG && in_left > 0) { + /* converted string is longer than out buffer */ + bsz += in_len; + + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + continue; + } + } + break; + } + + if (result != (size_t)(-1)) { + /* flush the shift-out sequences */ + for (;;) { + result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left); + out_size = bsz - out_left; + + if (result != (size_t)(-1)) { + break; + } + + if (errno == E2BIG) { + bsz += 16; + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + } else { + break; + } + } + } + + iconv_close(cd); + + if (result == (size_t)(-1)) { + switch (errno) { + case EINVAL: + retval = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + retval = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + /* should not happen */ + retval = PHP_ICONV_ERR_TOO_BIG; + break; + + default: + /* other error */ + retval = PHP_ICONV_ERR_UNKNOWN; + zend_string_free(out_buf); + return PHP_ICONV_ERR_UNKNOWN; + } + } + *out_p = '\0'; + ZSTR_LEN(out_buf) = out_size; + *out = out_buf; + return retval; +#endif +} +/* }}} */ + +/* {{{ _php_iconv_strlen() */ +static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_t nbytes, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES*2]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + + *pretval = (size_t)-1; + + cd = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + errno = 0; + out_left = 0; + + for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0; cnt+=2) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { + break; + } + } + } + + if (out_left > 0) { + cnt -= out_left / GENERIC_SUPERSET_NBYTES; + } + +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + case 0: + *pretval = cnt; + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + break; + } +#else + *pretval = cnt; +#endif + + iconv_close(cd); + + return err; +} + +/* }}} */ + +/* {{{ _php_iconv_substr() */ +static php_iconv_err_t _php_iconv_substr(smart_str *pretval, + const char *str, size_t nbytes, zend_long offset, zend_long len, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd1, cd2; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + size_t total_len; + + err = _php_iconv_strlen(&total_len, str, nbytes, enc); + if (err != PHP_ICONV_ERR_SUCCESS) { + return err; + } + + if (len < 0) { + if ((len += (total_len - offset)) < 0) { + return PHP_ICONV_ERR_SUCCESS; + } + } + + if (offset < 0) { + if ((offset += total_len) < 0) { + return PHP_ICONV_ERR_SUCCESS; + } + } + + if((size_t)len > total_len) { + len = total_len; + } + + + if ((size_t)offset >= total_len) { + return PHP_ICONV_ERR_SUCCESS; + } + + if ((size_t)(offset + len) > total_len ) { + /* trying to compute the length */ + len = total_len - offset; + } + + if (len == 0) { + smart_str_appendl(pretval, "", 0); + smart_str_0(pretval); + return PHP_ICONV_ERR_SUCCESS; + } + + cd1 = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd1 == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + cd2 = (iconv_t)NULL; + errno = 0; + + for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0 && len > 0; ++cnt) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd1, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { + break; + } + } + + if ((zend_long)cnt >= offset) { + if (cd2 == (iconv_t)NULL) { + cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME); + + if (cd2 == (iconv_t)(-1)) { + cd2 = (iconv_t)NULL; +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + break; + } + } + + if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) { + break; + } + --len; + } + + } + +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + break; + } +#endif + if (err == PHP_ICONV_ERR_SUCCESS) { + if (cd2 != (iconv_t)NULL) { + _php_iconv_appendl(pretval, NULL, 0, cd2); + } + smart_str_0(pretval); + } + + if (cd1 != (iconv_t)NULL) { + iconv_close(cd1); + } + + if (cd2 != (iconv_t)NULL) { + iconv_close(cd2); + } + return err; +} + +/* }}} */ + +/* {{{ _php_iconv_strpos() */ +static php_iconv_err_t _php_iconv_strpos(size_t *pretval, + const char *haystk, size_t haystk_nbytes, + const char *ndl, size_t ndl_nbytes, + zend_long offset, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + + zend_string *ndl_buf; + const char *ndl_buf_p; + size_t ndl_buf_left; + + size_t match_ofs; + + *pretval = (size_t)-1; + + err = php_iconv_string(ndl, ndl_nbytes, &ndl_buf, GENERIC_SUPERSET_NAME, enc); + + if (err != PHP_ICONV_ERR_SUCCESS) { + if (ndl_buf != NULL) { + zend_string_free(ndl_buf); + } + return err; + } + + cd = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd == (iconv_t)(-1)) { + if (ndl_buf != NULL) { + zend_string_free(ndl_buf); + } +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + match_ofs = (size_t)-1; + + for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; in_left > 0; ++cnt) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + break; + } +#endif + break; + } + } + if (offset >= 0) { + if (cnt >= (size_t)offset) { + if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) { + if (match_ofs == (size_t)-1) { + match_ofs = cnt; + } + ndl_buf_p += GENERIC_SUPERSET_NBYTES; + ndl_buf_left -= GENERIC_SUPERSET_NBYTES; + if (ndl_buf_left == 0) { + *pretval = match_ofs; + break; + } + } else { + size_t i, j, lim; + + i = 0; + j = GENERIC_SUPERSET_NBYTES; + lim = (size_t)(ndl_buf_p - ZSTR_VAL(ndl_buf)); + + while (j < lim) { + if (_php_iconv_memequal(&ZSTR_VAL(ndl_buf)[j], &ZSTR_VAL(ndl_buf)[i], + GENERIC_SUPERSET_NBYTES)) { + i += GENERIC_SUPERSET_NBYTES; + } else { + j -= i; + i = 0; + } + j += GENERIC_SUPERSET_NBYTES; + } + + if (_php_iconv_memequal(buf, &ZSTR_VAL(ndl_buf)[i], sizeof(buf))) { + match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES; + i += GENERIC_SUPERSET_NBYTES; + ndl_buf_p = &ZSTR_VAL(ndl_buf)[i]; + ndl_buf_left = ZSTR_LEN(ndl_buf) - i; + } else { + match_ofs = (size_t)-1; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + } + } + } + } else { + if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) { + if (match_ofs == (size_t)-1) { + match_ofs = cnt; + } + ndl_buf_p += GENERIC_SUPERSET_NBYTES; + ndl_buf_left -= GENERIC_SUPERSET_NBYTES; + if (ndl_buf_left == 0) { + *pretval = match_ofs; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + match_ofs = -1; + } + } else { + size_t i, j, lim; + + i = 0; + j = GENERIC_SUPERSET_NBYTES; + lim = (size_t)(ndl_buf_p - ZSTR_VAL(ndl_buf)); + + while (j < lim) { + if (_php_iconv_memequal(&ZSTR_VAL(ndl_buf)[j], &ZSTR_VAL(ndl_buf)[i], + GENERIC_SUPERSET_NBYTES)) { + i += GENERIC_SUPERSET_NBYTES; + } else { + j -= i; + i = 0; + } + j += GENERIC_SUPERSET_NBYTES; + } + + if (_php_iconv_memequal(buf, &ZSTR_VAL(ndl_buf)[i], sizeof(buf))) { + match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES; + i += GENERIC_SUPERSET_NBYTES; + ndl_buf_p = &ZSTR_VAL(ndl_buf)[i]; + ndl_buf_left = ZSTR_LEN(ndl_buf) - i; + } else { + match_ofs = (size_t)-1; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + } + } + } + } + + if (ndl_buf) { + zend_string_free(ndl_buf); + } + + iconv_close(cd); + + return err; +} +/* }}} */ + +/* {{{ _php_iconv_mime_encode() */ +static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, size_t max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc) +{ + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); + size_t char_cnt = 0; + size_t out_charset_len; + size_t lfchars_len; + char *buf = NULL; + const char *in_p; + size_t in_left; + char *out_p; + size_t out_left; + zend_string *encoded = NULL; + static int qp_table[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x00 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 */ + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x20 */ + 1, 1, 1, 1, 1, 1, 1 ,1, 1, 1, 1, 1, 1, 3, 1, 3, /* 0x30 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x50 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x70 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x80 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x90 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xA0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xB0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xC0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xD0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xE0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 /* 0xF0 */ + }; + + out_charset_len = strlen(out_charset); + lfchars_len = strlen(lfchars); + + if ((fname_nbytes + 2) >= max_line_len + || (out_charset_len + 12) >= max_line_len) { + /* field name is too long */ + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + + cd_pl = iconv_open(ICONV_ASCII_ENCODING, enc); + if (cd_pl == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + cd = iconv_open(out_charset, enc); + if (cd == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + buf = safe_emalloc(1, max_line_len, 5); + + char_cnt = max_line_len; + + _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl); + char_cnt -= fname_nbytes; + smart_str_appendl(pretval, ": ", sizeof(": ") - 1); + char_cnt -= 2; + + in_p = fval; + in_left = fval_nbytes; + + do { + size_t prev_in_left; + size_t out_size; + + if (char_cnt < (out_charset_len + 12)) { + /* lfchars must be encoded in ASCII here*/ + smart_str_appendl(pretval, lfchars, lfchars_len); + smart_str_appendc(pretval, ' '); + char_cnt = max_line_len - 1; + } + + smart_str_appendl(pretval, "=?", sizeof("=?") - 1); + char_cnt -= 2; + smart_str_appendl(pretval, out_charset, out_charset_len); + char_cnt -= out_charset_len; + smart_str_appendc(pretval, '?'); + char_cnt --; + + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: { + size_t ini_in_left; + const char *ini_in_p; + size_t out_reserved = 4; + + smart_str_appendc(pretval, 'B'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; + + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; + + out_size = (char_cnt - 2) / 4 * 3; + + for (;;) { + out_p = buf; + + if (out_size <= out_reserved) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + + out_left = out_size - out_reserved; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; + + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } + + out_left += out_reserved; + + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (out_left != 0) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } else { + break; + } + + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + out_reserved += 4; + in_left = ini_in_left; + in_p = ini_in_p; + } + + prev_in_left = in_left; + + encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left)); + + if (char_cnt < ZSTR_LEN(encoded)) { + /* something went wrong! */ + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded)); + char_cnt -= ZSTR_LEN(encoded); + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; + + zend_string_release(encoded); + encoded = NULL; + } break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */ + + case PHP_ICONV_ENC_SCHEME_QPRINT: { + size_t ini_in_left; + const char *ini_in_p; + const unsigned char *p; + size_t nbytes_required; + + smart_str_appendc(pretval, 'Q'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; + + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; + + for (out_size = (char_cnt - 2) / 3; out_size > 0;) { +#if !ICONV_SUPPORTS_ERRNO + size_t prev_out_left; +#endif + + nbytes_required = 0; + + out_p = buf; + out_left = out_size; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; + + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } +#if !ICONV_SUPPORTS_ERRNO + prev_out_left = out_left; +#endif + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (out_left == prev_out_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } + + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + nbytes_required += qp_table[*p]; + } + + if (nbytes_required <= char_cnt - 2) { + break; + } + + out_size -= ((nbytes_required - (char_cnt - 2)) + 1) / 3; + in_left = ini_in_left; + in_p = ini_in_p; + } + + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + if (qp_table[*p] == 1) { + smart_str_appendc(pretval, *(char *)p); + char_cnt--; + } else { + static char qp_digits[] = "0123456789ABCDEF"; + smart_str_appendc(pretval, '='); + smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]); + smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]); + char_cnt -= 3; + } + } + + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; + + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */ + } + } while (in_left > 0); + + smart_str_0(pretval); + +out: + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + if (cd_pl != (iconv_t)(-1)) { + iconv_close(cd_pl); + } + if (encoded != NULL) { + zend_string_release(encoded); + } + if (buf != NULL) { + efree(buf); + } + return err; +} +/* }}} */ + +/* {{{ _php_iconv_mime_decode() */ +static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode) +{ + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); + + const char *p1; + size_t str_left; + unsigned int scan_stat = 0; + const char *csname = NULL; + size_t csname_len; + const char *encoded_text = NULL; + size_t encoded_text_len = 0; + const char *encoded_word = NULL; + const char *spaces = NULL; + + php_iconv_enc_scheme_t enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; + + if (next_pos != NULL) { + *next_pos = NULL; + } + + cd_pl = iconv_open(enc, ICONV_ASCII_ENCODING); + + if (cd_pl == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + p1 = str; + for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { + int eos = 0; + + switch (scan_stat) { + case 0: /* expecting any character */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + encoded_word = p1; + scan_stat = 1; + break; + + case ' ': case '\t': /* a chunk of whitespaces */ + spaces = p1; + scan_stat = 11; + break; + + default: /* first letter of a non-encoded word */ + _php_iconv_appendc(pretval, *p1, cd_pl); + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } + break; + } + break; + + case 1: /* expecting a delimiter */ + if (*p1 != '?') { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } + csname = p1 + 1; + scan_stat = 2; + break; + + case 2: /* expecting a charset name */ + switch (*p1) { + case '?': /* normal delimiter: encoding scheme follows */ + scan_stat = 3; + break; + + case '*': /* new style delimiter: locale id follows */ + scan_stat = 10; + break; + } + if (scan_stat != 2) { + char tmpbuf[80]; + + if (csname == NULL) { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + + csname_len = (size_t)(p1 - csname); + + if (csname_len > sizeof(tmpbuf) - 1) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + + memcpy(tmpbuf, csname, csname_len); + tmpbuf[csname_len] = '\0'; + + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + + cd = iconv_open(enc, tmpbuf); + + if (cd == (iconv_t)(-1)) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* Bad character set, but the user wants us to + * press on. In this case, we'll just insert the + * undecoded encoded word, since there isn't really + * a more sensible behaviour available; the only + * other options are to swallow the encoded word + * entirely or decode it with an arbitrarily chosen + * single byte encoding, both of which seem to have + * a higher WTF factor than leaving it undecoded. + * + * Given this approach, we need to skip ahead to + * the end of the encoded word. */ + int qmarks = 2; + while (qmarks > 0 && str_left > 1) { + if (*(++p1) == '?') { + --qmarks; + } + --str_left; + } + + /* Look ahead to check for the terminating = that + * should be there as well; if it's there, we'll + * also include that. If it's not, there isn't much + * we can do at this point. */ + if (*(p1 + 1) == '=') { + ++p1; + --str_left; + } + + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + + /* Let's go back and see if there are further + * encoded words or bare content, and hope they + * might actually have a valid character set. */ + scan_stat = 12; + break; + } else { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + } + } + break; + + case 3: /* expecting a encoding scheme specifier */ + switch (*p1) { + case 'b': + case 'B': + enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; + scan_stat = 4; + break; + + case 'q': + case 'Q': + enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT; + scan_stat = 4; + break; + + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + break; + + case 4: /* expecting a delimiter */ + if (*p1 != '?') { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + encoded_text = p1 + 1; + scan_stat = 5; + break; + + case 5: /* expecting an encoded portion */ + if (*p1 == '?') { + encoded_text_len = (size_t)(p1 - encoded_text); + scan_stat = 6; + } + break; + + case 7: /* expecting a "\n" character */ + if (*p1 == '\n') { + scan_stat = 8; + } else { + /* bare CR */ + _php_iconv_appendc(pretval, '\r', cd_pl); + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 0; + } + break; + + case 8: /* checking whether the following line is part of a + folded header */ + if (*p1 != ' ' && *p1 != '\t') { + --p1; + str_left = 1; /* quit_loop */ + break; + } + if (encoded_word == NULL) { + _php_iconv_appendc(pretval, ' ', cd_pl); + } + spaces = NULL; + scan_stat = 11; + break; + + case 6: /* expecting a End-Of-Chunk character "=" */ + if (*p1 != '=') { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + scan_stat = 9; + if (str_left == 1) { + eos = 1; + } else { + break; + } + + case 9: /* choice point, seeing what to do next.*/ + switch (*p1) { + default: + /* Handle non-RFC-compliant formats + * + * RFC2047 requires the character that comes right + * after an encoded word (chunk) to be a whitespace, + * while there are lots of broken implementations that + * generate such malformed headers that don't fulfill + * that requirement. + */ + if (!eos) { + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + scan_stat = 12; + break; + } + } + /* break is omitted intentionally */ + + case '\r': case '\n': case ' ': case '\t': { + zend_string *decoded_text; + + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: + decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len); + break; + + case PHP_ICONV_ENC_SCHEME_QPRINT: + decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1); + break; + default: + decoded_text = NULL; + break; + } + + if (decoded_text == NULL) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + } + + err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd); + zend_string_release(decoded_text); + + if (err != PHP_ICONV_ERR_SUCCESS) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl); + encoded_word = NULL; + if (err != PHP_ICONV_ERR_SUCCESS) { + break; + } + } else { + goto out; + } + } + + if (eos) { /* reached end-of-string. done. */ + scan_stat = 0; + break; + } + + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + scan_stat = 1; + break; + + case ' ': case '\t': /* medial whitespaces */ + spaces = p1; + scan_stat = 11; + break; + + default: /* first letter of a non-encoded word */ + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 12; + break; + } + } break; + } + break; + + case 10: /* expects a language specifier. dismiss it for now */ + if (*p1 == '?') { + scan_stat = 3; + } + break; + + case 11: /* expecting a chunk of whitespaces */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + if (spaces != NULL && encoded_word == NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + encoded_word = p1; + scan_stat = 1; + break; + + case ' ': case '\t': + break; + + default: /* first letter of a non-encoded word */ + if (spaces != NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + _php_iconv_appendc(pretval, *p1, cd_pl); + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } + break; + + case 12: /* expecting a non-encoded word */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case ' ': case '\t': + spaces = p1; + scan_stat = 11; + break; + + case '=': /* first letter of an encoded chunk */ + if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) { + encoded_word = p1; + scan_stat = 1; + break; + } + /* break is omitted intentionally */ + + default: + _php_iconv_appendc(pretval, *p1, cd_pl); + break; + } + break; + } + } + switch (scan_stat) { + case 0: case 8: case 11: case 12: + break; + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + if (scan_stat == 1) { + _php_iconv_appendc(pretval, '=', cd_pl); + } + err = PHP_ICONV_ERR_SUCCESS; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + + if (next_pos != NULL) { + *next_pos = p1; + } + + smart_str_0(pretval); +out: + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + if (cd_pl != (iconv_t)(-1)) { + iconv_close(cd_pl); + } + return err; +} +/* }}} */ + +/* {{{ php_iconv_show_error() */ +static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset) +{ + switch (err) { + case PHP_ICONV_ERR_SUCCESS: + break; + + case PHP_ICONV_ERR_CONVERTER: + php_error_docref(NULL, E_NOTICE, "Cannot open converter"); + break; + + case PHP_ICONV_ERR_WRONG_CHARSET: + php_error_docref(NULL, E_NOTICE, "Wrong charset, conversion from `%s' to `%s' is not allowed", + in_charset, out_charset); + break; + + case PHP_ICONV_ERR_ILLEGAL_CHAR: + php_error_docref(NULL, E_NOTICE, "Detected an incomplete multibyte character in input string"); + break; + + case PHP_ICONV_ERR_ILLEGAL_SEQ: + php_error_docref(NULL, E_NOTICE, "Detected an illegal character in input string"); + break; + + case PHP_ICONV_ERR_TOO_BIG: + /* should not happen */ + php_error_docref(NULL, E_WARNING, "Buffer length exceeded"); + break; + + case PHP_ICONV_ERR_MALFORMED: + php_error_docref(NULL, E_WARNING, "Malformed string"); + break; + + default: + /* other error */ + php_error_docref(NULL, E_NOTICE, "Unknown error (%d)", errno); + break; + } +} +/* }}} */ + +/* {{{ proto int iconv_strlen(string str [, string charset]) + Returns the character count of str */ +PHP_FUNCTION(iconv_strlen) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *str; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|s", + &str, &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_strlen(&retval, ZSTR_VAL(str), ZSTR_LEN(str), charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + if (err == PHP_ICONV_ERR_SUCCESS) { + RETVAL_LONG(retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv_substr(string str, int offset, [int length, string charset]) + Returns specified part of a string */ +PHP_FUNCTION(iconv_substr) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *str; + zend_long offset, length = 0; + + php_iconv_err_t err; + + smart_str retval = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|ls", + &str, &offset, &length, + &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (ZEND_NUM_ARGS() < 3) { + length = ZSTR_LEN(str); + } + + err = _php_iconv_substr(&retval, ZSTR_VAL(str), ZSTR_LEN(str), offset, length, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && ZSTR_LEN(str) > 0 && retval.s != NULL) { + RETURN_NEW_STR(retval.s); + } + smart_str_free(&retval); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int iconv_strpos(string haystack, string needle [, int offset [, string charset]]) + Finds position of first occurrence of needle within part of haystack beginning with offset */ +PHP_FUNCTION(iconv_strpos) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *haystk; + zend_string *ndl; + zend_long offset = 0; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|ls", + &haystk, &ndl, + &offset, &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (offset < 0) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string."); + RETURN_FALSE; + } + + if (ZSTR_LEN(ndl) < 1) { + RETURN_FALSE; + } + + err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl), + offset, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) { + RETVAL_LONG((zend_long)retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto int iconv_strrpos(string haystack, string needle [, string charset]) + Finds position of last occurrence of needle within part of haystack beginning with offset */ +PHP_FUNCTION(iconv_strrpos) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *haystk; + zend_string *ndl; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|s", + &haystk, &ndl, + &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZSTR_LEN(ndl) < 1) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl), + -1, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) { + RETVAL_LONG((zend_long)retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv_mime_encode(string field_name, string field_value [, array preference]) + Composes a mime header field with field_name and field_value in a specified scheme */ +PHP_FUNCTION(iconv_mime_encode) +{ + zend_string *field_name = NULL; + zend_string *field_value = NULL; + zend_string *tmp_str = NULL; + zval *pref = NULL; + smart_str retval = {0}; + php_iconv_err_t err; + + const char *in_charset = get_internal_encoding(); + const char *out_charset = in_charset; + zend_long line_len = 76; + const char *lfchars = "\r\n"; + php_iconv_enc_scheme_t scheme_id = PHP_ICONV_ENC_SCHEME_BASE64; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|a", + &field_name, &field_value, + &pref) == FAILURE) { + + RETURN_FALSE; + } + + if (pref != NULL) { + zval *pzval; + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "scheme", sizeof("scheme") - 1)) != NULL) { + if (Z_TYPE_P(pzval) == IS_STRING && Z_STRLEN_P(pzval) > 0) { + switch (Z_STRVAL_P(pzval)[0]) { + case 'B': case 'b': + scheme_id = PHP_ICONV_ENC_SCHEME_BASE64; + break; + + case 'Q': case 'q': + scheme_id = PHP_ICONV_ENC_SCHEME_QPRINT; + break; + } + } + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "input-charset", sizeof("input-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) { + if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (Z_STRLEN_P(pzval) > 0) { + in_charset = Z_STRVAL_P(pzval); + } + } + + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "output-charset", sizeof("output-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) { + if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (Z_STRLEN_P(pzval) > 0) { + out_charset = Z_STRVAL_P(pzval); + } + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-length", sizeof("line-length") - 1)) != NULL) { + line_len = zval_get_long(pzval); + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-break-chars", sizeof("line-break-chars") - 1)) != NULL) { + if (Z_TYPE_P(pzval) != IS_STRING) { + tmp_str = zval_get_string(pzval); + lfchars = ZSTR_VAL(tmp_str); + } else { + lfchars = Z_STRVAL_P(pzval); + } + } + } + + err = _php_iconv_mime_encode(&retval, ZSTR_VAL(field_name), ZSTR_LEN(field_name), + ZSTR_VAL(field_value), ZSTR_LEN(field_value), line_len, lfchars, scheme_id, + out_charset, in_charset); + _php_iconv_show_error(err, out_charset, in_charset); + + if (err == PHP_ICONV_ERR_SUCCESS) { + if (retval.s != NULL) { + RETVAL_STR(retval.s); + } else { + RETVAL_EMPTY_STRING(); + } + } else { + smart_str_free(&retval); + RETVAL_FALSE; + } + + if (tmp_str) { + zend_string_release(tmp_str); + } +} +/* }}} */ + +/* {{{ proto string iconv_mime_decode(string encoded_string [, int mode, string charset]) + Decodes a mime header field */ +PHP_FUNCTION(iconv_mime_decode) +{ + zend_string *encoded_str; + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_long mode = 0; + + smart_str retval = {0}; + + php_iconv_err_t err; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", + &encoded_str, &mode, &charset, &charset_len) == FAILURE) { + + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_mime_decode(&retval, ZSTR_VAL(encoded_str), ZSTR_LEN(encoded_str), charset, NULL, (int)mode); + _php_iconv_show_error(err, charset, "???"); + + if (err == PHP_ICONV_ERR_SUCCESS) { + if (retval.s != NULL) { + RETVAL_STR(retval.s); + } else { + RETVAL_EMPTY_STRING(); + } + } else { + smart_str_free(&retval); + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto array iconv_mime_decode_headers(string headers [, int mode, string charset]) + Decodes multiple mime header fields */ +PHP_FUNCTION(iconv_mime_decode_headers) +{ + zend_string *encoded_str; + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_long mode = 0; + char *enc_str_tmp; + size_t enc_str_len_tmp; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", + &encoded_str, &mode, &charset, &charset_len) == FAILURE) { + + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + array_init(return_value); + + enc_str_tmp = ZSTR_VAL(encoded_str); + enc_str_len_tmp = ZSTR_LEN(encoded_str); + while (enc_str_len_tmp > 0) { + smart_str decoded_header = {0}; + char *header_name = NULL; + size_t header_name_len = 0; + char *header_value = NULL; + size_t header_value_len = 0; + char *p, *limit; + const char *next_pos; + + if (PHP_ICONV_ERR_SUCCESS != (err = _php_iconv_mime_decode(&decoded_header, enc_str_tmp, enc_str_len_tmp, charset, &next_pos, (int)mode))) { + smart_str_free(&decoded_header); + break; + } + + if (decoded_header.s == NULL) { + break; + } + + limit = ZSTR_VAL(decoded_header.s) + ZSTR_LEN(decoded_header.s); + for (p = ZSTR_VAL(decoded_header.s); p < limit; p++) { + if (*p == ':') { + *p = '\0'; + header_name = ZSTR_VAL(decoded_header.s); + header_name_len = p - ZSTR_VAL(decoded_header.s); + + while (++p < limit) { + if (*p != ' ' && *p != '\t') { + break; + } + } + + header_value = p; + header_value_len = limit - p; + + break; + } + } + + if (header_name != NULL) { + zval *elem; + + if ((elem = zend_hash_str_find(Z_ARRVAL_P(return_value), header_name, header_name_len)) != NULL) { + if (Z_TYPE_P(elem) != IS_ARRAY) { + zval new_elem; + + array_init(&new_elem); + Z_ADDREF_P(elem); + add_next_index_zval(&new_elem, elem); + + elem = zend_hash_str_update(Z_ARRVAL_P(return_value), header_name, header_name_len, &new_elem); + } + add_next_index_stringl(elem, header_value, header_value_len); + } else { + add_assoc_stringl_ex(return_value, header_name, header_name_len, header_value, header_value_len); + } + } + enc_str_len_tmp -= next_pos - enc_str_tmp; + enc_str_tmp = (char *)next_pos; + + smart_str_free(&decoded_header); + } + + if (err != PHP_ICONV_ERR_SUCCESS) { + _php_iconv_show_error(err, charset, "???"); + zval_dtor(return_value); + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv(string in_charset, string out_charset, string str) + Returns str converted to the out_charset character set */ +PHP_NAMED_FUNCTION(php_if_iconv) +{ + char *in_charset, *out_charset; + zend_string *in_buffer; + size_t in_charset_len = 0, out_charset_len = 0; + php_iconv_err_t err; + zend_string *out_buffer; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssS", + &in_charset, &in_charset_len, &out_charset, &out_charset_len, &in_buffer) == FAILURE) + return; + + if (in_charset_len >= ICONV_CSNMAXLEN || out_charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = php_iconv_string(ZSTR_VAL(in_buffer), (size_t)ZSTR_LEN(in_buffer), &out_buffer, out_charset, in_charset); + _php_iconv_show_error(err, out_charset, in_charset); + if (err == PHP_ICONV_ERR_SUCCESS && out_buffer != NULL) { + RETVAL_STR(out_buffer); + } else { + if (out_buffer != NULL) { + zend_string_free(out_buffer); + } + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool iconv_set_encoding(string type, string charset) + Sets internal encoding and output encoding for ob_iconv_handler() */ +PHP_FUNCTION(iconv_set_encoding) +{ + char *type; + zend_string *charset; + size_t type_len, retval; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &type, &type_len, &charset) == FAILURE) + return; + + if (ZSTR_LEN(charset) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if(!strcasecmp("input_encoding", type)) { + name = zend_string_init("iconv.input_encoding", sizeof("iconv.input_encoding") - 1, 0); + } else if(!strcasecmp("output_encoding", type)) { + name = zend_string_init("iconv.output_encoding", sizeof("iconv.output_encoding") - 1, 0); + } else if(!strcasecmp("internal_encoding", type)) { + name = zend_string_init("iconv.internal_encoding", sizeof("iconv.internal_encoding") - 1, 0); + } else { + RETURN_FALSE; + } + + retval = zend_alter_ini_entry(name, charset, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(name); + + if (retval == SUCCESS) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto mixed iconv_get_encoding([string type]) + Get internal encoding and output encoding for ob_iconv_handler() */ +PHP_FUNCTION(iconv_get_encoding) +{ + char *type = "all"; + size_t type_len = sizeof("all")-1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &type, &type_len) == FAILURE) + return; + + if (!strcasecmp("all", type)) { + array_init(return_value); + add_assoc_string(return_value, "input_encoding", get_input_encoding()); + add_assoc_string(return_value, "output_encoding", get_output_encoding()); + add_assoc_string(return_value, "internal_encoding", get_internal_encoding()); + } else if (!strcasecmp("input_encoding", type)) { + RETVAL_STRING(get_input_encoding()); + } else if (!strcasecmp("output_encoding", type)) { + RETVAL_STRING(get_output_encoding()); + } else if (!strcasecmp("internal_encoding", type)) { + RETVAL_STRING(get_internal_encoding()); + } else { + RETURN_FALSE; + } + +} +/* }}} */ + +/* {{{ iconv stream filter */ +typedef struct _php_iconv_stream_filter { + iconv_t cd; + int persistent; + char *to_charset; + size_t to_charset_len; + char *from_charset; + size_t from_charset_len; + char stub[128]; + size_t stub_len; +} php_iconv_stream_filter; +/* }}} iconv stream filter */ + +/* {{{ php_iconv_stream_filter_dtor */ +static void php_iconv_stream_filter_dtor(php_iconv_stream_filter *self) +{ + iconv_close(self->cd); + pefree(self->to_charset, self->persistent); + pefree(self->from_charset, self->persistent); +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_ctor() */ +static php_iconv_err_t php_iconv_stream_filter_ctor(php_iconv_stream_filter *self, + const char *to_charset, size_t to_charset_len, + const char *from_charset, size_t from_charset_len, int persistent) +{ + if (NULL == (self->to_charset = pemalloc(to_charset_len + 1, persistent))) { + return PHP_ICONV_ERR_ALLOC; + } + self->to_charset_len = to_charset_len; + if (NULL == (self->from_charset = pemalloc(from_charset_len + 1, persistent))) { + pefree(self->to_charset, persistent); + return PHP_ICONV_ERR_ALLOC; + } + self->from_charset_len = from_charset_len; + + memcpy(self->to_charset, to_charset, to_charset_len); + self->to_charset[to_charset_len] = '\0'; + memcpy(self->from_charset, from_charset, from_charset_len); + self->from_charset[from_charset_len] = '\0'; + + if ((iconv_t)-1 == (self->cd = iconv_open(self->to_charset, self->from_charset))) { + pefree(self->from_charset, persistent); + pefree(self->to_charset, persistent); + return PHP_ICONV_ERR_UNKNOWN; + } + self->persistent = persistent; + self->stub_len = 0; + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_append_bucket */ +static int php_iconv_stream_filter_append_bucket( + php_iconv_stream_filter *self, + php_stream *stream, php_stream_filter *filter, + php_stream_bucket_brigade *buckets_out, + const char *ps, size_t buf_len, size_t *consumed, + int persistent) +{ + php_stream_bucket *new_bucket; + char *out_buf = NULL; + size_t out_buf_size; + char *pd, *pt; + size_t ocnt, prev_ocnt, icnt, tcnt; + size_t initial_out_buf_size; + + if (ps == NULL) { + initial_out_buf_size = 64; + icnt = 1; + } else { + initial_out_buf_size = buf_len; + icnt = buf_len; + } + + out_buf_size = ocnt = prev_ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + + pd = out_buf; + + if (self->stub_len > 0) { + pt = self->stub; + tcnt = self->stub_len; + + while (tcnt > 0) { + if (iconv(self->cd, &pt, &tcnt, &pd, &ocnt) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EILSEQ: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; + + case EINVAL: + if (ps != NULL) { + if (icnt > 0) { + if (self->stub_len >= sizeof(self->stub)) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset); + goto out_failure; + } + self->stub[self->stub_len++] = *(ps++); + icnt--; + pt = self->stub; + tcnt = self->stub_len; + } else { + tcnt = 0; + break; + } + } + break; + + case E2BIG: { + char *new_out_buf; + size_t new_out_buf_size; + + new_out_buf_size = out_buf_size << 1; + + if (new_out_buf_size < out_buf_size) { + /* whoa! no bigger buckets are sold anywhere... */ + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + + out_buf_size = ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + pd = out_buf; + } else { + if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + return FAILURE; + } + pd = new_out_buf + (pd - out_buf); + ocnt += (new_out_buf_size - out_buf_size); + out_buf = new_out_buf; + out_buf_size = new_out_buf_size; + } + } break; + + default: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#else + if (ocnt == prev_ocnt) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#endif + } + prev_ocnt = ocnt; + } + memmove(self->stub, pt, tcnt); + self->stub_len = tcnt; + } + + while (icnt > 0) { + if ((ps == NULL ? iconv(self->cd, NULL, NULL, &pd, &ocnt): + iconv(self->cd, (char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EILSEQ: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; + + case EINVAL: + if (ps != NULL) { + if (icnt > sizeof(self->stub)) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset); + goto out_failure; + } + memcpy(self->stub, ps, icnt); + self->stub_len = icnt; + ps += icnt; + icnt = 0; + } else { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unexpected octet values", self->from_charset, self->to_charset); + goto out_failure; + } + break; + + case E2BIG: { + char *new_out_buf; + size_t new_out_buf_size; + + new_out_buf_size = out_buf_size << 1; + + if (new_out_buf_size < out_buf_size) { + /* whoa! no bigger buckets are sold anywhere... */ + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + + out_buf_size = ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + pd = out_buf; + } else { + if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + return FAILURE; + } + pd = new_out_buf + (pd - out_buf); + ocnt += (new_out_buf_size - out_buf_size); + out_buf = new_out_buf; + out_buf_size = new_out_buf_size; + } + } break; + + default: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#else + if (ocnt == prev_ocnt) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#endif + } else { + if (ps == NULL) { + break; + } + } + prev_ocnt = ocnt; + } + + if (out_buf_size > ocnt) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + php_stream_bucket_append(buckets_out, new_bucket); + } else { + pefree(out_buf, persistent); + } + *consumed += buf_len - icnt; + + return SUCCESS; + +out_failure: + pefree(out_buf, persistent); + return FAILURE; +} +/* }}} php_iconv_stream_filter_append_bucket */ + +/* {{{ php_iconv_stream_filter_do_filter */ +static php_stream_filter_status_t php_iconv_stream_filter_do_filter( + php_stream *stream, php_stream_filter *filter, + php_stream_bucket_brigade *buckets_in, + php_stream_bucket_brigade *buckets_out, + size_t *bytes_consumed, int flags) +{ + php_stream_bucket *bucket = NULL; + size_t consumed = 0; + php_iconv_stream_filter *self = (php_iconv_stream_filter *)Z_PTR(filter->abstract); + + while (buckets_in->head != NULL) { + bucket = buckets_in->head; + + php_stream_bucket_unlink(bucket); + + if (php_iconv_stream_filter_append_bucket(self, stream, filter, + buckets_out, bucket->buf, bucket->buflen, &consumed, + php_stream_is_persistent(stream)) != SUCCESS) { + goto out_failure; + } + + php_stream_bucket_delref(bucket); + } + + if (flags != PSFS_FLAG_NORMAL) { + if (php_iconv_stream_filter_append_bucket(self, stream, filter, + buckets_out, NULL, 0, &consumed, + php_stream_is_persistent(stream)) != SUCCESS) { + goto out_failure; + } + } + + if (bytes_consumed != NULL) { + *bytes_consumed = consumed; + } + + return PSFS_PASS_ON; + +out_failure: + if (bucket != NULL) { + php_stream_bucket_delref(bucket); + } + return PSFS_ERR_FATAL; +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_cleanup */ +static void php_iconv_stream_filter_cleanup(php_stream_filter *filter) +{ + php_iconv_stream_filter_dtor((php_iconv_stream_filter *)Z_PTR(filter->abstract)); + pefree(Z_PTR(filter->abstract), ((php_iconv_stream_filter *)Z_PTR(filter->abstract))->persistent); +} +/* }}} */ + +static php_stream_filter_ops php_iconv_stream_filter_ops = { + php_iconv_stream_filter_do_filter, + php_iconv_stream_filter_cleanup, + "convert.iconv.*" +}; + +/* {{{ php_iconv_stream_filter_create */ +static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, int persistent) +{ + php_stream_filter *retval = NULL; + php_iconv_stream_filter *inst; + char *from_charset = NULL, *to_charset = NULL; + size_t from_charset_len, to_charset_len; + + if ((from_charset = strchr(name, '.')) == NULL) { + return NULL; + } + ++from_charset; + if ((from_charset = strchr(from_charset, '.')) == NULL) { + return NULL; + } + ++from_charset; + if ((to_charset = strpbrk(from_charset, "/.")) == NULL) { + return NULL; + } + from_charset_len = to_charset - from_charset; + ++to_charset; + to_charset_len = strlen(to_charset); + + if (from_charset_len >= ICONV_CSNMAXLEN || to_charset_len >= ICONV_CSNMAXLEN) { + return NULL; + } + + if (NULL == (inst = pemalloc(sizeof(php_iconv_stream_filter), persistent))) { + return NULL; + } + + if (php_iconv_stream_filter_ctor(inst, to_charset, to_charset_len, from_charset, from_charset_len, persistent) != PHP_ICONV_ERR_SUCCESS) { + pefree(inst, persistent); + return NULL; + } + + if (NULL == (retval = php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent))) { + php_iconv_stream_filter_dtor(inst); + pefree(inst, persistent); + } + + return retval; +} +/* }}} */ + +/* {{{ php_iconv_stream_register_factory */ +static php_iconv_err_t php_iconv_stream_filter_register_factory(void) +{ + static php_stream_filter_factory filter_factory = { + php_iconv_stream_filter_factory_create + }; + + if (FAILURE == php_stream_filter_register_factory( + php_iconv_stream_filter_ops.label, + &filter_factory)) { + return PHP_ICONV_ERR_UNKNOWN; + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ php_iconv_stream_unregister_factory */ +static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void) +{ + if (FAILURE == php_stream_filter_unregister_factory( + php_iconv_stream_filter_ops.label)) { + return PHP_ICONV_ERR_UNKNOWN; + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ +/* }}} */ +#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/iconv/tests/bug76249.phpt b/ext/iconv/tests/bug76249.phpt new file mode 100644 index 0000000000000..4aef7f3a0c573 --- /dev/null +++ b/ext/iconv/tests/bug76249.phpt @@ -0,0 +1,16 @@ +--TEST-- +Bug #76249 (stream filter convert.iconv leads to infinite loop on invalid sequence) +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +Warning: stream_get_contents(): iconv stream filter ("ucs-2"=>"utf8//IGNORE"): invalid multibyte sequence in %sbug76249.php on line %d +æ…¢DONE From c70afabe07c32170e115b81fd32beffa1ed77b5c Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:57 +0000 Subject: [PATCH 24/46] commit patch 19113363 --- sapi/apache2handler/sapi_apache2.c | 1 + sapi/apache2handler/sapi_apache2.c.orig | 733 ++++++++++++++++++++++++ 2 files changed, 734 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 4c69fa6b4d574..0623f8cc93d57 100644 --- a/sapi/apache2handler/sapi_apache2.c +++ b/sapi/apache2handler/sapi_apache2.c @@ -689,6 +689,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..4c69fa6b4d574 --- /dev/null +++ b/sapi/apache2handler/sapi_apache2.c.orig @@ -0,0 +1,733 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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" +#ifndef NETWARE +#include "ext/standard/php_standard.h" +#else +#include "ext/standard/basic_functions.h" +#endif + +#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 and Netware 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.K + */ +#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; +#if defined(NETWARE) && defined(CLIB_STAT_PATCH) + ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime); +#else + 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); +#endif + + 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) +{ + php_struct *ctx; + + ctx = SG(server_context); + + 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_ERR, 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); + } +} + +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 + +#ifdef ZEND_SIGNALS + zend_signal_startup(); +#endif + + 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 e43af8f8729a963216d04abda34573a70f559928 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:44:58 +0000 Subject: [PATCH 25/46] commit patch 25007562 --- ext/imap/php_imap.c | 1 - ext/imap/php_imap.c.orig | 5107 ++++++++++++++++++++++++++++++++++ ext/imap/tests/bug77020.phpt | 15 + 3 files changed, 5122 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 3d60e9473dc6f..3bbb640d30f01 100644 --- a/ext/imap/php_imap.c +++ b/ext/imap/php_imap.c @@ -4111,7 +4111,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..3d60e9473dc6f --- /dev/null +++ b/ext/imap/php_imap.c.orig @@ -0,0 +1,5107 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 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) */ + long regex_flags = 0; /* Flags (not used) */ + long start_offset = 0; /* Start offset (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, regex_flags, start_offset); + + 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 int msgno, unreadmsg, deletedmsg, msize; + + 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); + + unreadmsg = 0; + deletedmsg = 0; + msize = 0; + + 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 [%ld] != 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_alloc(outlen, 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; + int 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 *)emalloc(bufferLen + 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, NULL); + 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, NULL); + 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, NULL); + 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; + 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 = (long)php_memnstr(&string[offset], "=?", 2, string + end))) { /* Is there anything encoded in the string? */ + charset_token -= (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 = (long)php_memnstr(&string[charset_token+2], "?", 1, string+end))) { /* Find token for encoding */ + encoding_token -= (long)string; + if ((end_token = (long)php_memnstr(&string[encoding_token+3], "?=", 2, string+end))) { /* Find token for end of encoded data */ + end_token -= (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) { + php_error_docref(NULL, E_ERROR, "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 = (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 = (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 bd6062bd69d530d44d6b038bddfe2bb8617e2757 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 17:45:00 +0000 Subject: [PATCH 26/46] commit patch 23337793 --- NEWS | 4 + NEWS.orig | 3 + UPGRADING | 7 + UPGRADING.orig | 809 +++++++++++++++++++++++++++++++++++ ext/imap/php_imap.c | 17 + ext/imap/php_imap.h | 1 + ext/imap/php_imap.h.orig | 235 ++++++++++ ext/imap/tests/bug77153.phpt | 24 ++ 8 files changed, 1100 insertions(+) create mode 100644 UPGRADING.orig create mode 100644 ext/imap/php_imap.h.orig create mode 100644 ext/imap/tests/bug77153.phpt diff --git a/NEWS b/NEWS index 1081c40c94202..bea7cf120e25c 100644 --- a/NEWS +++ b/NEWS @@ -663,6 +663,10 @@ PHP NEWS . Corrected oci8 hash destructors to prevent segfaults, and a few other fixes. (Cameron Porter) +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + - ODBC: . Fixed bug #69975 (PHP segfaults when accessing nvarchar(max) defined columns). (cmb) diff --git a/NEWS.orig b/NEWS.orig index 0993dd011cc00..1081c40c94202 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -132,6 +132,9 @@ PHP NEWS . Implemented FR #55651 (Option to ignore the returned FTP PASV address). (abrender at elitehosts dot com) +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + - Mbstring: . Fixed bug #71066 (mb_send_mail: Program terminated with signal SIGSEGV, Segmentation fault). (Laruence) diff --git a/UPGRADING b/UPGRADING index 836e35253dbef..8c8d1dfcee1de 100644 --- a/UPGRADING +++ b/UPGRADING @@ -15,6 +15,13 @@ 13. Other Changes +- 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. + ======================================== 1. Backward Incompatible Changes ======================================== diff --git a/UPGRADING.orig b/UPGRADING.orig new file mode 100644 index 0000000000000..836e35253dbef --- /dev/null +++ b/UPGRADING.orig @@ -0,0 +1,809 @@ +PHP 7.0 UPGRADE NOTES + +1. Backward Incompatible Changes +2. New Features +3. Changes in SAPI modules +4. Deprecated Functionality +5. Changed Functions +6. New Functions +7. New Classes and Interfaces +8. Removed Extensions and SAPIs +9. Other Changes to Extensions +10. New Global Constants +11. Changes to INI File Handling +12. Windows Support +13. Other Changes + + +======================================== +1. Backward Incompatible Changes +======================================== + +Language changes +================ + +Changes to variable handling +---------------------------- + +* Indirect variable, property and method references are now interpreted with + left-to-right semantics. Some examples: + + $$foo['bar']['baz'] // interpreted as ($$foo)['bar']['baz'] + $foo->$bar['baz'] // interpreted as ($foo->$bar)['baz'] + $foo->$bar['baz']() // interpreted as ($foo->$bar)['baz']() + Foo::$bar['baz']() // interpreted as (Foo::$bar)['baz']() + + To restore the previous behavior add explicit curly braces: + + ${$foo['bar']['baz']} + $foo->{$bar['baz']} + $foo->{$bar['baz']}() + Foo::{$bar['baz']}() + +* The global keyword now only accepts simple variables. Instead of + + global $$foo->bar; + + it is now required to write the following: + + global ${$foo->bar}; + +* Parentheses around variables or function calls no longer have any influence + on behavior. For example the following code, where the result of a function + call is passed to a by-reference function + + function getArray() { return [1, 2, 3]; } + + $last = array_pop(getArray()); + // Strict Standards: Only variables should be passed by reference + $last = array_pop((getArray())); + // Strict Standards: Only variables should be passed by reference + + will now throw a strict standards error regardless of whether parentheses + are used. Previously no notice was generated in the second case. + +* Array elements or object properties that are automatically created during + by-reference assignments will now result in a different order. For example + + $array = []; + $array["a"] =& $array["b"]; + $array["b"] = 1; + var_dump($array); + + now results in the array ["a" => 1, "b" => 1], while previously the result + was ["b" => 1, "a" => 1]; + +Relevant RFCs: +* https://wiki.php.net/rfc/uniform_variable_syntax +* https://wiki.php.net/rfc/abstract_syntax_tree + +Changes to list() +----------------- + +* list() will no longer assign variables in reverse order. For example + + list($array[], $array[], $array[]) = [1, 2, 3]; + var_dump($array); + + will now result in $array == [1, 2, 3] rather than [3, 2, 1]. Note that only + the **order** of the assignments changed, but the assigned values stay the + same. E.g. a normal usage like + + list($a, $b, $c) = [1, 2, 3]; + // $a = 1; $b = 2; $c = 3; + + will retain its current behavior. + +* Empty list() assignments are no longer allowed. As such all of the following + are invalid: + + list() = $a; + list(,,) = $a; + list($x, list(), $y) = $a; + +* list() no longer supports unpacking strings (while previously this was only + supported in some cases). The code + + $string = "xy"; + list($x, $y) = $string; + + will now result in $x == null and $y == null (without notices) instead of + $x == "x" and $y == "y". Furthermore list() is now always guaranteed to + work with objects implementing ArrayAccess, e.g. + + list($a, $b) = (object) new ArrayObject([0, 1]); + + will now result in $a == 0 and $b == 1. Previously both $a and $b were null. + +Relevant RFCs: +* https://wiki.php.net/rfc/abstract_syntax_tree#changes_to_list +* https://wiki.php.net/rfc/fix_list_behavior_inconsistency + +Changes to foreach +------------------ + +* Iteration with foreach() no longer has any effect on the internal array + pointer, which can be accessed through the current()/next()/etc family of + functions. For example + + $array = [0, 1, 2]; + foreach ($array as &$val) { + var_dump(current($array)); + } + + will now print the value int(0) three times. Previously the output was int(1), + int(2) and bool(false). + +* When iterating arrays by-value, foreach will now always operate on a copy of + the array, as such changes to the array during iteration will not influence + iteration behavior. For example + + $array = [0, 1, 2]; + $ref =& $array; // Necessary to trigger the old behavior + foreach ($array as $val) { + var_dump($val); + unset($array[1]); + } + + will now print all three elements (0 1 2), while previously the second element + 1 was skipped (0 2). + +* When iterating arrays by-reference, modifications to the array will continue + to influence the iteration. However PHP will now do a better job of + maintaining a correct position in a number of cases. E.g. appending to an + array during by-reference iteration + + $array = [0]; + foreach ($array as &$val) { + var_dump($val); + $array[1] = 1; + } + + will now iterate over the appended element as well. As such the output of this + example will now be "int(0) int(1)", while previously it was only "int(0)". + +* Iteration of plain (non-Traversable) objects by-value or by-reference will + behave like by-reference iteration of arrays. This matches the previous + behavior apart from the more accurate position management mentioned in the + previous point. + +* Iteration of Traversable objects remains unchanged. + +Relevant RFC: https://wiki.php.net/rfc/php7_foreach + +Changes to parameter handling +----------------------------- + +* It is no longer possible to define two function parameters with the same name. + For example, the following method will trigger a compile-time error: + + public function foo($a, $b, $unused, $unused) { + // ... + } + + Code like this should be changed to use distinct parameter names, for example: + + public function foo($a, $b, $unused1, $unused2) { + // ... + } + +* The func_get_arg() and func_get_args() functions will no longer return the + original value that was passed to a parameter and will instead provide the + current value (which might have been modified). For example + + function foo($x) { + $x++; + var_dump(func_get_arg(0)); + } + foo(1); + + will now print "2" instead of "1". This code should be changed to either + perform modifications only after calling func_get_arg(s) + + function foo($x) { + var_dump(func_get_arg(0)); + $x++; + } + + or avoid modifying the parameters altogether: + + function foo($x) { + $newX = $x + 1; + var_dump(func_get_arg(0)); + } + +* Similarly exception backtraces will no longer display the original value that + was passed to a function and show the modified value instead. For example + + function foo($x) { + $x = 42; + throw new Exception; + } + foo("string"); + + will now result in the stack trace + + Stack trace: + #0 file.php(4): foo(42) + #1 {main} + + while previously it was: + + Stack trace: + #0 file.php(4): foo('string') + #1 {main} + + While this should not impact runtime behavior of your code, it is worthwhile + to be aware of this difference for debugging purposes. + + The same limitation also applies to debug_backtrace() and other functions + inspecting function arguments. + +Relevant RFC: https://wiki.php.net/phpng + +Changes to integer handling +--------------------------- + +* Invalid octal literals (containing digits larger than 7) now produce compile + errors. For example, the following is no longer valid: + + $i = 0781; // 8 is not a valid octal digit! + + Previously the invalid digits (and any following valid digits) were simply + ignored. As such $i previously held the value 7, because the last two digits + were silently discarded. + +* Bitwise shifts by negative numbers will now throw an ArithmeticError: + + var_dump(1 >> -1); + // ArithmeticError: Bit shift by negative number + +* Left bitwise shifts by a number of bits beyond the bit width of an integer + will always result in 0: + + var_dump(1 << 64); // int(0) + + Previously the behavior of this code was dependent on the used CPU + architecture. For example on x86 (including x86-64) the result was int(1), + because the shift operand was wrapped. + +* Similarly right bitwise shifts by a number of bits beyond the bit width of an + integer will always result in 0 or -1 (depending on sign): + + var_dump(1 >> 64); // int(0) + var_dump(-1 >> 64); // int(-1) + +Relevant RFC: https://wiki.php.net/rfc/integer_semantics + +Changes to string handling +-------------------------- + +* Strings that contain hexadecimal numbers are no longer considered to be + numeric and don't receive special treatment anymore. Some examples of the + new behavior: + + var_dump("0x123" == "291"); // bool(false) (previously true) + var_dump(is_numeric("0x123")); // bool(false) (previously true) + var_dump("0xe" + "0x1"); // int(0) (previously 16) + + var_dump(substr("foo", "0x1")); // string(3) "foo" (previously "oo") + // Notice: A non well formed numeric value encountered + + filter_var() can be used to check if a string contains a hexadecimal number + or convert such a string into an integer: + + $str = "0xffff"; + $int = filter_var($str, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX); + if (false === $int) { + throw new Exception("Invalid integer!"); + } + var_dump($int); // int(65535) + +* Due to the addition of the Unicode Codepoint Escape Syntax for double-quoted + strings and heredocs, "\u{" followed by an invalid sequence will now result in + an error: + + $str = "\u{xyz}"; // Fatal error: Invalid UTF-8 codepoint escape sequence + + To avoid this the leading backslash should be escaped: + + $str = "\\u{xyz}"; // Works fine + + However, "\u" without a following { is unaffected. As such the following code + won't error and will work the same as before: + + $str = "\u202e"; // Works fine + +Relevant RFCs: +* https://wiki.php.net/rfc/remove_hex_support_in_numeric_strings +* https://wiki.php.net/rfc/unicode_escape + +Changes to error handling +------------------------- + +* There are now two exception classes: Exception and Error. Both classes + implement a new interface Throwable. Type hints in exception handling code + may need to be changed to account for this. + +* Some fatal errors and recoverable fatal errors now throw an Error instead. + As Error is a separate class from Exception, these exceptions will not be + caught by existing try/catch blocks. + + For the recoverable fatal errors which have been converted into an exception, + it is no longer possible to silently ignore the error from an error handler. + In particular, it is no longer possible to ignore type hint failures. + +* Parser errors now generate a ParseError that extends Error. Error + handling for eval()s on potentially invalid code should be changed to catch + ParseError in addition to the previous return value / error_get_last() + based handling. + +* Constructors of internal classes will now always throw an exception on + failure. Previously some constructors returned NULL or an unusable object. + +* The error level of some E_STRICT notices has been changed. + +Relevant RFCs: +* https://wiki.php.net/rfc/engine_exceptions_for_php7 +* https://wiki.php.net/rfc/throwable-interface +* https://wiki.php.net/rfc/internal_constructor_behaviour +* https://wiki.php.net/rfc/reclassify_e_strict + +Other language changes +---------------------- + +* Removed support for static calls to non-static methods from an incompatible + $this context. In this case $this will not be defined, but the call will be + allowed with a deprecation notice. An example: + + class A { + public function test() { var_dump($this); } + } + + // Note: Does NOT extend A + class B { + public function callNonStaticMethodOfA() { A::test(); } + } + + (new B)->callNonStaticMethodOfA(); + + // Deprecated: Non-static method A::test() should not be called statically + // Notice: Undefined variable $this + NULL + + Note that this only applies to calls from an incompatible context. If class B + extended from A the call would be allowed without any notices. + +* It is no longer possible to use the following class, interface and trait names + (case-insensitive): + + bool + int + float + string + null + false + true + + This applies to class/interface/trait declarations, class_alias() and use + statements. + + Furthermore the following class, interface and trait names are now reserved + for future use, but do not yet throw an error when used: + + resource + object + mixed + numeric + +* The yield language construct no longer requires parentheses when used in an + expression context. It is now a right-associative operator with precedence + between the "print" and "=>" operators. This can result in different behavior + in some cases, for example: + + echo yield -1; + // Was previously interpreted as + echo (yield) - 1; + // And is now interpreted as + echo yield (-1); + + yield $foo or die; + // Was previously interpreted as + yield ($foo or die); + // And is now interpreted as + (yield $foo) or die; + + Such cases can always be resolved by adding additional parentheses. + + . Removed ASP (<%) and script (