halo-sigs / halo-sigs/plugin-links

[Bug] 博客园 RSS 获取失败:UTF-8 BOM 导致 XML 解析错误,HTTP 重定向触发 restricted Host header

Open
#152 0 comments 0 reactions 0 assignees View on GitHub
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

Image

## 复现步骤

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.