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
12 changes: 12 additions & 0 deletions ext/intl/spoofchecker/spoofchecker.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ class Spoofchecker
public const int HIDDEN_OVERLAY = UNKNOWN;
#endif

/** @cvalue USET_IGNORE_SPACE */
public const int IGNORE_SPACE = UNKNOWN;
/** @cvalue USET_CASE_INSENSITIVE */
public const int CASE_INSENSITIVE = UNKNOWN;
/** @cvalue USET_ADD_CASE_MAPPINGS */
public const int ADD_CASE_MAPPINGS = UNKNOWN;
#if U_ICU_VERSION_MAJOR_NUM >= 73
/** @cvalue USET_SIMPLE_CASE_INSENSITIVE */
public const int SIMPLE_CASE_INSENSITIVE = UNKNOWN;
#endif

public function __construct() {}

/**
Expand All @@ -64,4 +75,5 @@ public function setChecks(int $checks): void {}
/** @tentative-return-type */
public function setRestrictionLevel(int $level): void {}
#endif
public function setAllowedChars(string $pattern, int $patternOptions = 0): void {}
}
35 changes: 34 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.

99 changes: 84 additions & 15 deletions ext/intl/spoofchecker/spoofchecker_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@
#endif

#include "php_intl.h"
#include "intl_convert.h"
#include "spoofchecker_class.h"

