[Bug Report] deepin 下载器无法识别 GitHub Release 链接的文件大小无法勾选下载
Tofloor
poster avatar
柒玖
deepin
2026-09-28 21:30
Author

deepin 下载器无法识别 GitHub Release 链接的文件大小、无法勾选下载问题排查

  • 日期 / Date:2026-09-28
  • 环境:deepin 25(crimson,25.2.2,amd64,X11 会话),dde-shell 运行中;内核 6.18.48-amd64-desktop-rolling;Intel N150 + Alder Lake-N 核显,内存 15GiB
  • 被排查对象:org.deepin.downloader 6.0.26(deepin 下载器,aria2 + Qt6 前端);源码取自 https://github.com/linuxdeepin/deepin-downloader(已核对 6.0.26 tag 与 master,相关文件完全一致)
  • 问题定位:新建下载任务 → 粘贴 GitHub Release 资产链接后的解析环节(UrlThread::onHttpRequest 的 case 200 分支内 curl 子进程调用)
  • 说明:⚠️ 本 issue 的现象、复现步骤与对照实验由实际测试确认;根因分析由 AI 辅助完成(基于公开源码推断 + 本机模拟验证),未经下载器开发环境实际调试确认,实际结论仅供参考,请以官方排查为准。可靠的部分:① GitHub Release 链接解析后 Size 列为空白、条目灰化不可用、无法勾选下载——实测确认存在;② Gitee 同类链接正常——实测确认;③ 网络侧(302/200/content-length)与 Qt HEAD 环节正常——实测确认。其余因果链推断仅供参考。
  • 结论:(现象实测确认,根因分析由 AI 辅助完成、仅供参考,见上方「说明」)粘贴 GitHub Release 链接(.deb/.zip/.sh 等任意资产)能识别出文件名出现在任务列表,但 Size 列始终为空白、条目整体灰化呈不可用状态,无法勾选、「确定」按钮置灰。AI 辅助根因推断:GitHub 302 跳转的 Location 中,response-content-disposition=attachment%3B%20filename%3D... 里的 %20(空格编码)在 Qt reply->url().toString() 回读时被解码成真实空格;下载器随后把该含空格 URL 传给 curl 获取真实头信息,而代码里的空格转义 list[1].replace(" ", "%20") 被错误地包在 if(list.size() > 2) 中——参数列表恰好只有 2 个元素,转义永远不执行;QProcess 不经 shell 直传参数,curl 收到含空格 URL 直接报 curl: (3) URL rejected: Malformed input to a URL function(exit 3、零输出)。curl 零输出时 readyReadStandardOutput 回调不触发、解析结果不再回传 UI,任务行停留在初始占位状态(size 为空、length=0)→ 界面按"size 为空"渲染为灰化不可用行,且该行不参与选中统计(各类勾选回调均要求 length > 0 才置选中),cnt 恒为 0,「确定」按钮 setEnabled(cnt > 0) 永远禁用。GitHub 所有 Release 资产的 disposition 均采用 attachment%3B%20filename%3D 统一格式,因此 .deb/.zip/.sh 全部命中,与实测吻合。

1. 问题描述

1.1 现象

在 deepin 下载器中新建下载任务,粘贴 GitHub Release 资产直链:

https://github.com/DibbayajyotiRoy/Fresco/releases/download/v1.1.45/fresco_1.1.45-1_amd64.deb
https://github.com/DibbayajyotiRoy/Fresco/releases/download/v1.1.45/install.sh
(.zip 等其它格式资产同样复现)
  • 链接能被识别:任务列表出现条目,文件名解析正常(文件名来自 Qt 回调链自身,不依赖 curl);
  • 大小无法识别:Size 列始终为空白(不是 "0KB",是完全没有内容);
  • 条目灰化不可用:整行以半透明灰化样式渲染(图标与文字均变暗),呈不可用状态;
  • 无法选中下载:复选框勾选无效,选中计数恒为"已选0个文件,共0B",「确定」按钮保持置灰。

📷 截图:
deepin下载器    -github链接无法识别.png

