diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 0ad56b0941c6..9cdc0bc9d15d 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -133,6 +133,8 @@ namespace using Utils::String::parseInt; using Utils::String::parseDouble; + const QStringList supportedWebseedSchemes{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")) @@ -250,6 +252,23 @@ namespace idList << BitTorrent::TorrentID::fromString(hash); return idList; } + + nonstd::expected validateWebseedUrl(const QString &urlStr) + { + // sanity check before parsing to prevent QUrl from checking for the existence of files on the filesystem + if (!supportedWebseedSchemes.contains(urlStr.section(u"://"_s, 0, 0))) + return nonstd::make_unexpected(TorrentsController::tr("URL scheme must be one of [%1]").arg(supportedWebseedSchemes.join(u","))); + + const QUrl url(urlStr, QUrl::StrictMode); + if (!url.isValid()) + return nonstd::make_unexpected(TorrentsController::tr("\"%1\" is not a valid url").arg(urlStr)); + + // sanity check after parsing in case QUrl interpereted a different scheme than intended + if (!supportedWebseedSchemes.contains(url.scheme())) + return nonstd::make_unexpected(TorrentsController::tr("URL scheme must be one of [%1]").arg(supportedWebseedSchemes.join(u","))); + + return url; + } } void TorrentsController::countAction() @@ -555,6 +574,88 @@ 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); + setStatus(204); +} + +void TorrentsController::editWebseedsAction() +{ + 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}); + } + + setStatus(204); +} + +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); + setStatus(204); +} + // 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..85adf6e00b96 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 editWebseedsAction(); + void removeWebseedsAction(); void filesAction(); void pieceHashesAction(); void pieceStatesAction(); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 9465195f6fc9..9075fcc98e8e 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"editWebseeds"_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},