Skip to content
Closed
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
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ PHP 8.6 UPGRADE NOTES
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.
. Added SpoofChecker::getSkeleton() to generate a confusable skeleton for a
given string.

- IO:
. Added new polling API.
Expand Down Expand Up @@ -491,6 +493,7 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue
. SpoofChecker::areBidiConfusable()
. SpoofChecker::getBidiSkeleton()
. SpoofChecker::getSkeleton()

- 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 @@ -80,6 +80,8 @@ public function setChecks(int $checks): void {}
public function setRestrictionLevel(int $level): void {}
public function setAllowedChars(string $pattern, int $patternOptions = 0): void {}

public function getSkeleton(string $string): string|false {}

#if U_ICU_VERSION_MAJOR_NUM >= 74
public function getBidiSkeleton(int $direction, string $string): string|false {}

Expand Down
8 changes: 7 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.

98 changes: 67 additions & 31 deletions ext/intl/spoofchecker/spoofchecker_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,67 +224,103 @@ 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)
/* {{{ Runs an ICU skeleton generator over a UTF-8 string, preflighting the result buffer */
template <typename F>
static zend_string *spoofchecker_skeleton(Spoofchecker_object *co, zend_string *string, F&& skeletonfn)
{
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;
return nullptr;
}

int32_t utf16_len;
u_strFromUTF8(nullptr, 0, &utf16_len, ZSTR_VAL(string), (int32_t) ZSTR_LEN(string),
u_strFromUTF8(nullptr, 0, nullptr, 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;
return nullptr;
}
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));
int32_t result_len = skeletonfn(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;
return nullptr;
}

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));
result_len = skeletonfn(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;
return nullptr;
}
SPOOFCHECKER_ERROR_CODE(co) = U_ZERO_ERROR;
ZSTR_LEN(result) = result_len;
ZSTR_VAL(result)[result_len] = '\0';
return result;
}
/* }}} */

/* {{{ Get the confusable skeleton for an identifier */
U_CFUNC PHP_METHOD(Spoofchecker, getSkeleton)
{
zend_string *string;
SPOOFCHECKER_METHOD_INIT_VARS;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(string)
ZEND_PARSE_PARAMETERS_END();

SPOOFCHECKER_METHOD_FETCH_OBJECT;

zend_string *result = spoofchecker_skeleton(co, string,
[&](char *dest, int32_t capacity, UErrorCode *status) {
/* The type parameter is deprecated since ICU 58 and must be 0. */
return uspoof_getSkeletonUTF8(co->uspoof, 0, ZSTR_VAL(string),
(int32_t) ZSTR_LEN(string), dest, capacity, status);
});
if (result == nullptr) {
RETURN_FALSE;
}
RETURN_STR(result);
}
/* }}} */

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

zend_string *result = spoofchecker_skeleton(co, string,
[&](char *dest, int32_t capacity, UErrorCode *status) {
return uspoof_getBidiSkeletonUTF8(co->uspoof, (UBiDiDirection) direction,
ZSTR_VAL(string), (int32_t) ZSTR_LEN(string), dest, capacity, status);
});
if (result == nullptr) {
RETURN_FALSE;
}
RETURN_STR(result);
}
/* }}} */
Expand Down
76 changes: 76 additions & 0 deletions ext/intl/tests/spoofchecker_skeleton.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
--TEST--
Spoofchecker::getSkeleton()
--EXTENSIONS--
intl
--FILE--
<?php
$checker = new Spoofchecker();

var_dump($checker->getSkeleton(""));
var_dump($checker->getSkeleton("abc"));

try {
$checker->getSkeleton();
} catch (ArgumentCountError $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

/* Cyrillic es is confusable with Latin c, so both share a skeleton. */
$latin = "c";
$cyrillic = "\u{0441}";
var_dump($checker->getSkeleton($latin) === $checker->getSkeleton($cyrillic));
var_dump($checker->areConfusable($latin, $cyrillic));

/* Unrelated identifiers must not collapse onto the same skeleton. */
var_dump($checker->getSkeleton("abc") === $checker->getSkeleton("xyz"));

/* A skeleton is its own skeleton. */
var_dump($checker->getSkeleton($checker->getSkeleton($cyrillic)) === $checker->getSkeleton($cyrillic));

/* The mapping may expand, which exercises the preflighted result buffer. */
var_dump($checker->getSkeleton("\u{FB01}"));

/* Skeletons are binary safe. */
var_dump(bin2hex($checker->getSkeleton("a\0b")));

/* The skeleton is derived from the confusable data only, never from the
checker configuration. */
$configured = new Spoofchecker();
$configured->setChecks(Spoofchecker::SINGLE_SCRIPT);
$configured->setRestrictionLevel(Spoofchecker::ASCII);
$configured->setAllowedChars("[a-z]");
var_dump($configured->getSkeleton($cyrillic) === $checker->getSkeleton($cyrillic));

/* Ill-formed UTF-8 is rejected instead of being substituted. */
foreach (["\x80", "\xC3", "\xE2\x82", "\xED\xA0\x80", "abc\xFF"] as $malformed) {
var_dump($checker->getSkeleton($malformed));
var_dump(intl_get_error_code() === U_INVALID_CHAR_FOUND);
}

/* The error state does not leak into the next call. */
var_dump($checker->getSkeleton("abc"));
var_dump(intl_get_error_code() === U_ZERO_ERROR);
?>
--EXPECT--
string(0) ""
string(3) "abc"
ArgumentCountError: Spoofchecker::getSkeleton() expects exactly 1 argument, 0 given
bool(true)
bool(true)
bool(false)
bool(true)
string(2) "fi"
string(6) "610062"
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
string(3) "abc"
bool(true)
Loading