Skip to content

Commit

Permalink
Add WebAPI for managing torrent webseeds
Browse files Browse the repository at this point in the history
  • Loading branch information
Piccirello committed Jul 29, 2024
1 parent 3acd540 commit 7d1b596
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
97 changes: 97 additions & 0 deletions src/webui/api/torrentscontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,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<void (BitTorrent::Torrent *torrent)> &func)
{
if ((idList.size() == 1) && (idList[0] == u"all"))
Expand Down Expand Up @@ -252,6 +254,23 @@ namespace
idList << BitTorrent::TorrentID::fromString(hash);
return idList;
}

nonstd::expected<QUrl, QString> 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 interpreted 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()
Expand Down Expand Up @@ -560,6 +579,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<QUrl> urls;
urls.reserve(paramUrls.size());
for (const QString &urlStr : paramUrls)
{
const auto result = validateWebseedUrl(QUrl::fromPercentEncoding(urlStr.toUtf8()));
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(QUrl::fromPercentEncoding(origUrlStr.toUtf8()));
if (!origUrlResult)
throw APIError(APIErrorType::BadParams, origUrlResult.error());
const QUrl origUrl = origUrlResult.value();

const auto newUrlResult = validateWebseedUrl(QUrl::fromPercentEncoding(newUrlStr.toUtf8()));
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<QUrl> urls;
urls.reserve(paramUrls.size());
for (const QString &urlStr : paramUrls)
{
const auto result = validateWebseedUrl(QUrl::fromPercentEncoding(urlStr.toUtf8()));
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:
Expand Down
3 changes: 3 additions & 0 deletions src/webui/api/torrentscontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ private slots:
void propertiesAction();
void trackersAction();
void webseedsAction();
void addWebseedsAction();
void editWebseedAction();
void removeWebseedsAction();
void filesAction();
void pieceHashesAction();
void pieceStatesAction();
Expand Down
3 changes: 3 additions & 0 deletions src/webui/webapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class WebApplication final : public ApplicationComponent<QObject>
{{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},
Expand All @@ -186,13 +187,15 @@ class WebApplication final : public ApplicationComponent<QObject>
{{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},
{{u"torrents"_s, u"recheck"_s}, Http::METHOD_POST},
{{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},
Expand Down

0 comments on commit 7d1b596

Please sign in to comment.