From a00863eb5bf915515131a817b9ebe621a8ce195d Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:15:57 +0000 Subject: [PATCH 01/43] commit patch 23612552 --- ext/intl/locale/locale_methods.c | 18 + ext/intl/locale/locale_methods.c.orig | 1623 +++++++++++++++++++++++++ ext/intl/tests/bug72533.phpt | 30 + 3 files changed, 1671 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 857c14a005917..32a70d2f7f0b9 100644 --- a/ext/intl/locale/locale_methods.c +++ b/ext/intl/locale/locale_methods.c @@ -1598,6 +1598,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..857c14a005917 --- /dev/null +++ b/ext/intl/locale/locale_methods.c.orig @@ -0,0 +1,1623 @@ +/* + +----------------------------------------------------------------------+ + | 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; + buflen++; /* add space for \0 */ + 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) { + if(fallback_loc_str) { + loc_range = ZSTR_VAL(fallback_loc_str); + } else { + 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 97a3c668abb24495f9d806513b71f6bbd2806c86 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:00 +0000 Subject: [PATCH 02/43] commit patch 22726065 --- ext/standard/tests/strings/bug72663.phpt | 26 + ext/standard/tests/strings/bug72663_2.phpt | 17 + ext/standard/var_unserializer.c | 29 +- ext/standard/var_unserializer.c.orig | 1339 ++++++++++++++++++++ 4 files changed, 1397 insertions(+), 14 deletions(-) create mode 100644 ext/standard/tests/strings/bug72663.phpt create mode 100644 ext/standard/tests/strings/bug72663_2.phpt create mode 100644 ext/standard/var_unserializer.c.orig diff --git a/ext/standard/tests/strings/bug72663.phpt b/ext/standard/tests/strings/bug72663.phpt new file mode 100644 index 0000000000000..e61f939d4dbef --- /dev/null +++ b/ext/standard/tests/strings/bug72663.phpt @@ -0,0 +1,26 @@ +--TEST-- +Bug #72663: Create an Unexpected Object and Don't Invoke __wakeup() in Deserialization +--FILE-- +data); + } + function unserialize($data) { + $this->data = unserialize($data); + } +} + +$inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."\0".'*'."\0".'file";R:4;}'; +$exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}'; + +$data = unserialize($exploit); +echo $data[1]; +?> +DONE +--EXPECTF-- +Notice: unserialize(): Unexpected end of serialized data in %sbug72663.php on line %d + +Notice: unserialize(): Error at offset 46 of 47 bytes in %sbug72663.php on line %d +DONE \ No newline at end of file diff --git a/ext/standard/tests/strings/bug72663_2.phpt b/ext/standard/tests/strings/bug72663_2.phpt new file mode 100644 index 0000000000000..ac605e9fd2647 --- /dev/null +++ b/ext/standard/tests/strings/bug72663_2.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #72663: Create an Unexpected Object and Don't Invoke __wakeup() in Deserialization +--FILE-- + +DONE +--EXPECTF-- +Notice: session_decode(): Unexpected end of serialized data in %sbug72663_2.php on line %d +array(0) { +} +DONE \ No newline at end of file diff --git a/ext/standard/var_unserializer.c b/ext/standard/var_unserializer.c index 3fc074dd6ae2c..f464c0b63a336 100644 --- a/ext/standard/var_unserializer.c +++ b/ext/standard/var_unserializer.c @@ -1,4 +1,4 @@ -/* Generated by re2c 0.13.5 */ +/* Generated by re2c 0.13.7.5 */ #line 1 "ext/standard/var_unserializer.re" /* +----------------------------------------------------------------------+ @@ -651,7 +651,8 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) if (yybm[0+yych] & 128) { goto yy20; } - if (yych != ':') goto yy18; + if (yych <= '/') goto yy18; + if (yych >= ';') goto yy18; yych = *++YYCURSOR; if (yych != '"') goto yy18; ++YYCURSOR; @@ -800,7 +801,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return object_common2(UNSERIALIZE_PASSTHRU, elements); } -#line 804 "ext/standard/var_unserializer.c" +#line 805 "ext/standard/var_unserializer.c" yy25: yych = *++YYCURSOR; if (yych <= ',') { @@ -832,7 +833,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return object_common2(UNSERIALIZE_PASSTHRU, object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR)); } -#line 836 "ext/standard/var_unserializer.c" +#line 837 "ext/standard/var_unserializer.c" yy32: yych = *++YYCURSOR; if (yych == '+') goto yy33; @@ -877,7 +878,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return finish_nested_data(UNSERIALIZE_PASSTHRU); } -#line 881 "ext/standard/var_unserializer.c" +#line 882 "ext/standard/var_unserializer.c" yy39: yych = *++YYCURSOR; if (yych == '+') goto yy40; @@ -932,7 +933,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_STR(rval, str); return 1; } -#line 936 "ext/standard/var_unserializer.c" +#line 937 "ext/standard/var_unserializer.c" yy46: yych = *++YYCURSOR; if (yych == '+') goto yy47; @@ -985,7 +986,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_STRINGL(rval, str, len); return 1; } -#line 989 "ext/standard/var_unserializer.c" +#line 990 "ext/standard/var_unserializer.c" yy53: yych = *++YYCURSOR; if (yych <= '/') { @@ -1082,7 +1083,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL)); return 1; } -#line 1086 "ext/standard/var_unserializer.c" +#line 1087 "ext/standard/var_unserializer.c" yy65: yych = *++YYCURSOR; if (yych <= ',') { @@ -1157,7 +1158,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return 1; } -#line 1161 "ext/standard/var_unserializer.c" +#line 1162 "ext/standard/var_unserializer.c" yy76: yych = *++YYCURSOR; if (yych == 'N') goto yy73; @@ -1210,7 +1211,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_LONG(rval, parse_iv(start + 2)); return 1; } -#line 1214 "ext/standard/var_unserializer.c" +#line 1215 "ext/standard/var_unserializer.c" yy83: yych = *++YYCURSOR; if (yych <= '/') goto yy18; @@ -1224,7 +1225,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_BOOL(rval, parse_iv(start + 2)); return 1; } -#line 1228 "ext/standard/var_unserializer.c" +#line 1229 "ext/standard/var_unserializer.c" yy87: ++YYCURSOR; #line 573 "ext/standard/var_unserializer.re" @@ -1233,7 +1234,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) ZVAL_NULL(rval); return 1; } -#line 1237 "ext/standard/var_unserializer.c" +#line 1238 "ext/standard/var_unserializer.c" yy89: yych = *++YYCURSOR; if (yych <= ',') { @@ -1281,7 +1282,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return 1; } -#line 1285 "ext/standard/var_unserializer.c" +#line 1286 "ext/standard/var_unserializer.c" yy95: yych = *++YYCURSOR; if (yych <= ',') { @@ -1330,7 +1331,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) return 1; } -#line 1334 "ext/standard/var_unserializer.c" +#line 1335 "ext/standard/var_unserializer.c" } #line 886 "ext/standard/var_unserializer.re" diff --git a/ext/standard/var_unserializer.c.orig b/ext/standard/var_unserializer.c.orig new file mode 100644 index 0000000000000..3fc074dd6ae2c --- /dev/null +++ b/ext/standard/var_unserializer.c.orig @@ -0,0 +1,1339 @@ +/* Generated by re2c 0.13.5 */ +#line 1 "ext/standard/var_unserializer.re" +/* + +----------------------------------------------------------------------+ + | 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: Sascha Schumann | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "ext/standard/php_var.h" +#include "php_incomplete_class.h" + +/* {{{ reference-handling for unserializer: var_* */ +#define VAR_ENTRIES_MAX 1024 +#define VAR_ENTRIES_DBG 0 + +typedef struct { + zval *data[VAR_ENTRIES_MAX]; + zend_long used_slots; + void *next; +} var_entries; + +typedef struct { + zval data[VAR_ENTRIES_MAX]; + zend_long used_slots; + void *next; +} var_dtor_entries; + +static inline void var_push(php_unserialize_data_t *var_hashx, zval *rval) +{ + var_entries *var_hash = (*var_hashx)->last; +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_push(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_P(rval)); +#endif + + if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { + var_hash = emalloc(sizeof(var_entries)); + var_hash->used_slots = 0; + var_hash->next = 0; + + if (!(*var_hashx)->first) { + (*var_hashx)->first = var_hash; + } else { + ((var_entries *) (*var_hashx)->last)->next = var_hash; + } + + (*var_hashx)->last = var_hash; + } + + var_hash->data[var_hash->used_slots++] = rval; +} + +PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval) +{ + zval *tmp_var = var_tmp_var(var_hashx); + if (!tmp_var) { + return; + } + ZVAL_COPY(tmp_var, rval); +} + +PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx) +{ + var_dtor_entries *var_hash; + + if (!var_hashx || !*var_hashx) { + return NULL; + } + + var_hash = (*var_hashx)->last_dtor; + if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { + var_hash = emalloc(sizeof(var_dtor_entries)); + var_hash->used_slots = 0; + var_hash->next = 0; + + if (!(*var_hashx)->first_dtor) { + (*var_hashx)->first_dtor = var_hash; + } else { + ((var_dtor_entries *) (*var_hashx)->last_dtor)->next = var_hash; + } + + (*var_hashx)->last_dtor = var_hash; + } + ZVAL_UNDEF(&var_hash->data[var_hash->used_slots]); + return &var_hash->data[var_hash->used_slots++]; +} + +PHPAPI void var_replace(php_unserialize_data_t *var_hashx, zval *ozval, zval *nzval) +{ + zend_long i; + var_entries *var_hash = (*var_hashx)->first; +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_replace(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_P(nzval)); +#endif + + while (var_hash) { + for (i = 0; i < var_hash->used_slots; i++) { + if (var_hash->data[i] == ozval) { + var_hash->data[i] = nzval; + /* do not break here */ + } + } + var_hash = var_hash->next; + } +} + +static zval *var_access(php_unserialize_data_t *var_hashx, zend_long id) +{ + var_entries *var_hash = (*var_hashx)->first; +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_access(%ld): %ld\n", var_hash?var_hash->used_slots:-1L, id); +#endif + + while (id >= VAR_ENTRIES_MAX && var_hash && var_hash->used_slots == VAR_ENTRIES_MAX) { + var_hash = var_hash->next; + id -= VAR_ENTRIES_MAX; + } + + if (!var_hash) return NULL; + + if (id < 0 || id >= var_hash->used_slots) return NULL; + + return var_hash->data[id]; +} + +PHPAPI void var_destroy(php_unserialize_data_t *var_hashx) +{ + void *next; + zend_long i; + var_entries *var_hash = (*var_hashx)->first; + var_dtor_entries *var_dtor_hash = (*var_hashx)->first_dtor; +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_destroy(%ld)\n", var_hash?var_hash->used_slots:-1L); +#endif + + while (var_hash) { + next = var_hash->next; + efree_size(var_hash, sizeof(var_entries)); + var_hash = next; + } + + while (var_dtor_hash) { + for (i = 0; i < var_dtor_hash->used_slots; i++) { +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_dtor_hash->data[i], Z_REFCOUNT_P(var_dtor_hash->data[i])); +#endif + zval_ptr_dtor(&var_dtor_hash->data[i]); + } + next = var_dtor_hash->next; + efree_size(var_dtor_hash, sizeof(var_dtor_entries)); + var_dtor_hash = next; + } +} + +/* }}} */ + +static zend_string *unserialize_str(const unsigned char **p, size_t len, size_t maxlen) +{ + size_t i, j; + zend_string *str = zend_string_safe_alloc(1, len, 0, 0); + unsigned char *end = *(unsigned char **)p+maxlen; + + if (end < *p) { + zend_string_free(str); + return NULL; + } + + for (i = 0; i < len; i++) { + if (*p >= end) { + zend_string_free(str); + return NULL; + } + if (**p != '\\') { + ZSTR_VAL(str)[i] = (char)**p; + } else { + unsigned char ch = 0; + + for (j = 0; j < 2; j++) { + (*p)++; + if (**p >= '0' && **p <= '9') { + ch = (ch << 4) + (**p -'0'); + } else if (**p >= 'a' && **p <= 'f') { + ch = (ch << 4) + (**p -'a'+10); + } else if (**p >= 'A' && **p <= 'F') { + ch = (ch << 4) + (**p -'A'+10); + } else { + zend_string_free(str); + return NULL; + } + } + ZSTR_VAL(str)[i] = (char)ch; + } + (*p)++; + } + ZSTR_VAL(str)[i] = 0; + ZSTR_LEN(str) = i; + return str; +} + +static inline int unserialize_allowed_class(zend_string *class_name, HashTable *classes) +{ + zend_string *lcname; + int res; + ALLOCA_FLAG(use_heap) + + if(classes == NULL) { + return 1; + } + if(!zend_hash_num_elements(classes)) { + return 0; + } + + ZSTR_ALLOCA_ALLOC(lcname, ZSTR_LEN(class_name), use_heap); + zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(class_name), ZSTR_LEN(class_name)); + res = zend_hash_exists(classes, lcname); + ZSTR_ALLOCA_FREE(lcname, use_heap); + return res; +} + +#define YYFILL(n) do { } while (0) +#define YYCTYPE unsigned char +#define YYCURSOR cursor +#define YYLIMIT limit +#define YYMARKER marker + + +#line 246 "ext/standard/var_unserializer.re" + + + + +static inline zend_long parse_iv2(const unsigned char *p, const unsigned char **q) +{ + char cursor; + zend_long result = 0; + int neg = 0; + + switch (*p) { + case '-': + neg++; + /* fall-through */ + case '+': + p++; + } + + while (1) { + cursor = (char)*p; + if (cursor >= '0' && cursor <= '9') { + result = result * 10 + (size_t)(cursor - (unsigned char)'0'); + } else { + break; + } + p++; + } + if (q) *q = p; + if (neg) return -result; + return result; +} + +static inline zend_long parse_iv(const unsigned char *p) +{ + return parse_iv2(p, NULL); +} + +/* no need to check for length - re2c already did */ +static inline size_t parse_uiv(const unsigned char *p) +{ + unsigned char cursor; + size_t result = 0; + + if (*p == '+') { + p++; + } + + while (1) { + cursor = *p; + if (cursor >= '0' && cursor <= '9') { + result = result * 10 + (size_t)(cursor - (unsigned char)'0'); + } else { + break; + } + p++; + } + return result; +} + +#define UNSERIALIZE_PARAMETER zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash, HashTable *classes +#define UNSERIALIZE_PASSTHRU rval, p, max, var_hash, classes + +static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, int objprops) +{ + while (elements-- > 0) { + zval key, *data, d, *old_data; + zend_ulong idx; + + ZVAL_UNDEF(&key); + + if (!php_var_unserialize_ex(&key, p, max, NULL, classes)) { + zval_dtor(&key); + return 0; + } + + data = NULL; + ZVAL_UNDEF(&d); + + if (!objprops) { + if (Z_TYPE(key) == IS_LONG) { + idx = Z_LVAL(key); +numeric_key: + if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) { + //??? update hash + var_push_dtor(var_hash, old_data); + data = zend_hash_index_update(ht, idx, &d); + } else { + data = zend_hash_index_add_new(ht, idx, &d); + } + } else if (Z_TYPE(key) == IS_STRING) { + if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) { + goto numeric_key; + } + if (UNEXPECTED((old_data = zend_hash_find(ht, Z_STR(key))) != NULL)) { + //??? update hash + var_push_dtor(var_hash, old_data); + data = zend_hash_update(ht, Z_STR(key), &d); + } else { + data = zend_hash_add_new(ht, Z_STR(key), &d); + } + } else { + zval_dtor(&key); + return 0; + } + } else { + if (EXPECTED(Z_TYPE(key) == IS_STRING)) { +string_key: + if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) { + if (Z_TYPE_P(old_data) == IS_INDIRECT) { + old_data = Z_INDIRECT_P(old_data); + } + var_push_dtor(var_hash, old_data); + data = zend_hash_update_ind(ht, Z_STR(key), &d); + } else { + data = zend_hash_add_new(ht, Z_STR(key), &d); + } + } else if (Z_TYPE(key) == IS_LONG) { + /* object properties should include no integers */ + convert_to_string(&key); + goto string_key; + } else { + zval_dtor(&key); + return 0; + } + } + + if (!php_var_unserialize_ex(data, p, max, var_hash, classes)) { + zval_dtor(&key); + return 0; + } + + if (UNEXPECTED(Z_ISUNDEF_P(data))) { + if (Z_TYPE(key) == IS_LONG) { + zend_hash_index_del(ht, Z_LVAL(key)); + } else { + zend_hash_del_ind(ht, Z_STR(key)); + } + } else { + var_push_dtor(var_hash, data); + } + + zval_dtor(&key); + + if (elements && *(*p-1) != ';' && *(*p-1) != '}') { + (*p)--; + return 0; + } + } + + return 1; +} + +static inline int finish_nested_data(UNSERIALIZE_PARAMETER) +{ + if (*((*p)++) == '}') + return 1; + +#if SOMETHING_NEW_MIGHT_LEAD_TO_CRASH_ENABLE_IF_YOU_ARE_BRAVE + zval_ptr_dtor(rval); +#endif + return 0; +} + +static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) +{ + zend_long datalen; + + datalen = parse_iv2((*p) + 2, p); + + (*p) += 2; + + if (datalen < 0 || (max - (*p)) <= datalen) { + zend_error(E_WARNING, "Insufficient data for unserializing - %pd required, %pd present", datalen, (zend_long)(max - (*p))); + return 0; + } + + if (ce->unserialize == NULL) { + zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name)); + object_init_ex(rval, ce); + } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) { + return 0; + } + + (*p) += datalen; + + return finish_nested_data(UNSERIALIZE_PASSTHRU); +} + +static inline zend_long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce) +{ + zend_long elements; + + elements = parse_iv2((*p) + 2, p); + + (*p) += 2; + + if (ce->serialize == NULL) { + object_init_ex(rval, ce); + } else { + /* If this class implements Serializable, it should not land here but in object_custom(). The passed string + obviously doesn't descend from the regular serializer. */ + zend_error(E_WARNING, "Erroneous data format for unserializing '%s'", ZSTR_VAL(ce->name)); + return 0; + } + + return elements; +} + +#ifdef PHP_WIN32 +# pragma optimize("", off) +#endif +static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements) +{ + zval retval; + zval fname; + HashTable *ht; + + if (Z_TYPE_P(rval) != IS_OBJECT) { + return 0; + } + + ht = Z_OBJPROP_P(rval); + zend_hash_extend(ht, zend_hash_num_elements(ht) + elements, (ht->u.flags & HASH_FLAG_PACKED)); + if (!process_nested_data(UNSERIALIZE_PASSTHRU, ht, elements, 1)) { + return 0; + } + + ZVAL_DEREF(rval); + if (Z_OBJCE_P(rval) != PHP_IC_ENTRY && + zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) { + ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1); + BG(serialize_lock)++; + call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL); + BG(serialize_lock)--; + zval_dtor(&fname); + zval_dtor(&retval); + } + + if (EG(exception)) { + return 0; + } + + return finish_nested_data(UNSERIALIZE_PASSTHRU); + +} +#ifdef PHP_WIN32 +# pragma optimize("", on) +#endif + +PHPAPI int php_var_unserialize(zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash) +{ + HashTable *classes = NULL; + return php_var_unserialize_ex(UNSERIALIZE_PASSTHRU); +} + + +PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) +{ + const unsigned char *cursor, *limit, *marker, *start; + zval *rval_ref; + + limit = max; + cursor = *p; + + if (YYCURSOR >= YYLIMIT) { + return 0; + } + + if (var_hash && (*p)[0] != 'R') { + var_push(var_hash, rval); + } + + start = cursor; + + +#line 518 "ext/standard/var_unserializer.c" +{ + YYCTYPE yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + if ((YYLIMIT - YYCURSOR) < 7) YYFILL(7); + yych = *YYCURSOR; + switch (yych) { + case 'C': + case 'O': goto yy13; + case 'N': goto yy5; + case 'R': goto yy2; + case 'S': goto yy10; + case 'a': goto yy11; + case 'b': goto yy6; + case 'd': goto yy8; + case 'i': goto yy7; + case 'o': goto yy12; + case 'r': goto yy4; + case 's': goto yy9; + case '}': goto yy14; + default: goto yy16; + } +yy2: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy95; +yy3: +#line 884 "ext/standard/var_unserializer.re" + { return 0; } +#line 580 "ext/standard/var_unserializer.c" +yy4: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy89; + goto yy3; +yy5: + yych = *++YYCURSOR; + if (yych == ';') goto yy87; + goto yy3; +yy6: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy83; + goto yy3; +yy7: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy77; + goto yy3; +yy8: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy53; + goto yy3; +yy9: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy46; + goto yy3; +yy10: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy39; + goto yy3; +yy11: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy32; + goto yy3; +yy12: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy25; + goto yy3; +yy13: + yych = *(YYMARKER = ++YYCURSOR); + if (yych == ':') goto yy17; + goto yy3; +yy14: + ++YYCURSOR; +#line 878 "ext/standard/var_unserializer.re" + { + /* this is the case where we have less data than planned */ + php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data"); + return 0; /* not sure if it should be 0 or 1 here? */ +} +#line 629 "ext/standard/var_unserializer.c" +yy16: + yych = *++YYCURSOR; + goto yy3; +yy17: + yych = *++YYCURSOR; + if (yybm[0+yych] & 128) { + goto yy20; + } + if (yych == '+') goto yy19; +yy18: + YYCURSOR = YYMARKER; + goto yy3; +yy19: + yych = *++YYCURSOR; + if (yybm[0+yych] & 128) { + goto yy20; + } + goto yy18; +yy20: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yybm[0+yych] & 128) { + goto yy20; + } + if (yych != ':') goto yy18; + yych = *++YYCURSOR; + if (yych != '"') goto yy18; + ++YYCURSOR; +#line 733 "ext/standard/var_unserializer.re" + { + size_t len, len2, len3, maxlen; + zend_long elements; + char *str; + zend_string *class_name; + zend_class_entry *ce; + int incomplete_class = 0; + + int custom_object = 0; + + zval user_func; + zval retval; + zval args[1]; + + if (!var_hash) return 0; + if (*start == 'C') { + custom_object = 1; + } + + len2 = len = parse_uiv(start + 2); + maxlen = max - YYCURSOR; + if (maxlen < len || len == 0) { + *p = start + 2; + return 0; + } + + str = (char*)YYCURSOR; + + YYCURSOR += len; + + if (*(YYCURSOR) != '"') { + *p = YYCURSOR; + return 0; + } + if (*(YYCURSOR+1) != ':') { + *p = YYCURSOR+1; + return 0; + } + + len3 = strspn(str, "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\\"); + if (len3 != len) + { + *p = YYCURSOR + len3 - len; + return 0; + } + + class_name = zend_string_init(str, len, 0); + + do { + if(!unserialize_allowed_class(class_name, classes)) { + incomplete_class = 1; + ce = PHP_IC_ENTRY; + break; + } + + /* Try to find class directly */ + BG(serialize_lock)++; + ce = zend_lookup_class(class_name); + if (ce) { + BG(serialize_lock)--; + if (EG(exception)) { + zend_string_release(class_name); + return 0; + } + break; + } + BG(serialize_lock)--; + + if (EG(exception)) { + zend_string_release(class_name); + return 0; + } + + /* Check for unserialize callback */ + if ((PG(unserialize_callback_func) == NULL) || (PG(unserialize_callback_func)[0] == '\0')) { + incomplete_class = 1; + ce = PHP_IC_ENTRY; + break; + } + + /* Call unserialize callback */ + ZVAL_STRING(&user_func, PG(unserialize_callback_func)); + + ZVAL_STR_COPY(&args[0], class_name); + BG(serialize_lock)++; + if (call_user_function_ex(CG(function_table), NULL, &user_func, &retval, 1, args, 0, NULL) != SUCCESS) { + BG(serialize_lock)--; + if (EG(exception)) { + zend_string_release(class_name); + zval_ptr_dtor(&user_func); + zval_ptr_dtor(&args[0]); + return 0; + } + php_error_docref(NULL, E_WARNING, "defined (%s) but not found", Z_STRVAL(user_func)); + incomplete_class = 1; + ce = PHP_IC_ENTRY; + zval_ptr_dtor(&user_func); + zval_ptr_dtor(&args[0]); + break; + } + BG(serialize_lock)--; + zval_ptr_dtor(&retval); + if (EG(exception)) { + zend_string_release(class_name); + zval_ptr_dtor(&user_func); + zval_ptr_dtor(&args[0]); + return 0; + } + + /* The callback function may have defined the class */ + if ((ce = zend_lookup_class(class_name)) == NULL) { + php_error_docref(NULL, E_WARNING, "Function %s() hasn't defined the class it was called for", Z_STRVAL(user_func)); + incomplete_class = 1; + ce = PHP_IC_ENTRY; + } + + zval_ptr_dtor(&user_func); + zval_ptr_dtor(&args[0]); + break; + } while (1); + + *p = YYCURSOR; + + if (custom_object) { + int ret; + + ret = object_custom(UNSERIALIZE_PASSTHRU, ce); + + if (ret && incomplete_class) { + php_store_class_name(rval, ZSTR_VAL(class_name), len2); + } + zend_string_release(class_name); + return ret; + } + + elements = object_common1(UNSERIALIZE_PASSTHRU, ce); + + if (incomplete_class) { + php_store_class_name(rval, ZSTR_VAL(class_name), len2); + } + zend_string_release(class_name); + + return object_common2(UNSERIALIZE_PASSTHRU, elements); +} +#line 804 "ext/standard/var_unserializer.c" +yy25: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych != '+') goto yy18; + } else { + if (yych <= '-') goto yy26; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy27; + goto yy18; + } +yy26: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy27: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy27; + if (yych >= ';') goto yy18; + yych = *++YYCURSOR; + if (yych != '"') goto yy18; + ++YYCURSOR; +#line 726 "ext/standard/var_unserializer.re" + { + if (!var_hash) return 0; + + return object_common2(UNSERIALIZE_PASSTHRU, + object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR)); +} +#line 836 "ext/standard/var_unserializer.c" +yy32: + yych = *++YYCURSOR; + if (yych == '+') goto yy33; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy34; + goto yy18; +yy33: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy34: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy34; + if (yych >= ';') goto yy18; + yych = *++YYCURSOR; + if (yych != '{') goto yy18; + ++YYCURSOR; +#line 702 "ext/standard/var_unserializer.re" + { + zend_long elements = parse_iv(start + 2); + /* use iv() not uiv() in order to check data range */ + *p = YYCURSOR; + if (!var_hash) return 0; + + if (elements < 0) { + return 0; + } + + array_init_size(rval, elements); + if (elements) { + /* we can't convert from packed to hash during unserialization, because + reference to some zvals might be keept in var_hash (to support references) */ + zend_hash_real_init(Z_ARRVAL_P(rval), 0); + } + + if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements, 0)) { + return 0; + } + + return finish_nested_data(UNSERIALIZE_PASSTHRU); +} +#line 881 "ext/standard/var_unserializer.c" +yy39: + yych = *++YYCURSOR; + if (yych == '+') goto yy40; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy41; + goto yy18; +yy40: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy41: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy41; + if (yych >= ';') goto yy18; + yych = *++YYCURSOR; + if (yych != '"') goto yy18; + ++YYCURSOR; +#line 668 "ext/standard/var_unserializer.re" + { + size_t len, maxlen; + zend_string *str; + + len = parse_uiv(start + 2); + maxlen = max - YYCURSOR; + if (maxlen < len) { + *p = start + 2; + return 0; + } + + if ((str = unserialize_str(&YYCURSOR, len, maxlen)) == NULL) { + return 0; + } + + if (*(YYCURSOR) != '"') { + zend_string_free(str); + *p = YYCURSOR; + return 0; + } + + if (*(YYCURSOR + 1) != ';') { + efree(str); + *p = YYCURSOR + 1; + return 0; + } + + YYCURSOR += 2; + *p = YYCURSOR; + + ZVAL_STR(rval, str); + return 1; +} +#line 936 "ext/standard/var_unserializer.c" +yy46: + yych = *++YYCURSOR; + if (yych == '+') goto yy47; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy48; + goto yy18; +yy47: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy48: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy48; + if (yych >= ';') goto yy18; + yych = *++YYCURSOR; + if (yych != '"') goto yy18; + ++YYCURSOR; +#line 636 "ext/standard/var_unserializer.re" + { + size_t len, maxlen; + char *str; + + len = parse_uiv(start + 2); + maxlen = max - YYCURSOR; + if (maxlen < len) { + *p = start + 2; + return 0; + } + + str = (char*)YYCURSOR; + + YYCURSOR += len; + + if (*(YYCURSOR) != '"') { + *p = YYCURSOR; + return 0; + } + + if (*(YYCURSOR + 1) != ';') { + *p = YYCURSOR + 1; + return 0; + } + + YYCURSOR += 2; + *p = YYCURSOR; + + ZVAL_STRINGL(rval, str, len); + return 1; +} +#line 989 "ext/standard/var_unserializer.c" +yy53: + yych = *++YYCURSOR; + if (yych <= '/') { + if (yych <= ',') { + if (yych == '+') goto yy57; + goto yy18; + } else { + if (yych <= '-') goto yy55; + if (yych <= '.') goto yy60; + goto yy18; + } + } else { + if (yych <= 'I') { + if (yych <= '9') goto yy58; + if (yych <= 'H') goto yy18; + goto yy56; + } else { + if (yych != 'N') goto yy18; + } + } + yych = *++YYCURSOR; + if (yych == 'A') goto yy76; + goto yy18; +yy55: + yych = *++YYCURSOR; + if (yych <= '/') { + if (yych == '.') goto yy60; + goto yy18; + } else { + if (yych <= '9') goto yy58; + if (yych != 'I') goto yy18; + } +yy56: + yych = *++YYCURSOR; + if (yych == 'N') goto yy72; + goto yy18; +yy57: + yych = *++YYCURSOR; + if (yych == '.') goto yy60; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy58: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 4) YYFILL(4); + yych = *YYCURSOR; + if (yych <= ':') { + if (yych <= '.') { + if (yych <= '-') goto yy18; + goto yy70; + } else { + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy58; + goto yy18; + } + } else { + if (yych <= 'E') { + if (yych <= ';') goto yy63; + if (yych <= 'D') goto yy18; + goto yy65; + } else { + if (yych == 'e') goto yy65; + goto yy18; + } + } +yy60: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy61: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 4) YYFILL(4); + yych = *YYCURSOR; + if (yych <= ';') { + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy61; + if (yych <= ':') goto yy18; + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy18; + goto yy65; + } else { + if (yych == 'e') goto yy65; + goto yy18; + } + } +yy63: + ++YYCURSOR; +#line 627 "ext/standard/var_unserializer.re" + { +#if SIZEOF_ZEND_LONG == 4 +use_double: +#endif + *p = YYCURSOR; + ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL)); + return 1; +} +#line 1086 "ext/standard/var_unserializer.c" +yy65: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych != '+') goto yy18; + } else { + if (yych <= '-') goto yy66; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy67; + goto yy18; + } +yy66: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych == '+') goto yy69; + goto yy18; + } else { + if (yych <= '-') goto yy69; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; + } +yy67: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy67; + if (yych == ';') goto yy63; + goto yy18; +yy69: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy67; + goto yy18; +yy70: + ++YYCURSOR; + if ((YYLIMIT - YYCURSOR) < 4) YYFILL(4); + yych = *YYCURSOR; + if (yych <= ';') { + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy70; + if (yych <= ':') goto yy18; + goto yy63; + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy18; + goto yy65; + } else { + if (yych == 'e') goto yy65; + goto yy18; + } + } +yy72: + yych = *++YYCURSOR; + if (yych != 'F') goto yy18; +yy73: + yych = *++YYCURSOR; + if (yych != ';') goto yy18; + ++YYCURSOR; +#line 611 "ext/standard/var_unserializer.re" + { + *p = YYCURSOR; + + if (!strncmp((char*)start + 2, "NAN", 3)) { + ZVAL_DOUBLE(rval, php_get_nan()); + } else if (!strncmp((char*)start + 2, "INF", 3)) { + ZVAL_DOUBLE(rval, php_get_inf()); + } else if (!strncmp((char*)start + 2, "-INF", 4)) { + ZVAL_DOUBLE(rval, -php_get_inf()); + } else { + ZVAL_NULL(rval); + } + + return 1; +} +#line 1161 "ext/standard/var_unserializer.c" +yy76: + yych = *++YYCURSOR; + if (yych == 'N') goto yy73; + goto yy18; +yy77: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych != '+') goto yy18; + } else { + if (yych <= '-') goto yy78; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy79; + goto yy18; + } +yy78: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy79: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy79; + if (yych != ';') goto yy18; + ++YYCURSOR; +#line 585 "ext/standard/var_unserializer.re" + { +#if SIZEOF_ZEND_LONG == 4 + int digits = YYCURSOR - start - 3; + + if (start[2] == '-' || start[2] == '+') { + digits--; + } + + /* Use double for large zend_long values that were serialized on a 64-bit system */ + if (digits >= MAX_LENGTH_OF_LONG - 1) { + if (digits == MAX_LENGTH_OF_LONG - 1) { + int cmp = strncmp((char*)YYCURSOR - MAX_LENGTH_OF_LONG, long_min_digits, MAX_LENGTH_OF_LONG - 1); + + if (!(cmp < 0 || (cmp == 0 && start[2] == '-'))) { + goto use_double; + } + } else { + goto use_double; + } + } +#endif + *p = YYCURSOR; + ZVAL_LONG(rval, parse_iv(start + 2)); + return 1; +} +#line 1214 "ext/standard/var_unserializer.c" +yy83: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= '2') goto yy18; + yych = *++YYCURSOR; + if (yych != ';') goto yy18; + ++YYCURSOR; +#line 579 "ext/standard/var_unserializer.re" + { + *p = YYCURSOR; + ZVAL_BOOL(rval, parse_iv(start + 2)); + return 1; +} +#line 1228 "ext/standard/var_unserializer.c" +yy87: + ++YYCURSOR; +#line 573 "ext/standard/var_unserializer.re" + { + *p = YYCURSOR; + ZVAL_NULL(rval); + return 1; +} +#line 1237 "ext/standard/var_unserializer.c" +yy89: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych != '+') goto yy18; + } else { + if (yych <= '-') goto yy90; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy91; + goto yy18; + } +yy90: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy91: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy91; + if (yych != ';') goto yy18; + ++YYCURSOR; +#line 548 "ext/standard/var_unserializer.re" + { + zend_long id; + + *p = YYCURSOR; + if (!var_hash) return 0; + + id = parse_iv(start + 2) - 1; + if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) { + return 0; + } + + if (rval_ref == rval) { + return 0; + } + + if (Z_ISUNDEF_P(rval_ref) || (Z_ISREF_P(rval_ref) && Z_ISUNDEF_P(Z_REFVAL_P(rval_ref)))) { + ZVAL_UNDEF(rval); + return 1; + } + + ZVAL_COPY(rval, rval_ref); + + return 1; +} +#line 1285 "ext/standard/var_unserializer.c" +yy95: + yych = *++YYCURSOR; + if (yych <= ',') { + if (yych != '+') goto yy18; + } else { + if (yych <= '-') goto yy96; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy97; + goto yy18; + } +yy96: + yych = *++YYCURSOR; + if (yych <= '/') goto yy18; + if (yych >= ':') goto yy18; +yy97: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '/') goto yy18; + if (yych <= '9') goto yy97; + if (yych != ';') goto yy18; + ++YYCURSOR; +#line 522 "ext/standard/var_unserializer.re" + { + zend_long id; + + *p = YYCURSOR; + if (!var_hash) return 0; + + id = parse_iv(start + 2) - 1; + if (id == -1 || (rval_ref = var_access(var_hash, id)) == NULL) { + return 0; + } + + zval_ptr_dtor(rval); + if (Z_ISUNDEF_P(rval_ref) || (Z_ISREF_P(rval_ref) && Z_ISUNDEF_P(Z_REFVAL_P(rval_ref)))) { + ZVAL_UNDEF(rval); + return 1; + } + if (Z_ISREF_P(rval_ref)) { + ZVAL_COPY(rval, rval_ref); + } else { + ZVAL_NEW_REF(rval_ref, rval_ref); + ZVAL_COPY(rval, rval_ref); + } + + return 1; +} +#line 1334 "ext/standard/var_unserializer.c" +} +#line 886 "ext/standard/var_unserializer.re" + + + return 0; +} From 212382ac928545015cb27ddbdafa329178dff80e Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:02 +0000 Subject: [PATCH 03/43] commit patch 20232470 --- ext/exif/exif.c | 14 +- ext/exif/exif.c.orig | 4200 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4212 insertions(+), 2 deletions(-) create mode 100644 ext/exif/exif.c.orig diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 44b9fee0b16c5..4e5293f09e0b7 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..182ef9a223eed --- /dev/null +++ b/ext/exif/exif.c.orig @@ -0,0 +1,4200 @@ +/* + +----------------------------------------------------------------------+ + | 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: 44b9fee0b16c5a702e88dca0fee5fd073399ab8c $ */ + +/* 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", ImageInfo->CopyrightPhotographer, ImageInfo->CopyrightEditor); + /* format = TAG_FMT_UNDEFINED; this musn't be ASCII */ + /* but we are not supposed to change this */ + /* keep in mind that image_info does not store editor value */ + } else { + ImageInfo->Copyright = estrndup(value_ptr, byte_count); + } + } + break; + + case TAG_USERCOMMENT: + ImageInfo->UserCommentLength = exif_process_user_comment(ImageInfo, &(ImageInfo->UserComment), &(ImageInfo->UserCommentEncoding), value_ptr, byte_count); + break; + + case TAG_XP_TITLE: + case TAG_XP_COMMENTS: + case TAG_XP_AUTHOR: + case TAG_XP_KEYWORDS: + case TAG_XP_SUBJECT: + tmp_xp = (xp_field_type*)safe_erealloc(ImageInfo->xp_fields.list, (ImageInfo->xp_fields.count+1), sizeof(xp_field_type), 0); + ImageInfo->sections_found |= FOUND_WINXP; + ImageInfo->xp_fields.list = tmp_xp; + ImageInfo->xp_fields.count++; + exif_process_unicode(ImageInfo, &(ImageInfo->xp_fields.list[ImageInfo->xp_fields.count-1]), tag, value_ptr, byte_count); + break; + + case TAG_FNUMBER: + /* Simplest way of expressing aperture, so I trust it the most. + (overwrite previously computed value if there is one) */ + ImageInfo->ApertureFNumber = (float)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_APERTURE: + case TAG_MAX_APERTURE: + /* More relevant info always comes earlier, so only use this field if we don't + have appropriate aperture information yet. */ + if (ImageInfo->ApertureFNumber == 0) { + ImageInfo->ApertureFNumber + = (float)exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2)*0.5); + } + break; + + case TAG_SHUTTERSPEED: + /* More complicated way of expressing exposure time, so only use + this value if we don't already have it from somewhere else. + SHUTTERSPEED comes after EXPOSURE TIME + */ + if (ImageInfo->ExposureTime == 0) { + ImageInfo->ExposureTime + = (float)(1/exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2))); + } + break; + case TAG_EXPOSURETIME: + ImageInfo->ExposureTime = -1; + break; + + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->ExifImageWidth = exif_convert_any_to_int(value_ptr, 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; + + if ((dir_start + 2) >= (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + + NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel); + + if ((dir_start+2+NumDirEntries*12) > (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: x%04X + 2 + x%04X*12 = x%04X > x%04X", (int)((size_t)dir_start+2-(size_t)offset_base), NumDirEntries, (int)((size_t)dir_start+2+NumDirEntries*12-(size_t)offset_base), IFDlength); + return FALSE; + } + + for (de=0;de= (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + NextDirOffset = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel); + if (NextDirOffset) { + /* the next line seems false but here IFDlength means length of all IFDs */ + if (offset_base + NextDirOffset < offset_base || offset_base + NextDirOffset > offset_base+IFDlength) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD offset"); + return FALSE; + } + /* That is the IFD for the first thumbnail */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Expect next IFD to be thumbnail"); +#endif + if (exif_process_IFD_in_JPEG(ImageInfo, offset_base + NextDirOffset, offset_base, IFDlength, displacement, SECTION_THUMBNAIL)) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail size: 0x%04X", ImageInfo->Thumbnail.size); +#endif + if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN + && ImageInfo->Thumbnail.size + && ImageInfo->Thumbnail.offset + && ImageInfo->read_thumbnail + ) { + exif_thumbnail_extract(ImageInfo, offset_base, IFDlength); + } + return TRUE; + } else { + return FALSE; + } + } + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_TIFF_in_JPEG + Process a TIFF header in a JPEG file +*/ +static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement) +{ + unsigned exif_value_2a, offset_of_ifd; + + /* set the thumbnail stuff to nothing so we can test to see if they get set up */ + if (memcmp(CharBuf, "II", 2) == 0) { + ImageInfo->motorola_intel = 0; + } else if (memcmp(CharBuf, "MM", 2) == 0) { + ImageInfo->motorola_intel = 1; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF alignment marker"); + return; + } + + /* Check the next two values for correctness. */ + if (length < 8) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + exif_value_2a = php_ifd_get16u(CharBuf+2, ImageInfo->motorola_intel); + offset_of_ifd = php_ifd_get32u(CharBuf+4, ImageInfo->motorola_intel); + if (exif_value_2a != 0x2a || offset_of_ifd < 0x08) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + if (offset_of_ifd > length) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid IFD start"); + return; + } + + ImageInfo->sections_found |= FOUND_IFD0; + /* First directory starts at offset 8. Offsets starts at 0. */ + exif_process_IFD_in_JPEG(ImageInfo, CharBuf+offset_of_ifd, CharBuf, length/*-14*/, displacement, SECTION_IFD0); + +#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 07e5dde7a6844e382d9b21f4c0888f7f4a354e52 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:04 +0000 Subject: [PATCH 04/43] commit patch 19176591 --- 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 4e5293f09e0b7..c0139478e3dae 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 182ef9a223eed..b8b7c5312dc83 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 0efba35d706afd88834fb8a18d44871262532b4e Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:06 +0000 Subject: [PATCH 05/43] commit patch 22599490 --- ext/wddx/tests/bug72790.phpt | 35 + ext/wddx/tests/bug72799.phpt | 28 + ext/wddx/wddx.c | 2 +- ext/wddx/wddx.c.orig | 1300 ++++++++++++++++++++++++++++++++++ 4 files changed, 1364 insertions(+), 1 deletion(-) create mode 100644 ext/wddx/tests/bug72790.phpt create mode 100644 ext/wddx/tests/bug72799.phpt create mode 100644 ext/wddx/wddx.c.orig diff --git a/ext/wddx/tests/bug72790.phpt b/ext/wddx/tests/bug72790.phpt new file mode 100644 index 0000000000000..a60524bdaf19e --- /dev/null +++ b/ext/wddx/tests/bug72790.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug 72790: wddx_deserialize null dereference with invalid xml +--SKIPIF-- + +--FILE-- + + + + |array> + + + + + + + + + + + + +XML; + +$array = wddx_deserialize($xml); +var_dump($array); +?> +--EXPECT-- +NULL \ No newline at end of file diff --git a/ext/wddx/tests/bug72799.phpt b/ext/wddx/tests/bug72799.phpt new file mode 100644 index 0000000000000..5861d5538f49f --- /dev/null +++ b/ext/wddx/tests/bug72799.phpt @@ -0,0 +1,28 @@ +--TEST-- +Bug #72799: wddx_deserialize null dereference in php_wddx_pop_element +--SKIPIF-- + +--FILE-- + + + + + + 1998-06-12T04:32:12+00 + + + +XML; + +$array = wddx_deserialize($xml); +var_dump($array); +?> +--EXPECT-- +NULL \ No newline at end of file diff --git a/ext/wddx/wddx.c b/ext/wddx/wddx.c index 2cc3c8b9cf661..d5a428ee0af3a 100644 --- a/ext/wddx/wddx.c +++ b/ext/wddx/wddx.c @@ -881,10 +881,10 @@ static void php_wddx_pop_element(void *user_data, const XML_Char *name) if (Z_TYPE(ent1->data) == IS_UNDEF) { if (stack->top > 1) { stack->top--; + efree(ent1); } else { stack->done = 1; } - efree(ent1); return; } diff --git a/ext/wddx/wddx.c.orig b/ext/wddx/wddx.c.orig new file mode 100644 index 0000000000000..2cc3c8b9cf661 --- /dev/null +++ b/ext/wddx/wddx.c.orig @@ -0,0 +1,1300 @@ +/* + +----------------------------------------------------------------------+ + | 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]) { + if (stack->varname) efree(stack->varname); + 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 (Z_TYPE(ent1->data) == IS_UNDEF) { + if (stack->top > 1) { + stack->top--; + } else { + stack->done = 1; + } + efree(ent1); + return; + } + + 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); + stack->varname = NULL; + } 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 { + zval_ptr_dtor(&ent->data); + if (ent->varname) { + efree(ent->varname); + ent->varname = NULL; + } + ZVAL_UNDEF(&ent->data); + } + 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 16:16:08 +0000 Subject: [PATCH 06/43] commit patch 24775808 --- Zend/zend_alloc.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 18765593174f6..a79d67b4b996a 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -1548,21 +1548,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"); From 1260a051f3bd7b28be7a92f64d80d6a819f9dbd5 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:10 +0000 Subject: [PATCH 07/43] commit patch 18817977 --- ext/gd/gd.c | 6 +- ext/gd/gd.c.orig | 4964 ++++++++++++++++++++++++++++++++++++ ext/gd/tests/bug72697.phpt | 17 + 3 files changed, 4984 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 b09990938d962..a74fa1e023691 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..b09990938d962 --- /dev/null +++ b/ext/gd/gd.c.orig @@ -0,0 +1,4964 @@ +/* + +----------------------------------------------------------------------+ + | 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; + } + } + + if (tmp_h <= 0 || tmp_w <= 0) { + RETURN_FALSE; + } + + 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 d20e313803c501aec6aca5f87bc8dbec209c48ef Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:13 +0000 Subject: [PATCH 08/43] commit patch 20591217 --- 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 78ca472a2bb2f..80a3a528058e7 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 a57751f9104b7c73535d80ec15802f5b8eb82c25 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:15 +0000 Subject: [PATCH 09/43] commit patch 19999511 --- 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 c189205879645..ad7887bd287fe 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 fa232ad681e74c968549652f49f1c428e84560dd Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:17 +0000 Subject: [PATCH 10/43] commit patch 24394411 --- ext/standard/tests/serialize/bug70172_2.phpt | 6 ++---- ext/standard/var.c | 10 +++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ext/standard/tests/serialize/bug70172_2.phpt b/ext/standard/tests/serialize/bug70172_2.phpt index dc9067168a081..2b12a78edb5be 100644 --- a/ext/standard/tests/serialize/bug70172_2.phpt +++ b/ext/standard/tests/serialize/bug70172_2.phpt @@ -1,7 +1,5 @@ --TEST-- Bug #70172 - Use After Free Vulnerability in unserialize() ---XFAIL-- -Unfinished merge, needs fix. --FILE-- array(1) { [0]=> - &object(obj2)#%d (1) { + object(obj2)#%d (1) { ["ryat"]=> int(1) } } } -} \ No newline at end of file +} diff --git a/ext/standard/var.c b/ext/standard/var.c index 472cd62d8f57b..88719ccb64dcd 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -1036,6 +1036,7 @@ PHP_FUNCTION(unserialize) const unsigned char *p; php_unserialize_data_t var_hash; zval *options = NULL, *classes = NULL; + zval *retval; HashTable *class_hash = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &buf, &buf_len, &options) == FAILURE) { @@ -1067,22 +1068,21 @@ PHP_FUNCTION(unserialize) } } - if (!php_var_unserialize_ex(return_value, &p, p + buf_len, &var_hash, class_hash)) { + retval = var_tmp_var(&var_hash); + if (!php_var_unserialize_ex(retval, &p, p + buf_len, &var_hash, class_hash)) { PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (class_hash) { zend_hash_destroy(class_hash); FREE_HASHTABLE(class_hash); } - zval_ptr_dtor(return_value); if (!EG(exception)) { php_error_docref(NULL, E_NOTICE, "Error at offset " ZEND_LONG_FMT " of %zd bytes", (zend_long)((char*)p - buf), buf_len); } RETURN_FALSE; } - /* We should keep an reference to return_value to prevent it from being dtor - in case nesting calls to unserialize */ - var_push_dtor(&var_hash, return_value); + + ZVAL_COPY(return_value, retval); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (class_hash) { From bbe6f3f35e89d6dd289fe9ad220dd23581cf1347 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:19 +0000 Subject: [PATCH 11/43] commit patch 23401314 --- ext/gd/libgd/gd.c | 2 +- ext/gd/libgd/gd.c.orig | 3097 ++++++++++++++++++++++++++++++++++++ ext/gd/tests/bug72696.phpt | 14 + 3 files changed, 3112 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 17b0a1091b5fc..5340eaa522307 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -1766,7 +1766,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..17b0a1091b5fc --- /dev/null +++ b/ext/gd/libgd/gd.c.orig @@ -0,0 +1,3097 @@ + +#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; + } + + if (overflow2(sizeof(unsigned char *), sx)) { + 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; + } + + if (!im->trueColor) { + if ((color > (im->colorsTotal - 1)) || (border > (im->colorsTotal - 1)) || (color < 0)) { + 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 a2ed0d69e1f75d721a351126be43c9829dafb2eb Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:21 +0000 Subject: [PATCH 12/43] commit patch 24479099 --- ext/wddx/tests/bug73631.phpt | 19 +++++++++++++++++++ ext/wddx/wddx.c | 5 +++++ ext/wddx/wddx.c.orig | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ext/wddx/tests/bug73631.phpt 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 d5a428ee0af3a..6422096b6b57a 100644 --- a/ext/wddx/wddx.c +++ b/ext/wddx/wddx.c @@ -819,6 +819,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 index 2cc3c8b9cf661..d5a428ee0af3a 100644 --- a/ext/wddx/wddx.c.orig +++ b/ext/wddx/wddx.c.orig @@ -881,10 +881,10 @@ static void php_wddx_pop_element(void *user_data, const XML_Char *name) if (Z_TYPE(ent1->data) == IS_UNDEF) { if (stack->top > 1) { stack->top--; + efree(ent1); } else { stack->done = 1; } - efree(ent1); return; } From 0de48942d7905205a9ecc6312bef22097ef2ebe4 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:23 +0000 Subject: [PATCH 13/43] commit patch 17972452 --- 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 5404187a973a3..3d363c9cadc39 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 4e54ae9337a147aaee8523b1b22d52bf120d053f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:25 +0000 Subject: [PATCH 14/43] commit patch 25282233 --- NEWS | 4 + NEWS.orig | 1416 ++++++++++++++++++++++++++++++++++++++++++ main/php_variables.c | 10 +- 3 files changed, 1428 insertions(+), 2 deletions(-) create mode 100644 NEWS.orig diff --git a/NEWS b/NEWS index 76c46b84b9233..b018631d8b0b7 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,10 @@ PHP NEWS . Fixed bug #72014 (Including a file with anonymous classes multiple times leads to fatal error). (Laruence) +- Core: + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + - OpenSSL: . Fixed bug #72165 (Null pointer dereference - openssl_csr_new). (Anatol) diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..76c46b84b9233 --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,1416 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +23 Jun 2016 PHP 7.0.8 + +- Core: + . Fixed bug #72218 (If host name cannot be resolved then PHP 7 crashes). + (Esminis at esminis dot lt) + . Fixed bug #72221 (segfault, past-the-end access). (Lauri Kenttä) + . Fixed bug #72268 (Integer Overflow in nl2br()). (Stas) + . Fixed bug #72275 (Integer Overflow in json_encode()/json_decode()/ + json_utf8_to_utf16()). (Stas) + . Fixed bug #72400 (Integer Overflow in addcslashes/addslashes). (Stas) + . Fixed bug #72403 (Integer Overflow in Length of String-typed ZVAL). (Stas) + +- FPM: + . Fixed bug #72308 (fastcgi_finish_request and logging environment + variables). (Laruence) + +- GD: + . Fixed bug #72298 (pass2_no_dither out-of-bounds access). (Stas) + . Fixed bug #72337 (invalid dimensions can lead to crash) (Pierre) + . Fixed bug #72339 (Integer Overflow in _gd2GetHeader() resulting in + heap overflow). (Pierre) + . Fixed bug #72407 (NULL Pointer Dereference at _gdScaleVert). (Stas) + +- Intl: + . Fixed bug #64524 (Add intl.use_exceptions to php.ini-*). (Anatol) + +- mbstring: + . Fixed bug #72402 (_php_mb_regex_ereg_replace_exec - double free). (Stas) + +- mcrypt: + . Fixed bug #72455 (Heap Overflow due to integer overflows). (Stas) + +- PCRE: + . Fixed bug #72143 (preg_replace uses int instead of size_t). (Joe) + +- PDO_pgsql: + . Fixed bug #71573 (Segfault (core dumped) if paramno beyond bound). + (Laruence) + . Fixed bug #72294 (Segmentation fault/invalid pointer in connection + with pgsql_stmt_dtor). (Anatol) + +- Phpdbg: + . Fixed bug #72284 (phpdbg fatal errors with coverage). (Bob) + +- Postgres: + . Fixed bug #72195 (pg_pconnect/pg_connect cause use-after-free). (Laruence) + . Fixed bug #72197 (pg_lo_create arbitrary read). (Anatol) + +- SPL: + . Fixed bug #72262 (int/size_t confusion in SplFileObject::fread). (Stas) + . Fixed bug #72433 (Use After Free Vulnerability in PHP's GC algorithm and + unserialize). (Dmitry) + +- Standard: + . Fixed bug #72017 (range() with float step produces unexpected result). + (Thomas Punt) + . Fixed bug #72193 (dns_get_record returns array containing elements of + type 'unknown'). (Laruence) + . Fixed bug #72229 (Wrong reference when serialize/unserialize an object). + (Laruence) + . Fixed bug #72300 (ignore_user_abort(false) has no effect). (Laruence) + +- XML: + . Fixed bug #72206 (xml_parser_create/xml_parser_free leaks mem). (Joe) + +- XMLRPC: + . Fixed bug #72155 (use-after-free caused by get_zval_xmlrpc_type). + (Joe, Laruence) + +- WDDX: + . Fixed bug #72340 (Double Free Courruption in wddx_deserialize). (Stas) + +- Zip: + . Fixed ug #72258 (ZipArchive converts filenames to unrecoverable form). + (Anatol) + . Fixed bug #72434 (ZipArchive class Use After Free Vulnerability in PHP's GC + algorithm and unserialize). (Dmitry) + +26 May 2016 PHP 7.0.7 + +- Core: + . Fixed bug #72162 (use-after-free - error_reporting). (Laruence) + . Add compiler option to disable special case function calls. (Joe) + . Fixed bug #72101 (crash on complex code). (Dmitry) + . Fixed bug #72100 (implode() inserts garbage into resulting string when + joins very big integer). (Mikhail Galanin) + . Fixed bug #72057 (PHP Hangs when using custom error handler and typehint). + (Nikita Nefedov) + . Fixed bug #72038 (Function calls with values to a by-ref parameter don't + always throw a notice). (Bob) + . Fixed bug #71737 (Memory leak in closure with parameter named $this). + (Nikita) + . Fixed bug #72059 (?? is not allowed on constant expressions). (Bob, Marcio) + . Fixed bug #72159 (Imported Class Overrides Local Class Name). (Nikita) + +- Curl: + . Fixed bug #68658 (Define CURLE_SSL_CACERT_BADFILE). (Pierrick) + +- DBA: + . Fixed bug #72157 (use-after-free caused by dba_open). (Shm, Laruence) + +- GD: + . Fixed bug #72227 (imagescale out-of-bounds read). (Stas) + +- Intl: + . Fixed #72241 (get_icu_value_internal out-of-bounds read). (Stas) + +- JSON: + . Fixed bug #72069 (Behavior \JsonSerializable different from json_encode). + (Laruence) + +- Mbstring: + . Fixed bug #72164 (Null Pointer Dereference - mb_ereg_replace). (Laruence) + +- OCI8: + . Fixed bug #71600 (oci_fetch_all segfaults when selecting more than eight + columns). (Tian Yang) + +- Opcache: + . Fixed bug #72014 (Including a file with anonymous classes multiple times + leads to fatal error). (Laruence) + +- OpenSSL: + . Fixed bug #72165 (Null pointer dereference - openssl_csr_new). (Anatol) + +- PCNTL: + . Fixed bug #72154 (pcntl_wait/pcntl_waitpid array internal structure + overwrite). (Laruence) + +- POSIX: + . Fixed bug #72133 (php_posix_group_to_array crashes if gr_passwd is NULL). + (esminis at esminis dot lt) + +- Postgres: + . Fixed bug #72028 (pg_query_params(): NULL converts to empty string). + (Laruence) + . Fixed bug #71062 (pg_convert() doesn't accept ISO 8601 for datatype + timestamp). (denver at timothy dot io) + . Fixed bug #72151 (mysqli_fetch_object changed behaviour). (Anatol) + +- Reflection: + . Fixed bug #72174 (ReflectionProperty#getValue() causes __isset call). + (Nikita) + +- Session: + . Fixed bug #71972 (Cyclic references causing session_start(): Failed to + decode session object). (Laruence) + +- Sockets: + . Added socket_export_stream() function for getting a stream compatible + resource from a socket resource. (Chris Wright, Bob) + +- SPL: + . Fixed bug #72051 (The reference in CallbackFilterIterator doesn't work as + expected). (Laruence) + +- SQLite3: + . Fixed bug #68849 (bindValue is not using the right data type). (Anatol) + +- Standard: + . Fixed bug #72075 (Referencing socket resources breaks stream_select). + (Laruence) + . Fixed bug #72031 (array_column() against an array of objects discards all + values matching null). (Nikita) + +28 Apr 2016 PHP 7.0.6 + +- Core: + . Fixed bug #71930 (_zval_dtor_func: Assertion `(arr)->gc.refcount <= 1' + failed). (Laruence) + . Fixed bug #71922 (Crash on assert(new class{})). (Nikita) + . Fixed bug #71914 (Reference is lost in "switch"). (Laruence) + . Fixed bug #71871 (Interfaces allow final and abstract functions). (Nikita) + . Fixed Bug #71859 (zend_objects_store_call_destructors operates on realloced + memory, crashing). (Laruence) + . Fixed bug #71841 (EG(error_zval) is not handled well). (Laruence) + . Fixed bug #71750 (Multiple Heap Overflows in php_raw_url_encode/ + php_url_encode). (Stas) + . Fixed bug #71731 (Null coalescing operator and ArrayAccess). (Nikita) + . Fixed bug #71609 (Segmentation fault on ZTS with gethostbyname). (krakjoe) + . Fixed bug #71414 (Inheritance, traits and interfaces). (krakjoe) + . Fixed bug #71359 (Null coalescing operator and magic). (krakjoe) + . Fixed bug #71334 (Cannot access array keys while uksort()). (Nikita) + . Fixed bug #69659 (ArrayAccess, isset() and the offsetExists method). + (Nikita) + . Fixed bug #69537 (__debugInfo with empty string for key gives error). + (krakjoe) + . Fixed bug #62059 (ArrayObject and isset are not friends). (Nikita) + . Fixed bug #71980 (Decorated/Nested Generator is Uncloseable in Finally). + (Nikita) + +- BCmath: + . Fixed bug #72093 (bcpowmod accepts negative scale and corrupts + _one_ definition). (Stas) + +- Curl: + . Fixed bug #71831 (CURLOPT_NOPROXY applied as long instead of string). + (Michael Sierks) + +- Date: + . Fixed bug #71889 (DateInterval::format Segmentation fault). (Thomas Punt) + +- EXIF: + . Fixed bug #72094 (Out of bounds heap read access in exif header processing). (Stas) + +- GD: + . Fixed bug #71912 (libgd: signedness vulnerability). (CVE-2016-3074) (Stas) + +- Intl: + . Fixed bug #71516 (IntlDateFormatter looses locale if pattern is set via + constructor). (Anatol) + . Fixed bug #70455 (Missing constant: IntlChar::NO_NUMERIC_VALUE). (Anatol) + . Fixed bug #70451, #70452 (Inconsistencies in return values of IntlChar + methods). (Daniel Persson) + . Fixed bug #68893 (Stackoverflow in datefmt_create). (Anatol) + . Fixed bug #66289 (Locale::lookup incorrectly returns en or en_US if locale + is empty). (Anatol) + . Fixed bug #70484 (selectordinal doesn't work with named parameters). + (Anatol) + . Fixed bug #72061 (Out-of-bounds reads in zif_grapheme_stripos with negative + offset). (Stas) + +- ODBC: + . Fixed bug #63171 (Script hangs after max_execution_time). (Remi) + +- Opcache: + . Fixed bug #71843 (null ptr deref ZEND_RETURN_SPEC_CONST_HANDLER). + (Laruence) + +- PDO: + . Fixed bug #52098 (Own PDOStatement implementation ignore __call()). + (Daniel kalaspuffar, Julien) + . Fixed bug #71447 (Quotes inside comments not properly handled). (Matteo) + +- PDO_DBlib: + . Fixed bug #71943 (dblib_handle_quoter needs to allocate an extra byte). + (Adam Baratz) + . Add DBLIB-specific attributes for controlling timeouts. (Adam Baratz) + +- PDO_pgsql: + . Fixed bug #62498 (pdo_pgsql inefficient when getColumnMeta() is used). + (Joseph Bylund) + +- Postgres: + . Fixed bug #71820 (pg_fetch_object binds parameters before call + constructor). (Anatol) + . Fixed bug #71998 (Function pg_insert does not insert when column + type = inet). (Anatol) + +- SOAP: + . Fixed bug #71986 (Nested foreach assign-by-reference creates broken + variables). (Laruence) + +- SPL: + . Fixed bug #71838 (Deserializing serialized SPLObjectStorage-Object can't + access properties in PHP). (Nikita) + . Fixed bug #71735 (Double-free in SplDoublyLinkedList::offsetSet). (Stas) + . Fixed bug #67582 (Cloned SplObjectStorage with overwritten getHash fails + offsetExists()). (Nikita) + . Fixed bug #52339 (SPL autoloader breaks class_exists()). (Nikita) + +- Standard: + . Fixed bug #71995 (Returning the same var twice from __sleep() produces + broken serialized data). (Laruence) + . Fixed bug #71940 (Unserialize crushes on restore object reference). + (Laruence) + . Fixed bug #71969 (str_replace returns an incorrect resulting array after + a foreach by reference). (Laruence) + . Fixed bug #71891 (header_register_callback() and + register_shutdown_function()). (Laruence) + . Fixed bug #71884 (Null pointer deref (segfault) in + stream_context_get_default). (Laruence) + . Fixed bug #71840 (Unserialize accepts wrongly data). (Ryat, Laruence) + . Fixed bug #71837 (Wrong arrays behaviour). (Laruence) + . Fixed bug #71827 (substr_replace bug, string length). (krakjoe) + . Fixed bug #67512 (php_crypt() crashes if crypt_r() does not exist or + _REENTRANT is not defined). (Nikita) + . Fixed bug #72116 (array_fill optimization breaks implementation). (Bob) + +- XML: + . Fixed bug #72099 (xml_parse_into_struct segmentation fault). (Stas) + +- Zip: + . Fixed bug #71923 (integer overflow in ZipArchive::getFrom*). + (CVE-2016-3078) (Stas) + +31 Mar 2016 PHP 7.0.5 + +- Core: + . Huge pages disabled by default. (Rasmus) + . Added ability to enable huge pages in Zend Memory Manager through + the environment variable USE_ZEND_ALLOC_HUGE_PAGES=1. (Dmitry) + . Fixed bug #71756 (Call-by-reference widens scope to uninvolved functions + when used in switch). (Laruence) + . Fixed bug #71729 (Possible crash in zend_bin_strtod, zend_oct_strtod, + zend_hex_strtod). (Laruence) + . Fixed bug #71695 (Global variables are reserved before execution). + (Laruence) + . Fixed bug #71629 (Out-of-bounds access in php_url_decode in context + php_stream_url_wrap_rfc2397). (mt at debian dot org) + . Fixed bug #71622 (Strings used in pass-as-reference cannot be used to + invoke C::$callable()). (Bob) + . Fixed bug #71596 (Segmentation fault on ZTS with date function + (setlocale)). (Anatol) + . Fixed bug #71535 (Integer overflow in zend_mm_alloc_heap()). (Dmitry) + . Fixed bug #71470 (Leaked 1 hashtable iterators). (Nikita) + . Fixed bug #71575 (ISO C does not allow extra ‘;’ outside of a function). + (asgrim) + . Fixed bug #71724 (yield from does not count EOLs). (Nikita) + . Fixed bug #71767 (ReflectionMethod::getDocComment returns the wrong + comment). (Grigorii Sokolik) + . Fixed bug #71806 (php_strip_whitespace() fails on some numerical values). + (Nikita) + . Fixed bug #71624 (`php -R` (PHP_MODE_PROCESS_STDIN) is broken). + (Sean DuBois) + +- CLI Server: + . Fixed bug #69953 (Support MKCALENDAR request method). (Christoph) + +- Curl: + . Fixed bug #71694 (Support constant CURLM_ADDED_ALREADY). (mpyw) + +- Date: + . Fixed bug #71635 (DatePeriod::getEndDate segfault). (Thomas Punt) + +- Fileinfo: + . Fixed bug #71527 (Buffer over-write in finfo_open with malformed magic + file). (CVE-2015-8865) (Anatol) + +- libxml: + . Fixed bug #71536 (Access Violation crashes php-cgi.exe). (Anatol) + +- mbstring: + . Fixed bug #71906 (AddressSanitizer: negative-size-param (-1) in + mbfl_strcut). (CVE-2016-4073) (Stas) + +- ODBC: + . Fixed bug #47803, #69526 (Executing prepared statements is succesfull only + for the first two statements). (einavitamar at gmail dot com, Anatol) + +- PCRE: + . Fixed bug #71659 (segmentation fault in pcre running twig tests). + (nish dot aravamudan at canonical dot com) + +- PDO_DBlib: + . Fixed bug #54648 (PDO::MSSQL forces format of datetime fields). + (steven dot lambeth at gmx dot de, Anatol) + +- Phar: + . Fixed bug #71625 (Crash in php7.dll with bad phar filename). (Anatol) + . Fixed bug #71317 (PharData fails to open specific file). (Jos Elstgeest) + . Fixed bug #71860 (Invalid memory write in phar on filename with \0 in + name). (CVE-2016-4072) (Stas) + +- phpdbg: + . Fixed crash when advancing (except step) inside an internal function. (Bob) + +- Session: + . Fixed bug #71683 (Null pointer dereference in zend_hash_str_find_bucket). + (Yasuo) + +- SNMP: + . Fixed bug #71704 (php_snmp_error() Format String Vulnerability). + (CVE-2016-4071) (andrew at jmpesp dot org) + +- SPL: + . Fixed bug #71617 (private properties lost when unserializing ArrayObject). + (Nikita) + +- Standard: + . Fixed bug #71660 (array_column behaves incorrectly after foreach by + reference). (Laruence) + . Fixed bug #71798 (Integer Overflow in php_raw_url_encode). (CVE-2016-4070) + (taoguangchen at icloud dot com, Stas) + +- Zip: + . Update bundled libzip to 1.1.2. (Remi, Anatol) + +03 Mar 2016 PHP 7.0.4 + +- Core: + . Fixed bug (Low probability segfault in zend_arena). (Laruence) + . Fixed bug #71441 (Typehinted Generator with return in try/finally crashes). + (Bob) + . Fixed bug #71442 (forward_static_call crash). (Laruence) + . Fixed bug #71443 (Segfault using built-in webserver with intl using + symfony). (Laruence) + . Fixed bug #71449 (An integer overflow bug in php_implode()). (Stas) + . Fixed bug #71450 (An integer overflow bug in php_str_to_str_ex()). (Stas) + . Fixed bug #71474 (Crash because of VM stack corruption on Magento2). + (Dmitry) + . Fixed bug #71485 (Return typehint on internal func causes Fatal error + when it throws exception). (Laruence) + . Fixed bug #71529 (Variable references on array elements don't work when + using count). (Nikita) + . Fixed bug #71601 (finally block not executed after yield from). (Bob) + . Fixed bug #71637 (Multiple Heap Overflow due to integer overflows in + xml/filter_url/addcslashes). (CVE-2016-4344, CVE-2016-4345, CVE-2016-4346) + (Stas) + +- CLI server: + . Fixed bug #71559 (Built-in HTTP server, we can download file in web by bug). + (Johannes, Anatol) + +- CURL: + . Fixed bug #71523 (Copied handle with new option CURLOPT_HTTPHEADER crashes + while curl_multi_exec). (Laruence) + . Fixed memory leak in curl_getinfo(). (Leigh) + +- Date: + . Fixed bug #71525 (Calls to date_modify will mutate timelib_rel_time, + causing date_date_set issues). (Sean DuBois) + +- Fileinfo: + . Fixed bug #71434 (finfo throws notice for specific python file). (Laruence) + +- FPM: + . Fixed bug #62172 (FPM not working with Apache httpd 2.4 balancer/fcgi + setup). (Matt Haught, Remi) + . Fixed bug #71269 (php-fpm dumped core). (Mickaël) + +- Opcache: + . Fixed bug #71584 (Possible use-after-free of ZCG(cwd) in Zend Opcache). + (Yussuf Khalil) + +- PCRE: + . Fixed bug #71537 (PCRE segfault from Opcache). (Laruence) + +- phpdbg: + . Fixed inherited functions from unspecified files being included in + phpdbg_get_executable(). (Bob) + +- SOAP: + . Fixed bug #71610 (Type Confusion Vulnerability - SOAP / + make_http_soap_request()). (CVE-2016-3185) (Stas) + +- Standard: + . Fixed bug #71603 (compact() maintains references in php7). (Laruence) + . Fixed bug #70720 (strip_tags improper php code parsing). (Julien) + +- XMLRPC: + . Fixed bug #71501 (xmlrpc_encode_request ignores encoding option). (Hieu Le) + +- Zip: + . Fixed bug #71561 (NULL pointer dereference in Zip::ExtractTo). (Laruence) + +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) + +- GD: + . Improved fix for bug #70976. (Remi) + +- 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) + +- PCRE: + . Upgraded pcrelib to 8.38. (CVE-2015-8383, CVE-2015-8386, CVE-2015-8387, + CVE-2015-8389, CVE-2015-8390, CVE-2015-8391, CVE-2015-8393, CVE-2015-8394) + +- Phar: + . Fixed bug #71354 (Heap corruption in tar/zip/phar parser). (CVE-2016-4342) + (Stas) + . Fixed bug #71331 (Uninitialized pointer in phar_make_dirstream()). + (CVE-2016-4343) (Stas) + . Fixed bug #71391 (NULL Pointer Dereference in phar_tar_setupmetadata()). + (Stas) + . Fixed bug #71488 (Stack overflow when decompressing tar archives). + (CVE-2016-2554) (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) + +- 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) + +- GD: + . Fixed bug #70976 (Memory Read via gdImageRotateInterpolated Array Index + Out of Bounds). (CVE-2016-1903) (emmanuel dot law at gmail 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). + (CVE-2016-1904) (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). (CVE-2015-8867) (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). + (CVE-2014-9767) (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 c499eb5f0259a930fa5b856146a83b61f635d517 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:27 +0000 Subject: [PATCH 15/43] commit patch 17718964 --- 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 f1608c9ecb1a4db0079727795d594762e91321b9 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:29 +0000 Subject: [PATCH 16/43] commit patch 25282232 --- NEWS | 3 +++ NEWS.orig | 4 ++++ ext/intl/msgformat/msgformat_parse.c | 1 + 3 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index b018631d8b0b7..72bf88fcfc00e 100644 --- a/NEWS +++ b/NEWS @@ -111,6 +111,9 @@ PHP NEWS . Fixed bug #72069 (Behavior \JsonSerializable different from json_encode). (Laruence) +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + - Mbstring: . Fixed bug #72164 (Null Pointer Dereference - mb_ereg_replace). (Laruence) diff --git a/NEWS.orig b/NEWS.orig index 76c46b84b9233..b018631d8b0b7 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -122,6 +122,10 @@ PHP NEWS . Fixed bug #72014 (Including a file with anonymous classes multiple times leads to fatal error). (Laruence) +- Core: + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + - OpenSSL: . Fixed bug #72165 (Null pointer dereference - openssl_csr_new). (Anatol) 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 62a79ee9990c31b48daf6ac3f297187fb785d46a Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:31 +0000 Subject: [PATCH 17/43] commit patch 22662656 --- 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 14abfa72c9385d771a0f3ab416141437ca8334d7 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:34 +0000 Subject: [PATCH 18/43] commit patch 21859779 --- ext/standard/http_fopen_wrapper.c | 4 +- ext/standard/http_fopen_wrapper.c.orig | 988 +++++++++++++++++++++++++ ext/standard/tests/http/bug75981.phpt | 32 + 3 files changed, 1022 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 02b580fa2897f..78a90ad0a4316 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -717,9 +717,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..02b580fa2897f --- /dev/null +++ b/ext/standard/http_fopen_wrapper.c.orig @@ -0,0 +1,988 @@ +/* + +----------------------------------------------------------------------+ + | 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"); + } + 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 d61034505131df1757fe9efe49ca1bf90a9a250d Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:36 +0000 Subject: [PATCH 19/43] commit patch 21162012 --- 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 c57bdef3c67ab..eff66039e7017 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..c57bdef3c67ab --- /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) != 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); + efree(new_state.cwd); + return FAILURE; + } + } else { + if (!php_stream_mkdir(fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + } + } + + if (slash) { + fullpath[dest_len + (slash - filename) + 1] = '/'; + } else { + fullpath[dest_len] = '/'; + } + + filename = NULL; + efree(new_state.cwd); + /* it is a standalone directory, job done */ + if (entry->is_dir) { + efree(fullpath); + return SUCCESS; + } + +#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 59ef523b099645094ca14a22167ae8e2329594a0 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:38 +0000 Subject: [PATCH 20/43] commit patch 17361244 --- 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 d4a7f6e0af4e7..7c6f309730912 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..d4a7f6e0af4e7 --- /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 : (int) strlen(mimetype), mimetype, (int) (p - get_output_encoding()), get_output_encoding()); + } else { + len = spprintf(&content_type, 0, "Content-Type:%.*s; charset=%s", mimetype_len ? mimetype_len : (int) 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 d8575542abea94ed97edcee38ef08ede27ce09b8 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:41 +0000 Subject: [PATCH 21/43] commit patch 21733281 --- 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 456b9719fa4f7..b4e885eced284 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..456b9719fa4f7 --- /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 63be2d482720f7e765b89e8067e577816d046676 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:43 +0000 Subject: [PATCH 22/43] commit patch 27630143 --- ext/imap/php_imap.c | 1 - ext/imap/php_imap.c.orig | 5105 ++++++++++++++++++++++++++++++++++ ext/imap/tests/bug77020.phpt | 15 + 3 files changed, 5120 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 2ce1070838bb1..b680ecd6ac4b8 100644 --- a/ext/imap/php_imap.c +++ b/ext/imap/php_imap.c @@ -4109,7 +4109,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..2ce1070838bb1 --- /dev/null +++ b/ext/imap/php_imap.c.orig @@ -0,0 +1,5105 @@ +/* + +----------------------------------------------------------------------+ + | 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) */ + int global = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS|SS", &streamind, &folder, &message, &flags, &internal_date) == FAILURE) { + return; + } + + regex = zend_string_init("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/", sizeof("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/") - 1, 0); + + if (internal_date) { + /* Make sure the given internal_date string matches the RFC specifiedformat */ + if ((pce = pcre_get_compiled_regex_cache(regex))== NULL) { + zend_string_free(regex); + RETURN_FALSE; + } + + zend_string_free(regex); + php_pcre_match_impl(pce, ZSTR_VAL(internal_date), ZSTR_LEN(internal_date), return_value, subpats, global, + 0, Z_L(0), Z_L(0)); + + if (!Z_LVAL_P(return_value)) { + php_error_docref(NULL, E_WARNING, "internal date not correctly formatted"); + internal_date = NULL; + } + } + + zend_string_free(regex); + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + INIT (&st, mail_string, (void *) ZSTR_VAL(message), ZSTR_LEN(message)); + + if (mail_append_full(imap_le_struct->imap_stream, ZSTR_VAL(folder), (flags ? ZSTR_VAL(flags) : NIL), (internal_date ? ZSTR_VAL(internal_date) : NIL), &st)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imap_num_msg(resource stream_id) + Gives the number of messages in the current mailbox */ +PHP_FUNCTION(imap_num_msg) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->nmsgs); +} +/* }}} */ + +/* {{{ proto bool imap_ping(resource stream_id) + Check if the IMAP stream is still active */ +PHP_FUNCTION(imap_ping) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(mail_ping(imap_le_struct->imap_stream)); +} +/* }}} */ + +/* {{{ proto int imap_num_recent(resource stream_id) + Gives the number of recent messages in current mailbox */ +PHP_FUNCTION(imap_num_recent) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->recent); +} +/* }}} */ + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +/* {{{ proto array imap_get_quota(resource stream_id, string qroot) + Returns the quota set to the mailbox account qroot */ +PHP_FUNCTION(imap_get_quota) +{ + zval *streamind; + zend_string *qroot; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &qroot) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTA function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquota failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_get_quotaroot(resource stream_id, string mbox) + Returns the quota set to the mailbox account mbox */ +PHP_FUNCTION(imap_get_quotaroot) +{ + zval *streamind; + zend_string *mbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTAROOT function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquotaroot(imap_le_struct->imap_stream, ZSTR_VAL(mbox))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquotaroot failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_set_quota(resource stream_id, string qroot, int mailbox_size) + Will set the quota for qroot mailbox */ +PHP_FUNCTION(imap_set_quota) +{ + zval *streamind; + zend_string *qroot; + zend_long mailbox_size; + pils *imap_le_struct; + STRINGLIST limits; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &streamind, &qroot, &mailbox_size) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + limits.text.data = (unsigned char*)"STORAGE"; + limits.text.size = mailbox_size; + limits.next = NIL; + + RETURN_BOOL(imap_setquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot), &limits)); +} +/* }}} */ + +/* {{{ proto bool imap_setacl(resource stream_id, string mailbox, string id, string rights) + Sets the ACL for a given mailbox */ +PHP_FUNCTION(imap_setacl) +{ + zval *streamind; + zend_string *mailbox, *id, *rights; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &mailbox, &id, &rights) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(imap_setacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox), ZSTR_VAL(id), ZSTR_VAL(rights))); +} +/* }}} */ + +/* {{{ proto array imap_getacl(resource stream_id, string mailbox) + Gets the ACL for a given mailbox */ +PHP_FUNCTION(imap_getacl) +{ + zval *streamind; + zend_string *mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* initializing the special array for the return values */ + array_init(return_value); + + IMAPG(imap_acl_list) = return_value; + + /* set the callback for the GET_ACL function */ + mail_parameters(NIL, SET_ACL, (void *) mail_getacl); + if (!imap_getacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox))) { + php_error(E_WARNING, "c-client imap_getacl failed"); + zval_dtor(return_value); + RETURN_FALSE; + } + + IMAPG(imap_acl_list) = NIL; +} +/* }}} */ +#endif /* HAVE_IMAP2000 || HAVE_IMAP2001 */ + +/* {{{ proto bool imap_expunge(resource stream_id) + Permanently delete all messages marked for deletion */ +PHP_FUNCTION(imap_expunge) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_expunge (imap_le_struct->imap_stream); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_gc(resource stream_id, int flags) + This function garbage collects (purges) the cache of entries of a specific type. */ +PHP_FUNCTION(imap_gc) +{ + zval *streamind; + pils *imap_le_struct; + zend_long flags; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(GC_TEXTS | GC_ELT | GC_ENV)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_gc(imap_le_struct->imap_stream, flags); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_close(resource stream_id [, int options]) + Close an IMAP stream */ +PHP_FUNCTION(imap_close) +{ + zval *streamind; + pils *imap_le_struct=NULL; + zend_long options = 0, flags = NIL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &streamind, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc == 2) { + flags = options; + + /* Check that flags is exactly equal to PHP_EXPUNGE or zero */ + if (flags && ((flags & ~PHP_EXPUNGE) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + /* Do the translation from PHP's internal PHP_EXPUNGE define to c-client's CL_EXPUNGE */ + if (flags & PHP_EXPUNGE) { + flags ^= PHP_EXPUNGE; + flags |= CL_EXPUNGE; + } + imap_le_struct->flags = flags; + } + + zend_list_close(Z_RES_P(streamind)); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imap_headers(resource stream_id) + Returns headers for all messages in a mailbox */ +PHP_FUNCTION(imap_headers) +{ + zval *streamind; + pils *imap_le_struct; + unsigned long i; + char *t; + unsigned int msgno; + char tmp[MAILTMPLEN]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* Initialize return array */ + array_init(return_value); + + for (msgno = 1; msgno <= imap_le_struct->imap_stream->nmsgs; msgno++) { + MESSAGECACHE * cache = mail_elt (imap_le_struct->imap_stream, msgno); + mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL); + tmp[0] = cache->recent ? (cache->seen ? 'R': 'N') : ' '; + tmp[1] = (cache->recent | cache->seen) ? ' ' : 'U'; + tmp[2] = cache->flagged ? 'F' : ' '; + tmp[3] = cache->answered ? 'A' : ' '; + tmp[4] = cache->deleted ? 'D' : ' '; + tmp[5] = cache->draft ? 'X' : ' '; + snprintf(tmp + 6, sizeof(tmp) - 6, "%4ld) ", cache->msgno); + mail_date(tmp+11, cache); + tmp[22] = ' '; + tmp[23] = '\0'; + mail_fetchfrom(tmp+23, imap_le_struct->imap_stream, msgno, (long)20); + strcat(tmp, " "); + if ((i = cache->user_flags)) { + strcat(tmp, "{"); + while (i) { + strlcat(tmp, imap_le_struct->imap_stream->user_flags[find_rightmost_bit (&i)], sizeof(tmp)); + if (i) strlcat(tmp, " ", sizeof(tmp)); + } + strlcat(tmp, "} ", sizeof(tmp)); + } + mail_fetchsubject(t = tmp + strlen(tmp), imap_le_struct->imap_stream, msgno, (long)25); + snprintf(t += strlen(t), sizeof(tmp) - strlen(tmp), " (%ld chars)", cache->rfc822_size); + add_next_index_string(return_value, tmp); + } +} +/* }}} */ + +/* {{{ proto string imap_body(resource stream_id, int msg_no [, int options]) + Read the message body */ +PHP_FUNCTION(imap_body) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + int msgindex, argc = ZEND_NUM_ARGS(); + char *body; + unsigned long body_len = 0; + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + body = mail_fetchtext_full (imap_le_struct->imap_stream, msgno, &body_len, (argc == 3 ? flags : NIL)); + if (body_len == 0) { + RETVAL_EMPTY_STRING(); + } else { + RETVAL_STRINGL(body, body_len); + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_copy(resource stream_id, string msglist, string mailbox [, int options]) + Copy specified message to a mailbox */ +PHP_FUNCTION(imap_mail_copy) +{ + zval *streamind; + zend_long options = 0; + zend_string *seq, *folder; + int argc = ZEND_NUM_ARGS(); + pils *imap_le_struct; + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? options : NIL)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_move(resource stream_id, string sequence, string mailbox [, int options]) + Move specified message to a mailbox */ +PHP_FUNCTION(imap_mail_move) +{ + zval *streamind; + zend_string *seq, *folder; + zend_long options = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? (options | CP_MOVE) : CP_MOVE)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_createmailbox(resource stream_id, string mailbox) + Create a new mailbox */ +PHP_FUNCTION(imap_createmailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_create(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_renamemailbox(resource stream_id, string old_name, string new_name) + Rename a mailbox */ +PHP_FUNCTION(imap_renamemailbox) +{ + zval *streamind; + zend_string *old_mailbox, *new_mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &old_mailbox, &new_mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_rename(imap_le_struct->imap_stream, ZSTR_VAL(old_mailbox), ZSTR_VAL(new_mailbox)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_deletemailbox(resource stream_id, string mailbox) + Delete a mailbox */ +PHP_FUNCTION(imap_deletemailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_delete(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_list(resource stream_id, string ref, string pattern) + Read the list of mailboxes */ +PHP_FUNCTION(imap_list) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto array imap_getmailboxes(resource stream_id, string ref, string pattern) + Reads the list of mailboxes and returns a full array of objects containing name, attributes, and delimiter */ +/* Author: CJH */ +PHP_FUNCTION(imap_list_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects mailbox list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_folder_objects) = IMAPG(imap_folder_objects_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_folder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist(&IMAPG(imap_folder_objects), &IMAPG(imap_folder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto array imap_listscan(resource stream_id, string ref, string pattern, string content) + Read list of mailboxes containing a certain string */ +PHP_FUNCTION(imap_listscan) +{ + zval *streamind; + zend_string *ref, *pat, *content; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &ref, &pat, &content) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + IMAPG(imap_folders) = NIL; + mail_scan(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat), ZSTR_VAL(content)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto object imap_check(resource stream_id) + Get mailbox properties */ +PHP_FUNCTION(imap_check) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_ping (imap_le_struct->imap_stream) == NIL) { + RETURN_FALSE; + } + + if (imap_le_struct->imap_stream && imap_le_struct->imap_stream->mailbox) { + rfc822_date(date); + object_init(return_value); + add_property_string(return_value, "Date", date); + add_property_string(return_value, "Driver", imap_le_struct->imap_stream->dtb->name); + add_property_string(return_value, "Mailbox", imap_le_struct->imap_stream->mailbox); + add_property_long(return_value, "Nmsgs", imap_le_struct->imap_stream->nmsgs); + add_property_long(return_value, "Recent", imap_le_struct->imap_stream->recent); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_delete(resource stream_id, int msg_no [, int options]) + Mark a message for deletion */ +PHP_FUNCTION(imap_delete) +{ + zval *streamind, *sequence; + pils *imap_le_struct; + zend_long flags = 0; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_setflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_undelete(resource stream_id, int msg_no [, int flags]) + Remove the delete flag from a message */ +PHP_FUNCTION(imap_undelete) +{ + zval *streamind, *sequence; + zend_long flags = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_clearflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto object imap_headerinfo(resource stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]]) + Read the headers of the message */ +PHP_FUNCTION(imap_headerinfo) +{ + zval *streamind; + zend_string *defaulthost = NULL; + int argc = ZEND_NUM_ARGS(); + zend_long msgno, fromlength, subjectlength; + pils *imap_le_struct; + MESSAGECACHE *cache; + ENVELOPE *en; + char dummy[2000], fulladdress[MAILTMPLEN + 1]; + + if (zend_parse_parameters(argc, "rl|llS", &streamind, &msgno, &fromlength, &subjectlength, &defaulthost) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc >= 3) { + if (fromlength < 0 || fromlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "From length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + fromlength = 0x00; + } + if (argc >= 4) { + if (subjectlength < 0 || subjectlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "Subject length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + subjectlength = 0x00; + } + + PHP_IMAP_CHECK_MSGNO(msgno); + + if (mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL)) { + cache = mail_elt(imap_le_struct->imap_stream, msgno); + } else { + RETURN_FALSE; + } + + en = mail_fetchenvelope(imap_le_struct->imap_stream, msgno); + + /* call a function to parse all the text, so that we can use the + same function to parse text from other sources */ + _php_make_header_object(return_value, en); + + /* now run through properties that are only going to be returned + from a server, not text headers */ + add_property_string(return_value, "Recent", cache->recent ? (cache->seen ? "R": "N") : " "); + add_property_string(return_value, "Unseen", (cache->recent | cache->seen) ? " " : "U"); + add_property_string(return_value, "Flagged", cache->flagged ? "F" : " "); + add_property_string(return_value, "Answered", cache->answered ? "A" : " "); + add_property_string(return_value, "Deleted", cache->deleted ? "D" : " "); + add_property_string(return_value, "Draft", cache->draft ? "X" : " "); + + snprintf(dummy, sizeof(dummy), "%4ld", cache->msgno); + add_property_string(return_value, "Msgno", dummy); + + mail_date(dummy, cache); + add_property_string(return_value, "MailDate", dummy); + + snprintf(dummy, sizeof(dummy), "%ld", cache->rfc822_size); + add_property_string(return_value, "Size", dummy); + + add_property_long(return_value, "udate", mail_longdate(cache)); + + if (en->from && fromlength) { + fulladdress[0] = 0x00; + mail_fetchfrom(fulladdress, imap_le_struct->imap_stream, msgno, fromlength); + add_property_string(return_value, "fetchfrom", fulladdress); + } + if (en->subject && subjectlength) { + fulladdress[0] = 0x00; + mail_fetchsubject(fulladdress, imap_le_struct->imap_stream, msgno, subjectlength); + add_property_string(return_value, "fetchsubject", fulladdress); + } +} +/* }}} */ + +/* {{{ proto object imap_rfc822_parse_headers(string headers [, string default_host]) + Parse a set of mail headers contained in a string, and return an object similar to imap_headerinfo() */ +PHP_FUNCTION(imap_rfc822_parse_headers) +{ + zend_string *headers, *defaulthost = NULL; + ENVELOPE *en; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "S|S", &headers, &defaulthost) == FAILURE) { + return; + } + + if (argc == 2) { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, ZSTR_VAL(defaulthost), NIL); + } else { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, "UNKNOWN", NIL); + } + + /* call a function to parse all the text, so that we can use the + same function no matter where the headers are from */ + _php_make_header_object(return_value, en); + mail_free_envelope(&en); +} +/* }}} */ + +/* KMLANG */ +/* {{{ proto array imap_lsub(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes */ +PHP_FUNCTION(imap_lsub) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_sfolders) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_sfolders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_sfolders)); + IMAPG(imap_sfolders) = IMAPG(imap_sfolders_tail) = NIL; +} +/* }}} */ + +/* {{{ proto array imap_getsubscribed(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes, in the same format as imap_getmailboxes() */ +/* Author: CJH */ +PHP_FUNCTION(imap_lsub_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_sfolder_objects) = IMAPG(imap_sfolder_objects_tail) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_sfolder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist (&IMAPG(imap_sfolder_objects), &IMAPG(imap_sfolder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto bool imap_subscribe(resource stream_id, string mailbox) + Subscribe to a mailbox */ +PHP_FUNCTION(imap_subscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_subscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_unsubscribe(resource stream_id, string mailbox) + Unsubscribe from a mailbox */ +PHP_FUNCTION(imap_unsubscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_unsubscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object imap_fetchstructure(resource stream_id, int msg_no [, int options]) + Read the full structure of a message */ +PHP_FUNCTION(imap_fetchstructure) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + BODY *body; + int msgindex, argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~FT_UID) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (msgno < 1) { + RETURN_FALSE; + } + + object_init(return_value); + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + PHP_IMAP_CHECK_MSGNO(msgindex); + + mail_fetchstructure_full(imap_le_struct->imap_stream, msgno, &body , (argc == 3 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + + _php_imap_add_body(return_value, body); +} +/* }}} */ + +/* {{{ proto string imap_fetchbody(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section */ +PHP_FUNCTION(imap_fetchbody) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetchbody_full(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + + +/* {{{ proto string imap_fetchmime(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section's MIME headers */ +PHP_FUNCTION(imap_fetchmime) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetch_mime(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body MIME information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + +/* {{{ proto bool imap_savebody(resource stream_id, string|resource file, int msg_no[, string section = ""[, int options = 0]]) + Save a specific body section to a file */ +PHP_FUNCTION(imap_savebody) +{ + zval *stream, *out; + pils *imap_ptr = NULL; + php_stream *writer = NULL; + zend_string *section = NULL; + int close_stream = 1; + zend_long msgno, flags = 0; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "rzl|Sl", &stream, &out, &msgno, §ion, &flags)) { + RETURN_FALSE; + } + + if ((imap_ptr = (pils *)zend_fetch_resource(Z_RES_P(stream), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (!imap_ptr) { + RETURN_FALSE; + } + + switch (Z_TYPE_P(out)) + { + case IS_LONG: + case IS_RESOURCE: + close_stream = 0; + php_stream_from_zval(writer, out); + break; + + default: + convert_to_string_ex(out); + writer = php_stream_open_wrapper(Z_STRVAL_P(out), "wb", REPORT_ERRORS, NULL); + break; + } + + if (!writer) { + RETURN_FALSE; + } + + IMAPG(gets_stream) = writer; + mail_parameters(NIL, SET_GETS, (void *) php_mail_gets); + mail_fetchbody_full(imap_ptr->imap_stream, msgno, section?ZSTR_VAL(section):"", NULL, flags); + mail_parameters(NIL, SET_GETS, (void *) NULL); + IMAPG(gets_stream) = NULL; + + if (close_stream) { + php_stream_close(writer); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string imap_base64(string text) + Decode BASE64 encoded text */ +PHP_FUNCTION(imap_base64) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_base64((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_qprint(string text) + Convert a quoted-printable string to an 8-bit string */ +PHP_FUNCTION(imap_qprint) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_qprint((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_8bit(string text) + Convert an 8-bit string to a quoted-printable string */ +PHP_FUNCTION(imap_8bit) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_8bit((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_binary(string text) + Convert an 8bit string to a base64 string */ +PHP_FUNCTION(imap_binary) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char*)rfc822_binary(ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto object imap_mailboxmsginfo(resource stream_id) + Returns info about the current mailbox */ +PHP_FUNCTION(imap_mailboxmsginfo) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + unsigned 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; + zend_long charset_token, encoding_token, end_token, end, offset=0, i; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + array_init(return_value); + + string = ZSTR_VAL(str); + end = ZSTR_LEN(str); + + charset = (char *) safe_emalloc((end + 1), 2, 0); + text = &charset[end + 1]; + while (offset < end) { /* Reached end of the string? */ + if ((charset_token = (zend_long)php_memnstr(&string[offset], "=?", 2, string + end))) { /* Is there anything encoded in the string? */ + charset_token -= (zend_long)string; + if (offset != charset_token) { /* Is there anything before the encoded data? */ + /* Retrieve unencoded data that is found before encoded data */ + memcpy(text, &string[offset], charset_token-offset); + text[charset_token - offset] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + } + if ((encoding_token = (zend_long)php_memnstr(&string[charset_token+2], "?", 1, string+end))) { /* Find token for encoding */ + encoding_token -= (zend_long)string; + if ((end_token = (zend_long)php_memnstr(&string[encoding_token+3], "?=", 2, string+end))) { /* Find token for end of encoded data */ + end_token -= (zend_long)string; + memcpy(charset, &string[charset_token + 2], encoding_token - (charset_token + 2)); /* Extract charset encoding */ + charset[encoding_token-(charset_token + 2)] = 0x00; + encoding=string[encoding_token + 1]; /* Extract encoding from string */ + memcpy(text, &string[encoding_token + 3], end_token - (encoding_token + 3)); /* Extract text */ + text[end_token - (encoding_token + 3)] = 0x00; + decode = text; + if (encoding == 'q' || encoding == 'Q') { /* Decode 'q' encoded data */ + for(i=0; text[i] != 0x00; i++) if (text[i] == '_') text[i] = ' '; /* Replace all *_' with space. */ + decode = (char *)rfc822_qprint((unsigned char *) text, strlen(text), &newlength); + } else if (encoding == 'b' || encoding == 'B') { + decode = (char *)rfc822_base64((unsigned char *) text, strlen(text), &newlength); /* Decode 'B' encoded data */ + } + if (decode == NULL) { + efree(charset); + zval_dtor(return_value); + RETURN_FALSE; + } + object_init(&myobject); + add_property_string(&myobject, "charset", charset); + add_property_string(&myobject, "text", decode); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + /* only free decode if it was allocated by rfc822_qprint or rfc822_base64 */ + if (decode != text) { + fs_give((void**)&decode); + } + + offset = end_token+2; + for (i = 0; (string[offset + i] == ' ') || (string[offset + i] == 0x0a) || (string[offset + i] == 0x0d) || (string[offset + i] == '\t'); i++); + if ((string[offset + i] == '=') && (string[offset + i + 1] == '?') && (offset + i < end)) { + offset += i; + } + continue; /*/ Iterate the loop again please. */ + } + } + } else { + /* Just some tweaking to optimize the code, and get the end statements work in a general manner. + * If we end up here we didn't find a position for "charset_token", + * so we need to set it to the start of the yet unextracted data. + */ + charset_token = offset; + } + /* Return the rest of the data as unencoded, as it was either unencoded or was missing separators + which rendered the remainder of the string impossible for us to decode. */ + memcpy(text, &string[charset_token], end - charset_token); /* Extract unencoded text from string */ + text[end - charset_token] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + offset = end; /* We have reached the end of the string. */ + } + efree(charset); +} +/* }}} */ + +/* Support Functions */ + +#ifdef HAVE_RFC822_OUTPUT_ADDRESS_LIST +/* {{{ _php_rfc822_soutr + */ +static long _php_rfc822_soutr (void *stream, char *string) +{ + smart_str *ret = (smart_str*)stream; + int len = strlen(string); + + smart_str_appendl(ret, string, len); + return LONGT; +} +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[MAILTMPLEN]; + smart_str ret = {0}; + RFC822BUFFER buf; + + buf.beg = address; + buf.cur = buf.beg; + buf.end = buf.beg + sizeof(address) - 1; + buf.s = &ret; + buf.f = _php_rfc822_soutr; + rfc822_output_address_list(&buf, addresslist, 0, NULL); + rfc822_output_flush(&buf); + smart_str_0(&ret); + return ret.s; +} +/* }}} */ + +#else + +/* {{{ _php_rfc822_len + * Calculate string length based on imap's rfc822_cat function. + */ +static int _php_rfc822_len(char *str) +{ + int len; + char *p; + + if (!str || !*str) { + return 0; + } + + /* strings with special characters will need to be quoted, as a safety measure we + * add 2 bytes for the quotes just in case. + */ + len = strlen(str) + 2; + p = str; + /* rfc822_cat() will escape all " and \ characters, therefor we need to increase + * our buffer length to account for these characters. + */ + while ((p = strpbrk(p, "\\\""))) { + p++; + len++; + } + + return len; +} +/* }}} */ + +/* {{{ _php_imap_get_address_size + */ +static int _php_imap_address_size (ADDRESS *addresslist) +{ + ADDRESS *tmp; + int ret=0, num_ent=0; + + tmp = addresslist; + + if (tmp) do { + ret += _php_rfc822_len(tmp->personal); + ret += _php_rfc822_len(tmp->adl); + ret += _php_rfc822_len(tmp->mailbox); + ret += _php_rfc822_len(tmp->host); + num_ent++; + } while ((tmp = tmp->next)); + + /* + * rfc822_write_address_full() needs some extra space for '<>,', etc. + * for this perpouse we allocate additional PHP_IMAP_ADDRESS_SIZE_BUF bytes + * by default this buffer is 10 bytes long + */ + ret += (ret) ? num_ent*PHP_IMAP_ADDRESS_SIZE_BUF : 0; + + return ret; +} + +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[SENDBUFLEN]; + + if (_php_imap_address_size(addresslist) >= SENDBUFLEN) { + 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 = (zend_long) mail_parameters(NIL, timeout_type, NIL); + RETURN_LONG(timeout); + } else if (timeout >= 0) { + switch (ttype) { + case 1: + timeout_type = SET_OPENTIMEOUT; + break; + case 2: + timeout_type = SET_READTIMEOUT; + break; + case 3: + timeout_type = SET_WRITETIMEOUT; + break; + case 4: + timeout_type = SET_CLOSETIMEOUT; + break; + default: + RETURN_FALSE; + break; + } + + timeout = (zend_long) mail_parameters(NIL, timeout_type, (void *) timeout); + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +#define GETS_FETCH_SIZE 8196LU +static char *php_mail_gets(readfn_t f, void *stream, unsigned long size, GETS_DATA *md) /* {{{ */ +{ + + /* write to the gets stream if it is set, + otherwise forward to c-clients gets */ + if (IMAPG(gets_stream)) { + char buf[GETS_FETCH_SIZE]; + + while (size) { + unsigned long read; + + if (size > GETS_FETCH_SIZE) { + read = GETS_FETCH_SIZE; + size -=GETS_FETCH_SIZE; + } else { + read = size; + size = 0; + } + + if (!f(stream, read, buf)) { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + break; + } else if (read != php_stream_write(IMAPG(gets_stream), buf, read)) { + php_error_docref(NULL, E_WARNING, "Failed to write to stream"); + break; + } + } + return NULL; + } else { + char *buf = pemalloc(size + 1, 1); + + if (f(stream, size, buf)) { + buf[size] = '\0'; + } else { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + free(buf); + buf = NULL; + } + return buf; + } +} +/* }}} */ + +/* {{{ Interfaces to C-client + */ +PHP_IMAP_EXPORT void mm_searched(MAILSTREAM *stream, unsigned long number) +{ + MESSAGELIST *cur = NIL; + + if (IMAPG(imap_messages) == NIL) { + IMAPG(imap_messages) = mail_newmessagelist(); + IMAPG(imap_messages)->msgid = number; + IMAPG(imap_messages)->next = NIL; + IMAPG(imap_messages_tail) = IMAPG(imap_messages); + } else { + cur = IMAPG(imap_messages_tail); + cur->next = mail_newmessagelist(); + cur = cur->next; + cur->msgid = number; + cur->next = NIL; + IMAPG(imap_messages_tail) = cur; + } +} + +PHP_IMAP_EXPORT void mm_exists(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_expunged(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_flags(MAILSTREAM *stream, unsigned long number) +{ +} + +/* Author: CJH */ +PHP_IMAP_EXPORT void mm_notify(MAILSTREAM *stream, char *str, long errflg) +{ + STRINGLIST *cur = NIL; + + if (strncmp(str, "[ALERT] ", 8) == 0) { + if (IMAPG(imap_alertstack) == NIL) { + IMAPG(imap_alertstack) = mail_newstringlist(); + IMAPG(imap_alertstack)->LSIZE = strlen((char*)(IMAPG(imap_alertstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_alertstack)->next = NIL; + } else { + cur = IMAPG(imap_alertstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newstringlist (); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_list(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build up a the new array of objects */ + /* Author: CJH */ + if (IMAPG(imap_folder_objects) == NIL) { + IMAPG(imap_folder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_folder_objects)->LSIZE=strlen((char*)(IMAPG(imap_folder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folder_objects)->delimiter = delimiter; + IMAPG(imap_folder_objects)->attributes = attributes; + IMAPG(imap_folder_objects)->next = NIL; + IMAPG(imap_folder_objects_tail) = IMAPG(imap_folder_objects); + } else { + ocur=IMAPG(imap_folder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE = strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_folder_objects_tail) = ocur; + } + + } else { + /* build the old IMAPG(imap_folders) variable to allow old imap_listmailbox() to work */ + if (!(attributes & LATT_NOSELECT)) { + if (IMAPG(imap_folders) == NIL) { + IMAPG(imap_folders)=mail_newstringlist(); + IMAPG(imap_folders)->LSIZE=strlen((char*)(IMAPG(imap_folders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folders)->next=NIL; + IMAPG(imap_folders_tail) = IMAPG(imap_folders); + } else { + cur=IMAPG(imap_folders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_folders_tail) = cur; + } + } + } +} + +PHP_IMAP_EXPORT void mm_lsub(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build the array of objects */ + /* Author: CJH */ + if (IMAPG(imap_sfolder_objects) == NIL) { + IMAPG(imap_sfolder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_sfolder_objects)->LSIZE = strlen((char*)(IMAPG(imap_sfolder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolder_objects)->delimiter = delimiter; + IMAPG(imap_sfolder_objects)->attributes = attributes; + IMAPG(imap_sfolder_objects)->next = NIL; + IMAPG(imap_sfolder_objects_tail) = IMAPG(imap_sfolder_objects); + } else { + ocur=IMAPG(imap_sfolder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE=strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_sfolder_objects_tail) = ocur; + } + } else { + /* build the old simple array for imap_listsubscribed() */ + if (IMAPG(imap_sfolders) == NIL) { + IMAPG(imap_sfolders)=mail_newstringlist(); + IMAPG(imap_sfolders)->LSIZE=strlen((char*)(IMAPG(imap_sfolders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolders)->next=NIL; + IMAPG(imap_sfolders_tail) = IMAPG(imap_sfolders); + } else { + cur=IMAPG(imap_sfolders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_sfolders_tail) = cur; + } + } +} + +PHP_IMAP_EXPORT void mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status) +{ + + IMAPG(status_flags)=status->flags; + if (IMAPG(status_flags) & SA_MESSAGES) { + IMAPG(status_messages)=status->messages; + } + if (IMAPG(status_flags) & SA_RECENT) { + IMAPG(status_recent)=status->recent; + } + if (IMAPG(status_flags) & SA_UNSEEN) { + IMAPG(status_unseen)=status->unseen; + } + if (IMAPG(status_flags) & SA_UIDNEXT) { + IMAPG(status_uidnext)=status->uidnext; + } + if (IMAPG(status_flags) & SA_UIDVALIDITY) { + IMAPG(status_uidvalidity)=status->uidvalidity; + } +} + +PHP_IMAP_EXPORT void mm_log(char *str, long errflg) +{ + ERRORLIST *cur = NIL; + + /* Author: CJH */ + if (errflg != NIL) { /* CJH: maybe put these into a more comprehensive log for debugging purposes? */ + if (IMAPG(imap_errorstack) == NIL) { + IMAPG(imap_errorstack) = mail_newerrorlist(); + IMAPG(imap_errorstack)->LSIZE = strlen((char*)(IMAPG(imap_errorstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_errorstack)->errflg = errflg; + IMAPG(imap_errorstack)->next = NIL; + } else { + cur = IMAPG(imap_errorstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newerrorlist(); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->errflg = errflg; + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_dlog(char *str) +{ + /* CJH: this is for debugging; it might be useful to allow setting + the stream to debug mode and capturing this somewhere - syslog? + php debugger? */ +} + +PHP_IMAP_EXPORT void mm_login(NETMBX *mb, char *user, char *pwd, long trial) +{ + + if (*mb->user) { + strlcpy (user, mb->user, MAILTMPLEN); + } else { + strlcpy (user, IMAPG(imap_user), MAILTMPLEN); + } + strlcpy (pwd, IMAPG(imap_password), MAILTMPLEN); +} + +PHP_IMAP_EXPORT void mm_critical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT void mm_nocritical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT long mm_diskerror(MAILSTREAM *stream, long errcode, long serious) +{ + return 1; +} + +PHP_IMAP_EXPORT void mm_fatal(char *str) +{ +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/imap/tests/bug77020.phpt b/ext/imap/tests/bug77020.phpt new file mode 100644 index 0000000000000..8a65232eec6d3 --- /dev/null +++ b/ext/imap/tests/bug77020.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #77020 (null pointer dereference in imap_mail) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECTF-- +Warning: imap_mail(): No message string in mail command in %s on line %d +%s +===DONE=== From 01f7109be4214fea88a460db8af4d2664396b730 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 16:16:45 +0000 Subject: [PATCH 23/43] commit patch 25959239 --- NEWS | 4 + NEWS.orig | 3 + UPGRADING | 7 + UPGRADING.orig | 817 +++++++++++++++++++++++++++++++++++ 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, 1108 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 72bf88fcfc00e..d5d652ebcaf37 100644 --- a/NEWS +++ b/NEWS @@ -229,6 +229,10 @@ PHP NEWS . Fixed bug #72061 (Out-of-bounds reads in zif_grapheme_stripos with negative offset). (Stas) +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + - ODBC: . Fixed bug #63171 (Script hangs after max_execution_time). (Remi) diff --git a/NEWS.orig b/NEWS.orig index b018631d8b0b7..72bf88fcfc00e 100644 --- a/NEWS.orig +++ b/NEWS.orig @@ -111,6 +111,9 @@ PHP NEWS . Fixed bug #72069 (Behavior \JsonSerializable different from json_encode). (Laruence) +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + - Mbstring: . Fixed bug #72164 (Null Pointer Dereference - mb_ereg_replace). (Laruence) diff --git a/UPGRADING b/UPGRADING index 46a93235721a3..4d17c163fefa3 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..46a93235721a3 --- /dev/null +++ b/UPGRADING.orig @@ -0,0 +1,817 @@ +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 (