diff --git a/NEWS b/NEWS index 653bcff9e05a..17ec66961b3b 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ PHP NEWS className/classList writes and attribute removals. (Ilia Alshanetsky) - Intl: + . Fixed grapheme_strpos() and grapheme_strrpos() with an empty needle + returning UTF-16 offsets instead of grapheme offsets. (Ilia Alshanetsky) . Fixed a memory leak when dumping IntlCalendar instances. (Ilia Alshanetsky) . Fixed Collator::sortWithSortKeys() allocating fixed 2MiB buffers regardless of array size. (Ilia Alshanetsky) @@ -22,6 +24,13 @@ PHP NEWS read. (iliaal) . Fixed a use-after-free when IntlRuleBasedBreakIterator is constructed from compiled rules. (iliaal) + . Fixed Spoofchecker methods not recording the ICU error code when an ICU + call fails. (Ilia Alshanetsky) + +- MBString: + . Fixed mb_ereg_replace() emitting a NUL or out-of-bounds bytes in the + replacement when a \k backref has no closing delimiter. + (Ilia Alshanetsky) - PDO_PGSQL: . Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 9f9f0a6159ed..27664623c6b9 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -3258,7 +3258,12 @@ PHP_FUNCTION(date_format) } /* }}} */ -static bool php_date_modify(zval *object, char *modify, size_t modify_len) /* {{{ */ +typedef enum { + PHP_DATE_MODIFY_WARNING, + PHP_DATE_MODIFY_THROW +} php_date_modify_error_mode; + +static bool php_date_modify(zval *object, char *modify, size_t modify_len, const php_date_modify_error_mode error_mode) /* {{{ */ { php_date_obj *dateobj; timelib_time *tmp_time; @@ -3278,10 +3283,22 @@ static bool php_date_modify(zval *object, char *modify, size_t modify_len) /* {{ if (err && err->error_count) { /* spit out the first library error message, at least */ - php_error_docref(NULL, E_WARNING, "Failed to parse time string (%s) at position %d (%c): %s", modify, - err->error_messages[0].position, - err->error_messages[0].character ? err->error_messages[0].character : ' ', - err->error_messages[0].message); + if (error_mode == PHP_DATE_MODIFY_THROW) { + zend_string *func_name = get_active_function_or_method_name(); + zend_throw_exception_ex(date_ce_date_malformed_string_exception, 0, + "%s(): Failed to parse time string (%s) at position %d (%c): %s", + ZSTR_VAL(func_name), + modify, + err->error_messages[0].position, + err->error_messages[0].character ? err->error_messages[0].character : ' ', + err->error_messages[0].message); + zend_string_release_ex(func_name, false); + } else { + php_error_docref(NULL, E_WARNING, "Failed to parse time string (%s) at position %d (%c): %s", modify, + err->error_messages[0].position, + err->error_messages[0].character ? err->error_messages[0].character : ' ', + err->error_messages[0].message); + } timelib_time_dtor(tmp_time); return false; } @@ -3350,7 +3367,7 @@ PHP_FUNCTION(date_modify) RETURN_THROWS(); } - if (!php_date_modify(object, modify, modify_len)) { + if (!php_date_modify(object, modify, modify_len, PHP_DATE_MODIFY_WARNING)) { RETURN_FALSE; } @@ -3364,21 +3381,16 @@ PHP_METHOD(DateTime, modify) zval *object; char *modify; size_t modify_len; - zend_error_handling zeh; object = ZEND_THIS; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(modify, modify_len) ZEND_PARSE_PARAMETERS_END(); - zend_replace_error_handling(EH_THROW, date_ce_date_malformed_string_exception, &zeh); - if (!php_date_modify(object, modify, modify_len)) { - zend_restore_error_handling(&zeh); + if (!php_date_modify(object, modify, modify_len, PHP_DATE_MODIFY_THROW)) { RETURN_THROWS(); } - zend_restore_error_handling(&zeh); - RETURN_OBJ_COPY(Z_OBJ_P(object)); } /* }}} */ @@ -3389,7 +3401,6 @@ PHP_METHOD(DateTimeImmutable, modify) zval *object, new_object; char *modify; size_t modify_len; - zend_error_handling zeh; object = ZEND_THIS; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -3398,15 +3409,11 @@ PHP_METHOD(DateTimeImmutable, modify) date_clone_immutable(object, &new_object); - zend_replace_error_handling(EH_THROW, date_ce_date_malformed_string_exception, &zeh); - if (!php_date_modify(&new_object, modify, modify_len)) { + if (!php_date_modify(&new_object, modify, modify_len, PHP_DATE_MODIFY_THROW)) { zval_ptr_dtor(&new_object); - zend_restore_error_handling(&zeh); RETURN_THROWS(); } - zend_restore_error_handling(&zeh); - RETURN_OBJ(Z_OBJ(new_object)); } /* }}} */ @@ -3463,7 +3470,7 @@ PHP_METHOD(DateTimeImmutable, add) } /* }}} */ -static void php_date_sub(zval *object, zval *interval, zval *return_value) /* {{{ */ +static void php_date_sub(zval *object, zval *interval, zval *return_value, const bool should_throw) /* {{{ */ { php_date_obj *dateobj; php_interval_obj *intobj; @@ -3475,7 +3482,15 @@ static void php_date_sub(zval *object, zval *interval, zval *return_value) /* {{ DATE_CHECK_INITIALIZED(intobj->initialized, Z_OBJCE_P(interval)); if (intobj->diff->have_weekday_relative || intobj->diff->have_special_relative) { - php_error_docref(NULL, E_WARNING, "Only non-special relative time specifications are supported for subtraction"); + if (should_throw) { + zend_string *func_name = get_active_function_or_method_name(); + zend_throw_exception_ex(date_ce_date_invalid_operation_exception, 0, + "%s(): Only non-special relative time specifications are supported for subtraction", + ZSTR_VAL(func_name)); + zend_string_release_ex(func_name, false); + } else { + php_error_docref(NULL, E_WARNING, "Only non-special relative time specifications are supported for subtraction"); + } return; } @@ -3497,7 +3512,7 @@ PHP_FUNCTION(date_sub) RETURN_THROWS(); } - php_date_sub(object, interval, return_value); + php_date_sub(object, interval, return_value, false); RETURN_OBJ_COPY(Z_OBJ_P(object)); } /* }}} */ @@ -3506,15 +3521,12 @@ PHP_FUNCTION(date_sub) PHP_METHOD(DateTime, sub) { zval *object, *interval; - zend_error_handling zeh; if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_date, &interval, date_ce_interval) == FAILURE) { RETURN_THROWS(); } - zend_replace_error_handling(EH_THROW, date_ce_date_invalid_operation_exception, &zeh); - php_date_sub(object, interval, return_value); - zend_restore_error_handling(&zeh); + php_date_sub(object, interval, return_value, true); RETURN_OBJ_COPY(Z_OBJ_P(object)); } @@ -3524,7 +3536,6 @@ PHP_METHOD(DateTime, sub) PHP_METHOD(DateTimeImmutable, sub) { zval *object, *interval, new_object; - zend_error_handling zeh; object = ZEND_THIS; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -3533,9 +3544,7 @@ PHP_METHOD(DateTimeImmutable, sub) date_clone_immutable(object, &new_object); - zend_replace_error_handling(EH_THROW, date_ce_date_invalid_operation_exception, &zeh); - php_date_sub(&new_object, interval, return_value); - zend_restore_error_handling(&zeh); + php_date_sub(&new_object, interval, return_value, true); RETURN_OBJ(Z_OBJ(new_object)); } diff --git a/ext/intl/grapheme/grapheme_util.cpp b/ext/intl/grapheme/grapheme_util.cpp index 031f1c717891..fdd69f08a669 100644 --- a/ext/intl/grapheme/grapheme_util.cpp +++ b/ext/intl/grapheme/grapheme_util.cpp @@ -139,7 +139,11 @@ U_CFUNC int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char ret_pos = -1; goto finish; } - ret_pos = last && offset >= 0 ? uhaystack_len : offset_pos; + if (last && offset >= 0) { + ret_pos = grapheme_count_graphemes(bi, uhaystack, uhaystack_len); + } else { + ret_pos = grapheme_count_graphemes(bi, uhaystack, offset_pos); + } goto finish; } diff --git a/ext/intl/spoofchecker/spoofchecker_main.cpp b/ext/intl/spoofchecker/spoofchecker_main.cpp index d7e1b4e0553d..7a3ffd9b809a 100644 --- a/ext/intl/spoofchecker/spoofchecker_main.cpp +++ b/ext/intl/spoofchecker/spoofchecker_main.cpp @@ -46,6 +46,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, isSuspicious) ret = intl_icu_compat_uspoof_check_utf8(co->uspoof, ZSTR_VAL(text), ZSTR_LEN(text), co->uspoofres, SPOOFCHECKER_ERROR_CODE_P(co)); if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); if (intl_icu_compat_uspoof_check_result_mismatch(co->uspoofres, ret, &errmask, SPOOFCHECKER_ERROR_CODE_P(co))) { @@ -83,6 +84,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, areConfusable) ret = uspoof_areConfusableUTF8(co->uspoof, ZSTR_VAL(s1), (int32_t)ZSTR_LEN(s1), ZSTR_VAL(s2), (int32_t)ZSTR_LEN(s2), SPOOFCHECKER_ERROR_CODE_P(co)); } if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); RETURN_TRUE; } @@ -109,6 +111,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, setAllowedLocales) uspoof_setAllowedLocales(co->uspoof, ZSTR_VAL(locales), SPOOFCHECKER_ERROR_CODE_P(co)); if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); return; } @@ -130,6 +133,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, setChecks) uspoof_setChecks(co->uspoof, checks, SPOOFCHECKER_ERROR_CODE_P(co)); if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); } } @@ -220,6 +224,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, setAllowedChars) efree(upattern); if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); } } @@ -355,6 +360,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, areBidiConfusable) ret = uspoof_areBidiConfusableUTF8(co->uspoof, (UBiDiDirection)direction, ZSTR_VAL(s1), (int32_t)ZSTR_LEN(s1), ZSTR_VAL(s2), (int32_t)ZSTR_LEN(s2), SPOOFCHECKER_ERROR_CODE_P(co)); } if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + intl_error_set_code(NULL, SPOOFCHECKER_ERROR_CODE(co)); php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co))); RETURN_TRUE; } diff --git a/ext/intl/tests/grapheme_empty_offset_multibyte.phpt b/ext/intl/tests/grapheme_empty_offset_multibyte.phpt new file mode 100644 index 000000000000..1e2d736da585 --- /dev/null +++ b/ext/intl/tests/grapheme_empty_offset_multibyte.phpt @@ -0,0 +1,34 @@ +--TEST-- +grapheme_strpos() family with empty needle and offset on multi-code-unit graphemes +--EXTENSIONS-- +intl +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +int(0) +int(0) +int(1) +int(1) +int(1) +int(2) +int(2) +int(2) +int(1) +ValueError: grapheme_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) diff --git a/ext/intl/tests/spoofchecker_setchecks_error_code.phpt b/ext/intl/tests/spoofchecker_setchecks_error_code.phpt new file mode 100644 index 000000000000..a9c1eaeab452 --- /dev/null +++ b/ext/intl/tests/spoofchecker_setchecks_error_code.phpt @@ -0,0 +1,18 @@ +--TEST-- +Spoofchecker::setChecks() records the ICU error code +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +setChecks(1 << 20); +var_dump(intl_get_error_code(), intl_get_error_message()); + +?> +--EXPECTF-- +Warning: Spoofchecker::setChecks(): (1) U_ILLEGAL_ARGUMENT_ERROR in %s on line %d +int(1) +string(24) "U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/mbstring/php_mbregex.c b/ext/mbstring/php_mbregex.c index 47297a9b15cd..58a8df4bc634 100644 --- a/ext/mbstring/php_mbregex.c +++ b/ext/mbstring/php_mbregex.c @@ -769,7 +769,7 @@ static inline void mb_regex_substitute( clen = (int) php_mb_mbchar_bytes(++p, enc); if (clen != 1 || p == eos || (p[0] != '<' && p[0] != '\'')) { /* not a backref delimiter */ - p += clen; + p = MIN(p + clen, eos); smart_str_appendl(pbuf, sp, p - sp); continue; } @@ -789,12 +789,13 @@ static inline void mb_regex_substitute( if (maybe_num && !isdigit((unsigned char)name_end[0])) maybe_num = 0; name_end++; } - p = name_end + 1; if (name_end - name < 1 || name_end >= eos) { /* the backref was empty or we failed to find the end delimiter */ + p = MIN(name_end + 1, eos); smart_str_appendl(pbuf, sp, p - sp); continue; } + p = name_end + 1; /* we have either a name or a number */ if (maybe_num) { if (!onig_noname_group_capture_is_active(regexp)) { diff --git a/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt b/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt new file mode 100644 index 000000000000..80308370d216 --- /dev/null +++ b/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt @@ -0,0 +1,27 @@ +--TEST-- +mb_ereg_replace() with unterminated \k backref must not embed a NUL byte +--EXTENSIONS-- +mbstring +--FILE-- + +--EXPECTF-- +Deprecated: Function mb_ereg_replace() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d +string(12) "5c6b3c6e756d" + +Deprecated: Function mb_ereg_replace() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d +string(12) "5c6b276e756d" + +Deprecated: Function mb_ereg_replace() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d +string(10) "615c6b3c6e" + +Deprecated: Function mb_ereg_replace() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d +string(8) "615c6bf0" + +Deprecated: Function mb_ereg_replace() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d +string(10) "5c5c6be282" diff --git a/ext/openssl/tests/CONFLICTS b/ext/openssl/tests/CONFLICTS deleted file mode 100644 index 254defddb53c..000000000000 --- a/ext/openssl/tests/CONFLICTS +++ /dev/null @@ -1 +0,0 @@ -server diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 1c93a319ccaa..8dd645271dbb 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -35,6 +35,7 @@ enum pdo_odbc_conv_result { static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { +#ifdef PHP_WIN32 if (!S->assume_utf8) return 0; switch (sqltype) { #ifdef SQL_WCHAR @@ -52,6 +53,9 @@ static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) default: return 0; } +#else + return 0; +#endif } static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, @@ -544,7 +548,15 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p break; } } else { - P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + zend_ulong ulen; + if (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen) == PDO_ODBC_CONV_OK) { + P->len = SQL_LEN_DATA_AT_EXEC(ulen); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } } } return 1; diff --git a/ext/pdo_odbc/tests/gh23444.phpt b/ext/pdo_odbc/tests/gh23444.phpt new file mode 100644 index 000000000000..a18dd202e732 --- /dev/null +++ b/ext/pdo_odbc/tests/gh23444.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-23444 (Unicode data is corrupted with ODBC_ATTR_ASSUME_UTF8) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE gh23444 (v NVARCHAR(100))"); + +// 13 bytes as UTF-8, so an unconverted parameter is an odd number of bytes +$string = "\u{6e2c}\u{8a66}\u{4e2d}\u{1f418}"; + +$db->setAttribute(Pdo\Odbc::ATTR_ASSUME_UTF8, true); +$stmt = $db->prepare("INSERT INTO gh23444 VALUES(?)"); +$stmt->execute([$string]); + +$stmt = $db->prepare("SELECT v FROM gh23444 WHERE v = ?"); +$stmt->execute([$string]); +var_dump($stmt->fetchColumn() === $string); +?> +--CLEAN-- +exec("DROP TABLE IF EXISTS gh23444"); +?> +--EXPECT-- +bool(true) diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 266d5001b81f..70829eb8465e 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -1062,7 +1062,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, { char *loc_path = NULL; if (*header_info.location != '/') { - if (header_info.location_len > 0 && resource->path) { + if (header_info.location_len > 1 && resource->path) { char *s = strrchr(ZSTR_VAL(resource->path), '/'); if (!s) { s = ZSTR_VAL(resource->path); diff --git a/ext/standard/tests/http/http_single_char_location_redirect.phpt b/ext/standard/tests/http/http_single_char_location_redirect.phpt new file mode 100644 index 000000000000..66af7a432537 --- /dev/null +++ b/ext/standard/tests/http/http_single_char_location_redirect.phpt @@ -0,0 +1,39 @@ +--TEST-- +Single-char relative Location header keeps resolving against the host root (pre-GH-23467 behavior) +--DESCRIPTION-- +Not RFC 3986 compliant ("x" against "/a/b" gives "/a/x"), but matches +PHP's long-standing behavior of resolving against the host root. See GH-23521. +--FILE-- + ['follow_location' => 1]]); +echo @file_get_contents("http://{{ ADDR }}/a/b", false, $ctx), "\n"; +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECT-- +uri=/x