dsh-reader
Verifieddsh-reader · v1.0.0 · MulanPSL-2.0 · Web UI
DSH Web 插件:在会话底部状态栏推送一行小说正文,支持本地 TXT/EPUB 与在线阅读。
Install
dsh plugin add dsh-reader Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
摸鱼阅读(dsh-reader)
DSH Web 插件:在会话底部「token 用量」这一行的右侧,推送一行小说正文。 用键盘快捷键翻页、翻章、开关自动翻页、隐藏正文;书路径、阅读模式、章节正则、每行字数、 字号、自动翻页间隔和 8 个快捷键都在 DSH 设置页唯一的「阅读」分区里配置。
支持本机 TXT / EPUB,也支持填一个目录 URL 在线阅读。设置页按 「书籍文件 → 公共书架 → 来源 → 显示 → 章节列表 / 快捷键 → 操作条」的卡片顺序排列; 「本地 / 在线」分段只切换配置面板,不会直接切书。本地书可以用「浏览…」调用 宿主原生目录选择器挑 TXT / EPUB;书架是公共区,本地与在线阅读时都显示同一份列表。
在线阅读由宿主抓取目录与正文、按站点规则或启发式解析、分页后推到同一行底栏,
正文缓存在本机 $DSH_HOME\reader\cache\。
截图
阅读效果(底栏 token 用量右侧的一行正文):

设置页(书籍文件 → 公共书架 → 来源 → 显示 → 章节/快捷键折叠 → 操作条):

