634 Commits

Author SHA1 Message Date
xfy
f5e19def1a feat(api): 新增回收站配置读写接口 2026-06-16 12:34:28 +08:00
xfy
b6de75ee97 feat(api): 新增 list_deleted_posts 回收站列表接口 2026-06-16 12:34:28 +08:00
xfy
c4c27a34c5 feat(model): 新增 TrashSettings 配置模型与 clamp 测试 2026-06-16 12:32:37 +08:00
xfy
65e090da27 feat(api): 新增回收站恢复/彻底删除/批量/清空接口 2026-06-16 12:31:55 +08:00
xfy
054d7450f7 feat(model): Post 新增 deleted_at 字段并用 try_get 兼容现有查询 2026-06-16 12:29:37 +08:00
xfy
863ec5ea52 fix(posts): get_post_by_slug 的 SELECT 补上 toc_html 列
row_to_post_full 用 row.get("toc_html") 读取该列,但 get_post_by_slug
的 SQL 漏选了 p.toc_html,导致访问公开文章详情时 panic(invalid column
toc_html)。补上列即可。
2026-06-16 11:34:12 +08:00
xfy
c60e4e08d7 fix(build): 用 cfg gate 消除 WASM 构建的 27 个假阳性 warning
Some checks failed
CI / check (push) Failing after 5m17s
CI / build (push) Has been skipped
Dioxus fullstack 在 WASM 构建中会剥离 #[server] 函数体,导致仅在
函数体内使用的导入/函数/常量被误报为 unused / dead_code。

- 仅在服务端使用的导入加 #[cfg(feature="server")]:get_conn、
  PostStatus/PostStats/Tag、password/session、User/UserRole、
  slug.rs 的 dioxus::prelude(保留 ServerFnError 给 server-gated
  的 ensure_unique_slug)
- 仅在服务端使用的项加 cfg gate:clamp_pagination 及其常量、
  rebuild 常量、format_relative_time、compute_content_hash、
  CommentStatus::from_str
- CommentCountResponse/PendingCommentsResponse 出现在 #[server]
  签名中(WASM 保留),无法 gate,改用 #[allow(dead_code)],
  与现有 CreateCommentRequest 处理一致

make build 零 warning,cargo test 299 passed。
2026-06-16 11:29:18 +08:00
xfy
1cc4bc5aeb style(list): 简化多行表达式为单行
Some checks failed
CI / check (push) Failing after 13m53s
CI / build (push) Has been skipped
2026-06-15 14:15:11 +08:00
xfy
a2ccc3da1a Merge branch 'fix-dos-and-toc-xss'
安全修复:
- 钳制公开分页接口 per_page 与 page 上限,消除 DoS 与缓存键扇出
- 修正 TOC 标题在属性上下文的转义,防止属性注入(XSS)

