#include "NetworkRequest.h" #include "NetworkManager.h" #include #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); // Set headers for(const auto &[header, value] : qAsConst(m_headers)) { request.setRawHeader(header.toUtf8(), value.toUtf8()); } 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, QList> headers, QNetworkAccessManager* manager) : m_reply(nullptr), m_maxRedirects(maxRedirects), m_redirects(0), m_timeoutDuration(timeoutDuration), m_headers(headers) { 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(); } NetworkRequest createRequest(int maxRedirects, std::chrono::milliseconds timeoutDuration, QList> additionalHeaders, QNetworkAccessManager* manager) { // Append user agent unless given if(std::none_of(additionalHeaders.begin(), additionalHeaders.end(), [](const auto& pair) { return pair.first == "User-Agent"; })) { additionalHeaders.append(QPair{"User-Agent", "KeePassXC"}); } return NetworkRequest(maxRedirects, timeoutDuration, additionalHeaders, manager); }