diff --git a/NEWS b/NEWS index 98d9c37492162..06a6abb4d1c54 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,17 @@ PHP NEWS - FPM: . Fixed failed debug assertion when php_admin_value setting fails. (ilutov) +- Filter: + . Fixed bug GH-16993 (filter_var_array with FILTER_VALIDATE_INT|FILTER_NULL_ON_FAILURE + should emit warning for invalid filter usage). (alexandre-daubois) + +- Intl: + . Added grapheme_strpos(), grapheme_stripos(), grapheme_strrpos(), + grapheme_strripos(), grapheme_strstr(), grapheme_stristr() and + grapheme_levenshtein() functions add $locale parameter (Yuya Hamada). + . Fixed bug GH-11952 (Fix locale strings canonicalization for IntlDateFormatter + and NumberFormatter). (alexandre-daubois) + - Opcache: . Fixed bug GH-19486 (Incorrect opline after deoptimization). (Arnaud) . Fixed bug GH-19601 (Wrong JIT stack setup on aarch64/clang). (Arnaud) diff --git a/UPGRADING b/UPGRADING index ab071435ffe58..875aa59cdff1b 100644 --- a/UPGRADING +++ b/UPGRADING @@ -565,6 +565,10 @@ PHP 8.5 UPGRADE NOTES TransLiterator::getErrorCode(), and TransLiterator::getErrorMessage() have dropped the false from the return type union. Returning false was actually never possible. + . grapheme_strpos(), grapheme_stripos(), grapheme_strrpos(), + grapheme_strripos(), grapheme_strstr(), grapheme_stristr() and + grapheme_levenshtein() functions add $locale parameter. + RFC: https://wiki.php.net/rfc/grapheme_add_locale_for_case_insensitive - LDAP: . ldap_get_option() now accepts a NULL connection, as ldap_set_option(), diff --git a/ext/filter/filter.c b/ext/filter/filter.c index 091e253842d52..4a928379877bc 100644 --- a/ext/filter/filter.c +++ b/ext/filter/filter.c @@ -627,6 +627,14 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op zval nval; ZVAL_DEREF(tmp); ZVAL_DUP(&nval, tmp); + + if (Z_TYPE_P(arg_elm) != IS_ARRAY) { + zend_long filter_id = zval_get_long(arg_elm); + if (!PHP_FILTER_ID_EXISTS(filter_id)) { + php_error_docref(NULL, E_WARNING, "Unknown filter with ID " ZEND_LONG_FMT, filter_id); + } + } + php_filter_call(&nval, -1, Z_TYPE_P(arg_elm) == IS_ARRAY ? Z_ARRVAL_P(arg_elm) : NULL, Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm), diff --git a/ext/filter/tests/039.phpt b/ext/filter/tests/039.phpt index 571d3a3265fc0..229feecd4475b 100644 --- a/ext/filter/tests/039.phpt +++ b/ext/filter/tests/039.phpt @@ -125,12 +125,18 @@ array(1) { ["var_name"]=> NULL } + +Warning: filter_var_array(): Unknown filter with ID -1 in %s on line %d array(1) { ["var_name"]=> string(0) "" } -- (5) + +Warning: filter_var_array(): Unknown filter with ID -1 in %s on line %d filter_var_array(): Argument #2 ($options) cannot contain empty keys + +Warning: filter_var_array(): Unknown filter with ID 0 in %s on line %d filter_var_array(): Argument #2 ($options) cannot contain empty keys Warning: filter_var_array(): Unknown filter with ID -1 in %s on line %d diff --git a/ext/filter/tests/gh16993.phpt b/ext/filter/tests/gh16993.phpt new file mode 100644 index 0000000000000..c264296fc15e0 --- /dev/null +++ b/ext/filter/tests/gh16993.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-16993: filter_var_array should emit warning for unknown filters +--EXTENSIONS-- +filter +--FILE-- + '42']; + +$result = filter_var_array($data, ['test' => FILTER_VALIDATE_INT|FILTER_NULL_ON_FAILURE]); +var_dump($result); + +$result = filter_var_array($data, ['test' => ['filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_NULL_ON_FAILURE]]); +var_dump($result); +?> +--EXPECTF-- +Warning: filter_var_array(): Unknown filter with ID 134217985 in %s on line %d +array(1) { + ["test"]=> + string(2) "42" +} +array(1) { + ["test"]=> + int(42) +} diff --git a/ext/intl/dateformat/dateformat_create.cpp b/ext/intl/dateformat/dateformat_create.cpp index d2d65bc267326..b6bac7a0a744a 100644 --- a/ext/intl/dateformat/dateformat_create.cpp +++ b/ext/intl/dateformat/dateformat_create.cpp @@ -22,6 +22,7 @@ extern "C" { #include #include +#include #include "php_intl.h" #include "dateformat_create.h" @@ -106,7 +107,12 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS) if (locale_len == 0) { locale_str = (char *) intl_locale_get_default(); } - locale = Locale::createFromName(locale_str); + + char* canonicalized_locale = canonicalize_locale_string(locale_str); + const char* final_locale = canonicalized_locale ? canonicalized_locale : locale_str; + const char* stored_locale = canonicalized_locale ? canonicalized_locale : locale_str; + + locale = Locale::createFromName(final_locale); /* get*Name accessors being set does not preclude being bogus */ if (locale.isBogus() || ((locale_len == 1 && locale_str[0] != 'C') || (locale_len > 1 && strlen(locale.getISO3Language()) == 0))) { zend_argument_value_error(1, "\"%s\" is invalid", locale_str); @@ -144,7 +150,7 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS) } DATE_FORMAT_OBJECT(dfo) = udat_open((UDateFormatStyle)time_type, - (UDateFormatStyle)date_type, locale_str, NULL, 0, svalue, + (UDateFormatStyle)date_type, final_locale, NULL, 0, svalue, slength, &INTL_DATA_ERROR_CODE(dfo)); if (pattern_str && pattern_str_len > 0) { @@ -176,9 +182,13 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS) dfo->date_type = date_type; dfo->time_type = time_type; dfo->calendar = calendar_type; - dfo->requested_locale = estrdup(locale_str); + /* Store the canonicalized locale, or fallback to original if canonicalization failed */ + dfo->requested_locale = estrdup(stored_locale); error: + if (canonicalized_locale) { + efree(canonicalized_locale); + } if (svalue) { efree(svalue); } diff --git a/ext/intl/formatter/formatter_main.c b/ext/intl/formatter/formatter_main.c index 6f92b7e787be4..d6d69f57277f9 100644 --- a/ext/intl/formatter/formatter_main.c +++ b/ext/intl/formatter/formatter_main.c @@ -64,12 +64,18 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS) return FAILURE; } - /* Create an ICU number formatter. */ - FORMATTER_OBJECT(nfo) = unum_open(style, spattern, spattern_len, locale, NULL, &INTL_DATA_ERROR_CODE(nfo)); + char* canonicalized_locale = canonicalize_locale_string(locale); + const char* final_locale = canonicalized_locale ? canonicalized_locale : locale; - if(spattern) { + FORMATTER_OBJECT(nfo) = unum_open(style, spattern, spattern_len, final_locale, NULL, &INTL_DATA_ERROR_CODE(nfo)); + + if (spattern) { efree(spattern); } + + if (canonicalized_locale) { + efree(canonicalized_locale); + } INTL_CTOR_CHECK_STATUS(nfo, "number formatter creation failed"); return SUCCESS; diff --git a/ext/intl/grapheme/grapheme_string.c b/ext/intl/grapheme/grapheme_string.c index 8abef7dd560aa..28d3130c3959d 100644 --- a/ext/intl/grapheme/grapheme_string.c +++ b/ext/intl/grapheme/grapheme_string.c @@ -81,19 +81,20 @@ PHP_FUNCTION(grapheme_strlen) /* {{{ Find position of first occurrence of a string within another */ PHP_FUNCTION(grapheme_strpos) { - char *haystack, *needle; - size_t haystack_len, needle_len; + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; const char *found; zend_long loffset = 0; int32_t offset = 0; size_t noffset = 0; zend_long ret_pos; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(haystack, haystack_len) Z_PARAM_STRING(needle, needle_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if ( OUTSIDE_STRING(loffset, haystack_len) ) { @@ -121,7 +122,7 @@ PHP_FUNCTION(grapheme_strpos) } /* do utf16 part of the strpos */ - ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 0 /* fIgnoreCase */, 0 /* last */ ); + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, /* fIgnoreCase */ 0, /* last */ 0, locale); if ( ret_pos >= 0 ) { RETURN_LONG(ret_pos); @@ -134,19 +135,20 @@ PHP_FUNCTION(grapheme_strpos) /* {{{ Find position of first occurrence of a string within another, ignoring case differences */ PHP_FUNCTION(grapheme_stripos) { - char *haystack, *needle; - size_t haystack_len, needle_len; + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; const char *found; zend_long loffset = 0; int32_t offset = 0; zend_long ret_pos; int is_ascii; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(haystack, haystack_len) Z_PARAM_STRING(needle, needle_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if ( OUTSIDE_STRING(loffset, haystack_len) ) { @@ -185,7 +187,7 @@ PHP_FUNCTION(grapheme_stripos) } /* do utf16 part of the strpos */ - ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 1 /* fIgnoreCase */, 0 /*last */ ); + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, /* fIgnoreCase */ 1, /*last */ 0, locale); if ( ret_pos >= 0 ) { RETURN_LONG(ret_pos); @@ -200,17 +202,19 @@ PHP_FUNCTION(grapheme_stripos) PHP_FUNCTION(grapheme_strrpos) { char *haystack, *needle; - size_t haystack_len, needle_len; + char *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; zend_long loffset = 0; int32_t offset = 0; zend_long ret_pos; int is_ascii; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(haystack, haystack_len) Z_PARAM_STRING(needle, needle_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if ( OUTSIDE_STRING(loffset, haystack_len) ) { @@ -242,7 +246,7 @@ PHP_FUNCTION(grapheme_strrpos) /* else we need to continue via utf16 */ } - ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 0 /* f_ignore_case */, 1/* last */); + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, /* f_ignore_case */ 0, /* last */ 1, locale); if ( ret_pos >= 0 ) { RETURN_LONG(ret_pos); @@ -257,18 +261,19 @@ PHP_FUNCTION(grapheme_strrpos) /* {{{ Find position of last occurrence of a string within another, ignoring case */ PHP_FUNCTION(grapheme_strripos) { - char *haystack, *needle; - size_t haystack_len, needle_len; + char *haystack, *needle, *locale = ""; + size_t haystack_len, needle_len, locale_len = 0; zend_long loffset = 0; int32_t offset = 0; zend_long ret_pos; int is_ascii; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(haystack, haystack_len) Z_PARAM_STRING(needle, needle_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(loffset) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if ( OUTSIDE_STRING(loffset, haystack_len) ) { @@ -309,7 +314,7 @@ PHP_FUNCTION(grapheme_strripos) /* else we need to continue via utf16 */ } - ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 1 /* f_ignore_case */, 1 /*last */); + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, /* f_ignore_case */ 1, /*last */ 1, locale); if ( ret_pos >= 0 ) { RETURN_LONG(ret_pos); @@ -324,10 +329,10 @@ PHP_FUNCTION(grapheme_strripos) /* {{{ Returns part of a string */ PHP_FUNCTION(grapheme_substr) { - char *str; + char *str, *locale = ""; zend_string *u8_sub_str; UChar *ustr; - size_t str_len; + size_t str_len, locale_len = 0; int32_t ustr_len; zend_long lstart = 0, length = 0; int32_t start = 0; @@ -339,11 +344,12 @@ PHP_FUNCTION(grapheme_substr) int32_t (*iter_func)(UBreakIterator *); bool no_length = true; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(str, str_len) Z_PARAM_LONG(lstart) Z_PARAM_OPTIONAL Z_PARAM_LONG_OR_NULL(length, no_length) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if (lstart < INT32_MIN || lstart > INT32_MAX) { @@ -537,17 +543,18 @@ PHP_FUNCTION(grapheme_substr) /* {{{ strstr_common_handler */ static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int f_ignore_case) { - char *haystack, *needle; + char *haystack, *needle, *locale = ""; const char *found; - size_t haystack_len, needle_len; + size_t haystack_len, needle_len, locale_len = 0; int32_t ret_pos, uchar_pos; bool part = false; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(haystack, haystack_len) Z_PARAM_STRING(needle, needle_len) Z_PARAM_OPTIONAL Z_PARAM_BOOL(part) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if ( !f_ignore_case ) { @@ -574,7 +581,7 @@ static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int f_ignore_cas } /* need to work in utf16 */ - ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, 0, &uchar_pos, f_ignore_case, 0 /*last */ ); + ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, 0, &uchar_pos, f_ignore_case, /* last */ 0, locale); if ( ret_pos < 0 ) { RETURN_FALSE; @@ -919,14 +926,17 @@ PHP_FUNCTION(grapheme_levenshtein) zend_long cost_ins = 1; zend_long cost_rep = 1; zend_long cost_del = 1; + char *locale = ""; + size_t locale_len = 0; - ZEND_PARSE_PARAMETERS_START(2, 5) + ZEND_PARSE_PARAMETERS_START(2, 6) Z_PARAM_STR(string1) Z_PARAM_STR(string2) Z_PARAM_OPTIONAL Z_PARAM_LONG(cost_ins) Z_PARAM_LONG(cost_rep) Z_PARAM_LONG(cost_del) + Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); if (cost_ins <= 0 || cost_ins > UINT_MAX / 4) { @@ -1043,7 +1053,7 @@ PHP_FUNCTION(grapheme_levenshtein) RETVAL_FALSE; goto out_bi2; } - UCollator *collator = ucol_open("", &ustatus); + UCollator *collator = ucol_open(locale, &ustatus); if (U_FAILURE(ustatus)) { intl_error_set_code(NULL, ustatus); diff --git a/ext/intl/grapheme/grapheme_util.c b/ext/intl/grapheme/grapheme_util.c index d4b6c1cbfd89a..825eea9468cb3 100644 --- a/ext/intl/grapheme/grapheme_util.c +++ b/ext/intl/grapheme/grapheme_util.c @@ -94,7 +94,7 @@ void grapheme_substr_ascii(char *str, size_t str_len, int32_t f, int32_t l, char /* {{{ grapheme_strpos_utf16 - strrpos using utf16*/ -int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int32_t *puchar_pos, int f_ignore_case, int last) +int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int32_t *puchar_pos, int f_ignore_case, int last, const char* locale) { UChar *uhaystack = NULL, *uneedle = NULL; int32_t uhaystack_len = 0, uneedle_len = 0, char_pos, ret_pos, offset_pos = 0; @@ -136,7 +136,7 @@ int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, } status = U_ZERO_ERROR; - src = usearch_open(uneedle, uneedle_len, uhaystack, uhaystack_len, "", bi, &status); + src = usearch_open(uneedle, uneedle_len, uhaystack, uhaystack_len, locale, bi, &status); STRPOS_CHECK_STATUS(status, "Error creating search object"); if(f_ignore_case) { diff --git a/ext/intl/grapheme/grapheme_util.h b/ext/intl/grapheme/grapheme_util.h index d03194621acf3..9d276a9dcfe26 100644 --- a/ext/intl/grapheme/grapheme_util.h +++ b/ext/intl/grapheme/grapheme_util.h @@ -25,8 +25,8 @@ zend_long grapheme_ascii_check(const unsigned char *day, size_t len); void grapheme_substr_ascii(char *str, size_t str_len, int32_t f, int32_t l, char **sub_str, int32_t *sub_str_len); zend_long grapheme_strrpos_ascii(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset); -int32_t grapheme_strrpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int f_ignore_case); -int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int *puchar_pos, int f_ignore_case, int last); +int32_t grapheme_strrpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int f_ignore_case, const char *locale); +int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle, size_t needle_len, int32_t offset, int *puchar_pos, int f_ignore_case, int last, const char *locale); int32_t grapheme_split_string(const UChar *text, int32_t text_length, int boundary_array[], int boundary_array_len ); diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index 28debad4133c4..9ede8a92e97a8 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -98,6 +98,20 @@ const char *intl_locale_get_default( void ) return INTL_G(default_locale); } +char* canonicalize_locale_string(const char* locale) { + char canonicalized[ULOC_FULLNAME_CAPACITY]; + UErrorCode status = U_ZERO_ERROR; + int32_t canonicalized_len; + + canonicalized_len = uloc_canonicalize(locale, canonicalized, sizeof(canonicalized), &status); + + if (U_FAILURE(status) || canonicalized_len <= 0) { + return NULL; + } + + return estrdup(canonicalized); +} + static PHP_INI_MH(OnUpdateErrorLevel) { zend_long *p = (zend_long *) ZEND_INI_GET_ADDR(); diff --git a/ext/intl/php_intl.h b/ext/intl/php_intl.h index 69772f4a85481..a56c34f3dce44 100644 --- a/ext/intl/php_intl.h +++ b/ext/intl/php_intl.h @@ -68,6 +68,7 @@ PHP_RSHUTDOWN_FUNCTION(intl); PHP_MINFO_FUNCTION(intl); const char *intl_locale_get_default( void ); +char *canonicalize_locale_string(const char* locale); #define PHP_INTL_VERSION PHP_VERSION diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index d2ca22c6d299a..9a8f036865cd5 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -427,23 +427,23 @@ function numfmt_get_error_message(NumberFormatter $formatter): string {} function grapheme_strlen(string $string): int|false|null {} -function grapheme_strpos(string $haystack, string $needle, int $offset = 0): int|false {} +function grapheme_strpos(string $haystack, string $needle, int $offset = 0, string $locale = ""): int|false {} -function grapheme_stripos(string $haystack, string $needle, int $offset = 0): int|false {} +function grapheme_stripos(string $haystack, string $needle, int $offset = 0, string $locale = ""): int|false {} -function grapheme_strrpos(string $haystack, string $needle, int $offset = 0): int|false {} +function grapheme_strrpos(string $haystack, string $needle, int $offset = 0, string $locale = ""): int|false {} -function grapheme_strripos(string $haystack, string $needle, int $offset = 0): int|false {} +function grapheme_strripos(string $haystack, string $needle, int $offset = 0, string $locale = ""): int|false {} -function grapheme_substr(string $string, int $offset, ?int $length = null): string|false {} +function grapheme_substr(string $string, int $offset, ?int $length = null, string $locale = ""): string|false {} -function grapheme_strstr(string $haystack, string $needle, bool $beforeNeedle = false): string|false {} +function grapheme_strstr(string $haystack, string $needle, bool $beforeNeedle = false, string $locale = ""): string|false {} -function grapheme_stristr(string $haystack, string $needle, bool $beforeNeedle = false): string|false {} +function grapheme_stristr(string $haystack, string $needle, bool $beforeNeedle = false, string $locale = ""): string|false {} function grapheme_str_split(string $string, int $length = 1): array|false {} -function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1): int|false {} +function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ""): int|false {} /** @param int $next */ function grapheme_extract(string $haystack, int $size, int $type = GRAPHEME_EXTR_COUNT, int $offset = 0, &$next = null): string|false {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index 012e274296a50..f778e4cacf3d4 100644 --- a/ext/intl/php_intl_arginfo.h +++ b/ext/intl/php_intl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3fa507cf4ace635ae620050db9662b3979890bc1 */ + * Stub hash: d9e331c3a1ae46f8eae07ef0d39cb9990e74a0d1 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_intlcal_create_instance, 0, 0, IntlCalendar, 1) ZEND_ARG_OBJ_TYPE_MASK(0, timezone, IntlTimeZone|DateTimeZone, MAY_BE_STRING|MAY_BE_NULL, "null") @@ -462,6 +462,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_strpos, 0, 2, MAY_BE_LO ZEND_ARG_TYPE_INFO(0, haystack, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, needle, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, locale, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() #define arginfo_grapheme_stripos arginfo_grapheme_strpos @@ -474,12 +475,14 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_substr, 0, 2, MAY_BE_ST ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, length, IS_LONG, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, locale, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_strstr, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, haystack, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, needle, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, beforeNeedle, _IS_BOOL, 0, "false") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, locale, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() #define arginfo_grapheme_stristr arginfo_grapheme_strstr @@ -495,6 +498,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_levenshtein, 0, 2, MAY_ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, insertion_cost, IS_LONG, 0, "1") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, replacement_cost, IS_LONG, 0, "1") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, deletion_cost, IS_LONG, 0, "1") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, locale, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_extract, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) diff --git a/ext/intl/tests/gh11942_datefmt_locale_canonicalization.phpt b/ext/intl/tests/gh11942_datefmt_locale_canonicalization.phpt new file mode 100644 index 0000000000000..aec539ff14df1 --- /dev/null +++ b/ext/intl/tests/gh11942_datefmt_locale_canonicalization.phpt @@ -0,0 +1,37 @@ +--TEST-- +Fix GH-11942: IntlDateFormatter should canonicalize locale strings +--EXTENSIONS-- +intl +--FILE-- +getLocale(); + + $status = ($actual === $expected) ? 'PASS' : 'FAIL'; + echo "Input: $input -> Expected: $expected -> Actual: $actual -> $status\n"; +} + +$dateFormatter = new IntlDateFormatter('pt_PT.utf8', IntlDateFormatter::SHORT, IntlDateFormatter::NONE, 'UTC'); +$dateResult = $dateFormatter->format(1691585260); +echo "\nDateFormatter with pt_PT.utf8: " . $dateResult . "\n"; +?> +--EXPECT-- +Testing IntlDateFormatter locale canonicalization: +Input: pt -> Expected: pt -> Actual: pt -> PASS +Input: pt-PT -> Expected: pt_PT -> Actual: pt_PT -> PASS +Input: pt_PT.utf8 -> Expected: pt_PT -> Actual: pt_PT -> PASS +Input: fr_CA@euro -> Expected: fr_CA -> Actual: fr_CA -> PASS + +DateFormatter with pt_PT.utf8: 09/08/23 diff --git a/ext/intl/tests/gh11942_numfmt_locale_canonicalization.phpt b/ext/intl/tests/gh11942_numfmt_locale_canonicalization.phpt new file mode 100644 index 0000000000000..9a17c33672986 --- /dev/null +++ b/ext/intl/tests/gh11942_numfmt_locale_canonicalization.phpt @@ -0,0 +1,37 @@ +--TEST-- +Fix GH-11942: NumberFormatter should canonicalize locale strings +--EXTENSIONS-- +intl +--FILE-- +getLocale(); + + $status = ($actual === $expected) ? 'PASS' : 'FAIL'; + echo "Input: $input -> Expected: $expected -> Actual: $actual -> $status\n"; +} + +$numFormatter = new NumberFormatter('pt_PT.utf8', NumberFormatter::DECIMAL); +$numResult = $numFormatter->format(1234.56); +echo "\nNumberFormatter with pt_PT.utf8: " . $numResult . "\n"; +?> +--EXPECT-- +Testing NumberFormatter locale canonicalization: +Input: pt -> Expected: pt -> Actual: pt -> PASS +Input: pt-PT -> Expected: pt_PT -> Actual: pt_PT -> PASS +Input: pt_PT.utf8 -> Expected: pt_PT -> Actual: pt_PT -> PASS +Input: fr_CA@euro -> Expected: fr_CA -> Actual: fr_CA -> PASS + +NumberFormatter with pt_PT.utf8: 1 234,56 diff --git a/ext/intl/tests/grapheme_levenshtein.phpt b/ext/intl/tests/grapheme_levenshtein.phpt index 4ff7dbb607bcd..37978f422363d 100644 --- a/ext/intl/tests/grapheme_levenshtein.phpt +++ b/ext/intl/tests/grapheme_levenshtein.phpt @@ -58,6 +58,14 @@ $nabe = '邊'; $nabe_E0100 = "邊󠄀"; var_dump(grapheme_levenshtein($nabe, $nabe_E0100)); +// variable $nabe and $nabe_E0101 is different because that is IVS. +// $nabe_E0101 is variable selector in U+908A U+E0101. +// grapheme_levenshtein can catches different only match strength is u-ks-identic for locale. +// So result is expect to 1. +$nabe = '邊'; +$nabe_E0101 = "\u{908A}\u{E0101}"; +var_dump(grapheme_levenshtein($nabe, $nabe_E0101, locale: "ja_JP-u-ks-identic")); + // combining character var_dump(grapheme_levenshtein("\u{0065}\u{0301}", "\u{00e9}")); @@ -80,6 +88,12 @@ try { } catch (ValueError $e) { echo $e->getMessage() . PHP_EOL; } + +echo "--- Invalid locales ---\n"; +var_dump(grapheme_levenshtein("abc", "abc", locale: "defaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); +var_dump(intl_get_error_code()); +var_dump(intl_get_error_message()); + ?> --EXPECTF-- --- Equal --- @@ -121,8 +135,13 @@ int(2) --- Variable selector --- int(1) int(0) +int(1) int(0) --- Corner case --- grapheme_levenshtein(): Argument #3 ($insertion_cost) must be greater than 0 and less than or equal to %d grapheme_levenshtein(): Argument #4 ($replacement_cost) must be greater than 0 and less than or equal to %d grapheme_levenshtein(): Argument #5 ($deletion_cost) must be greater than 0 and less than or equal to %d +--- Invalid locales --- +bool(false) +int(%d) +string(68) "grapheme_levenshtein(): Error on ucol_open: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/grapheme_stripos_locale_dependency.phpt b/ext/intl/tests/grapheme_stripos_locale_dependency.phpt new file mode 100644 index 0000000000000..e1a8d05e2541d --- /dev/null +++ b/ext/intl/tests/grapheme_stripos_locale_dependency.phpt @@ -0,0 +1,23 @@ +--TEST-- +grapheme_stripos() function locale dependency test +--EXTENSIONS-- +intl +--FILE-- + +--EXPECTF-- +int(0) +int(0) +bool(false) +=== Invalid locales === +bool(false) +int(%d) +string(74) "grapheme_stripos(): Error creating search object: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/grapheme_stristr_locale_dependency.phpt b/ext/intl/tests/grapheme_stristr_locale_dependency.phpt new file mode 100644 index 0000000000000..91d0e7273be0c --- /dev/null +++ b/ext/intl/tests/grapheme_stristr_locale_dependency.phpt @@ -0,0 +1,23 @@ +--TEST-- +grapheme_stristr() function locale dependency test +--EXTENSIONS-- +intl +--FILE-- + +--EXPECTF-- +string(3) "abc" +string(1) "i" +bool(false) +=== Invalid locales === +bool(false) +int(%d) +string(74) "grapheme_stristr(): Error creating search object: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/grapheme_strpos_locale_dependency.phpt b/ext/intl/tests/grapheme_strpos_locale_dependency.phpt new file mode 100644 index 0000000000000..f43b54123ab51 --- /dev/null +++ b/ext/intl/tests/grapheme_strpos_locale_dependency.phpt @@ -0,0 +1,12 @@ +--TEST-- +grapheme_strpos() function locale dependency test +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +bool(false) +int(0) diff --git a/ext/intl/tests/grapheme_strripos_locale_dependency.phpt b/ext/intl/tests/grapheme_strripos_locale_dependency.phpt new file mode 100644 index 0000000000000..87e87c483b7a4 --- /dev/null +++ b/ext/intl/tests/grapheme_strripos_locale_dependency.phpt @@ -0,0 +1,23 @@ +--TEST-- +grapheme_strripos() function locale dependency test +--EXTENSIONS-- +intl +--FILE-- + +--EXPECTF-- +int(0) +int(0) +bool(false) +=== Invalid locales === +bool(false) +int(%d) +string(75) "grapheme_strripos(): Error creating search object: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 9982074f73a81..7a797df29b9f1 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -2237,6 +2237,12 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert PHAR_G(last_phar) = NULL; PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + php_stream *tmp_fp = php_stream_fopen_tmpfile(); + if (tmp_fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "unable to create temporary file"); + return NULL; + } + phar = (phar_archive_data *) ecalloc(1, sizeof(phar_archive_data)); /* set whole-archive compression and type from parameter */ phar->flags = flags; @@ -2261,11 +2267,7 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert 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->fp = tmp_fp; phar->fname = source->fname; phar->fname_len = source->fname_len; phar->is_temporary_alias = source->is_temporary_alias; @@ -2289,6 +2291,7 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert } if (FAILURE == phar_copy_file_contents(&newentry, phar->fp)) { + phar_metadata_tracker_free(&phar->metadata_tracker, phar->is_persistent); zend_hash_destroy(&(phar->manifest)); php_stream_close(phar->fp); efree(phar); @@ -2326,6 +2329,7 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert return ret; } else { if(phar != NULL) { + phar_metadata_tracker_free(&phar->metadata_tracker, phar->is_persistent); zend_hash_destroy(&(phar->manifest)); zend_hash_destroy(&(phar->mounted_dirs)); zend_hash_destroy(&(phar->virtual_dirs)); diff --git a/ext/phar/tests/phar_convert_metadata_leak.phpt b/ext/phar/tests/phar_convert_metadata_leak.phpt new file mode 100644 index 0000000000000..61a240c8888da --- /dev/null +++ b/ext/phar/tests/phar_convert_metadata_leak.phpt @@ -0,0 +1,24 @@ +--TEST-- +Phar convert logic leaks metadata +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +phar.readonly=0 +--FILE-- +setMetadata("foobar"); +$phar['x'] = 'hi'; +try { + $phar->convertToData(Phar::ZIP, Phar::NONE, 'phar.zip'); +} catch (BadMethodCallException $e) { + echo $e->getMessage(),"\n"; +} +?> +--CLEAN-- + +--EXPECTF-- +data phar "%s" has invalid extension phar.zip diff --git a/ext/standard/tests/filters/oss_fuzz_385993744.phpt b/ext/standard/tests/filters/oss_fuzz_385993744.phpt new file mode 100644 index 0000000000000..bfed9e57a9185 --- /dev/null +++ b/ext/standard/tests/filters/oss_fuzz_385993744.phpt @@ -0,0 +1,28 @@ +--TEST-- +OSS-Fuzz #385993744 +--FILE-- +data .= $bucket->data; + } + + $bucket = stream_bucket_new($this->stream, $this->data); + stream_bucket_append($out, $bucket); + + return PSFS_FEED_ME; + } +} +stream_filter_register('sample.filter', SampleFilter::class); +var_dump(file_get_contents('php://filter/read=sample.filter/resource='. __FILE__)); + +?> +--EXPECT-- +string(0) "" diff --git a/main/streams/filter.c b/main/streams/filter.c index beaba503cbe16..741dcde684d6c 100644 --- a/main/streams/filter.c +++ b/main/streams/filter.c @@ -350,6 +350,13 @@ PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, p Reset stream's internal read buffer since the filter is "holding" it. */ stream->readpos = 0; stream->writepos = 0; + + /* Filter could have added buckets anyway, but signalled that it did not return any. Discard them. */ + while (brig_out.head) { + bucket = brig_out.head; + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } break; case PSFS_PASS_ON: /* If any data is consumed, we cannot rely upon the existing read buffer, diff --git a/main/streams/streams.c b/main/streams/streams.c index 88d982b760cd8..3a0a289e337cf 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -631,6 +631,12 @@ PHPAPI zend_result _php_stream_fill_read_buffer(php_stream *stream, size_t size) /* when a filter needs feeding, there is no brig_out to deal with. * we simply continue the loop; if the caller needs more data, * we will read again, otherwise out job is done here */ + + /* Filter could have added buckets anyway, but signalled that it did not return any. Discard them. */ + while ((bucket = brig_outp->head)) { + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } break; case PSFS_ERR_FATAL: @@ -1264,14 +1270,22 @@ static ssize_t _php_stream_write_filtered(php_stream *stream, const char *buf, s php_stream_bucket_delref(bucket); } break; - case PSFS_FEED_ME: - /* need more data before we can push data through to the stream */ - break; case PSFS_ERR_FATAL: /* some fatal error. Theoretically, the stream is borked, so all * further writes should fail. */ - return (ssize_t) -1; + consumed = (ssize_t) -1; + ZEND_FALLTHROUGH; + + case PSFS_FEED_ME: + /* need more data before we can push data through to the stream */ + /* Filter could have added buckets anyway, but signalled that it did not return any. Discard them. */ + while (brig_inp->head) { + bucket = brig_inp->head; + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } + break; } return consumed;