diff --git a/NEWS b/NEWS index 460051eed719..b8bd4280baaf 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ PHP NEWS . Implemented GH-20255 (Add a predefined calendar constant in IntlDateFormatter for the proleptic gregorian calendar). (David Carlier) . Added SpoofChecker::areBidiConfusable(). (David Carlier) + . Added SpoofChecker::getBidiSkeleton(). (Weilin Du) - Reflection: . Added ReflectionAttribute::inNamespace(), diff --git a/UPGRADING b/UPGRADING index b988847c8f53..9bbe6d42c804 100644 --- a/UPGRADING +++ b/UPGRADING @@ -332,6 +332,8 @@ PHP 8.6 UPGRADE NOTES confusable for a given text direction, along with the SpoofChecker::LTR and SpoofChecker::RTL direction constants. It is supported from icu 74. + . Added SpoofChecker::getBidiSkeleton() to generate a confusable skeleton for + a given text direction. It is supported from icu 74. - IO: . Added new polling API. @@ -488,6 +490,7 @@ PHP 8.6 UPGRADE NOTES . Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue() RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue . SpoofChecker::areBidiConfusable() + . SpoofChecker::getBidiSkeleton() - mysqli: . Added mysqli::quote_string() and mysqli_quote_string(). diff --git a/ext/intl/spoofchecker/spoofchecker.stub.php b/ext/intl/spoofchecker/spoofchecker.stub.php index f3de2fcfcf10..1ffc61faaaf5 100644 --- a/ext/intl/spoofchecker/spoofchecker.stub.php +++ b/ext/intl/spoofchecker/spoofchecker.stub.php @@ -81,6 +81,8 @@ public function setRestrictionLevel(int $level): void {} public function setAllowedChars(string $pattern, int $patternOptions = 0): void {} #if U_ICU_VERSION_MAJOR_NUM >= 74 + public function getBidiSkeleton(int $direction, string $string): string|false {} + /** * @param int $errorCode */ diff --git a/ext/intl/spoofchecker/spoofchecker_arginfo.h b/ext/intl/spoofchecker/spoofchecker_arginfo.h index cc4274d8bfe6..380f27213558 100644 --- a/ext/intl/spoofchecker/spoofchecker_arginfo.h +++ b/ext/intl/spoofchecker/spoofchecker_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit spoofchecker.stub.php instead. - * Stub hash: 528a85d28312688d9ba2c78ede2f07356bbff0f6 */ + * Stub hash: cb99c1be997a0b11e23b6ae61c27fa2ecd6e403f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Spoofchecker___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -33,6 +33,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Spoofchecker_setAllowedCha ZEND_END_ARG_INFO() #if U_ICU_VERSION_MAJOR_NUM >= 74 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Spoofchecker_getBidiSkeleton, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, direction, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Spoofchecker_areBidiConfusable, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, direction, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, string1, IS_STRING, 0) @@ -49,6 +54,7 @@ ZEND_METHOD(Spoofchecker, setChecks); ZEND_METHOD(Spoofchecker, setRestrictionLevel); ZEND_METHOD(Spoofchecker, setAllowedChars); #if U_ICU_VERSION_MAJOR_NUM >= 74 +ZEND_METHOD(Spoofchecker, getBidiSkeleton); ZEND_METHOD(Spoofchecker, areBidiConfusable); #endif @@ -61,6 +67,7 @@ static const zend_function_entry class_Spoofchecker_methods[] = { ZEND_ME(Spoofchecker, setRestrictionLevel, arginfo_class_Spoofchecker_setRestrictionLevel, ZEND_ACC_PUBLIC) ZEND_ME(Spoofchecker, setAllowedChars, arginfo_class_Spoofchecker_setAllowedChars, ZEND_ACC_PUBLIC) #if U_ICU_VERSION_MAJOR_NUM >= 74 + ZEND_ME(Spoofchecker, getBidiSkeleton, arginfo_class_Spoofchecker_getBidiSkeleton, ZEND_ACC_PUBLIC) ZEND_ME(Spoofchecker, areBidiConfusable, arginfo_class_Spoofchecker_areBidiConfusable, ZEND_ACC_PUBLIC) #endif ZEND_FE_END diff --git a/ext/intl/spoofchecker/spoofchecker_main.cpp b/ext/intl/spoofchecker/spoofchecker_main.cpp index d80ada93660f..bb1dfe6bfc76 100644 --- a/ext/intl/spoofchecker/spoofchecker_main.cpp +++ b/ext/intl/spoofchecker/spoofchecker_main.cpp @@ -225,6 +225,70 @@ U_CFUNC PHP_METHOD(Spoofchecker, setAllowedChars) } #if U_ICU_VERSION_MAJOR_NUM >= 74 +/* {{{ Get the confusable skeleton for an identifier in a given text direction */ +U_CFUNC PHP_METHOD(Spoofchecker, getBidiSkeleton) +{ + zend_long direction; + zend_string *string; + SPOOFCHECKER_METHOD_INIT_VARS; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(direction) + Z_PARAM_STR(string) + ZEND_PARSE_PARAMETERS_END(); + + SPOOFCHECKER_METHOD_FETCH_OBJECT; + + if (direction != UBIDI_LTR && direction != UBIDI_RTL) { + zend_argument_value_error(1, "must be either Spoofchecker::LTR or Spoofchecker::RTL"); + RETURN_THROWS(); + } + + if (UNEXPECTED(ZSTR_LEN(string) > INT32_MAX)) { + SPOOFCHECKER_ERROR_CODE(co) = U_BUFFER_OVERFLOW_ERROR; + intl_errors_set(SPOOFCHECKER_ERROR_P(co), SPOOFCHECKER_ERROR_CODE(co), + "Failed to convert input string to UTF-16"); + RETURN_FALSE; + } + + int32_t utf16_len; + u_strFromUTF8(nullptr, 0, &utf16_len, ZSTR_VAL(string), (int32_t) ZSTR_LEN(string), + SPOOFCHECKER_ERROR_CODE_P(co)); + if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co)) && SPOOFCHECKER_ERROR_CODE(co) != U_BUFFER_OVERFLOW_ERROR) { + intl_errors_set(SPOOFCHECKER_ERROR_P(co), SPOOFCHECKER_ERROR_CODE(co), + "Failed to convert input string to UTF-16"); + RETURN_FALSE; + } + SPOOFCHECKER_ERROR_CODE(co) = U_ZERO_ERROR; + + int32_t result_len = uspoof_getBidiSkeletonUTF8( + co->uspoof, (UBiDiDirection) direction, ZSTR_VAL(string), (int32_t) ZSTR_LEN(string), + nullptr, 0, SPOOFCHECKER_ERROR_CODE_P(co)); + if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co)) && SPOOFCHECKER_ERROR_CODE(co) != U_BUFFER_OVERFLOW_ERROR) { + intl_errors_set(SPOOFCHECKER_ERROR_P(co), SPOOFCHECKER_ERROR_CODE(co), + "Failed to generate skeleton"); + RETURN_FALSE; + } + + zend_string *result = zend_string_alloc(result_len, false); + int32_t result_capacity = result_len < INT32_MAX ? result_len + 1 : result_len; + SPOOFCHECKER_ERROR_CODE(co) = U_ZERO_ERROR; + result_len = uspoof_getBidiSkeletonUTF8( + co->uspoof, (UBiDiDirection) direction, ZSTR_VAL(string), (int32_t) ZSTR_LEN(string), + ZSTR_VAL(result), result_capacity, SPOOFCHECKER_ERROR_CODE_P(co)); + if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) { + zend_string_release(result); + intl_errors_set(SPOOFCHECKER_ERROR_P(co), SPOOFCHECKER_ERROR_CODE(co), + "Failed to generate skeleton"); + RETURN_FALSE; + } + SPOOFCHECKER_ERROR_CODE(co) = U_ZERO_ERROR; + ZSTR_LEN(result) = result_len; + ZSTR_VAL(result)[result_len] = '\0'; + RETURN_STR(result); +} +/* }}} */ + /* {{{ Checks if a given text contains any confusable characters, for a given text direction */ U_CFUNC PHP_METHOD(Spoofchecker, areBidiConfusable) { diff --git a/ext/intl/tests/spoofchecker_bidi_skeleton.phpt b/ext/intl/tests/spoofchecker_bidi_skeleton.phpt new file mode 100644 index 000000000000..c9b8a1fca1ba --- /dev/null +++ b/ext/intl/tests/spoofchecker_bidi_skeleton.phpt @@ -0,0 +1,50 @@ +--TEST-- +Spoofchecker::getBidiSkeleton() +--EXTENSIONS-- +intl +--SKIPIF-- += 74.0'); ?> +--FILE-- +getBidiSkeleton(Spoofchecker::LTR, "")); + +try { + $checker->getBidiSkeleton(Spoofchecker::RTL + 1, "a"); +} catch (ValueError $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +/* These identifiers are confusable in a left-to-right context only. */ +var_dump( + $checker->getBidiSkeleton(Spoofchecker::LTR, "A1\u{05D0}") === + $checker->getBidiSkeleton(Spoofchecker::LTR, "A\u{05D0}1") +); +var_dump( + $checker->getBidiSkeleton(Spoofchecker::RTL, "A1\u{05D0}") === + $checker->getBidiSkeleton(Spoofchecker::RTL, "A\u{05D0}1") +); + +/* These identifiers are confusable in a right-to-left context only. */ +var_dump( + $checker->getBidiSkeleton(Spoofchecker::LTR, "\u{05D0}A_1") === + $checker->getBidiSkeleton(Spoofchecker::LTR, "\u{05D0}1_A") +); +var_dump( + $checker->getBidiSkeleton(Spoofchecker::RTL, "\u{05D0}A_1") === + $checker->getBidiSkeleton(Spoofchecker::RTL, "\u{05D0}1_A") +); + +var_dump($checker->getBidiSkeleton(Spoofchecker::LTR, "\x80")); +var_dump(intl_get_error_code() === U_INVALID_CHAR_FOUND); +?> +--EXPECT-- +string(0) "" +ValueError: Spoofchecker::getBidiSkeleton(): Argument #1 ($direction) must be either Spoofchecker::LTR or Spoofchecker::RTL +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true)