mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-17 06:01:33 -06:00
WebAPI: Respond with more detailed info
* WebAPI: return error message when endpoint not found * WebAPI: send appropriate status code when logging in * WebAPI: return more info when adding torrents PR #23202. Closes #375. Closes #10688. Closes #10747. Closes #11464.
This commit is contained in:
committed by
GitHub
parent
69b2d7a53e
commit
7ddbf58a3b
@@ -1,5 +1,13 @@
|
||||
# WebAPI Changelog
|
||||
|
||||
## 2.14.0
|
||||
* [#23202](https://github.com/qbittorrent/qBittorrent/pull/23202)
|
||||
* WebAPI responds with the error message "Endpoint does not exist" when the endpoint does not exist, to better differentiate from unrelated Not Found (i.e. 404) responses
|
||||
* `auth/login` endpoint responds to invalid credentials with a 401
|
||||
* `torrents/add` endpoint responds with `success_count`, `pending_count`, `failure_count`, and `added_torrent_ids`
|
||||
* When `pending_count` is non-zero, response code 202 is used
|
||||
* When all torrents fail to be added, response code 409 is used
|
||||
|
||||
## 2.13.1
|
||||
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
|
||||
* `torrents/add` endpoint now supports downloading from a search plugin via the `downloader` parameter
|
||||
|
||||
@@ -59,7 +59,7 @@ APIResult APIController::run(const QString &action, const StringMap ¶ms, con
|
||||
|
||||
const QByteArray methodName = action.toLatin1() + "Action";
|
||||
if (!QMetaObject::invokeMethod(this, methodName.constData()))
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
throw APIError(APIErrorType::NotFound, tr("Endpoint does not exist"));
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
@@ -32,11 +32,12 @@
|
||||
|
||||
enum class APIErrorType
|
||||
{
|
||||
AccessDenied,
|
||||
BadParams,
|
||||
BadData,
|
||||
Conflict,
|
||||
NotFound,
|
||||
AccessDenied,
|
||||
Conflict
|
||||
Unauthorized
|
||||
};
|
||||
|
||||
class APIError : public RuntimeError
|
||||
|
||||
@@ -59,7 +59,7 @@ void AuthController::loginAction()
|
||||
{
|
||||
if (m_sessionManager->session())
|
||||
{
|
||||
setResult(u"Ok."_s);
|
||||
setStatus(APIStatus::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@ void AuthController::loginAction()
|
||||
m_clientFailedLogins.remove(clientAddr);
|
||||
|
||||
m_sessionManager->sessionStart();
|
||||
setResult(u"Ok."_s);
|
||||
setStatus(APIStatus::Ok);
|
||||
LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0)
|
||||
increaseFailedAttempts();
|
||||
setResult(u"Fails."_s);
|
||||
LogMsg(tr("WebAPI login failure. Reason: invalid credentials, attempt count: %1, IP: %2, username: %3")
|
||||
.arg(QString::number(failedAttemptsCount()), clientAddr, usernameFromWeb)
|
||||
, Log::WARNING);
|
||||
throw APIError(APIErrorType::Unauthorized);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1098,7 +1098,10 @@ void TorrentsController::addAction()
|
||||
};
|
||||
|
||||
|
||||
bool partialSuccess = false;
|
||||
int pending = 0;
|
||||
int failure = 0;
|
||||
QList<BitTorrent::TorrentID> addedTorrentIDs;
|
||||
addedTorrentIDs.reserve(urls.size() + torrents.size());
|
||||
for (QString url : urls)
|
||||
{
|
||||
url = url.trimmed();
|
||||
@@ -1123,7 +1126,14 @@ void TorrentsController::addAction()
|
||||
addTorrentParams.filePriorities = filePriorities;
|
||||
}
|
||||
|
||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(torrentDescr, addTorrentParams);
|
||||
if (BitTorrent::Session::instance()->addTorrent(torrentDescr, addTorrentParams))
|
||||
{
|
||||
addedTorrentIDs.append(torrentID);
|
||||
}
|
||||
else
|
||||
{
|
||||
++failure;
|
||||
}
|
||||
}
|
||||
else if (!downloaderParam.isEmpty())
|
||||
{
|
||||
@@ -1137,14 +1147,29 @@ void TorrentsController::addAction()
|
||||
app()->addTorrentManager()->addTorrent(torrentFilePath, addTorrentParams);
|
||||
});
|
||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
||||
partialSuccess = true;
|
||||
|
||||
++pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!filePriorities.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("`filePriorities` may only be specified when metadata has already been fetched"));
|
||||
|
||||
partialSuccess |= app()->addTorrentManager()->addTorrent(url, addTorrentParams);
|
||||
if (app()->addTorrentManager()->addTorrent(url, addTorrentParams))
|
||||
{
|
||||
if (infoHash.isValid())
|
||||
{
|
||||
addedTorrentIDs.append(torrentID);
|
||||
}
|
||||
else
|
||||
{
|
||||
++pending;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++failure;
|
||||
}
|
||||
}
|
||||
m_torrentSourceCache.remove(url);
|
||||
m_torrentMetadataCache.remove(torrentID);
|
||||
@@ -1155,7 +1180,15 @@ void TorrentsController::addAction()
|
||||
{
|
||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(it.value()))
|
||||
{
|
||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(loadResult.value(), addTorrentParams);
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = loadResult.value();
|
||||
if (BitTorrent::Session::instance()->addTorrent(torrentDescr, addTorrentParams))
|
||||
{
|
||||
addedTorrentIDs.append(torrentDescr.infoHash().toTorrentID());
|
||||
}
|
||||
else
|
||||
{
|
||||
++failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1163,10 +1196,25 @@ void TorrentsController::addAction()
|
||||
}
|
||||
}
|
||||
|
||||
if (partialSuccess)
|
||||
setResult(u"Ok."_s);
|
||||
if (!addedTorrentIDs.isEmpty() || (pending > 0))
|
||||
{
|
||||
QJsonArray ids;
|
||||
for (const BitTorrent::TorrentID &torrentID : addedTorrentIDs)
|
||||
ids.append(torrentID.toString());
|
||||
|
||||
setResult(QJsonObject {
|
||||
{u"success_count"_s, addedTorrentIDs.size()},
|
||||
{u"failure_count"_s, failure},
|
||||
{u"pending_count"_s, pending},
|
||||
{u"added_torrent_ids"_s, ids},
|
||||
});
|
||||
if (pending > 0)
|
||||
setStatus(APIStatus::Async);
|
||||
}
|
||||
else
|
||||
setResult(u"Fails."_s);
|
||||
{
|
||||
throw APIError(APIErrorType::Conflict);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentsController::addTrackersAction()
|
||||
|
||||
@@ -418,6 +418,8 @@ void WebApplication::doProcessRequest()
|
||||
throw ConflictHTTPError(error.message());
|
||||
case APIErrorType::NotFound:
|
||||
throw NotFoundHTTPError(error.message());
|
||||
case APIErrorType::Unauthorized:
|
||||
throw UnauthorizedHTTPError(error.message());
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
#include "base/utils/version.h"
|
||||
#include "api/isessionmanager.h"
|
||||
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 13, 1};
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 14, 0};
|
||||
|
||||
class APIController;
|
||||
class AuthController;
|
||||
|
||||
@@ -45,12 +45,12 @@ const submitLoginForm = (event) => {
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
const responseText = await response.text();
|
||||
if (response.ok && (responseText === "Ok.")) {
|
||||
if (response.ok) {
|
||||
location.replace(location); // redirect
|
||||
location.reload(true);
|
||||
}
|
||||
else {
|
||||
const responseText = await response.text();
|
||||
errorMsgElement.textContent = `QBT_TR(Invalid Username or Password.)QBT_TR[CONTEXT=Login]\nQBT_TR(Server response:)QBT_TR[CONTEXT=Login] ${responseText}`;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user