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
|
# 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
|
## 2.13.1
|
||||||
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
|
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
|
||||||
* `torrents/add` endpoint now supports downloading from a search plugin via the `downloader` parameter
|
* `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";
|
const QByteArray methodName = action.toLatin1() + "Action";
|
||||||
if (!QMetaObject::invokeMethod(this, methodName.constData()))
|
if (!QMetaObject::invokeMethod(this, methodName.constData()))
|
||||||
throw APIError(APIErrorType::NotFound);
|
throw APIError(APIErrorType::NotFound, tr("Endpoint does not exist"));
|
||||||
|
|
||||||
return m_result;
|
return m_result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,12 @@
|
|||||||
|
|
||||||
enum class APIErrorType
|
enum class APIErrorType
|
||||||
{
|
{
|
||||||
|
AccessDenied,
|
||||||
BadParams,
|
BadParams,
|
||||||
BadData,
|
BadData,
|
||||||
|
Conflict,
|
||||||
NotFound,
|
NotFound,
|
||||||
AccessDenied,
|
Unauthorized
|
||||||
Conflict
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class APIError : public RuntimeError
|
class APIError : public RuntimeError
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void AuthController::loginAction()
|
|||||||
{
|
{
|
||||||
if (m_sessionManager->session())
|
if (m_sessionManager->session())
|
||||||
{
|
{
|
||||||
setResult(u"Ok."_s);
|
setStatus(APIStatus::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +84,17 @@ void AuthController::loginAction()
|
|||||||
m_clientFailedLogins.remove(clientAddr);
|
m_clientFailedLogins.remove(clientAddr);
|
||||||
|
|
||||||
m_sessionManager->sessionStart();
|
m_sessionManager->sessionStart();
|
||||||
setResult(u"Ok."_s);
|
setStatus(APIStatus::Ok);
|
||||||
LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr));
|
LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0)
|
if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0)
|
||||||
increaseFailedAttempts();
|
increaseFailedAttempts();
|
||||||
setResult(u"Fails."_s);
|
|
||||||
LogMsg(tr("WebAPI login failure. Reason: invalid credentials, attempt count: %1, IP: %2, username: %3")
|
LogMsg(tr("WebAPI login failure. Reason: invalid credentials, attempt count: %1, IP: %2, username: %3")
|
||||||
.arg(QString::number(failedAttemptsCount()), clientAddr, usernameFromWeb)
|
.arg(QString::number(failedAttemptsCount()), clientAddr, usernameFromWeb)
|
||||||
, Log::WARNING);
|
, 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)
|
for (QString url : urls)
|
||||||
{
|
{
|
||||||
url = url.trimmed();
|
url = url.trimmed();
|
||||||
@@ -1123,7 +1126,14 @@ void TorrentsController::addAction()
|
|||||||
addTorrentParams.filePriorities = filePriorities;
|
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())
|
else if (!downloaderParam.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -1137,14 +1147,29 @@ void TorrentsController::addAction()
|
|||||||
app()->addTorrentManager()->addTorrent(torrentFilePath, addTorrentParams);
|
app()->addTorrentManager()->addTorrent(torrentFilePath, addTorrentParams);
|
||||||
});
|
});
|
||||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
||||||
partialSuccess = true;
|
|
||||||
|
++pending;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!filePriorities.isEmpty())
|
if (!filePriorities.isEmpty())
|
||||||
throw APIError(APIErrorType::BadParams, tr("`filePriorities` may only be specified when metadata has already been fetched"));
|
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_torrentSourceCache.remove(url);
|
||||||
m_torrentMetadataCache.remove(torrentID);
|
m_torrentMetadataCache.remove(torrentID);
|
||||||
@@ -1155,7 +1180,15 @@ void TorrentsController::addAction()
|
|||||||
{
|
{
|
||||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(it.value()))
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -1163,10 +1196,25 @@ void TorrentsController::addAction()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partialSuccess)
|
if (!addedTorrentIDs.isEmpty() || (pending > 0))
|
||||||
setResult(u"Ok."_s);
|
{
|
||||||
|
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
|
else
|
||||||
setResult(u"Fails."_s);
|
{
|
||||||
|
throw APIError(APIErrorType::Conflict);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentsController::addTrackersAction()
|
void TorrentsController::addTrackersAction()
|
||||||
|
|||||||
@@ -418,6 +418,8 @@ void WebApplication::doProcessRequest()
|
|||||||
throw ConflictHTTPError(error.message());
|
throw ConflictHTTPError(error.message());
|
||||||
case APIErrorType::NotFound:
|
case APIErrorType::NotFound:
|
||||||
throw NotFoundHTTPError(error.message());
|
throw NotFoundHTTPError(error.message());
|
||||||
|
case APIErrorType::Unauthorized:
|
||||||
|
throw UnauthorizedHTTPError(error.message());
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
Q_UNREACHABLE();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
#include "base/utils/version.h"
|
#include "base/utils/version.h"
|
||||||
#include "api/isessionmanager.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 APIController;
|
||||||
class AuthController;
|
class AuthController;
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ const submitLoginForm = (event) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
const responseText = await response.text();
|
if (response.ok) {
|
||||||
if (response.ok && (responseText === "Ok.")) {
|
|
||||||
location.replace(location); // redirect
|
location.replace(location); // redirect
|
||||||
location.reload(true);
|
location.reload(true);
|
||||||
}
|
}
|
||||||
else {
|
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}`;
|
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