From 3a7153fc1123f0ce9ae3982f9660385326917229 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Tue, 9 Jul 2024 14:33:00 -0700 Subject: [PATCH] Add WebAPI for managing torrent webseeds --- src/webui/api/torrentscontroller.cpp | 94 ++++++++++++++++++++++++++++ src/webui/api/torrentscontroller.h | 3 + src/webui/webapplication.h | 3 + 3 files changed, 100 insertions(+) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index d5cd8d1e495e..de8c193b10bc 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -135,6 +135,8 @@ namespace using Utils::String::parseInt; using Utils::String::parseDouble; + const QStringList SUPPORTED_WEB_SEED_SCHEMES {u"http"_s, u"https"_s, u"ftp"_s}; + void applyToTorrents(const QStringList &idList, const std::function &func) { if ((idList.size() == 1) && (idList[0] == u"all")) @@ -252,6 +254,20 @@ namespace idList << BitTorrent::TorrentID::fromString(hash); return idList; } + + nonstd::expected validateWebSeedUrl(const QString &urlStr) + { + const QString normalizedUrlStr = QUrl::fromPercentEncoding(urlStr.toLatin1()); + + const QUrl url {normalizedUrlStr, QUrl::StrictMode}; + if (!url.isValid()) + return nonstd::make_unexpected(TorrentsController::tr("\"%1\" is not a valid url").arg(normalizedUrlStr)); + + if (!SUPPORTED_WEB_SEED_SCHEMES.contains(url.scheme())) + return nonstd::make_unexpected(TorrentsController::tr("URL scheme must be one of [%1]").arg(SUPPORTED_WEB_SEED_SCHEMES.join(u","))); + + return url; + } } void TorrentsController::countAction() @@ -560,6 +576,84 @@ void TorrentsController::webseedsAction() setResult(webSeedList); } +void TorrentsController::addWebSeedsAction() +{ + requireParams({u"hash"_s, u"urls"_s}); + const QStringList paramUrls = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); + BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + QList urls; + urls.reserve(paramUrls.size()); + for (const QString &urlStr : paramUrls) + { + const auto result = validateWebSeedUrl(urlStr); + if (!result) + throw APIError(APIErrorType::BadParams, result.error()); + urls << result.value(); + } + + torrent->addUrlSeeds(urls); +} + +void TorrentsController::editWebSeedAction() +{ + requireParams({u"hash"_s, u"origUrl"_s, u"newUrl"_s}); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); + const QString origUrlStr = params()[u"origUrl"_s]; + const QString newUrlStr = params()[u"newUrl"_s]; + + BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + const auto origUrlResult = validateWebSeedUrl(origUrlStr); + if (!origUrlResult) + throw APIError(APIErrorType::BadParams, origUrlResult.error()); + const QUrl origUrl = origUrlResult.value(); + + const auto newUrlResult = validateWebSeedUrl(newUrlStr); + if (!newUrlResult) + throw APIError(APIErrorType::BadParams, newUrlResult.error()); + const QUrl newUrl = newUrlResult.value(); + + if (newUrl != origUrl) + { + if (!torrent->urlSeeds().contains(origUrl)) + throw APIError(APIErrorType::BadParams, tr("\"%1\" is not an existing url").arg(origUrl.toString())); + + torrent->removeUrlSeeds({origUrl}); + torrent->addUrlSeeds({newUrl}); + } +} + +void TorrentsController::removeWebSeedsAction() +{ + requireParams({u"hash"_s, u"urls"_s}); + const QStringList paramUrls = params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); + BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + QList urls; + urls.reserve(paramUrls.size()); + for (const QString &urlStr : paramUrls) + { + const auto result = validateWebSeedUrl(urlStr); + if (!result) + throw APIError(APIErrorType::BadParams, result.error()); + urls << result.value(); + } + + torrent->removeUrlSeeds(urls); +} + // Returns the files in a torrent in JSON format. // The return value is a JSON-formatted list of dictionaries. // The dictionary keys are: diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index d731b0de8f5c..82e6e01a6b4d 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -44,6 +44,9 @@ private slots: void propertiesAction(); void trackersAction(); void webseedsAction(); + void addWebSeedsAction(); + void editWebSeedAction(); + void removeWebSeedsAction(); void filesAction(); void pieceHashesAction(); void pieceStatesAction(); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 4d304934100b..4582cc357a4c 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -177,6 +177,7 @@ class WebApplication final : public ApplicationComponent {{u"torrents"_s, u"addPeers"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"addTags"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"addTrackers"_s}, Http::METHOD_POST}, + {{u"torrents"_s, u"addWebSeeds"_s}, Http::METHOD_POST}, {{u"transfer"_s, u"banPeers"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"bottomPrio"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"createCategory"_s}, Http::METHOD_POST}, @@ -186,6 +187,7 @@ class WebApplication final : public ApplicationComponent {{u"torrents"_s, u"deleteTags"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"editCategory"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"editTracker"_s}, Http::METHOD_POST}, + {{u"torrents"_s, u"editWebSeed"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"filePrio"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"increasePrio"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"reannounce"_s}, Http::METHOD_POST}, @@ -193,6 +195,7 @@ class WebApplication final : public ApplicationComponent {{u"torrents"_s, u"removeCategories"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"removeTags"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"removeTrackers"_s}, Http::METHOD_POST}, + {{u"torrents"_s, u"removeWebSeeds"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"rename"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"renameFile"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"renameFolder"_s}, Http::METHOD_POST},