Skip to content

feat(offline-download): support torrent uploads for qBittorrent PT - #2676

Open
ctaoist wants to merge 8 commits into
OpenListTeam:mainfrom
ctaoist:pt
Open

feat(offline-download): support torrent uploads for qBittorrent PT#2676
ctaoist wants to merge 8 commits into
OpenListTeam:mainfrom
ctaoist:pt

Conversation

@ctaoist

@ctaoist ctaoist commented Jun 29, 2026

Copy link
Copy Markdown

Summary / 摘要

离线下载时,直接传递种子文件到qBittorrent,以支持PT下载。

  • 在上传种子页面添加删除策略:delete_after_seeding,即等待做种结束再删除

  • 修改删除策略:从不删除,改完后,后端针对从不删除策略,不再在 qbittorrent 中删除对应的任务,更符合PT场景。

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。

  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。

  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository PRs / 关联仓库 PR:

Testing / 测试

  • go test ./...
  • Manual test / 手动测试:
    • 编译前端和后端
    • 上传PT种子可以正常下载
    • 做种时间到时删除临时下载文件和对应的任务
    • 后端不再删除从不删除策略的任务

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmtgo fmtprettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明):

Usage scope / 使用范围:

  • Code generation / 代码生成

  • Refactoring / 重构

  • Documentation / 文档

  • Tests / 测试

  • Translation / 翻译

  • Review assistance / 审查辅助

  • I have reviewed and validated all AI-assisted content included in this PR.
    / 我已审核并验证此 PR 中的所有 AI 辅助内容。

  • I have ensured that all AI-assisted commits include Co-Authored-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

