Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)

- Intl:
. Fixed bug GH-23094 (NumberFormatter parsing offsets use UTF-16 positions
for UTF-8 strings). (ColumbusLabs)

- Opcache:
. Fixed opcache.protect_memory race under ZTS. (realFlowControl)

Expand Down
46 changes: 46 additions & 0 deletions ext/intl/formatter/formatter_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,42 @@

#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(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;
}

static int32_t numfmt_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;
}

u_strToUTF8(NULL, 0, &utf8_position, str, position, &status);
if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) {
return position;
}

return utf8_position;
}

/* {{{ Parse a number. */
PHP_FUNCTION( numfmt_parse )
{
Expand Down Expand Up @@ -61,6 +97,10 @@ PHP_FUNCTION( numfmt_parse )
/* Convert given string to UTF-16. */
intl_convert_utf8_to_utf16(&sstr, &sstr_len, 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))) {
efree(sstr);
INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset");
}

#if ICU_LOCALE_BUG && defined(LC_NUMERIC)
/* need to copy here since setlocale may change it later */
Expand Down Expand Up @@ -101,6 +141,7 @@ PHP_FUNCTION( numfmt_parse )
}

if (zposition) {
position = numfmt_utf16_offset_to_utf8(sstr, sstr_len, position);
ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
}

Expand Down Expand Up @@ -150,11 +191,16 @@ PHP_FUNCTION( numfmt_parse_currency )

if(zposition) {
position = (int32_t) zval_get_long(zposition);
if (!numfmt_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) {
efree(sstr);
INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset");
}
position_p = &position;
}

number = unum_parseDoubleCurrency(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, currency, &INTL_DATA_ERROR_CODE(nfo));
if(zposition) {
position = numfmt_utf16_offset_to_utf8(sstr, sstr_len, position);
ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
}
if (sstr) {
Expand Down
47 changes: 47 additions & 0 deletions ext/intl/tests/gh23094.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
GH-23094 NumberFormatter parse offsets use UTF-8 byte positions
--EXTENSIONS--
intl
--FILE--
<?php

$prefix = "\u{1F600}";

$formatter = new NumberFormatter('en_US', NumberFormatter::DECIMAL);
$offset = strlen($prefix);
var_dump($formatter->parse($prefix . '123', NumberFormatter::TYPE_INT32, $offset));
var_dump($offset);

$offset = 1;
var_dump($formatter->parse("\u{00E9}123", NumberFormatter::TYPE_INT32, $offset));
var_dump($offset);
var_dump(intl_is_failure($formatter->getErrorCode()));

$formatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
$offset = strlen($prefix);
$currency = null;
var_dump($formatter->parseCurrency($prefix . '$123.45', $currency, $offset));
var_dump($currency);
var_dump($offset);

$offset = 1;
$currency = null;
var_dump($formatter->parseCurrency("\u{00E9}$123.45", $currency, $offset));
var_dump($currency);
var_dump($offset);
var_dump(intl_is_failure($formatter->getErrorCode()));

?>
--EXPECT--
int(123)
int(7)
bool(false)
int(1)
bool(true)
float(123.45)
string(3) "USD"
int(11)
bool(false)
NULL
int(1)
bool(true)
Loading