截图由项目作者提供并授权用于本 README 与 npm 包内 README;图中的书名、进度与用量只是示例阅读状态。
功能
- 底栏一行只读展示正文:不显示书名、章节名、页码,也没有 title / hover 提示; 鼠标悬停不会有浮层。无书、加载中或空正文时整行隐藏;字号可调(11–16),最大宽度 60vw。
- 老板键(默认
Alt + Shift + H):按一次只显示占位符···,正文完全不进 DOM; 隐藏时再按老板键无操作(不再 toggle)。按下下一页 / 上一页 / 下一章 / 上一章 / 本章第一页 / 本章最后一页这些阅读快捷键时会先恢复显示再执行动作。 自动翻页 tick 静默推进,不会解除隐藏;隐藏状态只在当前页面内存里,刷新页面默认显示正文。 清空绑定即禁用老板键。 - 三种阅读模式:
- 章节模式:按章节正则切章,每章按每行字数分页;
- 按字数:整本书作为一章,按每行字数分页;
- 按换行:整本书作为一章,每个非空行一页(超过 2000 字的长行才二次切)。
- 章节标题正则可在设置页修改(仅章节模式),支持
m标志、300 字符上限; 留空使用内置默认。 - 本地 EPUB:读取 ZIP 中央目录(store / deflate)、OPF spine 顺序、NCX / EPUB3 nav
标题,缺失时退化到正文
<h1>/<h2>/<title>或「第 N 章」。 - 设置页重设计:720px 单列、卡片分组、140px 行标签、11px 辅助字;
高级项全部收进默认收起的
<details>,底部操作条左侧状态、右侧按钮。 - 公共书架:书架在「本地 / 在线」分段之外,两种来源下都显示同一份列表 (最近 20 本、显示 6 行后滚动);显示「共 N 本」、当前书高亮、本地/EPUB/在线 kind 与章节/页码,可直接打开或移除记录;每本书进度独立。
- 选书器:本地「书籍文件」一行提供「浏览…」,调用宿主
uiWorkspace.pickDirectory()选目录,再由宿主列出该目录下的.txt/.epub; 点选文件名即填入路径并自动打开。无该服务、取消、空目录、接口失败都有对应提示。 - 设置页章节列表:标题搜索(宿主过滤,300ms 防抖)、当前章高亮、一键跳转;
长书按当前章所在窗口请求(
offset = current-100),保证当前章在列表内。 - 支持 UTF-8、UTF-16LE / UTF-16BE(带 BOM)、GB18030 编码的 TXT。
- 按每行字数分页,优先在句末标点处断页,不切坏 emoji 代理对。
- 进度持久化,刷新页面或重启 DSH 后从上次位置继续;切书不丢进度。
- 自动翻页;标签页在后台时跳过 tick,且后台不启动定时器。
- 设置页支持录制快捷键、清除绑定、恢复默认、重置进度、删除书架记录。
- 在线阅读(设置页「在线」分段):目录 URL → 章节列表 → 正文 → 分页 →
翻页/翻章,规则优先自定义书源(
sources.json)→ 内置站点库(3002 条,可在设置页 关闭)→ 启发式;正文按章节缓存,离线时回退到缓存;在线书进书架,和本地书互不干扰。 - 设置合并:本地与在线共用一个「阅读」页;「高级」「章节列表」「快捷键」 默认收起;不再有「显示章节名」开关。
- 节点调试:填 URL + 章节/正文选择器,只读测试命中数与首段文字,不写缓存; 支持「复制 JSON」。
- 站点库检索:按 host 搜索内置站点(300ms 防抖),一键填入目录 URL。
安装
本仓库只包含插件源码,不包含安装动作。将 dsh-reader/ 作为本地插件包交给 DSH
的插件管理流程安装(bundle patch 会插入一条加载项),然后在 Web GUI 刷新页面。
插件清单:
package.json:dsh.bundle.patch与dsh.client声明。cordis.patch.yml:向当前 profile 插入dsh-reader。index.js:宿主半边,注册/dsh-reader前缀 HTTP 路由。book.js:解码、切章、三种模式分页的纯函数。epub.js:EPUB 解析(node:zlib,无第三方依赖)。shelf.js:书架模型的纯函数。online.js:在线抓取(node:http(s))、编码识别、SSRF 防护、目录/正文解析、章节缓存。html.js/selector.js/xpath.js/sources.js:容错 HTML、CSS 选择器子集、 XPath → CSS、站点库与规则合并。client.js:浏览器半边,注册conversation.composer.dock与一个settings.section(reader/ order 60),本地与在线共用。locale/*.json:插件管理页的标题/描述。icon.svg:插件图标。
安装并启用后,插件作用于当前 profile 的所有会话。没有配置书时底栏不显示任何内容。
使用
- 打开 DSH 设置页,左侧进入「阅读」。页面从上到下是:书籍文件 → 公共书架 → 来源(本地 / 在线分段)→ 显示 → 章节列表 / 快捷键(折叠)→ 底部操作条。
- 书籍文件:可以直接填 TXT / EPUB 的绝对路径后点「打开」,也可以点「浏览…」
选一个目录,再从宿主返回的
.txt/.epub列表里点文件名;点选后会自动填入并打开。 - 书架在分段之外,切本地/在线都显示同一份:点「打开」切换当前书(每本进度独立), 点「移除」只删除记录,不会删除磁盘文件。
- 在「来源」里切「本地 / 在线」只改变下方的配置面板,不会切书;本地高级里选择 阅读模式、填写章节正则,在线面板里配置目录 URL 与站点规则。
- 「显示」卡片调整每行字数、字号、自动翻页。
- 展开「章节列表」搜索并跳章(非章节模式只有 1 条);展开「快捷键」录制 8 个动作。
- 点击底部「保存」。保存成功后底栏立即刷新;失败时操作条左侧显示红色原因。
- 把焦点留在页面任意位置,用快捷键翻页。焦点在输入框里时,带 Alt/Ctrl/Meta 的组合仍然生效。
在线阅读(已并入「阅读」页)
- 在「来源」卡片把分段切到「在线」(只切配置面板,当前书不变)。
- 在「目录 URL」里填目录页 URL(只支持 http/https),点「解析并阅读」。宿主会抓目录、 匹配规则、切到在线书;首次解析可能需要几秒,底栏在加载期间隐藏(不显示占位)。
- 底栏只显示正文;翻页/翻章快捷键与本地书相同。跨章时才抓新章(1–15 秒), 失败会保留当前页,并在设置页在线面板用红字提示原因(下一条成功动作后清除)。 底栏不显示书名/章节名/页码,也不显示任何 title、缓存或错误提示。
- 「当前规则」压成一行:来源徽标(自定义 / 内置 / 启发式)+ host + 4 条路径的省略显示。
- 展开「高级(自定义书源 / 节点调试 / 站点库)」: 「自定义书源」可以按 host 覆盖规则;「删除自定义」只删规则、不删缓存, 之后回落到内置或启发式;「恢复内置」把内置规则填回表单。
- 「节点调试」用任意 URL + 选择器做只读预览(不落盘、不写缓存), 显示章节链接数、正文字数、首段 80 字和不支持的选择器。
- 「站点库」按 host 搜索内置 3002 条站点,点「填入」把目录 URL 写进上方输入框; 「使用内置站点库」开关可关闭内置库(只用自定义 + 启发式)。
- 需要回到本地书时,点「切回本地书」(来源切回本地并显示本地面板),或在书架里打开一本 TXT/EPUB。
规则优先级:$DSH_HOME\reader\sources.json 自定义 → 内置站点库(精确 / 去 www /
逐级后缀)→ 启发式。自定义规则命中但选择器解析不到章节时会直接报错(方便调试),
内置规则失效时会自动回退启发式并在设置页提示。
抓取约束:只抓 http: / https:;每一跳(含重定向)都先 dns.lookup 解析,所有
A/AAAA 地址都必须落在公网段,命中本机/内网/保留地址即拒绝;校验通过的地址会通过
lookup 固定给 http.request(DNS pinning),不会二次解析;解析失败按网络错误拒绝。
单请求最长 15 秒(含响应体的绝对 deadline),响应体 ≤ 8MB,最多 5 次重定向;
整章(多页合并)预算 30 秒,超时停止后续分页;单页正文 ≤ 1,500,000 字符、合并后
≤ 2,000,000 字符,超出截断并提示;缓存文件 > 4MB 不写入。单章最多合并 20 页
(nextLoopMax 1–40)。
阅读模式
| 模式 | 行为 | 章节数 |
|---|---|---|
| 章节模式 | 按章节正则逐行匹配标题并切章,每章按每行字数分页 | 取决于书 |
| 按字数 | 整本书作为一章,按每行字数分页(不做章节识别) | 1 |
| 按换行 | 整本书作为一章,每个非空行一页;连续空行合并 | 1 |
切换模式会重置当前页到第 0 页,章节下标越界时自动收敛;三种模式都支持 下一页 / 上一页 / 下一章 / 上一章 / 本章第一页 / 本章最后一页。
章节正则
- 默认空值 = 内置规则(
第X章 / 序章 / 楔子 / 引子 / 尾声 / 后记 / 番外)。 - 自定义正则按行匹配,编译时固定使用
m标志;建议写成^第.*章.*这样的行首锚定形式。 - 最长 300 字符;语法错误、超长、包含
(a+)+这类嵌套量词、反向引用(\1/\k<name>) 等灾难性回溯写法时保存会被拒绝且不落盘。 - 宿主加载到非法/危险正则时会回落内置默认、回写安全值,并在 state 的
warning与设置页提示。 - 超过 1000 字符的行一律当正文处理,不参与标题匹配(防止误判和正则回溯)。
EPUB
- 支持:标准 ZIP(store / deflate)、
META-INF/container.xml、OPF manifest/spine、dc:title、NCX 2.0 与 EPUB3 nav 目录、XHTML 正文(实体解码、<br>换行、去脚本/样式)。 - 章节顺序 = spine 顺序,一章对应一个 XHTML 文件;同一文件内按锚点拆章不支持。
- 不支持:ZIP64、加密 / DRM EPUB、CSS 排版、图片与音视频、脚注弹窗。
- 安全限制:拒绝
../ 绝对路径条目、单条目解压上限 20MB、总解压量 64MB、条目数 5000。 - EPUB 固定按「章节模式」阅读;设置里的全局模式只对 TXT 生效。
默认快捷键
| 动作 | 默认绑定 |
|---|---|
| 下一页 | Alt + ] |
| 上一页 | Alt + [ |
| 下一章 | Alt + Shift + ] |
| 上一章 | Alt + Shift + [ |
| 本章第一页 | 未绑定 |
| 本章最后一页 | 未绑定 |
| 自动翻页开关 | Alt + \ |
| 老板键(隐藏正文) | Alt + Shift + H |
快捷键按 event.code(物理键位)精确匹配,修饰键集合必须完全一致。
在设置页点击某个动作右侧的按钮即可重新录制:
- 必须包含
Alt、Ctrl或Meta,纯字母/符号组合会被拒绝。 Esc取消录制;Delete或Backspace清除绑定(清除后该动作禁用)。- 同一个组合键不能分配给两个动作;保存时会校验。
- 「恢复默认」只改表单,点击「保存」后才写入。
- 老板键是纯客户端动作:宿主只接收前 7 个快捷键,老板键绑定单独存在浏览器
localStorage(dsh-reader.bossKey,仅当前 DSH Web origin),刷新后仍有效; 换浏览器、清站点数据或端口变化会回默认,settings.json备份不含它。 清空后老板键禁用;隐藏状态本身不持久化,刷新页面默认显示正文。 - 老板键语义:按下只隐藏(
···);已隐藏时再按无操作。下一页 / 上一页 / 下一章 / 上一章 / 本章第一页 / 本章最后一页任一快捷键触发时,先解除隐藏再执行动作。 自动翻页 tick 直接推进,不解除隐藏;刷新页面默认可见。
自动翻页
- 设置页可选关闭 / 5 / 10 / 20 / 30 秒。
- 用
Alt + \在「关闭」和上一次的非零间隔之间切换;首次开启默认 10 秒。 document.visibilityState === 'hidden'时不启动定时器,也不会跳过式空转。
语言
界面语言跟随 DSH(中文 / English 词典随宿主语言切换)。这是与 IDEA 版参考项目
唯一有意不同的地方:插件不提供独立的 language 设置,避免与 DSH 全局语言冲突。
数据文件
运行数据放在 $DSH_HOME\reader\(默认 $DSH_HOME\reader\):
settings.json:书路径、模式、章节正则、分页/字号/自动翻页设置、宿主认识的 7 个快捷键,以及 新增的sourceKind(local/online)、catalogUrl与siteLibraryEnabled(默认 true)。老板键绑定不在这里,见下方localStorage。 写入采用.tmp+rename原子替换。- 浏览器
localStorage的dsh-reader.bossKey:老板键绑定。宿主settings.json的shortcuts只有 7 个动作,所以老板键由客户端单独保存。 它的作用域是当前 DSH Web 页面的 origin(协议 + 主机 + 端口),端口变化即失效; 换浏览器、隐私/无痕模式、清站点数据或清理浏览器存储都会回到默认Alt + Shift + H。settings.json的备份/迁移不包含这个绑定;清空为空串表示禁用。 shelf.json:书架记录(最多 20 条)与每本书各自的进度;翻页/切章/切书后写入。 在线书记录的path形如online:https://…、kind为online。sources.json:自定义书源({version:1, items:{host:rule}},原子写;host 必须是 合法小写主机名,非法 PUT 会被 400 拒绝)。内容不是合法 JSON 时会先复制成sources.json.bad-<ts>再重建空表。progress.json:旧版进度文件。升级后只在shelf.json不存在时读取一次并 迁移成一条书架记录,文件保留、不再写入;之后即使书架被删空也不会再次迁移。 确认不再需要旧版本时可以手动删除。cache/:在线正文缓存,cache/<host>/<sha1(url)>.json,最多 200 个文件 (超出按 mtime 淘汰最旧的,只删该目录内文件)。删除规则或移除书架记录都不会删缓存。
书籍本身不会复制进 DSH_HOME,始终读取用户填写的原路径;在线正文只缓存纯文本。
卸载与数据残留
卸载插件不会自动删除 $DSH_HOME\reader\。如需彻底清理:
- 先退出 DSH(避免运行中的进程继续写回文件)。
- 删除整个
$DSH_HOME\reader\目录(或只删其中不再需要的文件)。
删除后会丢失:书架列表与每本书的阅读进度、全部设置(模式/正则/字号/快捷键)。 书本身不受影响。升级/回退版本前建议先备份该目录。
HTTP API
浏览器半边通过同源 /dsh-reader/api/* 访问宿主:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /dsh-reader/api/state |
当前展示状态(含 mode、fontSize、warning、book.kind) |
| POST | /dsh-reader/api/action |
next / prev / nextChapter / prevChapter / chapterStart / chapterEnd / setAuto / reset |
| GET | /dsh-reader/api/settings |
设置、书籍元信息、错误、默认值、warning |
| PUT | /dsh-reader/api/settings |
整体保存设置并重新加载书 |
| POST | /dsh-reader/api/reset |
进度归零 |
| GET | /dsh-reader/api/shelf |
书架列表(含 current / missing) |
| POST | /dsh-reader/api/shelf |
{action:'open'|'remove', path},返回 state |
| GET | /dsh-reader/api/chapters |
章节列表,query / offset / limit(默认 50,上限 200,小于 1 按 1 处理),index 为全局下标 |
| POST | /dsh-reader/api/chapters |
{action:'open', index} 跳章,返回 state |
| POST | /dsh-reader/api/online/open |
{catalogUrl} → 解析目录并切换在线书,返回 state + rule/ruleSource |
| POST | /dsh-reader/api/online/refresh |
重抓目录,按章节 URL 尽量保留当前章 |
| POST | /dsh-reader/api/online/preview |
{url, chapterPath, contentPath} 只读预览,不落盘/不写缓存 |
| GET | /dsh-reader/api/source |
?url= 或 ?host= → 命中规则、来源、是否自定义覆盖 |
| PUT | /dsh-reader/api/source |
{host, rule} 保存自定义规则(chapterPath/contentPath 必填) |
| DELETE | /dsh-reader/api/source |
?host= 删除自定义规则(不删缓存) |
| GET | /dsh-reader/api/sites |
?query=&limit= 搜索站点库,默认 50 条(上限 100),含 hasCustom |
| GET | /dsh-reader/api/browse |
?dir= 绝对目录 → {files:[{name,path,size}], truncated},只列 .txt/.epub,最多 500 条 |
/api/browse 只做一层 readdir:不递归、不读文件内容,Dirent.isFile() 排除目录与
符号链接;dir 不是绝对路径、不存在或不是目录时返回 400。响应示例:
{ "ok": true, "dir": "D:\\books", "files": [{ "name": "a.txt", "path": "D:\\books\\a.txt", "size": 1234 }], "truncated": false }
在线接口失败时返回 {ok:false, error, code}(HTTP 400),code 为
timeout / http-403 / http-404 / http-other / not-html / no-rule /
no-chapters / no-content / js-rendered / ssrf-blocked / bad-url /
network(DNS 解析失败等)/ too-large;
抓章失败时 /api/action 仍返回 200 的 state,并用 actionError 说明原因、保留上一页,
客户端只把该字段显示在设置页 banner(底栏不显示错误、也没有 title),直到下一条成功动作。
POST /api/online/open 接受 force 参数但目录不落盘,因此没有可强刷的缓存(忽略)。
章节文本超过上限时,响应 state 里 chapterWarning 带「内容超长已截断」/
「内容超长,已跳过缓存」等提示,chapterTruncated 为 true。
所有响应都是 JSON,cache-control: no-store。请求必须带 x-dsh-reader: 1;
跨站 sec-fetch-site、Origin/Host 不一致、缺少自定义头的请求会被 403。
请求体上限 64KB;500 只返回 {ok:false,error:"内部错误"},详细错误只进宿主日志。
已知限制
- 本地 TXT / EPUB 与在线阅读都只做纯文本:不解析 CSS 排版、图片、脚本。
- TXT 一次把整本书读进内存;超大文件(几十 MB 以上)会占用较多内存。
- 「按换行」模式需要为整本书生成页面数组,超大文件的页数会很多。
- EPUB 只取纯文本,不还原排版;不支持 ZIP64 / 加密 / 按锚点拆章。
- 章节列表一次最多展示 200 条(宿主过滤后再分页,不会把全书章节拉到前端)。
- 多标签页同时翻页时,各自的内存状态可能短暂不一致;宿主以最后一次写入的进度为准。
- 老板键只是普通键盘事件:若与浏览器/系统/输入法快捷键冲突,可能被上层拦截;
建议在设置页改成带
Alt/Ctrl且不常用的组合。清空绑定即禁用。 - 老板键绑定只存在当前浏览器的
localStorage['dsh-reader.bossKey'],不属于settings.json的备份内容;作用域是当前 DSH Web origin,端口变化、换浏览器、 隐私模式或清站点数据都会静默回默认Alt + Shift + H。 - 老板键隐藏态只在当前页面内存里;刷新页面或重开应用后恢复显示正文 (重开设置页不会复位隐藏态)。隐藏时按下一页/上一页/翻章/章首/章末快捷键会先恢复显示; 自动翻页仍在后台推进,但不会解除隐藏。
- 「浏览…」依赖宿主
uiWorkspace.pickDirectory()(平台只有目录选择器,没有文件选择器); 服务缺失时只能手动输入路径。它只列出所选目录第一层的.txt/.epub(扩展名大小写不敏感,最多 500 条),不递归、不跟随符号链接、不读取文件内容。 - 没有 GUI 安装器;需要按 DSH 的本地插件流程安装。
- 在线阅读的边界:
- JS 渲染站点抓不到:只有服务端返回的 HTML 里的章节/正文可见;页面脚本
执行后才有内容的站点会报
js-rendered或no-chapters。 - 反爬:403、验证码、Cloudflare 挑战等只能报
http-403/not-html; 不带 Cookie、不执行 JS、不模拟登录,也不做 IP 轮换。 - 编码:支持
Content-Type/<meta>/ BOM / UTF-8 / GB18030;Big5、 Shift_JIS 等依赖 Node 的 ICU 解码能力,失败时按 GB18030 / latin1 兜底。 - 规则误配:自定义选择器只支持本项目 CSS 子集(
tag/#id/.class/[attr]、:nth-of-type/:first-child/:last-child、后代/子代/并列);不支持:has/:not/:contains/:nth-child、属性运算符、兄弟组合,命中不了会报no-rule/no-chapters,可用「节点调试」定位。 - SSRF 防护:每一跳先做字面量判定(拒绝 localhost、内网/保留 IP、单标签主机名、
内网域名后缀、十进制/十六进制 IP),再
dns.lookup校验所有 A/AAAA 地址; 命中私网、解析失败或超时都拒绝,绝不回落直连。校验通过的地址会 pin 给http.request,避免二次解析(DNS rebinding 的另一半);但不做 DNS 服务器的 可信性检查,且不防「DNS 返回公网地址、实际路由劫持」这类网络层攻击。 - 在线书固定按章节模式阅读(每章按
charsPerLine分页),不复用本地的 「按字数 / 按换行」模式。 - 目录与正文都在宿主内存里缓存;重启 DSH 后目录会重新抓一次,正文命中磁盘缓存。
- 单页/合并正文有上限(1.5M / 2M 字符),超长章节会被截断;改规则不会失效
已缓存正文(缓存键只有 URL),需要时手动清
cache/。
- JS 渲染站点抓不到:只有服务端返回的 HTML 里的章节/正文可见;页面脚本
执行后才有内容的站点会报
第三方数据与许可
在线书源数据来自 chapter_reader(MulanPSL2)。站点库由该项目作者 wind 维护,
本仓库只裁剪出阅读所需的 7 个字段(checkUrl / chapterPath / contentPath /
nextUrlPath / nextPathText / nextLoop / nextLoopMax)。许可证全文与
来源、抓取日期、裁剪说明见 third-party/chapter_reader/LICENSE 和
third-party/chapter_reader/NOTICE.md。
开发与测试
cd dsh-reader
node --check index.js
node --check online.js
node --check book.js
node --check epub.js
node --check shelf.js
node --check html.js
node --check selector.js
node --check xpath.js
node --check sources.js
node --check client.js
node --test tests/
node -e "import('./index.js').then(m => console.log(typeof m.apply))"
测试覆盖:三种解码 / 切章 / 分页边界 / 标点优先 / 代理对安全、三种阅读模式、
自定义正则(合法、非法、锚定、超长、嵌套量词)、EPUB(store + deflate、NCX / nav /
标题回退、实体与 <br>、恶意 ZIP)、书架(去重、上限、移除不删文件、进度独立、
旧 progress.json 迁移)、章节列表过滤分页、假 ctx + 真 HTTP 的接口冒烟,以及
在线阅读(本地模拟站的目录/正文/多页合并、编码、错误分类、SSRF 与重定向、
缓存命中/损坏/离线回退/LRU、自定义书源 CRUD、站点检索、节点预览、重启 loading、
书架在线记录、合并后的在线面板渲染、[\1] 正则误报回归),单一设置页 /
底栏纯正文 / 分段显隐,以及 720px 卡片结构 / 公共书架 / 老板键「只隐藏 +
阅读动作恢复、auto tick 不恢复」/「浏览…」选书流程与降级 / /api/browse 边界
(大小写、排序、500 条截断、目录与相对路径 400、缺头 403)。