diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 78ca36f3e..6188f4706 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -35,6 +35,7 @@ net/dnsupdater.h net/downloadmanager.h net/geoipmanager.h net/portforwarder.h +net/private/downloadhandlerimpl.h net/private/geoipdatabase.h net/proxyconfigurationmanager.h net/reverseresolution.h @@ -107,6 +108,7 @@ net/dnsupdater.cpp net/downloadmanager.cpp net/geoipmanager.cpp net/portforwarder.cpp +net/private/downloadhandlerimpl.cpp net/private/geoipdatabase.cpp net/proxyconfigurationmanager.cpp net/reverseresolution.cpp diff --git a/src/base/base.pri b/src/base/base.pri index a637831c9..68df7315a 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -40,6 +40,7 @@ HEADERS += \ $$PWD/net/downloadmanager.h \ $$PWD/net/geoipmanager.h \ $$PWD/net/portforwarder.h \ + $$PWD/net/private/downloadhandlerimpl.h \ $$PWD/net/private/geoipdatabase.h \ $$PWD/net/proxyconfigurationmanager.h \ $$PWD/net/reverseresolution.h \ @@ -110,6 +111,7 @@ SOURCES += \ $$PWD/net/downloadmanager.cpp \ $$PWD/net/geoipmanager.cpp \ $$PWD/net/portforwarder.cpp \ + $$PWD/net/private/downloadhandlerimpl.cpp \ $$PWD/net/private/geoipdatabase.cpp \ $$PWD/net/proxyconfigurationmanager.cpp \ $$PWD/net/reverseresolution.cpp \ diff --git a/src/base/net/downloadmanager.cpp b/src/base/net/downloadmanager.cpp index ab002e4cd..ad6725a94 100644 --- a/src/base/net/downloadmanager.cpp +++ b/src/base/net/downloadmanager.cpp @@ -39,22 +39,19 @@ #include #include #include -#include #include #include "base/global.h" #include "base/logger.h" #include "base/preferences.h" -#include "base/utils/fs.h" -#include "base/utils/gzip.h" -#include "base/utils/misc.h" +#include "private/downloadhandlerimpl.h" #include "proxyconfigurationmanager.h" -// Disguise as Firefox to avoid web server banning -const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"; - namespace { + // Disguise as Firefox to avoid web server banning + const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"; + const int MAX_REDIRECTIONS = 20; // the common value for web browsers class NetworkCookieJar : public QNetworkCookieJar @@ -113,35 +110,6 @@ namespace } }; - class DownloadHandlerImpl : public Net::DownloadHandler - { - Q_DISABLE_COPY(DownloadHandlerImpl) - - public: - explicit DownloadHandlerImpl(const Net::DownloadRequest &downloadRequest, QObject *parent); - ~DownloadHandlerImpl() override; - - void cancel() override; - - QString url() const; - const Net::DownloadRequest downloadRequest() const; - - void assignNetworkReply(QNetworkReply *reply); - - private: - void processFinishedDownload(); - void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal); - void handleRedirection(const QUrl &newUrl); - void setError(const QString &error); - void finish(); - - static QString errorCodeToString(QNetworkReply::NetworkError status); - - QNetworkReply *m_reply = nullptr; - const Net::DownloadRequest m_downloadRequest; - Net::DownloadResult m_result; - }; - QNetworkRequest createNetworkRequest(const Net::DownloadRequest &downloadRequest) { QNetworkRequest request {downloadRequest.url()}; @@ -161,20 +129,6 @@ namespace return request; } - - bool saveToFile(const QByteArray &replyData, QString &filePath) - { - QTemporaryFile tmpfile {Utils::Fs::tempPath() + "XXXXXX"}; - tmpfile.setAutoRemove(false); - - if (!tmpfile.open()) - return false; - - filePath = tmpfile.fileName(); - - tmpfile.write(replyData); - return true; - } } Net::DownloadManager *Net::DownloadManager::m_instance = nullptr; @@ -401,182 +355,3 @@ bool Net::operator==(const ServiceID &lhs, const ServiceID &rhs) { return ((lhs.hostName == rhs.hostName) && (lhs.port == rhs.port)); } - -namespace -{ - DownloadHandlerImpl::DownloadHandlerImpl(const Net::DownloadRequest &downloadRequest, QObject *parent) - : DownloadHandler {parent} - , m_downloadRequest {downloadRequest} - { - m_result.url = url(); - m_result.status = Net::DownloadStatus::Success; - } - - DownloadHandlerImpl::~DownloadHandlerImpl() - { - if (m_reply) - delete m_reply; - } - - void DownloadHandlerImpl::cancel() - { - if (m_reply) { - m_reply->abort(); - } - else { - setError(errorCodeToString(QNetworkReply::OperationCanceledError)); - finish(); - } - } - - void DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply) - { - Q_ASSERT(reply); - - m_reply = reply; - m_reply->setParent(this); - if (m_downloadRequest.limit() > 0) - connect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize); - connect(m_reply, &QNetworkReply::finished, this, &DownloadHandlerImpl::processFinishedDownload); - connect(m_reply, &QNetworkReply::redirected, this, &DownloadHandlerImpl::handleRedirection); - } - - // Returns original url - QString DownloadHandlerImpl::url() const - { - return m_downloadRequest.url(); - } - - const Net::DownloadRequest DownloadHandlerImpl::downloadRequest() const - { - return m_downloadRequest; - } - - void DownloadHandlerImpl::processFinishedDownload() - { - qDebug("Download finished: %s", qUtf8Printable(url())); - - // Check if the request was successful - if (m_reply->error() != QNetworkReply::NoError) { - // Failure - qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(errorCodeToString(m_reply->error()))); - setError(errorCodeToString(m_reply->error())); - finish(); - return; - } - - // Success - m_result.data = (m_reply->rawHeader("Content-Encoding") == "gzip") - ? Utils::Gzip::decompress(m_reply->readAll()) - : m_reply->readAll(); - - if (m_downloadRequest.saveToFile()) { - QString filePath; - if (saveToFile(m_result.data, filePath)) - m_result.filePath = filePath; - else - setError(tr("I/O Error")); - } - - finish(); - } - - void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal) - { - if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit())) { - // Total number of bytes is available - disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize); - return; - } - - if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit())) { - m_reply->abort(); - setError(tr("The file size is %1. It exceeds the download limit of %2.") - .arg(Utils::Misc::friendlyUnit(bytesTotal) - , Utils::Misc::friendlyUnit(m_downloadRequest.limit()))); - finish(); - } - } - - void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl) - { - // Resolve relative urls - const QUrl resolvedUrl = newUrl.isRelative() ? m_reply->url().resolved(newUrl) : newUrl; - const QString newUrlString = resolvedUrl.toString(); - qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString)); - - // Redirect to magnet workaround - if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) { - qDebug("Magnet redirect detected."); - m_result.status = Net::DownloadStatus::RedirectedToMagnet; - m_result.magnet = newUrlString; - m_result.errorString = tr("Redirected to magnet URI."); - - finish(); - return; - } - - emit m_reply->redirectAllowed(); - } - - void DownloadHandlerImpl::setError(const QString &error) - { - m_result.errorString = error; - m_result.status = Net::DownloadStatus::Failed; - } - - void DownloadHandlerImpl::finish() - { - emit finished(m_result); - } - - QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status) - { - switch (status) { - case QNetworkReply::HostNotFoundError: - return tr("The remote host name was not found (invalid hostname)"); - case QNetworkReply::OperationCanceledError: - return tr("The operation was canceled"); - case QNetworkReply::RemoteHostClosedError: - return tr("The remote server closed the connection prematurely, before the entire reply was received and processed"); - case QNetworkReply::TimeoutError: - return tr("The connection to the remote server timed out"); - case QNetworkReply::SslHandshakeFailedError: - return tr("SSL/TLS handshake failed"); - case QNetworkReply::ConnectionRefusedError: - return tr("The remote server refused the connection"); - case QNetworkReply::ProxyConnectionRefusedError: - return tr("The connection to the proxy server was refused"); - case QNetworkReply::ProxyConnectionClosedError: - return tr("The proxy server closed the connection prematurely"); - case QNetworkReply::ProxyNotFoundError: - return tr("The proxy host name was not found"); - case QNetworkReply::ProxyTimeoutError: - return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent"); - case QNetworkReply::ProxyAuthenticationRequiredError: - return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered"); - case QNetworkReply::ContentAccessDenied: - return tr("The access to the remote content was denied (401)"); - case QNetworkReply::ContentOperationNotPermittedError: - return tr("The operation requested on the remote content is not permitted"); - case QNetworkReply::ContentNotFoundError: - return tr("The remote content was not found at the server (404)"); - case QNetworkReply::AuthenticationRequiredError: - return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted"); - case QNetworkReply::ProtocolUnknownError: - return tr("The Network Access API cannot honor the request because the protocol is not known"); - case QNetworkReply::ProtocolInvalidOperationError: - return tr("The requested operation is invalid for this protocol"); - case QNetworkReply::UnknownNetworkError: - return tr("An unknown network-related error was detected"); - case QNetworkReply::UnknownProxyError: - return tr("An unknown proxy-related error was detected"); - case QNetworkReply::UnknownContentError: - return tr("An unknown error related to the remote content was detected"); - case QNetworkReply::ProtocolFailure: - return tr("A breakdown in protocol was detected"); - default: - return tr("Unknown error"); - } - } -} diff --git a/src/base/net/private/downloadhandlerimpl.cpp b/src/base/net/private/downloadhandlerimpl.cpp new file mode 100644 index 000000000..5f76b1db7 --- /dev/null +++ b/src/base/net/private/downloadhandlerimpl.cpp @@ -0,0 +1,230 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015, 2018 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "downloadhandlerimpl.h" + +#include +#include + +#include "base/utils/fs.h" +#include "base/utils/gzip.h" +#include "base/utils/misc.h" + +namespace +{ + bool saveToFile(const QByteArray &replyData, QString &filePath) + { + QTemporaryFile tmpfile {Utils::Fs::tempPath() + "XXXXXX"}; + tmpfile.setAutoRemove(false); + + if (!tmpfile.open()) + return false; + + filePath = tmpfile.fileName(); + + tmpfile.write(replyData); + return true; + } +} + +DownloadHandlerImpl::DownloadHandlerImpl(const Net::DownloadRequest &downloadRequest, QObject *parent) + : DownloadHandler {parent} + , m_downloadRequest {downloadRequest} +{ + m_result.url = url(); + m_result.status = Net::DownloadStatus::Success; +} + +DownloadHandlerImpl::~DownloadHandlerImpl() +{ + if (m_reply) + delete m_reply; +} + +void DownloadHandlerImpl::cancel() +{ + if (m_reply) { + m_reply->abort(); + } + else { + setError(errorCodeToString(QNetworkReply::OperationCanceledError)); + finish(); + } +} + +void DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply) +{ + Q_ASSERT(reply); + + m_reply = reply; + m_reply->setParent(this); + if (m_downloadRequest.limit() > 0) + connect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize); + connect(m_reply, &QNetworkReply::finished, this, &DownloadHandlerImpl::processFinishedDownload); + connect(m_reply, &QNetworkReply::redirected, this, &DownloadHandlerImpl::handleRedirection); +} + +// Returns original url +QString DownloadHandlerImpl::url() const +{ + return m_downloadRequest.url(); +} + +const Net::DownloadRequest DownloadHandlerImpl::downloadRequest() const +{ + return m_downloadRequest; +} + +void DownloadHandlerImpl::processFinishedDownload() +{ + qDebug("Download finished: %s", qUtf8Printable(url())); + + // Check if the request was successful + if (m_reply->error() != QNetworkReply::NoError) { + // Failure + qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(errorCodeToString(m_reply->error()))); + setError(errorCodeToString(m_reply->error())); + finish(); + return; + } + + // Success + m_result.data = (m_reply->rawHeader("Content-Encoding") == "gzip") + ? Utils::Gzip::decompress(m_reply->readAll()) + : m_reply->readAll(); + + if (m_downloadRequest.saveToFile()) { + QString filePath; + if (saveToFile(m_result.data, filePath)) + m_result.filePath = filePath; + else + setError(tr("I/O Error")); + } + + finish(); +} + +void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal) +{ + if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit())) { + // Total number of bytes is available + disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize); + return; + } + + if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit())) { + m_reply->abort(); + setError(tr("The file size is %1. It exceeds the download limit of %2.") + .arg(Utils::Misc::friendlyUnit(bytesTotal) + , Utils::Misc::friendlyUnit(m_downloadRequest.limit()))); + finish(); + } +} + +void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl) +{ + // Resolve relative urls + const QUrl resolvedUrl = newUrl.isRelative() ? m_reply->url().resolved(newUrl) : newUrl; + const QString newUrlString = resolvedUrl.toString(); + qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString)); + + // Redirect to magnet workaround + if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) { + qDebug("Magnet redirect detected."); + m_result.status = Net::DownloadStatus::RedirectedToMagnet; + m_result.magnet = newUrlString; + m_result.errorString = tr("Redirected to magnet URI."); + + finish(); + return; + } + + emit m_reply->redirectAllowed(); +} + +void DownloadHandlerImpl::setError(const QString &error) +{ + m_result.errorString = error; + m_result.status = Net::DownloadStatus::Failed; +} + +void DownloadHandlerImpl::finish() +{ + emit finished(m_result); +} + +QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status) +{ + switch (status) { + case QNetworkReply::HostNotFoundError: + return tr("The remote host name was not found (invalid hostname)"); + case QNetworkReply::OperationCanceledError: + return tr("The operation was canceled"); + case QNetworkReply::RemoteHostClosedError: + return tr("The remote server closed the connection prematurely, before the entire reply was received and processed"); + case QNetworkReply::TimeoutError: + return tr("The connection to the remote server timed out"); + case QNetworkReply::SslHandshakeFailedError: + return tr("SSL/TLS handshake failed"); + case QNetworkReply::ConnectionRefusedError: + return tr("The remote server refused the connection"); + case QNetworkReply::ProxyConnectionRefusedError: + return tr("The connection to the proxy server was refused"); + case QNetworkReply::ProxyConnectionClosedError: + return tr("The proxy server closed the connection prematurely"); + case QNetworkReply::ProxyNotFoundError: + return tr("The proxy host name was not found"); + case QNetworkReply::ProxyTimeoutError: + return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent"); + case QNetworkReply::ProxyAuthenticationRequiredError: + return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered"); + case QNetworkReply::ContentAccessDenied: + return tr("The access to the remote content was denied (401)"); + case QNetworkReply::ContentOperationNotPermittedError: + return tr("The operation requested on the remote content is not permitted"); + case QNetworkReply::ContentNotFoundError: + return tr("The remote content was not found at the server (404)"); + case QNetworkReply::AuthenticationRequiredError: + return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted"); + case QNetworkReply::ProtocolUnknownError: + return tr("The Network Access API cannot honor the request because the protocol is not known"); + case QNetworkReply::ProtocolInvalidOperationError: + return tr("The requested operation is invalid for this protocol"); + case QNetworkReply::UnknownNetworkError: + return tr("An unknown network-related error was detected"); + case QNetworkReply::UnknownProxyError: + return tr("An unknown proxy-related error was detected"); + case QNetworkReply::UnknownContentError: + return tr("An unknown error related to the remote content was detected"); + case QNetworkReply::ProtocolFailure: + return tr("A breakdown in protocol was detected"); + default: + return tr("Unknown error"); + } +} diff --git a/src/base/net/private/downloadhandlerimpl.h b/src/base/net/private/downloadhandlerimpl.h new file mode 100644 index 000000000..a8cf83e25 --- /dev/null +++ b/src/base/net/private/downloadhandlerimpl.h @@ -0,0 +1,67 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015, 2018 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +#include "base/net/downloadmanager.h" + +class QObject; +class QUrl; + +class DownloadHandlerImpl : public Net::DownloadHandler +{ + Q_OBJECT + Q_DISABLE_COPY(DownloadHandlerImpl) + +public: + explicit DownloadHandlerImpl(const Net::DownloadRequest &downloadRequest, QObject *parent); + ~DownloadHandlerImpl() override; + + void cancel() override; + + QString url() const; + const Net::DownloadRequest downloadRequest() const; + + void assignNetworkReply(QNetworkReply *reply); + +private: + void processFinishedDownload(); + void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal); + void handleRedirection(const QUrl &newUrl); + void setError(const QString &error); + void finish(); + + static QString errorCodeToString(QNetworkReply::NetworkError status); + + QNetworkReply *m_reply = nullptr; + const Net::DownloadRequest m_downloadRequest; + Net::DownloadResult m_result; +};