- 字节嗅探唯一裁决 + 扩展名白名单 deny-by-default(23 个文本/代码扩展名);
客户端 Content-Type 与文件名只作参考、可被否决。
- 内容寻址落盘名 sha256[:32]+ext ⇒ 同字节同扩展名天然去重;首次 201/deduped:false,
命中 200/同一 name/deduped:true。
- 独立限额 CHAT_DOCUMENT_MAX_BYTES(默认 20 MiB,与图片 5 MiB 刻意解耦)+ MaxBytesReader。
- 读侧 Bearer owner|admin + Content-Disposition: attachment + nosniff(与图片读侧刻意不同);
写侧审计 document.upload;错误码 unsupported_media_type/document_too_large/document_not_found。
- 元数据表 documents(0014,含 idx_documents_name 唯一、idx_documents_sha256);
guardDocumentPaths 防路径穿越(否则 ServeMux 会 307 泄漏路径)。
- 既有 /api/v1/upload 与 /api/v1/files/{name} 零改动(upload.go 零 diff)。
验收(主会话独立复跑,非自报):隔离 worktree(HEAD + 本切片)go build/vet 绿、
全树 58 包 0 FAIL、linux/amd64 + darwin/arm64 交叉编译绿、9 条新单测 PASS。
变异独立复现 3 条:白名单加 ".js" ⇒ 415 用例红;嗅探裁决一律放行 ⇒ 谎报类型用例红;
摘掉 guardDocumentPaths ⇒ 穿越用例红(307 重定向,正是那道防护存在的理由)。
36 lines
2.0 KiB
SQL
36 lines
2.0 KiB
SQL
-- 0014_documents: 文档存储元数据(design/24 §3.2 / §3.3 / §6.1,切片 S1)。
|
||
--
|
||
-- 一行 = 一份内容寻址落盘的文档。落盘名 name = sha256(bytes)[:32] + ext,
|
||
-- 因此「同字节同扩展名」必然同名 —— 去重是内容寻址的自然结果,本表只负责
|
||
-- 记账(谁传的、多大、什么类型、什么时候),不参与去重判定。
|
||
--
|
||
-- 为什么必须有这张表:文件系统只回答「文件在不在」,回答不了「这份文档是谁
|
||
-- 传的、最后一次被消息引用是什么时候」。切片 S7 的引用计数与 GC(未被任何
|
||
-- 消息引用且超过保留期者删除)据此工作,因此本表从一开始就留足列;S1 只写。
|
||
--
|
||
-- 口径:
|
||
-- - name 唯一索引承载 ON CONFLICT(name) DO NOTHING(重复上传不刷新首次记录);
|
||
-- - mime 是**服务端字节嗅探所得**的基类型,不是客户端声明的 Content-Type;
|
||
-- - size 是落盘字节数;uploader 是 JWT sub,取不到时为 'anonymous';
|
||
-- - last_referenced_at / ref_count 由 S7 维护,此处只建列(S1 不填)。
|
||
-- 表名用复数,援引 AGENTS §10 已登记的例外(既有迁移口径一致)。
|
||
CREATE TABLE IF NOT EXISTS documents (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
sha256 TEXT NOT NULL,
|
||
mime TEXT NOT NULL,
|
||
size INTEGER NOT NULL,
|
||
uploader TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||
last_referenced_at TEXT,
|
||
ref_count INTEGER NOT NULL DEFAULT 0
|
||
);
|
||
|
||
-- 唯一索引同时承担 upsert 的冲突目标(ON CONFLICT(name) 需要唯一约束/索引)。
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_documents_name
|
||
ON documents (name);
|
||
|
||
-- 按内容哈希查找(同字节不同扩展名时也想知道「这份内容还存着哪些名字」)。
|
||
CREATE INDEX IF NOT EXISTS idx_documents_sha256
|
||
ON documents (sha256);
|