Commit Graph
128 Commits
Author SHA1 Message Date
linyanmandlinym 6e03f57747 fix(server): 全拼导入按汉字数选择切分并识别文件编码 (#270)
无分隔拼音按汉字数另选完整切分,避免西安/xian 误报音节数不一致。设置页按 BOM 识别 UTF-8/UTF-16,无效 UTF-8 再回退 GBK。带撇号的水杉导出三列 TSV 本身已合法,回归测试钉住该格式。

Co-authored-by: linym <lin.yanming@foxmail.com>
2026-09-08 22:52:45 +09:00
Aegon8719 6ff1cff42d issue-52 加入简繁切换快捷键;增加候选词翻页快捷键组合、增加以词定字快捷键组合;
issue-55 候选词与辅助码间保留一个半角空格。
2026-09-08 14:07:48 +08:00
fanlusky de4d3acf45 fix(config): handle non-ASCII (e.g. Chinese) user profile paths
std::filesystem::path(std::string) and path::string() convert through the
system ANSI code page, so a non-ASCII (e.g. Chinese) user profile path on a
non-UTF-8 (GBK 936) system was corrupted and every config read/write failed
with "设置保存失败"; on a code page that cannot represent the characters
path::string() threw, crashing the Server at startup in a watchdog restart loop.

Never route config paths through the ANSI narrow encoding:
- build g_config_path and the legacy voice path from the wide accessors;
- read config by opening the wide path + toml::parse(text) instead of
  toml::parse_file(path.string()) (LoadImeConfig, MigrateLegacyVoiceInputConfig);
- build and open the TSF shared config path as a wide std::filesystem::path.

Add a regression test that round-trips config read/write under a Chinese
profile path.
2026-09-08 02:20:37 +08:00
Evan Hu 84ef90cc8d fix(server): test valid escaped CSS URLs 2026-09-07 13:37:22 +09:00
Evan Hu 8972593b3e style(server): format skin CSS policy 2026-09-07 13:32:09 +09:00
Evan Hu 818069f9c3 fix(server): make release probes non-interactive 2026-09-07 13:28:25 +09:00
Evan Hu 205cafa4f5 fix(skin): @import 剥离只匹配真正的 at-rule;补转义绕过的不变量说明与用例
自查时发现的两处。

**@import 匹配过宽会损坏合法皮肤。** 此前在任意位置匹配 `@import`,于是 `.a{content:"@import //x";color:red}` 会被从 `@` 切到该声明的分号,留下一个未闭合的字符串和一份坏掉的样式表——为了保护皮肤反而弄坏了一个本来没问题的皮肤。改成只在 at-rule 能出现的位置匹配:样式表开头,或 `;` / `}` 之后;并要求关键字后面不是字母数字,避免 `@importantthing` 被当成 `@import`。

**CSS 转义不是漏洞,但原因不显然,值得钉死。** 样式表可以写 `url("https:\/\/host/x.png")`,浏览器解转义后得到一个可用的远程 URL,而字面文本里没有 `://`,分类器给出的是 Embed 而不是 Drop。这是安全的,但安全性来自 Embed 能产出什么:要么把文件内联成 data: 负载,要么回退到 candidate-skins 虚拟主机下的 URL,两者都无法命名皮肤作者选定的 origin。转义过的 `..` 同理——它以字面转义文本的形式进入文件系统查找,不会穿越,也永远不会被写回文档。

也就是说,真正保护候选窗的不变量是「Embed 永不产出攻击者命名的 origin」,而不是「Drop 拦住了远程 URL 的每一种写法」。把这条写进头文件,并补一个用例钉住「转义写法永远不落到 Keep」——将来若有人改动 Embed 的回退逻辑让它原样输出 URL,这个洞会重新打开,用例会先失败。

本机以 -Wall -Wextra -Wpedantic -Werror + ASan/UBSan 编译运行 8/8 通过,portable 目标经 CMake + ctest 验证,clang-format 干净。
2026-09-06 19:33:39 +09:00
Evan Hu 680fdfa648 fix(skin): 丢弃第三方皮肤 CSS 里的远程引用,不再原样注入候选窗
皮肤是用户往 %LOCALAPPDATA%\metasequoiaime\skins 里丢的文件夹,是彻头彻尾的第三方内容,而它的 CSS 会被内联进候选窗文档。此前 IsRewritableSkinCssUrl() 对含 :// 的 URL 直接返回 false,而 false 分支把原始 url("…") 原封不动写回样式表——也就是说 `background-image: url(https://attacker/beacon.png)` 会在每一次候选窗渲染时向外发一个请求,泄露「此人此刻正在打字」以及 IP。这直接推翻了 PRIVACY.md 的 "Local conversion never contacts the network",而那句话是这个项目最强的信任资产。

问题出在这个判断只有两态:不可重写就等于放行。实际上有三类:

- Embed——皮肤包内的相对路径,读文件内联;
- Keep——本身就是惰性的 data: 负载和文档内 #fragment 引用,照原样保留;
- Drop——任何会让候选窗向皮肤包之外取数据的写法。丢掉 URL 后声明不完整,CSS 会整条丢弃,这正是想要的结果。

Drop 覆盖 http/https/ftp 等带 scheme 的、协议相对的 //host/x(不写 scheme 但照样出网)、路径穿越,以及会逃出皮肤包的绝对路径。

url() 不是唯一入口:`@import "https://…"` 根本不含 url() token,扫描器永远看不到它。补 StripRemoteImports() 做前置处理,只删远程 @import,本地的保留。

skin.preview 来自皮肤自己的 skin.toml,此前直接进 EmbedSkinCssUrl。现在同样过一遍分类,不因为它是通过 manifest 字段到达就被信任。

判断逻辑抽到 server/src/webview2/skin_css_policy.{h,cpp},不依赖任何 Windows API,配 6 个用例覆盖三类判定与两种 @import 写法。本机以 -Wall -Wextra 编译运行全部通过。
2026-09-06 18:05:09 +09:00
Evan 688972b28c Merge remote-tracking branch 'origin/main' into pr184-merge
# Conflicts:
#	server/src/session/input_session.h
2026-09-06 15:31:05 +09:00
Evan Hu ac805078e4 refactor(server): carry engine online queries through asynchronous candidate tasks 2026-09-06 13:24:03 +09:00
Evan Hu 9785ecd4bf fix(server): revalidate queued online candidate generations on apply 2026-09-06 13:17:50 +09:00
Evan Hu a533d5aa2b Merge remote-tracking branch 'origin/main' into chore/oss-hardening
# Conflicts:
#	AGENTS.md
2026-09-06 13:06:54 +09:00
Evan 42238a2af6 style: 用 .clang-format 重排 server / windows / ui / log
一次性重排,无任何行为改动。四个目录各自的 .clang-format 放在仓库里已久,但全组织只有
MSIME-Linux 在 CI 里执行 clang-format 检查,这边 149 个文件里有 149 个不符合自己声明的风格。

已逐文件核对语义等价:去掉空白、注释与相邻字符串字面量拆分之后,全部 149 个文件与重排前逐字节
相同。差异只有三类——缩进与换行、clang-format 补的 } // namespace 结尾注释、以及长字符串被拆成
相邻字面量(C++ 保证自动拼接)。

