Rename Core to Base (Closes #3733).

This commit is contained in:
Vladimir Golovnev (Glassez)
2015-09-25 11:10:05 +03:00
parent 29b0200f62
commit 9db93e5d8f
156 changed files with 303 additions and 302 deletions

305
src/base/net/dnsupdater.cpp Normal file
View File

@@ -0,0 +1,305 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2011 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.
*
* Contact : chris@qbittorrent.org
*/
#include <QNetworkAccessManager>
#include <QDebug>
#include <QRegExp>
#include <QStringList>
#ifdef QBT_USES_QT5
#include <QUrlQuery>
#endif
#include "base/logger.h"
#include "dnsupdater.h"
using namespace Net;
DNSUpdater::DNSUpdater(QObject *parent)
: QObject(parent)
, m_state(OK)
, m_service(DNS::NONE)
{
updateCredentials();
// Load saved settings from previous session
const Preferences *const pref = Preferences::instance();
m_lastIPCheckTime = pref->getDNSLastUpd();
m_lastIP = QHostAddress(pref->getDNSLastIP());
// Start IP checking timer
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP()));
m_ipCheckTimer.start();
// Check lastUpdate to avoid flooding
if (!m_lastIPCheckTime.isValid()
|| (m_lastIPCheckTime.secsTo(QDateTime::currentDateTime()) * 1000 > IP_CHECK_INTERVAL_MS)) {
checkPublicIP();
}
}
DNSUpdater::~DNSUpdater()
{
// Save lastupdate time and last ip
Preferences *const pref = Preferences::instance();
pref->setDNSLastUpd(m_lastIPCheckTime);
pref->setDNSLastIP(m_lastIP.toString());
}
void DNSUpdater::checkPublicIP()
{
Q_ASSERT(m_state == OK);
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipRequestFinished(QNetworkReply *)));
m_lastIPCheckTime = QDateTime::currentDateTime();
QNetworkRequest request;
request.setUrl(QUrl("http://checkip.dyndns.org"));
request.setRawHeader("User-Agent", "qBittorrent/" VERSION);
manager->get(request);
}
void DNSUpdater::ipRequestFinished(QNetworkReply *reply)
{
qDebug() << Q_FUNC_INFO;
if (reply->error()) {
// Error
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
}
else {
// Parse response
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
QString ret = reply->readAll();
if (ipregex.indexIn(ret) >= 0) {
QString ip_str = ipregex.cap(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str;
QHostAddress new_ip(ip_str);
if (!new_ip.isNull()) {
if (m_lastIP != new_ip) {
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
qDebug() << m_lastIP.toString() << "->" << new_ip.toString();
m_lastIP = new_ip;
updateDNSService();
}
}
else {
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
}
}
else {
qWarning() << Q_FUNC_INFO << "Regular expression failed to capture the IP address";
}
}
// Clean up
reply->deleteLater();
sender()->deleteLater();
}
void DNSUpdater::updateDNSService()
{
qDebug() << Q_FUNC_INFO;
// Prepare request
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipUpdateFinished(QNetworkReply *)));
m_lastIPCheckTime = QDateTime::currentDateTime();
QNetworkRequest request;
request.setUrl(getUpdateUrl());
request.setRawHeader("User-Agent", "qBittorrent/" VERSION);
manager->get(request);
}
QUrl DNSUpdater::getUpdateUrl() const
{
QUrl url;
#ifdef QT_NO_OPENSSL
url.setScheme("http");
#else
url.setScheme("https");
#endif
url.setUserName(m_username);
url.setPassword(m_password);
Q_ASSERT(!m_lastIP.isNull());
// Service specific
switch(m_service) {
case DNS::DYNDNS:
url.setHost("members.dyndns.org");
break;
case DNS::NOIP:
url.setHost("dynupdate.no-ip.com");
break;
default:
qWarning() << "Unrecognized Dynamic DNS service!";
Q_ASSERT(0);
}
url.setPath("/nic/update");
#ifndef QBT_USES_QT5
url.addQueryItem("hostname", m_domain);
url.addQueryItem("myip", m_lastIP.toString());
#else
QUrlQuery urlQuery(url);
urlQuery.addQueryItem("hostname", m_domain);
urlQuery.addQueryItem("myip", m_lastIP.toString());
url.setQuery(urlQuery);
#endif
Q_ASSERT(url.isValid());
qDebug() << Q_FUNC_INFO << url.toString();
return url;
}
void DNSUpdater::ipUpdateFinished(QNetworkReply *reply)
{
if (reply->error()) {
// Error
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
}
else {
// Parse reply
processIPUpdateReply(reply->readAll());
}
// Clean up
reply->deleteLater();
sender()->deleteLater();
}
void DNSUpdater::processIPUpdateReply(const QString &reply)
{
Logger *const logger = Logger::instance();
qDebug() << Q_FUNC_INFO << reply;
QString code = reply.split(" ").first();
qDebug() << Q_FUNC_INFO << "Code:" << code;
if (code == "good" || code == "nochg") {
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
return;
}
if ((code == "911") || (code == "dnserr")) {
logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL);
m_lastIP.clear();
// It will retry in 30 minutes because the timer was not stopped
return;
}
// Everything bellow is an error, stop updating until the user updates something
m_ipCheckTimer.stop();
m_lastIP.clear();
if (code == "nohost") {
logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL);
m_state = INVALID_CREDS;
return;
}
if (code == "badauth") {
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
m_state = INVALID_CREDS;
return;
}
if (code == "badagent") {
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
Log::CRITICAL);
m_state = FATAL;
return;
}
if (code == "!donator") {
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"),
Log::CRITICAL);
m_state = FATAL;
return;
}
if (code == "abuse") {
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
m_state = FATAL;
return;
}
}
void DNSUpdater::updateCredentials()
{
if (m_state == FATAL) return;
Preferences *const pref = Preferences::instance();
Logger *const logger = Logger::instance();
bool change = false;
// Get DNS service information
if (m_service != pref->getDynDNSService()) {
m_service = pref->getDynDNSService();
change = true;
}
if (m_domain != pref->getDynDomainName()) {
m_domain = pref->getDynDomainName();
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
if (domain_regex.indexIn(m_domain) < 0) {
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
m_lastIP.clear();
m_ipCheckTimer.stop();
m_state = INVALID_CREDS;
return;
}
change = true;
}
if (m_username != pref->getDynDNSUsername()) {
m_username = pref->getDynDNSUsername();
if (m_username.length() < 4) {
logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL);
m_lastIP.clear();
m_ipCheckTimer.stop();
m_state = INVALID_CREDS;
return;
}
change = true;
}
if (m_password != pref->getDynDNSPassword()) {
m_password = pref->getDynDNSPassword();
if (m_password.length() < 4) {
logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL);
m_lastIP.clear();
m_ipCheckTimer.stop();
m_state = INVALID_CREDS;
return;
}
change = true;
}
if ((m_state == INVALID_CREDS) && change) {
m_state = OK; // Try again
m_ipCheckTimer.start();
checkPublicIP();
}
}
QUrl DNSUpdater::getRegistrationUrl(int service)
{
switch(service) {
case DNS::DYNDNS:
return QUrl("https://www.dyndns.com/account/services/hosts/add.html");
case DNS::NOIP:
return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html");
default:
Q_ASSERT(0);
}
return QUrl();
}

90
src/base/net/dnsupdater.h Normal file
View File

@@ -0,0 +1,90 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2011 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.
*
* Contact : chris@qbittorrent.org
*/
#ifndef DNSUPDATER_H
#define DNSUPDATER_H
#include <QObject>
#include <QHostAddress>
#include <QNetworkReply>
#include <QDateTime>
#include <QTimer>
#include "base/preferences.h"
namespace Net
{
// Based on http://www.dyndns.com/developers/specs/
class DNSUpdater : public QObject
{
Q_OBJECT
public:
explicit DNSUpdater(QObject *parent = 0);
~DNSUpdater();
static QUrl getRegistrationUrl(int service);
public slots:
void updateCredentials();
private slots:
void checkPublicIP();
void ipRequestFinished(QNetworkReply *reply);
void updateDNSService();
void ipUpdateFinished(QNetworkReply *reply);
private:
QUrl getUpdateUrl() const;
void processIPUpdateReply(const QString &reply);
private:
QHostAddress m_lastIP;
QDateTime m_lastIPCheckTime;
QTimer m_ipCheckTimer;
int m_state;
// Service creds
DNS::Service m_service;
QString m_domain;
QString m_username;
QString m_password;
private:
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
enum State
{
OK,
INVALID_CREDS,
FATAL
};
};
}
#endif // DNSUPDATER_H

View File

@@ -0,0 +1,248 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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 <QTemporaryFile>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkProxy>
#include <QNetworkCookie>
#include <QUrl>
#include <QDebug>
#include "base/utils/fs.h"
#include "base/utils/gzip.h"
#include "base/utils/misc.h"
#include "downloadmanager.h"
#include "downloadhandler.h"
static QString errorCodeToString(QNetworkReply::NetworkError status);
using namespace Net;
DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile, qint64 limit, bool handleRedirectToMagnet)
: QObject(manager)
, m_reply(reply)
, m_manager(manager)
, m_saveToFile(saveToFile)
, m_sizeLimit(limit)
, m_handleRedirectToMagnet(handleRedirectToMagnet)
, m_url(reply->url().toString())
{
init();
}
DownloadHandler::~DownloadHandler()
{
if (m_reply)
delete m_reply;
}
// Returns original url
QString DownloadHandler::url() const
{
return m_url;
}
void DownloadHandler::processFinishedDownload()
{
QString url = m_reply->url().toString();
qDebug("Download finished: %s", qPrintable(url));
// Check if the request was successful
if (m_reply->error() != QNetworkReply::NoError) {
// Failure
qDebug("Download failure (%s), reason: %s", qPrintable(url), qPrintable(errorCodeToString(m_reply->error())));
emit downloadFailed(m_url, errorCodeToString(m_reply->error()));
this->deleteLater();
}
else {
// Check if the server ask us to redirect somewhere else
const QVariant redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirection.isValid()) {
// We should redirect
handleRedirection(redirection.toUrl());
}
else {
// Success
QByteArray replyData = m_reply->readAll();
if (m_reply->rawHeader("Content-Encoding") == "gzip") {
// uncompress gzip reply
Utils::Gzip::uncompress(replyData, replyData);
}
if (m_saveToFile) {
QString filePath;
if (saveToFile(replyData, filePath))
emit downloadFinished(m_url, filePath);
else
emit downloadFailed(m_url, tr("I/O Error"));
}
else {
emit downloadFinished(m_url, replyData);
}
this->deleteLater();
}
}
}
void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
{
QString msg = tr("The file size is %1. It exceeds the download limit of %2.");
if (bytesTotal > 0) {
// Total number of bytes is available
if (bytesTotal > m_sizeLimit) {
m_reply->abort();
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal)).arg(Utils::Misc::friendlyUnit(m_sizeLimit)));
}
else {
disconnect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(checkDownloadSize(qint64, qint64)));
}
}
else if (bytesReceived > m_sizeLimit) {
m_reply->abort();
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived)).arg(Utils::Misc::friendlyUnit(m_sizeLimit)));
}
}
void DownloadHandler::init()
{
m_reply->setParent(this);
if (m_sizeLimit > 0)
connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(checkDownloadSize(qint64, qint64)));
connect(m_reply, SIGNAL(finished()), this, SLOT(processFinishedDownload()));
}
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
{
QTemporaryFile *tmpfile = new QTemporaryFile;
if (!tmpfile->open()) {
delete tmpfile;
return false;
}
tmpfile->setAutoRemove(false);
filePath = tmpfile->fileName();
qDebug("Temporary filename is: %s", qPrintable(filePath));
if (m_reply->isOpen() || m_reply->open(QIODevice::ReadOnly)) {
tmpfile->write(replyData);
tmpfile->close();
// XXX: tmpfile needs to be deleted on Windows before using the file
// or it will complain that the file is used by another process.
delete tmpfile;
return true;
}
else {
delete tmpfile;
Utils::Fs::forceRemove(filePath);
}
return false;
}
void DownloadHandler::handleRedirection(QUrl newUrl)
{
// Resolve relative urls
if (newUrl.isRelative())
newUrl = m_reply->url().resolved(newUrl);
const QString newUrlString = newUrl.toString();
qDebug("Redirecting from %s to %s", qPrintable(m_reply->url().toString()), qPrintable(newUrlString));
// Redirect to magnet workaround
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) {
qDebug("Magnet redirect detected.");
m_reply->abort();
if (m_handleRedirectToMagnet)
emit redirectedToMagnet(m_url, newUrlString);
else
emit downloadFailed(m_url, tr("Unexpected redirect to magnet URI."));
this->deleteLater();
}
else {
DownloadHandler *tmp = m_manager->downloadUrl(newUrlString, m_saveToFile, m_sizeLimit, m_handleRedirectToMagnet);
m_reply->deleteLater();
m_reply = tmp->m_reply;
init();
tmp->m_reply = 0;
delete tmp;
}
}
QString errorCodeToString(QNetworkReply::NetworkError status)
{
switch(status) {
case QNetworkReply::HostNotFoundError:
return QObject::tr("The remote host name was not found (invalid hostname)");
case QNetworkReply::OperationCanceledError:
return QObject::tr("The operation was canceled");
case QNetworkReply::RemoteHostClosedError:
return QObject::tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
case QNetworkReply::TimeoutError:
return QObject::tr("The connection to the remote server timed out");
case QNetworkReply::SslHandshakeFailedError:
return QObject::tr("SSL/TLS handshake failed");
case QNetworkReply::ConnectionRefusedError:
return QObject::tr("The remote server refused the connection");
case QNetworkReply::ProxyConnectionRefusedError:
return QObject::tr("The connection to the proxy server was refused");
case QNetworkReply::ProxyConnectionClosedError:
return QObject::tr("The proxy server closed the connection prematurely");
case QNetworkReply::ProxyNotFoundError:
return QObject::tr("The proxy host name was not found");
case QNetworkReply::ProxyTimeoutError:
return QObject::tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
case QNetworkReply::ProxyAuthenticationRequiredError:
return QObject::tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
case QNetworkReply::ContentAccessDenied:
return QObject::tr("The access to the remote content was denied (401)");
case QNetworkReply::ContentOperationNotPermittedError:
return QObject::tr("The operation requested on the remote content is not permitted");
case QNetworkReply::ContentNotFoundError:
return QObject::tr("The remote content was not found at the server (404)");
case QNetworkReply::AuthenticationRequiredError:
return QObject::tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
case QNetworkReply::ProtocolUnknownError:
return QObject::tr("The Network Access API cannot honor the request because the protocol is not known");
case QNetworkReply::ProtocolInvalidOperationError:
return QObject::tr("The requested operation is invalid for this protocol");
case QNetworkReply::UnknownNetworkError:
return QObject::tr("An unknown network-related error was detected");
case QNetworkReply::UnknownProxyError:
return QObject::tr("An unknown proxy-related error was detected");
case QNetworkReply::UnknownContentError:
return QObject::tr("An unknown error related to the remote content was detected");
case QNetworkReply::ProtocolFailure:
return QObject::tr("A breakdown in protocol was detected");
default:
return QObject::tr("Unknown error");
}
}

View File

@@ -0,0 +1,79 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#ifndef NET_DOWNLOADHANDLER_H
#define NET_DOWNLOADHANDLER_H
#include <QObject>
QT_BEGIN_NAMESPACE
class QNetworkAccessManager;
class QNetworkReply;
class QUrl;
QT_END_NAMESPACE
namespace Net
{
class DownloadManager;
class DownloadHandler : public QObject
{
Q_OBJECT
public:
DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false);
~DownloadHandler();
QString url() const;
signals:
void downloadFinished(const QString &url, const QByteArray &data);
void downloadFinished(const QString &url, const QString &filePath);
void downloadFailed(const QString &url, const QString &reason);
void redirectedToMagnet(const QString &url, const QString &magnetUri);
private slots:
void processFinishedDownload();
void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal);
private:
void init();
bool saveToFile(const QByteArray &replyData, QString &filePath);
void handleRedirection(QUrl newUrl);
QNetworkReply *m_reply;
DownloadManager *m_manager;
bool m_saveToFile;
qint64 m_sizeLimit;
bool m_handleRedirectToMagnet;
QString m_url;
};
}
#endif // NET_DOWNLOADHANDLER_H

View File

