Skip to content
Merged
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
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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().
Expand Down
2 changes: 2 additions & 0 deletions ext/intl/spoofchecker/spoofchecker.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
9 changes: 8 additions & 1 deletion ext/intl/spoofchecker/spoofchecker_arginfo.h

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

64 changes: 64 additions & 0 deletions ext/intl/spoofchecker/spoofchecker_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
50 changes: 50 additions & 0 deletions ext/intl/tests/spoofchecker_bidi_skeleton.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Spoofchecker::getBidiSkeleton()
--EXTENSIONS--
intl
--SKIPIF--
<?php if (version_compare(INTL_ICU_VERSION, '74.0') < 0) die('skip for ICU >= 74.0'); ?>
--FILE--
<?php
$checker = new Spoofchecker();

var_dump($checker->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);
?>

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.

nice to have

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")
);

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.

RTL tests. Added.

--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)
Loading