ctaoist added 2 commits June 29, 2026 20:28
- Accept torrent_data in offline download requests and validate uploaded torrent payloads.
- Route uploaded torrent data and detected HTTP torrent files through qBittorrent's torrent upload API.
- Add delete_after_seeding handling for seeding-capable offline download tools.
- Improve qBittorrent add-task response handling for 2xx and non-2xx responses.
- Add qBittorrent client tests for torrent uploads, link adds, and error reporting.
- Skip seeding task removal when the delete policy is set to delete_never.
- Reuse the delete-policy guard for qBittorrent and Transmission seeding cleanup.
Comment thread server/handles/offline_download.go Outdated
}
meta := strings.ToLower(encoded[:comma])
if !strings.Contains(meta, ";base64") {
return nil, "", fmt.Errorf("torrent data URL 必须使用 base64 编码")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

请使用英文输出便于LQA翻译

@ctaoist ctaoist Jul 1, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改,dfad1fa

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the offline download pipeline to support uploading .torrent files to qBittorrent (to better support PT scenarios), and adds a new deletion policy that defers cleanup until seeding is complete.

Changes:

  • Accept torrent_data (base64 / data URL) in the offline download request and pass decoded torrent bytes into the offline-download tool layer.
  • Extend the qBittorrent client/tool to add tasks from uploaded torrent data (and improve add-task error reporting / status progress handling).
  • Introduce delete_after_seeding and propagate a delete_after_time timestamp through download/transfer tasks to defer temp cleanup until seeding finishes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/handles/offline_download.go Adds torrent_data request parsing + torrent base64 decoding/validation and task creation using uploaded torrent bytes.
pkg/qbittorrent/client.go Adds AddFromTorrent, refactors “add torrent” request building, and improves non-2xx error reporting.
pkg/qbittorrent/client_test.go Adds tests for link vs torrent upload behavior and non-2xx error handling.
internal/offline_download/tool/add.go Adds DeleteAfterSeeding, threads TorrentData through AddURL, and gates torrent upload to qBittorrent.
internal/offline_download/tool/base.go Extends AddUrlArgs to include TorrentData.
internal/offline_download/tool/download.go Propagates torrent data to tools; adds seeding-time deletion scheduling via DeleteAfterTime.
internal/offline_download/tool/transfer.go Adds DeleteAfterTime and defers temp cleanup when policy is delete_after_seeding.
internal/offline_download/qbit/qbit.go Implements torrent-upload support in qBittorrent tool (upload bytes or fetch+upload http(s) torrent), and fixes progress calculation for size==0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +23
addCalled = true
if err := r.ParseMultipartForm(1024 * 1024); err != nil {
t.Fatalf("ParseMultipartForm() error = %v", err)
}
Comment on lines +84 to +87
addCalled = true
if err := r.ParseMultipartForm(1024 * 1024); err != nil {
t.Fatalf("ParseMultipartForm() error = %v", err)
}
Comment on lines +122 to +124
default:
t.Fatalf("unexpected path %s", r.URL.Path)
}
Comment on lines 119 to 123
t.Status = "offline download completed, maybe transferring"
// hack for qBittorrent
if t.tool.Name() == "qBittorrent" {
seedTime := setting.GetInt(conf.QbittorrentSeedtime, 0)
if seedTime >= 0 {
if seedDuration, ok := t.seedingDuration(); ok {
t.Status = "offline download completed, waiting for seeding"
- Replace Chinese torrent data validation errors with English messages.
- Remove the accidentally committed qBittorrent client test from Git tracking.
- Map 115 Open error code 430004 to object-not-found.
- Create destination directories before enqueueing child transfer tasks.
- Use the save path reported by qBittorrent for existing downloads.
- Add centralized regression tests for 115 Open and qBittorrent behavior.
Track cleanup state across download, transfer, and seeding phases. Delay temporary-file removal until transfers complete, preserve active paths during startup cleanup, restore pending jobs, and cover failure and retry flows with regression tests.
- Integrate the latest main updates, including 115 Open path recovery and SDK upgrades.
- Preserve torrent submissions and the persistent offline cleanup lifecycle.
- Combine native ED2K routing and GuangYaPan handling with the custom download flow.

@pikachuren pikachuren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @ctaoist 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。
⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出决策。

🎯 结论

🔄 Request Changes — 功能实用且已有基础防护,但从 URL 抓取种子存在 SSRF 风险,需要收紧

📖 概要

feat(offline-download): support torrent uploads for qBittorrent PT · 支持上传种子文件做离线下载(PT 站场景)。
核心改动:AddURLArgs 新增 TorrentData 字段,qBittorrent 走 AddFromTorrent;新增从 URL 自动抓取种子的能力;新增 DeleteAfterSeeding 删除策略。

🧭 整体方案

技术路线是「优先用种子数据、退化到磁力链接」:AddURL 依次尝试 TorrentData → 从 URL 抓取的种子 → 原有 link 方式。针对 PT 站必须用种子文件(含 passkey)而非磁力的场景,这个功能确实有实际价值。实现里已经有几处不错的防护:maxQbittorrentTorrentSize 限制 10MB、用 io.LimitReader 而非无限读取、抓取后用 torrent.Decode 校验确实是合法种子、非 qBittorrent 工具显式报错。主要顾虑是 fetchTorrentDataFromURL 的服务端请求缺乏目标地址限制。

📊 变更统计

7 个文件(+314 / -71 行) | 功能 ⭐⭐⭐⭐ | 最小改动 ⭐⭐⭐ | 前向兼容 ⭐⭐⭐⭐ | 方案设计 ⭐⭐⭐

🚨 关键问题

P0(阻塞合并)

  • ⚠️ internal/offline_download/qbit/qbit.go:fetchTorrentDataFromURL — 该函数会用服务端身份向用户提供的任意 http/https URL 发起 GET 请求,构成 SSRF:用户可以填入 http://127.0.0.1:8080/...http://169.254.169.254/latest/meta-data/(云元数据)或内网地址,借服务端探测内网。虽然响应内容会经 torrent.Decode 校验、失败即静默返回 false,限制了数据回显,但响应时间差异仍可用于端口/存活探测。请问是否考虑加上目标地址校验呢?例如:
// 解析后校验 IP 不落在私有/环回/链路本地网段
ips, err := net.DefaultResolver.LookupIPAddr(args.Ctx, u.Hostname())
if err != nil {
    return nil, false
}
for _, ip := range ips {
    if ip.IP.IsLoopback() || ip.IP.IsPrivate() || ip.IP.IsLinkLocalUnicast() {
        return nil, false
    }
}

另外建议为该请求设置独立超时,避免慢速目标长期占用连接。

P1(建议修复)

  • ⚠️ 同函数 — 所有失败路径都返回 (nil, false)静默退化AddFromLink。用户上传了一个 PT 种子 URL,若抓取失败会变成用磁力方式添加(PT 场景下通常会失败或不计流量),而用户看不到任何原因。是否考虑区分「不是种子 URL,正常退化」与「是种子但抓取/解析失败,应当报错」呢?
  • ⚠️ AddURL 中对非 qBittorrent 工具返回错误,但错误信息里硬编码了工具名 "qBittorrent" 字符串。若将来支持更多工具需同步修改,是否考虑改为能力查询(例如 tool.SupportsTorrentData())?

P2(可选)

  • 💡 新增 DeleteAfterSeeding 删除策略与 PT 做种需求契合,方向很好。建议在文档中说明它与现有 DeleteOnUploadSucceed 等策略的区别与优先级~
  • 💡 qbit.goelse { s.Progress = info.Progress * 100 } 补充了 Size 为 0 时的进度回退,是个好改进。不过这与 #2622#2581 在同文件有重叠改动,合并时会冲突,建议维护者留意顺序~
  • 💡 10MB 的种子大小上限对绝大多数场景够用,但超大型种子(数万文件)可能超限。是否考虑提为配置项?

📂 逐文件分析

internal/offline_download/qbit/qbit.go

改动意图:支持用种子数据添加任务。
代码逻辑AddURL 三级降级;fetchTorrentDataFromURL 校验 scheme → 发起 GET → 校验状态码 → 限长读取 → torrent.Decode 验证。
问题分析:大小限制、格式校验、状态码检查都做到位了;核心缺口是缺少目标地址校验(P0)与静默降级(P1)。

internal/offline_download/tool/add.go

改动意图:在通用层承载种子数据并做工具能力校验。
问题分析len(args.TorrentData) == 0 的前置判断避免了种子数据被 SimpleHttp / ed2k 分支误处理,考虑周到;工具名硬编码可优化(P1)。

✅ 待处理清单

  • [P0] 为 fetchTorrentDataFromURL 增加目标地址校验(禁止环回/私有/链路本地)与独立超时
  • [P1] 区分「正常退化」与「种子抓取失败」,后者应明确报错
  • [P1] 用能力查询替代硬编码工具名
  • [P2] 文档说明 DeleteAfterSeeding 语义
  • [P2] 与 #2622 / #2581 协调 qbit.go 合并顺序

🎯 结论:🔄 Request Changes — 功能有价值、已有大小与格式防护,但服务端代抓 URL 必须先补上 SSRF 防护。

- Restrict remote torrent fetching to public addresses and pin validated DNS results.
- Validate redirects, disable proxy use, and reject unsafe schemes without qBittorrent fallback.
- Add regression tests for private addresses, DNS rebinding protection, and magnet compatibility.
- Add optional capability declarations with zero-value defaults for offline download tools.
- Let qBittorrent advertise torrent data support and remove hardcoded tool-name checks.
- Cover default and qBittorrent capabilities with regression tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants