[php-src] master: Merge branch 'PHP-8.6'

From: Date: Mon, 05 Oct 2026 13:13:53 +0000
Subject: [php-src] master: Merge branch 'PHP-8.6'
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-141963@lists.php.net to get a copy of this message
Author: Ilia Alshanetsky (iliaal) Date: 2026-10-05T09:06:26-04:00 Commit: https://github.com/php/php-src/commit/6fa256a2d17deb14698da6570aad78270774a9f2 Raw diff: https://github.com/php/php-src/commit/6fa256a2d17deb14698da6570aad78270774a9f2.diff Merge branch 'PHP-8.6' * PHP-8.6: ext/intl: Use byte offsets in IntlDateFormatter parsing Changed paths: A ext/intl/tests/dateformat_parse_utf8_offset.phpt M ext/intl/dateformat/dateformat_parse.cpp M ext/intl/formatter/formatter_parse.cpp M ext/intl/intl_convert.c M ext/intl/intl_convert.h Diff: diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp index 0537b42feec5..8a554ae404de 100644 --- a/ext/intl/dateformat/dateformat_parse.cpp +++ b/ext/intl/dateformat/dateformat_parse.cpp @@ -43,10 +43,19 @@ static void internal_parse_to_timestamp(IntlDateFormatter_object *dfo, const cha /* Convert timezone to UTF-16. */ intl_convert_utf8_to_utf16(&text_utf16, &text_utf16_len, text_to_parse, text_len, &INTL_DATA_ERROR_CODE(dfo)); INTL_METHOD_CHECK_STATUS(dfo, "Error converting timezone to UTF-16" ); + if (parse_pos && !intl_convert_utf8_offset_to_utf16(text_to_parse, text_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo))) { + if (text_utf16) { + efree(text_utf16); + } + INTL_METHOD_CHECK_STATUS(dfo, "Invalid UTF-8 offset" ); + } if (UNEXPECTED(update_calendar)) { UCalendar *parsed_calendar = (UCalendar *)udat_getCalendar(DATE_FORMAT_OBJECT(dfo)); udat_parseCalendar(DATE_FORMAT_OBJECT(dfo), parsed_calendar, text_utf16, text_utf16_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo)); + if (parse_pos) { + *parse_pos = intl_convert_utf16_offset_to_utf8(text_utf16, text_utf16_len, *parse_pos); + } if (text_utf16) { efree(text_utf16); } @@ -54,6 +63,9 @@ static void internal_parse_to_timestamp(IntlDateFormatter_object *dfo, const cha timestamp = ucal_getMillis( parsed_calendar, &INTL_DATA_ERROR_CODE(dfo)); } else { timestamp = udat_parse(DATE_FORMAT_OBJECT(dfo), text_utf16, text_utf16_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo)); + if (parse_pos) { + *parse_pos = intl_convert_utf16_offset_to_utf8(text_utf16, text_utf16_len, *parse_pos); + } if (text_utf16) { efree(text_utf16); } @@ -97,9 +109,18 @@ static void internal_parse_to_localtime(IntlDateFormatter_object *dfo, const cha /* Convert timezone to UTF-16. */ intl_convert_utf8_to_utf16(&text_utf16, &text_utf16_len, text_to_parse, text_len, &INTL_DATA_ERROR_CODE(dfo)); INTL_METHOD_CHECK_STATUS(dfo, "Error converting timezone to UTF-16" ); + if (parse_pos && !intl_convert_utf8_offset_to_utf16(text_to_parse, text_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo))) { + if (text_utf16) { + efree(text_utf16); + } + INTL_METHOD_CHECK_STATUS(dfo, "Invalid UTF-8 offset" ); + } parsed_calendar = (UCalendar *)udat_getCalendar(DATE_FORMAT_OBJECT(dfo)); udat_parseCalendar( DATE_FORMAT_OBJECT(dfo), parsed_calendar, text_utf16, text_utf16_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo)); + if (parse_pos) { + *parse_pos = intl_convert_utf16_offset_to_utf8(text_utf16, text_utf16_len, *parse_pos); + } if (text_utf16) { efree(text_utf16); diff --git a/ext/intl/formatter/formatter_parse.cpp b/ext/intl/formatter/formatter_parse.cpp index 7a233f012d46..db7463030b7a 100644 --- a/ext/intl/formatter/formatter_parse.cpp +++ b/ext/intl/formatter/formatter_parse.cpp @@ -25,6 +25,7 @@ extern "C" { #include "php_intl.h" +#include "intl_convert.h" } #include <locale.h> @@ -32,42 +33,6 @@ extern "C" { #define ICU_LOCALE_BUG 1 -static bool numfmt_utf8_offset_to_utf16(const char *str, size_t str_len, int32_t *position, UErrorCode *status) -{ - int32_t utf16_position; - - if (*position < 0 || (size_t) *position > str_len) { - return true; - } - - *status = U_ZERO_ERROR; - u_strFromUTF8(nullptr, 0, &utf16_position, str, *position, status); - if (*status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(*status)) { - return false; - } - *status = U_ZERO_ERROR; - - *position = utf16_position; - return true; -} - -static int32_t numfmt_utf16_offset_to_utf8(const icu::UnicodeString &str, int32_t position) -{ - int32_t utf8_position; - UErrorCode status = U_ZERO_ERROR; - - if (position < 0 || position > str.length()) { - return position; - } - - u_strToUTF8(nullptr, 0, &utf8_position, str.getBuffer(), position, &status); - if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) { - return position; - } - - return utf8_position; -} - /* {{{ Parse a number. */ U_CFUNC PHP_FUNCTION( numfmt_parse ) { @@ -102,7 +67,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse ) icu::UnicodeString ustr; intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo)); INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" ); - if (zposition && !numfmt_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) { + if (zposition && !intl_convert_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) { INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset"); } @@ -162,7 +127,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse ) } if (zposition) { - position = numfmt_utf16_offset_to_utf8(ustr, position); + position = intl_convert_utf16_offset_to_utf8(ustr.getBuffer(), ustr.length(), position); ZEND_TRY_ASSIGN_REF_LONG(zposition, position); } @@ -208,7 +173,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse_currency ) RETURN_THROWS(); } position = (int32_t) long_position; - if (!numfmt_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) { + if (!intl_convert_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) { INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset"); } } @@ -222,7 +187,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse_currency ) } if(zposition) { - position = numfmt_utf16_offset_to_utf8(ustr, pp.getIndex()); + position = intl_convert_utf16_offset_to_utf8(ustr.getBuffer(), ustr.length(), pp.getIndex()); ZEND_TRY_ASSIGN_REF_LONG(zposition, position); } diff --git a/ext/intl/intl_convert.c b/ext/intl/intl_convert.c index 5497e84508b4..5b97c354262c 100644 --- a/ext/intl/intl_convert.c +++ b/ext/intl/intl_convert.c @@ -22,6 +22,8 @@ #include "intl_common.h" #include "intl_convert.h" +#include <unicode/utf16.h> + /* {{{ intl_convert_utf8_to_utf16 * Convert given string from UTF-8 to UTF-16 to *target buffer. * @@ -198,3 +200,43 @@ zend_string* intl_convert_utf16_to_utf8( return dst; } /* }}} */ + +bool intl_convert_utf8_offset_to_utf16(const char *str, size_t str_len, int32_t *position, UErrorCode *status) +{ + int32_t utf16_position; + + if (*position < 0 || (size_t) *position > str_len) { + return true; + } + + *status = U_ZERO_ERROR; + u_strFromUTF8(NULL, 0, &utf16_position, str, *position, status); + if (*status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(*status)) { + return false; + } + *status = U_ZERO_ERROR; + + *position = utf16_position; + return true; +} + +int32_t intl_convert_utf16_offset_to_utf8(const UChar *str, int32_t str_len, int32_t position) +{ + int32_t utf8_position; + UErrorCode status = U_ZERO_ERROR; + + if (position < 0 || position > str_len) { + return position; + } + + if (position > 0 && position < str_len && U16_IS_LEAD(str[position - 1]) && U16_IS_TRAIL(str[position])) { + position--; + } + + u_strToUTF8(NULL, 0, &utf8_position, str, position, &status); + if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) { + return position; + } + + return utf8_position; +} diff --git a/ext/intl/intl_convert.h b/ext/intl/intl_convert.h index 443d373d55c2..f33746b00c92 100644 --- a/ext/intl/intl_convert.h +++ b/ext/intl/intl_convert.h @@ -31,4 +31,12 @@ zend_string* intl_convert_utf16_to_utf8( const UChar* src, int32_t src_len, UErrorCode* status ); +bool intl_convert_utf8_offset_to_utf16( + const char* str, size_t str_len, + int32_t* position, UErrorCode* status ); + +int32_t intl_convert_utf16_offset_to_utf8( + const UChar* str, int32_t str_len, + int32_t position ); + #endif // INTL_CONVERT_H diff --git a/ext/intl/tests/dateformat_parse_utf8_offset.phpt b/ext/intl/tests/dateformat_parse_utf8_offset.phpt new file mode 100644 index 000000000000..aa80a39d8099 --- /dev/null +++ b/ext/intl/tests/dateformat_parse_utf8_offset.phpt @@ -0,0 +1,68 @@ +--TEST-- +IntlDateFormatter parsing uses UTF-8 byte offsets +--EXTENSIONS-- +intl +--FILE-- +<?php +$formatter = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE, 'UTC', IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd'); +$prefix = "\u{1F600}"; +$text = $prefix . '2017-10-12 tail'; + +echo "parse():\n"; +$offset = strlen($prefix); +var_dump(gmdate('Y-m-d', $formatter->parse($text, $offset)), $offset); + +echo "localtime():\n"; +$offset = strlen($prefix); +$tm = $formatter->localtime($text, $offset); +var_dump($tm['tm_year'] + 1900, $tm['tm_mon'] + 1, $tm['tm_mday'], $offset); + +echo "parseToCalendar():\n"; +$offset = strlen($prefix); +var_dump(gmdate('Y-m-d', $formatter->parseToCalendar($text, $offset)), $offset); + +echo "failed parse:\n"; +$offset = strlen($prefix); +var_dump($formatter->parse($prefix . '2017-xx-12', $offset), $offset); + +echo "offset inside a character:\n"; +$offset = 1; +var_dump($formatter->parse($text, $offset), $offset, intl_get_error_message()); + +echo "partial match inside a surrogate pair:\n"; +$formatter = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE, 'UTC', IntlDateFormatter::GREGORIAN, "\u{20AC}\u{1F600}yyyy-MM-dd"); +$offset = 0; +var_dump($formatter->parse("\u{20AC}\u{1F601}2017-10-12", $offset), $offset); +$formatter = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE, 'UTC', IntlDateFormatter::GREGORIAN, "yyyy-MM-dd\u{1F600}"); +foreach (['parse', 'localtime', 'parseToCalendar'] as $method) { + $offset = 0; + $formatter->$method("2017-10-12\u{1F601}", $offset); + echo "$method(): "; + var_dump($offset); +} +?> +--EXPECT-- +parse(): +string(10) "2017-10-12" +int(14) +localtime(): +int(2017) +int(10) +int(12) +int(14) +parseToCalendar(): +string(10) "2017-10-12" +int(14) +failed parse: +bool(false) +int(9) +offset inside a character: +bool(false) +int(1) +string(70) "IntlDateFormatter::parse(): Invalid UTF-8 offset: U_INVALID_CHAR_FOUND" +partial match inside a surrogate pair: +bool(false) +int(3) +parse(): int(10) +localtime(): int(10) +parseToCalendar(): int(10)

« previous php.cvs (#141963) next »