Fix prefjson::setPreferences() doesn't actually save.

This commit is contained in:
Vladimir Golovnev (Glassez)
2015-01-28 12:03:22 +03:00
parent 8e1698d563
commit 2707f5205f
37 changed files with 1972 additions and 1763 deletions

View File

@@ -1,167 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012, 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 <QDir>
#include <QTemporaryFile>
#include <QNetworkCookie>
#include "preferences.h"
#include "webapplication.h"
#include "abstractrequesthandler.h"
AbstractRequestHandler::AbstractRequestHandler(const HttpRequest &request, const HttpEnvironment &env, WebApplication *app)
: app_(app), session_(0), request_(request), env_(env)
{
sessionInitialize();
if (!sessionActive() && !isAuthNeeded())
sessionStart();
}
HttpResponse AbstractRequestHandler::run()
{
if (isBanned()) {
status(403, "Forbidden");
print(QObject::tr("Your IP address has been banned after too many failed authentication attempts."), CONTENT_TYPE_TXT);
}
else {
processRequest();
}
return response_;
}
bool AbstractRequestHandler::isAuthNeeded()
{
return
(
env_.clientAddress != QHostAddress::LocalHost &&
env_.clientAddress != QHostAddress::LocalHostIPv6
) || Preferences::instance()->isWebUiLocalAuthEnabled();
}
void AbstractRequestHandler::status(uint code, const QString& text)
{
response_.status = HttpResponseStatus(code, text);
}
void AbstractRequestHandler::header(const QString& name, const QString& value)
{
response_.headers[name] = value;
}
void AbstractRequestHandler::print(const QString& text, const QString& type)
{
print_impl(text.toUtf8(), type);
}
void AbstractRequestHandler::print(const QByteArray& data, const QString& type)
{
print_impl(data, type);
}
void AbstractRequestHandler::print_impl(const QByteArray& data, const QString& type)
{
if (!response_.headers.contains(HEADER_CONTENT_TYPE))
response_.headers[HEADER_CONTENT_TYPE] = type;
response_.content += data;
}
void AbstractRequestHandler::printFile(const QString& path)
{
QByteArray data;
QString type;
if (!app_->readFile(path, data, type)) {
status(404, "Not Found");
return;
}
print(data, type);
}
void AbstractRequestHandler::sessionInitialize()
{
app_->sessionInitialize(this);
}
void AbstractRequestHandler::sessionStart()
{
if (app_->sessionStart(this)) {
QNetworkCookie cookie(C_SID.toUtf8(), session_->id.toUtf8());
cookie.setPath("/");
header(HEADER_SET_COOKIE, cookie.toRawForm());
}
}
void AbstractRequestHandler::sessionEnd()
{
if (sessionActive()) {
QNetworkCookie cookie(C_SID.toUtf8(), session_->id.toUtf8());
cookie.setPath("/");
cookie.setExpirationDate(QDateTime::currentDateTime());
if (app_->sessionEnd(this))
header(HEADER_SET_COOKIE, cookie.toRawForm());
}
}
bool AbstractRequestHandler::isBanned() const
{
return app_->isBanned(this);
}
int AbstractRequestHandler::failedAttempts() const
{
return app_->failedAttempts(this);
}
void AbstractRequestHandler::resetFailedAttempts()
{
app_->resetFailedAttempts(this);
}
void AbstractRequestHandler::increaseFailedAttempts()
{
app_->increaseFailedAttempts(this);
}
QString AbstractRequestHandler::saveTmpFile(const QByteArray &data)
{
QTemporaryFile tmpfile(QDir::temp().absoluteFilePath("qBT-XXXXXX.torrent"));
tmpfile.setAutoRemove(false);
if (tmpfile.open()) {
tmpfile.write(data);
tmpfile.close();
return tmpfile.fileName();
}
qWarning() << "I/O Error: Could not create temporary file";
return QString();
}

View File

@@ -0,0 +1,413 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 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 <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QNetworkCookie>
#include <QTemporaryFile>
#include <QTimer>
#include "preferences.h"
#include "websessiondata.h"
#include "abstractwebapplication.h"
// UnbanTimer
class UnbanTimer: public QTimer
{
public:
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
: QTimer(parent), m_peerIp(peer_ip)
{
setSingleShot(true);
setInterval(BAN_TIME);
}
inline const QHostAddress& peerIp() const { return m_peerIp; }
private:
QHostAddress m_peerIp;
};
// WebSession
struct WebSession
{
const QString id;
uint timestamp;
WebSessionData data;
WebSession(const QString& id)
: id(id)
{
}
void updateTimestamp()
{
timestamp = QDateTime::currentDateTime().toTime_t();
}
};
// AbstractWebApplication
AbstractWebApplication::AbstractWebApplication(QObject *parent)
: Http::ResponseBuilder(parent)
, session_(0)
{
QTimer *timer = new QTimer(this);
timer->setInterval(60000); // 1 min.
connect(timer, SIGNAL(timeout()), SLOT(removeInactiveSessions()));
}
AbstractWebApplication::~AbstractWebApplication()
{
// cleanup sessions data
qDeleteAll(sessions_);
}
Http::Response AbstractWebApplication::processRequest(const Http::Request &request, const Http::Environment &env)
{
request_ = request;
env_ = env;
clear(); // clear response
sessionInitialize();
if (!sessionActive() && !isAuthNeeded())
sessionStart();
if (isBanned()) {
status(403, "Forbidden");
print(QObject::tr("Your IP address has been banned after too many failed authentication attempts."), Http::CONTENT_TYPE_TXT);
}
else {
processRequest();
}
return response();
}
void AbstractWebApplication::UnbanTimerEvent()
{
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
clientFailedAttempts_.remove(ubantimer->peerIp());
ubantimer->deleteLater();
}
void AbstractWebApplication::removeInactiveSessions()
{
const uint now = QDateTime::currentDateTime().toTime_t();
foreach (const QString &id, sessions_.keys()) {
if ((now - sessions_[id]->timestamp) > INACTIVE_TIME)
delete sessions_.take(id);
}
}
bool AbstractWebApplication::sessionInitialize()
{
static const QString SID_START = QLatin1String(C_SID) + QLatin1String("=");
if (session_ == 0)
{
QString cookie = request_.headers.value("cookie");
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
QString sessionId;
int pos = cookie.indexOf(SID_START);
if (pos >= 0)
{
pos += SID_START.length();
int end = cookie.indexOf(QRegExp("[,;]"), pos);
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
}
// TODO: Additional session check
if (!sessionId.isNull())
{
if (sessions_.contains(sessionId))
{
session_ = sessions_[sessionId];
session_->updateTimestamp();
return true;
}
else
{
qDebug() << Q_FUNC_INFO << "session does not exist!";
}
}
}
return false;
}
bool AbstractWebApplication::readFile(const QString& path, QByteArray &data, QString &type)
{
QString ext = "";
int index = path.lastIndexOf('.') + 1;
if (index > 0)
ext = path.mid(index);
// find translated file in cache
if (translatedFiles_.contains(path))
{
data = translatedFiles_[path];
}
else
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
qDebug("File %s was not found!", qPrintable(path));
return false;
}
data = file.readAll();
file.close();
// Translate the file
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js")))
{
QString dataStr = QString::fromUtf8(data.constData());
translateDocument(dataStr);
if (path.endsWith("about.html"))
{
dataStr.replace("${VERSION}", VERSION);
}
data = dataStr.toUtf8();
translatedFiles_[path] = data; // cashing translated file
}
}
type = CONTENT_TYPE_BY_EXT[ext];
return true;
}
WebSessionData *AbstractWebApplication::session()
{
Q_ASSERT(session_ != 0);
return &session_->data;
}
QString AbstractWebApplication::generateSid()
{
QString sid;
qsrand(QDateTime::currentDateTime().toTime_t());
do
{
const size_t size = 6;
quint32 tmp[size];
for (size_t i = 0; i < size; ++i)
tmp[i] = qrand();
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
}
while (sessions_.contains(sid));
return sid;
}
void AbstractWebApplication::translateDocument(QString& data)
{
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
const std::string contexts[] = {
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
"StatusBar"
};
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
int i = 0;
bool found = true;
const QString locale = Preferences::instance()->getLocale();
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
while(i < data.size() && found)
{
i = regex.indexIn(data, i);
if (i >= 0)
{
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
QByteArray word = regex.cap(1).toUtf8();
QString translation = word;
if (isTranslationNeeded)
{
size_t context_index = 0;
while ((context_index < context_count) && (translation == word))
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
#else
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
#endif
++context_index;
}
}
// Remove keyboard shortcuts
translation.replace(mnemonic, "");
data.replace(i, regex.matchedLength(), translation);
i += translation.length();
}
else
{
found = false; // no more translatable strings
}
}
}
bool AbstractWebApplication::isBanned() const
{
return clientFailedAttempts_.value(env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
}
int AbstractWebApplication::failedAttempts() const
{
return clientFailedAttempts_.value(env_.clientAddress, 0);
}
void AbstractWebApplication::resetFailedAttempts()
{
clientFailedAttempts_.remove(env_.clientAddress);
}
void AbstractWebApplication::increaseFailedAttempts()
{
const int nb_fail = clientFailedAttempts_.value(env_.clientAddress, 0) + 1;
clientFailedAttempts_[env_.clientAddress] = nb_fail;
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS)
{
// Max number of failed attempts reached
// Start ban period
UnbanTimer* ubantimer = new UnbanTimer(env_.clientAddress, this);
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
ubantimer->start();
}
}
bool AbstractWebApplication::isAuthNeeded()
{
return (env_.clientAddress != QHostAddress::LocalHost
&& env_.clientAddress != QHostAddress::LocalHostIPv6)
|| Preferences::instance()->isWebUiLocalAuthEnabled();
}
void AbstractWebApplication::printFile(const QString& path)
{
QByteArray data;
QString type;
if (!readFile(path, data, type)) {
status(404, "Not Found");
return;
}
print(data, type);
}
bool AbstractWebApplication::sessionStart()
{
if (session_ == 0)
{
session_ = new WebSession(generateSid());
session_->updateTimestamp();
sessions_[session_->id] = session_;
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
cookie.setPath(QLatin1String("/"));
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
return true;
}
return false;
}
bool AbstractWebApplication::sessionEnd()
{
if ((session_ != 0) && (sessions_.contains(session_->id)))
{
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
cookie.setPath(QLatin1String("/"));
cookie.setExpirationDate(QDateTime::currentDateTime());
sessions_.remove(session_->id);
delete session_;
session_ = 0;
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
return true;
}
return false;
}
QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
{
QTemporaryFile tmpfile(QDir::temp().absoluteFilePath("qBT-XXXXXX.torrent"));
tmpfile.setAutoRemove(false);
if (tmpfile.open()) {
tmpfile.write(data);
tmpfile.close();
return tmpfile.fileName();
}
qWarning() << "I/O Error: Could not create temporary file";
return QString();
}
QStringMap AbstractWebApplication::initializeContentTypeByExtMap()
{
QStringMap map;
map["htm"] = Http::CONTENT_TYPE_HTML;
map["html"] = Http::CONTENT_TYPE_HTML;
map["css"] = Http::CONTENT_TYPE_CSS;
map["gif"] = Http::CONTENT_TYPE_GIF;
map["png"] = Http::CONTENT_TYPE_PNG;
map["js"] = Http::CONTENT_TYPE_JS;
return map;
}
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = AbstractWebApplication::initializeContentTypeByExtMap();

View File

@@ -26,64 +26,83 @@
* exception statement from your version.
*/
#ifndef ABSTRACTREQUESTHANDLER_H
#define ABSTRACTREQUESTHANDLER_H
#ifndef ABSTRACTWEBAPPLICATION_H
#define ABSTRACTWEBAPPLICATION_H
#include <QString>
#include "httptypes.h"
#include <QObject>
#include <QMap>
#include <QHash>
#include "http/types.h"
#include "http/responsebuilder.h"
#include "http/irequesthandler.h"
class WebApplication;
struct WebSession;
struct WebSessionData;
class AbstractRequestHandler
const char C_SID[] = "SID"; // name of session id cookie
const int BAN_TIME = 3600000; // 1 hour
const int INACTIVE_TIME = 900; // Session inactive time (in secs = 15 min.)
const int MAX_AUTH_FAILED_ATTEMPTS = 5;
class AbstractWebApplication : public Http::ResponseBuilder, public Http::IRequestHandler
{
friend class WebApplication;
Q_OBJECT
Q_DISABLE_COPY(AbstractWebApplication)
public:
AbstractRequestHandler(
const HttpRequest& request, const HttpEnvironment& env,
WebApplication* app);
explicit AbstractWebApplication(QObject *parent = 0);
virtual ~AbstractWebApplication();
HttpResponse run();
Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
protected:
virtual void processRequest() = 0;
void status(uint code, const QString& text);
void header(const QString& name, const QString& value);
void print(const QString& text, const QString& type = CONTENT_TYPE_HTML);
void print(const QByteArray& data, const QString& type = CONTENT_TYPE_HTML);
void printFile(const QString& path);
// Session management
bool sessionActive() const { return session_ != 0; }
void sessionInitialize();
void sessionStart();
void sessionEnd();
// Ban management
bool isBanned() const;
int failedAttempts() const;
void resetFailedAttempts();
void increaseFailedAttempts();
void printFile(const QString &path);
// Session management
bool sessionActive() const { return session_ != 0; }
bool sessionStart();
bool sessionEnd();
bool isAuthNeeded();
// save data to temporary file on disk and return its name (or empty string if fails)
static QString saveTmpFile(const QByteArray& data);
bool readFile(const QString &path, QByteArray &data, QString &type);
inline WebSession* session() { return session_; }
inline HttpRequest request() const { return request_; }
inline HttpEnvironment env() const { return env_; }
// save data to temporary file on disk and return its name (or empty string if fails)
static QString saveTmpFile(const QByteArray &data);
WebSessionData *session();
Http::Request request() const { return request_; }
Http::Environment env() const { return env_; }
private slots:
void UnbanTimerEvent();
void removeInactiveSessions();
private:
WebApplication* app_;
WebSession* session_;
const HttpRequest request_;
const HttpEnvironment env_;
HttpResponse response_;
// Persistent data
QMap<QString, WebSession *> sessions_;
QHash<QHostAddress, int> clientFailedAttempts_;
QMap<QString, QByteArray> translatedFiles_;
void print_impl(const QByteArray& data, const QString& type);
// Current data
WebSession *session_;
Http::Request request_;
Http::Environment env_;
QString generateSid();
bool sessionInitialize();
static void translateDocument(QString &data);
static const QStringMap CONTENT_TYPE_BY_EXT;
static QStringMap initializeContentTypeByExtMap();
};
#endif // ABSTRACTREQUESTHANDLER_H
#endif // ABSTRACTWEBAPPLICATION_H

View File

@@ -1,110 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Ishan Arora and 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 <QTcpSocket>
#include <QDebug>
#include "httptypes.h"
#include "httpserver.h"
#include "httprequestparser.h"
#include "httpresponsegenerator.h"
#include "webapplication.h"
#include "requesthandler.h"
#include "httpconnection.h"
HttpConnection::HttpConnection(QTcpSocket *socket, HttpServer *httpserver)
: QObject(httpserver), m_socket(socket)
{
m_socket->setParent(this);
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
}
HttpConnection::~HttpConnection()
{
delete m_socket;
}
void HttpConnection::read()
{
m_receivedData.append(m_socket->readAll());
HttpRequest request;
HttpRequestParser::ErrorCode err = HttpRequestParser::parse(m_receivedData, request);
switch (err)
{
case HttpRequestParser::IncompleteRequest:
// Partial request waiting for the rest
break;
case HttpRequestParser::BadRequest:
write(HttpResponse(400, "Bad Request"));
break;
case HttpRequestParser::NoError:
HttpEnvironment env;
env.clientAddress = m_socket->peerAddress();
HttpResponse response = RequestHandler(request, env, WebApplication::instance()).run();
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
write(response);
break;
}
}
void HttpConnection::write(const HttpResponse& response)
{
m_socket->write(HttpResponseGenerator::generate(response));
m_socket->disconnectFromHost();
}
bool HttpConnection::acceptsGzipEncoding(const QString& encoding)
{
int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive);
if (pos == -1)
return false;
// Let's see if there's a qvalue of 0.0 following
if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway
return true;
//So let's find = and the next comma
pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive);
int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive);
QString value;
if (comma_pos == -1)
value = encoding.mid(pos + 1, comma_pos);
else
value = encoding.mid(pos + 1, comma_pos - (pos + 1));
if (value.toDouble() == 0.0)
return false;
return true;
}

View File

@@ -1,97 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2006 Ishan Arora <ishan@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 QT_NO_OPENSSL
#include <QSslSocket>
#else
#include <QTcpSocket>
#endif
#include "httpconnection.h"
#include "httpserver.h"
HttpServer::HttpServer(QObject* parent)
: QTcpServer(parent)
#ifndef QT_NO_OPENSSL
, m_https(false)
#endif
{
}
HttpServer::~HttpServer()
{
}
#ifndef QT_NO_OPENSSL
void HttpServer::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
{
m_certificate = certificate;
m_key = key;
m_https = true;
}
void HttpServer::disableHttps()
{
m_https = false;
m_certificate.clear();
m_key.clear();
}
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
void HttpServer::incomingConnection(qintptr socketDescriptor)
#else
void HttpServer::incomingConnection(int socketDescriptor)
#endif
{
QTcpSocket *serverSocket;
#ifndef QT_NO_OPENSSL
if (m_https)
serverSocket = new QSslSocket(this);
else
#endif
serverSocket = new QTcpSocket(this);
if (serverSocket->setSocketDescriptor(socketDescriptor))
{
#ifndef QT_NO_OPENSSL
if (m_https)
{
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol);
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate);
static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
}
#endif
new HttpConnection(serverSocket, this);
}
else
{
serverSocket->deleteLater();
}
}

View File

@@ -333,6 +333,6 @@ void prefjson::setPreferences(const QString& json)
pref->setDynDNSPassword(m["dyndns_password"].toString());
if (m.contains("dyndns_domain"))
pref->setDynDomainName(m["dyndns_domain"].toString());
// Reload preferences
QBtSession::instance()->configureSession();
// Save preferences
pref->save();
}

View File

@@ -1,716 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012, 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>
#ifdef DISABLE_GUI
#include <QCoreApplication>
#else
#include <QApplication>
#endif
#include <QTimer>
#include <QCryptographicHash>
#include <queue>
#include <vector>
#include <libtorrent/session.hpp>
#ifndef DISABLE_GUI
#include "iconprovider.h"
#endif
#include "misc.h"
#include "fs_utils.h"
#include "preferences.h"
#include "btjson.h"
#include "prefjson.h"
#include "qbtsession.h"
#include "requesthandler.h"
#include "webapplication.h"
using namespace libtorrent;
static const int API_VERSION = 2;
static const int API_VERSION_MIN = 2;
const QString WWW_FOLDER = ":/www/public/";
const QString PRIVATE_FOLDER = ":/www/private/";
const QString DEFAULT_SCOPE = "public";
const QString SCOPE_IMAGES = "images";
const QString SCOPE_THEME = "theme";
const QString DEFAULT_ACTION = "index";
const QString WEBUI_ACTION = "webui";
const QString VERSION_INFO = "version";
const QString MAX_AGE_MONTH = "public, max-age=2592000";
#define ADD_ACTION(scope, action) actions[#scope][#action] = &RequestHandler::action_##scope##_##action
QMap<QString, QMap<QString, RequestHandler::Action> > RequestHandler::initializeActions()
{
QMap<QString,QMap<QString, RequestHandler::Action> > actions;
ADD_ACTION(public, webui);
ADD_ACTION(public, index);
ADD_ACTION(public, login);
ADD_ACTION(public, logout);
ADD_ACTION(public, theme);
ADD_ACTION(public, images);
ADD_ACTION(query, torrents);
ADD_ACTION(query, preferences);
ADD_ACTION(query, transferInfo);
ADD_ACTION(query, propertiesGeneral);
ADD_ACTION(query, propertiesTrackers);
ADD_ACTION(query, propertiesFiles);
ADD_ACTION(sync, maindata);
ADD_ACTION(command, shutdown);
ADD_ACTION(command, download);
ADD_ACTION(command, upload);
ADD_ACTION(command, addTrackers);
ADD_ACTION(command, resumeAll);
ADD_ACTION(command, pauseAll);
ADD_ACTION(command, resume);
ADD_ACTION(command, pause);
ADD_ACTION(command, setPreferences);
ADD_ACTION(command, setFilePrio);
ADD_ACTION(command, getGlobalUpLimit);
ADD_ACTION(command, getGlobalDlLimit);
ADD_ACTION(command, setGlobalUpLimit);
ADD_ACTION(command, setGlobalDlLimit);
ADD_ACTION(command, getTorrentUpLimit);
ADD_ACTION(command, getTorrentDlLimit);
ADD_ACTION(command, setTorrentUpLimit);
ADD_ACTION(command, setTorrentDlLimit);
ADD_ACTION(command, alternativeSpeedLimitsEnabled);
ADD_ACTION(command, toggleAlternativeSpeedLimits);
ADD_ACTION(command, toggleSequentialDownload);
ADD_ACTION(command, toggleFirstLastPiecePrio);
ADD_ACTION(command, delete);
ADD_ACTION(command, deletePerm);
ADD_ACTION(command, increasePrio);
ADD_ACTION(command, decreasePrio);
ADD_ACTION(command, topPrio);
ADD_ACTION(command, bottomPrio);
ADD_ACTION(command, recheck);
ADD_ACTION(version, api);
ADD_ACTION(version, api_min);
ADD_ACTION(version, qbittorrent);
return actions;
}
#define CHECK_URI(ARGS_NUM) \
if (args_.size() != ARGS_NUM) { \
status(404, "Not Found"); \
return; \
}
#define CHECK_PARAMETERS(PARAMETERS) \
QStringList parameters; \
parameters << PARAMETERS; \
if (parameters.size() != request().posts.size()) { \
status(400, "Bad Request"); \
return; \
} \
foreach (QString key, request().posts.keys()) { \
if (!parameters.contains(key, Qt::CaseInsensitive)) { \
status(400, "Bad Request"); \
return; \
} \
}
void RequestHandler::action_public_index()
{
QString path;
if (!args_.isEmpty()) {
if (args_.back() == "favicon.ico")
path = ":/icons/skin/qbittorrent16.png";
else
path = WWW_FOLDER + args_.join("/");
}
printFile(path);
}
void RequestHandler::action_public_webui()
{
if (!sessionActive())
printFile(PRIVATE_FOLDER + "login.html");
else
printFile(PRIVATE_FOLDER + "index.html");
}
void RequestHandler::action_public_login()
{
const Preferences* const pref = Preferences::instance();
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(request().posts["password"].toLocal8Bit());
QString pass = md5.result().toHex();
bool equalUser = misc::slowEquals(request().posts["username"].toUtf8(), pref->getWebUiUsername().toUtf8());
bool equalPass = misc::slowEquals(pass.toUtf8(), pref->getWebUiPassword().toUtf8());
if (equalUser && equalPass) {
sessionStart();
print(QByteArray("Ok."), CONTENT_TYPE_TXT);
}
else {
QString addr = env().clientAddress.toString();
increaseFailedAttempts();
qDebug("client IP: %s (%d failed attempts)", qPrintable(addr), failedAttempts());
print(QByteArray("Fails."), CONTENT_TYPE_TXT);
}
}
void RequestHandler::action_public_logout()
{
CHECK_URI(0);
sessionEnd();
}
void RequestHandler::action_public_theme()
{
if (args_.size() != 1) {
status(404, "Not Found");
return;
}
#ifdef DISABLE_GUI
QString url = ":/icons/oxygen/" + args_.front() + ".png";
#else
QString url = IconProvider::instance()->getIconPath(args_.front());
#endif
qDebug() << Q_FUNC_INFO << "There icon:" << url;
printFile(url);
header(HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
}
void RequestHandler::action_public_images()
{
const QString path = ":/icons/" + args_.join("/");
printFile(path);
header(HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
}
// GET params:
// - filter (string): all, downloading, completed, paused, active, inactive
// - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label")
// - sort (string): name of column for sorting by its value
// - reverse (bool): enable reverse sorting
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
// - offset (int): set offset (if less than 0 - offset from end)
void RequestHandler::action_query_torrents()
{
CHECK_URI(0);
const QStringMap& gets = request().gets;
print(btjson::getTorrents(
gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true",
gets["limit"].toInt(), gets["offset"].toInt()
), CONTENT_TYPE_JS);
}
void RequestHandler::action_query_preferences()
{
CHECK_URI(0);
print(prefjson::getPreferences(), CONTENT_TYPE_JS);
}
void RequestHandler::action_query_transferInfo()
{
CHECK_URI(0);
print(btjson::getTransferInfo(), CONTENT_TYPE_JS);
}
void RequestHandler::action_query_propertiesGeneral()
{
CHECK_URI(1);
print(btjson::getPropertiesForTorrent(args_.front()), CONTENT_TYPE_JS);
}
void RequestHandler::action_query_propertiesTrackers()
{
CHECK_URI(1);
print(btjson::getTrackersForTorrent(args_.front()), CONTENT_TYPE_JS);
}
void RequestHandler::action_query_propertiesFiles()
{
CHECK_URI(1);
print(btjson::getFilesForTorrent(args_.front()), CONTENT_TYPE_JS);
}
// GET param:
// - rid (int): last response id
void RequestHandler::action_sync_maindata()
{
CHECK_URI(0);
print(btjson::getSyncMainData(request().gets["rid"].toInt(), session()->syncMainDataLastResponse, session()->syncMainDataLastAcceptedResponse));
}
void RequestHandler::action_version_api()
{
CHECK_URI(0);
print(QString::number(API_VERSION), CONTENT_TYPE_TXT);
}
void RequestHandler::action_version_api_min()
{
CHECK_URI(0);
print(QString::number(API_VERSION_MIN), CONTENT_TYPE_TXT);
}
void RequestHandler::action_version_qbittorrent()
{
CHECK_URI(0);
print(QString(VERSION), CONTENT_TYPE_TXT);
}
void RequestHandler::action_command_shutdown()
{
qDebug() << "Shutdown request from Web UI";
// Special case handling for shutdown, we
// need to reply to the Web UI before
// actually shutting down.
CHECK_URI(0);
QTimer::singleShot(0, qApp, SLOT(quit()));
}
void RequestHandler::action_command_download()
{
CHECK_URI(0);
CHECK_PARAMETERS("urls");
QString urls = request().posts["urls"];
QStringList list = urls.split('\n');
foreach (QString url, list) {
url = url.trimmed();
if (!url.isEmpty()) {
if (url.startsWith("bc://bt/", Qt::CaseInsensitive)) {
qDebug("Converting bc link to magnet link");
url = misc::bcLinkToMagnet(url);
}
else if (url.startsWith("magnet:", Qt::CaseInsensitive)) {
QBtSession::instance()->addMagnetSkipAddDlg(url);
}
else {
qDebug("Downloading url: %s", qPrintable(url));
QBtSession::instance()->downloadUrlAndSkipDialog(url);
}
}
}
}
void RequestHandler::action_command_upload()
{
qDebug() << Q_FUNC_INFO;
CHECK_URI(0);
foreach(const UploadedFile& torrent, request().files) {
QString filePath = saveTmpFile(torrent.data);
if (!filePath.isEmpty()) {
QTorrentHandle h = QBtSession::instance()->addTorrent(filePath);
if (!h.is_valid()) {
status(415, "Internal Server Error");
print(QObject::tr("Error: '%1' is not a valid torrent file.\n").arg(torrent.filename), CONTENT_TYPE_TXT);
}
// Clean up
fsutils::forceRemove(filePath);
}
else {
qWarning() << "I/O Error: Could not create temporary file";
status(500, "Internal Server Error");
print(QObject::tr("I/O Error: Could not create temporary file."), CONTENT_TYPE_TXT);
}
}
}
void RequestHandler::action_command_addTrackers()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "urls");
QString hash = request().posts["hash"];
if (!hash.isEmpty()) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid() && h.has_metadata()) {
QString urls = request().posts["urls"];
QStringList list = urls.split('\n');
foreach (const QString& url, list) {
announce_entry e(url.toStdString());
h.add_tracker(e);
}
}
}
}
void RequestHandler::action_command_resumeAll()
{
CHECK_URI(0);
QBtSession::instance()->resumeAllTorrents();
}
void RequestHandler::action_command_pauseAll()
{
CHECK_URI(0);
QBtSession::instance()->pauseAllTorrents();
}
void RequestHandler::action_command_resume()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->resumeTorrent(request().posts["hash"]);
}
void RequestHandler::action_command_pause()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->pauseTorrent(request().posts["hash"]);
}
void RequestHandler::action_command_setPreferences()
{
CHECK_URI(0);
CHECK_PARAMETERS("json");
prefjson::setPreferences(request().posts["json"]);
}
void RequestHandler::action_command_setFilePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "id" << "priority");
QString hash = request().posts["hash"];
int file_id = request().posts["id"].toInt();
int priority = request().posts["priority"].toInt();
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid() && h.has_metadata())
h.file_priority(file_id, priority);
}
void RequestHandler::action_command_getGlobalUpLimit()
{
CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit));
}
void RequestHandler::action_command_getGlobalDlLimit()
{
CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit));
}
void RequestHandler::action_command_setGlobalUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("limit");
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QBtSession::instance()->setUploadRateLimit(limit);
if (Preferences::instance()->isAltBandwidthEnabled())
Preferences::instance()->setAltGlobalUploadLimit(limit / 1024.);
else
Preferences::instance()->setGlobalUploadLimit(limit / 1024.);
}
void RequestHandler::action_command_setGlobalDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("limit");
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QBtSession::instance()->setDownloadRateLimit(limit);
if (Preferences::instance()->isAltBandwidthEnabled())
Preferences::instance()->setAltGlobalDownloadLimit(limit / 1024.);
else
Preferences::instance()->setGlobalDownloadLimit(limit / 1024.);
}
void RequestHandler::action_command_getTorrentUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QString hash = request().posts["hash"];
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
print(QByteArray::number(h.upload_limit()));
}
void RequestHandler::action_command_getTorrentDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QString hash = request().posts["hash"];
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
print(QByteArray::number(h.download_limit()));
}
void RequestHandler::action_command_setTorrentUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "limit");
QString hash = request().posts["hash"];
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
h.set_upload_limit(limit);
}
void RequestHandler::action_command_setTorrentDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "limit");
QString hash = request().posts["hash"];
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
h.set_download_limit(limit);
}
void RequestHandler::action_command_toggleAlternativeSpeedLimits()
{
CHECK_URI(0);
QBtSession::instance()->useAlternativeSpeedsLimit(!Preferences::instance()->isAltBandwidthEnabled());
}
void RequestHandler::action_command_alternativeSpeedLimitsEnabled()
{
CHECK_URI(0);
print(QByteArray::number(Preferences::instance()->isAltBandwidthEnabled()));
}
void RequestHandler::action_command_toggleSequentialDownload()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
h.toggleSequentialDownload();
}
catch(invalid_handle&) {}
}
}
void RequestHandler::action_command_toggleFirstLastPiecePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
h.toggleFirstLastPiecePrio();
}
catch(invalid_handle&) {}
}
}
void RequestHandler::action_command_delete()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, false);
}
void RequestHandler::action_command_deletePerm()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, true);
}
void RequestHandler::action_command_increasePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
std::priority_queue<QPair<int, QTorrentHandle>,
std::vector<QPair<int, QTorrentHandle> >,
std::greater<QPair<int, QTorrentHandle> > > torrent_queue;
// Sort torrents by priority
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (!h.is_seed())
torrent_queue.push(qMakePair(h.queue_position(), h));
}
catch(invalid_handle&) {}
}
// Increase torrents priority (starting with the ones with highest priority)
while(!torrent_queue.empty()) {
QTorrentHandle h = torrent_queue.top().second;
try {
h.queue_position_up();
}
catch(invalid_handle&) {}
torrent_queue.pop();
}
}
void RequestHandler::action_command_decreasePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
std::priority_queue<QPair<int, QTorrentHandle>,
std::vector<QPair<int, QTorrentHandle> >,
std::less<QPair<int, QTorrentHandle> > > torrent_queue;
// Sort torrents by priority
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (!h.is_seed())
torrent_queue.push(qMakePair(h.queue_position(), h));
}
catch(invalid_handle&) {}
}
// Decrease torrents priority (starting with the ones with lowest priority)
while(!torrent_queue.empty()) {
QTorrentHandle h = torrent_queue.top().second;
try {
h.queue_position_down();
}
catch(invalid_handle&) {}
torrent_queue.pop();
}
}
void RequestHandler::action_command_topPrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
foreach (const QString &hash, request().posts["hashes"].split("|")) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid()) h.queue_position_top();
}
}
void RequestHandler::action_command_bottomPrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
foreach (const QString &hash, request().posts["hashes"].split("|")) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid()) h.queue_position_bottom();
}
}
void RequestHandler::action_command_recheck()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->recheckTorrent(request().posts["hash"]);
}
bool RequestHandler::isPublicScope()
{
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO);
}
void RequestHandler::processRequest()
{
if (args_.contains(".") || args_.contains("..")) {
qDebug() << Q_FUNC_INFO << "Invalid path:" << request().path;
status(404, "Not Found");
return;
}
if (!isPublicScope() && !sessionActive()) {
status(403, "Forbidden");
return;
}
if (actions_.value(scope_).value(action_) != 0) {
(this->*(actions_[scope_][action_]))();
}
else {
status(404, "Not Found");
qDebug() << Q_FUNC_INFO << "Resource not found:" << request().path;
}
}
void RequestHandler::parsePath()
{
if(request().path == "/") action_ = WEBUI_ACTION;
// check action for requested path
QStringList pathItems = request().path.split('/', QString::SkipEmptyParts);
if (!pathItems.empty()) {
if (actions_.contains(pathItems.front())) {
scope_ = pathItems.front();
pathItems.pop_front();
}
}
if (!pathItems.empty()) {
if (actions_[scope_].contains(pathItems.front())) {
action_ = pathItems.front();
pathItems.pop_front();
}
}
args_ = pathItems;
}
RequestHandler::RequestHandler(const HttpRequest &request, const HttpEnvironment &env, WebApplication *app)
: AbstractRequestHandler(request, env, app), scope_(DEFAULT_SCOPE), action_(DEFAULT_ACTION)
{
parsePath();
}
QMap<QString, QMap<QString, RequestHandler::Action> > RequestHandler::actions_ = RequestHandler::initializeActions();

View File

@@ -1,108 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 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 REQUESTHANDLER_H
#define REQUESTHANDLER_H
#include <QStringList>
#include "httptypes.h"
#include "abstractrequesthandler.h"
class WebApplication;
class RequestHandler: public AbstractRequestHandler
{
public:
RequestHandler(
const HttpRequest& request, const HttpEnvironment& env,
WebApplication* app);
private:
// Actions
void action_public_webui();
void action_public_index();
void action_public_login();
void action_public_logout();
void action_public_theme();
void action_public_images();
void action_query_torrents();
void action_query_preferences();
void action_query_transferInfo();
void action_query_propertiesGeneral();
void action_query_propertiesTrackers();
void action_query_propertiesFiles();
void action_sync_maindata();
void action_command_shutdown();
void action_command_download();
void action_command_upload();
void action_command_addTrackers();
void action_command_resumeAll();
void action_command_pauseAll();
void action_command_resume();
void action_command_pause();
void action_command_setPreferences();
void action_command_setFilePrio();
void action_command_getGlobalUpLimit();
void action_command_getGlobalDlLimit();
void action_command_setGlobalUpLimit();
void action_command_setGlobalDlLimit();
void action_command_getTorrentUpLimit();
void action_command_getTorrentDlLimit();
void action_command_setTorrentUpLimit();
void action_command_setTorrentDlLimit();
void action_command_alternativeSpeedLimitsEnabled();
void action_command_toggleAlternativeSpeedLimits();
void action_command_toggleSequentialDownload();
void action_command_toggleFirstLastPiecePrio();
void action_command_delete();
void action_command_deletePerm();
void action_command_increasePrio();
void action_command_decreasePrio();
void action_command_topPrio();
void action_command_bottomPrio();
void action_command_recheck();
void action_version_api();
void action_version_api_min();
void action_version_qbittorrent();
typedef void (RequestHandler::*Action)();
QString scope_;
QString action_;
QStringList args_;
void processRequest();
bool isPublicScope();
void parsePath();
static QMap<QString, QMap<QString, Action> > initializeActions();
static QMap<QString, QMap<QString, Action> > actions_;
};
#endif // REQUESTHANDLER_H

View File

@@ -26,285 +26,691 @@
* exception statement from your version.
*/
#ifdef DISABLE_GUI
#include <QCoreApplication>
#else
#include <QApplication>
#endif
#include <QDateTime>
#include <QTimer>
#include <QFile>
#include <QDebug>
#include <QCoreApplication>
#include <QTimer>
#include <QCryptographicHash>
#include <queue>
#include <vector>
#include <libtorrent/session.hpp>
#ifndef DISABLE_GUI
// TODO: Drop GUI dependency!
#include "iconprovider.h"
#endif
#include "misc.h"
#include "fs_utils.h"
#include "preferences.h"
#include "requesthandler.h"
#include "btjson.h"
#include "prefjson.h"
#include "qbtsession.h"
#include "websessiondata.h"
#include "webapplication.h"
// UnbanTimer
using namespace libtorrent;
class UnbanTimer: public QTimer
static const int API_VERSION = 2;
static const int API_VERSION_MIN = 2;
const QString WWW_FOLDER = ":/www/public/";
const QString PRIVATE_FOLDER = ":/www/private/";
const QString DEFAULT_SCOPE = "public";
const QString SCOPE_IMAGES = "images";
const QString SCOPE_THEME = "theme";
const QString DEFAULT_ACTION = "index";
const QString WEBUI_ACTION = "webui";
const QString VERSION_INFO = "version";
const QString MAX_AGE_MONTH = "public, max-age=2592000";
#define ADD_ACTION(scope, action) actions[#scope][#action] = &WebApplication::action_##scope##_##action
QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::initializeActions()
{
public:
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
: QTimer(parent), m_peerIp(peer_ip)
{
setSingleShot(true);
setInterval(BAN_TIME);
}
QMap<QString,QMap<QString, WebApplication::Action> > actions;
inline const QHostAddress& peerIp() const { return m_peerIp; }
ADD_ACTION(public, webui);
ADD_ACTION(public, index);
ADD_ACTION(public, login);
ADD_ACTION(public, logout);
ADD_ACTION(public, theme);
ADD_ACTION(public, images);
ADD_ACTION(query, torrents);
ADD_ACTION(query, preferences);
ADD_ACTION(query, transferInfo);
ADD_ACTION(query, propertiesGeneral);
ADD_ACTION(query, propertiesTrackers);
ADD_ACTION(query, propertiesFiles);
ADD_ACTION(sync, maindata);
ADD_ACTION(command, shutdown);
ADD_ACTION(command, download);
ADD_ACTION(command, upload);
ADD_ACTION(command, addTrackers);
ADD_ACTION(command, resumeAll);
ADD_ACTION(command, pauseAll);
ADD_ACTION(command, resume);
ADD_ACTION(command, pause);
ADD_ACTION(command, setPreferences);
ADD_ACTION(command, setFilePrio);
ADD_ACTION(command, getGlobalUpLimit);
ADD_ACTION(command, getGlobalDlLimit);
ADD_ACTION(command, setGlobalUpLimit);
ADD_ACTION(command, setGlobalDlLimit);
ADD_ACTION(command, getTorrentUpLimit);
ADD_ACTION(command, getTorrentDlLimit);
ADD_ACTION(command, setTorrentUpLimit);
ADD_ACTION(command, setTorrentDlLimit);
ADD_ACTION(command, alternativeSpeedLimitsEnabled);
ADD_ACTION(command, toggleAlternativeSpeedLimits);
ADD_ACTION(command, toggleSequentialDownload);
ADD_ACTION(command, toggleFirstLastPiecePrio);
ADD_ACTION(command, delete);
ADD_ACTION(command, deletePerm);
ADD_ACTION(command, increasePrio);
ADD_ACTION(command, decreasePrio);
ADD_ACTION(command, topPrio);
ADD_ACTION(command, bottomPrio);
ADD_ACTION(command, recheck);
ADD_ACTION(version, api);
ADD_ACTION(version, api_min);
ADD_ACTION(version, qbittorrent);
private:
QHostAddress m_peerIp;
};
return actions;
}
// WebApplication
#define CHECK_URI(ARGS_NUM) \
if (args_.size() != ARGS_NUM) { \
status(404, "Not Found"); \
return; \
}
#define CHECK_PARAMETERS(PARAMETERS) \
QStringList parameters; \
parameters << PARAMETERS; \
if (parameters.size() != request().posts.size()) { \
status(400, "Bad Request"); \
return; \
} \
foreach (QString key, request().posts.keys()) { \
if (!parameters.contains(key, Qt::CaseInsensitive)) { \
status(400, "Bad Request"); \
return; \
} \
}
void WebApplication::action_public_index()
{
QString path;
if (!args_.isEmpty()) {
if (args_.back() == "favicon.ico")
path = ":/icons/skin/qbittorrent16.png";
else
path = WWW_FOLDER + args_.join("/");
}
printFile(path);
}
void WebApplication::action_public_webui()
{
if (!sessionActive())
printFile(PRIVATE_FOLDER + "login.html");
else
printFile(PRIVATE_FOLDER + "index.html");
}
void WebApplication::action_public_login()
{
const Preferences* const pref = Preferences::instance();
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(request().posts["password"].toLocal8Bit());
QString pass = md5.result().toHex();
bool equalUser = misc::slowEquals(request().posts["username"].toUtf8(), pref->getWebUiUsername().toUtf8());
bool equalPass = misc::slowEquals(pass.toUtf8(), pref->getWebUiPassword().toUtf8());
if (equalUser && equalPass) {
sessionStart();
print(QByteArray("Ok."), Http::CONTENT_TYPE_TXT);
}
else {
QString addr = env().clientAddress.toString();
increaseFailedAttempts();
qDebug("client IP: %s (%d failed attempts)", qPrintable(addr), failedAttempts());
print(QByteArray("Fails."), Http::CONTENT_TYPE_TXT);
}
}
void WebApplication::action_public_logout()
{
CHECK_URI(0);
sessionEnd();
}
void WebApplication::action_public_theme()
{
if (args_.size() != 1) {
status(404, "Not Found");
return;
}
#ifdef DISABLE_GUI
QString url = ":/icons/oxygen/" + args_.front() + ".png";
#else
QString url = IconProvider::instance()->getIconPath(args_.front());
#endif
qDebug() << Q_FUNC_INFO << "There icon:" << url;
printFile(url);
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
}
void WebApplication::action_public_images()
{
const QString path = ":/icons/" + args_.join("/");
printFile(path);
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
}
// GET params:
// - filter (string): all, downloading, completed, paused, active, inactive
// - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label")
// - sort (string): name of column for sorting by its value
// - reverse (bool): enable reverse sorting
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
// - offset (int): set offset (if less than 0 - offset from end)
void WebApplication::action_query_torrents()
{
CHECK_URI(0);
const QStringMap& gets = request().gets;
print(btjson::getTorrents(
gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true",
gets["limit"].toInt(), gets["offset"].toInt()
), Http::CONTENT_TYPE_JS);
}
void WebApplication::action_query_preferences()
{
CHECK_URI(0);
print(prefjson::getPreferences(), Http::CONTENT_TYPE_JS);
}
void WebApplication::action_query_transferInfo()
{
CHECK_URI(0);
print(btjson::getTransferInfo(), Http::CONTENT_TYPE_JS);
}
void WebApplication::action_query_propertiesGeneral()
{
CHECK_URI(1);
print(btjson::getPropertiesForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
}
void WebApplication::action_query_propertiesTrackers()
{
CHECK_URI(1);
print(btjson::getTrackersForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
}
void WebApplication::action_query_propertiesFiles()
{
CHECK_URI(1);
print(btjson::getFilesForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
}
// GET param:
// - rid (int): last response id
void WebApplication::action_sync_maindata()
{
CHECK_URI(0);
print(btjson::getSyncMainData(request().gets["rid"].toInt(), session()->syncMainDataLastResponse, session()->syncMainDataLastAcceptedResponse));
}
void WebApplication::action_version_api()
{
CHECK_URI(0);
print(QString::number(API_VERSION), Http::CONTENT_TYPE_TXT);
}
void WebApplication::action_version_api_min()
{
CHECK_URI(0);
print(QString::number(API_VERSION_MIN), Http::CONTENT_TYPE_TXT);
}
void WebApplication::action_version_qbittorrent()
{
CHECK_URI(0);
print(QString(VERSION), Http::CONTENT_TYPE_TXT);
}
void WebApplication::action_command_shutdown()
{
qDebug() << "Shutdown request from Web UI";
// Special case handling for shutdown, we
// need to reply to the Web UI before
// actually shutting down.
CHECK_URI(0);
QTimer::singleShot(0, qApp, SLOT(quit()));
}
void WebApplication::action_command_download()
{
CHECK_URI(0);
CHECK_PARAMETERS("urls");
QString urls = request().posts["urls"];
QStringList list = urls.split('\n');
foreach (QString url, list) {
url = url.trimmed();
if (!url.isEmpty()) {
if (url.startsWith("bc://bt/", Qt::CaseInsensitive)) {
qDebug("Converting bc link to magnet link");
url = misc::bcLinkToMagnet(url);
}
else if (url.startsWith("magnet:", Qt::CaseInsensitive)) {
QBtSession::instance()->addMagnetSkipAddDlg(url);
}
else {
qDebug("Downloading url: %s", qPrintable(url));
QBtSession::instance()->downloadUrlAndSkipDialog(url);
}
}
}
}
void WebApplication::action_command_upload()
{
qDebug() << Q_FUNC_INFO;
CHECK_URI(0);
foreach(const Http::UploadedFile& torrent, request().files) {
QString filePath = saveTmpFile(torrent.data);
if (!filePath.isEmpty()) {
QTorrentHandle h = QBtSession::instance()->addTorrent(filePath);
if (!h.is_valid()) {
status(415, "Internal Server Error");
print(QObject::tr("Error: '%1' is not a valid torrent file.\n").arg(torrent.filename), Http::CONTENT_TYPE_TXT);
}
// Clean up
fsutils::forceRemove(filePath);
}
else {
qWarning() << "I/O Error: Could not create temporary file";
status(500, "Internal Server Error");
print(QObject::tr("I/O Error: Could not create temporary file."), Http::CONTENT_TYPE_TXT);
}
}
}
void WebApplication::action_command_addTrackers()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "urls");
QString hash = request().posts["hash"];
if (!hash.isEmpty()) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid() && h.has_metadata()) {
QString urls = request().posts["urls"];
QStringList list = urls.split('\n');
foreach (const QString& url, list) {
announce_entry e(url.toStdString());
h.add_tracker(e);
}
}
}
}
void WebApplication::action_command_resumeAll()
{
CHECK_URI(0);
QBtSession::instance()->resumeAllTorrents();
}
void WebApplication::action_command_pauseAll()
{
CHECK_URI(0);
QBtSession::instance()->pauseAllTorrents();
}
void WebApplication::action_command_resume()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->resumeTorrent(request().posts["hash"]);
}
void WebApplication::action_command_pause()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->pauseTorrent(request().posts["hash"]);
}
void WebApplication::action_command_setPreferences()
{
CHECK_URI(0);
CHECK_PARAMETERS("json");
prefjson::setPreferences(request().posts["json"]);
}
void WebApplication::action_command_setFilePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "id" << "priority");
QString hash = request().posts["hash"];
int file_id = request().posts["id"].toInt();
int priority = request().posts["priority"].toInt();
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid() && h.has_metadata())
h.file_priority(file_id, priority);
}
void WebApplication::action_command_getGlobalUpLimit()
{
CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit));
}
void WebApplication::action_command_getGlobalDlLimit()
{
CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit));
}
void WebApplication::action_command_setGlobalUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("limit");
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QBtSession::instance()->setUploadRateLimit(limit);
if (Preferences::instance()->isAltBandwidthEnabled())
Preferences::instance()->setAltGlobalUploadLimit(limit / 1024.);
else
Preferences::instance()->setGlobalUploadLimit(limit / 1024.);
}
void WebApplication::action_command_setGlobalDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("limit");
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QBtSession::instance()->setDownloadRateLimit(limit);
if (Preferences::instance()->isAltBandwidthEnabled())
Preferences::instance()->setAltGlobalDownloadLimit(limit / 1024.);
else
Preferences::instance()->setGlobalDownloadLimit(limit / 1024.);
}
void WebApplication::action_command_getTorrentUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QString hash = request().posts["hash"];
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
print(QByteArray::number(h.upload_limit()));
}
void WebApplication::action_command_getTorrentDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QString hash = request().posts["hash"];
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
print(QByteArray::number(h.download_limit()));
}
void WebApplication::action_command_setTorrentUpLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "limit");
QString hash = request().posts["hash"];
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
h.set_upload_limit(limit);
}
void WebApplication::action_command_setTorrentDlLimit()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "limit");
QString hash = request().posts["hash"];
qlonglong limit = request().posts["limit"].toLongLong();
if (limit == 0) limit = -1;
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid())
h.set_download_limit(limit);
}
void WebApplication::action_command_toggleAlternativeSpeedLimits()
{
CHECK_URI(0);
QBtSession::instance()->useAlternativeSpeedsLimit(!Preferences::instance()->isAltBandwidthEnabled());
}
void WebApplication::action_command_alternativeSpeedLimitsEnabled()
{
CHECK_URI(0);
print(QByteArray::number(Preferences::instance()->isAltBandwidthEnabled()));
}
void WebApplication::action_command_toggleSequentialDownload()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
h.toggleSequentialDownload();
}
catch(invalid_handle&) {}
}
}
void WebApplication::action_command_toggleFirstLastPiecePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
h.toggleFirstLastPiecePrio();
}
catch(invalid_handle&) {}
}
}
void WebApplication::action_command_delete()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, false);
}
void WebApplication::action_command_deletePerm()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, true);
}
void WebApplication::action_command_increasePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
std::priority_queue<QPair<int, QTorrentHandle>,
std::vector<QPair<int, QTorrentHandle> >,
std::greater<QPair<int, QTorrentHandle> > > torrent_queue;
// Sort torrents by priority
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (!h.is_seed())
torrent_queue.push(qMakePair(h.queue_position(), h));
}
catch(invalid_handle&) {}
}
// Increase torrents priority (starting with the ones with highest priority)
while(!torrent_queue.empty()) {
QTorrentHandle h = torrent_queue.top().second;
try {
h.queue_position_up();
}
catch(invalid_handle&) {}
torrent_queue.pop();
}
}
void WebApplication::action_command_decreasePrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
std::priority_queue<QPair<int, QTorrentHandle>,
std::vector<QPair<int, QTorrentHandle> >,
std::less<QPair<int, QTorrentHandle> > > torrent_queue;
// Sort torrents by priority
foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (!h.is_seed())
torrent_queue.push(qMakePair(h.queue_position(), h));
}
catch(invalid_handle&) {}
}
// Decrease torrents priority (starting with the ones with lowest priority)
while(!torrent_queue.empty()) {
QTorrentHandle h = torrent_queue.top().second;
try {
h.queue_position_down();
}
catch(invalid_handle&) {}
torrent_queue.pop();
}
}
void WebApplication::action_command_topPrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
foreach (const QString &hash, request().posts["hashes"].split("|")) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid()) h.queue_position_top();
}
}
void WebApplication::action_command_bottomPrio()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes");
foreach (const QString &hash, request().posts["hashes"].split("|")) {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
if (h.is_valid()) h.queue_position_bottom();
}
}
void WebApplication::action_command_recheck()
{
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->recheckTorrent(request().posts["hash"]);
}
bool WebApplication::isPublicScope()
{
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO);
}
void WebApplication::processRequest()
{
scope_ = DEFAULT_SCOPE;
action_ = DEFAULT_ACTION;
parsePath();
if (args_.contains(".") || args_.contains("..")) {
qDebug() << Q_FUNC_INFO << "Invalid path:" << request().path;
status(404, "Not Found");
return;
}
if (!isPublicScope() && !sessionActive()) {
status(403, "Forbidden");
return;
}
if (actions_.value(scope_).value(action_) != 0) {
(this->*(actions_[scope_][action_]))();
}
else {
status(404, "Not Found");
qDebug() << Q_FUNC_INFO << "Resource not found:" << request().path;
}
}
void WebApplication::parsePath()
{
if(request().path == "/") action_ = WEBUI_ACTION;
// check action for requested path
QStringList pathItems = request().path.split('/', QString::SkipEmptyParts);
if (!pathItems.empty()) {
if (actions_.contains(pathItems.front())) {
scope_ = pathItems.front();
pathItems.pop_front();
}
}
if (!pathItems.empty()) {
if (actions_[scope_].contains(pathItems.front())) {
action_ = pathItems.front();
pathItems.pop_front();
}
}
args_ = pathItems;
}
WebApplication::WebApplication(QObject *parent)
: QObject(parent)
: AbstractWebApplication(parent)
{
}
WebApplication::~WebApplication()
{
// cleanup sessions data
foreach (WebSession* session, sessions_.values())
delete session;
}
WebApplication *WebApplication::instance()
{
static WebApplication inst;
return &inst;
}
void WebApplication::UnbanTimerEvent()
{
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
clientFailedAttempts_.remove(ubantimer->peerIp());
ubantimer->deleteLater();
}
bool WebApplication::sessionInitialize(AbstractRequestHandler* _this)
{
if (_this->session_ == 0)
{
QString cookie = _this->request_.headers.value("cookie");
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
QString sessionId;
const QString SID_START = C_SID + "=";
int pos = cookie.indexOf(SID_START);
if (pos >= 0)
{
pos += SID_START.length();
int end = cookie.indexOf(QRegExp("[,;]"), pos);
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
}
// TODO: Additional session check
if (!sessionId.isNull())
{
if (sessions_.contains(sessionId))
{
_this->session_ = sessions_[sessionId];
return true;
}
else
{
qDebug() << Q_FUNC_INFO << "session does not exist!";
}
}
}
return false;
}
bool WebApplication::readFile(const QString& path, QByteArray &data, QString &type)
{
QString ext = "";
int index = path.lastIndexOf('.') + 1;
if (index > 0)
ext = path.mid(index);
// find translated file in cache
if (translatedFiles_.contains(path))
{
data = translatedFiles_[path];
}
else
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
qDebug("File %s was not found!", qPrintable(path));
return false;
}
data = file.readAll();
file.close();
// Translate the file
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js")))
{
QString dataStr = QString::fromUtf8(data.constData());
translateDocument(dataStr);
if (path.endsWith("about.html"))
{
dataStr.replace("${VERSION}", VERSION);
}
data = dataStr.toUtf8();
translatedFiles_[path] = data; // cashing translated file
}
}
type = CONTENT_TYPE_BY_EXT[ext];
return true;
}
QString WebApplication::generateSid()
{
QString sid;
qsrand(QDateTime::currentDateTime().toTime_t());
do
{
const size_t size = 6;
quint32 tmp[size];
for (size_t i = 0; i < size; ++i)
tmp[i] = qrand();
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
}
while (sessions_.contains(sid));
return sid;
}
void WebApplication::translateDocument(QString& data)
{
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
const std::string contexts[] = {
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
"StatusBar"
};
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
int i = 0;
bool found = true;
const QString locale = Preferences::instance()->getLocale();
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
while(i < data.size() && found)
{
i = regex.indexIn(data, i);
if (i >= 0)
{
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
QByteArray word = regex.cap(1).toUtf8();
QString translation = word;
if (isTranslationNeeded)
{
size_t context_index = 0;
while ((context_index < context_count) && (translation == word))
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
#else
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
#endif
++context_index;
}
}
// Remove keyboard shortcuts
translation.replace(mnemonic, "");
data.replace(i, regex.matchedLength(), translation);
i += translation.length();
}
else
{
found = false; // no more translatable strings
}
}
}
bool WebApplication::isBanned(const AbstractRequestHandler *_this) const
{
return clientFailedAttempts_.value(_this->env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
}
int WebApplication::failedAttempts(const AbstractRequestHandler* _this) const
{
return clientFailedAttempts_.value(_this->env_.clientAddress, 0);
}
void WebApplication::resetFailedAttempts(AbstractRequestHandler* _this)
{
clientFailedAttempts_.remove(_this->env_.clientAddress);
}
void WebApplication::increaseFailedAttempts(AbstractRequestHandler* _this)
{
const int nb_fail = clientFailedAttempts_.value(_this->env_.clientAddress, 0) + 1;
clientFailedAttempts_[_this->env_.clientAddress] = nb_fail;
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS)
{
// Max number of failed attempts reached
// Start ban period
UnbanTimer* ubantimer = new UnbanTimer(_this->env_.clientAddress, this);
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
ubantimer->start();
}
}
bool WebApplication::sessionStart(AbstractRequestHandler *_this)
{
if (_this->session_ == 0)
{
_this->session_ = new WebSession(generateSid());
sessions_[_this->session_->id] = _this->session_;
return true;
}
return false;
}
bool WebApplication::sessionEnd(AbstractRequestHandler *_this)
{
if ((_this->session_ != 0) && (sessions_.contains(_this->session_->id)))
{
sessions_.remove(_this->session_->id);
delete _this->session_;
_this->session_ = 0;
return true;
}
return false;
}
QStringMap WebApplication::initializeContentTypeByExtMap()
{
QStringMap map;
map["htm"] = CONTENT_TYPE_HTML;
map["html"] = CONTENT_TYPE_HTML;
map["css"] = CONTENT_TYPE_CSS;
map["gif"] = CONTENT_TYPE_GIF;
map["png"] = CONTENT_TYPE_PNG;
map["js"] = CONTENT_TYPE_JS;
return map;
}
const QStringMap WebApplication::CONTENT_TYPE_BY_EXT = WebApplication::initializeContentTypeByExtMap();
QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::actions_ = WebApplication::initializeActions();

View File

@@ -29,61 +29,77 @@
#ifndef WEBAPPLICATION_H
#define WEBAPPLICATION_H
#include <QObject>
#include <QMap>
#include <QHash>
#include "httptypes.h"
#include <QVariant>
#include <QStringList>
#include "abstractwebapplication.h"
struct WebSession
class WebApplication: public AbstractWebApplication
{
const QString id;
QVariantMap syncMainDataLastResponse;
QVariantMap syncMainDataLastAcceptedResponse;
WebSession(const QString& id): id(id) {}
};
const QString C_SID = "SID"; // name of session id cookie
const int BAN_TIME = 3600000; // 1 hour
const int MAX_AUTH_FAILED_ATTEMPTS = 5;
class AbstractRequestHandler;
class WebApplication: public QObject
{
Q_OBJECT
Q_DISABLE_COPY(WebApplication)
public:
WebApplication(QObject* parent = 0);
virtual ~WebApplication();
static WebApplication* instance();
bool isBanned(const AbstractRequestHandler* _this) const;
int failedAttempts(const AbstractRequestHandler *_this) const;
void resetFailedAttempts(AbstractRequestHandler* _this);
void increaseFailedAttempts(AbstractRequestHandler* _this);
bool sessionStart(AbstractRequestHandler* _this);
bool sessionEnd(AbstractRequestHandler* _this);
bool sessionInitialize(AbstractRequestHandler* _this);
bool readFile(const QString &path, QByteArray& data, QString& type);
private slots:
void UnbanTimerEvent();
explicit WebApplication(QObject* parent = 0);
private:
QMap<QString, WebSession*> sessions_;
QHash<QHostAddress, int> clientFailedAttempts_;
QMap<QString, QByteArray> translatedFiles_;
// Actions
void action_public_webui();
void action_public_index();
void action_public_login();
void action_public_logout();
void action_public_theme();
void action_public_images();
void action_query_torrents();
void action_query_preferences();
void action_query_transferInfo();
void action_query_propertiesGeneral();
void action_query_propertiesTrackers();
void action_query_propertiesFiles();
void action_sync_maindata();
void action_command_shutdown();
void action_command_download();
void action_command_upload();
void action_command_addTrackers();
void action_command_resumeAll();
void action_command_pauseAll();
void action_command_resume();
void action_command_pause();
void action_command_setPreferences();
void action_command_setFilePrio();
void action_command_getGlobalUpLimit();
void action_command_getGlobalDlLimit();
void action_command_setGlobalUpLimit();
void action_command_setGlobalDlLimit();
void action_command_getTorrentUpLimit();
void action_command_getTorrentDlLimit();
void action_command_setTorrentUpLimit();
void action_command_setTorrentDlLimit();
void action_command_alternativeSpeedLimitsEnabled();
void action_command_toggleAlternativeSpeedLimits();
void action_command_toggleSequentialDownload();
void action_command_toggleFirstLastPiecePrio();
void action_command_delete();
void action_command_deletePerm();
void action_command_increasePrio();
void action_command_decreasePrio();
void action_command_topPrio();
void action_command_bottomPrio();
void action_command_recheck();
void action_version_api();
void action_version_api_min();
void action_version_qbittorrent();
QString generateSid();
static void translateDocument(QString& data);
typedef void (WebApplication::*Action)();
static const QStringMap CONTENT_TYPE_BY_EXT;
static QStringMap initializeContentTypeByExtMap();
QString scope_;
QString action_;
QStringList args_;
void processRequest();
bool isPublicScope();
void parsePath();
static QMap<QString, QMap<QString, Action> > initializeActions();
static QMap<QString, QMap<QString, Action> > actions_;
};
#endif // WEBAPPLICATION_H

View File

@@ -1,7 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
* 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
@@ -25,47 +24,18 @@
* 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 WEBSESSIONDATA
#define WEBSESSIONDATA
#ifndef HTTPSERVER_H
#define HTTPSERVER_H
#include <QVariant>
#include <QTcpServer>
#ifndef QT_NO_OPENSSL
#include <QSslCertificate>
#include <QSslKey>
#endif
class HttpServer : public QTcpServer
struct WebSessionData
{
Q_OBJECT
Q_DISABLE_COPY(HttpServer)
public:
HttpServer(QObject* parent = 0);
~HttpServer();
#ifndef QT_NO_OPENSSL
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
void disableHttps();
#endif
private:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
void incomingConnection(qintptr socketDescriptor);
#else
void incomingConnection(int socketDescriptor);
#endif
private:
#ifndef QT_NO_OPENSSL
bool m_https;
QSslCertificate m_certificate;
QSslKey m_key;
#endif
QVariantMap syncMainDataLastResponse;
QVariantMap syncMainDataLastAcceptedResponse;
};
#endif
#endif // WEBSESSIONDATA

102
src/webui/webui.cpp Normal file
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.
*/
#include "webui.h"
#include "http/server.h"
#include "webapplication.h"
#include "dnsupdater.h"
#include "preferences.h"
#include "logger.h"
WebUI::WebUI(QObject *parent) : QObject(parent)
{
init();
connect(Preferences::instance(), SIGNAL(changed()), SLOT(init()));
}
void WebUI::init()
{
Preferences* const pref = Preferences::instance();
Logger* const logger = Logger::instance();
if (pref->isWebUiEnabled()) {
const quint16 port = pref->getWebUiPort();
if (httpServer_) {
if (httpServer_->serverPort() != port)
httpServer_->close();
}
else {
webapp_ = new WebApplication(this);
httpServer_ = new Http::Server(webapp_, this);
}
#ifndef QT_NO_OPENSSL
if (pref->isWebUiHttpsEnabled()) {
QSslCertificate cert(pref->getWebUiHttpsCertificate());
QSslKey key;
key = QSslKey(pref->getWebUiHttpsKey(), QSsl::Rsa);
if (!cert.isNull() && !key.isNull())
httpServer_->enableHttps(cert, key);
else
httpServer_->disableHttps();
}
else {
httpServer_->disableHttps();
}
#endif
if (!httpServer_->isListening()) {
bool success = httpServer_->listen(QHostAddress::Any, port);
if (success)
logger->addMessage(tr("The Web UI is listening on port %1").arg(port));
else
logger->addMessage(tr("Web User Interface Error - Unable to bind Web UI to port %1").arg(port), Log::CRITICAL);
}
// DynDNS
if (pref->isDynDNSEnabled()) {
if (!dynDNSUpdater_)
dynDNSUpdater_ = new DNSUpdater(this);
else
dynDNSUpdater_->updateCredentials();
}
else {
if (dynDNSUpdater_)
delete dynDNSUpdater_;
}
}
else {
if (httpServer_)
delete httpServer_;
if (webapp_)
delete webapp_;
if (dynDNSUpdater_)
delete dynDNSUpdater_;
}
}

View File

@@ -1,7 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
* 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
@@ -25,42 +24,32 @@
* 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 HTTPCONNECTION_H
#define HTTPCONNECTION_H
#ifndef WEBUI_H
#define WEBUI_H
#include <QObject>
#include "httptypes.h"
#include <QPointer>
class HttpServer;
namespace Http { class Server; }
class DNSUpdater;
class AbstractWebApplication;
QT_BEGIN_NAMESPACE
class QTcpSocket;
QT_END_NAMESPACE
class HttpConnection : public QObject
class WebUI : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(HttpConnection)
Q_OBJECT
public:
HttpConnection(QTcpSocket* socket, HttpServer* httpserver);
~HttpConnection();
explicit WebUI(QObject *parent = 0);
private slots:
void read();
void init();
private:
void write(const HttpResponse& response);
static bool acceptsGzipEncoding(const QString& encoding);
QTcpSocket *m_socket;
QByteArray m_receivedData;
QPointer<Http::Server> httpServer_;
QPointer<DNSUpdater> dynDNSUpdater_;
QPointer<AbstractWebApplication> webapp_;
};
#endif
#endif // WEBUI_H

View File

@@ -1,30 +1,25 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/httpserver.h \
$$PWD/httpconnection.h \
$$PWD/btjson.h \
$$PWD/prefjson.h \
$$PWD/jsonutils.h \
$$PWD/extra_translations.h \
$$PWD/webapplication.h \
$$PWD/abstractrequesthandler.h \
$$PWD/requesthandler.h \
$$PWD/qtorrentfilter.h
HEADERS += \
$$PWD/webui.h \
$$PWD/btjson.h \
$$PWD/prefjson.h \
$$PWD/jsonutils.h \
$$PWD/extra_translations.h \
$$PWD/webapplication.h \
$$PWD/qtorrentfilter.h \
$$PWD/websessiondata.h \
$$PWD/abstractwebapplication.h
SOURCES += $$PWD/httpserver.cpp \
$$PWD/httpconnection.cpp \
$$PWD/httprequestparser.cpp \
$$PWD/httpresponsegenerator.cpp \
$$PWD/btjson.cpp \
$$PWD/prefjson.cpp \
$$PWD/webapplication.cpp \
$$PWD/abstractrequesthandler.cpp \
$$PWD/requesthandler.cpp \
$$PWD/qtorrentfilter.cpp
SOURCES += \
$$PWD/webui.cpp \
$$PWD/btjson.cpp \
$$PWD/prefjson.cpp \
$$PWD/webapplication.cpp \
$$PWD/qtorrentfilter.cpp \
$$PWD/abstractwebapplication.cpp
# QJson JSON parser/serializer for using with Qt4
lessThan(QT_MAJOR_VERSION, 5) {
include(qjson/qjson.pri)
}
lessThan(QT_MAJOR_VERSION, 5): include(qjson/qjson.pri)
RESOURCES += $$PWD/webui.qrc