deepin下载器 -github链接无法识别.png——新建下载任务对话框,条目 rustdesk-1.5.0-x86_64 整行灰化、类型与大小两列空白、"已选0个文件,共0B"、确定按钮置灰

1.2 网络侧排查(已排除网络/链接因素)

$ curl -sIL 
HTTP/2 302                          ← github.com 正常 302(Location 含签名参数)
HTTP/2 200                          ← 真实资产链接正常 200
content-disposition: attachment; filename=fresco_1.1.45-1_amd64.deb
content-type: application/octet-stream
content-length: 5317964
accept-ranges: bytes

服务端 302/200/content-length/accept-ranges 全部正常,问题在下载器自身解析逻辑。用 Qt6 复刻 UrlThread::begin()(manager->head())实测:Qt 能正常跟随 302 并在 finished 回调中拿到 200 + content-length: 5317964 + content-disposition —— Qt 环节本身也是好的,断点出在拿到头信息后调用的 curl 子进程。

1.3 对照实验:Gitee 同类链接正常

用 Gitee 上同一类"Release 资产直链"做对照(可正常识别大小并勾选下载):

$ curl -sIL "https://gitee.com/ffishh/rustdesk/releases/download/1.4.4/rustdesk-1.4.4-x86_64.deb"
HTTP/1.1 302 Found
Location: https://gitee.com/ffishh/rustdesk/attach_files/2550453/download/rustdesk-1.4.4-x86_64.deb
HTTP/1.1 302 Found
Location: https://foruda.gitee.com/attach_file/.../rustdesk-1.4.4-x86_64.deb?token=...&ts=...&attname=rustdesk-1.4.4-x86_64.deb
HTTP/1.1 200 OK
content-type: application/vnd.debian.binary-package
content-length: 22556076
content-disposition: attachment; filename="rustdesk-1.4.4-x86_64.deb"
环节 Gitee(✅ 正常) GitHub(❌ 失败)
302 目标 URL 查询串 attname=rustdesk-...deb —— 无 %20(文件名放在 attname= 参数且不含空格) response-content-disposition=attachment%3B%20filename%3D... —— 含 %20
Qt toString() 回读后 URL 无空格 → curl 正常执行 URL 含真实空格 → curl exit 3 拒绝
最终 content-type application/vnd.debian.binary-package(content-type.conf 有映射) application/octet-stream(无映射,type 落 "html")
结果 大小/类型解析正常、可勾选下载 Size 空白、行灰化、不可勾选、确定按钮置灰

Gitee 的跳转链中根本不存在 %20,即使下载器存在空格转义 bug 也不会被触发;GitHub 把 attachment; filename=(含空格)整体编码为 response-content-disposition 参数,%20 必然出现。同一个 bug,Gitee 的链接格式绕过了它,GitHub 的链接格式必然踩中——这也解释了为什么失败与 .deb/.zip/.sh 文件类型无关。另外注意:即使修复空格问题,GitHub 的 application/octet-stream 仍会因 content-type.conf 无映射而使 type 判定为 "html"(见 1.4 后续解析与第 3 节兜底建议)。

1.4 源码定位与实证复现

(src/src/ui/createTask/urlthread.cpp,6.0.26 与 master 一致)

① Qt 拿到的重定向 URL 含真实空格

GitHub 302 的 Location 中,文件名字段是合法编码的:

response-content-disposition=attachment%3B%20filename%3Dfresco_1.1.45-1_amd64.deb
                                                      ^^ %20 = 空格

Qt 的 QNetworkAccessManager 自动跟随 302,reply->url().toString() 回读时会把 %20 解码成真实空格(Qt6 测试程序实测输出):

qt:   response-content-disposition=attachment%3B filename%3Dfresco_1.1.45-1_amd64.deb
                                              ^^ 空格

② 空格转义被 if(list.size() > 2) 永久短路

case 200 分支把该 URL 交给 curl 获取真实头信息,并"贴心地"处理了空格——但条件写错了:

QStringList list;
list << "-I" << reply->url().toString();
if(list.size() > 2){                       // ← list 恒为 2 个元素,永不成立
    list[1].replace(" ", "%20");           // 空格转义永远不执行
}
process->start("curl", list);              // QProcess 直传参数,不经 shell

