2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Jonathan Ketchker
4 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
5 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "downloadmanager.h"
38 #include <QNetworkAccessManager>
39 #include <QNetworkCookie>
40 #include <QNetworkCookieJar>
41 #include <QNetworkProxy>
42 #include <QNetworkReply>
43 #include <QNetworkRequest>
48 #include "base/global.h"
49 #include "base/logger.h"
50 #include "base/preferences.h"
51 #include "downloadhandlerimpl.h"
52 #include "proxyconfigurationmanager.h"
54 using namespace std::chrono_literals
;
58 // Disguise as browser to circumvent website blocking
59 QByteArray
getBrowserUserAgent()
61 // Firefox release calendar
62 // https://whattrainisitnow.com/calendar/
63 // https://wiki.mozilla.org/index.php?title=Release_Management/Calendar&redirect=no
65 static QByteArray ret
;
68 const std::chrono::time_point baseDate
= std::chrono::sys_days(2024y
/ 04 / 16);
69 const int baseVersion
= 125;
71 const std::chrono::time_point nowDate
= std::chrono::system_clock::now();
72 const int nowVersion
= baseVersion
+ std::chrono::duration_cast
<std::chrono::months
>(nowDate
- baseDate
).count();
74 QByteArray userAgentTemplate
= QByteArrayLiteral("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:%1.0) Gecko/20100101 Firefox/%1.0");
75 ret
= userAgentTemplate
.replace("%1", QByteArray::number(nowVersion
));
81 class Net::DownloadManager::NetworkCookieJar final
: public QNetworkCookieJar
84 explicit NetworkCookieJar(QObject
*parent
= nullptr)
85 : QNetworkCookieJar(parent
)
87 const QDateTime now
= QDateTime::currentDateTime();
88 QList
<QNetworkCookie
> cookies
= Preferences::instance()->getNetworkCookies();
89 cookies
.removeIf([&now
](const QNetworkCookie
&cookie
)
91 return cookie
.isSessionCookie() || (cookie
.expirationDate() <= now
);
94 setAllCookies(cookies
);
97 ~NetworkCookieJar() override
99 const QDateTime now
= QDateTime::currentDateTime();
100 QList
<QNetworkCookie
> cookies
= allCookies();
101 cookies
.removeIf([&now
](const QNetworkCookie
&cookie
)
103 return cookie
.isSessionCookie() || (cookie
.expirationDate() <= now
);
106 Preferences::instance()->setNetworkCookies(cookies
);
109 using QNetworkCookieJar::allCookies
;
110 using QNetworkCookieJar::setAllCookies
;
112 QList
<QNetworkCookie
> cookiesForUrl(const QUrl
&url
) const override
114 const QDateTime now
= QDateTime::currentDateTime();
115 QList
<QNetworkCookie
> cookies
= QNetworkCookieJar::cookiesForUrl(url
);
116 cookies
.removeIf([&now
](const QNetworkCookie
&cookie
)
118 return !cookie
.isSessionCookie() && (cookie
.expirationDate() <= now
);
124 bool setCookiesFromUrl(const QList
<QNetworkCookie
> &cookieList
, const QUrl
&url
) override
126 const QDateTime now
= QDateTime::currentDateTime();
127 QList
<QNetworkCookie
> cookies
= cookieList
;
128 cookies
.removeIf([&now
](const QNetworkCookie
&cookie
)
130 return !cookie
.isSessionCookie() && (cookie
.expirationDate() <= now
);
133 return QNetworkCookieJar::setCookiesFromUrl(cookies
, url
);
137 Net::DownloadManager
*Net::DownloadManager::m_instance
= nullptr;
139 Net::DownloadManager::DownloadManager(QObject
*parent
)
141 , m_networkCookieJar
{new NetworkCookieJar(this)}
142 , m_networkManager
{new QNetworkAccessManager(this)}
144 m_networkManager
->setCookieJar(m_networkCookieJar
);
145 connect(m_networkManager
, &QNetworkAccessManager::sslErrors
, this
146 , [](QNetworkReply
*reply
, const QList
<QSslError
> &errors
)
148 QStringList errorList
;
149 for (const QSslError
&error
: errors
)
150 errorList
+= error
.errorString();
153 if (!Preferences::instance()->isIgnoreSSLErrors())
155 errorMsg
= tr("SSL error, URL: \"%1\", errors: \"%2\"");
159 errorMsg
= tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"");
160 // Ignore all SSL errors
161 reply
->ignoreSslErrors();
164 LogMsg(errorMsg
.arg(reply
->url().toString(), errorList
.join(u
". ")), Log::WARNING
);
167 connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
168 , this, &DownloadManager::applyProxySettings
);
169 connect(Preferences::instance(), &Preferences::changed
, this, &DownloadManager::applyProxySettings
);
170 applyProxySettings();
173 void Net::DownloadManager::initInstance()
176 m_instance
= new DownloadManager
;
179 void Net::DownloadManager::freeInstance()
182 m_instance
= nullptr;
185 Net::DownloadManager
*Net::DownloadManager::instance()
190 Net::DownloadHandler
*Net::DownloadManager::download(const DownloadRequest
&downloadRequest
, const bool useProxy
)
192 // Process download request
193 const auto serviceID
= ServiceID::fromURL(downloadRequest
.url());
194 const bool isSequentialService
= m_sequentialServices
.contains(serviceID
);
196 auto *downloadHandler
= new DownloadHandlerImpl(this, downloadRequest
, useProxy
);
197 connect(downloadHandler
, &DownloadHandler::finished
, this, [this, serviceID
, downloadHandler
]
199 if (!downloadHandler
->assignedNetworkReply())
201 // DownloadHandler was finished (canceled) before QNetworkReply was assigned,
202 // so it's still in the queue. Just remove it from there.
203 m_waitingJobs
[serviceID
].removeOne(downloadHandler
);
206 downloadHandler
->deleteLater();
209 if (isSequentialService
&& m_busyServices
.contains(serviceID
))
211 m_waitingJobs
[serviceID
].enqueue(downloadHandler
);
215 qDebug("Downloading %s...", qUtf8Printable(downloadRequest
.url()));
216 if (isSequentialService
)
217 m_busyServices
.insert(serviceID
);
218 processRequest(downloadHandler
);
221 return downloadHandler
;
224 void Net::DownloadManager::registerSequentialService(const Net::ServiceID
&serviceID
, const std::chrono::seconds delay
)
226 m_sequentialServices
.insert(serviceID
, delay
);
229 QList
<QNetworkCookie
> Net::DownloadManager::cookiesForUrl(const QUrl
&url
) const
231 return m_networkCookieJar
->cookiesForUrl(url
);
234 bool Net::DownloadManager::setCookiesFromUrl(const QList
<QNetworkCookie
> &cookieList
, const QUrl
&url
)
236 return m_networkCookieJar
->setCookiesFromUrl(cookieList
, url
);
239 QList
<QNetworkCookie
> Net::DownloadManager::allCookies() const
241 return m_networkCookieJar
->allCookies();
244 void Net::DownloadManager::setAllCookies(const QList
<QNetworkCookie
> &cookieList
)
246 m_networkCookieJar
->setAllCookies(cookieList
);
249 bool Net::DownloadManager::deleteCookie(const QNetworkCookie
&cookie
)
251 return m_networkCookieJar
->deleteCookie(cookie
);
254 bool Net::DownloadManager::hasSupportedScheme(const QString
&url
)
256 const QStringList schemes
= QNetworkAccessManager().supportedSchemes();
257 return std::any_of(schemes
.cbegin(), schemes
.cend(), [&url
](const QString
&scheme
)
259 return url
.startsWith((scheme
+ u
':'), Qt::CaseInsensitive
);
263 void Net::DownloadManager::applyProxySettings()
265 const auto *proxyManager
= ProxyConfigurationManager::instance();
266 const ProxyConfiguration proxyConfig
= proxyManager
->proxyConfiguration();
268 m_proxy
= QNetworkProxy(QNetworkProxy::NoProxy
);
270 if ((proxyConfig
.type
== Net::ProxyType::None
) || (proxyConfig
.type
== ProxyType::SOCKS4
))
274 if (proxyConfig
.type
== ProxyType::SOCKS5
)
276 qDebug() << Q_FUNC_INFO
<< "using SOCKS proxy";
277 m_proxy
.setType(QNetworkProxy::Socks5Proxy
);
281 qDebug() << Q_FUNC_INFO
<< "using HTTP proxy";
282 m_proxy
.setType(QNetworkProxy::HttpProxy
);
285 m_proxy
.setHostName(proxyConfig
.ip
);
286 m_proxy
.setPort(proxyConfig
.port
);
289 if (proxyConfig
.authEnabled
)
291 qDebug("Proxy requires authentication, authenticating...");
292 m_proxy
.setUser(proxyConfig
.username
);
293 m_proxy
.setPassword(proxyConfig
.password
);
296 if (proxyConfig
.hostnameLookupEnabled
)
297 m_proxy
.setCapabilities(m_proxy
.capabilities() | QNetworkProxy::HostNameLookupCapability
);
299 m_proxy
.setCapabilities(m_proxy
.capabilities() & ~QNetworkProxy::HostNameLookupCapability
);
302 void Net::DownloadManager::processWaitingJobs(const ServiceID
&serviceID
)
304 const auto waitingJobsIter
= m_waitingJobs
.find(serviceID
);
305 if ((waitingJobsIter
== m_waitingJobs
.end()) || waitingJobsIter
.value().isEmpty())
307 // No more waiting jobs for given ServiceID
308 m_busyServices
.remove(serviceID
);
312 auto *handler
= waitingJobsIter
.value().dequeue();
313 qDebug("Downloading %s...", qUtf8Printable(handler
->url()));
314 processRequest(handler
);
317 void Net::DownloadManager::processRequest(DownloadHandlerImpl
*downloadHandler
)
319 m_networkManager
->setProxy((downloadHandler
->useProxy() == true) ? m_proxy
: QNetworkProxy(QNetworkProxy::NoProxy
));
321 const DownloadRequest downloadRequest
= downloadHandler
->downloadRequest();
322 QNetworkRequest request
{downloadRequest
.url()};
324 if (downloadRequest
.userAgent().isEmpty())
325 request
.setRawHeader("User-Agent", getBrowserUserAgent());
327 request
.setRawHeader("User-Agent", downloadRequest
.userAgent().toUtf8());
329 // Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
330 request
.setRawHeader("Referer", request
.url().toEncoded().data());
331 #ifdef QT_NO_COMPRESS
332 // The macro "QT_NO_COMPRESS" defined in QT will disable the zlib related features
333 // and reply data auto-decompression in QT will also be disabled. But we can support
334 // gzip encoding and manually decompress the reply data.
335 request
.setRawHeader("Accept-Encoding", "gzip");
337 // Qt doesn't support Magnet protocol so we need to handle redirections manually
338 request
.setAttribute(QNetworkRequest::RedirectPolicyAttribute
, QNetworkRequest::ManualRedirectPolicy
);
340 request
.setTransferTimeout();
342 QNetworkReply
*reply
= m_networkManager
->get(request
);
343 connect(reply
, &QNetworkReply::finished
, this, [this, serviceID
= ServiceID::fromURL(downloadHandler
->url())]
345 QTimer::singleShot(m_sequentialServices
.value(serviceID
, 0s
), this, [this, serviceID
] { processWaitingJobs(serviceID
); });
347 downloadHandler
->assignNetworkReply(reply
);
350 Net::DownloadRequest::DownloadRequest(const QString
&url
)
355 QString
Net::DownloadRequest::url() const
360 Net::DownloadRequest
&Net::DownloadRequest::url(const QString
&value
)
366 QString
Net::DownloadRequest::userAgent() const
371 Net::DownloadRequest
&Net::DownloadRequest::userAgent(const QString
&value
)
377 qint64
Net::DownloadRequest::limit() const
382 Net::DownloadRequest
&Net::DownloadRequest::limit(const qint64 value
)
388 bool Net::DownloadRequest::saveToFile() const
393 Net::DownloadRequest
&Net::DownloadRequest::saveToFile(const bool value
)
395 m_saveToFile
= value
;
399 Path
Net::DownloadRequest::destFileName() const
401 return m_destFileName
;
404 Net::DownloadRequest
&Net::DownloadRequest::destFileName(const Path
&value
)
406 m_destFileName
= value
;
410 Net::ServiceID
Net::ServiceID::fromURL(const QUrl
&url
)
412 return {url
.host(), url
.port(80)};
415 std::size_t Net::qHash(const ServiceID
&serviceID
, const std::size_t seed
)
417 return qHashMulti(seed
, serviceID
.hostName
, serviceID
.port
);
420 bool Net::operator==(const ServiceID
&lhs
, const ServiceID
&rhs
)
422 return ((lhs
.hostName
== rhs
.hostName
) && (lhs
.port
== rhs
.port
));