halo-sigs / halo-sigs/plugin-links
[Bug] 博客园 RSS 获取失败:UTF-8 BOM 导致 XML 解析错误,HTTP 重定向触发 restricted Host header
- Dominant language
- Java
- Stars
- 38
- Forks
- 14
- Avg merge
- 11h 33m
- Merged PRs (30d)
- 1
Description
```markdown
## 环境
- plugin-links:2.2.1
- Halo 版本:2.25.4
- Java:21
- RSS 来源:博客园
## 问题描述
添加博客园友链并启用 RSS 订阅后,订阅内容无法获取。
下面两个地址分别会触发两种错误:
1. https://feed.cnblogs.com/blog/u/805157/rss
2. https://www.cnblogs.com/young-tea/rss
## 复现步骤
1. 新建或编辑一个友链
2. 启用 RSS 订阅
3. 分别填写上述 RSS 地址
4. 保存后手动刷新订阅,尝试拉取rss文章,会发现无法获取
5. 查看 Halo 日志
## 实际结果
```text
Failed to refresh RSS feed
https://feed.cnblogs.com/blog/u/805157/rss:
Invalid XML: Error on line 1: Content is not allowed in prolog.
Failed to refresh RSS feed
https://www.cnblogs.com/young-tea/rss:
restricted header name: "Host"
```
完整日志:
```text
2026-07-28T18:18:36.990+08:00 INFO
[plugin-links] Refreshing RSS feeds for link link-vwqor with 2 configured feed URL(s)
2026-07-28T18:18:37.317+08:00 WARN
[plugin-links] Failed to refresh RSS feed
https://feed.cnblogs.com/blog/u/805157/rss:
Invalid XML: Error on line 1: Content is not allowed in prolog.
2026-07-28T18:18:37.553+08:00 WARN
[plugin-links] Failed to refresh RSS feed
https://www.cnblogs.com/young-tea/rss:
restricted header name: "Host"
2026-07-28T18:18:37.553+08:00 INFO
[plugin-links] RSS refresh completed:
feeds=2, failedFeeds=2, fetchedItems=0, cachedItems=0
```
## 期望结果
两个地址都应该能够完成重定向并解析 RSS/Atom 内容,不应因为 BOM 或受限请求头而失败。
## 初步定位
这个问题似乎包含两个独立原因。
### 1. UTF-8 BOM 没有在字符串解析前移除
`https://feed.cnblogs.com/blog/u/805157/rss` 返回的正文开头包含 UTF-8 BOM。
字节解码后,Java 字符串的第一个字符是:
```text
U+FEFF
```
正文类似:
```text
\uFEFF
```
插件随后通过 `StringReader` 把正文交给 ROME/JDOM。此时 BOM 已经是普通 Java 字符,XML 解析器会报:
```text
Content is not allowed in prolog
```
建议在统一正文解码处移除开头的 BOM,例如:
```java
String decoded = new String(body, charset);
return decoded.startsWith("\uFEFF")
? decoded.substring(1)
: decoded;
```
### 2. 固定 IP 的 HTTP 请求把 Host 头交给了 Jsoup
`https://www.cnblogs.com/young-tea/rss` 会重定向到:
```text
http://feed.cnblogs.com/blog/u/805157/rss/
```
HTTP 分支为了防止 DNS 重绑定,会连接校验后的 IP,同时在
`requestHeaders()` 中手动加入原始域名的 `Host`。
但这些 headers 随后通过 Jsoup/Java HTTP 实现发送,Java 会拒绝手动设置
`Host`,于是抛出:
```text
restricted header name: "Host"
```
不建议简单删除 `Host`,因为请求实际连接的是 IP,删除后可能无法正确访问虚拟主机。
建议让 HTTP 分支与当前 HTTPS 固定 IP 分支采用相同思路:
1. 校验并固定解析后的公网 IP;
2. 直接通过 Socket 连接该 IP;
3. 手动写入 HTTP 请求和正确的 `Host`;
4. 继续对每次重定向重新执行 SSRF 校验。
也不建议通过全局属性开放 Java 的受限请求头,因为这会扩大整个进程的影响范围。
我已经借助 `codex` 完成了修复版并且测试通过,如需参考可以提供。
Contributor guide
No contributing guide indexed for this repository
Research direction
Trace the RSS refresh HTTP fetcher, including the unified response-body decoding path and the HTTP redirect branch that builds request headers. Reproduce both cnblogs URLs and inspect the BOM handling and restricted Host-header failure. Done means both URLs follow redirects, pass SSRF checks, and parse their RSS or Atom content successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100