IsPlaceholderToken 与 IsUsableSecret 都只识别 `<YOUR_OWN_` 前缀,而 config.default.toml 实际使用 `<YOUR_XXX>` 和 `FAKESECRET_` 两种写法,18 个占位符无一被识别。叠加 ai_assistant.enabled、 voice_input.voice_input、tencent_tmt.enabled 三项默认为 true,全新安装在用户未做任何配置的情况下 就会把输入串发往 api.deepseek.com、把候选文本发往 tmt.tencentcloudapi.com——请求虽然鉴权失败, 内容已经离开本机。 两处判定改为按占位符的形状判断,并新增 test_default_config_credentials:它直接读安装器实际打包的 config.default.toml,逐条校验没有任何凭据被当成可用值,因此换一种占位符写法也会被拦下。
152 lines
4.0 KiB
C++
152 lines
4.0 KiB
C++
#include "translation_gloss.h"
|
|
|
|
#include <utf8.h>
|
|
#include <cstdint>
|
|
#include <cctype>
|
|
|
|
namespace CloudTranslation
|
|
{
|
|
namespace
|
|
{
|
|
constexpr size_t kMaxPersistChars = 32;
|
|
|
|
bool IsAsciiSpace(unsigned char ch)
|
|
{
|
|
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
|
|
}
|
|
|
|
std::string CollapseWhitespace(const std::string &text)
|
|
{
|
|
std::string out;
|
|
out.reserve(text.size());
|
|
bool pending_space = false;
|
|
for (const unsigned char ch : text)
|
|
{
|
|
if (IsAsciiSpace(ch))
|
|
{
|
|
pending_space = !out.empty();
|
|
continue;
|
|
}
|
|
if (pending_space)
|
|
{
|
|
out.push_back(' ');
|
|
pending_space = false;
|
|
}
|
|
out.push_back(static_cast<char>(ch));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string AsciiLower(std::string value)
|
|
{
|
|
for (char &ch : value)
|
|
{
|
|
if (ch >= 'A' && ch <= 'Z')
|
|
ch = static_cast<char>(ch - 'A' + 'a');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
bool IsHanCodepoint(uint32_t cp)
|
|
{
|
|
return (cp >= 0x3400 && cp <= 0x4DBF) || (cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
(cp >= 0x20000 && cp <= 0x2CEAF) || cp == 0x3007;
|
|
}
|
|
|
|
bool IsEmojiOrPictograph(uint32_t cp)
|
|
{
|
|
if (cp == 0x200D || cp == 0xFE0E || cp == 0xFE0F || cp == 0x20E3)
|
|
return true;
|
|
if (cp >= 0x2600 && cp <= 0x27BF)
|
|
return true;
|
|
if (cp >= 0x1F000 && cp <= 0x1FAFF)
|
|
return true;
|
|
if (cp >= 0x1F1E6 && cp <= 0x1F1FF)
|
|
return true;
|
|
return false;
|
|
}
|
|
} // namespace
|
|
|
|
size_t Utf8Length(const std::string &text)
|
|
{
|
|
if (text.empty() || !utf8::is_valid(text.begin(), text.end()))
|
|
return text.size();
|
|
return static_cast<size_t>(utf8::distance(text.begin(), text.end()));
|
|
}
|
|
|
|
std::string FormatGloss(const std::string &text)
|
|
{
|
|
std::string collapsed = CollapseWhitespace(text);
|
|
if (collapsed.empty() || !utf8::is_valid(collapsed.begin(), collapsed.end()))
|
|
return {};
|
|
return collapsed;
|
|
}
|
|
|
|
bool ShouldPersistGloss(const std::string &key, const std::string &formatted)
|
|
{
|
|
if (formatted.empty() || key.empty())
|
|
return false;
|
|
if (Utf8Length(formatted) > kMaxPersistChars)
|
|
return false;
|
|
return AsciiLower(formatted) != AsciiLower(key);
|
|
}
|
|
|
|
bool IsUsableSecret(const std::string &value)
|
|
{
|
|
const std::string trimmed = TrimSecret(value);
|
|
if (trimmed.empty())
|
|
return false;
|
|
// Same rule as VoiceInput::IsPlaceholderToken, and same reason: config.default.toml ships
|
|
// "<YOUR_TENCENT_SECRET_ID>", which the old "<YOUR_OWN_" prefix test accepted as a real secret. With
|
|
// tencent_tmt.enabled defaulting to true that made a fresh install send candidate text to
|
|
// tmt.tencentcloudapi.com signed with the placeholder.
|
|
if (trimmed.front() == '<' && trimmed.back() == '>')
|
|
return false;
|
|
return trimmed.find("FAKESECRET_") != 0;
|
|
}
|
|
|
|
std::string TrimSecret(const std::string &value)
|
|
{
|
|
size_t begin = 0;
|
|
while (begin < value.size() && IsAsciiSpace(static_cast<unsigned char>(value[begin])))
|
|
++begin;
|
|
size_t end = value.size();
|
|
while (end > begin && IsAsciiSpace(static_cast<unsigned char>(value[end - 1])))
|
|
--end;
|
|
return value.substr(begin, end - begin);
|
|
}
|
|
|
|
bool IsCloudTranslatableChinese(const std::string &text)
|
|
{
|
|
if (text.empty() || !utf8::is_valid(text.begin(), text.end()))
|
|
return false;
|
|
bool has_han = false;
|
|
for (auto it = text.begin(); it != text.end();)
|
|
{
|
|
const uint32_t cp = utf8::next(it, text.end());
|
|
if (IsEmojiOrPictograph(cp))
|
|
return false;
|
|
has_han = has_han || IsHanCodepoint(cp);
|
|
}
|
|
return has_han;
|
|
}
|
|
|
|
bool IsCloudTranslatableEnglish(const std::string &text)
|
|
{
|
|
if (text.empty())
|
|
return false;
|
|
bool has_ascii_letter = false;
|
|
for (const unsigned char ch : text)
|
|
{
|
|
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
|
|
{
|
|
has_ascii_letter = true;
|
|
continue;
|
|
}
|
|
if (ch != ' ' && ch != '-' && ch != '\'')
|
|
return false;
|
|
}
|
|
return has_ascii_letter;
|
|
}
|
|
} // namespace CloudTranslation
|