list 只有 "-I" 和 URL 两个元素,if(list.size() > 2) 永远为假;即使条件成立,replace 也只把空格改回 %20,属于"先解码再转义"的无谓往返。QProcess 不经 shell,curl 收到含真实空格的单个参数:

$ curl -I "https://release-assets.githubusercontent.com/...attachment%3B filename%3D..."
curl: (3) URL rejected: Malformed input to a URL function     ← exit 3, stdout 为空

实测确认(python subprocess 直传参数复刻 QProcess 行为):exit 3、stdout 零字节。 readyReadStandardOutput 永不触发 → getUrlSize()/getUrlType()/文件名解析全部没有输入。

③ 失败后的表现

  • 由于 curl 零输出、回调不触发、sendFinishedUrl 不再发出,任务行停留在初始占位状态(size=""、length=0),界面上 Size 列为空白;注意 case 200 分支里那个把空大小强制置 "0KB" 的兜底(urlthread.cpp 第 108 行附近)在此路径下根本走不到——所以实测看到的是空白而不是 "0KB";
  • TaskDelegate::paint() 中当 size.isEmpty() 时,行图标与文字按半透明/暗色绘制(setOpacity(0.4)、QColor(192,198,212,70)),即截图中看到的灰化不可用样式;
  • 文件名能显示是因为粘贴链接时 getUrlToName() 直接从 URL 路径取末段(含 .deb 后缀),不依赖解析线程的结果。

④ size 为空/length=0 直接导致无法勾选、确定按钮置灰

createtaskwidget.cpp 中所有勾选回调(onAllCheck、onVideoCheck、onZipCheck……)与 updateSelectedInfo() 对行置选中的统一前提是:

if (m_model->data(m_model->index(i, 4),4).toString().toLong() > 0) {   // length > 0
    m_model->setData(m_model->index(i, 0), state == Qt::Checked ? "1" : "0");
}

该行 length=0 → 永远不被置选中 → cnt 恒为 0 → m_sureButton->setEnabled(cnt > 0 ? true : false) 「确定」按钮永远置灰。length=0 的行还被各类统计(video/zip/doc…)按类型过滤后完全忽略,单行手动勾选同样不计入。

1.5 影响范围

  • GitHub 所有 Release 资产:disposition 统一为 attachment%3B%20filename%3D(已实测 .deb 与 .sh 的 302 输出一致),全部无法获取大小与下载;
  • 推而广之,任何"302 跳转目标 URL 的查询参数中含 %20"的站点(部分 CDN/网盘直链)都会以同样方式失败;
  • 与 .deb/.zip/.sh 的文件类型无关——失败发生在拿到类型信息之前。

2. 复现步骤

  1. 打开 deepin 下载器 → 新建下载任务;
  2. 粘贴任一 GitHub Release 资产链接(如上述 .deb 或 install.sh),开始解析;
  3. 任务列表出现条目:文件名正常,但类型与大小两列空白、整行灰化(实测截图见 1.1 占位说明);
  4. 勾选该行(或点「全部」)——选中计数不变("已选0个文件,共0B"),「确定」按钮保持置灰,无法开始下载。

3. 结论与建议