@@ -0,0 +1,151 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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 <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkCookieJar>
#include <QNetworkReply>
#include <QNetworkCookie>
#include <QSslError>
#include <QUrl>
#include <QDebug>
#include "base/preferences.h"
#include "downloadhandler.h"
#include "downloadmanager.h"
using namespace Net;
DownloadManager *DownloadManager::m_instance = 0;
DownloadManager::DownloadManager(QObject *parent)
: QObject(parent)
{
#ifndef QT_NO_OPENSSL
connect(&m_networkManager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)), this, SLOT(ignoreSslErrors(QNetworkReply *, QList<QSslError>)));
#endif
}
DownloadManager::~DownloadManager()
{
}
void DownloadManager::initInstance()
{
if (!m_instance)
m_instance = new DownloadManager;
}
void DownloadManager::freeInstance()
{
if (m_instance) {
delete m_instance;
m_instance = 0;
}
}
DownloadManager *DownloadManager::instance()
{
return m_instance;
}
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet)
{
// Update proxy settings
applyProxySettings();
// Process download request
qDebug("url is %s", qPrintable(url));
const QUrl qurl = QUrl::fromEncoded(url.toUtf8());
QNetworkRequest request(qurl);
// Spoof Firefox 38 user agent to avoid web server banning
request.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0");
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
request.setRawHeader("Referer", request.url().toEncoded().data());
qDebug("Downloading %s...", request.url().toEncoded().data());
// accept gzip
request.setRawHeader("Accept-Encoding", "gzip");
return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet);
}
QList<QNetworkCookie> DownloadManager::cookiesForUrl(const QString &url) const
{
return m_networkManager.cookieJar()->cookiesForUrl(url);
}
bool DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
qDebug("Setting %d cookies for url: %s", cookieList.size(), qPrintable(url.toString()));
return m_networkManager.cookieJar()->setCookiesFromUrl(cookieList, url);
}
void DownloadManager::applyProxySettings()
{
QNetworkProxy proxy;
const Preferences* const pref = Preferences::instance();
if (pref->isProxyEnabled() && !pref->isProxyOnlyForTorrents()) {
// Proxy enabled
proxy.setHostName(pref->getProxyIp());
proxy.setPort(pref->getProxyPort());
// Default proxy type is HTTP, we must change if it is SOCKS5
const int proxyType = pref->getProxyType();
if ((proxyType == Proxy::SOCKS5) || (proxyType == Proxy::SOCKS5_PW)) {
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
proxy.setType(QNetworkProxy::Socks5Proxy);
}
else {
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
proxy.setType(QNetworkProxy::HttpProxy);
}
// Authentication?
if (pref->isProxyAuthEnabled()) {
qDebug("Proxy requires authentication, authenticating");
proxy.setUser(pref->getProxyUsername());
proxy.setPassword(pref->getProxyPassword());
}
}
else {
proxy.setType(QNetworkProxy::NoProxy);
}
m_networkManager.setProxy(proxy);
}
#ifndef QT_NO_OPENSSL
void DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{
Q_UNUSED(errors)
// Ignore all SSL errors
reply->ignoreSslErrors();
}
#endif

View File

@@ -0,0 +1,76 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#ifndef NET_DOWNLOADMANAGER_H
#define NET_DOWNLOADMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
QT_BEGIN_NAMESPACE
class QNetworkReply;
class QNetworkCookie;
class QSslError;
class QUrl;
QT_END_NAMESPACE
namespace Net
{
class DownloadHandler;
class DownloadManager : public QObject
{
Q_OBJECT
public:
static void initInstance();
static void freeInstance();
static DownloadManager *instance();
DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false);
QList<QNetworkCookie> cookiesForUrl(const QString &url) const;
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url);
private slots:
#ifndef QT_NO_OPENSSL
void ignoreSslErrors(QNetworkReply *,const QList<QSslError> &);
#endif
private:
DownloadManager(QObject *parent = 0);
~DownloadManager();
void applyProxySettings();
static DownloadManager *m_instance;
QNetworkAccessManager m_networkManager;
};
}
#endif // NET_DOWNLOADMANAGER_H

View File

@@ -0,0 +1,461 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* 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 <QDebug>
#include <QFile>
#include <QDir>
#include <QHostAddress>
#include <QDateTime>
#include "base/logger.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
#include "base/utils/gzip.h"
#include "downloadmanager.h"
#include "downloadhandler.h"
#include "private/geoipdatabase.h"
#include "geoipmanager.h"
static const char DATABASE_URL[] = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz";
static const char GEOIP_FOLDER[] = "GeoIP";
static const char GEOIP_FILENAME[] = "GeoLite2-Country.mmdb";
static const int CACHE_SIZE = 1000;
static const int UPDATE_INTERVAL = 30; // Days between database updates
using namespace Net;
// GeoIPManager
GeoIPManager *GeoIPManager::m_instance = 0;
GeoIPManager::GeoIPManager()
: m_enabled(false)
, m_geoIPDatabase(0)
{
configure();
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
}
GeoIPManager::~GeoIPManager()
{
if (m_geoIPDatabase)
delete m_geoIPDatabase;
}
void GeoIPManager::initInstance()
{
if (!m_instance)
m_instance = new GeoIPManager;
}
void GeoIPManager::freeInstance()
{
if (m_instance) {
delete m_instance;
m_instance = 0;
}
}
GeoIPManager *GeoIPManager::instance()
{
return m_instance;
}
void GeoIPManager::loadDatabase()
{
if (m_geoIPDatabase) {
delete m_geoIPDatabase;
m_geoIPDatabase = 0;
}
QString filepath = Utils::Fs::expandPathAbs(
QString("%1%2/%3").arg(Utils::Fs::QDesktopServicesDataLocation())
.arg(GEOIP_FOLDER).arg(GEOIP_FILENAME));
QString error;
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
if (m_geoIPDatabase)
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
else
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
manageDatabaseUpdate();
}
void GeoIPManager::manageDatabaseUpdate()
{
if (!m_geoIPDatabase || (m_geoIPDatabase->buildEpoch().daysTo(QDateTime::currentDateTimeUtc()) >= UPDATE_INTERVAL))
downloadDatabaseFile();
}
void GeoIPManager::downloadDatabaseFile()
{
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(downloadFinished(QString, QByteArray)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(downloadFailed(QString, QString)));
}
QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
{
if (m_enabled && m_geoIPDatabase)
return m_geoIPDatabase->lookup(hostAddr);
return QString();
}
// http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
QString GeoIPManager::CountryName(const QString &countryISOCode)
{
static QHash<QString, QString> countries;
static bool initialized = false;
if (!initialized) {
countries[QString()] = tr("N/A");
countries["AP"] = tr("Asia/Pacific Region");
countries["EU"] = tr("Europe");
countries["AD"] = tr("Andorra");
countries["AE"] = tr("United Arab Emirates");
countries["AF"] = tr("Afghanistan");
countries["AG"] = tr("Antigua and Barbuda");
countries["AI"] = tr("Anguilla");
countries["AL"] = tr("Albania");
countries["AM"] = tr("Armenia");
countries["AN"] = tr("Netherlands Antilles");
countries["AO"] = tr("Angola");
countries["AQ"] = tr("Antarctica");
countries["AR"] = tr("Argentina");
countries["AS"] = tr("American Samoa");
countries["AT"] = tr("Austria");
countries["AU"] = tr("Australia");
countries["AW"] = tr("Aruba");
countries["AZ"] = tr("Azerbaijan");
countries["BA"] = tr("Bosnia and Herzegovina");
countries["BB"] = tr("Barbados");
countries["BD"] = tr("Bangladesh");
countries["BE"] = tr("Belgium");
countries["BF"] = tr("Burkina Faso");
countries["BG"] = tr("Bulgaria");
countries["BH"] = tr("Bahrain");
countries["BI"] = tr("Burundi");
countries["BJ"] = tr("Benin");
countries["BM"] = tr("Bermuda");
countries["BN"] = tr("Brunei Darussalam");
countries["BO"] = tr("Bolivia");
countries["BR"] = tr("Brazil");
countries["BS"] = tr("Bahamas");
countries["BT"] = tr("Bhutan");
countries["BV"] = tr("Bouvet Island");
countries["BW"] = tr("Botswana");
countries["BY"] = tr("Belarus");
countries["BZ"] = tr("Belize");
countries["CA"] = tr("Canada");
countries["CC"] = tr("Cocos (Keeling) Islands");
countries["CD"] = tr("Congo, The Democratic Republic of the");
countries["CF"] = tr("Central African Republic");
countries["CG"] = tr("Congo");
countries["CH"] = tr("Switzerland");
countries["CI"] = tr("Cote D'Ivoire");
countries["CK"] = tr("Cook Islands");
countries["CL"] = tr("Chile");
countries["CM"] = tr("Cameroon");
countries["CN"] = tr("China");
countries["CO"] = tr("Colombia");
countries["CR"] = tr("Costa Rica");
countries["CU"] = tr("Cuba");
countries["CV"] = tr("Cape Verde");
countries["CX"] = tr("Christmas Island");
countries["CY"] = tr("Cyprus");
countries["CZ"] = tr("Czech Republic");
countries["DE"] = tr("Germany");
countries["DJ"] = tr("Djibouti");
countries["DK"] = tr("Denmark");
countries["DM"] = tr("Dominica");
countries["DO"] = tr("Dominican Republic");
countries["DZ"] = tr("Algeria");
countries["EC"] = tr("Ecuador");
countries["EE"] = tr("Estonia");
countries["EG"] = tr("Egypt");
countries["EH"] = tr("Western Sahara");
countries["ER"] = tr("Eritrea");
countries["ES"] = tr("Spain");
countries["ET"] = tr("Ethiopia");
countries["FI"] = tr("Finland");
countries["FJ"] = tr("Fiji");
countries["FK"] = tr("Falkland Islands (Malvinas)");
countries["FM"] = tr("Micronesia, Federated States of");
countries["FO"] = tr("Faroe Islands");
countries["FR"] = tr("France");
countries["FX"] = tr("France, Metropolitan");
countries["GA"] = tr("Gabon");
countries["GB"] = tr("United Kingdom");
countries["GD"] = tr("Grenada");
countries["GE"] = tr("Georgia");
countries["GF"] = tr("French Guiana");
countries["GH"] = tr("Ghana");
countries["GI"] = tr("Gibraltar");
countries["GL"] = tr("Greenland");
countries["GM"] = tr("Gambia");
countries["GN"] = tr("Guinea");
countries["GP"] = tr("Guadeloupe");
countries["GQ"] = tr("Equatorial Guinea");
countries["GR"] = tr("Greece");
countries["GS"] = tr("South Georgia and the South Sandwich Islands");
countries["GT"] = tr("Guatemala");
countries["GU"] = tr("Guam");
countries["GW"] = tr("Guinea-Bissau");
countries["GY"] = tr("Guyana");
countries["HK"] = tr("Hong Kong");
countries["HM"] = tr("Heard Island and McDonald Islands");
countries["HN"] = tr("Honduras");
countries["HR"] = tr("Croatia");
countries["HT"] = tr("Haiti");
countries["HU"] = tr("Hungary");
countries["ID"] = tr("Indonesia");
countries["IE"] = tr("Ireland");
countries["IL"] = tr("Israel");
countries["IN"] = tr("India");
countries["IO"] = tr("British Indian Ocean Territory");
countries["IQ"] = tr("Iraq");
countries["IR"] = tr("Iran, Islamic Republic of");
countries["IS"] = tr("Iceland");
countries["IT"] = tr("Italy");
countries["JM"] = tr("Jamaica");
countries["JO"] = tr("Jordan");
countries["JP"] = tr("Japan");
countries["KE"] = tr("Kenya");
countries["KG"] = tr("Kyrgyzstan");
countries["KH"] = tr("Cambodia");
countries["KI"] = tr("Kiribati");
countries["KM"] = tr("Comoros");
countries["KN"] = tr("Saint Kitts and Nevis");
countries["KP"] = tr("Korea, Democratic People's Republic of");
countries["KR"] = tr("Korea, Republic of");
countries["KW"] = tr("Kuwait");
countries["KY"] = tr("Cayman Islands");
countries["KZ"] = tr("Kazakhstan");
countries["LA"] = tr("Lao People's Democratic Republic");
countries["LB"] = tr("Lebanon");
countries["LC"] = tr("Saint Lucia");
countries["LI"] = tr("Liechtenstein");
countries["LK"] = tr("Sri Lanka");
countries["LR"] = tr("Liberia");
countries["LS"] = tr("Lesotho");
countries["LT"] = tr("Lithuania");
countries["LU"] = tr("Luxembourg");
countries["LV"] = tr("Latvia");
countries["LY"] = tr("Libyan Arab Jamahiriya");
countries["MA"] = tr("Morocco");
countries["MC"] = tr("Monaco");
countries["MD"] = tr("Moldova, Republic of");
countries["MG"] = tr("Madagascar");
countries["MH"] = tr("Marshall Islands");
countries["MK"] = tr("Macedonia");
countries["ML"] = tr("Mali");
countries["MM"] = tr("Myanmar");
countries["MN"] = tr("Mongolia");
countries["MO"] = tr("Macau");
countries["MP"] = tr("Northern Mariana Islands");
countries["MQ"] = tr("Martinique");
countries["MR"] = tr("Mauritania");
countries["MS"] = tr("Montserrat");
countries["MT"] = tr("Malta");
countries["MU"] = tr("Mauritius");
countries["MV"] = tr("Maldives");
countries["MW"] = tr("Malawi");
countries["MX"] = tr("Mexico");
countries["MY"] = tr("Malaysia");
countries["MZ"] = tr("Mozambique");
countries["NA"] = tr("Namibia");
countries["NC"] = tr("New Caledonia");
countries["NE"] = tr("Niger");
countries["NF"] = tr("Norfolk Island");
countries["NG"] = tr("Nigeria");
countries["NI"] = tr("Nicaragua");
countries["NL"] = tr("Netherlands");
countries["NO"] = tr("Norway");
countries["NP"] = tr("Nepal");
countries["NR"] = tr("Nauru");
countries["NU"] = tr("Niue");
countries["NZ"] = tr("New Zealand");
countries["OM"] = tr("Oman");
countries["PA"] = tr("Panama");
countries["PE"] = tr("Peru");
countries["PF"] = tr("French Polynesia");
countries["PG"] = tr("Papua New Guinea");
countries["PH"] = tr("Philippines");
countries["PK"] = tr("Pakistan");
countries["PL"] = tr("Poland");
countries["PM"] = tr("Saint Pierre and Miquelon");
countries["PN"] = tr("Pitcairn Islands");
countries["PR"] = tr("Puerto Rico");
countries["PS"] = tr("Palestinian Territory");
countries["PT"] = tr("Portugal");
countries["PW"] = tr("Palau");
countries["PY"] = tr("Paraguay");
countries["QA"] = tr("Qatar");
countries["RE"] = tr("Reunion");
countries["RO"] = tr("Romania");
countries["RU"] = tr("Russian Federation");
countries["RW"] = tr("Rwanda");
countries["SA"] = tr("Saudi Arabia");
countries["SB"] = tr("Solomon Islands");
countries["SC"] = tr("Seychelles");
countries["SD"] = tr("Sudan");
countries["SE"] = tr("Sweden");
countries["SG"] = tr("Singapore");
countries["SH"] = tr("Saint Helena");
countries["SI"] = tr("Slovenia");
countries["SJ"] = tr("Svalbard and Jan Mayen");
countries["SK"] = tr("Slovakia");
countries["SL"] = tr("Sierra Leone");
countries["SM"] = tr("San Marino");
countries["SN"] = tr("Senegal");
countries["SO"] = tr("Somalia");
countries["SR"] = tr("Suriname");
countries["ST"] = tr("Sao Tome and Principe");
countries["SV"] = tr("El Salvador");
countries["SY"] = tr("Syrian Arab Republic");
countries["SZ"] = tr("Swaziland");
countries["TC"] = tr("Turks and Caicos Islands");
countries["TD"] = tr("Chad");
countries["TF"] = tr("French Southern Territories");
countries["TG"] = tr("Togo");
countries["TH"] = tr("Thailand");
countries["TJ"] = tr("Tajikistan");
countries["TK"] = tr("Tokelau");
countries["TM"] = tr("Turkmenistan");
countries["TN"] = tr("Tunisia");
countries["TO"] = tr("Tonga");
countries["TL"] = tr("Timor-Leste");
countries["TR"] = tr("Turkey");
countries["TT"] = tr("Trinidad and Tobago");
countries["TV"] = tr("Tuvalu");
countries["TW"] = tr("Taiwan");
countries["TZ"] = tr("Tanzania, United Republic of");
countries["UA"] = tr("Ukraine");
countries["UG"] = tr("Uganda");
countries["UM"] = tr("United States Minor Outlying Islands");
countries["US"] = tr("United States");
countries["UY"] = tr("Uruguay");
countries["UZ"] = tr("Uzbekistan");
countries["VA"] = tr("Holy See (Vatican City State)");
countries["VC"] = tr("Saint Vincent and the Grenadines");
countries["VE"] = tr("Venezuela");
countries["VG"] = tr("Virgin Islands, British");
countries["VI"] = tr("Virgin Islands, U.S.");
countries["VN"] = tr("Vietnam");
countries["VU"] = tr("Vanuatu");
countries["WF"] = tr("Wallis and Futuna");
countries["WS"] = tr("Samoa");
countries["YE"] = tr("Yemen");
countries["YT"] = tr("Mayotte");
countries["RS"] = tr("Serbia");
countries["ZA"] = tr("South Africa");
countries["ZM"] = tr("Zambia");
countries["ME"] = tr("Montenegro");
countries["ZW"] = tr("Zimbabwe");
countries["A1"] = tr("Anonymous Proxy");
countries["A2"] = tr("Satellite Provider");
countries["O1"] = tr("Other");
countries["AX"] = tr("Aland Islands");
countries["GG"] = tr("Guernsey");
countries["IM"] = tr("Isle of Man");
countries["JE"] = tr("Jersey");
countries["BL"] = tr("Saint Barthelemy");
countries["MF"] = tr("Saint Martin");
initialized = true;
}
return countries.value(countryISOCode, tr("N/A"));
}
void GeoIPManager::configure()
{
const bool enabled = Preferences::instance()->resolvePeerCountries();
if (m_enabled != enabled) {
m_enabled = enabled;
if (m_enabled && !m_geoIPDatabase) {
loadDatabase();
}
else if (!m_enabled && m_geoIPDatabase) {
delete m_geoIPDatabase;
m_geoIPDatabase = 0;
}
}
}
void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
{
Q_UNUSED(url);
if (!Utils::Gzip::uncompress(data, data)) {
Logger::instance()->addMessage(tr("Could not uncompress GeoIP database file."), Log::WARNING);
return;
}
QString error;
GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error);
if (geoIPDatabase) {
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch())) {
if (m_geoIPDatabase)
delete m_geoIPDatabase;
m_geoIPDatabase = geoIPDatabase;
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
QString targetPath = Utils::Fs::expandPathAbs(
Utils::Fs::QDesktopServicesDataLocation() + GEOIP_FOLDER);
if (!QDir(targetPath).exists())
QDir().mkpath(targetPath);
QFile targetFile(QString("%1/%2").arg(targetPath).arg(GEOIP_FILENAME));
if (!targetFile.open(QFile::WriteOnly) || (targetFile.write(data) == -1)) {
Logger::instance()->addMessage(
tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
}
else {
Logger::instance()->addMessage(tr("Successfully updated GeoIP database."), Log::INFO);
}
}
else {
delete geoIPDatabase;
}
}
else {
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
}
}
void GeoIPManager::downloadFailed(const QString &url, const QString &reason)
{
Q_UNUSED(url);
Logger::instance()->addMessage(tr("Couldn't download GeoIP database file. Reason: %1").arg(reason), Log::WARNING);
}

View File

@@ -0,0 +1,76 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#ifndef NET_GEOIPMANAGER_H
#define NET_GEOIPMANAGER_H
#include <QObject>
#include <QCache>
class QHostAddress;
class QString;
class GeoIPDatabase;
namespace Net
{
class GeoIPManager : public QObject
{
Q_OBJECT
public:
static void initInstance();
static void freeInstance();
static GeoIPManager *instance();
QString lookup(const QHostAddress &hostAddr) const;
static QString CountryName(const QString &countryISOCode);
private slots:
void configure();
void downloadFinished(const QString &url, QByteArray data);
void downloadFailed(const QString &url, const QString &reason);
private:
GeoIPManager();
~GeoIPManager();
void loadDatabase();
void manageDatabaseUpdate();
void downloadDatabaseFile();
bool m_enabled;
GeoIPDatabase *m_geoIPDatabase;
static GeoIPManager *m_instance;
};
}
#endif // NET_GEOIPMANAGER_H

View File

@@ -0,0 +1,122 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QDebug>
#include <libtorrent/session.hpp>
#include "base/logger.h"
#include "base/preferences.h"
#include "portforwarder.h"
namespace libt = libtorrent;
using namespace Net;
PortForwarder::PortForwarder(libtorrent::session *provider, QObject *parent)
: QObject(parent)
, m_active(false)
, m_provider(provider)
{
configure();
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
}
PortForwarder::~PortForwarder()
{
stop();
}
void PortForwarder::initInstance(libtorrent::session *const provider)
{
if (!m_instance)
m_instance = new PortForwarder(provider);
}
void PortForwarder::freeInstance()
{
if (m_instance) {
delete m_instance;
m_instance = 0;
}
}
PortForwarder *PortForwarder::instance()
{
return m_instance;
}
void PortForwarder::addPort(qint16 port)
{
if (!m_mappedPorts.contains(port)) {
m_mappedPorts.insert(port, 0);
if (m_active)
m_mappedPorts[port] = m_provider->add_port_mapping(libt::session::tcp, port, port);
}
}
void PortForwarder::deletePort(qint16 port)
{
if (m_mappedPorts.contains(port)) {
if (m_active)
m_provider->delete_port_mapping(m_mappedPorts[port]);
m_mappedPorts.remove(port);
}
}
void PortForwarder::configure()
{
bool enable = Preferences::instance()->isUPnPEnabled();
if (m_active != enable) {
if (enable)
start();
else
stop();
}
}
void PortForwarder::start()
{
qDebug("Enabling UPnP / NAT-PMP");
m_provider->start_upnp();
m_provider->start_natpmp();
foreach (qint16 port, m_mappedPorts.keys())
m_mappedPorts[port] = m_provider->add_port_mapping(libt::session::tcp, port, port);
m_active = true;
Logger::instance()->addMessage(tr("UPnP / NAT-PMP support [ON]"), Log::INFO);
}
void PortForwarder::stop()
{
qDebug("Disabling UPnP / NAT-PMP");
m_provider->stop_upnp();
m_provider->stop_natpmp();
m_active = false;
Logger::instance()->addMessage(tr("UPnP / NAT-PMP support [OFF]"), Log::INFO);
}
PortForwarder *PortForwarder::m_instance = 0;

View File

@@ -0,0 +1,73 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* 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.
*/
#ifndef NET_PORTFORWARDER_H
#define NET_PORTFORWARDER_H
#include <QObject>
#include <QHash>
namespace libtorrent
{
class session;
}
namespace Net
{
class PortForwarder : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(PortForwarder)
public:
static void initInstance(libtorrent::session *const provider);
static void freeInstance();
static PortForwarder *instance();
void addPort(qint16 port);
void deletePort(qint16 port);
private slots:
void configure();
private:
explicit PortForwarder(libtorrent::session *const provider, QObject *parent = 0);
~PortForwarder();
void start();
void stop();
bool m_active;
libtorrent::session *m_provider;
QHash<qint16, int> m_mappedPorts;
static PortForwarder *m_instance;
};
}
#endif // NET_PORTFORWARDER_H

View File