这个提交会写进 .git-blame-ignore-revs。
2026-09-06 12:13:47 +09:00
Evan 6341b4fffc fix(server): 出厂配置的占位凭据不再被当作真凭据
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,逐条校验没有任何凭据被当成可用值,因此换一种占位符写法也会被拦下。
2026-09-06 11:56:11 +09:00
Evan 2b8bf937d9 fix(server): adopt isolated engine sessions and helpcode annotations (#178) 2026-09-06 11:44:30 +09:00
Evan 2562026a2a fix: 修复审计发现的候选页竞态、管道权限、TSF 空指针与皮肤 CSS 逃逸等八项缺陷 (#169)
* fix(server): publish candidate pages as immutable snapshots

Global::candidate_ui is a plain global with no lock. The IPC worker rebuilds and destroys its vectors while the UI thread paints from them, and the handoff is an asynchronous PostMessage, so the worker returns to its loop and pops the next task without waiting. A second keystroke, or an async english.db candidate arrival, could free the page_views buffer and its strings while FillItemsFromUi held a reference into it. ShowItemContextMenu reads the same vectors from a mouse callback with no message handoff at all.

The worker now copies each finished page into an immutable snapshot and publishes it at the single point where the rebuild completes; every UI-thread reader loads that snapshot once and reads only its own copy. PostMessage is deliberately left alone: the worker also serves UI-originated tasks, so a synchronous handoff would invite a cross-thread deadlock.

* fix(server): bound the aux and diagnostic pipe reads

Both listeners serve a single pipe instance inline and polled a NOWAIT handle for as long as the process ran, breaking only on a read that succeeded or failed with something other than ERROR_NO_DATA. A client that connected and never wrote held the only instance forever, so the loop never returned to ConnectNamedPipe and no later client was accepted, permanently losing the langbar right-click channel and the terminal-deactivation teardown fallback.

Give both loops the same deadline the hello reads already use and go back to accepting when it expires.

* fix(server): snapshot the voice config and replace config.toml atomically

GetConfiguredVoiceInput handed out a reference to a global that LoadImeConfig rewrites from the IPC worker thread, clearing and refilling two std::map members and around twenty strings. The voice control thread and the low-level keyboard hook, which copies the struct on every keystroke, walked those nodes as they were freed. LoadImeConfig now parses into a local and publishes it in one step under a process-local shared_mutex, and the getter returns a snapshot by value. The file lock is deliberately not reused for readers: it is a named cross-process mutex with a five second wait, and taking it in a keyboard hook would stall typing system-wide.

WriteConfiguredValue also reopened the live config.toml with ios::trunc, so a crash mid-write destroyed the user settings and could leave the file unparseable for good. It now goes through WriteFileTextAtomically, which this file already uses elsewhere. That makes the directory entry swap atomic; it does not flush, so it is not a durability guarantee.

* fix(ipc): restrict the server pipes to the owning account

The pipe DACL granted Everyone FILE_ALL_ACCESS. That bit set includes FILE_CREATE_PIPE_INSTANCE, so any local account could add an instance to a name this server already owned and answer part of the connections in its place. The names are machine-global with no per-user component, and the TSF client opened them with no SQOS, so a squatter could also impersonate the connecting thread.

Build the descriptor from the process owner SID and admit only LocalSystem, that account, and app containers with connect-only rights so UWP text input still works; fail pipe creation outright when the SID cannot be resolved rather than falling back to something permissive. Pass FILE_FLAG_FIRST_PIPE_INSTANCE on the first instance of each name so a squatted name fails loudly, and request SECURITY_IDENTIFICATION on the client side.

Authenticating the peer process after connect is a larger change to the handshake and is not attempted here.

* fix(tsf): guard the null focus paths and unadvise the compartment sink

_CandidateChangeNotification treated ITfThreadMgr::GetFocus and ITfDocumentMgr::GetTop as if they could only fail through a FAILED HRESULT. Both return S_OK with a null out-pointer when nothing has focus or the document stack is empty, and the results were dereferenced unconditionally, so an external UILess host calling Finalize while focus moved away crashed the host application. The sibling _RequestCancelComposition in the same class already checks both.

CLangBarItemButton::CleanUp only unregistered the compartment inside the SUCCEEDED branch of a CoCreateInstance, then deleted the sink regardless. This TIP registers COM-less activation support, where that call does fail, and the sink was freed while TSF still held it advised. Unadvising never needed the thread manager, so it now runs unconditionally before the deletes.

* fix(ui): drop the cached visuals when the scene is replaced

Window caches focused, hovered and captured Visual pointers into the scene tree, and SetScene destroyed that tree without clearing them. Toggling the input mode rebuilds the floating toolbar scene, so the next mouse move dispatched a virtual call through a freed object.

Clear all three before the assignment, and release the capture only when this window actually holds it: an adopted HWND may never have called SetCapture, and capturedVisual_ is set even when it did not.

* fix(settings): scope external skin CSS through the CSS parser

injectScopedSkinCss concatenated a skin toolbar.css into an @scope block as text. Block nesting is textual, so a leading close brace in the skin ended the block early and the rest styled the whole settings window. The host validates only the skin file name, never the body, and candidatePreviewCss interpolated unvalidated colour strings into rule bodies the same way.

Create the scope rule empty and re-insert every rule parsed out of the skin as a child of it, so nothing lands outside however the braces are balanced, and accept a colour only in the plain notations we actually render.

* fix(release): gate the dictionary source commit on its default branch

verify_published iterated only data["repositories"], but the dictionary block pins a source_commit exactly like they do. The manifest check merely proves the release agrees with the lock about that commit, which a branch commit satisfies just as happily, so a dictionary built from an unmerged branch shipped with the provenance the gate exists to refuse.

Run the dictionary through the same loop under its own name, resolving its repository from the lock. A retired dictionary repository that no longer answers is reported rather than failing the release: its published artifacts are immutable history, and being unable to re-check them is not the same as finding them unmerged.
2026-09-06 10:31:48 +09:00
Evan Hu cf872efc52 refactor(server): share batch voice encoding and provider codecs 2026-09-06 04:35:40 +09:00
Evan Hu 06bba15d4f fix(server): keep settings I/O off UI thread and bound dictionary rendering 2026-09-06 03:31:40 +09:00
Evan Hu b5d07f11eb fix(server): reduce WebView message and first-paint overhead 2026-09-06 03:05:36 +09:00
Evan Hu 8e54b79547 merge(main): retain direct shared local-mode calls and remove legacy adapters 2026-09-06 00:27:39 +09:00
Evan Hu 6783ff7175 refactor(session): use shared composition and local queries with one candidate view model 2026-09-06 00:26:38 +09:00
Evan Hu 60fce0f043 Merge branch main into feat/generic-pinyin-order-correction
唯一冲突是 engine 子模块指针,取 main 的当前值(其中已含本 PR 依赖的
MSIME-Engine#1,以及后续的 #20 门控修复)。
2026-09-05 23:34:30 +09:00
Evan 6ba26c3baf Merge pull request #30 from metasequoiaime/architecture/product-contracts
Use shared IPC definitions and negotiate main client sessions
2026-09-05 23:23:03 +09:00
Evan Hu 8a36ee264a test(quanpin): expect four-syllable alternatives and bound them at five
The engine pin moved 55839c8 -> 127019f in the previous commit, and that range contains 86c5fa7 "fix(quanpin): enumerate alternative segmentations for four-syllable input". QuanpinDictionaryOnlyTriesMultipleSegmentationsForAtMostThreeSyllables asserted the behaviour that fix deliberately changed, so it failed here and, through the product lock, in MSIME-Windows' locked-Server integration job as well.

The engine is the authoritative layer: quanpin/quanpin_dictionary.cpp declares kMaxSyllablesForMultipleSegmentations = 4 and gates enumeration on segments.size() <= that. Four syllables now enumerate alternatives; five still do not. The test says exactly that, with a tbl_6_x fixture row so the five-syllable case would have something to find if the bound ever moved silently.
2026-09-05 23:00:52 +09:00
Evan Hu e9c8fd4077 Merge remote-tracking branch 'origin/main' into pr2 2026-09-05 22:51:54 +09:00
Evan e6e4d593d3 refactor: use the engine's local mode queries instead of this repository's copies (#29)
The engine absorbed the six local mode queries during the cross-platform work,
and MSIME-Linux has been driving them in production since. This repository kept
its own implementations of the same six features, so date-time, emoji, kaomoji,
jianpin, quick-phrase and unicode each existed twice and were free to drift.

Deletes this repository's copies and points the eight call sites in
event_listener.cpp plus the two async workers at metasequoia::local_modes.

The shuangpin profile is the part to look at. The old modules resolved the
*configured* scheme themselves:

    GetShuangpinProfile(shuangpin_schema.empty() ? GetConfiguredShuangpinSchema()
                                                 : shuangpin_schema)

The engine takes a resolved ShuangpinProfile and defaults it to Xiaohe. Taking
that default would have decoded J mode, emoji and kaomoji as Xiaohe for every
user on Ziranma, Shoudao or Microsoft shuangpin, so every call site passes the
configured profile explicitly through a ConfiguredShuangpinProfile() helper.

The database path needs the same care in the other direction. The old modules
took a UTF-8 std::string and handed it straight to sqlite3_open_v2; the engine
takes a std::filesystem::path and converts back with path_to_utf8 before
opening. The two async workers therefore bridge with path_from_utf8 rather than
constructing a path from the narrow string, which on Windows would interpret a
non-ASCII data directory in the active code page.

The four existing test files are kept and re-pointed at the engine functions
with every expectation byte-identical, including the seventeen date-time format
strings and the four shuangpin schemes jianpin covers. They are the equivalence
check: if the engine's port diverged from what this repository shipped, those
assertions fail rather than the difference reaching users.
2026-09-05 22:20:17 +09:00
Evan Hu 937e1c4f4a Merge branch main into feat/generic-pinyin-order-correction
唯一冲突是 MetasequoiaImeEngine 子模块指针。本 PR 原本指向作者 fork 上的提交
1017239,该提交已随 MSIME-Engine#1 合入引擎 main,因此改为指向引擎 main 当前
的 ecb28f6,它同时包含 #1 #5 #6 #18。
2026-09-05 22:17:22 +09:00
Ouchihao 2af10f56aa fix(candidates): honor English ranking in mixed input 2026-09-05 16:40:31 +08:00
fanlusky 82048fb8be Add a setting to always use Chinese/English punctuation. 2026-09-02 20:53:04 +08:00
fanlusky 7e8fbf8e7c Fix kei missing in shuangpin. 2026-09-02 16:40:46 +08:00
fanlusky 5c02503139 Render the candidate window with D2D instead of WebView2
Drive the existing candidate HWND through CandidatePresenter and msimeui,
keep WebView2 for the menu, floating toolbar, and settings, and load
external skins from skin.toml color tokens. Restore cloud/AI badges on
D2D items, and keep click, pin, fix-position, and delete on the same
window messages as before.
2026-09-02 15:30:03 +08:00
Jia Sui fbb2a56eaf style: apply clang-format to the new autocorrect tests 2026-08-31 16:01:49 +08:00
Jia Sui bfd42d314e feat(quanpin): wire up typing autocorrect config and tests
- config.toml [quanpin] autocorrect = true (default on)
- ime_config: GetConfiguredQuanpinAutocorrectEnabled /
  SetConfiguredQuanpinAutocorrectEnabled with template-preserving write
- engine_input_session injects the flag into the engine session
- tests: autocorrect table integrity, autocorrect_cut units, dictionary-level
  corrected-candidate-first behavior, legacy behavior when disabled, and
  cache isolation for plain spellings
- bump MetasequoiaImeEngine to 2422fd1
2026-08-31 16:01:25 +08:00
fanlusky 2effb7c393 Add support for the DeepLX translation service. 2026-08-30 15:39:02 +08:00
fanlusky 7c0461fe3a Fix adjust word frequency issue. 2026-08-29 23:56:50 +08:00
Manwood ea30b5fe90 test(quanpin): cover additional typo aliases 2026-08-26 17:17:10 +08:00
Manwood 6e97113899 test(quanpin): cover generic typo corrections
Update the engine submodule and add regression coverage for suffix-based corrections, ambiguous ehng interpretations, and continuous input.
2026-08-26 14:14:34 +08:00
fanlusky 27a7143050 Fix the issue where English words created by Enter cannot be replayed during new version installation. 2026-08-25 22:50:06 +08:00
fanlusky 1eef18401c Fix the issue where long sentence translations are not fully displayed. 2026-08-25 20:01:48 +08:00
fanlusky 38fe99be62 Add a function that commits the raw string when the Enter key is pressed and records the string into the dictionary. 2026-08-25 19:05:25 +08:00
fanlusky dafc7d6040 Optimize the logging on both the TSF and Server sides to facilitate issue diagnosis. 2026-08-25 13:53:30 +08:00
fanlusky bf0712cffd Further standardize the issue of batch importing dictionaries. 2026-08-25 11:58:33 +08:00
fanlusky 03b4dfeff4 Supplement: Tencent Cloud API translation from English to Chinese, and API invocation. 2026-08-22 19:44:06 +08:00
fanlusky e42cc92b8e Attempt to fix the flickering issue with Microsoft Shuangpin. Add an R mode for temporary Japanese input. 2026-08-22 16:54:29 +08:00
fanlusky 5da93af45c Optimize Japanese queries. 2026-08-22 12:03:55 +08:00
fanlusky 7bc55dab39 Integrate the Tencent Cloud Machine Translation API. 2026-08-22 02:22:38 +08:00
fanlusky 0eb5478cd3 Implement the whole-sentence algorithm and fuzzy query for Japanese. 2026-08-22 01:18:04 +08:00
fanlusky 1cf7846382 There should also be a corresponding status indicator when the keyboard Caps Lock is turned on. 2026-08-21 23:09:03 +08:00
fanlusky 6960a74fce Add dynamic switching of Japanese icons. 2026-08-21 17:33:37 +08:00
fanlusky 27b0974be1 Add preliminary support for Japanese input. 2026-08-21 16:55:41 +08:00