⚠️ 声明:问题本身(Size 空白、行灰化、无法勾选下载、Gitee 正常)实测确认存在;以下根因推断与修复建议由 AI 辅助完成,仅供参考,请以官方排查为准。

  • 现象确定、根因实证:GitHub Release 链接失败不是因为服务器或 Qt,而是 下载器在 case 200 分支把 Qt 解码后含真实空格的重定向 URL 未加转义直接传给 curl,if(list.size() > 2) 的错误条件使空格转义永远不生效,curl 以 exit 3 拒绝该 URL,后续大小/类型/选中统计全部连锁失效;
  • 建议修复方向(供参考,实现方式由您决定):
    1. 核心修复:把空格转义移出 if(list.size() > 2)(或直接删除该 if)——对 URL 参数调用 QUrl::toPercentEncoding/QUrl::toString(QUrl::FullyEncoded) 后再传 curl,杜绝"解码后再裸传";
    2. 更彻底的做法是不再用 curl 子进程:Qt 的 reply->rawHeaderPairs() 已含全部所需头信息(content-length/content-type/content-disposition 实测齐全),直接在 Qt 侧解析即可,少一次进程调用也少一个失败点;
    3. 兜底增强:content-type.conf 增加 application/octet-stream 的处理(回退到 Content-Disposition 文件名后缀或原 URL 后缀,而不是 "html");文件名解析兼容 attachment; filename=...(filename 前带空格)与 filename*=UTF-8''...;curl 失败(非零退出)时重试一次或改用 GET + Range: bytes=0-0(206 + Content-Range),不要静默落回 "0KB";
    4. UI 兜底:type 判为 "html"/length=0 的行不应被勾选与下载逻辑完全排除——允许用户手动强制勾选下载(aria2 下载阶段并不依赖解析阶段的 length)。
Reply Favorite View the author
All Replies
avatar
𰻞𰻝面
deepin
2026-09-28 21:41
#1

国家政策规定,没办法,你给前面加个代理就行,或者DNS改成8.8.8.8

Reply View the author
avatar
柒玖
deepin
2026-09-28 21:46
#2
𰻞𰻝面:

国家政策规定,没办法,你给前面加个代理就行,或者DNS改成8.8.8.8

这也要限制吗,那行吧😞

Reply View the author
avatar
𰻞𰻝面
deepin
2026-09-28 22:21
#3
柒玖:

这也要限制吗,那行吧😞

原则是不让用github,但没墙,只是DNS污染,让一般人连不上,改DNS就可以正常访问。
Screenshot_20260928-222012.png

Reply View the author
avatar
柒玖
deepin
2026-09-29 08:06
#4
𰻞𰻝面:

原则是不让用github,但没墙,只是DNS污染,让一般人连不上,改DNS就可以正常访问。
Screenshot_20260928-222012.png

想访问还是挺简单的,主要是我看deepin的下载器界面挺简约的,想用一下结果真要用到的场景用不了。。。。

Reply View the author
avatar
𰻞𰻝面
deepin
2026-09-29 08:27
#5
柒玖:

想访问还是挺简单的,主要是我看deepin的下载器界面挺简约的,想用一下结果真要用到的场景用不了。。。。

改DNS就能访问,但运营商默认的DNS是不行的
Screenshot_20260929-082640.png

Reply View the author
avatar
柒玖
deepin
2026-09-29 08:51
#6

image.png

偶尔也能直连,不能直连的时候我都用watt toolkit的,能随时开关,主要是有时候就是github不稳定想直接下载点什么东西确实太慢了,用些类似motrix的多线程下载器下起来确实更快一些

Reply View the author
avatar
𰻞𰻝面
deepin
2026-09-29 19:27
#7
柒玖:

image.png

偶尔也能直连,不能直连的时候我都用watt toolkit的,能随时开关,主要是有时候就是github不稳定想直接下载点什么东西确实太慢了,用些类似motrix的多线程下载器下起来确实更快一些

下载时给地址前面加上代理就行,

比如你要下载 https://github.com/AAA/BBB/CCC.xyz

可以改成这样 https://gh-proxy.com/github.com/AAA/BBB/CCC.xyz

我这可以70MB/s,同样适用 git clone、git pull、git submodule update等

Reply View the author
avatar
柒玖
deepin
2026-09-30 08:14
#8
𰻞𰻝面:

下载时给地址前面加上代理就行,

比如你要下载 https://github.com/AAA/BBB/CCC.xyz

可以改成这样 https://gh-proxy.com/github.com/AAA/BBB/CCC.xyz

我这可以70MB/s,同样适用 git clone、git pull、git submodule update等

好的,很实用,感谢帮助kissing_heart

Reply View the author
avatar
牧野
deepin
2026-09-30 10:41
#9

就是因为这个原因,直接把deepin的下载器直接卸载了。用motrixnext比deepin的下载器更好用。

Reply View the author