/* {{{ Checks if a given text contains any suspicious characters */
PHP_METHOD(Spoofchecker, isSuspicious)
{
int32_t ret, errmask;
char *text;
size_t text_len;
zend_string *text;
zval *error_code = NULL;
SPOOFCHECKER_METHOD_INIT_VARS;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STRING(text, text_len)
Z_PARAM_STR(text)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(error_code)
ZEND_PARSE_PARAMETERS_END();

SPOOFCHECKER_METHOD_FETCH_OBJECT;

#if U_ICU_VERSION_MAJOR_NUM >= 58
ret = uspoof_check2UTF8(co->uspoof, text, text_len, co->uspoofres, SPOOFCHECKER_ERROR_CODE_P(co));
ret = uspoof_check2UTF8(co->uspoof, ZSTR_VAL(text), ZSTR_LEN(text), co->uspoofres, SPOOFCHECKER_ERROR_CODE_P(co));
#else
ret = uspoof_checkUTF8(co->uspoof, text, text_len, NULL, SPOOFCHECKER_ERROR_CODE_P(co));
ret = uspoof_checkUTF8(co->uspoof, ZSTR_VAL(text), ZSTR_LEN(text), NULL, SPOOFCHECKER_ERROR_CODE_P(co));
#endif

if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
Expand All @@ -65,23 +65,22 @@ PHP_METHOD(Spoofchecker, isSuspicious)
PHP_METHOD(Spoofchecker, areConfusable)
{
int ret;
char *s1, *s2;
size_t s1_len, s2_len;
zend_string *s1, *s2;
zval *error_code = NULL;
SPOOFCHECKER_METHOD_INIT_VARS;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(s1, s1_len)
Z_PARAM_STRING(s2, s2_len)
Z_PARAM_STR(s1)
Z_PARAM_STR(s2)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(error_code)
ZEND_PARSE_PARAMETERS_END();

SPOOFCHECKER_METHOD_FETCH_OBJECT;
if(s1_len > INT32_MAX || s2_len > INT32_MAX) {
if(ZSTR_LEN(s1) > INT32_MAX || ZSTR_LEN(s2) > INT32_MAX) {
SPOOFCHECKER_ERROR_CODE(co) = U_BUFFER_OVERFLOW_ERROR;
} else {
ret = uspoof_areConfusableUTF8(co->uspoof, s1, (int32_t)s1_len, s2, (int32_t)s2_len, SPOOFCHECKER_ERROR_CODE_P(co));
ret = uspoof_areConfusableUTF8(co->uspoof, ZSTR_VAL(s1), (int32_t)ZSTR_LEN(s1), ZSTR_VAL(s2), (int32_t)ZSTR_LEN(s2), SPOOFCHECKER_ERROR_CODE_P(co));
}
if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
Expand All @@ -98,17 +97,16 @@ PHP_METHOD(Spoofchecker, areConfusable)
/* {{{ Locales to use when running checks */
PHP_METHOD(Spoofchecker, setAllowedLocales)
{
char *locales;
size_t locales_len;
zend_string *locales;
SPOOFCHECKER_METHOD_INIT_VARS;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(locales, locales_len)
Z_PARAM_STR(locales)
ZEND_PARSE_PARAMETERS_END();

SPOOFCHECKER_METHOD_FETCH_OBJECT;

uspoof_setAllowedLocales(co->uspoof, locales, SPOOFCHECKER_ERROR_CODE_P(co));
uspoof_setAllowedLocales(co->uspoof, ZSTR_VAL(locales), SPOOFCHECKER_ERROR_CODE_P(co));

if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
Expand Down Expand Up @@ -167,3 +165,74 @@ PHP_METHOD(Spoofchecker, setRestrictionLevel)
}
/* }}} */
#endif

PHP_METHOD(Spoofchecker, setAllowedChars)
{
zend_string *pattern;
UChar *upattern = NULL;
int32_t upattern_len = 0;
zend_long pattern_option = 0;
SPOOFCHECKER_METHOD_INIT_VARS;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STR(pattern)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(pattern_option)
ZEND_PARSE_PARAMETERS_END();
SPOOFCHECKER_METHOD_FETCH_OBJECT;

if (ZSTR_LEN(pattern) > INT32_MAX) {
zend_argument_value_error(1, "must be less than or equal to " ZEND_LONG_FMT " bytes long", INT32_MAX);
RETURN_THROWS();
}

/* uset_applyPattern requires to start with a regex range char */
if (ZSTR_VAL(pattern)[0] != '[' || ZSTR_VAL(pattern)[ZSTR_LEN(pattern) -1] != ']') {
zend_argument_value_error(1, "must be a valid regular expression character set pattern");
RETURN_THROWS();
}

intl_convert_utf8_to_utf16(&upattern, &upattern_len, ZSTR_VAL(pattern), ZSTR_LEN(pattern), SPOOFCHECKER_ERROR_CODE_P(co));
if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
zend_argument_value_error(1, "string conversion to unicode encoding failed (%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
RETURN_THROWS();
}

USet *set = uset_openEmpty();

/* pattern is either USE_IGNORE_SPACE alone or in conjunction with the following flags (but mutually exclusive) */
if (pattern_option &&
pattern_option != USET_IGNORE_SPACE &&
#if U_ICU_VERSION_MAJOR_NUM >= 73
pattern_option != (USET_IGNORE_SPACE|USET_SIMPLE_CASE_INSENSITIVE) &&
#endif
pattern_option != (USET_IGNORE_SPACE|USET_CASE_INSENSITIVE) &&
pattern_option != (USET_IGNORE_SPACE|USET_ADD_CASE_MAPPINGS)) {
zend_argument_value_error(2, "must be a valid pattern option, 0 or (SpoofChecker::IGNORE_SPACE|(<none> or SpoofChecker::USET_CASE_INSENSITIVE or SpoofChecker::USET_ADD_CASE_MAPPINGS"
#if U_ICU_VERSION_MAJOR_NUM >= 73
" or SpoofChecker::USET_SIMPLE_CASE_INSENSITIVE"
#endif
"))"
);
uset_close(set);
efree(upattern);
RETURN_THROWS();
}

uset_applyPattern(set, upattern, upattern_len, (uint32_t)pattern_option, SPOOFCHECKER_ERROR_CODE_P(co));
if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
zend_argument_value_error(1, "must be a valid regular expression character set pattern (%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
uset_close(set);
efree(upattern);
RETURN_THROWS();
}

uset_compact(set);
uspoof_setAllowedChars(co->uspoof, set, SPOOFCHECKER_ERROR_CODE_P(co));
uset_close(set);
efree(upattern);

if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
Copy link
Member

Choose a reason for hiding this comment

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

A bit unfortunate of an API design, but at least consistent with what we have, so 🤷

}
}
44 changes: 44 additions & 0 deletions ext/intl/tests/spoofchecker_008.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
spoofchecker with locale settings
--EXTENSIONS--
intl
--SKIPIF--
<?php if(!class_exists("Spoofchecker")) print 'skip'; ?>
--FILE--
<?php

$s = new Spoofchecker();

$s->setAllowedChars('[a-z]');
var_dump($s->isSuspicious("123"));
$s->setAllowedChars('[1-3]');
var_dump($s->isSuspicious("123"));
$s->setAllowedChars('[a-z]', SpoofChecker::IGNORE_SPACE | SpoofChecker::CASE_INSENSITIVE);
var_dump($s->isSuspicious("ABC"));

try {
$s->setAllowedChars('[a-z]', 1024);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}

try {
$s->setAllowedChars("A-Z]");
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}

try {
$s->setAllowedChars("[A-Z");
} catch (\ValueError $e) {
echo $e->getMessage();
}

?>
--EXPECTF--
bool(true)
bool(false)
bool(false)
Spoofchecker::setAllowedChars(): Argument #2 ($patternOptions) must be a valid pattern option, 0 or (SpoofChecker::IGNORE_SPACE|(<none> or SpoofChecker::USET_CASE_INSENSITIVE%s))
Spoofchecker::setAllowedChars(): Argument #1 ($pattern) must be a valid regular expression character set pattern
Spoofchecker::setAllowedChars(): Argument #1 ($pattern) must be a valid regular expression character set pattern