From 9d7d3dd5fdcbfbcb9d6905d75d1e6186bf13c7cd Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Sun, 7 May 2023 21:30:46 +0100 Subject: [PATCH] Add NetworkRequest. --- src/CMakeLists.txt | 1 + src/core/NetworkRequest.cpp | 143 ++++++++++++++++++++++++++++++++++++ src/core/NetworkRequest.h | 56 ++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 src/core/NetworkRequest.cpp create mode 100644 src/core/NetworkRequest.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f37f55b0a..563d9f114 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(keepassx_SOURCES core/Merger.cpp core/Metadata.cpp core/ModifiableObject.cpp + core/NetworkRequest.cpp core/PasswordGenerator.cpp core/PasswordHealth.cpp core/PassphraseGenerator.cpp diff --git a/src/core/NetworkRequest.cpp b/src/core/NetworkRequest.cpp new file mode 100644 index 000000000..38707c725 --- /dev/null +++ b/src/core/NetworkRequest.cpp @@ -0,0 +1,143 @@ + +#include "NetworkRequest.h" +#include "NetworkManager.h" +#include +#include +#include + +namespace +{ + QUrl getRedirectTarget(QNetworkReply* reply) + { + QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + QUrl url; + if (var.canConvert()) { + url = var.toUrl(); + } + return url; + } +} + +void NetworkRequest::fetch(const QUrl& url) +{ + reset(); + + QNetworkRequest request(url); + m_reply = m_manager->get(request); + + connect(m_reply, &QNetworkReply::finished, this, &NetworkRequest::fetchFinished); + connect(m_reply, &QIODevice::readyRead, this, &NetworkRequest::fetchReadyRead); +} + +void NetworkRequest::fetchFinished() +{ + auto error = m_reply->error(); + QUrl redirectTarget = getRedirectTarget(m_reply); + QUrl url = m_reply->url(); + // Returns an empty string if the header was not set + QString contentTypeHeader = m_reply->header(QNetworkRequest::ContentTypeHeader).toString(); + + m_reply->deleteLater(); + m_reply = nullptr; + + if(error != QNetworkReply::NoError) { + // Do not emit on abort. + if(error != QNetworkReply::OperationCanceledError) { + emit failure(); + } + return; + } + + if (redirectTarget.isValid()) { + // Redirected, we need to follow it, or fall through if we have + // done too many redirects already. + if (m_redirects < m_maxRedirects) { + if (redirectTarget.isRelative()) { + redirectTarget = url.resolved(redirectTarget); + } + // Request the redirect target + fetch(redirectTarget); + return; + } else { + emit failure(); + return; + } + } + + // Parse content type + auto tokens = contentTypeHeader.split(";", Qt::SkipEmptyParts); + m_content_type = tokens[0].trimmed(); + for(int i = 1; i < tokens.size(); ++i) { + auto parameterTokens = tokens[i].split("="); + m_content_type_parameters[parameterTokens[0]] = parameterTokens[1]; + } + + emit success(std::move(m_bytes)); +} + +void NetworkRequest::fetchReadyRead() +{ + m_bytes += m_reply->readAll(); +} + +void NetworkRequest::reset() +{ + m_redirects = 0; + m_bytes.clear(); + m_content_type = ""; + m_content_type_parameters.clear(); + m_timeout.setInterval(m_timeoutDuration); + m_timeout.setSingleShot(true); +} + +void NetworkRequest::cancel() +{ + if(m_reply) { + m_reply->abort(); + } +} + +NetworkRequest::~NetworkRequest() +{ + cancel(); +} + +QUrl NetworkRequest::url() const +{ + if(m_reply) { + return m_reply->url(); + } + return {}; +} + +void NetworkRequest::setMaxRedirects(int maxRedirects) +{ + m_maxRedirects = std::max(0, maxRedirects); +} + +NetworkRequest::NetworkRequest(int maxRedirects, std::chrono::milliseconds timeoutDuration, QNetworkAccessManager* manager) : m_reply(nullptr), m_maxRedirects(maxRedirects), m_redirects(0), m_timeoutDuration(timeoutDuration) +{ + m_manager = manager ? manager : getNetMgr(); + connect(&m_timeout, &QTimer::timeout, this, &NetworkRequest::fetchTimeout); +} + +const QString& NetworkRequest::ContentType() const +{ + return m_content_type; +} + +const QHash& NetworkRequest::ContentTypeParameters() const +{ + return m_content_type_parameters; +} + +void NetworkRequest::setTimeout(std::chrono::milliseconds timeoutDuration) +{ + m_timeoutDuration = timeoutDuration; +} + +void NetworkRequest::fetchTimeout() +{ + // Cancel request on timeout + cancel(); +} diff --git a/src/core/NetworkRequest.h b/src/core/NetworkRequest.h new file mode 100644 index 000000000..f6e6e8261 --- /dev/null +++ b/src/core/NetworkRequest.h @@ -0,0 +1,56 @@ + +#ifndef KEEPASSXC_NETWORKREQUEST_H +#define KEEPASSXC_NETWORKREQUEST_H + +#include +#include +#include +class QNetworkReply; +class QNetworkAccessManager; + +class NetworkRequest : public QObject { + Q_OBJECT + + QNetworkAccessManager* m_manager; + QNetworkReply* m_reply; + QByteArray m_bytes; + + QString m_content_type; + QHash m_content_type_parameters; + QTimer m_timeout; + + int m_maxRedirects; + int m_redirects; + std::chrono::milliseconds m_timeoutDuration; + public: + explicit NetworkRequest(int maxRedirects, std::chrono::milliseconds timeoutDuration, QNetworkAccessManager* manager = nullptr); + ~NetworkRequest() override; + + void setMaxRedirects(int maxRedirects); + void setTimeout(std::chrono::milliseconds timeoutDuration); + + void fetch(const QUrl& url); + void cancel(); + + QUrl url() const; + /*** + * @return The MIME Type set in the Content-Type header. Empty string if Content-Type was not set. + */ + const QString& ContentType() const; + /*** + * @return Any parameters set in the Content-Type header. + */ + const QHash& ContentTypeParameters() const; + private: + void reset(); + private slots: + void fetchFinished(); + void fetchReadyRead(); + void fetchTimeout(); + + signals: + void success(QByteArray); + void failure(); + }; + +#endif // KEEPASSXC_NETWORKREQUEST_H