diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index cf2ca7701..848442237 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -263,6 +263,8 @@ set(SOURCES network-web/googlesuggest.h network-web/httpresponse.cpp network-web/httpresponse.h + network-web/httpserver.cpp + network-web/httpserver.h network-web/networkfactory.cpp network-web/networkfactory.h network-web/oauth2service.cpp diff --git a/src/librssguard/network-web/httpserver.cpp b/src/librssguard/network-web/httpserver.cpp new file mode 100644 index 000000000..5a9c74df7 --- /dev/null +++ b/src/librssguard/network-web/httpserver.cpp @@ -0,0 +1,276 @@ +// For license of this file, see /LICENSE.md. + +#include "network-web/httpserver.h" + +#include "definitions/definitions.h" + +HttpServer::HttpServer(QObject* parent) : QObject(parent), m_listenAddress(QHostAddress()), m_listenPort(0) { + connect(&m_httpServer, &QTcpServer::newConnection, this, &HttpServer::clientConnected); + + // NOTE: We do not want to start handler immediately, sometimes + // we want to start it later, perhaps when correct redirect URL/port comes in. +} + +HttpServer::~HttpServer() { + if (m_httpServer.isListening()) { + qWarningNN << LOGSEC_NETWORK << "Redirection OAuth handler is listening. Stopping it now."; + stop(); + } +} + +bool HttpServer::isListening() const { + return m_httpServer.isListening(); +} + +void HttpServer::setListenAddressPort(const QString& full_uri, bool start_handler) { + QUrl url = QUrl::fromUserInput(full_uri); + QHostAddress listen_address; + quint16 listen_port = quint16(url.port(80)); + + if (url.host() == QL1S("localhost")) { + listen_address = QHostAddress(QHostAddress::SpecialAddress::LocalHost); + } + else { + listen_address = QHostAddress(url.host()); + } + + if (listen_address == m_listenAddress && listen_port == m_listenPort && start_handler == m_httpServer.isListening()) { + // NOTE: We do not need to change listener's settings or re-start it. + return; + } + + if (m_httpServer.isListening()) { + qWarningNN << LOGSEC_NETWORK << "Redirection OAuth handler is listening. Stopping it now."; + stop(); + } + + m_listenAddress = listen_address; + m_listenPort = listen_port; + m_listenAddressPort = full_uri; + + if (!start_handler) { + qDebugNN << LOGSEC_NETWORK << "User does not want handler to be running."; + return; + } + + if (!m_httpServer.listen(listen_address, listen_port)) { + qCriticalNN << LOGSEC_NETWORK << "OAuth redirect handler FAILED TO START TO LISTEN on address" + << QUOTE_W_SPACE(listen_address.toString()) << "and port" << QUOTE_W_SPACE(listen_port) << "with error" + << QUOTE_W_SPACE_DOT(m_httpServer.errorString()); + } + else { + qDebugNN << LOGSEC_NETWORK << "OAuth redirect handler IS LISTENING on address" + << QUOTE_W_SPACE(m_listenAddress.toString()) << "and port" << QUOTE_W_SPACE_DOT(m_listenPort); + } +} + +void HttpServer::clientConnected() { + QTcpSocket* socket = m_httpServer.nextPendingConnection(); + + QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); + QObject::connect(socket, &QTcpSocket::readyRead, [this, socket]() { + readReceivedData(socket); + }); +} + +void HttpServer::readReceivedData(QTcpSocket* socket) { + if (!m_connectedClients.contains(socket)) { + m_connectedClients[socket].m_address = QSL(URI_SCHEME_HTTP) + m_httpServer.serverAddress().toString(); + m_connectedClients[socket].m_port = m_httpServer.serverPort(); + } + + QHttpRequest* request = &m_connectedClients[socket]; + bool error = false; + + if (Q_LIKELY(request->m_state == QHttpRequest::State::ReadingMethod)) { + if (Q_UNLIKELY(error = !request->readMethod(socket))) { + qWarningNN << LOGSEC_NETWORK << "Invalid method."; + } + } + + if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingUrl)) { + if (Q_UNLIKELY(error = !request->readUrl(socket))) { + qWarningNN << LOGSEC_NETWORK << "Invalid URL."; + } + } + + if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingStatus)) { + if (Q_UNLIKELY(error = !request->readStatus(socket))) { + qWarningNN << LOGSEC_NETWORK << "Invalid status."; + } + } + + if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingHeader)) { + if (Q_UNLIKELY(error = !request->readHeader(socket))) { + qWarningNN << LOGSEC_NETWORK << "Invalid header."; + } + } + + if (error) { + socket->disconnectFromHost(); + m_connectedClients.remove(socket); + } + else if (!request->m_url.isEmpty()) { + Q_ASSERT(request->m_state != QHttpRequest::State::ReadingUrl); + + answerClient(socket, *request); + m_connectedClients.remove(socket); + } +} + +QHostAddress HttpServer::listenAddress() const { + return m_listenAddress; +} + +QString HttpServer::listenAddressPort() const { + return m_listenAddressPort; +} + +quint16 HttpServer::listenPort() const { + return m_listenPort; +} + +bool HttpServer::QHttpRequest::readMethod(QTcpSocket* socket) { + bool finished = false; + + while ((socket->bytesAvailable() != 0) && !finished) { + const auto c = socket->read(1).at(0); + + if ((std::isupper(c) != 0) && m_fragment.size() < 6) { + m_fragment += c; + } + else { + finished = true; + } + } + + if (finished) { + if (m_fragment == "HEAD") { + m_method = Method::Head; + } + else if (m_fragment == "GET") { + m_method = Method::Get; + } + else if (m_fragment == "PUT") { + m_method = Method::Put; + } + else if (m_fragment == "POST") { + m_method = Method::Post; + } + else if (m_fragment == "DELETE") { + m_method = Method::Delete; + } + else { + qWarningNN << LOGSEC_NETWORK << "Invalid operation:" << QUOTE_W_SPACE_DOT(m_fragment.data()); + } + + m_state = State::ReadingUrl; + m_fragment.clear(); + + return m_method != Method::Unknown; + } + + return true; +} + +bool HttpServer::QHttpRequest::readUrl(QTcpSocket* socket) { + bool finished = false; + + while ((socket->bytesAvailable() != 0) && !finished) { + const auto c = socket->read(1).at(0); + + if (std::isspace(c) != 0) { + finished = true; + } + else { + m_fragment += c; + } + } + + if (finished) { + if (!m_fragment.startsWith("/")) { + qWarningNN << LOGSEC_NETWORK << "Invalid URL path" << QUOTE_W_SPACE_DOT(m_fragment); + return false; + } + + m_url.setUrl(m_address + QString::number(m_port) + QString::fromUtf8(m_fragment)); + m_state = State::ReadingStatus; + + if (!m_url.isValid()) { + qWarningNN << LOGSEC_NETWORK << "Invalid URL" << QUOTE_W_SPACE_DOT(m_fragment); + return false; + } + + m_fragment.clear(); + return true; + } + + return true; +} + +bool HttpServer::QHttpRequest::readStatus(QTcpSocket* socket) { + bool finished = false; + + while ((socket->bytesAvailable() != 0) && !finished) { + m_fragment += socket->read(1); + + if (m_fragment.endsWith("\r\n")) { + finished = true; + m_fragment.resize(m_fragment.size() - 2); + } + } + + if (finished) { + if ((std::isdigit(m_fragment.at(m_fragment.size() - 3)) == 0) || + (std::isdigit(m_fragment.at(m_fragment.size() - 1)) == 0)) { + qWarningNN << LOGSEC_NETWORK << "Invalid version"; + return false; + } + + m_version = qMakePair(m_fragment.at(m_fragment.size() - 3) - '0', m_fragment.at(m_fragment.size() - 1) - '0'); + m_state = State::ReadingHeader; + m_fragment.clear(); + } + + return true; +} + +bool HttpServer::QHttpRequest::readHeader(QTcpSocket* socket) { + while (socket->bytesAvailable() != 0) { + m_fragment += socket->read(1); + + if (m_fragment.endsWith("\r\n")) { + if (m_fragment == "\r\n") { + m_state = State::ReadingBody; + m_fragment.clear(); + return true; + } + else { + m_fragment.chop(2); + const int index = m_fragment.indexOf(':'); + + if (index == -1) { + return false; + } + + const QByteArray key = m_fragment.mid(0, index).trimmed(); + const QByteArray value = m_fragment.mid(index + 1).trimmed(); + + m_headers.insert(key, value); + m_fragment.clear(); + } + } + } + + return false; +} + +void HttpServer::stop() { + m_httpServer.close(); + m_connectedClients.clear(); + m_listenAddress = QHostAddress(); + m_listenPort = 0; + m_listenAddressPort = QString(); + + qDebugNN << LOGSEC_NETWORK << "Stopped redirection handler."; +} diff --git a/src/librssguard/network-web/httpserver.h b/src/librssguard/network-web/httpserver.h new file mode 100644 index 000000000..e4416550d --- /dev/null +++ b/src/librssguard/network-web/httpserver.h @@ -0,0 +1,86 @@ +// For license of this file, see /LICENSE.md. + +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include + +#include +#include +#include +#include + +class HttpServer : public QObject { + Q_OBJECT + + public: + explicit HttpServer(QObject* parent = nullptr); + virtual ~HttpServer(); + + bool isListening() const; + + // Stops server and clear all connections. + void stop(); + + // Returns listening portnumber. + quint16 listenPort() const; + + // Returns listening IP address, usually something like "127.0.0.1". + QHostAddress listenAddress() const; + + // Returns full URL string. + QString listenAddressPort() const; + + // Sets full URL string, for example "http://localhost:123456". + void setListenAddressPort(const QString& full_uri, bool start_handler); + + protected: + struct QHttpRequest { + bool readMethod(QTcpSocket* socket); + bool readUrl(QTcpSocket* socket); + bool readStatus(QTcpSocket* socket); + bool readHeader(QTcpSocket* socket); + + enum class State { + ReadingMethod, + ReadingUrl, + ReadingStatus, + ReadingHeader, + ReadingBody, + AllDone + } m_state = State::ReadingMethod; + + enum class Method { + Unknown, + Head, + Get, + Put, + Post, + Delete, + } m_method = Method::Unknown; + + QString m_address; + quint16 m_port = 0; + QByteArray m_fragment; + QUrl m_url; + QPair m_version; + QMap m_headers; + }; + + virtual void answerClient(QTcpSocket* socket, const QHttpRequest& request) = 0; + + private slots: + void clientConnected(); + + private: + void readReceivedData(QTcpSocket* socket); + + private: + QMap m_connectedClients; + QTcpServer m_httpServer; + QHostAddress m_listenAddress; + quint16 m_listenPort; + QString m_listenAddressPort; +}; + +#endif // HTTPSERVER_H diff --git a/src/librssguard/network-web/oauthhttphandler.cpp b/src/librssguard/network-web/oauthhttphandler.cpp index 8d7474abd..0f7d15ebd 100644 --- a/src/librssguard/network-web/oauthhttphandler.cpp +++ b/src/librssguard/network-web/oauthhttphandler.cpp @@ -11,74 +11,9 @@ #include OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent) - : QObject(parent), m_listenAddress(QHostAddress()), m_listenPort(0), m_successText(success_text) { - connect(&m_httpServer, &QTcpServer::newConnection, this, &OAuthHttpHandler::clientConnected); + : HttpServer(parent), m_successText(success_text) {} - // NOTE: We do not want to start handler immediately, sometimes - // we want to start it later, perhaps when correct redirect URL/port comes in. -} - -OAuthHttpHandler::~OAuthHttpHandler() { - if (m_httpServer.isListening()) { - qWarningNN << LOGSEC_OAUTH << "Redirection OAuth handler is listening. Stopping it now."; - stop(); - } -} - -bool OAuthHttpHandler::isListening() const { - return m_httpServer.isListening(); -} - -void OAuthHttpHandler::setListenAddressPort(const QString& full_uri, bool start_handler) { - QUrl url = QUrl::fromUserInput(full_uri); - QHostAddress listen_address; - quint16 listen_port = quint16(url.port(80)); - - if (url.host() == QL1S("localhost")) { - listen_address = QHostAddress(QHostAddress::SpecialAddress::LocalHost); - } - else { - listen_address = QHostAddress(url.host()); - } - - if (listen_address == m_listenAddress && listen_port == m_listenPort && start_handler == m_httpServer.isListening()) { - // NOTE: We do not need to change listener's settings or re-start it. - return; - } - - if (m_httpServer.isListening()) { - qWarningNN << LOGSEC_OAUTH << "Redirection OAuth handler is listening. Stopping it now."; - stop(); - } - - m_listenAddress = listen_address; - m_listenPort = listen_port; - m_listenAddressPort = full_uri; - - if (!start_handler) { - qDebugNN << LOGSEC_OAUTH << "User does not want handler to be running."; - return; - } - - if (!m_httpServer.listen(listen_address, listen_port)) { - qCriticalNN << LOGSEC_OAUTH << "OAuth redirect handler FAILED TO START TO LISTEN on address" - << QUOTE_W_SPACE(listen_address.toString()) << "and port" << QUOTE_W_SPACE(listen_port) << "with error" - << QUOTE_W_SPACE_DOT(m_httpServer.errorString()); - } - else { - qDebugNN << LOGSEC_OAUTH << "OAuth redirect handler IS LISTENING on address" - << QUOTE_W_SPACE(m_listenAddress.toString()) << "and port" << QUOTE_W_SPACE_DOT(m_listenPort); - } -} - -void OAuthHttpHandler::clientConnected() { - QTcpSocket* socket = m_httpServer.nextPendingConnection(); - - QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); - QObject::connect(socket, &QTcpSocket::readyRead, [this, socket]() { - readReceivedData(socket); - }); -} +OAuthHttpHandler::~OAuthHttpHandler() {} void OAuthHttpHandler::handleRedirection(const QVariantMap& data) { if (data.isEmpty()) { @@ -109,22 +44,19 @@ void OAuthHttpHandler::handleRedirection(const QVariantMap& data) { } } -void OAuthHttpHandler::answerClient(QTcpSocket* socket, const QUrl& url) { - if (!url.path().remove(QL1C('/')).isEmpty()) { - qCriticalNN << LOGSEC_OAUTH << "Invalid request:" << QUOTE_W_SPACE_DOT(url.toString()); +void OAuthHttpHandler::answerClient(QTcpSocket* socket, const QHttpRequest& request) { + if (!request.m_url.path().remove(QL1C('/')).isEmpty()) { + qCriticalNN << LOGSEC_OAUTH << "Invalid request:" << QUOTE_W_SPACE_DOT(request.m_url.toString()); } else { QVariantMap received_data; - const QUrlQuery query(url.query()); + const QUrlQuery query(request.m_url.query()); const auto items = query.queryItems(); for (const auto& item : items) { received_data.insert(item.first, item.second); } - QByteArray res = socket->readAll(); - QString res_utf = QString::fromUtf8(res); - handleRedirection(received_data); const QString html = QSL("") + qApp->applicationName() + QSL("") + @@ -141,205 +73,3 @@ void OAuthHttpHandler::answerClient(QTcpSocket* socket, const QUrl& url) { socket->disconnectFromHost(); } - -void OAuthHttpHandler::readReceivedData(QTcpSocket* socket) { - if (!m_connectedClients.contains(socket)) { - m_connectedClients[socket].m_address = QSL(URI_SCHEME_HTTP) + m_httpServer.serverAddress().toString(); - m_connectedClients[socket].m_port = m_httpServer.serverPort(); - } - - QHttpRequest* request = &m_connectedClients[socket]; - bool error = false; - - if (Q_LIKELY(request->m_state == QHttpRequest::State::ReadingMethod)) { - if (Q_UNLIKELY(error = !request->readMethod(socket))) { - qWarningNN << LOGSEC_OAUTH << "Invalid method."; - } - } - - if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingUrl)) { - if (Q_UNLIKELY(error = !request->readUrl(socket))) { - qWarningNN << LOGSEC_OAUTH << "Invalid URL."; - } - } - - if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingStatus)) { - if (Q_UNLIKELY(error = !request->readStatus(socket))) { - qWarningNN << LOGSEC_OAUTH << "Invalid status."; - } - } - - if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingHeader)) { - if (Q_UNLIKELY(error = !request->readHeader(socket))) { - qWarningNN << LOGSEC_OAUTH << "Invalid header."; - } - } - - if (error) { - socket->disconnectFromHost(); - m_connectedClients.remove(socket); - } - else if (!request->m_url.isEmpty()) { - Q_ASSERT(request->m_state != QHttpRequest::State::ReadingUrl); - - answerClient(socket, request->m_url); - m_connectedClients.remove(socket); - } -} - -QHostAddress OAuthHttpHandler::listenAddress() const { - return m_listenAddress; -} - -QString OAuthHttpHandler::listenAddressPort() const { - return m_listenAddressPort; -} - -quint16 OAuthHttpHandler::listenPort() const { - return m_listenPort; -} - -bool OAuthHttpHandler::QHttpRequest::readMethod(QTcpSocket* socket) { - bool finished = false; - - while ((socket->bytesAvailable() != 0) && !finished) { - const auto c = socket->read(1).at(0); - - if ((std::isupper(c) != 0) && m_fragment.size() < 6) { - m_fragment += c; - } - else { - finished = true; - } - } - - if (finished) { - if (m_fragment == "HEAD") { - m_method = Method::Head; - } - else if (m_fragment == "GET") { - m_method = Method::Get; - } - else if (m_fragment == "PUT") { - m_method = Method::Put; - } - else if (m_fragment == "POST") { - m_method = Method::Post; - } - else if (m_fragment == "DELETE") { - m_method = Method::Delete; - } - else { - qWarningNN << LOGSEC_OAUTH << "Invalid operation:" << QUOTE_W_SPACE_DOT(m_fragment.data()); - } - - m_state = State::ReadingUrl; - m_fragment.clear(); - - return m_method != Method::Unknown; - } - - return true; -} - -bool OAuthHttpHandler::QHttpRequest::readUrl(QTcpSocket* socket) { - bool finished = false; - - while ((socket->bytesAvailable() != 0) && !finished) { - const auto c = socket->read(1).at(0); - - if (std::isspace(c) != 0) { - finished = true; - } - else { - m_fragment += c; - } - } - - if (finished) { - if (!m_fragment.startsWith("/")) { - qWarningNN << LOGSEC_OAUTH << "Invalid URL path" << QUOTE_W_SPACE_DOT(m_fragment); - return false; - } - - m_url.setUrl(m_address + QString::number(m_port) + QString::fromUtf8(m_fragment)); - m_state = State::ReadingStatus; - - if (!m_url.isValid()) { - qWarningNN << LOGSEC_OAUTH << "Invalid URL" << QUOTE_W_SPACE_DOT(m_fragment); - return false; - } - - m_fragment.clear(); - return true; - } - - return true; -} - -bool OAuthHttpHandler::QHttpRequest::readStatus(QTcpSocket* socket) { - bool finished = false; - - while ((socket->bytesAvailable() != 0) && !finished) { - m_fragment += socket->read(1); - - if (m_fragment.endsWith("\r\n")) { - finished = true; - m_fragment.resize(m_fragment.size() - 2); - } - } - - if (finished) { - if ((std::isdigit(m_fragment.at(m_fragment.size() - 3)) == 0) || - (std::isdigit(m_fragment.at(m_fragment.size() - 1)) == 0)) { - qWarningNN << LOGSEC_OAUTH << "Invalid version"; - return false; - } - - m_version = qMakePair(m_fragment.at(m_fragment.size() - 3) - '0', m_fragment.at(m_fragment.size() - 1) - '0'); - m_state = State::ReadingHeader; - m_fragment.clear(); - } - - return true; -} - -bool OAuthHttpHandler::QHttpRequest::readHeader(QTcpSocket* socket) { - while (socket->bytesAvailable() != 0) { - m_fragment += socket->read(1); - - if (m_fragment.endsWith("\r\n")) { - if (m_fragment == "\r\n") { - m_state = State::ReadingBody; - m_fragment.clear(); - return true; - } - else { - m_fragment.chop(2); - const int index = m_fragment.indexOf(':'); - - if (index == -1) { - return false; - } - - const QByteArray key = m_fragment.mid(0, index).trimmed(); - const QByteArray value = m_fragment.mid(index + 1).trimmed(); - - m_headers.insert(key, value); - m_fragment.clear(); - } - } - } - - return false; -} - -void OAuthHttpHandler::stop() { - m_httpServer.close(); - m_connectedClients.clear(); - m_listenAddress = QHostAddress(); - m_listenPort = 0; - m_listenAddressPort = QString(); - - qDebugNN << LOGSEC_OAUTH << "Stopped redirection handler."; -} diff --git a/src/librssguard/network-web/oauthhttphandler.h b/src/librssguard/network-web/oauthhttphandler.h index 93cd2a417..e5534478e 100644 --- a/src/librssguard/network-web/oauthhttphandler.h +++ b/src/librssguard/network-web/oauthhttphandler.h @@ -3,85 +3,26 @@ #ifndef OAUTHHTTPHANDLER_H #define OAUTHHTTPHANDLER_H -#include +#include "network-web/httpserver.h" -#include -#include - -class OAuthHttpHandler : public QObject { - Q_OBJECT +class OAuthHttpHandler : public HttpServer { + Q_OBJECT public: explicit OAuthHttpHandler(const QString& success_text, QObject* parent = nullptr); virtual ~OAuthHttpHandler(); - bool isListening() const; - - // Stops server and clear all connections. - void stop(); - - // Returns listening portnumber. - quint16 listenPort() const; - - // Returns listening IP address, usually something like "127.0.0.1". - QHostAddress listenAddress() const; - - // Returns full URL string. - QString listenAddressPort() const; - - // Sets full URL string, for example "http://localhost:123456". - void setListenAddressPort(const QString& full_uri, bool start_handler); - signals: void authRejected(const QString& error_description, const QString& state); void authGranted(const QString& auth_code, const QString& state); - private slots: - void clientConnected(); + protected: + virtual void answerClient(QTcpSocket* socket, const QHttpRequest& request); private: void handleRedirection(const QVariantMap& data); - void answerClient(QTcpSocket* socket, const QUrl& url); - void readReceivedData(QTcpSocket* socket); private: - struct QHttpRequest { - bool readMethod(QTcpSocket* socket); - bool readUrl(QTcpSocket* socket); - bool readStatus(QTcpSocket* socket); - bool readHeader(QTcpSocket* socket); - - enum class State { - ReadingMethod, - ReadingUrl, - ReadingStatus, - ReadingHeader, - ReadingBody, - AllDone - } m_state = State::ReadingMethod; - - enum class Method { - Unknown, - Head, - Get, - Put, - Post, - Delete, - } m_method = Method::Unknown; - - QString m_address; - quint16 m_port = 0; - QByteArray m_fragment; - QUrl m_url; - QPair m_version; - QMap m_headers; - }; - - QMap m_connectedClients; - QTcpServer m_httpServer; - QHostAddress m_listenAddress; - quint16 m_listenPort; - QString m_listenAddressPort; QString m_successText; };