Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
10 changes: 10 additions & 0 deletions ext/intl/locale/locale.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ public static function getDisplayLanguage(string $locale, ?string $displayLocale
*/
public static function getDisplayVariant(string $locale, ?string $displayLocale = null): string|false {}

/**
* @alias locale_get_display_keyword
*/
public static function getDisplayKeyword(string $keyword, ?string $displayLocale = null): string|false {}

/**
* @alias locale_get_display_keyword_value
*/
public static function getDisplayKeywordValue(string $locale, string $keyword, ?string $displayLocale = null): string|false {}

/**
* @tentative-return-type
* @alias locale_compose
Expand Down
17 changes: 16 additions & 1 deletion ext/intl/locale/locale_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 117 additions & 0 deletions ext/intl/locale/locale_methods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl )
#define EXTLANG_PREFIX "a"
#define PRIVATE_PREFIX "x"
#define DISP_NAME "name"
#define DISP_KEYWORD "keyword"
#define DISP_KEYWORD_VALUE "keyword_value"

#define MAX_NO_VARIANT 15
#define MAX_NO_EXTLANG 3
Expand Down Expand Up @@ -671,6 +673,107 @@ static void get_icu_disp_value_src_php( const char* tag_name, INTERNAL_FUNCTION_
}
/* }}} */

/* {{{
* common code shared by display keyword functions to get the value from ICU
}}} */
static void get_icu_disp_keyword_value_src_php(const char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
{
char* loc_name = NULL;
size_t loc_name_len = 0;
char* keyword_name = NULL;
size_t keyword_name_len = 0;
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;
int32_t buflen = 512;
UErrorCode status = U_ZERO_ERROR;

zend_string* u8str;
char* msg = NULL;

intl_error_reset( NULL );

if (strcmp(tag_name, DISP_KEYWORD) == 0) {
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_PATH(keyword_name, keyword_name_len)
Z_PARAM_OPTIONAL
Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len)
ZEND_PARSE_PARAMETERS_END();
} else {
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_PATH(loc_name, loc_name_len)
Z_PARAM_PATH(keyword_name, keyword_name_len)
Z_PARAM_OPTIONAL
Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len)
ZEND_PARSE_PARAMETERS_END();

if (loc_name_len > ULOC_FULLNAME_CAPACITY) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: loc_name is bounded but keyword_name is not, in either branch. Unlike the locale-name overflow (bug 67397), uloc_getDisplayKeyword{,Value} grows the output buffer in the loop and echoes unknown keywords back, so I do not think an overlong keyword can crash. Noting it for symmetry; fine to leave as-is.

intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "name too long");
RETURN_FALSE;
}

if (loc_name_len == 0) {
loc_name = (char *)intl_locale_get_default();
}
}

if (!disp_loc_name) {
disp_loc_name = estrdup(intl_locale_get_default());
free_loc_name = 1;
}

do {
disp_name = reinterpret_cast<UChar *>(erealloc(disp_name, buflen * sizeof(UChar)));
disp_name_len = buflen;

if (strcmp(tag_name, DISP_KEYWORD) == 0) {
buflen = uloc_getDisplayKeyword(keyword_name, disp_loc_name, disp_name, disp_name_len, &status);
} else {
buflen = uloc_getDisplayKeywordValue(loc_name, keyword_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, "unable to get locale %s", tag_name);
intl_error_set( NULL, status, msg);
efree(msg);
if (disp_name) {
efree(disp_name);
}
if (free_loc_name) {
efree((void *)disp_loc_name);
disp_loc_name = NULL;
}
RETURN_FALSE;
}
} while (buflen > disp_name_len);

if (free_loc_name) {
efree((void *)disp_loc_name);
disp_loc_name = NULL;
}

u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status);
efree(disp_name);
if (!u8str) {
spprintf(&msg, 0, "error converting display name for %s to UTF-8", tag_name);
intl_error_set( NULL, status, msg);
efree(msg);
RETURN_FALSE;
}

RETVAL_NEW_STR(u8str);
}
/* }}} */

