Skip to content

Commit ea06eb9

Browse files
authored
Add WebAPI for managing torrent webseeds
Closes qbittorrent#18465. PR qbittorrent#21043.
1 parent 5afeecb commit ea06eb9

3 files changed

Lines changed: 100 additions & 0 deletions

File tree

src/webui/api/torrentscontroller.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ namespace
135135
using Utils::String::parseInt;
136136
using Utils::String::parseDouble;
137137

138+
const QSet<QString> SUPPORTED_WEB_SEED_SCHEMES {u"http"_s, u"https"_s, u"ftp"_s};
139+
138140
void applyToTorrents(const QStringList &idList, const std::function<void (BitTorrent::Torrent *torrent)> &func)
139141
{
140142
if ((idList.size() == 1) && (idList[0] == u"all"))
@@ -252,6 +254,20 @@ namespace
252254
idList << BitTorrent::TorrentID::fromString(hash);
253255
return idList;
254256
}
257+
258+
nonstd::expected<QUrl, QString> validateWebSeedUrl(const QString &urlStr)
259+
{
260+
const QString normalizedUrlStr = QUrl::fromPercentEncoding(urlStr.toLatin1());
261+
262+
const QUrl url {normalizedUrlStr, QUrl::StrictMode};
263+
if (!url.isValid())
264+
return nonstd::make_unexpected(TorrentsController::tr("\"%1\" is not a valid URL").arg(normalizedUrlStr));
265+
266+
if (!SUPPORTED_WEB_SEED_SCHEMES.contains(url.scheme()))
267+
return nonstd::make_unexpected(TorrentsController::tr("URL scheme must be one of [%1]").arg(SUPPORTED_WEB_SEED_SCHEMES.values().join(u", ")));
268+
269+
return url;
270+
}
255271
}
256272

257273
void TorrentsController::countAction()
@@ -560,6 +576,84 @@ void TorrentsController::webseedsAction()
560576
setResult(webSeedList);
561577
}
562578

579+
void TorrentsController::addWebSeedsAction()
580+
{
581+
requireParams({u"hash"_s, u"urls"_s});
582+
const QStringList paramUrls = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts);
583+
584+
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
585+
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
586+
if (!torrent)
587+
throw APIError(APIErrorType::NotFound);
588+
589+
QList<QUrl> urls;
590+
urls.reserve(paramUrls.size());
591+
for (const QString &urlStr : paramUrls)
592+
{
593+
const auto result = validateWebSeedUrl(urlStr);
594+
if (!result)
595+
throw APIError(APIErrorType::BadParams, result.error());
596+
urls << result.value();
597+
}
598+
599+
torrent->addUrlSeeds(urls);
600+
}
601+
602+
void TorrentsController::editWebSeedAction()
603+
{
604+
requireParams({u"hash"_s, u"origUrl"_s, u"newUrl"_s});
605+
606+
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
607+
const QString origUrlStr = params()[u"origUrl"_s];
608+
const QString newUrlStr = params()[u"newUrl"_s];
609+
610+
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
611+
if (!torrent)
612+
throw APIError(APIErrorType::NotFound);
613+
614+
const auto origUrlResult = validateWebSeedUrl(origUrlStr);
615+
if (!origUrlResult)
616+
throw APIError(APIErrorType::BadParams, origUrlResult.error());
617+
const QUrl origUrl = origUrlResult.value();
618+
619+
const auto newUrlResult = validateWebSeedUrl(newUrlStr);
620+
if (!newUrlResult)
621+
throw APIError(APIErrorType::BadParams, newUrlResult.error());
622+
const QUrl newUrl = newUrlResult.value();
623+
624+
if (newUrl != origUrl)
625+
{
626+
if (!torrent->urlSeeds().contains(origUrl))
627+
throw APIError(APIErrorType::Conflict, tr("\"%1\" is not an existing URL").arg(origUrl.toString()));
628+
629+
torrent->removeUrlSeeds({origUrl});
630+
torrent->addUrlSeeds({newUrl});
631+
}
632+
}
633+
634+
void TorrentsController::removeWebSeedsAction()
635+
{
636+
requireParams({u"hash"_s, u"urls"_s});
637+
const QStringList paramUrls = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts);
638+
639+
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
640+
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
641+
if (!torrent)
642+
throw APIError(APIErrorType::NotFound);
643+
644+
QList<QUrl> urls;
645+
urls.reserve(paramUrls.size());
646+
for (const QString &urlStr : paramUrls)
647+
{
648+
const auto result = validateWebSeedUrl(urlStr);
649+
if (!result)
650+
throw APIError(APIErrorType::BadParams, result.error());
651+
urls << result.value();
652+
}
653+
654+
torrent->removeUrlSeeds(urls);
655+
}
656+
563657
// Returns the files in a torrent in JSON format.
564658
// The return value is a JSON-formatted list of dictionaries.
565659
// The dictionary keys are:

src/webui/api/torrentscontroller.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ private slots:
4444
void propertiesAction();
4545
void trackersAction();
4646
void webseedsAction();
47+
void addWebSeedsAction();
48+
void editWebSeedAction();
49+
void removeWebSeedsAction();
4750
void filesAction();
4851
void pieceHashesAction();
4952
void pieceStatesAction();

src/webui/webapplication.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class WebApplication final : public ApplicationComponent<QObject>
177177
{{u"torrents"_s, u"addPeers"_s}, Http::METHOD_POST},
178178
{{u"torrents"_s, u"addTags"_s}, Http::METHOD_POST},
179179
{{u"torrents"_s, u"addTrackers"_s}, Http::METHOD_POST},
180+
{{u"torrents"_s, u"addWebSeeds"_s}, Http::METHOD_POST},
180181
{{u"transfer"_s, u"banPeers"_s}, Http::METHOD_POST},
181182
{{u"torrents"_s, u"bottomPrio"_s}, Http::METHOD_POST},
182183
{{u"torrents"_s, u"createCategory"_s}, Http::METHOD_POST},
@@ -186,13 +187,15 @@ class WebApplication final : public ApplicationComponent<QObject>
186187
{{u"torrents"_s, u"deleteTags"_s}, Http::METHOD_POST},
187188
{{u"torrents"_s, u"editCategory"_s}, Http::METHOD_POST},
188189
{{u"torrents"_s, u"editTracker"_s}, Http::METHOD_POST},
190+
{{u"torrents"_s, u"editWebSeed"_s}, Http::METHOD_POST},
189191
{{u"torrents"_s, u"filePrio"_s}, Http::METHOD_POST},
190192
{{u"torrents"_s, u"increasePrio"_s}, Http::METHOD_POST},
191193
{{u"torrents"_s, u"reannounce"_s}, Http::METHOD_POST},
192194
{{u"torrents"_s, u"recheck"_s}, Http::METHOD_POST},
193195
{{u"torrents"_s, u"removeCategories"_s}, Http::METHOD_POST},
194196
{{u"torrents"_s, u"removeTags"_s}, Http::METHOD_POST},
195197
{{u"torrents"_s, u"removeTrackers"_s}, Http::METHOD_POST},
198+
{{u"torrents"_s, u"removeWebSeeds"_s}, Http::METHOD_POST},
196199
{{u"torrents"_s, u"rename"_s}, Http::METHOD_POST},
197200
{{u"torrents"_s, u"renameFile"_s}, Http::METHOD_POST},
198201
{{u"torrents"_s, u"renameFolder"_s}, Http::METHOD_POST},

0 commit comments

Comments
 (0)