qBittorrent
downloadhandlerimpl.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
4  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "downloadhandlerimpl.h"
31 
32 #include <QTemporaryFile>
33 #include <QUrl>
34 
36 #include "base/utils/fs.h"
37 #include "base/utils/gzip.h"
38 #include "base/utils/io.h"
39 #include "base/utils/misc.h"
40 
41 const int MAX_REDIRECTIONS = 20; // the common value for web browsers
42 
43 namespace
44 {
45  nonstd::expected<QString, QString> saveToTempFile(const QByteArray &data)
46  {
47  QTemporaryFile file {Utils::Fs::tempPath()};
48  if (!file.open() || (file.write(data) != data.length()) || !file.flush())
49  return nonstd::make_unexpected(file.errorString());
50 
51  file.setAutoRemove(false);
52  return file.fileName();
53  }
54 }
55 
57  : DownloadHandler {manager}
58  , m_manager {manager}
59  , m_downloadRequest {downloadRequest}
60 {
61  m_result.url = url();
63 }
64 
66 {
67  if (m_reply)
68  {
69  m_reply->abort();
70  }
71  else
72  {
73  setError(errorCodeToString(QNetworkReply::OperationCanceledError));
74  finish();
75  }
76 }
77 
78 void DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply)
79 {
80  Q_ASSERT(reply);
81  Q_ASSERT(!m_reply);
82 
83  m_reply = reply;
84  m_reply->setParent(this);
85  if (m_downloadRequest.limit() > 0)
86  connect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize);
87  connect(m_reply, &QNetworkReply::finished, this, &DownloadHandlerImpl::processFinishedDownload);
88 }
89 
90 // Returns original url
91 QString DownloadHandlerImpl::url() const
92 {
93  return m_downloadRequest.url();
94 }
95 
97 {
98  return m_downloadRequest;
99 }
100 
102 {
103  qDebug("Download finished: %s", qUtf8Printable(url()));
104 
105  // Check if the request was successful
106  if (m_reply->error() != QNetworkReply::NoError)
107  {
108  // Failure
109  qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(errorCodeToString(m_reply->error())));
111  finish();
112  return;
113  }
114 
115  // Check if the server ask us to redirect somewhere else
116  const QVariant redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
117  if (redirection.isValid())
118  {
119  handleRedirection(redirection.toUrl());
120  return;
121  }
122 
123  // Success
124  m_result.data = (m_reply->rawHeader("Content-Encoding") == "gzip")
125  ? Utils::Gzip::decompress(m_reply->readAll())
126  : m_reply->readAll();
127 
129  {
130  const QString destinationPath = m_downloadRequest.destFileName();
131  if (destinationPath.isEmpty())
132  {
133  const nonstd::expected<QString, QString> result = saveToTempFile(m_result.data);
134  if (result)
135  m_result.filePath = result.value();
136  else
137  setError(tr("I/O Error: %1").arg(result.error()));
138  }
139  else
140  {
141  const nonstd::expected<void, QString> result = Utils::IO::saveToFile(destinationPath, m_result.data);
142  if (result)
143  m_result.filePath = destinationPath;
144  else
145  setError(tr("I/O Error: %1").arg(result.error()));
146  }
147  }
148 
149  finish();
150 }
151 
152 void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal)
153 {
154  if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit()))
155  {
156  // Total number of bytes is available
157  disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize);
158  return;
159  }
160 
161  if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit()))
162  {
163  m_reply->abort();
164  setError(tr("The file size (%1) exceeds the download limit (%2)")
165  .arg(Utils::Misc::friendlyUnit(bytesTotal)
167  finish();
168  }
169 }
170 
172 {
174  {
175  setError(tr("Exceeded max redirections (%1)").arg(MAX_REDIRECTIONS));
176  finish();
177  return;
178  }
179 
180  // Resolve relative urls
181  const QUrl resolvedUrl = newUrl.isRelative() ? m_reply->url().resolved(newUrl) : newUrl;
182  const QString newUrlString = resolvedUrl.toString();
183  qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
184 
185  // Redirect to magnet workaround
186  if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive))
187  {
188  qDebug("Magnet redirect detected.");
190  m_result.magnet = newUrlString;
191  m_result.errorString = tr("Redirected to magnet URI");
192 
193  finish();
194  return;
195  }
196 
197  auto redirected = static_cast<DownloadHandlerImpl *>(
199  redirected->m_redirectionCount = m_redirectionCount + 1;
200  connect(redirected, &DownloadHandlerImpl::finished, this, [this](const Net::DownloadResult &result)
201  {
202  m_result = result;
203  m_result.url = url();
204  finish();
205  });
206 }
207 
208 void DownloadHandlerImpl::setError(const QString &error)
209 {
210  m_result.errorString = error;
212 }
213 
215 {
216  emit finished(m_result);
217 }
218 
219 QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status)
220 {
221  switch (status)
222  {
223  case QNetworkReply::HostNotFoundError:
224  return tr("The remote host name was not found (invalid hostname)");
225  case QNetworkReply::OperationCanceledError:
226  return tr("The operation was canceled");
227  case QNetworkReply::RemoteHostClosedError:
228  return tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
229  case QNetworkReply::TimeoutError:
230  return tr("The connection to the remote server timed out");
231  case QNetworkReply::SslHandshakeFailedError:
232  return tr("SSL/TLS handshake failed");
233  case QNetworkReply::ConnectionRefusedError:
234  return tr("The remote server refused the connection");
235  case QNetworkReply::ProxyConnectionRefusedError:
236  return tr("The connection to the proxy server was refused");
237  case QNetworkReply::ProxyConnectionClosedError:
238  return tr("The proxy server closed the connection prematurely");
239  case QNetworkReply::ProxyNotFoundError:
240  return tr("The proxy host name was not found");
241  case QNetworkReply::ProxyTimeoutError:
242  return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
243  case QNetworkReply::ProxyAuthenticationRequiredError:
244  return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
245  case QNetworkReply::ContentAccessDenied:
246  return tr("The access to the remote content was denied (401)");
247  case QNetworkReply::ContentOperationNotPermittedError:
248  return tr("The operation requested on the remote content is not permitted");
249  case QNetworkReply::ContentNotFoundError:
250  return tr("The remote content was not found at the server (404)");
251  case QNetworkReply::AuthenticationRequiredError:
252  return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
253  case QNetworkReply::ProtocolUnknownError:
254  return tr("The Network Access API cannot honor the request because the protocol is not known");
255  case QNetworkReply::ProtocolInvalidOperationError:
256  return tr("The requested operation is invalid for this protocol");
257  case QNetworkReply::UnknownNetworkError:
258  return tr("An unknown network-related error was detected");
259  case QNetworkReply::UnknownProxyError:
260  return tr("An unknown proxy-related error was detected");
261  case QNetworkReply::UnknownContentError:
262  return tr("An unknown error related to the remote content was detected");
263  case QNetworkReply::ProtocolFailure:
264  return tr("A breakdown in protocol was detected");
265  default:
266  return tr("Unknown error");
267  }
268 }
void assignNetworkReply(QNetworkReply *reply)
QNetworkReply * m_reply
Net::DownloadResult m_result
static QString errorCodeToString(QNetworkReply::NetworkError status)
void handleRedirection(const QUrl &newUrl)
void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
DownloadHandlerImpl(Net::DownloadManager *manager, const Net::DownloadRequest &downloadRequest)
const Net::DownloadRequest downloadRequest() const
const Net::DownloadRequest m_downloadRequest
Net::DownloadManager * m_manager
void setError(const QString &error)
void finished(const DownloadResult &result)
DownloadHandler * download(const DownloadRequest &downloadRequest)
QString destFileName() const
const int MAX_REDIRECTIONS
QString tempPath()
Definition: fs.cpp:314
QByteArray decompress(const QByteArray &data, bool *ok=nullptr)
Definition: gzip.cpp:102
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
Definition: io.cpp:69
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
nonstd::expected< QString, QString > saveToTempFile(const QByteArray &data)
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5
DownloadStatus status