1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 ******************************************************************************
5 *
6 * Copyright (C) 2008-2015, International Business Machines
7 * Corporation and others. All Rights Reserved.
8 *
9 ******************************************************************************
10 * file name: uspoof_conf.cpp
11 * encoding: UTF-8
12 * tab size: 8 (not used)
13 * indentation:4
14 *
15 * created on: 2009Jan05 (refactoring earlier files)
16 * created by: Andy Heninger
17 *
18 * Internal classes for compiling confusable data into its binary (runtime) form.
19 */
20
21 #include "unicode/utypes.h"
22 #include "unicode/uspoof.h"
23 #if !UCONFIG_NO_REGULAR_EXPRESSIONS
24 #if !UCONFIG_NO_NORMALIZATION
25
26 #include "unicode/unorm.h"
27 #include "unicode/uregex.h"
28 #include "unicode/ustring.h"
29 #include "cmemory.h"
30 #include "uspoof_impl.h"
31 #include "uhash.h"
32 #include "uvector.h"
33 #include "uassert.h"
34 #include "uarrsort.h"
35 #include "uspoof_conf.h"
36
37 U_NAMESPACE_USE
38
39
40 //---------------------------------------------------------------------
41 //
42 // buildConfusableData Compile the source confusable data, as defined by
43 // the Unicode data file confusables.txt, into the binary
44 // structures used by the confusable detector.
45 //
46 // The binary structures are described in uspoof_impl.h
47 //
48 // 1. Parse the data, making a hash table mapping from a UChar32 to a String.
49 //
50 // 2. Sort all of the strings encountered by length, since they will need to
51 // be stored in that order in the final string table.
52 // TODO: Sorting these strings by length is no longer needed since the removal of
53 // the string lengths table. This logic can be removed to save processing time
54 // when building confusables data.
55 //
56 // 3. Build a list of keys (UChar32s) from the four mapping tables. Sort the
57 // list because that will be the ordering of our runtime table.
58 //
59 // 4. Generate the run time string table. This is generated before the key & value
60 // tables because we need the string indexes when building those tables.
61 //
62 // 5. Build the run-time key and value tables. These are parallel tables, and are built
63 // at the same time
64 //
65
SPUString(LocalPointer<UnicodeString> s)66 SPUString::SPUString(LocalPointer<UnicodeString> s) {
67 fStr = std::move(s);
68 fCharOrStrTableIndex = 0;
69 }
70
71
~SPUString()72 SPUString::~SPUString() {
73 }
74
75
SPUStringPool(UErrorCode & status)76 SPUStringPool::SPUStringPool(UErrorCode &status) : fVec(nullptr), fHash(nullptr) {
77 LocalPointer<UVector> vec(new UVector(status), status);
78 if (U_FAILURE(status)) {
79 return;
80 }
81 vec->setDeleter(
82 [](void *obj) {delete (SPUString *)obj;});
83 fVec = vec.orphan();
84 fHash = uhash_open(uhash_hashUnicodeString, // key hash function
85 uhash_compareUnicodeString, // Key Comparator
86 NULL, // Value Comparator
87 &status);
88 }
89
90
~SPUStringPool()91 SPUStringPool::~SPUStringPool() {
92 delete fVec;
93 uhash_close(fHash);
94 }
95
96
size()97 int32_t SPUStringPool::size() {
98 return fVec->size();
99 }
100
getByIndex(int32_t index)101 SPUString *SPUStringPool::getByIndex(int32_t index) {
102 SPUString *retString = (SPUString *)fVec->elementAt(index);
103 return retString;
104 }
105
106
107 // Comparison function for ordering strings in the string pool.
108 // Compare by length first, then, within a group of the same length,
109 // by code point order.
110 // Conforms to the type signature for a USortComparator in uvector.h
111
SPUStringCompare(UHashTok left,UHashTok right)112 static int32_t U_CALLCONV SPUStringCompare(UHashTok left, UHashTok right) {
113 const SPUString *sL = const_cast<const SPUString *>(
114 static_cast<SPUString *>(left.pointer));
115 const SPUString *sR = const_cast<const SPUString *>(
116 static_cast<SPUString *>(right.pointer));
117 int32_t lenL = sL->fStr->length();
118 int32_t lenR = sR->fStr->length();
119 if (lenL < lenR) {
120 return -1;
121 } else if (lenL > lenR) {
122 return 1;
123 } else {
124 return sL->fStr->compare(*(sR->fStr));
125 }
126 }
127
sort(UErrorCode & status)128 void SPUStringPool::sort(UErrorCode &status) {
129 fVec->sort(SPUStringCompare, status);
130 }
131
132
addString(UnicodeString * src,UErrorCode & status)133 SPUString *SPUStringPool::addString(UnicodeString *src, UErrorCode &status) {
134 LocalPointer<UnicodeString> lpSrc(src);
135 if (U_FAILURE(status)) {
136 return nullptr;
137 }
138 SPUString *hashedString = static_cast<SPUString *>(uhash_get(fHash, src));
139 if (hashedString != nullptr) {
140 return hashedString;
141 }
142 LocalPointer<SPUString> spuStr(new SPUString(std::move(lpSrc)), status);
143 hashedString = spuStr.getAlias();
144 fVec->adoptElement(spuStr.orphan(), status);
145 if (U_FAILURE(status)) {
146 return nullptr;
147 }
148 uhash_put(fHash, src, hashedString, &status);
149 return hashedString;
150 }
151
152
153
ConfusabledataBuilder(SpoofImpl * spImpl,UErrorCode & status)154 ConfusabledataBuilder::ConfusabledataBuilder(SpoofImpl *spImpl, UErrorCode &status) :
155 fSpoofImpl(spImpl),
156 fInput(NULL),
157 fTable(NULL),
158 fKeySet(NULL),
159 fKeyVec(NULL),
160 fValueVec(NULL),
161 fStringTable(NULL),
162 stringPool(NULL),
163 fParseLine(NULL),
164 fParseHexNum(NULL),
165 fLineNum(0)
166 {
167 if (U_FAILURE(status)) {
168 return;
169 }
170
171 fTable = uhash_open(uhash_hashLong, uhash_compareLong, NULL, &status);
172
173 fKeySet = new UnicodeSet();
174 if (fKeySet == NULL) {
175 status = U_MEMORY_ALLOCATION_ERROR;
176 return;
177 }
178
179 fKeyVec = new UVector(status);
180 if (fKeyVec == NULL) {
181 status = U_MEMORY_ALLOCATION_ERROR;
182 return;
183 }
184
185 fValueVec = new UVector(status);
186 if (fValueVec == NULL) {
187 status = U_MEMORY_ALLOCATION_ERROR;
188 return;
189 }
190
191 stringPool = new SPUStringPool(status);
192 if (stringPool == NULL) {
193 status = U_MEMORY_ALLOCATION_ERROR;
194 return;
195 }
196 }
197
198
~ConfusabledataBuilder()199 ConfusabledataBuilder::~ConfusabledataBuilder() {
200 uprv_free(fInput);
201 uregex_close(fParseLine);
202 uregex_close(fParseHexNum);
203 uhash_close(fTable);
204 delete fKeySet;
205 delete fKeyVec;
206 delete fStringTable;
207 delete fValueVec;
208 delete stringPool;
209 }
210
211
buildConfusableData(SpoofImpl * spImpl,const char * confusables,int32_t confusablesLen,int32_t * errorType,UParseError * pe,UErrorCode & status)212 void ConfusabledataBuilder::buildConfusableData(SpoofImpl * spImpl, const char * confusables,
213 int32_t confusablesLen, int32_t *errorType, UParseError *pe, UErrorCode &status) {
214
215 if (U_FAILURE(status)) {
216 return;
217 }
218 ConfusabledataBuilder builder(spImpl, status);
219 builder.build(confusables, confusablesLen, status);
220 if (U_FAILURE(status) && errorType != NULL) {
221 *errorType = USPOOF_SINGLE_SCRIPT_CONFUSABLE;
222 pe->line = builder.fLineNum;
223 }
224 }
225
226
build(const char * confusables,int32_t confusablesLen,UErrorCode & status)227 void ConfusabledataBuilder::build(const char * confusables, int32_t confusablesLen,
228 UErrorCode &status) {
229
230 // Convert the user input data from UTF-8 to UChar (UTF-16)
231 int32_t inputLen = 0;
232 if (U_FAILURE(status)) {
233 return;
234 }
235 u_strFromUTF8(NULL, 0, &inputLen, confusables, confusablesLen, &status);
236 if (status != U_BUFFER_OVERFLOW_ERROR) {
237 return;
238 }
239 status = U_ZERO_ERROR;
240 fInput = static_cast<UChar *>(uprv_malloc((inputLen+1) * sizeof(UChar)));
241 if (fInput == NULL) {
242 status = U_MEMORY_ALLOCATION_ERROR;
243 return;
244 }
245 u_strFromUTF8(fInput, inputLen+1, NULL, confusables, confusablesLen, &status);
246
247
248 // Regular Expression to parse a line from Confusables.txt. The expression will match
249 // any line. What was matched is determined by examining which capture groups have a match.
250 // Capture Group 1: the source char
251 // Capture Group 2: the replacement chars
252 // Capture Group 3-6 the table type, SL, SA, ML, or MA (deprecated)
253 // Capture Group 7: A blank or comment only line.
254 // Capture Group 8: A syntactically invalid line. Anything that didn't match before.
255 // Example Line from the confusables.txt source file:
256 // "1D702 ; 006E 0329 ; SL # MATHEMATICAL ITALIC SMALL ETA ... "
257 UnicodeString pattern(
258 "(?m)^[ \\t]*([0-9A-Fa-f]+)[ \\t]+;" // Match the source char
259 "[ \\t]*([0-9A-Fa-f]+" // Match the replacement char(s)
260 "(?:[ \\t]+[0-9A-Fa-f]+)*)[ \\t]*;" // (continued)
261 "\\s*(?:(SL)|(SA)|(ML)|(MA))" // Match the table type
262 "[ \\t]*(?:#.*?)?$" // Match any trailing #comment
263 "|^([ \\t]*(?:#.*?)?)$" // OR match empty lines or lines with only a #comment
264 "|^(.*?)$", -1, US_INV); // OR match any line, which catches illegal lines.
265 // TODO: Why are we using the regex C API here? C++ would just take UnicodeString...
266 fParseLine = uregex_open(pattern.getBuffer(), pattern.length(), 0, NULL, &status);
267
268 // Regular expression for parsing a hex number out of a space-separated list of them.
269 // Capture group 1 gets the number, with spaces removed.
270 pattern = UNICODE_STRING_SIMPLE("\\s*([0-9A-F]+)");
271 fParseHexNum = uregex_open(pattern.getBuffer(), pattern.length(), 0, NULL, &status);
272
273 // Zap any Byte Order Mark at the start of input. Changing it to a space is benign
274 // given the syntax of the input.
275 if (*fInput == 0xfeff) {
276 *fInput = 0x20;
277 }
278
279 // Parse the input, one line per iteration of this loop.
280 uregex_setText(fParseLine, fInput, inputLen, &status);
281 while (uregex_findNext(fParseLine, &status)) {
282 fLineNum++;
283 if (uregex_start(fParseLine, 7, &status) >= 0) {
284 // this was a blank or comment line.
285 continue;
286 }
287 if (uregex_start(fParseLine, 8, &status) >= 0) {
288 // input file syntax error.
289 status = U_PARSE_ERROR;
290 return;
291 }
292
293 // We have a good input line. Extract the key character and mapping string, and
294 // put them into the appropriate mapping table.
295 UChar32 keyChar = SpoofImpl::ScanHex(fInput, uregex_start(fParseLine, 1, &status),
296 uregex_end(fParseLine, 1, &status), status);
297
298 int32_t mapStringStart = uregex_start(fParseLine, 2, &status);
299 int32_t mapStringLength = uregex_end(fParseLine, 2, &status) - mapStringStart;
300 uregex_setText(fParseHexNum, &fInput[mapStringStart], mapStringLength, &status);
301
302 UnicodeString *mapString = new UnicodeString();
303 if (mapString == NULL) {
304 status = U_MEMORY_ALLOCATION_ERROR;
305 return;
306 }
307 while (uregex_findNext(fParseHexNum, &status)) {
308 UChar32 c = SpoofImpl::ScanHex(&fInput[mapStringStart], uregex_start(fParseHexNum, 1, &status),
309 uregex_end(fParseHexNum, 1, &status), status);
310 mapString->append(c);
311 }
312 U_ASSERT(mapString->length() >= 1);
313
314 // Put the map (value) string into the string pool
315 // This a little like a Java intern() - any duplicates will be eliminated.
316 SPUString *smapString = stringPool->addString(mapString, status);
317
318 // Add the UChar32 -> string mapping to the table.
319 // For Unicode 8, the SL, SA and ML tables have been discontinued.
320 // All input data from confusables.txt is tagged MA.
321 uhash_iput(fTable, keyChar, smapString, &status);
322 if (U_FAILURE(status)) { return; }
323 fKeySet->add(keyChar);
324 }
325
326 // Input data is now all parsed and collected.
327 // Now create the run-time binary form of the data.
328 //
329 // This is done in two steps. First the data is assembled into vectors and strings,
330 // for ease of construction, then the contents of these collections are dumped
331 // into the actual raw-bytes data storage.
332
333 // Build up the string array, and record the index of each string therein
334 // in the (build time only) string pool.
335 // Strings of length one are not entered into the strings array.
336 // (Strings in the table are sorted by length)
337 stringPool->sort(status);
338 fStringTable = new UnicodeString();
339 int32_t poolSize = stringPool->size();
340 int32_t i;
341 for (i=0; i<poolSize; i++) {
342 SPUString *s = stringPool->getByIndex(i);
343 int32_t strLen = s->fStr->length();
344 int32_t strIndex = fStringTable->length();
345 if (strLen == 1) {
346 // strings of length one do not get an entry in the string table.
347 // Keep the single string character itself here, which is the same
348 // convention that is used in the final run-time string table index.
349 s->fCharOrStrTableIndex = s->fStr->charAt(0);
350 } else {
351 s->fCharOrStrTableIndex = strIndex;
352 fStringTable->append(*(s->fStr));
353 }
354 }
355
356 // Construct the compile-time Key and Value tables
357 //
358 // For each key code point, check which mapping tables it applies to,
359 // and create the final data for the key & value structures.
360 //
361 // The four logical mapping tables are conflated into one combined table.
362 // If multiple logical tables have the same mapping for some key, they
363 // share a single entry in the combined table.
364 // If more than one mapping exists for the same key code point, multiple
365 // entries will be created in the table
366
367 for (int32_t range=0; range<fKeySet->getRangeCount(); range++) {
368 // It is an oddity of the UnicodeSet API that simply enumerating the contained
369 // code points requires a nested loop.
370 for (UChar32 keyChar=fKeySet->getRangeStart(range);
371 keyChar <= fKeySet->getRangeEnd(range); keyChar++) {
372 SPUString *targetMapping = static_cast<SPUString *>(uhash_iget(fTable, keyChar));
373 U_ASSERT(targetMapping != NULL);
374
375 // Set an error code if trying to consume a long string. Otherwise,
376 // codePointAndLengthToKey will abort on a U_ASSERT.
377 if (targetMapping->fStr->length() > 256) {
378 status = U_ILLEGAL_ARGUMENT_ERROR;
379 return;
380 }
381
382 int32_t key = ConfusableDataUtils::codePointAndLengthToKey(keyChar,
383 targetMapping->fStr->length());
384 int32_t value = targetMapping->fCharOrStrTableIndex;
385
386 fKeyVec->addElement(key, status);
387 fValueVec->addElement(value, status);
388 }
389 }
390
391 // Put the assembled data into the flat runtime array
392 outputData(status);
393
394 // All of the intermediate allocated data belongs to the ConfusabledataBuilder
395 // object (this), and is deleted in the destructor.
396 return;
397 }
398
399 //
400 // outputData The confusable data has been compiled and stored in intermediate
401 // collections and strings. Copy it from there to the final flat
402 // binary array.
403 //
404 // Note that as each section is added to the output data, the
405 // expand (reserveSpace() function will likely relocate it in memory.
406 // Be careful with pointers.
407 //
outputData(UErrorCode & status)408 void ConfusabledataBuilder::outputData(UErrorCode &status) {
409
410 U_ASSERT(fSpoofImpl->fSpoofData->fDataOwned == true);
411
412 // The Key Table
413 // While copying the keys to the runtime array,
414 // also sanity check that they are sorted.
415
416 int32_t numKeys = fKeyVec->size();
417 int32_t *keys =
418 static_cast<int32_t *>(fSpoofImpl->fSpoofData->reserveSpace(numKeys*sizeof(int32_t), status));
419 if (U_FAILURE(status)) {
420 return;
421 }
422 int i;
423 UChar32 previousCodePoint = 0;
424 for (i=0; i<numKeys; i++) {
425 int32_t key = fKeyVec->elementAti(i);
426 UChar32 codePoint = ConfusableDataUtils::keyToCodePoint(key);
427 (void)previousCodePoint; // Suppress unused variable warning.
428 // strictly greater because there can be only one entry per code point
429 U_ASSERT(codePoint > previousCodePoint);
430 keys[i] = key;
431 previousCodePoint = codePoint;
432 }
433 SpoofDataHeader *rawData = fSpoofImpl->fSpoofData->fRawData;
434 rawData->fCFUKeys = (int32_t)((char *)keys - (char *)rawData);
435 rawData->fCFUKeysSize = numKeys;
436 fSpoofImpl->fSpoofData->fCFUKeys = keys;
437
438
439 // The Value Table, parallels the key table
440 int32_t numValues = fValueVec->size();
441 U_ASSERT(numKeys == numValues);
442 uint16_t *values =
443 static_cast<uint16_t *>(fSpoofImpl->fSpoofData->reserveSpace(numKeys*sizeof(uint16_t), status));
444 if (U_FAILURE(status)) {
445 return;
446 }
447 for (i=0; i<numValues; i++) {
448 uint32_t value = static_cast<uint32_t>(fValueVec->elementAti(i));
449 U_ASSERT(value < 0xffff);
450 values[i] = static_cast<uint16_t>(value);
451 }
452 rawData = fSpoofImpl->fSpoofData->fRawData;
453 rawData->fCFUStringIndex = (int32_t)((char *)values - (char *)rawData);
454 rawData->fCFUStringIndexSize = numValues;
455 fSpoofImpl->fSpoofData->fCFUValues = values;
456
457 // The Strings Table.
458
459 uint32_t stringsLength = fStringTable->length();
460 // Reserve an extra space so the string will be nul-terminated. This is
461 // only a convenience, for when debugging; it is not needed otherwise.
462 UChar *strings =
463 static_cast<UChar *>(fSpoofImpl->fSpoofData->reserveSpace(stringsLength*sizeof(UChar)+2, status));
464 if (U_FAILURE(status)) {
465 return;
466 }
467 fStringTable->extract(strings, stringsLength+1, status);
468 rawData = fSpoofImpl->fSpoofData->fRawData;
469 U_ASSERT(rawData->fCFUStringTable == 0);
470 rawData->fCFUStringTable = (int32_t)((char *)strings - (char *)rawData);
471 rawData->fCFUStringTableLen = stringsLength;
472 fSpoofImpl->fSpoofData->fCFUStrings = strings;
473 }
474
475 #endif
476 #endif // !UCONFIG_NO_REGULAR_EXPRESSIONS
477
478