Repository navigation
Translation And Security
translation/ 包含 provider 适配、超时与重试、request-id、内容选择和密钥管理。
- 启用的多个服务按
spawn_blocking并行执行 - 单个服务失败作为数据返回,不中断其它服务的结果
- 多服务结果卡互不覆盖
direction.rs 在文本已经是目标语言时,按备选语言换向。未知的拉丁语系源语言仍尊重用户设定的目标语言。
react/main/translationStore.ts 管理主预览的翻译状态、多服务结果卡、单服务重试与陈旧响应保护。翻译与朗读分别管理在途任务,同一个 request-id 的旧响应不能覆盖新结果。
这类竞态在快速切换条目时必然出现,是本项目修过多次的一类缺陷:搜索、分页、新增/删除事件、预览渲染、翻译,每一条异步路径都需要代次隔离。
tts.rs 由 Rust 取回音频后以 data URL 播放,webview 不直接请求外部端点。文本长度上限 200 字符。
这样超时、大小上限与端点白名单只在 translation/http.rs 一处实现,webview 的 CSP 也不必为第三方主机放开 media-src。
以下每条都是硬约束,不是建议。
- 敏感条目在 Rust 内容选择阶段拒绝翻译
- 朗读走同一条内容选择路径,因此同样被拒绝
- 敏感条目从不进入翻译,因此也不会产生翻译记录
判定放在 Rust 而不是前端,因为前端可以被绕过,而内容选择是所有消费者的共同入口。
图片翻译只把本地 OCR 文本发送给 provider,不上传原图。
API key 只进入系统 Secret Service,不提供明文 fallback。Secret Service 不可用时报错,不降级写文件。
配置、数据库、Portal restore token 等本地私有文件:
| 平台 | 权限 |
|---|---|
| Unix | 文件 0600,目录 0700 |
| Windows | 禁止继承宽松权限的受保护 DACL,只保留当前用户的完整访问 ACE |
配置与 restore token 通过同一个私有文件替换函数提交:
- Unix 用
rename - Windows 用
MoveFileExW(REPLACE_EXISTING | WRITE_THROUGH)
替换后必须再次校正最终路径的权限。 临时文件的权限不会自动传递给目标路径。
Portal token 不进入普通配置文件。
| 约束 | URL 元数据 | 翻译响应 |
|---|---|---|
| 超时 | 5 s | 有 |
| 大小上限 | 1 MiB | 1 MiB |
| 凭据 | 无凭据 | — |
| 拒绝 | 私有/保留 IP、私有 DNS 解析、重定向 | — |
非 2xx 响应只在 4xx 时读取最多 4 KiB 正文用于错误归类,5xx 正文一律不读。
理由:有些服务不返回结构化错误,「缺少或无效 key」会退化成不透明的 http_status。读 4xx 正文能把它区分出来;而读 5xx 正文只会让网关错误页被误判成凭据问题。
- 用户文本一律通过 React 文本节点或
textContent写入 - 富文本仅使用严格 DOMPurify 配置后的
innerHTML - HTML 实体解码使用隔离的
DOMParser - 数学表达式不使用
eval/Function - 大文本在识别/解码/格式化之前执行预览预算
- 整数进制转换使用精确整数运算(BigInt),不经过浮点
截图保存目录与文件名模板可配置(留空即内置默认)。模板只生成文件名:路径分隔符与前导点被清洗,写不到目录之外。同名时追加序号,不覆盖已有文件。
写入采用同目录临时文件 → 完整写入 → sync_all → 原子提交,且不覆盖。
成功的译文与原文落在同一个 SQLite 库(translation_history,全库上限 500 条)。
条目删除、历史清空和上限清理都会一并删除它的译文;设置里另有「清空已保存的译文」入口。
这条「删条目必然删译文」由事务保证——详见剪贴板管线里关于 FTS 一致性的说明,两者是同一个机制。
各领域使用 thiserror:StorageError、TranslationError、PasteError、PinError、CaptureError,每个都带稳定的 code()。
顶层 ClippyError(error.rs)聚合领域错误,提供 domain() / code() / identifier(),让日志标识形如 paste.portal_start_rejected。
命令层对外仍是 String,但内部结构化:From<XxxError> for String 集中在各领域的 error.rs。
区分两种日志级别,避免把预期路径记成故障:
-
error::report(warn)— 真实故障 -
error::note(info)— 预期路径,如 Wayland 首次未授权、请求被新请求取代、快捷键连按撞上进行中的会话