Skip to content

fix(content): sanitize hostile font-size:0 in server book css - #137

Merged
finlater merged 3 commits into
finlater:mainfrom
baily-zhang:fix/sanitize-book-css
Sep 4, 2026
Merged

fix(content): sanitize hostile font-size:0 in server book css#137
finlater merged 3 commits into
finlater:mainfrom
baily-zhang:fix/sanitize-book-css

Conversation

@baily-zhang

@baily-zhang baily-zhang commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

变更说明

修复服务端返回的书籍 CSS 中 html, body { font-size: 0 } 导致 KOReader 全书正文塌缩成极小字号的缺陷。新增 Content.sanitize_book_css,在 fetch_chapter_css 解码样式分片后清洗独立的零字号声明。

相关:#133(调试同一本书时发现的脚注定义污染问题)。

类型

  • Bugfix
  • Feature
  • Refactor
  • Documentation
  • Other

Bugfix 要求

无关联 issue,提供复现步骤。

复现步骤(修复前):

  1. 用插件下载一本 e_2 样式分片中含有 html, body { ...; font-size: 0; } 的书。实测书目:《天才的責任:維根斯坦傳》繁简版
  2. 在 KOReader 中打开该书
  3. 现象:全书正文塌缩成接近零的字号,无法阅读,页数远低于该书真实长度
  4. 对照:同一本书在微信读书自家 App 与 Apple Books 中显示正常
  5. 佐证:手动从缓存的 epub 中删掉这一条声明后,字号立即恢复正常

实测环境:KOReader v2026.07.1,Kindle。

根因:

服务端返回的 e_2 样式分片偶尔包含:

html, body { margin: 0; padding: 0; font-size: 0; }

微信读书自家 App 与多数阅读器会忽略根元素的字号设置,但 crengine 会认真执行。正文使用 p { font-size: 1rem },一旦根字号归零,全书随之塌缩。

修复:

新增 Content.sanitize_book_css(css),在 fetch_chapter_css 解码分片时执行,因此覆盖所有消费路径(整书构建、分章缓存、单章拉取):

  • 移除独立的零字号声明:裸 0、任意字母单位(px/em/rem/vh/...)、%,以及可选的 !important。小数字号(如 0.5rem)与非零值不受影响
  • 迭代至不动点,使相邻声明(font-size:0;font-size:0)被全部移除
  • 保留块终止符(声明末尾缺少 ; 时保留 }),不触碰 -webkit-font-size 这类带前缀的属性,并记录被移除的声明条数

代码内已注明的已知限制:属性名匹配区分大小写(目前观察到的分片均为小写)。

Feature 要求

不适用,本 PR 为 bugfix,未新增特性。

测试

  • 已在 KOReader 中手动测试
  • 已运行 bash scripts/run_lua_specs.sh
  • 已运行 bash scripts/check_lua_namespace.sh
  • 已运行 luacheck main.lua _meta.lua weread spec
  • 如涉及插件加载/KOReader 兼容性,已运行或触发 KOReader integration
  • 不适用,仅文档或注释变更

测试说明:

新增 spec/content_css_sanitize_spec.lua(+103),覆盖:
真实问题分片样本、同级声明存活、!important、px/%/vh 单位、
小数字号透传、缺少结尾分号的声明、nil 与空字符串透传、
相邻重复声明的不动点处理、注释场景的结构完整性、幂等性。

全套 46 个 Lua spec 通过;check_lua_namespace.sh 无告警;
luacheck main.lua _meta.lua weread spec 无告警。

未触发 KOReader integration 工作流:本 PR 只改动 content.lua 中的
CSS 清洗逻辑,不涉及插件加载路径或 KOReader 兼容性。

设备侧已手动验证:修复后该书字号恢复正常。

已知影响:本次改动之前已下载的 EPUB,磁盘上仍保留有问题的 CSS
(插件按原样使用缓存路径),需重新下载受影响的书籍后才会生效。

非公开 WeRead API

  • 不涉及非公开 WeRead API
  • 已新增或更新可复现的 Python 验证脚本

脚本路径:

不适用

复现命令与脱敏结果:

不适用。本 PR 未新增或修改任何 WeRead Web API 调用,
只是在 fetch_chapter_css 解码既有响应之后,对返回的 CSS 文本做清洗。

模块结构

未新增或移动任何模块。仅修改既有的 weread/lib/content.lua,并新增 spec/content_css_sanitize_spec.lua。命名空间保持不变,未引入根级 lib/ui/ 模块,也未引入裸 lib.*ui.* 模块键。

截图

不适用。本 PR 是渲染缺陷修复,未新增或修改 UI、菜单、弹窗与交互。

