1 /*
2    +----------------------------------------------------------------------+
3    | This source file is subject to version 3.01 of the PHP license,      |
4    | that is bundled with this package in the file LICENSE, and is        |
5    | available through the world-wide-web at the following url:           |
6    | https://www.php.net/license/3_01.txt                                 |
7    | If you did not receive a copy of the PHP license and are unable to   |
8    | obtain it through the world-wide-web, please send a note to          |
9    | license@php.net so we can mail you a copy immediately.               |
10    +----------------------------------------------------------------------+
11    | Authors: Scott MacVicar <scottmac@php.net>                           |
12    +----------------------------------------------------------------------+
13  */
14 
15 #ifdef HAVE_CONFIG_H
16 #include <config.h>
17 #endif
18 
19 #include "php_intl.h"
20 #include "intl_convert.h"
21 #include "spoofchecker_class.h"
22 
23 /* {{{ Checks if a given text contains any suspicious characters */
PHP_METHOD(Spoofchecker,isSuspicious)24 PHP_METHOD(Spoofchecker, isSuspicious)
25 {
26 	int32_t ret, errmask;
27 	zend_string *text;
28 	zval *error_code = NULL;
29 	SPOOFCHECKER_METHOD_INIT_VARS;
30 
31 	ZEND_PARSE_PARAMETERS_START(1, 2)
32 		Z_PARAM_STR(text)
33 		Z_PARAM_OPTIONAL
34 		Z_PARAM_ZVAL(error_code)
35 	ZEND_PARSE_PARAMETERS_END();
36 
37 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
38 
39 #if U_ICU_VERSION_MAJOR_NUM >= 58
40 	ret = uspoof_check2UTF8(co->uspoof, ZSTR_VAL(text), ZSTR_LEN(text), co->uspoofres, SPOOFCHECKER_ERROR_CODE_P(co));
41 #else
42 	ret = uspoof_checkUTF8(co->uspoof, ZSTR_VAL(text), ZSTR_LEN(text), NULL, SPOOFCHECKER_ERROR_CODE_P(co));
43 #endif
44 
45 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
46 		php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
47 #if U_ICU_VERSION_MAJOR_NUM >= 58
48 		errmask = uspoof_getCheckResultChecks(co->uspoofres, SPOOFCHECKER_ERROR_CODE_P(co));
49 
50 		if (errmask != ret) {
51 			php_error_docref(NULL, E_WARNING, "unexpected error (%d), does not relate to the flags passed to setChecks (%d)", ret, errmask);
52 		}
53 #endif
54 		RETURN_TRUE;
55 	}
56 
57 	if (error_code) {
58 		ZEND_TRY_ASSIGN_REF_LONG(error_code, ret);
59 	}
60 	RETVAL_BOOL(ret != 0);
61 }
62 /* }}} */
63 
64 /* {{{ Checks if a given text contains any confusable characters */
PHP_METHOD(Spoofchecker,areConfusable)65 PHP_METHOD(Spoofchecker, areConfusable)
66 {
67 	int ret;
68 	zend_string *s1, *s2;
69 	zval *error_code = NULL;
70 	SPOOFCHECKER_METHOD_INIT_VARS;
71 
72 	ZEND_PARSE_PARAMETERS_START(2, 3)
73 		Z_PARAM_STR(s1)
74 		Z_PARAM_STR(s2)
75 		Z_PARAM_OPTIONAL
76 		Z_PARAM_ZVAL(error_code)
77 	ZEND_PARSE_PARAMETERS_END();
78 
79 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
80 	if(ZSTR_LEN(s1) > INT32_MAX || ZSTR_LEN(s2) > INT32_MAX) {
81 		SPOOFCHECKER_ERROR_CODE(co) = U_BUFFER_OVERFLOW_ERROR;
82 	} else {
83 		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));
84 	}
85 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
86 		php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
87 		RETURN_TRUE;
88 	}
89 
90 	if (error_code) {
91 		ZEND_TRY_ASSIGN_REF_LONG(error_code, ret);
92 	}
93 	RETVAL_BOOL(ret != 0);
94 }
95 /* }}} */
96 
97 /* {{{ Locales to use when running checks */
PHP_METHOD(Spoofchecker,setAllowedLocales)98 PHP_METHOD(Spoofchecker, setAllowedLocales)
99 {
100 	zend_string *locales;
101 	SPOOFCHECKER_METHOD_INIT_VARS;
102 
103 	ZEND_PARSE_PARAMETERS_START(1, 1)
104 		Z_PARAM_STR(locales)
105 	ZEND_PARSE_PARAMETERS_END();
106 
107 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
108 
109 	uspoof_setAllowedLocales(co->uspoof, ZSTR_VAL(locales), SPOOFCHECKER_ERROR_CODE_P(co));
110 
111 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
112 		php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
113 		return;
114 	}
115 }
116 /* }}} */
117 
118 /* {{{ Set the checks to run */
PHP_METHOD(Spoofchecker,setChecks)119 PHP_METHOD(Spoofchecker, setChecks)
120 {
121 	zend_long checks;
122 	SPOOFCHECKER_METHOD_INIT_VARS;
123 
124 	ZEND_PARSE_PARAMETERS_START(1, 1)
125 		Z_PARAM_LONG(checks)
126 	ZEND_PARSE_PARAMETERS_END();
127 
128 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
129 
130 	uspoof_setChecks(co->uspoof, checks, SPOOFCHECKER_ERROR_CODE_P(co));
131 
132 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
133 		php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
134 	}
135 }
136 /* }}} */
137 
138 #if U_ICU_VERSION_MAJOR_NUM >= 58
139 /* TODO Document this method on PHP.net */
140 /* {{{ Set the loosest restriction level allowed for strings. */
PHP_METHOD(Spoofchecker,setRestrictionLevel)141 PHP_METHOD(Spoofchecker, setRestrictionLevel)
142 {
143 	zend_long level;
144 	SPOOFCHECKER_METHOD_INIT_VARS;
145 
146 	ZEND_PARSE_PARAMETERS_START(1, 1)
147 		Z_PARAM_LONG(level)
148 	ZEND_PARSE_PARAMETERS_END();
149 
150 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
151 
152 	if (USPOOF_ASCII != level &&
153 			USPOOF_SINGLE_SCRIPT_RESTRICTIVE != level &&
154 			USPOOF_HIGHLY_RESTRICTIVE != level &&
155 			USPOOF_MODERATELY_RESTRICTIVE != level &&
156 			USPOOF_MINIMALLY_RESTRICTIVE != level &&
157 			USPOOF_UNRESTRICTIVE != level) {
158 		zend_argument_value_error(1, "must be one of Spoofchecker::ASCII, Spoofchecker::SINGLE_SCRIPT_RESTRICTIVE, "
159 			"Spoofchecker::SINGLE_HIGHLY_RESTRICTIVE, Spoofchecker::SINGLE_MODERATELY_RESTRICTIVE, "
160 			"Spoofchecker::SINGLE_MINIMALLY_RESTRICTIVE, or Spoofchecker::UNRESTRICTIVE");
161 		RETURN_THROWS();
162 	}
163 
164 	uspoof_setRestrictionLevel(co->uspoof, (URestrictionLevel)level);
165 }
166 /* }}} */
167 #endif
168 
PHP_METHOD(Spoofchecker,setAllowedChars)169 PHP_METHOD(Spoofchecker, setAllowedChars)
170 {
171 	zend_string *pattern;
172 	UChar *upattern = NULL;
173 	int32_t upattern_len = 0;
174 	zend_long pattern_option = 0;
175 	SPOOFCHECKER_METHOD_INIT_VARS;
176 
177 	ZEND_PARSE_PARAMETERS_START(1, 2)
178 		Z_PARAM_STR(pattern)
179 		Z_PARAM_OPTIONAL
180 		Z_PARAM_LONG(pattern_option)
181 	ZEND_PARSE_PARAMETERS_END();
182 	SPOOFCHECKER_METHOD_FETCH_OBJECT;
183 
184 	if (ZSTR_LEN(pattern) > INT32_MAX) {
185 		zend_argument_value_error(1, "must be less than or equal to " ZEND_LONG_FMT " bytes long", INT32_MAX);
186 		RETURN_THROWS();
187 	}
188 
189 	/* uset_applyPattern requires to start with a regex range char */
190 	if (ZSTR_VAL(pattern)[0] != '[' || ZSTR_VAL(pattern)[ZSTR_LEN(pattern) -1] != ']') {
191 		zend_argument_value_error(1, "must be a valid regular expression character set pattern");
192 		RETURN_THROWS();
193 	}
194 
195 	intl_convert_utf8_to_utf16(&upattern, &upattern_len, ZSTR_VAL(pattern), ZSTR_LEN(pattern), SPOOFCHECKER_ERROR_CODE_P(co));
196 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
197 		zend_argument_value_error(1, "string conversion to unicode encoding failed (%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
198 		RETURN_THROWS();
199 	}
200 
201 	USet *set = uset_openEmpty();
202 
203 	/* pattern is either USE_IGNORE_SPACE alone or in conjunction with the following flags (but mutually exclusive) */
204 	if (pattern_option &&
205             pattern_option != USET_IGNORE_SPACE &&
206 #if U_ICU_VERSION_MAJOR_NUM >= 73
207             pattern_option != (USET_IGNORE_SPACE|USET_SIMPLE_CASE_INSENSITIVE) &&
208 #endif
209             pattern_option != (USET_IGNORE_SPACE|USET_CASE_INSENSITIVE) &&
210             pattern_option != (USET_IGNORE_SPACE|USET_ADD_CASE_MAPPINGS)) {
211 		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"
212 #if U_ICU_VERSION_MAJOR_NUM >= 73
213 				" or SpoofChecker::USET_SIMPLE_CASE_INSENSITIVE"
214 #endif
215 				"))"
216 		);
217 		uset_close(set);
218 		efree(upattern);
219 		RETURN_THROWS();
220 	}
221 
222 	uset_applyPattern(set, upattern, upattern_len, (uint32_t)pattern_option, SPOOFCHECKER_ERROR_CODE_P(co));
223 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
224 		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)));
225 		uset_close(set);
226 		efree(upattern);
227 		RETURN_THROWS();
228 	}
229 
230 	uset_compact(set);
231 	uspoof_setAllowedChars(co->uspoof, set, SPOOFCHECKER_ERROR_CODE_P(co));
232 	uset_close(set);
233 	efree(upattern);
234 
235 	if (U_FAILURE(SPOOFCHECKER_ERROR_CODE(co))) {
236 		php_error_docref(NULL, E_WARNING, "(%d) %s", SPOOFCHECKER_ERROR_CODE(co), u_errorName(SPOOFCHECKER_ERROR_CODE(co)));
237 	}
238 }
239