/* {{{ gets the name for the $locale in $in_locale or default_locale */
U_CFUNC PHP_FUNCTION(locale_get_display_name)
{
Expand Down Expand Up @@ -711,6 +814,20 @@ U_CFUNC PHP_FUNCTION(locale_get_display_variant)
{
get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
}
/* }}} */

/* {{{ gets the keyword display label in $in_locale or default_locale */
U_CFUNC PHP_FUNCTION(locale_get_display_keyword)
{
get_icu_disp_keyword_value_src_php(DISP_KEYWORD, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */

/* {{{ gets the keyword value display label in $in_locale or default_locale */
U_CFUNC PHP_FUNCTION(locale_get_display_keyword_value)
{
get_icu_disp_keyword_value_src_php(DISP_KEYWORD_VALUE, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */

/* {{{ return an associative array containing keyword-value
Expand Down
4 changes: 4 additions & 0 deletions ext/intl/php_intl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,10 @@ function locale_get_display_language(string $locale, ?string $displayLocale = nu

function locale_get_display_variant(string $locale, ?string $displayLocale = null): string|false {}

function locale_get_display_keyword(string $keyword, ?string $displayLocale = null): string|false {}
Comment thread
TimWolla marked this conversation as resolved.

function locale_get_display_keyword_value(string $locale, string $keyword, ?string $displayLocale = null): string|false {}

function locale_compose(array $subtags): string|false {}

function locale_parse(string $locale): ?array {}
Expand Down
17 changes: 16 additions & 1 deletion ext/intl/php_intl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions ext/intl/tests/locale/bug74993.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ intl
--FILE--
<?php
$funcs = [
'locale_get_display_keyword',
'locale_get_display_keyword_value',
'locale_get_display_language',
'locale_get_display_name',
'locale_get_display_region',
Expand All @@ -19,6 +21,23 @@ foreach ($funcs as $func) {
}
?>
--EXPECT--
Function [ <internal:intl> function locale_get_display_keyword ] {

- Parameters [2] {
Parameter #0 [ <required> string $keyword ]
Parameter #1 [ <optional> ?string $displayLocale = null ]
}
- Return [ string|false ]
}
Function [ <internal:intl> function locale_get_display_keyword_value ] {

- Parameters [3] {
Parameter #0 [ <required> string $locale ]
Parameter #1 [ <required> string $keyword ]
Parameter #2 [ <optional> ?string $displayLocale = null ]
}
- Return [ string|false ]
}
Function [ <internal:intl> function locale_get_display_language ] {

- Parameters [2] {
Expand Down
40 changes: 40 additions & 0 deletions ext/intl/tests/locale_get_display_keyword.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
locale_get_display_keyword() basic
--EXTENSIONS--
intl
--FILE--
<?php

function ut_main()
{
$default = ut_loc_get_default();
ut_loc_set_default('en');

$keyword = ut_loc_get_display_keyword('calendar', 'en');
var_dump($keyword);
var_dump(ut_loc_get_display_keyword('calendar', null) === $keyword);

$keywordValue = ut_loc_get_display_keyword_value('de_DE@calendar=gregorian', 'calendar', 'en');
var_dump($keywordValue);
var_dump(ut_loc_get_display_keyword_value('de_DE@calendar=gregorian', 'calendar', null) === $keywordValue);

$collationValue = ut_loc_get_display_keyword_value('de_DE@collation=phonebook', 'collation', 'en');
var_dump($collationValue);

ut_loc_set_default($default);
}

include_once 'ut_common.inc';
ut_run();
?>
--EXPECTREGEX--
string\([1-9][0-9]*\) "[^"\n]+"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant ; there is probably a safe subset of characters to expect within a value ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. "[A-Za-z]+( [A-Za-z]+)*" should be safer but it could only test the spaces 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite what I had in mind, e.g. previously was hardcoded "Gregorian Calendar", it might be safe to assume "Gregorian" token is going to be a safe assumption

bool(true)
string\([1-9][0-9]*\) "[^"\n]+"
bool(true)
string\([1-9][0-9]*\) "[^"\n]+"
string\([1-9][0-9]*\) "[^"\n]+"
bool(true)
string\([1-9][0-9]*\) "[^"\n]+"
bool(true)
string\([1-9][0-9]*\) "[^"\n]+"
20 changes: 20 additions & 0 deletions ext/intl/tests/locale_get_display_keyword_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
locale_get_display_keyword_value() error path
--EXTENSIONS--
intl
--FILE--
<?php

$locale = str_repeat('*', 256);

var_dump(Locale::getDisplayKeywordValue($locale, 'calendar', 'en'));
var_dump(intl_get_error_message());
var_dump(locale_get_display_keyword_value($locale, 'calendar', 'en'));
var_dump(intl_get_error_message());

?>
--EXPECT--
bool(false)
string(73) "Locale::getDisplayKeywordValue(): name too long: U_ILLEGAL_ARGUMENT_ERROR"
bool(false)
string(75) "locale_get_display_keyword_value(): name too long: U_ILLEGAL_ARGUMENT_ERROR"
40 changes: 40 additions & 0 deletions ext/intl/tests/locale_get_display_keyword_null_bytes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
locale_get_display_keyword() throwing null bytes exceptions.
--EXTENSIONS--
intl
--FILE--
<?php

function ut_main()
{
$calls = [
fn() => ut_loc_get_display_keyword("cur\0rency", "fr"),
fn() => ut_loc_get_display_keyword("currency", "f\0r"),
fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian\0", "calendar", "en"),
fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "cal\0endar", "en"),
fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "calendar", "e\0n"),
];

foreach ($calls as $call) {
try {
$call();
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
}
}

include_once 'ut_common.inc';
ut_run();
?>
--EXPECT--
Locale::getDisplayKeyword(): Argument #1 ($keyword) must not contain any null bytes
Locale::getDisplayKeyword(): Argument #2 ($displayLocale) must not contain any null bytes
Locale::getDisplayKeywordValue(): Argument #1 ($locale) must not contain any null bytes
Locale::getDisplayKeywordValue(): Argument #2 ($keyword) must not contain any null bytes
Locale::getDisplayKeywordValue(): Argument #3 ($displayLocale) must not contain any null bytes
locale_get_display_keyword(): Argument #1 ($keyword) must not contain any null bytes
locale_get_display_keyword(): Argument #2 ($displayLocale) must not contain any null bytes
locale_get_display_keyword_value(): Argument #1 ($locale) must not contain any null bytes
locale_get_display_keyword_value(): Argument #2 ($keyword) must not contain any null bytes
locale_get_display_keyword_value(): Argument #3 ($displayLocale) must not contain any null bytes
Loading
Loading