diff --git a/src/webui/api/apicontroller.cpp b/src/webui/api/apicontroller.cpp index 1c7352bab..df7890cfc 100644 --- a/src/webui/api/apicontroller.cpp +++ b/src/webui/api/apicontroller.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2018 Vladimir Golovnev + * Copyright (C) 2018, 2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -37,9 +37,8 @@ #include "apierror.h" -APIController::APIController(ISessionManager *sessionManager, QObject *parent) +APIController::APIController(QObject *parent) : QObject {parent} - , m_sessionManager {sessionManager} { } @@ -56,11 +55,6 @@ QVariant APIController::run(const QString &action, const StringMap ¶ms, cons return m_result; } -ISessionManager *APIController::sessionManager() const -{ - return m_sessionManager; -} - const StringMap &APIController::params() const { return m_params; diff --git a/src/webui/api/apicontroller.h b/src/webui/api/apicontroller.h index 9e44163e3..91c0730b3 100644 --- a/src/webui/api/apicontroller.h +++ b/src/webui/api/apicontroller.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2018 Vladimir Golovnev + * Copyright (C) 2018, 2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,14 +28,12 @@ #pragma once +#include #include #include -#include class QString; -struct ISessionManager; - using DataMap = QHash; using StringMap = QHash; @@ -44,18 +42,11 @@ class APIController : public QObject Q_OBJECT Q_DISABLE_COPY_MOVE(APIController) -#ifndef Q_MOC_RUN -#define WEBAPI_PUBLIC -#define WEBAPI_PRIVATE -#endif - public: - explicit APIController(ISessionManager *sessionManager, QObject *parent = nullptr); + explicit APIController(QObject *parent = nullptr); QVariant run(const QString &action, const StringMap ¶ms, const DataMap &data = {}); - ISessionManager *sessionManager() const; - protected: const StringMap ¶ms() const; const DataMap &data() const; @@ -66,7 +57,6 @@ protected: void setResult(const QJsonObject &result); private: - ISessionManager *m_sessionManager; StringMap m_params; DataMap m_data; QVariant m_result; diff --git a/src/webui/api/authcontroller.cpp b/src/webui/api/authcontroller.cpp index 786aef66f..77bd4ca61 100644 --- a/src/webui/api/authcontroller.cpp +++ b/src/webui/api/authcontroller.cpp @@ -37,15 +37,21 @@ #include "apierror.h" #include "isessionmanager.h" +AuthController::AuthController(ISessionManager *sessionManager, QObject *parent) + : APIController {parent} + , m_sessionManager {sessionManager} +{ +} + void AuthController::loginAction() { - if (sessionManager()->session()) + if (m_sessionManager->session()) { setResult(u"Ok."_qs); return; } - const QString clientAddr {sessionManager()->clientId()}; + const QString clientAddr {m_sessionManager->clientId()}; const QString usernameFromWeb {params()[u"username"_qs]}; const QString passwordFromWeb {params()[u"password"_qs]}; @@ -69,7 +75,7 @@ void AuthController::loginAction() { m_clientFailedLogins.remove(clientAddr); - sessionManager()->sessionStart(); + m_sessionManager->sessionStart(); setResult(u"Ok."_qs); LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr)); } @@ -86,12 +92,12 @@ void AuthController::loginAction() void AuthController::logoutAction() const { - sessionManager()->sessionEnd(); + m_sessionManager->sessionEnd(); } bool AuthController::isBanned() const { - const auto failedLoginIter = m_clientFailedLogins.find(sessionManager()->clientId()); + const auto failedLoginIter = m_clientFailedLogins.find(m_sessionManager->clientId()); if (failedLoginIter == m_clientFailedLogins.end()) return false; @@ -107,14 +113,14 @@ bool AuthController::isBanned() const int AuthController::failedAttemptsCount() const { - return m_clientFailedLogins.value(sessionManager()->clientId()).failedAttemptsCount; + return m_clientFailedLogins.value(m_sessionManager->clientId()).failedAttemptsCount; } void AuthController::increaseFailedAttempts() { Q_ASSERT(Preferences::instance()->getWebUIMaxAuthFailCount() > 0); - FailedLogin &failedLogin = m_clientFailedLogins[sessionManager()->clientId()]; + FailedLogin &failedLogin = m_clientFailedLogins[m_sessionManager->clientId()]; ++failedLogin.failedAttemptsCount; if (failedLogin.failedAttemptsCount >= Preferences::instance()->getWebUIMaxAuthFailCount()) diff --git a/src/webui/api/authcontroller.h b/src/webui/api/authcontroller.h index 6833a3de0..ed9641cfb 100644 --- a/src/webui/api/authcontroller.h +++ b/src/webui/api/authcontroller.h @@ -35,13 +35,15 @@ class QString; +struct ISessionManager; + class AuthController : public APIController { Q_OBJECT Q_DISABLE_COPY_MOVE(AuthController) public: - using APIController::APIController; + explicit AuthController(ISessionManager *sessionManager, QObject *parent = nullptr); private slots: void loginAction(); @@ -52,6 +54,8 @@ private: int failedAttemptsCount() const; void increaseFailedAttempts(); + ISessionManager *m_sessionManager = nullptr; + struct FailedLogin { int failedAttemptsCount = 0; diff --git a/src/webui/api/isessionmanager.h b/src/webui/api/isessionmanager.h index 389d86865..64396a7da 100644 --- a/src/webui/api/isessionmanager.h +++ b/src/webui/api/isessionmanager.h @@ -36,14 +36,6 @@ struct ISession { virtual ~ISession() = default; virtual QString id() const = 0; - virtual QVariant getData(const QString &id) const = 0; - virtual void setData(const QString &id, const QVariant &data) = 0; - - template - T getData(const QString &id) const - { - return this->getData(id).value(); - } }; struct ISessionManager diff --git a/src/webui/api/searchcontroller.cpp b/src/webui/api/searchcontroller.cpp index 5e4c085f1..98dd99fd1 100644 --- a/src/webui/api/searchcontroller.cpp +++ b/src/webui/api/searchcontroller.cpp @@ -45,21 +45,8 @@ #include "apierror.h" #include "isessionmanager.h" -using SearchHandlerPtr = QSharedPointer; -using SearchHandlerDict = QMap; - namespace { - const QString ACTIVE_SEARCHES = u"activeSearches"_qs; - const QString SEARCH_HANDLERS = u"searchHandlers"_qs; - - void removeActiveSearch(ISession *session, const int id) - { - auto activeSearches = session->getData>(ACTIVE_SEARCHES); - if (activeSearches.remove(id)) - session->setData(ACTIVE_SEARCHES, QVariant::fromValue(activeSearches)); - } - /** * Returns the search categories in JSON format. * @@ -117,22 +104,17 @@ void SearchController::startAction() pluginsToUse << plugins; } - ISession *const session = sessionManager()->session(); - auto activeSearches = session->getData>(ACTIVE_SEARCHES); - if (activeSearches.size() >= MAX_CONCURRENT_SEARCHES) + if (m_activeSearches.size() >= MAX_CONCURRENT_SEARCHES) throw APIError(APIErrorType::Conflict, tr("Unable to create more than %1 concurrent searches.").arg(MAX_CONCURRENT_SEARCHES)); const auto id = generateSearchId(); - const SearchHandlerPtr searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)}; - QObject::connect(searchHandler.data(), &SearchHandler::searchFinished, this, [session, id, this]() { searchFinished(session, id); }); - QObject::connect(searchHandler.data(), &SearchHandler::searchFailed, this, [session, id, this]() { searchFailed(session, id); }); + const std::shared_ptr searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)}; + QObject::connect(searchHandler.get(), &SearchHandler::searchFinished, this, [id, this]() { m_activeSearches.remove(id); }); + QObject::connect(searchHandler.get(), &SearchHandler::searchFailed, this, [id, this]() { m_activeSearches.remove(id); }); - auto searchHandlers = session->getData(SEARCH_HANDLERS); - searchHandlers.insert(id, searchHandler); - session->setData(SEARCH_HANDLERS, QVariant::fromValue(searchHandlers)); + m_searchHandlers.insert(id, searchHandler); - activeSearches.insert(id); - session->setData(ACTIVE_SEARCHES, QVariant::fromValue(activeSearches)); + m_activeSearches.insert(id); const QJsonObject result = {{u"id"_qs, id}}; setResult(result); @@ -143,18 +125,17 @@ void SearchController::stopAction() requireParams({u"id"_qs}); const int id = params()[u"id"_qs].toInt(); - ISession *const session = sessionManager()->session(); - const auto searchHandlers = session->getData(SEARCH_HANDLERS); - if (!searchHandlers.contains(id)) + const auto iter = m_searchHandlers.find(id); + if (iter == m_searchHandlers.end()) throw APIError(APIErrorType::NotFound); - const SearchHandlerPtr searchHandler = searchHandlers[id]; + const std::shared_ptr &searchHandler = iter.value(); if (searchHandler->isActive()) { searchHandler->cancelSearch(); - removeActiveSearch(session, id); + m_activeSearches.remove(id); } } @@ -162,16 +143,15 @@ void SearchController::statusAction() { const int id = params()[u"id"_qs].toInt(); - const auto searchHandlers = sessionManager()->session()->getData(SEARCH_HANDLERS); - if ((id != 0) && !searchHandlers.contains(id)) + if ((id != 0) && !m_searchHandlers.contains(id)) throw APIError(APIErrorType::NotFound); QJsonArray statusArray; - const QList searchIds {(id == 0) ? searchHandlers.keys() : QList {id}}; + const QList searchIds {(id == 0) ? m_searchHandlers.keys() : QList {id}}; for (const int searchId : searchIds) { - const SearchHandlerPtr searchHandler = searchHandlers[searchId]; + const std::shared_ptr &searchHandler = m_searchHandlers[searchId]; statusArray << QJsonObject { {u"id"_qs, searchId}, @@ -191,11 +171,11 @@ void SearchController::resultsAction() int limit = params()[u"limit"_qs].toInt(); int offset = params()[u"offset"_qs].toInt(); - const auto searchHandlers = sessionManager()->session()->getData(SEARCH_HANDLERS); - if (!searchHandlers.contains(id)) + const auto iter = m_searchHandlers.find(id); + if (iter == m_searchHandlers.end()) throw APIError(APIErrorType::NotFound); - const SearchHandlerPtr searchHandler = searchHandlers[id]; + const std::shared_ptr &searchHandler = iter.value(); const QList searchResults = searchHandler->results(); const int size = searchResults.size(); @@ -221,18 +201,15 @@ void SearchController::deleteAction() requireParams({u"id"_qs}); const int id = params()[u"id"_qs].toInt(); - ISession *const session = sessionManager()->session(); - auto searchHandlers = session->getData(SEARCH_HANDLERS); - if (!searchHandlers.contains(id)) + const auto iter = m_searchHandlers.find(id); + if (iter == m_searchHandlers.end()) throw APIError(APIErrorType::NotFound); - const SearchHandlerPtr searchHandler = searchHandlers[id]; + const std::shared_ptr &searchHandler = iter.value(); searchHandler->cancelSearch(); - searchHandlers.remove(id); - session->setData(SEARCH_HANDLERS, QVariant::fromValue(searchHandlers)); - - removeActiveSearch(session, id); + m_activeSearches.remove(id); + m_searchHandlers.erase(iter); } void SearchController::pluginsAction() @@ -302,24 +279,12 @@ void SearchController::checkForUpdatesFailed(const QString &reason) LogMsg(tr("Failed to check for plugin updates: %1").arg(reason), Log::INFO); } -void SearchController::searchFinished(ISession *session, const int id) -{ - removeActiveSearch(session, id); -} - -void SearchController::searchFailed(ISession *session, const int id) -{ - removeActiveSearch(session, id); -} - int SearchController::generateSearchId() const { - const auto searchHandlers = sessionManager()->session()->getData(SEARCH_HANDLERS); - while (true) { const int id = Utils::Random::rand(1, std::numeric_limits::max()); - if (!searchHandlers.contains(id)) + if (!m_searchHandlers.contains(id)) return id; } } diff --git a/src/webui/api/searchcontroller.h b/src/webui/api/searchcontroller.h index 840e695a7..78e70e759 100644 --- a/src/webui/api/searchcontroller.h +++ b/src/webui/api/searchcontroller.h @@ -28,7 +28,11 @@ #pragma once +#include + #include +#include +#include #include "base/search/searchpluginmanager.h" #include "apicontroller.h" @@ -36,7 +40,6 @@ class QJsonArray; class QJsonObject; -struct ISession; struct SearchResult; class SearchController : public APIController @@ -64,9 +67,10 @@ private: void checkForUpdatesFinished(const QHash &updateInfo); void checkForUpdatesFailed(const QString &reason); - void searchFinished(ISession *session, int id); - void searchFailed(ISession *session, int id); int generateSearchId() const; QJsonObject getResults(const QList &searchResults, bool isSearchActive, int totalResults) const; QJsonArray getPluginsInfo(const QStringList &plugins) const; + + QSet m_activeSearches; + QHash> m_searchHandlers; }; diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index 1c1c919e6..d63c9f143 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -366,8 +366,8 @@ namespace } } -SyncController::SyncController(ISessionManager *sessionManager, QObject *parent) - : APIController(sessionManager, parent) +SyncController::SyncController(QObject *parent) + : APIController(parent) { m_freeDiskSpaceThread = new QThread(this); m_freeDiskSpaceChecker = new FreeDiskSpaceChecker(); @@ -456,9 +456,6 @@ void SyncController::maindataAction() QVariantMap data; - QVariantMap lastResponse = sessionManager()->session()->getData(u"syncMainDataLastResponse"_qs).toMap(); - QVariantMap lastAcceptedResponse = sessionManager()->session()->getData(u"syncMainDataLastAcceptedResponse"_qs).toMap(); - QVariantHash torrents; QHash trackers; for (const BitTorrent::Torrent *torrent : asConst(session->torrents())) @@ -470,8 +467,8 @@ void SyncController::maindataAction() // Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue). // So we don't need unnecessary updates of last activity time in response. - const auto iterTorrents = lastResponse.find(u"torrents"_qs); - if (iterTorrents != lastResponse.end()) + const auto iterTorrents = m_lastMaindataResponse.find(u"torrents"_qs); + if (iterTorrents != m_lastMaindataResponse.end()) { const QVariantHash lastResponseTorrents = iterTorrents->toHash(); const auto iterID = lastResponseTorrents.find(torrentID.toString()); @@ -529,11 +526,8 @@ void SyncController::maindataAction() serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval(); data[u"server_state"_qs] = serverState; - const int acceptedResponseId {params()[u"rid"_qs].toInt()}; - setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse))); - - sessionManager()->session()->setData(u"syncMainDataLastResponse"_qs, lastResponse); - sessionManager()->session()->setData(u"syncMainDataLastAcceptedResponse"_qs, lastAcceptedResponse); + const int acceptedResponseId = params()[u"rid"_qs].toInt(); + setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, m_lastAcceptedMaindataResponse, m_lastMaindataResponse))); } // GET param: @@ -541,9 +535,6 @@ void SyncController::maindataAction() // - rid (int): last response id void SyncController::torrentPeersAction() { - auto lastResponse = sessionManager()->session()->getData(u"syncTorrentPeersLastResponse"_qs).toMap(); - auto lastAcceptedResponse = sessionManager()->session()->getData(u"syncTorrentPeersLastAcceptedResponse"_qs).toMap(); - const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_qs]); const BitTorrent::Torrent *torrent = BitTorrent::Session::instance()->findTorrent(id); if (!torrent) @@ -598,11 +589,8 @@ void SyncController::torrentPeersAction() } data[u"peers"_qs] = peers; - const int acceptedResponseId {params()[u"rid"_qs].toInt()}; - setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse))); - - sessionManager()->session()->setData(u"syncTorrentPeersLastResponse"_qs, lastResponse); - sessionManager()->session()->setData(u"syncTorrentPeersLastAcceptedResponse"_qs, lastAcceptedResponse); + const int acceptedResponseId = params()[u"rid"_qs].toInt(); + setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, m_lastAcceptedPeersResponse, m_lastPeersResponse))); } qint64 SyncController::getFreeDiskSpace() diff --git a/src/webui/api/synccontroller.h b/src/webui/api/synccontroller.h index 415ee6421..9350c6822 100644 --- a/src/webui/api/synccontroller.h +++ b/src/webui/api/synccontroller.h @@ -29,11 +29,10 @@ #pragma once #include +#include #include "apicontroller.h" -struct ISessionManager; - class QThread; class FreeDiskSpaceChecker; @@ -46,7 +45,7 @@ class SyncController : public APIController public: using APIController::APIController; - explicit SyncController(ISessionManager *sessionManager, QObject *parent = nullptr); + explicit SyncController(QObject *parent = nullptr); ~SyncController() override; private slots: @@ -62,4 +61,9 @@ private: FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr; QThread *m_freeDiskSpaceThread = nullptr; QElapsedTimer m_freeDiskSpaceElapsedTimer; + + QVariantMap m_lastMaindataResponse; + QVariantMap m_lastAcceptedMaindataResponse; + QVariantMap m_lastPeersResponse; + QVariantMap m_lastAcceptedPeersResponse; }; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index a830726f8..50c96306d 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2014, 2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -119,16 +119,8 @@ namespace WebApplication::WebApplication(QObject *parent) : QObject(parent) , m_cacheID {QString::number(Utils::Random::rand(), 36)} + , m_authController {new AuthController(this, this)} { - registerAPIController(u"app"_qs, new AppController(this, this)); - registerAPIController(u"auth"_qs, new AuthController(this, this)); - registerAPIController(u"log"_qs, new LogController(this, this)); - registerAPIController(u"rss"_qs, new RSSController(this, this)); - registerAPIController(u"search"_qs, new SearchController(this, this)); - registerAPIController(u"sync"_qs, new SyncController(this, this)); - registerAPIController(u"torrents"_qs, new TorrentsController(this, this)); - registerAPIController(u"transfer"_qs, new TransferController(this, this)); - declarePublicAPI(u"auth/login"_qs); configure(); @@ -260,9 +252,16 @@ void WebApplication::doProcessRequest() const QString action = match.captured(u"action"_qs); const QString scope = match.captured(u"scope"_qs); - APIController *controller = m_apiControllers.value(scope); + APIController *controller = nullptr; + if (session()) + controller = session()->getAPIController(scope); if (!controller) - throw NotFoundHTTPError(); + { + if (scope == u"auth") + controller = m_authController; + else + throw NotFoundHTTPError(); + } if (!session() && !isPublicAPI(scope, action)) throw ForbiddenHTTPError(); @@ -414,14 +413,6 @@ void WebApplication::configure() } } -void WebApplication::registerAPIController(const QString &scope, APIController *controller) -{ - Q_ASSERT(controller); - Q_ASSERT(!m_apiControllers.value(scope)); - - m_apiControllers[scope] = controller; -} - void WebApplication::declarePublicAPI(const QString &apiPath) { m_publicAPIs << apiPath; @@ -607,6 +598,13 @@ void WebApplication::sessionStart() }); m_currentSession = new WebSession(generateSid()); + m_currentSession->registerAPIController(u"app"_qs); + m_currentSession->registerAPIController(u"log"_qs); + m_currentSession->registerAPIController(u"rss"_qs); + m_currentSession->registerAPIController(u"search"_qs); + m_currentSession->registerAPIController(u"sync"_qs); + m_currentSession->registerAPIController(u"torrents"_qs); + m_currentSession->registerAPIController(u"transfer"_qs); m_sessions[m_currentSession->id()] = m_currentSession; QNetworkCookie cookie(C_SID, m_currentSession->id().toUtf8()); @@ -775,12 +773,7 @@ void WebSession::updateTimestamp() m_timer.start(); } -QVariant WebSession::getData(const QString &id) const -{ - return m_data.value(id); -} - -void WebSession::setData(const QString &id, const QVariant &data) +APIController *WebSession::getAPIController(const QString &scope) const { - m_data[id] = data; + return m_apiControllers.value(scope); } diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 3a73e7ee0..70e563460 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2014, 2017 Vladimir Golovnev + * Copyright (C) 2014, 2017, 2022 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,9 +28,12 @@ #pragma once +#include + #include #include #include +#include #include #include #include @@ -48,9 +51,10 @@ inline const Utils::Version API_VERSION {2, 8, 9}; class APIController; +class AuthController; class WebApplication; -class WebSession final : public ISession +class WebSession final : public QObject, public ISession { public: explicit WebSession(const QString &sid); @@ -60,13 +64,19 @@ public: bool hasExpired(qint64 seconds) const; void updateTimestamp(); - QVariant getData(const QString &id) const override; - void setData(const QString &id, const QVariant &data) override; + template + void registerAPIController(const QString &scope) + { + static_assert(std::is_base_of_v, "Class should be derived from APIController."); + m_apiControllers[scope] = new T(this); + } + + APIController *getAPIController(const QString &scope) const; private: const QString m_sid; QElapsedTimer m_timer; // timestamp - QVariantHash m_data; + QMap m_apiControllers; }; class WebApplication final @@ -76,11 +86,6 @@ class WebApplication final Q_OBJECT Q_DISABLE_COPY_MOVE(WebApplication) -#ifndef Q_MOC_RUN -#define WEBAPI_PUBLIC -#define WEBAPI_PRIVATE -#endif - public: explicit WebApplication(QObject *parent = nullptr); ~WebApplication() override; @@ -99,7 +104,6 @@ private: void doProcessRequest(); void configure(); - void registerAPIController(const QString &scope, APIController *controller); void declarePublicAPI(const QString &apiPath); void sendFile(const Path &path); @@ -130,7 +134,6 @@ private: const QRegularExpression m_apiPathPattern {u"^/api/v2/(?[A-Za-z_][A-Za-z_0-9]*)/(?[A-Za-z_][A-Za-z_0-9]*)$"_qs}; - QHash m_apiControllers; QSet m_publicAPIs; bool m_isAltUIUsed = false; Path m_rootFolder; @@ -146,6 +149,7 @@ private: QTranslator m_translator; bool m_translationFileLoaded = false; + AuthController *m_authController = nullptr; bool m_isLocalAuthEnabled; bool m_isAuthSubnetWhitelistEnabled; QVector m_authSubnetWhitelist;