经 code review 后追加:
- 复用既有 escape_html,删除重复转义实现
- 增加 page 上限作为纵深防御
2026-06-15 11:40:01 +08:00
xfy
a3d6c2f19e refactor(markdown): 复用既有 escape_html,删除重复的转义实现
hooks::comment_storage::escape_html 已实现完整 OWASP 转义(& < > " '),
且无 server feature 门控,全平台可用。escape_heading_text 改为委托给它,
避免在仓库内维护第二份行为略有差异(缺 >)的转义函数。补充 < 转义测试。
2026-06-15 11:38:24 +08:00
xfy
eaa7118e09 fix(posts): 钳制 page 上限,防止无界 OFFSET 与缓存键扇出
clamp_pagination 原先仅限制 per_page,page 无上限。攻击者可用海量不同
page 值撑大缓存键空间(缓存污染)并触发无意义的超大 OFFSET 扫描。新增
MAX_PAGE=10_000,对任何实际博客都足够宽裕(配合 MAX_PER_PAGE 最多覆盖
50 万篇文章),同时把缓存键空间限制在有限范围。
2026-06-15 11:38:24 +08:00
xfy
6d32664020 fix(markdown): 转义 TOC 标题文本,防止属性上下文注入
generate_toc_html 原先用 clean_html 处理标题后拼进 aria-label="..." 与
<a> 正文,但 clean_html 只做正文 HTML 消毒、不转义双引号。标题形如
" onmouseover="alert(1) 会越出属性边界。新增 escape_html_attr(),对
标题文本统一做属性上下文转义(& " <),正文与属性两处一致使用。

内容由 admin 写入、严重度中低,但属真实消毒逻辑缺口,TOC 会展示给所有读者。
2026-06-15 11:27:32 +08:00
xfy
cfa4975813 fix(posts): 钳制分页参数 per_page 上限,消除公开接口 DoS
list_published_posts 为无需认证的公开接口,原先 per_page 直接透传给
SQL LIMIT,攻击者可传入巨大值迫使数据库扫描并实例化超大 Vec,造成
内存放大与拒绝服务。新增 MAX_PER_PAGE=50 与 clamp_pagination(),在
list_published_posts 与 list_posts 入口钳制 page/per_page;钳制值同时
用于缓存键与查询,避免同一逻辑页落入不同缓存条目。
2026-06-15 11:27:25 +08:00
xfy
19ffcada4a test: 移除脆弱断言与凑数测试(review 修正)
对本次新增测试自审后的清理:

1. webp: encode_lower_quality_produces_smaller_or_equal_bytes
   原断言“低质量体积 <= 高质量体积”是 WebP 的统计趋势而非
   确定性不变量,依赖底层 libwebp 量化策略,纯色图上高低差异
   接近 0,未来库升级易变为 flaky。改为验证两个质量档位都能
   成功编码并产生可被本模块解码的合法 WebP。

2. theme: theme_is_copy
   Copy 语义由编译器在编译期保证,运行期断言永真,不测任何
   运行逻辑,属凑数。

3. webp: webp_config_default_quality_and_method
   构造 struct 再断言字段等于构造时填入的值,是同义反复。

净减 2 个测试(292 → 290),全部通过。
2026-06-15 11:26:24 +08:00
xfy
263208b930 style: 修正新测试的格式以符合 rustfmt 宽度限制
cargo fmt 自动调整了 highlight.rs 与 error.rs 中新增测试的
assert! / assert_eq! 长行折行。
2026-06-15 11:22:35 +08:00
xfy
9fc626232c test(error): 补全 AppError 各变体的消息转换测试
原仅覆盖 Unauthorized/Forbidden/NotFound/DbConn/Query 五个变体,
补充:
- Internal 变体的消息透传
- Transaction 变体的 SQL 细节隐藏(与 Query 行为一致)
- 三类数据库错误统一返回中文通用提示的聚合断言

共 8 个测试,全部通过。
2026-06-15 11:20:33 +08:00
xfy
28f0117f06 test(sanitizer): 补充 is_safe_url 分支测试
is_safe_url 是 HTML 消毒器的安全核心,原仅通过 clean_html 间接覆盖。
新增 9 个直接单元测试,锁定各分支契约:

- https/http 白名单通过
- javascript / vbscript scheme 拒绝(含大小写混淆)
- data URI 按 allow_data_uri 标志决定(文章正文 vs 评论)
- 相对路径与锚点片段通过
- 空字符串/纯空白视为安全
- mailto / tel / ftp 等其它白名单 scheme 通过
- 含空白的 scheme 名(混淆手法)被拒绝

共 17 个测试,全部通过。
2026-06-15 11:19:46 +08:00
xfy
a9cde41de8 test(webp): 扩充 WebP 编解码测试覆盖
原仅 6 个测试,补充后覆盖:
- WebpError 的 Display 格式与 std::error::Error trait 约束
- encode 对非快速路径像素格式(Luma8 / LumaA8)的 RGBA 转换
- encode 质量参数对体积的单调性(低质量 <= 高质量)
- decode 对非法字节流与空输入的错误处理(不 panic)
- decode 错误信息带 'WebP decode error' 前缀便于排查
- 编解码往返保持图像尺寸不变

共 17 个测试,全部通过。
2026-06-15 11:18:55 +08:00
xfy
e9aac7d6b4 fix(highlight): 修复大写语言标识无法走别名映射的问题
别名表此前用 lang == from 做严格相等比较,导致 'RUST' 这类
大写标识无法命中 ('rust' → 'rs') 这类别名,最终回退到纯文本。

改用 eq_ignore_ascii_case 比较,使别名解析与扩展名/名称的小写
回退路径保持一致。

test(highlight): 补充 find_syntax 分支覆盖

新增 8 个测试覆盖此前未测的路径:
- 大写语言标识的小写回退(同时验证了上述修复)
- 别名表:golang/bash/yml 等映射
- 未知语言与空字符串的纯文本回退
- 代码首尾空白被 trim
- 多行代码逐行高亮

通过等价性断言(而非脆弱的 span class 字面量)校验别名解析,
避免 syntect 内部 class 命名变化导致测试漂移。

共 15 个测试,全部通过。
2026-06-15 11:17:53 +08:00
xfy
4e89412cde test(theme): 扩充主题模块测试覆盖
原仅 2 个 toggle 测试,补充后覆盖:
- toggle 的对合性(连续切换两次回到原值)
- Theme 的 PartialEq / Copy trait 行为
- 首屏预加载脚本的关键行为契约:读取 localStorage、
  回退 prefers-color-scheme、添加 dark class、
  包裹 try/catch 防止禁用 localStorage 时抛错

共 9 个测试,全部通过。
2026-06-15 10:57:51 +08:00
xfy
f7288cc390 style: 格式化测试代码以符合 clippy 宽度限制 2026-06-15 10:40:00 +08:00
xfy
0242534274 style(tags): 修复文档注释列表缩进以消除 clippy 警告
Some checks failed
CI / check (push) Failing after 4m52s
CI / build (push) Has been skipped
2026-06-12 19:48:10 +08:00
xfy
194611bb7e docs(bin): 补充中文注释 2026-06-12 19:45:59 +08:00
xfy
c43da3676f docs(components): 补充中文注释 2026-06-12 19:42:42 +08:00
xfy
c5d1eb117c docs(auth-pages, router, main): 补充中文注释 2026-06-12 19:31:21 +08:00
xfy
18500c9496 docs(pages-admin): 补充中文注释 2026-06-12 19:24:22 +08:00
xfy
abfab19839 docs(pages-frontend): 补充中文注释 2026-06-12 19:16:59 +08:00
xfy
1904907add docs(hooks, theme): 补充中文注释 2026-06-12 19:07:43 +08:00
xfy
671a9fea7a docs(infra): 补充中文注释 2026-06-12 19:02:37 +08:00
xfy
2db652137d docs(models, db, cache): 补充中文注释 2026-06-12 18:56:56 +08:00
xfy
a785683fc6 docs(posts): 补充中文注释 2026-06-12 18:50:17 +08:00
xfy
9921f8eebf docs(comments): 补充中文注释 2026-06-12 18:42:19 +08:00
xfy
26b012c40c docs(api, auth): 补充中文注释 2026-06-12 18:27:24 +08:00
xfy
4fe26f7eb3 test: improve unit test coverage and assertions
Some checks failed
CI / check (push) Failing after 15m51s
CI / build (push) Has been skipped
- Add tests for sanitizer, mime_to_ext, clean_tags, Theme::toggle
- Tighten assertions in highlight, markdown, post status classes
- Add boundary and XSS cases for comments, slug, rate_limit, webp
- Update Cargo.lock for serial_test dev-dependency
2026-06-12 18:13:51 +08:00
xfy
041cdf4102 test(cache): mark cache tests serial to fix parallel flakiness 2026-06-12 17:40:15 +08:00
xfy
942ac853fe refactor: tighten module-level allow attributes 2026-06-12 17:26:46 +08:00
xfy
294d60afab style: format rust code
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
2026-06-12 17:14:31 +08:00
xfy
71ac08c373 feat(rate_limit): derive real client IP from X-Forwarded-For with TRUSTED_PROXY_COUNT 2026-06-12 17:13:17 +08:00
xfy
4ae7b38131 feat(auth): add Secure flag to session cookie via COOKIE_SECURE env 2026-06-12 17:02:22 +08:00
xfy
ee6aaf179c feat(posts): add batch limit and error handling to rebuild_content_html 2026-06-12 11:44:53 +08:00
xfy
835d71972c fix(admin): add missing dark mode background colors to skeleton components 2026-06-12 11:15:46 +08:00
xfy
a10bf8737c refactor(sanitizer): extract shared sanitizer module and migrate from ammonia to lol_html 2026-06-12 11:15:42 +08:00
xfy
f8fb35f8c2 fix(admin): use arbitrary value for tooltip dark text color 2026-06-12 10:55:32 +08:00
xfy
1c3f1aac34 fix(admin): flip tooltip to bottom to avoid navbar overlap
Changed from bottom-full/mb-2 to top-full/mt-2, added z-50.
2026-06-12 10:54:02 +08:00
xfy
8840faa830 fix(admin): use arbitrary dark mode values for tooltip bg
Matches project pattern of dark:text-[#dadadb] / dark:bg-[#dadadb]
for reliable Tailwind v4 class-based dark mode.
2026-06-12 10:52:05 +08:00
xfy
c62cf5a239 feat(admin): add tooltip to rebuild content button
Pure CSS tooltip appears on hover, inverted color scheme
(white-on-dark / dark-on-light) matching admin aesthetic.
2026-06-12 10:50:18 +08:00
xfy
67e23d672c feat(admin): add rebuild content button to posts management page
Outline-style button next to '写文章', calls rebuild_content_html(false)
to rebuild posts with NULL content_html. Shows result message below header.
2026-06-12 10:43:49 +08:00
xfy
f6589121da perf(markdown): eliminate duplicate clean_html call in generate_toc_html
clean_html() triggers expensive ammonia sanitization with aho_corasick.
Previously called twice for the same text (aria-label + display text).
Now calls once and reuses the result.
2026-06-12 10:36:06 +08:00
xfy
2e9d123396 feat(posts): add rebuild_content_html server function
New admin-only server function that re-renders content_html and
toc_html for posts from their content_md:
- rebuild_all=false: only processes posts where content_html IS NULL
- rebuild_all=true: processes all non-deleted posts
- Processes rows one at a time to avoid table locks
- Invalidates post list and stats caches after rebuild
2026-06-12 10:35:59 +08:00
xfy
a975e45cc9 feat(posts): store toc_html in DB on create and update
- create_post: include toc_html in INSERT alongside content_html
- update_post: include toc_html in UPDATE alongside content_html
- Both write toc_html as NULL when empty, Some(..) otherwise
2026-06-12 10:35:51 +08:00