Checklist

  • 我已经说明这个 PR 解决的问题或新增的特性。
  • 如果是 bugfix,我已经提供复现步骤或关联 issue。
  • 如果是新增 UI/交互特性,我已经提供截图或录屏。(不适用:未新增 UI 或交互特性)
  • 我没有提交 KOReader settings/weread.lua、API key、cookie、token、x-wrpa-* 或私人书籍内容。
  • 新增或移动的项目 Lua 模块遵循 weread/lib/weread/ui/ 命名空间规范。(不适用:未新增或移动模块)
  • 新增或修复的逻辑包含对应回归测试;若无法自动化,已在测试说明中解释原因。
  • 如果修改了用户可见文本,我已经更新 weread/lib/i18n.lua。(不适用:未修改用户可见文本)
  • 如果修改了菜单结构,我已经同步更新 README 菜单结构。(不适用:未修改菜单结构)
  • 如果涉及非公开 WeRead Web API,我已经在 scripts/ 中提交可独立运行、可复现的 Python 验证脚本,并填写了复现命令和脱敏结果。(不适用:不涉及)

WeRead's e_2 stylesheet shard occasionally ships 'html, body { ...
font-size: 0; }'. WeRead's own apps ignore root-element sizing, but
crengine honors it, and body text uses p { font-size: 1rem }, so the
whole book collapses to a near-zero font size on device.

Content.sanitize_book_css() now removes standalone zero font-size
declarations (bare or with any unit, optionally !important) while
keeping fractional sizes such as 0.5rem. It iterates to a fixpoint so
adjacent declarations are all removed, preserves block terminators,
and logs how many declarations were dropped.
@finlater

Copy link
Copy Markdown
Owner

请按模版提交 cr

@baily-zhang

Copy link
Copy Markdown
Contributor Author

已按 .github/PULL_REQUEST_TEMPLATE.md 重新整理了 PR 描述,补齐了变更说明、类型、Bugfix 复现步骤、测试、非公开 API、模块结构与 Checklist 各节,感谢提醒。

有一项想说明:测试小节里的「KOReader integration」我留空了。本 PR 只改动 weread/lib/content.lua 中的 CSS 清洗逻辑,不涉及插件加载路径或 KOReader 兼容性,理由写在测试说明里了。如果你希望无论如何都跑一次,我可以补上。

另外「非公开 WeRead API」勾的是不涉及:本 PR 没有新增或修改任何接口调用,只是在 fetch_chapter_css 解码既有响应之后,对返回的 CSS 文本做清洗,所以没有附 Python 验证脚本。如果你认为这种情况仍需脚本,我照做。

@finlater

finlater commented Sep 2, 2026

Copy link
Copy Markdown
Owner

修复方向没问题,不过这里直接删除所有 font-size: 0 会不会范围有点大?有些样式可能是有意这么设置的。

是不是只处理 html/body 上的 font-size: 0 会更安全一些?

Review feedback: removing every font-size:0 declaration from the book css
is too broad -- outside the root elements the declaration can be
intentional (e.g. hiding whitespace between inline-block items).

The sanitizer now walks whole selector{block} units and only rewrites
rules whose selector list is exactly html and/or body (case- and
whitespace-insensitive). Compound selectors (body p, body, .wrapper),
:root and @media-wrapped rules pass through verbatim; an at-rule ahead
of the selector no longer blocks matching. The zero-value forms (bare 0,
any letter unit, %, optional !important), the adjacent-declaration
fixpoint and the fractional-size guard are unchanged.
@baily-zhang

Copy link
Copy Markdown
Contributor Author

是不是只处理 html/body 上的 font-size: 0 会更安全一些?

已按这个方向收窄(62c2eb2):sanitizer 现在按 selector { block } 整体扫描,只重写选择器恰好是 html / body 的规则(大小写、空白不敏感,前置 @charset 等 at-rule 不影响匹配)。

以下情况全部原样保留,不再动:

  • 复合选择器:body pbody, .wrapper —— 它们还作用于其他内容
  • :root、无选择器的裸块
  • @media 包裹的规则

零值判定(裸 0 / 任意单位 / % / 可选 !important)、相邻声明的 fixpoint 迭代、小数尺寸(如 0.5rem)保护都保持不变。担心的"其他地方有意设置的 font-size: 0"(比如 inline-block 去空白的常见写法)现在不会被碰到了。

@finlater

finlater commented Sep 3, 2026

Copy link
Copy Markdown
Owner

请修复一下ci问题

The two untouched-cases only checked the removal count, leaving the
assigned `cleaned` unused until overwritten, which luacheck flags as a
warning and CI treats as fatal. Compare the full returned css instead,
which also makes the assertions stricter.
@baily-zhang
baily-zhang force-pushed the fix/sanitize-book-css branch from 452619e to 3d63fca Compare September 3, 2026 17:31
@finlater
finlater merged commit 2b28fa5 into finlater:main Sep 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants