PHP Intl SpoofChecker::isSuspicious() 产生误报



Case

似乎Intl扩展的Spoofchecker会产生误报:

<?php  // 7.0 on linux
// File encoding of this script is UTF-8 (thus without BOM)
$sDefaultLocale = (new Locale)->getDefault();
$oSpoofchecker = new Spoofchecker;
$oSpoofchecker->setAllowedLocales($sDefaultLocale);
$sText = 'abc';  // US-ASCII
header('Content-Type: text/plain');
print
'Default locale: ' . $sDefaultLocale . PHP_EOL
. 'Byte length: ' . strlen($sText) . PHP_EOL  // US-ASCII check
. 'Text "' . $sText . '" '
. ($oSpoofchecker->isSuspicious($sText, $sError) ? 'IS' : 'IS NOT')
. ' suspicious' . PHP_EOL
. 'Spoofchecker internal error information:' . PHP_EOL;
var_dump($sError);

结果

Default locale: en_US_POSIX
Byte length: 3
Text "abc" IS suspicious
Spoofchecker internal error information:
NULL    

预期成果

Text "abc" IS NOT suspicious

这是因为 abc 是 US-ASCII,应该是en_US_POSIX的默认值。PHP Spoofchecker 类还提到,如果使用任何非英文字符,Spoofchecker::isSuspicious()的返回代码将被TRUE,但这里的情况并非如此。

可能的原因

Spoofchecker::setAllowedLocales()的文档目前几乎不存在,参数列表不包含可能值的列表。人们只能假设它必须与Locale兼容。文档内容如下:

区域设置使用 RFC 4646 语言标记(使用连字符,而不是下划线)进行标识

与测试结果相矛盾,其中Locale使用下划线作为默认区域设置而不是连字符。但是,当使用$oSpoofchecker->setAllowedLocales('en-US');运行另一个测试时,结果保持不变。

问题

如何正确使用Spoofchecker::isSuspicious()

PHP 的国际扩展只是 ICU 的包装器,其 Spoofchecker 从 ICU 版本 58 开始减少了误报。

从他们的错误跟踪器:

ICU 58 反映了最新的 Unicode 更新,该更新弃用了 全脚本可混淆 (WSC) 检查和混合脚本可混淆项 (MSC) 检查,可在 http://www.unicode.org/L2/L2016/16229-revising-uts-39-algorithm.pdf。

在ICU 57下,检查(WSC和MSC)有以下缺陷:

  1. 他们没有将自己限制在指定的字符集 SpoofChecker#setAllowedChars 或 SpoofChecker#setAllowedLocales.
  2. 他们没有正确处理包含多个骨架的混淆对象 字符,如"æ"到"ae"。
  3. WSC表现出高假阳性 率,特别是随着越来越多的条目被添加到 令人困惑.txt。
  4. 所有未通过 MSC 的字符串也未达到限制级别。 (你的字符串"goօgle"就是一个例子。

考虑到这些陷阱, WSC和MSC从ICU 58中移除。

强调我的。WSC 检查是字符串失败的原因。(请注意,它在 ICU 版本为 58.1 及更高版本的地方通过,因为该检查已被完全删除。

至于如何正确使用Spoofchecker::isSuspicious():

  1. 升级 ICU(这通常是个好主意)或
  2. 按照 Syscall 的答案使用Spoofchecker::setChecks(),省略 WSC 检查Spoofchecker::WHOLE_SCRIPT_CONFUSABLE(涵盖这种情况)和 MSC 检查Spoofchecker::MIXED_SCRIPT_CONFUSABLE(同样从最新版本中删除)。

您可以使用Spoofchecker::setChecks(int $checks)来指定如何验证字符串。

$checks常量列在 Spoofchecker 类文档中,并由用户在注释中描述。

您可以使用SpoofChecker::CHAR_LIMIT(或多个常量的组合,例如:SpoofChecker::CHAR_LIMIT|Spoofchecker::INVISIBLE):

CHAR_LIMIT:检查标识符是否仅包含一组指定的可接受字符中的字符。
INVISIBLE:检查标识符是否存在不可见字符(如零宽度空格)或可能不会显示的字符序列,例如多次出现相同的非空格标记。

$sDefaultLocale = (new Locale)->getDefault();
$oSpoofchecker = new Spoofchecker;
$oSpoofchecker->setAllowedLocales($sDefaultLocale);
$oSpoofchecker->setChecks(SpoofChecker::CHAR_LIMIT);
$sText = 'abc';  // US-ASCII
header('Content-Type: text/plain');
print
'Default locale: ' . $sDefaultLocale . PHP_EOL
. 'Byte length: ' . strlen($sText) . PHP_EOL  // US-ASCII check
. 'Text "' . $sText . '" '
. ($oSpoofchecker->isSuspicious($sText, $sError) ? 'IS' : 'IS NOT')
. ' suspicious' . PHP_EOL
. 'Spoofchecker internal error information:' . PHP_EOL;
var_dump($sError);

将输出:

Default locale: en_US_POSIX
Byte length: 3
Text "abc" IS NOT suspicious
Spoofchecker internal error information:
NULL

使用isSuspicious()文档中的示例,文本Рaypal.com(第一个字母来自 Cyrylic),返回的方法:

Text "Рaypal.com" IS suspicious 

最新更新