惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
博客园_首页
J
Java Code Geeks
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
量子位
C
Check Point Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 三生石上(FineUI控件)
L
LangChain Blog
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
B
Blog
美团技术团队
Microsoft Security Blog
Microsoft Security Blog
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
Last Week in AI
Last Week in AI

蛋蛋之家

Halo 博客晚间卡顿排查记:从"玄学卡顿"到"真凶落网" 我用这个开源项目,把 AI 绘画提示词变成了"代码"——awesome-gpt-image-2 深度体验报告 群晖部署 LX Music Server:浏览器就是你的音乐播放器【详细版】 500道Word题库太痛苦?于是我写了个智能学习平台 别用一个人的奔跑,耗尽两个人的余生——《你凭什么认为我会一直喜欢你呢》 博客评论自动填充新方案:兼容 Vue/React 响应式绑定与跨架构填充逻辑 2026 生产级指南:Halo 2.x + PostgreSQL 自动化部署与性能优化手册 从 Typecho 转向 Halo 2.x:全能型 Nginx 架构与 SEO 无损迁移指南 Typecho 迁移 Halo 完整教程:数据库视图解决表前缀 + 自动提取封面图 + 去重检测 我的 CodeBuddy 装备库:218个技能全公开 + 使用心得 为博客添加自定义评论表情 Comment2Bark:Typecho评论推送到Bark插件 又拍云插件 UpyunFile v1.2 更新:修复 Typecho 1.3.0 兼容性问题 手把手修复RSS 403:从PHP警告到Cloudflare拦截的全链路排查 网站被镜像怎么办?2026年反镜像攻防实战指南 VLESS + Reality + Vision:2026 极致隐蔽节点搭建全解析 给OpenList和Alist加上液态玻璃效果,颜值直接拉满! 网页截图瞬间“赛博飞升”?手把手教你根治 Windows HDR 截图发白 群晖 NAS 影音终极方案:Docker Rclone + OpenList 完美挂载 Plex 全攻略 一个简洁高效的SVG图标库 告别重复劳动!一键填充评论的神奇书签脚本 - 蛋蛋之家 解锁思源笔记的隐藏力量:siyuan-patch 深度体验与技术实现 Oracle Cloud 自救指南:旧手机丢失后如何绕过 MFA 重新登录【更新2026年最新政策】 本站已加入“萌国ICP备案”联萌!😄 Excel 月报自动化指南:告别重复劳动,让数据主动为你服务 梦呓:为异地女友准备的2周年惊喜礼物 Typecho 多吉云插件优化:更灵活的存储目录配置 甲骨文云硬盘扩容指南:从50G到200G的“免费午餐”怎么吃? ⏰ 时间同步:当你的服务器开始“穿越”时,如何优雅地把它拉回现实 Typecho 又拍云文件管理插件修改版使用指南:新增自定义目录结构功能
Dream2.0 Plus 友链头像 400 错误:双重编码陷阱排查
吴蛋蛋 · 2026-06-08 · via 蛋蛋之家

遇到的问题

在使用 Dream2.0 Plus 主题时,友链页面里,有几个博主的头像怎么都刷不出来,F12 一看全是 400 Bad Request

奇怪的是,把头像链接复制到浏览器地址栏直接打开,图片又是正常的。而且不是所有头像都坏,只有部分挂了。

后台检查了一圈,发现规律:头像 URL 带中文路径的才会炸

Update:更新 PR 时 CodeQL 完全误报,采用更“聪明”的方式解决。

Dream2.0 Plus 友链头像 400 错误:双重编码陷阱排查

排查原因

查看代码后终于发现了根本原因所在:双重编码问题(核心问题)

控制台报错如下:

GET https://www.example.ccom/upload/%25E5%25A4%25B4%25E5%2583%258F.png  400 (Bad Request)

注意 URL 里的 %25——这是 % 被再次编码的结果。

原代码中:

function setSafeSrc(element, url) {
  var isSafe = /^(https?|data:image\/[a-z]+;base64|\/)/i.test(url)
  element.src = isSafe ? encodeURI(url) : ''  

当 URL 已经是编码状态(如 %E5%A4%B4%E5%83%8F.png ), encodeURI() 会再次编码,变成了 %25E5%25A4%25B4%25E5%2583%258F.png

编码过程变成了这样:

原始中文:     头像.png
一次编码后:   %E5%A4%B4%E5%83%8F.png         ← 正确的
二次编码后:   %25E5%25A4%25B4%25E5%2583%258F.png  ← % 被编码成 %25,炸了

解决方案

修改位置

文件 : templates/links.html

行号 :第 8-12 行( setSafeSrc 函数)

function setSafeSrc(element, url) {
            // 允许:http/https/data:image 协议 或 相对路径(以/开头)
            var isSafe = /^(https?|data:image\/[a-z]+;base64|\/)/i.test(url)
            element.src = isSafe ? encodeURI(url) : '' // 不安全则清空
          }

修改为:

function setSafeSrc(element, url) {
  // 空值检查:防止 url 为空或 undefined 时报错
  if (!url) { element.src = ''; return; }
  
  // 安全协议检查:只允许 http/https/data:image/相对路径
  var isSafe = /^(https?|data:image\/[a-z]+;base64|\/)/i.test(url);
  if (!isSafe) { element.src = ''; return; }
  
  // 编码检测:检查URL是否已包含 %XX 格式的编码字符
  // 如果已编码则直接使用,避免双重编码导致 400 Bad Request
  // 如果未编码则使用 encodeURI() 处理中文等特殊字符
  element.src = /%[0-9A-Fa-f]{2}/.test(url) ? url : encodeURI(url);
}

修改说明

修改内容 如下:

  1. 增加空值检查

  2. 增加已编码 URL 检测

  3. 条件执行 encodeURI()
    修复效果 :解决了中文头像 URL 无法显示的问题,同时兼容已编码 URL 的情况。

验证修改

  1. 修改保存后,进入后台 Dream2.0 Plus 主题设置

  2. 执行清理模板缓存

  3. 刷新前台友链页面,中文头像正常显示


这个坑的本质是前端编码逻辑没有考虑输入状态。如果 URL 来自用户输入或数据库,可能已经是编码态,也可能是原始态,直接 encodeURI 会埋雷。

判断逻辑很简单: %XX就不编码,没有才编码。搞定!

后记与最终解决方案

CodeQL 安全警告(误报)

在 PR 的时候遇到遇到CodeQL 安全警告,属于是误报。

原因 :CodeQL 扫描器对正则表达式 /%[0-9A-Fa-f]{2}/ 中的 % 符号产生了误判,认为可能存在 HTML 注入风险。

核心思路:先解码再编码

element.src = encodeURI(decodeURI(url));

完整最终代码

function setSafeSrc(element, url) {
    // 空值检查:防止 url 为空或 undefined 时报错
    if (!url) { element.src = ''; return; }

    // 安全协议检查:只允许 http/https/data:image/相对路径
    var isSafe = /^(https?|data:image\/[a-z]+;base64|\/)/i.test(url);
    if (!isSafe) { element.src = ''; return; }

    // 先 decode 再 encode,统一处理已编码和未编码的 URL
    element.src = encodeURI(decodeURI(url));
}