@@ -0,0 +1,520 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QDebug>
#include <QVariant>
#include <QHash>
#include <QHostAddress>
#include <QDateTime>
#include <QFile>
#include "base/types.h"
#include "geoipdatabase.h"
namespace
{
const quint32 __ENDIAN_TEST__ = 0x00000001;
const bool __IS_LITTLE_ENDIAN__ = (reinterpret_cast<const uchar *>(&__ENDIAN_TEST__)[0] == 0x01);
const int MAX_FILE_SIZE = 10485760; // 10MB
const char DB_TYPE[] = "GeoLite2-Country";
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
const char DATA_SECTION_SEPARATOR[16] = { 0 };
enum class DataType
{
Unknown = 0,
Pointer = 1,
String = 2,
Double = 3,
Bytes = 4,
Integer16 = 5,
Integer32 = 6,
Map = 7,
SignedInteger32 = 8,
Integer64 = 9,
Integer128 = 10,
Array = 11,
DataCacheContainer = 12,
EndMarker = 13,
Boolean = 14,
Float = 15
};
#ifndef QBT_USES_QT5
Q_IPV6ADDR createMappedAddress(quint32 ip4);
#endif
}
struct DataFieldDescriptor
{
DataType fieldType;
union
{
quint32 fieldSize;
quint32 offset; // Pointer
};
};
GeoIPDatabase::GeoIPDatabase(quint32 size)
: m_ipVersion(0)
, m_recordSize(0)
, m_nodeCount(0)
, m_nodeSize(0)
, m_indexSize(0)
, m_recordBytes(0)
, m_size(size)
, m_data(new uchar[size])
{
}
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
{
GeoIPDatabase *db = 0;
QFile file(filename);
if (file.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
}
if (!file.open(QFile::ReadOnly)) {
error = file.errorString();
return 0;
}
db = new GeoIPDatabase(file.size());
if (file.read((char *)db->m_data, db->m_size) != db->m_size) {
error = file.errorString();
delete db;
return 0;
}
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
delete db;
return 0;
}
return db;
}
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
{
GeoIPDatabase *db = 0;
if (data.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
}
db = new GeoIPDatabase(data.size());
memcpy((char *)db->m_data, data.constData(), db->m_size);
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
delete db;
return 0;
}
return db;
}
GeoIPDatabase::~GeoIPDatabase()
{
delete [] m_data;
}
QString GeoIPDatabase::type() const
{
return DB_TYPE;
}
quint16 GeoIPDatabase::ipVersion() const
{
return m_ipVersion;
}
QDateTime GeoIPDatabase::buildEpoch() const
{
return m_buildEpoch;
}
QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
{
#ifndef QBT_USES_QT5
Q_IPV6ADDR addr = hostAddr.protocol() == QAbstractSocket::IPv4Protocol
? createMappedAddress(hostAddr.toIPv4Address())
: hostAddr.toIPv6Address();
#else
Q_IPV6ADDR addr = hostAddr.toIPv6Address();
#endif
const uchar *ptr = m_data;
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 8; ++j) {
bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1);
// Interpret the left/right record as number
if (right)
ptr += m_recordBytes;
quint32 id = 0;
uchar *idPtr = reinterpret_cast<uchar *>(&id);
memcpy(&idPtr[4 - m_recordBytes], ptr, m_recordBytes);
fromBigEndian(idPtr, 4);
if (id == m_nodeCount) {
return QString();
}
else if (id > m_nodeCount) {
QString country = m_countries.value(id);
if (country.isEmpty()) {
const quint32 offset = id - m_nodeCount - sizeof(DATA_SECTION_SEPARATOR);
quint32 tmp = offset + m_indexSize + sizeof(DATA_SECTION_SEPARATOR);
QVariant val = readDataField(tmp);
if (val.userType() == QMetaType::QVariantHash) {
country = val.toHash()["country"].toHash()["iso_code"].toString();
m_countries[id] = country;
}
}
return country;
}
else {
ptr = m_data + (id * m_nodeSize);
}
}
}
return QString();
}
#define CHECK_METADATA_REQ(key, type) \
if (!metadata.contains(#key)) { \
error = errMsgNotFound.arg(#key); \
return false; \
} \
else if (metadata.value(#key).userType() != QMetaType::type) { \
error = errMsgInvalid.arg(#key); \
return false; \
}
#define CHECK_METADATA_OPT(key, type) \
if (metadata.contains(#key)) { \
if (metadata.value(#key).userType() != QMetaType::type) { \
error = errMsgInvalid.arg(#key); \
return false; \
} \
}
bool GeoIPDatabase::parseMetadata(const QVariantHash &metadata, QString &error)
{
const QString errMsgNotFound = tr("Metadata error: '%1' entry not found.");
const QString errMsgInvalid = tr("Metadata error: '%1' entry has invalid type.");
qDebug() << "Parsing MaxMindDB metadata...";
CHECK_METADATA_REQ(binary_format_major_version, UShort);
CHECK_METADATA_REQ(binary_format_minor_version, UShort);
uint versionMajor = metadata.value("binary_format_major_version").toUInt();
uint versionMinor = metadata.value("binary_format_minor_version").toUInt();
if (versionMajor != 2) {
error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor);
return false;
}
CHECK_METADATA_REQ(ip_version, UShort);
m_ipVersion = metadata.value("ip_version").value<quint16>();
if (m_ipVersion != 6) {
error = tr("Unsupported IP version: %1").arg(m_ipVersion);
return false;
}
CHECK_METADATA_REQ(record_size, UShort);
m_recordSize = metadata.value("record_size").value<quint16>();
if (m_recordSize != 24) {
error = tr("Unsupported record size: %1").arg(m_recordSize);
return false;
}
m_nodeSize = m_recordSize / 4;
m_recordBytes = m_nodeSize / 2;
CHECK_METADATA_REQ(node_count, UInt);
m_nodeCount = metadata.value("node_count").value<quint32>();
m_indexSize = m_nodeCount * m_nodeSize;
CHECK_METADATA_REQ(database_type, QString);
QString dbType = metadata.value("database_type").toString();
if (dbType != DB_TYPE) {
error = tr("Invalid database type: %1").arg(dbType);
return false;
}
CHECK_METADATA_REQ(build_epoch, ULongLong);
m_buildEpoch = QDateTime::fromTime_t(metadata.value("build_epoch").toULongLong());
CHECK_METADATA_OPT(languages, QVariantList);
CHECK_METADATA_OPT(description, QVariantHash);
return true;
}
bool GeoIPDatabase::loadDB(QString &error) const
{
qDebug() << "Parsing MaxMindDB index tree...";
const int nodeSize = m_recordSize / 4; // in bytes
const int indexSize = m_nodeCount * nodeSize;
if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR)))
|| (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0)) {
error = tr("Database corrupted: no data section found.");
return false;
}
return true;
}
QVariantHash GeoIPDatabase::readMetadata() const
{
const char *ptr = reinterpret_cast<const char *>(m_data);
quint32 size = m_size;
if (m_size > MAX_METADATA_SIZE) {
ptr += m_size - MAX_METADATA_SIZE;
size = MAX_METADATA_SIZE;
}
const QByteArray data = QByteArray::fromRawData(ptr, size);
int index = data.lastIndexOf(METADATA_BEGIN_MARK);
if (index >= 0) {
if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
quint32 offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
QVariant metadata = readDataField(offset);
if (metadata.userType() == QMetaType::QVariantHash)
return metadata.toHash();
}
return QVariantHash();
}
QVariant GeoIPDatabase::readDataField(quint32 &offset) const
{
DataFieldDescriptor descr;
if (!readDataFieldDescriptor(offset, descr))
return QVariant();
quint32 locOffset = offset;
bool usePointer = false;
if (descr.fieldType == DataType::Pointer) {
usePointer = true;
// convert offset from data section to global
locOffset = descr.offset + (m_nodeCount * m_recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
if (!readDataFieldDescriptor(locOffset, descr))
return QVariant();
}
QVariant fieldValue;
switch (descr.fieldType) {
case DataType::Pointer:
qDebug() << "* Illegal Pointer using";
break;
case DataType::String:
fieldValue = QString::fromUtf8(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Double:
if (descr.fieldSize == 8)
fieldValue = readPlainValue<double>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Double";
break;
case DataType::Bytes:
fieldValue = QByteArray(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Integer16:
fieldValue = readPlainValue<quint16>(locOffset, descr.fieldSize);
break;
case DataType::Integer32:
fieldValue = readPlainValue<quint32>(locOffset, descr.fieldSize);
break;
case DataType::Map:
fieldValue = readMapValue(locOffset, descr.fieldSize);
break;
case DataType::SignedInteger32:
fieldValue = readPlainValue<qint32>(locOffset, descr.fieldSize);
break;
case DataType::Integer64:
fieldValue = readPlainValue<quint64>(locOffset, descr.fieldSize);
break;
case DataType::Integer128:
qDebug() << "* Unsupported data type: Integer128";
break;
case DataType::Array:
fieldValue = readArrayValue(locOffset, descr.fieldSize);
break;
case DataType::DataCacheContainer:
qDebug() << "* Unsupported data type: DataCacheContainer";
break;
case DataType::EndMarker:
qDebug() << "* Unsupported data type: EndMarker";
break;
case DataType::Boolean:
fieldValue = QVariant::fromValue(static_cast<bool>(descr.fieldSize));
break;
case DataType::Float:
if (descr.fieldSize == 4)
fieldValue = readPlainValue<float>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Float";
break;
default:
qDebug() << "* Unsupported data type: Unknown";
}
if (!usePointer)
offset = locOffset;
return fieldValue;
}
bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out) const
{
const uchar *dataPtr = m_data + offset;
int availSize = m_size - offset;
if (availSize < 1) return false;
out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5);
if (out.fieldType == DataType::Pointer) {
int size = ((dataPtr[0] & 0x18) >> 3);
if (availSize < (size + 2)) return false;
if (size == 0)
out.offset = ((dataPtr[0] & 0x07) << 8) + dataPtr[1];
else if (size == 1)
out.offset = ((dataPtr[0] & 0x07) << 16) + (dataPtr[1] << 8) + dataPtr[2] + 2048;
else if (size == 2)
out.offset = ((dataPtr[0] & 0x07) << 24) + (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 526336;
else if (size == 3)
out.offset = (dataPtr[1] << 24) + (dataPtr[2] << 16) + (dataPtr[3] << 8) + dataPtr[4];
offset += size + 2;
return true;
}
out.fieldSize = dataPtr[0] & 0x1F;
if (out.fieldSize <= 28) {
if (out.fieldType == DataType::Unknown) {
out.fieldType = static_cast<DataType>(dataPtr[1] + 7);
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3))
return false;
offset += 2;
}
else {
offset += 1;
}
}
else if (out.fieldSize == 29) {
if (availSize < 2) return false;
out.fieldSize = dataPtr[1] + 29;
offset += 2;
}
else if (out.fieldSize == 30) {
if (availSize < 3) return false;
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
offset += 3;
}
else if (out.fieldSize == 31) {
if (availSize < 4) return false;
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821;
offset += 4;
}
return true;
}
void GeoIPDatabase::fromBigEndian(uchar *buf, quint32 len) const
{
if (__IS_LITTLE_ENDIAN__)
std::reverse(buf, buf + len);
}
QVariant GeoIPDatabase::readMapValue(quint32 &offset, quint32 count) const
{
QVariantHash map;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() != QMetaType::QString)
return QVariant();
QString key = field.toString();
field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
map[key] = field;
}
return map;
}
QVariant GeoIPDatabase::readArrayValue(quint32 &offset, quint32 count) const
{
QVariantList array;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
array.append(field);
}
return array;
}
namespace
{
#ifndef QBT_USES_QT5
Q_IPV6ADDR createMappedAddress(quint32 ip4)
{
Q_IPV6ADDR ip6;
memset(&ip6, 0, sizeof(ip6));
int i;
for (i = 15; ip4 != 0; i--) {
ip6[i] = ip4 & 0xFF;
ip4 >>= 8;
}
ip6[11] = 0xFF;
ip6[10] = 0xFF;
return ip6;
}
#endif
}

