960 lines
31 KiB
C++
960 lines
31 KiB
C++
|
// © 2016 and later: Unicode, Inc. and others.
|
|||
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|||
|
/*
|
|||
|
**********************************************************************
|
|||
|
* Copyright (C) 2008-2016, International Business Machines
|
|||
|
* Corporation and others. All Rights Reserved.
|
|||
|
**********************************************************************
|
|||
|
*/
|
|||
|
|
|||
|
#include "unicode/utypes.h"
|
|||
|
#include "unicode/uspoof.h"
|
|||
|
#include "unicode/uchar.h"
|
|||
|
#include "unicode/uniset.h"
|
|||
|
#include "unicode/utf16.h"
|
|||
|
#include "utrie2.h"
|
|||
|
#include "cmemory.h"
|
|||
|
#include "cstring.h"
|
|||
|
#include "scriptset.h"
|
|||
|
#include "umutex.h"
|
|||
|
#include "udataswp.h"
|
|||
|
#include "uassert.h"
|
|||
|
#include "ucln_in.h"
|
|||
|
#include "uspoof_impl.h"
|
|||
|
|
|||
|
#if !UCONFIG_NO_NORMALIZATION
|
|||
|
|
|||
|
|
|||
|
U_NAMESPACE_BEGIN
|
|||
|
|
|||
|
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SpoofImpl)
|
|||
|
|
|||
|
SpoofImpl::SpoofImpl(SpoofData *data, UErrorCode& status) {
|
|||
|
construct(status);
|
|||
|
fSpoofData = data;
|
|||
|
}
|
|||
|
|
|||
|
SpoofImpl::SpoofImpl(UErrorCode& status) {
|
|||
|
construct(status);
|
|||
|
|
|||
|
// TODO: Call this method where it is actually needed, instead of in the
|
|||
|
// constructor, to allow for lazy data loading. See #12696.
|
|||
|
fSpoofData = SpoofData::getDefault(status);
|
|||
|
}
|
|||
|
|
|||
|
SpoofImpl::SpoofImpl() {
|
|||
|
UErrorCode status = U_ZERO_ERROR;
|
|||
|
construct(status);
|
|||
|
|
|||
|
// TODO: Call this method where it is actually needed, instead of in the
|
|||
|
// constructor, to allow for lazy data loading. See #12696.
|
|||
|
fSpoofData = SpoofData::getDefault(status);
|
|||
|
}
|
|||
|
|
|||
|
void SpoofImpl::construct(UErrorCode& status) {
|
|||
|
fChecks = USPOOF_ALL_CHECKS;
|
|||
|
fSpoofData = NULL;
|
|||
|
fAllowedCharsSet = NULL;
|
|||
|
fAllowedLocales = NULL;
|
|||
|
fRestrictionLevel = USPOOF_HIGHLY_RESTRICTIVE;
|
|||
|
|
|||
|
if (U_FAILURE(status)) { return; }
|
|||
|
|
|||
|
UnicodeSet *allowedCharsSet = new UnicodeSet(0, 0x10ffff);
|
|||
|
fAllowedCharsSet = allowedCharsSet;
|
|||
|
fAllowedLocales = uprv_strdup("");
|
|||
|
if (fAllowedCharsSet == NULL || fAllowedLocales == NULL) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
allowedCharsSet->freeze();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// Copy Constructor, used by the user level clone() function.
|
|||
|
SpoofImpl::SpoofImpl(const SpoofImpl &src, UErrorCode &status) :
|
|||
|
fChecks(USPOOF_ALL_CHECKS), fSpoofData(NULL), fAllowedCharsSet(NULL) ,
|
|||
|
fAllowedLocales(NULL) {
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
fChecks = src.fChecks;
|
|||
|
if (src.fSpoofData != NULL) {
|
|||
|
fSpoofData = src.fSpoofData->addReference();
|
|||
|
}
|
|||
|
fAllowedCharsSet = src.fAllowedCharsSet->clone();
|
|||
|
fAllowedLocales = uprv_strdup(src.fAllowedLocales);
|
|||
|
if (fAllowedCharsSet == NULL || fAllowedLocales == NULL) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
}
|
|||
|
fRestrictionLevel = src.fRestrictionLevel;
|
|||
|
}
|
|||
|
|
|||
|
SpoofImpl::~SpoofImpl() {
|
|||
|
if (fSpoofData != NULL) {
|
|||
|
fSpoofData->removeReference(); // Will delete if refCount goes to zero.
|
|||
|
}
|
|||
|
delete fAllowedCharsSet;
|
|||
|
uprv_free((void *)fAllowedLocales);
|
|||
|
}
|
|||
|
|
|||
|
// Cast this instance as a USpoofChecker for the C API.
|
|||
|
USpoofChecker *SpoofImpl::asUSpoofChecker() {
|
|||
|
return exportForC();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Incoming parameter check on Status and the SpoofChecker object
|
|||
|
// received from the C API.
|
|||
|
//
|
|||
|
const SpoofImpl *SpoofImpl::validateThis(const USpoofChecker *sc, UErrorCode &status) {
|
|||
|
auto* This = validate(sc, status);
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
if (This->fSpoofData != NULL && !This->fSpoofData->validateDataVersion(status)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
return This;
|
|||
|
}
|
|||
|
|
|||
|
SpoofImpl *SpoofImpl::validateThis(USpoofChecker *sc, UErrorCode &status) {
|
|||
|
return const_cast<SpoofImpl *>
|
|||
|
(SpoofImpl::validateThis(const_cast<const USpoofChecker *>(sc), status));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void SpoofImpl::setAllowedLocales(const char *localesList, UErrorCode &status) {
|
|||
|
UnicodeSet allowedChars;
|
|||
|
UnicodeSet *tmpSet = NULL;
|
|||
|
const char *locStart = localesList;
|
|||
|
const char *locEnd = NULL;
|
|||
|
const char *localesListEnd = localesList + uprv_strlen(localesList);
|
|||
|
int32_t localeListCount = 0; // Number of locales provided by caller.
|
|||
|
|
|||
|
// Loop runs once per locale from the localesList, a comma separated list of locales.
|
|||
|
do {
|
|||
|
locEnd = uprv_strchr(locStart, ',');
|
|||
|
if (locEnd == NULL) {
|
|||
|
locEnd = localesListEnd;
|
|||
|
}
|
|||
|
while (*locStart == ' ') {
|
|||
|
locStart++;
|
|||
|
}
|
|||
|
const char *trimmedEnd = locEnd-1;
|
|||
|
while (trimmedEnd > locStart && *trimmedEnd == ' ') {
|
|||
|
trimmedEnd--;
|
|||
|
}
|
|||
|
if (trimmedEnd <= locStart) {
|
|||
|
break;
|
|||
|
}
|
|||
|
const char *locale = uprv_strndup(locStart, (int32_t)(trimmedEnd + 1 - locStart));
|
|||
|
localeListCount++;
|
|||
|
|
|||
|
// We have one locale from the locales list.
|
|||
|
// Add the script chars for this locale to the accumulating set of allowed chars.
|
|||
|
// If the locale is no good, we will be notified back via status.
|
|||
|
addScriptChars(locale, &allowedChars, status);
|
|||
|
uprv_free((void *)locale);
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
break;
|
|||
|
}
|
|||
|
locStart = locEnd + 1;
|
|||
|
} while (locStart < localesListEnd);
|
|||
|
|
|||
|
// If our caller provided an empty list of locales, we disable the allowed characters checking
|
|||
|
if (localeListCount == 0) {
|
|||
|
uprv_free((void *)fAllowedLocales);
|
|||
|
fAllowedLocales = uprv_strdup("");
|
|||
|
tmpSet = new UnicodeSet(0, 0x10ffff);
|
|||
|
if (fAllowedLocales == NULL || tmpSet == NULL) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
tmpSet->freeze();
|
|||
|
delete fAllowedCharsSet;
|
|||
|
fAllowedCharsSet = tmpSet;
|
|||
|
fChecks &= ~USPOOF_CHAR_LIMIT;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// Add all common and inherited characters to the set of allowed chars.
|
|||
|
UnicodeSet tempSet;
|
|||
|
tempSet.applyIntPropertyValue(UCHAR_SCRIPT, USCRIPT_COMMON, status);
|
|||
|
allowedChars.addAll(tempSet);
|
|||
|
tempSet.applyIntPropertyValue(UCHAR_SCRIPT, USCRIPT_INHERITED, status);
|
|||
|
allowedChars.addAll(tempSet);
|
|||
|
|
|||
|
// If anything went wrong, we bail out without changing
|
|||
|
// the state of the spoof checker.
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Store the updated spoof checker state.
|
|||
|
tmpSet = allowedChars.clone();
|
|||
|
const char *tmpLocalesList = uprv_strdup(localesList);
|
|||
|
if (tmpSet == NULL || tmpLocalesList == NULL) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
uprv_free((void *)fAllowedLocales);
|
|||
|
fAllowedLocales = tmpLocalesList;
|
|||
|
tmpSet->freeze();
|
|||
|
delete fAllowedCharsSet;
|
|||
|
fAllowedCharsSet = tmpSet;
|
|||
|
fChecks |= USPOOF_CHAR_LIMIT;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
const char * SpoofImpl::getAllowedLocales(UErrorCode &/*status*/) {
|
|||
|
return fAllowedLocales;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// Given a locale (a language), add all the characters from all of the scripts used with that language
|
|||
|
// to the allowedChars UnicodeSet
|
|||
|
|
|||
|
void SpoofImpl::addScriptChars(const char *locale, UnicodeSet *allowedChars, UErrorCode &status) {
|
|||
|
UScriptCode scripts[30];
|
|||
|
|
|||
|
int32_t numScripts = uscript_getCode(locale, scripts, UPRV_LENGTHOF(scripts), &status);
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (status == U_USING_DEFAULT_WARNING) {
|
|||
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
UnicodeSet tmpSet;
|
|||
|
int32_t i;
|
|||
|
for (i=0; i<numScripts; i++) {
|
|||
|
tmpSet.applyIntPropertyValue(UCHAR_SCRIPT, scripts[i], status);
|
|||
|
allowedChars->addAll(tmpSet);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Computes the augmented script set for a code point, according to UTS 39 section 5.1.
|
|||
|
void SpoofImpl::getAugmentedScriptSet(UChar32 codePoint, ScriptSet& result, UErrorCode& status) {
|
|||
|
result.resetAll();
|
|||
|
result.setScriptExtensions(codePoint, status);
|
|||
|
if (U_FAILURE(status)) { return; }
|
|||
|
|
|||
|
// Section 5.1 step 1
|
|||
|
if (result.test(USCRIPT_HAN, status)) {
|
|||
|
result.set(USCRIPT_HAN_WITH_BOPOMOFO, status);
|
|||
|
result.set(USCRIPT_JAPANESE, status);
|
|||
|
result.set(USCRIPT_KOREAN, status);
|
|||
|
}
|
|||
|
if (result.test(USCRIPT_HIRAGANA, status)) {
|
|||
|
result.set(USCRIPT_JAPANESE, status);
|
|||
|
}
|
|||
|
if (result.test(USCRIPT_KATAKANA, status)) {
|
|||
|
result.set(USCRIPT_JAPANESE, status);
|
|||
|
}
|
|||
|
if (result.test(USCRIPT_HANGUL, status)) {
|
|||
|
result.set(USCRIPT_KOREAN, status);
|
|||
|
}
|
|||
|
if (result.test(USCRIPT_BOPOMOFO, status)) {
|
|||
|
result.set(USCRIPT_HAN_WITH_BOPOMOFO, status);
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.1 step 2
|
|||
|
if (result.test(USCRIPT_COMMON, status) || result.test(USCRIPT_INHERITED, status)) {
|
|||
|
result.setAll();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Computes the resolved script set for a string, according to UTS 39 section 5.1.
|
|||
|
void SpoofImpl::getResolvedScriptSet(const UnicodeString& input, ScriptSet& result, UErrorCode& status) const {
|
|||
|
getResolvedScriptSetWithout(input, USCRIPT_CODE_LIMIT, result, status);
|
|||
|
}
|
|||
|
|
|||
|
// Computes the resolved script set for a string, omitting characters having the specified script.
|
|||
|
// If USCRIPT_CODE_LIMIT is passed as the second argument, all characters are included.
|
|||
|
void SpoofImpl::getResolvedScriptSetWithout(const UnicodeString& input, UScriptCode script, ScriptSet& result, UErrorCode& status) const {
|
|||
|
result.setAll();
|
|||
|
|
|||
|
ScriptSet temp;
|
|||
|
UChar32 codePoint;
|
|||
|
for (int32_t i = 0; i < input.length(); i += U16_LENGTH(codePoint)) {
|
|||
|
codePoint = input.char32At(i);
|
|||
|
|
|||
|
// Compute the augmented script set for the character
|
|||
|
getAugmentedScriptSet(codePoint, temp, status);
|
|||
|
if (U_FAILURE(status)) { return; }
|
|||
|
|
|||
|
// Intersect the augmented script set with the resolved script set, but only if the character doesn't
|
|||
|
// have the script specified in the function call
|
|||
|
if (script == USCRIPT_CODE_LIMIT || !temp.test(script, status)) {
|
|||
|
result.intersect(temp);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Computes the set of numerics for a string, according to UTS 39 section 5.3.
|
|||
|
void SpoofImpl::getNumerics(const UnicodeString& input, UnicodeSet& result, UErrorCode& /*status*/) const {
|
|||
|
result.clear();
|
|||
|
|
|||
|
UChar32 codePoint;
|
|||
|
for (int32_t i = 0; i < input.length(); i += U16_LENGTH(codePoint)) {
|
|||
|
codePoint = input.char32At(i);
|
|||
|
|
|||
|
// Store a representative character for each kind of decimal digit
|
|||
|
if (u_charType(codePoint) == U_DECIMAL_DIGIT_NUMBER) {
|
|||
|
// Store the zero character as a representative for comparison.
|
|||
|
// Unicode guarantees it is codePoint - value
|
|||
|
result.add(codePoint - (UChar32)u_getNumericValue(codePoint));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Computes the restriction level of a string, according to UTS 39 section 5.2.
|
|||
|
URestrictionLevel SpoofImpl::getRestrictionLevel(const UnicodeString& input, UErrorCode& status) const {
|
|||
|
// Section 5.2 step 1:
|
|||
|
if (!fAllowedCharsSet->containsAll(input)) {
|
|||
|
return USPOOF_UNRESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.2 step 2
|
|||
|
// Java use a static UnicodeSet for this test. In C++, avoid the static variable
|
|||
|
// and just do a simple for loop.
|
|||
|
UBool allASCII = TRUE;
|
|||
|
for (int32_t i=0, length=input.length(); i<length; i++) {
|
|||
|
if (input.charAt(i) > 0x7f) {
|
|||
|
allASCII = FALSE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (allASCII) {
|
|||
|
return USPOOF_ASCII;
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.2 steps 3:
|
|||
|
ScriptSet resolvedScriptSet;
|
|||
|
getResolvedScriptSet(input, resolvedScriptSet, status);
|
|||
|
if (U_FAILURE(status)) { return USPOOF_UNRESTRICTIVE; }
|
|||
|
|
|||
|
// Section 5.2 step 4:
|
|||
|
if (!resolvedScriptSet.isEmpty()) {
|
|||
|
return USPOOF_SINGLE_SCRIPT_RESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.2 step 5:
|
|||
|
ScriptSet resolvedNoLatn;
|
|||
|
getResolvedScriptSetWithout(input, USCRIPT_LATIN, resolvedNoLatn, status);
|
|||
|
if (U_FAILURE(status)) { return USPOOF_UNRESTRICTIVE; }
|
|||
|
|
|||
|
// Section 5.2 step 6:
|
|||
|
if (resolvedNoLatn.test(USCRIPT_HAN_WITH_BOPOMOFO, status)
|
|||
|
|| resolvedNoLatn.test(USCRIPT_JAPANESE, status)
|
|||
|
|| resolvedNoLatn.test(USCRIPT_KOREAN, status)) {
|
|||
|
return USPOOF_HIGHLY_RESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.2 step 7:
|
|||
|
if (!resolvedNoLatn.isEmpty()
|
|||
|
&& !resolvedNoLatn.test(USCRIPT_CYRILLIC, status)
|
|||
|
&& !resolvedNoLatn.test(USCRIPT_GREEK, status)
|
|||
|
&& !resolvedNoLatn.test(USCRIPT_CHEROKEE, status)) {
|
|||
|
return USPOOF_MODERATELY_RESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
// Section 5.2 step 8:
|
|||
|
return USPOOF_MINIMALLY_RESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
int32_t SpoofImpl::findHiddenOverlay(const UnicodeString& input, UErrorCode&) const {
|
|||
|
bool sawLeadCharacter = false;
|
|||
|
for (int32_t i=0; i<input.length();) {
|
|||
|
UChar32 cp = input.char32At(i);
|
|||
|
if (sawLeadCharacter && cp == 0x0307) {
|
|||
|
return i;
|
|||
|
}
|
|||
|
uint8_t combiningClass = u_getCombiningClass(cp);
|
|||
|
// Skip over characters except for those with combining class 0 (non-combining characters) or with
|
|||
|
// combining class 230 (same class as U+0307)
|
|||
|
U_ASSERT(u_getCombiningClass(0x0307) == 230);
|
|||
|
if (combiningClass == 0 || combiningClass == 230) {
|
|||
|
sawLeadCharacter = isIllegalCombiningDotLeadCharacter(cp);
|
|||
|
}
|
|||
|
i += U16_LENGTH(cp);
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static inline bool isIllegalCombiningDotLeadCharacterNoLookup(UChar32 cp) {
|
|||
|
return cp == u'i' || cp == u'j' || cp == u'ı' || cp == u'ȷ' || cp == u'l' ||
|
|||
|
u_hasBinaryProperty(cp, UCHAR_SOFT_DOTTED);
|
|||
|
}
|
|||
|
|
|||
|
bool SpoofImpl::isIllegalCombiningDotLeadCharacter(UChar32 cp) const {
|
|||
|
if (isIllegalCombiningDotLeadCharacterNoLookup(cp)) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
UnicodeString skelStr;
|
|||
|
fSpoofData->confusableLookup(cp, skelStr);
|
|||
|
UChar32 finalCp = skelStr.char32At(skelStr.moveIndex32(skelStr.length(), -1));
|
|||
|
if (finalCp != cp && isIllegalCombiningDotLeadCharacterNoLookup(finalCp)) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// Convert a text format hex number. Utility function used by builder code. Static.
|
|||
|
// Input: UChar *string text. Output: a UChar32
|
|||
|
// Input has been pre-checked, and will have no non-hex chars.
|
|||
|
// The number must fall in the code point range of 0..0x10ffff
|
|||
|
// Static Function.
|
|||
|
UChar32 SpoofImpl::ScanHex(const UChar *s, int32_t start, int32_t limit, UErrorCode &status) {
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
U_ASSERT(limit-start > 0);
|
|||
|
uint32_t val = 0;
|
|||
|
int i;
|
|||
|
for (i=start; i<limit; i++) {
|
|||
|
int digitVal = s[i] - 0x30;
|
|||
|
if (digitVal>9) {
|
|||
|
digitVal = 0xa + (s[i] - 0x41); // Upper Case 'A'
|
|||
|
}
|
|||
|
if (digitVal>15) {
|
|||
|
digitVal = 0xa + (s[i] - 0x61); // Lower Case 'a'
|
|||
|
}
|
|||
|
U_ASSERT(digitVal <= 0xf);
|
|||
|
val <<= 4;
|
|||
|
val += digitVal;
|
|||
|
}
|
|||
|
if (val > 0x10ffff) {
|
|||
|
status = U_PARSE_ERROR;
|
|||
|
val = 0;
|
|||
|
}
|
|||
|
return (UChar32)val;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------
|
|||
|
//
|
|||
|
// class CheckResult Implementation
|
|||
|
//
|
|||
|
//-----------------------------------------
|
|||
|
|
|||
|
CheckResult::CheckResult() {
|
|||
|
clear();
|
|||
|
}
|
|||
|
|
|||
|
USpoofCheckResult* CheckResult::asUSpoofCheckResult() {
|
|||
|
return exportForC();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Incoming parameter check on Status and the CheckResult object
|
|||
|
// received from the C API.
|
|||
|
//
|
|||
|
const CheckResult* CheckResult::validateThis(const USpoofCheckResult *ptr, UErrorCode &status) {
|
|||
|
return validate(ptr, status);
|
|||
|
}
|
|||
|
|
|||
|
CheckResult* CheckResult::validateThis(USpoofCheckResult *ptr, UErrorCode &status) {
|
|||
|
return validate(ptr, status);
|
|||
|
}
|
|||
|
|
|||
|
void CheckResult::clear() {
|
|||
|
fChecks = 0;
|
|||
|
fNumerics.clear();
|
|||
|
fRestrictionLevel = USPOOF_UNDEFINED_RESTRICTIVE;
|
|||
|
}
|
|||
|
|
|||
|
int32_t CheckResult::toCombinedBitmask(int32_t enabledChecks) {
|
|||
|
if ((enabledChecks & USPOOF_AUX_INFO) != 0 && fRestrictionLevel != USPOOF_UNDEFINED_RESTRICTIVE) {
|
|||
|
return fChecks | fRestrictionLevel;
|
|||
|
} else {
|
|||
|
return fChecks;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
CheckResult::~CheckResult() {
|
|||
|
}
|
|||
|
|
|||
|
//----------------------------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// class SpoofData Implementation
|
|||
|
//
|
|||
|
//----------------------------------------------------------------------------------------------
|
|||
|
|
|||
|
|
|||
|
UBool SpoofData::validateDataVersion(UErrorCode &status) const {
|
|||
|
if (U_FAILURE(status) ||
|
|||
|
fRawData == NULL ||
|
|||
|
fRawData->fMagic != USPOOF_MAGIC ||
|
|||
|
fRawData->fFormatVersion[0] != USPOOF_CONFUSABLE_DATA_FORMAT_VERSION ||
|
|||
|
fRawData->fFormatVersion[1] != 0 ||
|
|||
|
fRawData->fFormatVersion[2] != 0 ||
|
|||
|
fRawData->fFormatVersion[3] != 0) {
|
|||
|
status = U_INVALID_FORMAT_ERROR;
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
static UBool U_CALLCONV
|
|||
|
spoofDataIsAcceptable(void *context,
|
|||
|
const char * /* type */, const char * /*name*/,
|
|||
|
const UDataInfo *pInfo) {
|
|||
|
if(
|
|||
|
pInfo->size >= 20 &&
|
|||
|
pInfo->isBigEndian == U_IS_BIG_ENDIAN &&
|
|||
|
pInfo->charsetFamily == U_CHARSET_FAMILY &&
|
|||
|
pInfo->dataFormat[0] == 0x43 && // dataFormat="Cfu "
|
|||
|
pInfo->dataFormat[1] == 0x66 &&
|
|||
|
pInfo->dataFormat[2] == 0x75 &&
|
|||
|
pInfo->dataFormat[3] == 0x20 &&
|
|||
|
pInfo->formatVersion[0] == USPOOF_CONFUSABLE_DATA_FORMAT_VERSION
|
|||
|
) {
|
|||
|
UVersionInfo *version = static_cast<UVersionInfo *>(context);
|
|||
|
if(version != NULL) {
|
|||
|
uprv_memcpy(version, pInfo->dataVersion, 4);
|
|||
|
}
|
|||
|
return TRUE;
|
|||
|
} else {
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Methods for the loading of the default confusables data file. The confusable
|
|||
|
// data is loaded only when it is needed.
|
|||
|
//
|
|||
|
// SpoofData::getDefault() - Return the default confusables data, and call the
|
|||
|
// initOnce() if it is not available. Adds a reference
|
|||
|
// to the SpoofData that the caller is responsible for
|
|||
|
// decrementing when they are done with the data.
|
|||
|
//
|
|||
|
// uspoof_loadDefaultData - Called once, from initOnce(). The resulting SpoofData
|
|||
|
// is shared by all spoof checkers using the default data.
|
|||
|
//
|
|||
|
// uspoof_cleanupDefaultData - Called during cleanup.
|
|||
|
//
|
|||
|
|
|||
|
static UInitOnce gSpoofInitDefaultOnce = U_INITONCE_INITIALIZER;
|
|||
|
static SpoofData* gDefaultSpoofData;
|
|||
|
|
|||
|
static UBool U_CALLCONV
|
|||
|
uspoof_cleanupDefaultData(void) {
|
|||
|
if (gDefaultSpoofData) {
|
|||
|
// Will delete, assuming all user-level spoof checkers were closed.
|
|||
|
gDefaultSpoofData->removeReference();
|
|||
|
gDefaultSpoofData = nullptr;
|
|||
|
gSpoofInitDefaultOnce.reset();
|
|||
|
}
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
static void U_CALLCONV uspoof_loadDefaultData(UErrorCode& status) {
|
|||
|
UDataMemory *udm = udata_openChoice(nullptr, "cfu", "confusables",
|
|||
|
spoofDataIsAcceptable,
|
|||
|
nullptr, // context, would receive dataVersion if supplied.
|
|||
|
&status);
|
|||
|
if (U_FAILURE(status)) { return; }
|
|||
|
gDefaultSpoofData = new SpoofData(udm, status);
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
delete gDefaultSpoofData;
|
|||
|
gDefaultSpoofData = nullptr;
|
|||
|
return;
|
|||
|
}
|
|||
|
if (gDefaultSpoofData == nullptr) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
ucln_i18n_registerCleanup(UCLN_I18N_SPOOFDATA, uspoof_cleanupDefaultData);
|
|||
|
}
|
|||
|
|
|||
|
SpoofData* SpoofData::getDefault(UErrorCode& status) {
|
|||
|
umtx_initOnce(gSpoofInitDefaultOnce, &uspoof_loadDefaultData, status);
|
|||
|
if (U_FAILURE(status)) { return NULL; }
|
|||
|
gDefaultSpoofData->addReference();
|
|||
|
return gDefaultSpoofData;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
SpoofData::SpoofData(UDataMemory *udm, UErrorCode &status)
|
|||
|
{
|
|||
|
reset();
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
fUDM = udm;
|
|||
|
// fRawData is non-const because it may be constructed by the data builder.
|
|||
|
fRawData = reinterpret_cast<SpoofDataHeader *>(
|
|||
|
const_cast<void *>(udata_getMemory(udm)));
|
|||
|
validateDataVersion(status);
|
|||
|
initPtrs(status);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
SpoofData::SpoofData(const void *data, int32_t length, UErrorCode &status)
|
|||
|
{
|
|||
|
reset();
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if ((size_t)length < sizeof(SpoofDataHeader)) {
|
|||
|
status = U_INVALID_FORMAT_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
if (data == NULL) {
|
|||
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
void *ncData = const_cast<void *>(data);
|
|||
|
fRawData = static_cast<SpoofDataHeader *>(ncData);
|
|||
|
if (length < fRawData->fLength) {
|
|||
|
status = U_INVALID_FORMAT_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
validateDataVersion(status);
|
|||
|
initPtrs(status);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// Spoof Data constructor for use from data builder.
|
|||
|
// Initializes a new, empty data area that will be populated later.
|
|||
|
SpoofData::SpoofData(UErrorCode &status) {
|
|||
|
reset();
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
fDataOwned = true;
|
|||
|
|
|||
|
// The spoof header should already be sized to be a multiple of 16 bytes.
|
|||
|
// Just in case it's not, round it up.
|
|||
|
uint32_t initialSize = (sizeof(SpoofDataHeader) + 15) & ~15;
|
|||
|
U_ASSERT(initialSize == sizeof(SpoofDataHeader));
|
|||
|
|
|||
|
fRawData = static_cast<SpoofDataHeader *>(uprv_malloc(initialSize));
|
|||
|
fMemLimit = initialSize;
|
|||
|
if (fRawData == NULL) {
|
|||
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|||
|
return;
|
|||
|
}
|
|||
|
uprv_memset(fRawData, 0, initialSize);
|
|||
|
|
|||
|
fRawData->fMagic = USPOOF_MAGIC;
|
|||
|
fRawData->fFormatVersion[0] = USPOOF_CONFUSABLE_DATA_FORMAT_VERSION;
|
|||
|
fRawData->fFormatVersion[1] = 0;
|
|||
|
fRawData->fFormatVersion[2] = 0;
|
|||
|
fRawData->fFormatVersion[3] = 0;
|
|||
|
initPtrs(status);
|
|||
|
}
|
|||
|
|
|||
|
// reset() - initialize all fields.
|
|||
|
// Should be updated if any new fields are added.
|
|||
|
// Called by constructors to put things in a known initial state.
|
|||
|
void SpoofData::reset() {
|
|||
|
fRawData = NULL;
|
|||
|
fDataOwned = FALSE;
|
|||
|
fUDM = NULL;
|
|||
|
fMemLimit = 0;
|
|||
|
fRefCount = 1;
|
|||
|
fCFUKeys = NULL;
|
|||
|
fCFUValues = NULL;
|
|||
|
fCFUStrings = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// SpoofData::initPtrs()
|
|||
|
// Initialize the pointers to the various sections of the raw data.
|
|||
|
//
|
|||
|
// This function is used both during the Trie building process (multiple
|
|||
|
// times, as the individual data sections are added), and
|
|||
|
// during the opening of a Spoof Checker from prebuilt data.
|
|||
|
//
|
|||
|
// The pointers for non-existent data sections (identified by an offset of 0)
|
|||
|
// are set to NULL.
|
|||
|
//
|
|||
|
// Note: During building the data, adding each new data section
|
|||
|
// reallocs the raw data area, which likely relocates it, which
|
|||
|
// in turn requires reinitializing all of the pointers into it, hence
|
|||
|
// multiple calls to this function during building.
|
|||
|
//
|
|||
|
void SpoofData::initPtrs(UErrorCode &status) {
|
|||
|
fCFUKeys = NULL;
|
|||
|
fCFUValues = NULL;
|
|||
|
fCFUStrings = NULL;
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (fRawData->fCFUKeys != 0) {
|
|||
|
fCFUKeys = (int32_t *)((char *)fRawData + fRawData->fCFUKeys);
|
|||
|
}
|
|||
|
if (fRawData->fCFUStringIndex != 0) {
|
|||
|
fCFUValues = (uint16_t *)((char *)fRawData + fRawData->fCFUStringIndex);
|
|||
|
}
|
|||
|
if (fRawData->fCFUStringTable != 0) {
|
|||
|
fCFUStrings = (UChar *)((char *)fRawData + fRawData->fCFUStringTable);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
SpoofData::~SpoofData() {
|
|||
|
if (fDataOwned) {
|
|||
|
uprv_free(fRawData);
|
|||
|
}
|
|||
|
fRawData = NULL;
|
|||
|
if (fUDM != NULL) {
|
|||
|
udata_close(fUDM);
|
|||
|
}
|
|||
|
fUDM = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void SpoofData::removeReference() {
|
|||
|
if (umtx_atomic_dec(&fRefCount) == 0) {
|
|||
|
delete this;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
SpoofData *SpoofData::addReference() {
|
|||
|
umtx_atomic_inc(&fRefCount);
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void *SpoofData::reserveSpace(int32_t numBytes, UErrorCode &status) {
|
|||
|
if (U_FAILURE(status)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
if (!fDataOwned) {
|
|||
|
UPRV_UNREACHABLE_EXIT;
|
|||
|
}
|
|||
|
|
|||
|
numBytes = (numBytes + 15) & ~15; // Round up to a multiple of 16
|
|||
|
uint32_t returnOffset = fMemLimit;
|
|||
|
fMemLimit += numBytes;
|
|||
|
fRawData = static_cast<SpoofDataHeader *>(uprv_realloc(fRawData, fMemLimit));
|
|||
|
fRawData->fLength = fMemLimit;
|
|||
|
uprv_memset((char *)fRawData + returnOffset, 0, numBytes);
|
|||
|
initPtrs(status);
|
|||
|
return (char *)fRawData + returnOffset;
|
|||
|
}
|
|||
|
|
|||
|
int32_t SpoofData::serialize(void *buf, int32_t capacity, UErrorCode &status) const {
|
|||
|
int32_t dataSize = fRawData->fLength;
|
|||
|
if (capacity < dataSize) {
|
|||
|
status = U_BUFFER_OVERFLOW_ERROR;
|
|||
|
return dataSize;
|
|||
|
}
|
|||
|
uprv_memcpy(buf, fRawData, dataSize);
|
|||
|
return dataSize;
|
|||
|
}
|
|||
|
|
|||
|
int32_t SpoofData::size() const {
|
|||
|
return fRawData->fLength;
|
|||
|
}
|
|||
|
|
|||
|
//-------------------------------
|
|||
|
//
|
|||
|
// Front-end APIs for SpoofData
|
|||
|
//
|
|||
|
//-------------------------------
|
|||
|
|
|||
|
int32_t SpoofData::confusableLookup(UChar32 inChar, UnicodeString &dest) const {
|
|||
|
// Perform a binary search.
|
|||
|
// [lo, hi), i.e lo is inclusive, hi is exclusive.
|
|||
|
// The result after the loop will be in lo.
|
|||
|
int32_t lo = 0;
|
|||
|
int32_t hi = length();
|
|||
|
do {
|
|||
|
int32_t mid = (lo + hi) / 2;
|
|||
|
if (codePointAt(mid) > inChar) {
|
|||
|
hi = mid;
|
|||
|
} else if (codePointAt(mid) < inChar) {
|
|||
|
lo = mid;
|
|||
|
} else {
|
|||
|
// Found result. Break early.
|
|||
|
lo = mid;
|
|||
|
break;
|
|||
|
}
|
|||
|
} while (hi - lo > 1);
|
|||
|
|
|||
|
// Did we find an entry? If not, the char maps to itself.
|
|||
|
if (codePointAt(lo) != inChar) {
|
|||
|
dest.append(inChar);
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
// Add the element to the string builder and return.
|
|||
|
return appendValueTo(lo, dest);
|
|||
|
}
|
|||
|
|
|||
|
int32_t SpoofData::length() const {
|
|||
|
return fRawData->fCFUKeysSize;
|
|||
|
}
|
|||
|
|
|||
|
UChar32 SpoofData::codePointAt(int32_t index) const {
|
|||
|
return ConfusableDataUtils::keyToCodePoint(fCFUKeys[index]);
|
|||
|
}
|
|||
|
|
|||
|
int32_t SpoofData::appendValueTo(int32_t index, UnicodeString& dest) const {
|
|||
|
int32_t stringLength = ConfusableDataUtils::keyToLength(fCFUKeys[index]);
|
|||
|
|
|||
|
// Value is either a char (for strings of length 1) or
|
|||
|
// an index into the string table (for longer strings)
|
|||
|
uint16_t value = fCFUValues[index];
|
|||
|
if (stringLength == 1) {
|
|||
|
dest.append((UChar)value);
|
|||
|
} else {
|
|||
|
dest.append(fCFUStrings + value, stringLength);
|
|||
|
}
|
|||
|
|
|||
|
return stringLength;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
U_NAMESPACE_END
|
|||
|
|
|||
|
U_NAMESPACE_USE
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// uspoof_swap - byte swap and char encoding swap of spoof data
|
|||
|
//
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
U_CAPI int32_t U_EXPORT2
|
|||
|
uspoof_swap(const UDataSwapper *ds, const void *inData, int32_t length, void *outData,
|
|||
|
UErrorCode *status) {
|
|||
|
|
|||
|
if (status == NULL || U_FAILURE(*status)) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
if(ds==NULL || inData==NULL || length<-1 || (length>0 && outData==NULL)) {
|
|||
|
*status=U_ILLEGAL_ARGUMENT_ERROR;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check that the data header is for spoof data.
|
|||
|
// (Header contents are defined in gencfu.cpp)
|
|||
|
//
|
|||
|
const UDataInfo *pInfo = (const UDataInfo *)((const char *)inData+4);
|
|||
|
if(!( pInfo->dataFormat[0]==0x43 && /* dataFormat="Cfu " */
|
|||
|
pInfo->dataFormat[1]==0x66 &&
|
|||
|
pInfo->dataFormat[2]==0x75 &&
|
|||
|
pInfo->dataFormat[3]==0x20 &&
|
|||
|
pInfo->formatVersion[0]==USPOOF_CONFUSABLE_DATA_FORMAT_VERSION &&
|
|||
|
pInfo->formatVersion[1]==0 &&
|
|||
|
pInfo->formatVersion[2]==0 &&
|
|||
|
pInfo->formatVersion[3]==0 )) {
|
|||
|
udata_printError(ds, "uspoof_swap(): data format %02x.%02x.%02x.%02x "
|
|||
|
"(format version %02x %02x %02x %02x) is not recognized\n",
|
|||
|
pInfo->dataFormat[0], pInfo->dataFormat[1],
|
|||
|
pInfo->dataFormat[2], pInfo->dataFormat[3],
|
|||
|
pInfo->formatVersion[0], pInfo->formatVersion[1],
|
|||
|
pInfo->formatVersion[2], pInfo->formatVersion[3]);
|
|||
|
*status=U_UNSUPPORTED_ERROR;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Swap the data header. (This is the generic ICU Data Header, not the uspoof Specific
|
|||
|
// header). This swap also conveniently gets us
|
|||
|
// the size of the ICU d.h., which lets us locate the start
|
|||
|
// of the uspoof specific data.
|
|||
|
//
|
|||
|
int32_t headerSize=udata_swapDataHeader(ds, inData, length, outData, status);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the Spoof Data Header, and check that it appears to be OK.
|
|||
|
//
|
|||
|
//
|
|||
|
const uint8_t *inBytes =(const uint8_t *)inData+headerSize;
|
|||
|
SpoofDataHeader *spoofDH = (SpoofDataHeader *)inBytes;
|
|||
|
if (ds->readUInt32(spoofDH->fMagic) != USPOOF_MAGIC ||
|
|||
|
ds->readUInt32(spoofDH->fLength) < sizeof(SpoofDataHeader))
|
|||
|
{
|
|||
|
udata_printError(ds, "uspoof_swap(): Spoof Data header is invalid.\n");
|
|||
|
*status=U_UNSUPPORTED_ERROR;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Prefight operation? Just return the size
|
|||
|
//
|
|||
|
int32_t spoofDataLength = ds->readUInt32(spoofDH->fLength);
|
|||
|
int32_t totalSize = headerSize + spoofDataLength;
|
|||
|
if (length < 0) {
|
|||
|
return totalSize;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check that length passed in is consistent with length from Spoof data header.
|
|||
|
//
|
|||
|
if (length < totalSize) {
|
|||
|
udata_printError(ds, "uspoof_swap(): too few bytes (%d after ICU Data header) for spoof data.\n",
|
|||
|
spoofDataLength);
|
|||
|
*status=U_INDEX_OUTOFBOUNDS_ERROR;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Swap the Data. Do the data itself first, then the Spoof Data Header, because
|
|||
|
// we need to reference the header to locate the data, and an
|
|||
|
// inplace swap of the header leaves it unusable.
|
|||
|
//
|
|||
|
uint8_t *outBytes = (uint8_t *)outData + headerSize;
|
|||
|
SpoofDataHeader *outputDH = (SpoofDataHeader *)outBytes;
|
|||
|
|
|||
|
int32_t sectionStart;
|
|||
|
int32_t sectionLength;
|
|||
|
|
|||
|
//
|
|||
|
// If not swapping in place, zero out the output buffer before starting.
|
|||
|
// Gaps may exist between the individual sections, and these must be zeroed in
|
|||
|
// the output buffer. The simplest way to do that is to just zero the whole thing.
|
|||
|
//
|
|||
|
if (inBytes != outBytes) {
|
|||
|
uprv_memset(outBytes, 0, spoofDataLength);
|
|||
|
}
|
|||
|
|
|||
|
// Confusables Keys Section (fCFUKeys)
|
|||
|
sectionStart = ds->readUInt32(spoofDH->fCFUKeys);
|
|||
|
sectionLength = ds->readUInt32(spoofDH->fCFUKeysSize) * 4;
|
|||
|
ds->swapArray32(ds, inBytes+sectionStart, sectionLength, outBytes+sectionStart, status);
|
|||
|
|
|||
|
// String Index Section
|
|||
|
sectionStart = ds->readUInt32(spoofDH->fCFUStringIndex);
|
|||
|
sectionLength = ds->readUInt32(spoofDH->fCFUStringIndexSize) * 2;
|
|||
|
ds->swapArray16(ds, inBytes+sectionStart, sectionLength, outBytes+sectionStart, status);
|
|||
|
|
|||
|
// String Table Section
|
|||
|
sectionStart = ds->readUInt32(spoofDH->fCFUStringTable);
|
|||
|
sectionLength = ds->readUInt32(spoofDH->fCFUStringTableLen) * 2;
|
|||
|
ds->swapArray16(ds, inBytes+sectionStart, sectionLength, outBytes+sectionStart, status);
|
|||
|
|
|||
|
// And, last, swap the header itself.
|
|||
|
// int32_t fMagic // swap this
|
|||
|
// uint8_t fFormatVersion[4] // Do not swap this, just copy
|
|||
|
// int32_t fLength and all the rest // Swap the rest, all is 32 bit stuff.
|
|||
|
//
|
|||
|
uint32_t magic = ds->readUInt32(spoofDH->fMagic);
|
|||
|
ds->writeUInt32((uint32_t *)&outputDH->fMagic, magic);
|
|||
|
|
|||
|
if (outputDH->fFormatVersion != spoofDH->fFormatVersion) {
|
|||
|
uprv_memcpy(outputDH->fFormatVersion, spoofDH->fFormatVersion, sizeof(spoofDH->fFormatVersion));
|
|||
|
}
|
|||
|
// swap starting at fLength
|
|||
|
ds->swapArray32(ds, &spoofDH->fLength, sizeof(SpoofDataHeader)-8 /* minus magic and fFormatVersion[4] */, &outputDH->fLength, status);
|
|||
|
|
|||
|
return totalSize;
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
|