From c930407430c5da459c5e68d5a80e53a8a5bf5f82 Mon Sep 17 00:00:00 2001 From: Blank Date: Fri, 7 Aug 2026 02:42:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(utility):=20=E5=8E=BB=E9=99=A4=20IllegalWor?= =?UTF-8?q?dDetection=20=E7=9A=84=20unsafe=20=E6=8C=87=E9=92=88=E5=B9=B6?= =?UTF-8?q?=E6=8B=86=E5=88=86=20DetectIllegalWords=20=E6=B6=88=E9=99=A4=20?= =?UTF-8?q?Sonar=20S6640=20=E4=B8=8E=20S3776?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linear: GFX-591 --- GameFrameX.Utility/IllegalWordDetection.cs | 220 +++++++++++++-------- 1 file changed, 134 insertions(+), 86 deletions(-) diff --git a/GameFrameX.Utility/IllegalWordDetection.cs b/GameFrameX.Utility/IllegalWordDetection.cs index eb05cafd..cbf4b8ca 100644 --- a/GameFrameX.Utility/IllegalWordDetection.cs +++ b/GameFrameX.Utility/IllegalWordDetection.cs @@ -264,57 +264,49 @@ private static string ReadBadWordFromRecord(byte[] data, ref int offset, List 0; + } + + /// + /// 在主扫描循环的当前位置执行一次敏感词检测:推进游标到下一个候选首字符、记录单字符敏感词、向后扫描多字符敏感词。 + /// 命中且需要首个即返回时返回 true;否则通过 ref 推进 aitorIdx 并返回 false。 + /// + /// + /// Performs one detection pass at the current cursor: advances to the next candidate start character, + /// records single-character bad words, then scans forward for multi-character bad words. + /// Returns true when a match is found and returnWhenFindFirst is set; otherwise advances aitorIdx via ref and returns false. + /// + /// 敏感词查询文本 / The text to check for sensitive words + /// 文本长度 / The text length + /// 当前游标位置(引用),会被推进 / The current cursor (by ref), advanced by this call + /// 是否找到第一个就返回 / Whether to return when the first match is found + /// 查找到的敏感词结果(键为起始位置,值为长度) / The found sensitive words (key is start position, value is length) + /// 是否找到敏感词并需立即返回 / Whether a match was found and the caller should return immediately + private static bool ScanFromPosition(string text, int textLength, ref int aitorIdx, bool returnWhenFindFirst, Dictionary findResult) + { + aitorIdx = AdvanceToNextStartChar(text, textLength, aitorIdx); - //如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表 - if (StartCache[text[aitorIdx]] != 0 && (FastLength[text[aitorIdx]] & 0x01) > 0) + //如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表 + if (StartCache[text[aitorIdx]] != 0 && (FastLength[text[aitorIdx]] & 0x01) > 0) + { + //返回敏感词在text中的位置,以及敏感词的长度,供过滤功能用 + findResult.Add(aitorIdx, 1); + if (returnWhenFindFirst) { - //返回敏感词在text中的位置,以及敏感词的长度,供过滤功能用 - findResult.Add(aitorIdx, 1); - if (returnWhenFindFirst) - { - return true; - } + return true; } + } + + //此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置 + //从当前的位置检测到字符串末尾 + if (TryScanForward(text, textLength, ref aitorIdx, findResult) && returnWhenFindFirst) + { + return true; + } + + ++aitorIdx; + return false; + } - var strIgorIdx = 0; - _dectectedBuffer[strIgorIdx++] = text[aitorIdx]; - var remainLength = textLength - aitorIdx - 1; - var skipCount = 0; - //此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置 - //从当前的位置检测到字符串末尾 - for (var i = 1; i <= remainLength; ++i) + /// + /// 跳过 FastCheck 标记为非敏感词首字符的位置,返回下一个候选首字符的索引。 + /// + /// + /// Skips characters not flagged by FastCheck as a sensitive-word start and returns the next candidate start index. + /// + /// 敏感词查询文本 / The text to check for sensitive words + /// 文本长度 / The text length + /// 当前游标位置 / The current cursor + /// 推进后的候选首字符索引 / The advanced candidate start index + private static int AdvanceToNextStartChar(string text, int textLength, int aitorIdx) + { + //如果text的第一个词不是敏感词汇或者当前遍历到了text第一个词的后面的词,则循环检测到text词汇的倒数第二个词,看看这一段子字符串中有没有敏感词汇 + if ((FastCheck[text[aitorIdx]] & 0x01) == 0) + { + while (aitorIdx < textLength - 1 && (FastCheck[text[++aitorIdx]] & 0x01) == 0) { - var subItoIdx = aitorIdx + i; - // 跳过一些过滤的字符,比如空格特殊符号之类的 - if (SkipBitArray[text[subItoIdx]]) - { - ++skipCount; - continue; - } + } + } - //如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的,则马上跳出遍历 - if (FastCheck[text[subItoIdx]] >> System.Math.Min(i - skipCount, 7) == 0) - { - break; - } + return aitorIdx; + } - _dectectedBuffer[strIgorIdx++] = text[subItoIdx]; - //如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感 - if (FastLength[text[aitorIdx]] >> System.Math.Min(i - 1 - skipCount, 7) > 0 && EndCache[text[subItoIdx]]) - { - //如果此子字符串在敏感词字典中存在,则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词,而上面的算法无法剔除,故先用hash数组来剔除 - //上述算法是用于减少大部分的比较消耗 - if (WordsSet.Contains(new string(_dectectedBuffer, 0, strIgorIdx))) - { - findResult[aitorIdx] = i + 1; - aitorIdx = subItoIdx; + /// + /// 从当前首字符向后扫描多字符敏感词。命中时把 aitorIdx 推进到匹配末字符并返回 true,否则返回 false。 + /// + /// + /// Scans forward from the current start character for multi-character sensitive words. + /// On a match, advances aitorIdx to the matched last character via ref and returns true; otherwise returns false. + /// + /// 敏感词查询文本 / The text to check for sensitive words + /// 文本长度 / The text length + /// 当前游标位置(引用),命中时推进到匹配末字符 / The current cursor (by ref), advanced to the matched last character on hit + /// 查找到的敏感词结果(键为起始位置,值为长度) / The found sensitive words (key is start position, value is length) + /// 是否命中多字符敏感词 / Whether a multi-character sensitive word was matched + private static bool TryScanForward(string text, int textLength, ref int aitorIdx, Dictionary findResult) + { + var strIgorIdx = 0; + _dectectedBuffer[strIgorIdx++] = text[aitorIdx]; + var remainLength = textLength - aitorIdx - 1; + var skipCount = 0; + for (var i = 1; i <= remainLength; ++i) + { + var subItoIdx = aitorIdx + i; + // 跳过一些过滤的字符,比如空格特殊符号之类的 + if (SkipBitArray[text[subItoIdx]]) + { + ++skipCount; + continue; + } - if (returnWhenFindFirst) - { - return true; - } + //如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的,则马上跳出遍历 + if (FastCheck[text[subItoIdx]] >> System.Math.Min(i - skipCount, 7) == 0) + { + break; + } - break; - } - } - else if (i - skipCount > StartCache[text[aitorIdx]] && StartCache[text[aitorIdx]] < 0x80) //如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的) + _dectectedBuffer[strIgorIdx++] = text[subItoIdx]; + //如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感 + if (FastLength[text[aitorIdx]] >> System.Math.Min(i - 1 - skipCount, 7) > 0 && EndCache[text[subItoIdx]]) + { + //如果此子字符串在敏感词字典中存在,则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词,而上面的算法无法剔除,故先用hash数组来剔除 + //上述算法是用于减少大部分的比较消耗 + if (WordsSet.Contains(new string(_dectectedBuffer, 0, strIgorIdx))) { - break; + findResult[aitorIdx] = i + 1; + aitorIdx = subItoIdx; + return true; } } - - ++aitorIdx; + else if (i - skipCount > StartCache[text[aitorIdx]] && StartCache[text[aitorIdx]] < 0x80) //如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的) + { + break; + } } - return findResult.Count > 0; + return false; } }