View File

@@ -0,0 +1,102 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* 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.
*/
#ifndef GEOIPDATABASE_H
#define GEOIPDATABASE_H
#include <QtGlobal>
#include <QCoreApplication>
class QHostAddress;
class QString;
class QByteArray;
class QDateTime;
struct DataFieldDescriptor;
class GeoIPDatabase
{
Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase)
public:
static GeoIPDatabase *load(const QString &filename, QString &error);
static GeoIPDatabase *load(const QByteArray &data, QString &error);
~GeoIPDatabase();
QString type() const;
quint16 ipVersion() const;
QDateTime buildEpoch() const;
QString lookup(const QHostAddress &hostAddr) const;
private:
GeoIPDatabase(quint32 size);
bool parseMetadata(const QVariantHash &metadata, QString &error);
bool loadDB(QString &error) const;
QVariantHash readMetadata() const;
QVariant readDataField(quint32 &offset) const;
bool readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out) const;
void fromBigEndian(uchar *buf, quint32 len) const;
QVariant readMapValue(quint32 &offset, quint32 count) const;
QVariant readArrayValue(quint32 &offset, quint32 count) const;
template<typename T>
QVariant readPlainValue(quint32 &offset, quint8 len) const
{
T value = 0;
const uchar *const data = m_data + offset;
const quint32 availSize = m_size - offset;
if ((len > 0) && (len <= sizeof(T) && (availSize >= len))) {
// copy input data to last 'len' bytes of 'value'
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
memcpy(dst, data, len);
fromBigEndian(reinterpret_cast<uchar *>(&value), sizeof(T));
offset += len;
}
return QVariant::fromValue(value);
}
// Metadata
quint16 m_ipVersion;
quint16 m_recordSize;
quint32 m_nodeCount;
int m_nodeSize;
int m_indexSize;
int m_recordBytes;
QDateTime m_buildEpoch;
// Search data
mutable QHash<quint32, QString> m_countries;
quint32 m_size;
const uchar *m_data;
};
#endif // GEOIPDATABASE_H

View File

@@ -0,0 +1,90 @@
/*
* Bittorrent Client using Qt and libtorrent.
* 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.
*
* Contact : chris@qbittorrent.org
*/
#include <QDebug>
#include <QHostInfo>
#include <QString>
#include <boost/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include "reverseresolution.h"
const int CACHE_SIZE = 500;
using namespace Net;
static inline bool isUsefulHostName(const QString &hostname, const QString &ip)
{
return (!hostname.isEmpty() && hostname != ip);
}
ReverseResolution::ReverseResolution(QObject *parent)
: QObject(parent)
{
m_cache.setMaxCost(CACHE_SIZE);
}
ReverseResolution::~ReverseResolution()
{
qDebug("Deleting host name resolver...");
}
void ReverseResolution::resolve(const QString &ip)
{
if (m_cache.contains(ip)) {
const QString &hostname = *m_cache.object(ip);
qDebug() << "Resolved host name using cache: " << ip << " -> " << hostname;
if (isUsefulHostName(hostname, ip))
emit ipResolved(ip, hostname);
}
else {
// Actually resolve the ip
m_lookups.insert(QHostInfo::lookupHost(ip, this, SLOT(hostResolved(QHostInfo))), ip);
}
}
void ReverseResolution::hostResolved(const QHostInfo &host)
{
const QString &ip = m_lookups.take(host.lookupId());
Q_ASSERT(!ip.isNull());
if (host.error() != QHostInfo::NoError) {
qDebug() << "DNS Reverse resolution error: " << host.errorString();
return;
}
const QString &hostname = host.hostName();
qDebug() << Q_FUNC_INFO << ip << QString("->") << hostname;
m_cache.insert(ip, new QString(hostname));
if (isUsefulHostName(hostname, ip))
emit ipResolved(ip, hostname);
}

View File

@@ -0,0 +1,67 @@
/*
* Bittorrent Client using Qt and libtorrent.
* 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.
*
* Contact : chris@qbittorrent.org
*/
#ifndef NET_REVERSERESOLUTION_H
#define NET_REVERSERESOLUTION_H
#include <QCache>
#include <QObject>
QT_BEGIN_NAMESPACE
class QHostInfo;
class QString;
QT_END_NAMESPACE
namespace Net
{
class ReverseResolution : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ReverseResolution)
public:
explicit ReverseResolution(QObject *parent = 0);
~ReverseResolution();
void resolve(const QString &ip);
signals:
void ipResolved(const QString &ip, const QString &hostname);
private slots:
void hostResolved(const QHostInfo &host);
private:
QHash<int /* LookupID */, QString /* IP */> m_lookups;
QCache<QString /* IP */, QString /* HostName */> m_cache;
};
}
#endif // NET_REVERSERESOLUTION_H

502
src/base/net/smtp.cpp Normal file
View File

@@ -0,0 +1,502 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2011 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.
*
* Contact : chris@qbittorrent.org
*/
/*
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
*/
#include "smtp.h"
#include "base/preferences.h"
#include "base/logger.h"
#include <QTextStream>
#ifndef QT_NO_OPENSSL
#include <QSslSocket>
#else
#include <QTcpSocket>
#endif
#include <QTextCodec>
#include <QDebug>
#include <QHostAddress>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QCryptographicHash>
#include <QStringList>
namespace
{
const short DEFAULT_PORT = 25;
const short DEFAULT_PORT_SSL = 465;
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
{
const int blockSize = 64; // HMAC-MD5 block size
if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression
key = QCryptographicHash::hash(key, QCryptographicHash::Md5);
}
QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
for (int i = 0; i < key.length(); i++) {
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
}
// result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
QByteArray total = outerPadding;
QByteArray part = innerPadding;
part.append(msg);
total.append(QCryptographicHash::hash(part, QCryptographicHash::Md5));
return QCryptographicHash::hash(total, QCryptographicHash::Md5);
}
QByteArray determineFQDN()
{
QString hostname = QHostInfo::localHostName();
if (hostname.isEmpty())
hostname = "localhost";
return hostname.toLocal8Bit();
}
} // namespace
using namespace Net;
Smtp::Smtp(QObject *parent)
: QObject(parent)
, m_state(Init)
, m_useSsl(false)
{
#ifndef QT_NO_OPENSSL
m_socket = new QSslSocket(this);
#else
m_socket = new QTcpSocket(this);
#endif
connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
== "750c783e6ab0b503eaa86e310a5db738");
Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), "Hi There").toHex()
== "9294727a3638bb1c13f48ef8158bfc9d");
}
Smtp::~Smtp()
{
qDebug() << Q_FUNC_INFO;
}
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
{
const Preferences* const pref = Preferences::instance();
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
m_message = "";
m_message += encodeMimeHeader("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
m_message += encodeMimeHeader("From", from, latin1);
m_message += encodeMimeHeader("Subject", subject, latin1);
m_message += encodeMimeHeader("To", to, latin1);
m_message += "MIME-Version: 1.0\r\n";
m_message += "Content-Type: text/plain; charset=UTF-8\r\n";
m_message += "Content-Transfer-Encoding: base64\r\n";
m_message += "\r\n";
// Encode the body in base64
QString crlf_body = body;
QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64();
int ct = b.length();
for (int i = 0; i < ct; i += 78)
m_message += b.mid(i, 78);
m_from = from;
m_rcpt = to;
// Authentication
if (pref->getMailNotificationSMTPAuth()) {
m_username = pref->getMailNotificationSMTPUsername();
m_password = pref->getMailNotificationSMTPPassword();
}
// Connect to SMTP server
#ifndef QT_NO_OPENSSL
if (pref->getMailNotificationSMTPSSL()) {
m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
m_useSsl = true;
}
else {
#endif
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
m_useSsl = false;
#ifndef QT_NO_OPENSSL
}
#endif
}
void Smtp::readyRead()
{
qDebug() << Q_FUNC_INFO;
// SMTP is line-oriented
m_buffer += m_socket->readAll();
while (true) {
int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition
QByteArray line = m_buffer.left(pos);
m_buffer = m_buffer.mid(pos + 2);
qDebug() << "Response line:" << line;
// Extract response code
QByteArray code = line.left(3);
switch (m_state) {
case Init: {
if (code[0] == '2') {
// The server may send a multiline greeting/INIT/220 response.
// We wait until it finishes.
if (line[3] != ' ')
break;
// Connection was successful
ehlo();
}
else {
logError("Connection failed, unrecognized reply: "+line);
m_state = Close;
}
break;
}
case EhloSent:
case HeloSent:
case EhloGreetReceived:
parseEhloResponse(code, line[3] != ' ', line.mid(4));
break;
#ifndef QT_NO_OPENSSL
case StartTLSSent:
if (code == "220") {
m_socket->startClientEncryption();
ehlo();
}
else {
authenticate();
}
break;
#endif
case AuthRequestSent:
case AuthUsernameSent:
if (m_authType == AuthPlain) authPlain();
else if (m_authType == AuthLogin) authLogin();
else authCramMD5(line.mid(4));
break;
case AuthSent:
case Authenticated:
if (code[0] == '2') {
qDebug() << "Sending <mail from>...";
m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n");
m_socket->flush();
m_state = Rcpt;
}
else {
// Authentication failed!
logError("Authentication failed, msg: "+line);
m_state = Close;
}
break;
case Rcpt:
if (code[0] == '2') {
m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n");
m_socket->flush();
m_state = Data;
}
else {
logError("<mail from> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Data:
if (code[0] == '2') {
m_socket->write("data\r\n");
m_socket->flush();
m_state = Body;
}
else {
logError("<Rcpt to> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Body:
if (code[0] == '3') {
m_socket->write(m_message + "\r\n.\r\n");
m_socket->flush();
m_state = Quit;
}
else {
logError("<data> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Quit:
if (code[0] == '2') {
m_socket->write("QUIT\r\n");
m_socket->flush();
// here, we just close.
m_state = Close;
}
else {
logError("Message was rejected by the server, error: "+line);
m_state = Close;
}
break;
default:
qDebug() << "Disconnecting from host";
m_socket->disconnectFromHost();
return;
}
}
}
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTextCodec *latin1, const QByteArray &prefix)
{
QByteArray rv = "";
QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value)) {
bool firstWord = true;
foreach (const QByteArray& word, value.toLatin1().split(' ')) {
if (line.size() > 78) {
rv = rv + line + "\r\n";
line.clear();
}
if (firstWord)
line += word;
else
line += " " + word;
firstWord = false;
}
}
else {
// The text cannot be losslessly encoded as Latin-1. Therefore, we
// must use base64 encoding.
QByteArray utf8 = value.toUtf8();
// Use base64 encoding
QByteArray base64 = utf8.toBase64();
int ct = base64.length();
line += "=?utf-8?b?";
for (int i = 0; i < ct; i += 4) {
/*if (line.length() > 72) {
rv += line + "?\n\r";
line = " =?utf-8?b?";
}*/
line = line + base64.mid(i, 4);
}
line += "?="; // end encoded-word atom
}
return rv + line + "\r\n";
}
void Smtp::ehlo()
{
QByteArray address = determineFQDN();
m_socket->write("ehlo " + address + "\r\n");
m_socket->flush();
m_state = EhloSent;
}
void Smtp::helo()
{
QByteArray address = determineFQDN();
m_socket->write("helo " + address + "\r\n");
m_socket->flush();
m_state = HeloSent;
}
void Smtp::parseEhloResponse(const QByteArray &code, bool continued, const QString &line)
{
if (code != "250") {
// Error
if (m_state == EhloSent) {
// try to send HELO instead of EHLO
qDebug() << "EHLO failed, trying HELO instead...";
helo();
}
else {
// Both EHLO and HELO failed, chances are this is NOT
// a SMTP server
logError("Both EHLO and HELO failed, msg: "+line);
m_state = Close;
}
return;
}
if (m_state != EhloGreetReceived) {
if (!continued) {
// greeting only, no extensions
qDebug() << "No extension";
m_state = EhloDone;
}
else {
// greeting followed by extensions
m_state = EhloGreetReceived;
qDebug () << "EHLO greet received";
return;
}
}
else {
qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper()
<< line.section(' ', 1);
m_extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
if (!continued)
m_state = EhloDone;
}
if (m_state != EhloDone) return;
if (m_extensions.contains("STARTTLS") && m_useSsl) {
qDebug() << "STARTTLS";
startTLS();
}
else {
authenticate();
}
}
void Smtp::authenticate()
{
qDebug() << Q_FUNC_INFO;
if (!m_extensions.contains("AUTH") ||
m_username.isEmpty() || m_password.isEmpty()) {
// Skip authentication
qDebug() << "Skipping authentication...";
m_state = Authenticated;
// At this point the server will not send any response
// So fill the buffer with a fake one to pass the tests
// in readyRead()
m_buffer.push_front("250 QBT FAKE RESPONSE\r\n");
return;
}
// AUTH extension is supported, check which
// authentication modes are supported by
// the server
QStringList auth = m_extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts);
if (auth.contains("CRAM-MD5")) {
qDebug() << "Using CRAM-MD5 authentication...";
authCramMD5();
}
else if (auth.contains("PLAIN")) {
qDebug() << "Using PLAIN authentication...";
authPlain();
}
else if (auth.contains("LOGIN")) {
qDebug() << "Using LOGIN authentication...";
authLogin();
}
else {
// Skip authentication
logError("The SMTP server does not seem to support any of the authentications modes "
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
"knowing it is likely to fail... Server Auth Modes: "+auth.join("|"));
m_state = Authenticated;
// At this point the server will not send any response
// So fill the buffer with a fake one to pass the tests
// in readyRead()
m_buffer.push_front("250 QBT FAKE RESPONSE\r\n");
}
}
void Smtp::startTLS()
{
qDebug() << Q_FUNC_INFO;
#ifndef QT_NO_OPENSSL
m_socket->write("starttls\r\n");
m_socket->flush();
m_state = StartTLSSent;
#else
authenticate();
#endif
}
void Smtp::authCramMD5(const QByteArray& challenge)
{
if (m_state != AuthRequestSent) {
m_socket->write("auth cram-md5\r\n");
m_socket->flush();
m_authType = AuthCramMD5;
m_state = AuthRequestSent;
}
else {
QByteArray response = m_username.toLatin1() + ' '
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
m_socket->write(response.toBase64() + "\r\n");
m_socket->flush();
m_state = AuthSent;
}
}
void Smtp::authPlain()
{
if (m_state != AuthRequestSent) {
m_authType = AuthPlain;
// Prepare Auth string
QByteArray auth;
auth += '\0';
auth += m_username.toLatin1();
qDebug() << "username: " << m_username.toLatin1();
auth += '\0';
auth += m_password.toLatin1();
qDebug() << "password: " << m_password.toLatin1();
// Send it
m_socket->write("auth plain "+ auth.toBase64() + "\r\n");
m_socket->flush();
m_state = AuthSent;
}
}
void Smtp::authLogin()
{
if ((m_state != AuthRequestSent) && (m_state != AuthUsernameSent)) {
m_socket->write("auth login\r\n");
m_socket->flush();
m_authType = AuthLogin;
m_state = AuthRequestSent;
}
else if (m_state == AuthRequestSent) {
m_socket->write(m_username.toLatin1().toBase64() + "\r\n");
m_socket->flush();
m_state = AuthUsernameSent;
}
else {
m_socket->write(m_password.toLatin1().toBase64() + "\r\n");
m_socket->flush();
m_state = AuthSent;
}
}
void Smtp::logError(const QString &msg)
{
qDebug() << "Email Notification Error:" << msg;
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
}

125
src/base/net/smtp.h Normal file
View File

@@ -0,0 +1,125 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2011 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.
*
* Contact : chris@qbittorrent.org
*/
/*
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
*/
#ifndef SMTP_H
#define SMTP_H
#include <QString>
#include <QObject>
#include <QByteArray>
#include <QHash>
QT_BEGIN_NAMESPACE
class QTextStream;
#ifndef QT_NO_OPENSSL
class QSslSocket;
#else
class QTcpSocket;
#endif
class QTextCodec;
QT_END_NAMESPACE
namespace Net
{
class Smtp : public QObject
{
Q_OBJECT
public:
Smtp(QObject *parent = 0);
~Smtp();
void sendMail(const QString &m_from, const QString &to, const QString &subject, const QString &body);
private slots:
void readyRead();
private:
enum States
{
Rcpt,
EhloSent,
HeloSent,
EhloDone,
EhloGreetReceived,
AuthRequestSent,
AuthSent,
AuthUsernameSent,
Authenticated,
StartTLSSent,
Data,
Init,
Body,
Quit,
Close
};
enum AuthType
{
AuthPlain,
AuthLogin,
AuthCramMD5
};
QByteArray encodeMimeHeader(const QString &key, const QString &value, QTextCodec *latin1, const QByteArray &prefix = QByteArray());
void ehlo();
void helo();
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line);
void authenticate();
void startTLS();
void authCramMD5(const QByteArray &challenge = QByteArray());
void authPlain();
void authLogin();
void logError(const QString &msg);
QByteArray m_message;
#ifndef QT_NO_OPENSSL
QSslSocket *m_socket;
#else
QTcpSocket *m_socket;
#endif
QString m_from;
QString m_rcpt;
QString m_response;
int m_state;
QHash<QString, QString> m_extensions;
QByteArray m_buffer;
bool m_useSsl;
AuthType m_authType;
QString m_username;
QString m_password;
};
}
#endif