Browse Source

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

adaptive-webui-19844
Vladimir Golovnev (Glassez) 10 years ago
parent
commit
2707f5205f
  1. 39
      src/app/application.cpp
  2. 11
      src/app/application.h
  3. 5
      src/app/main.cpp
  4. 24
      src/core/core.pri
  5. 49
      src/core/http/connection.cpp
  6. 31
      src/core/http/connection.h
  7. 47
      src/core/http/irequesthandler.h
  8. 58
      src/core/http/requestparser.cpp
  9. 25
      src/core/http/requestparser.h
  10. 74
      src/core/http/responsebuilder.cpp
  11. 60
      src/core/http/responsebuilder.h
  12. 6
      src/core/http/responsegenerator.cpp
  13. 17
      src/core/http/responsegenerator.h
  14. 21
      src/core/http/server.cpp
  15. 23
      src/core/http/server.h
  16. 25
      src/core/http/types.h
  17. 69
      src/core/qtlibtorrent/qbtsession.cpp
  18. 9
      src/core/qtlibtorrent/qbtsession.h
  19. 184
      src/core/qtracker.cpp
  20. 52
      src/core/qtracker.h
  21. 35
      src/core/tracker/qpeer.h
  22. 9
      src/core/tracker/tracker.pri
  23. 15
      src/core/tracker/trackerannouncerequest.h
  24. 3
      src/gui/mainwindow.cpp
  25. 6
      src/gui/options_imp.cpp
  26. 167
      src/webui/abstractrequesthandler.cpp
  27. 413
      src/webui/abstractwebapplication.cpp
  28. 89
      src/webui/abstractwebapplication.h
  29. 4
      src/webui/prefjson.cpp
  30. 716
      src/webui/requesthandler.cpp
  31. 108
      src/webui/requesthandler.h
  32. 782
      src/webui/webapplication.cpp
  33. 112
      src/webui/webapplication.h
  34. 41
      src/webui/websessiondata.h
  35. 102
      src/webui/webui.cpp
  36. 55
      src/webui/webui.h
  37. 25
      src/webui/webui.pri

39
src/app/application.cpp

@ -32,6 +32,7 @@
#include <QLocale> #include <QLocale>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QSysInfo> #include <QSysInfo>
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <Windows.h> #include <Windows.h>
@ -39,6 +40,7 @@
#endif // Q_OS_WIN #endif // Q_OS_WIN
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <QFont>
#include <QUrl> #include <QUrl>
#endif // Q_OS_MAC #endif // Q_OS_MAC
#include "mainwindow.h" #include "mainwindow.h"
@ -47,8 +49,8 @@
#include <iostream> #include <iostream>
#endif // DISABLE_GUI #endif // DISABLE_GUI
#if (!defined(DISABLE_GUI) && defined(Q_OS_MAC)) #ifndef DISABLE_WEBUI
#include <QFont> #include "webui.h"
#endif #endif
#include "application.h" #include "application.h"
@ -92,17 +94,6 @@ void Application::processMessage(const QString &message)
m_paramsQueue.append(params); m_paramsQueue.append(params);
} }
void Application::cleanup()
{
#ifndef DISABLE_GUI
delete m_window;
#endif
QBtSession::drop();
TorrentPersistentData::drop();
Preferences::drop();
Logger::drop();
}
bool Application::sendParams(const QStringList &params) bool Application::sendParams(const QStringList &params)
{ {
return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR))); return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR)));
@ -159,7 +150,12 @@ int Application::exec(const QStringList &params)
// Resume unfinished torrents // Resume unfinished torrents
QBtSession::instance()->startUpTorrents(); QBtSession::instance()->startUpTorrents();
#ifndef DISABLE_WEBUI
m_webui = new WebUI;
#endif
#ifdef DISABLE_GUI #ifdef DISABLE_GUI
#ifndef DISABLE_WEBUI
Preferences* const pref = Preferences::instance(); Preferences* const pref = Preferences::instance();
if (pref->isWebUiEnabled()) { if (pref->isWebUiEnabled()) {
// Display some information to the user // Display some information to the user
@ -172,9 +168,10 @@ int Application::exec(const QStringList &params)
std::cout << qPrintable(tr("This is a security risk, please consider changing your password from program preferences.")) << std::endl; std::cout << qPrintable(tr("This is a security risk, please consider changing your password from program preferences.")) << std::endl;
} }
} }
#endif // DISABLE_WEBUI
#else #else
m_window = new MainWindow; m_window = new MainWindow;
#endif #endif // DISABLE_GUI
m_running = true; m_running = true;
m_paramsQueue = params + m_paramsQueue; m_paramsQueue = params + m_paramsQueue;
@ -290,3 +287,17 @@ void Application::initializeTranslation()
} }
#endif #endif
} }
void Application::cleanup()
{
#ifndef DISABLE_GUI
delete m_window;
#endif
#ifndef DISABLE_WEBUI
delete m_webui;
#endif
QBtSession::drop();
TorrentPersistentData::drop();
Preferences::drop();
Logger::drop();
}

11
src/app/application.h

@ -30,6 +30,7 @@
#ifndef APPLICATION_H #ifndef APPLICATION_H
#define APPLICATION_H #define APPLICATION_H
#include <QPointer>
#include <QStringList> #include <QStringList>
#include <QTranslator> #include <QTranslator>
@ -42,6 +43,10 @@ class MainWindow;
typedef QtSingleCoreApplication BaseApplication; typedef QtSingleCoreApplication BaseApplication;
#endif #endif
#ifndef DISABLE_WEBUI
class WebUI;
#endif
class Application : public BaseApplication class Application : public BaseApplication
{ {
Q_OBJECT Q_OBJECT
@ -69,9 +74,15 @@ private slots:
private: private:
bool m_running; bool m_running;
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
QPointer<MainWindow> m_window; QPointer<MainWindow> m_window;
#endif #endif
#ifndef DISABLE_WEBUI
QPointer<WebUI> m_webui;
#endif
QTranslator m_qtTranslator; QTranslator m_qtTranslator;
QTranslator m_translator; QTranslator m_translator;
QStringList m_paramsQueue; QStringList m_paramsQueue;

5
src/app/main.cpp

@ -49,11 +49,8 @@ Q_IMPORT_PLUGIN(qico)
#endif // QBT_STATIC_QT #endif // QBT_STATIC_QT
#else // DISABLE_GUI #else // DISABLE_GUI
#include <cstdio> #include <cstdio>
#include <iostream>
#endif // DISABLE_GUI #endif // DISABLE_GUI
#include "application.h"
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include <signal.h> #include <signal.h>
#include <execinfo.h> #include <execinfo.h>
@ -67,6 +64,8 @@ Q_IMPORT_PLUGIN(qico)
#endif //STACKTRACE_WIN #endif //STACKTRACE_WIN
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include "application.h"
#include "misc.h" #include "misc.h"
#include "preferences.h" #include "preferences.h"
#include "logger.h" #include "logger.h"

24
src/core/core.pri

@ -3,7 +3,6 @@ INCLUDEPATH += $$PWD
unix:!macx:dbus: include(qtnotify/qtnotify.pri) unix:!macx:dbus: include(qtnotify/qtnotify.pri)
include(qtlibtorrent/qtlibtorrent.pri) include(qtlibtorrent/qtlibtorrent.pri)
include(tracker/tracker.pri)
HEADERS += \ HEADERS += \
$$PWD/misc.h \ $$PWD/misc.h \
@ -16,10 +15,15 @@ HEADERS += \
$$PWD/smtp.h \ $$PWD/smtp.h \
$$PWD/dnsupdater.h \ $$PWD/dnsupdater.h \
$$PWD/logger.h \ $$PWD/logger.h \
$$PWD/httptypes.h \ $$PWD/preferences.h \
$$PWD/httprequestparser.h \ $$PWD/qtracker.h \
$$PWD/httpresponsegenerator.h \ $$PWD/http/irequesthandler.h \
$$PWD/preferences.h $$PWD/http/connection.h \
$$PWD/http/requestparser.h \
$$PWD/http/responsegenerator.h \
$$PWD/http/server.h \
$$PWD/http/types.h \
$$PWD/http/responsebuilder.h
SOURCES += \ SOURCES += \
$$PWD/downloadthread.cpp \ $$PWD/downloadthread.cpp \
@ -30,6 +34,10 @@ SOURCES += \
$$PWD/smtp.cpp \ $$PWD/smtp.cpp \
$$PWD/dnsupdater.cpp \ $$PWD/dnsupdater.cpp \
$$PWD/logger.cpp \ $$PWD/logger.cpp \
$$PWD/httprequestparser.cpp \ $$PWD/preferences.cpp \
$$PWD/httpresponsegenerator.cpp \ $$PWD/qtracker.cpp \
$$PWD/preferences.cpp $$PWD/http/connection.cpp \
$$PWD/http/requestparser.cpp \
$$PWD/http/responsegenerator.cpp \
$$PWD/http/server.cpp \
$$PWD/http/responsebuilder.cpp

49
src/webui/httpconnection.cpp → src/core/http/connection.cpp

@ -31,59 +31,60 @@
#include <QTcpSocket> #include <QTcpSocket>
#include <QDebug> #include <QDebug>
#include "httptypes.h" #include "types.h"
#include "httpserver.h" #include "requestparser.h"
#include "httprequestparser.h" #include "responsegenerator.h"
#include "httpresponsegenerator.h" #include "irequesthandler.h"
#include "webapplication.h" #include "connection.h"
#include "requesthandler.h"
#include "httpconnection.h"
HttpConnection::HttpConnection(QTcpSocket *socket, HttpServer *httpserver) using namespace Http;
: QObject(httpserver), m_socket(socket)
Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent)
: QObject(parent)
, m_socket(socket)
, m_requestHandler(requestHandler)
{ {
m_socket->setParent(this); m_socket->setParent(this);
connect(m_socket, SIGNAL(readyRead()), SLOT(read())); connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater())); connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
} }
HttpConnection::~HttpConnection() Connection::~Connection()
{ {
delete m_socket;
} }
void HttpConnection::read() void Connection::read()
{ {
m_receivedData.append(m_socket->readAll()); m_receivedData.append(m_socket->readAll());
HttpRequest request; Request request;
HttpRequestParser::ErrorCode err = HttpRequestParser::parse(m_receivedData, request); RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request);
switch (err) switch (err)
{ {
case HttpRequestParser::IncompleteRequest: case RequestParser::IncompleteRequest:
// Partial request waiting for the rest // Partial request waiting for the rest
break; break;
case HttpRequestParser::BadRequest: case RequestParser::BadRequest:
write(HttpResponse(400, "Bad Request")); sendResponse(Response(400, "Bad Request"));
break; break;
case HttpRequestParser::NoError: case RequestParser::NoError:
HttpEnvironment env; Environment env;
env.clientAddress = m_socket->peerAddress(); env.clientAddress = m_socket->peerAddress();
HttpResponse response = RequestHandler(request, env, WebApplication::instance()).run(); Response response = m_requestHandler->processRequest(request, env);
if (acceptsGzipEncoding(request.headers["accept-encoding"])) if (acceptsGzipEncoding(request.headers["accept-encoding"]))
response.headers[HEADER_CONTENT_ENCODING] = "gzip"; response.headers[HEADER_CONTENT_ENCODING] = "gzip";
write(response); sendResponse(response);
break; break;
} }
} }
void HttpConnection::write(const HttpResponse& response) void Connection::sendResponse(const Response &response)
{ {
m_socket->write(HttpResponseGenerator::generate(response)); m_socket->write(ResponseGenerator::generate(response));
m_socket->disconnectFromHost(); m_socket->disconnectFromHost();
} }
bool HttpConnection::acceptsGzipEncoding(const QString& encoding) bool Connection::acceptsGzipEncoding(const QString &encoding)
{ {
int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive); int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive);
if (pos == -1) if (pos == -1)

31
src/webui/httpconnection.h → src/core/http/connection.h

@ -30,37 +30,42 @@
*/ */
#ifndef HTTPCONNECTION_H #ifndef HTTP_CONNECTION_H
#define HTTPCONNECTION_H #define HTTP_CONNECTION_H
#include <QObject> #include <QObject>
#include "httptypes.h" #include "types.h"
class HttpServer;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QTcpSocket; class QTcpSocket;
QT_END_NAMESPACE QT_END_NAMESPACE
class HttpConnection : public QObject namespace Http
{
class IRequestHandler;
class Connection : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(HttpConnection) Q_DISABLE_COPY(Connection)
public: public:
HttpConnection(QTcpSocket* socket, HttpServer* httpserver); Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
~HttpConnection(); ~Connection();
private slots: private slots:
void read(); void read();
private: private:
void write(const HttpResponse& response); static bool acceptsGzipEncoding(const QString &encoding);
void sendResponse(const Response &response);
static bool acceptsGzipEncoding(const QString& encoding);
QTcpSocket *m_socket; QTcpSocket *m_socket;
IRequestHandler *m_requestHandler;
QByteArray m_receivedData; QByteArray m_receivedData;
}; };
#endif }
#endif // HTTP_CONNECTION_H

47
src/core/http/irequesthandler.h

@ -0,0 +1,47 @@
/*
* 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 HTTP_IREQUESTHANDLER_H
#define HTTP_IREQUESTHANDLER_H
#include "types.h"
namespace Http
{
class IRequestHandler
{
public:
virtual ~IRequestHandler() {}
virtual Response processRequest(const Request &request, const Environment &env) = 0;
};
}
#endif // HTTP_IREQUESTHANDLER_H

58
src/core/httprequestparser.cpp → src/core/http/requestparser.cpp

@ -38,7 +38,7 @@
#include <QDir> #include <QDir>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QDebug> #include <QDebug>
#include "httprequestparser.h" #include "requestparser.h"
const QByteArray EOL("\r\n"); const QByteArray EOL("\r\n");
const QByteArray EOH("\r\n\r\n"); const QByteArray EOH("\r\n\r\n");
@ -51,19 +51,21 @@ inline QString unquoted(const QString& str)
return str; return str;
} }
HttpRequestParser::ErrorCode HttpRequestParser::parse(const QByteArray& data, HttpRequest& request, uint maxContentLength) using namespace Http;
RequestParser::ErrorCode RequestParser::parse(const QByteArray& data, Request& request, uint maxContentLength)
{ {
return HttpRequestParser(maxContentLength).parseHttpRequest(data, request); return RequestParser(maxContentLength).parseHttpRequest(data, request);
} }
HttpRequestParser::HttpRequestParser(uint maxContentLength) RequestParser::RequestParser(uint maxContentLength)
: maxContentLength_(maxContentLength) : m_maxContentLength(maxContentLength)
{ {
} }
HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArray& data, HttpRequest& request) RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray& data, Request& request)
{ {
request_ = HttpRequest(); m_request = Request();
// Parse HTTP request header // Parse HTTP request header
const int header_end = data.indexOf(EOH); const int header_end = data.indexOf(EOH);
@ -81,10 +83,10 @@ HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArra
// Parse HTTP request message // Parse HTTP request message
int content_length = 0; int content_length = 0;
if (request_.headers.contains("content-length")) if (m_request.headers.contains("content-length"))
{ {
content_length = request_.headers["content-length"].toInt(); content_length = m_request.headers["content-length"].toInt();
if (content_length > static_cast<int>(maxContentLength_)) if (content_length > static_cast<int>(m_maxContentLength))
{ {
qWarning() << Q_FUNC_INFO << "bad request: message too long"; qWarning() << Q_FUNC_INFO << "bad request: message too long";
return BadRequest; return BadRequest;
@ -108,20 +110,20 @@ HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArra
// qDebug() << "HTTP Request header:"; // qDebug() << "HTTP Request header:";
// qDebug() << data.left(header_end) << "\n"; // qDebug() << data.left(header_end) << "\n";
request = request_; request = m_request;
return NoError; return NoError;
} }
bool HttpRequestParser::parseStartingLine(const QString &line) bool RequestParser::parseStartingLine(const QString &line)
{ {
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$"); const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
if (rx.indexIn(line.trimmed()) >= 0) if (rx.indexIn(line.trimmed()) >= 0)
{ {
request_.method = rx.cap(1); m_request.method = rx.cap(1);
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1()); QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
request_.path = url.path(); // Path m_request.path = url.path(); // Path
// Parse GET parameters // Parse GET parameters
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
@ -132,7 +134,7 @@ bool HttpRequestParser::parseStartingLine(const QString &line)
while (i.hasNext()) while (i.hasNext())
{ {
QPair<QString, QString> pair = i.next(); QPair<QString, QString> pair = i.next();
request_.gets[pair.first] = pair.second; m_request.gets[pair.first] = pair.second;
} }
return true; return true;
@ -142,7 +144,7 @@ bool HttpRequestParser::parseStartingLine(const QString &line)
return false; return false;
} }
bool HttpRequestParser::parseHeaderLine(const QString &line, QPair<QString, QString>& out) bool RequestParser::parseHeaderLine(const QString &line, QPair<QString, QString>& out)
{ {
int i = line.indexOf(QLatin1Char(':')); int i = line.indexOf(QLatin1Char(':'));
if (i == -1) if (i == -1)
@ -155,7 +157,7 @@ bool HttpRequestParser::parseHeaderLine(const QString &line, QPair<QString, QStr
return true; return true;
} }
bool HttpRequestParser::parseHttpHeader(const QByteArray &data) bool RequestParser::parseHttpHeader(const QByteArray &data)
{ {
QString str = QString::fromUtf8(data); QString str = QString::fromUtf8(data);
QStringList lines = str.trimmed().split(EOL); QStringList lines = str.trimmed().split(EOL);
@ -191,13 +193,13 @@ bool HttpRequestParser::parseHttpHeader(const QByteArray &data)
if (!parseHeaderLine(*it, header)) if (!parseHeaderLine(*it, header))
return false; return false;
request_.headers[header.first] = header.second; m_request.headers[header.first] = header.second;
} }
return true; return true;
} }
QList<QByteArray> HttpRequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary) QList<QByteArray> RequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary)
{ {
QList<QByteArray> ret; QList<QByteArray> ret;
QByteArray sep = boundary + EOL; QByteArray sep = boundary + EOL;
@ -223,14 +225,14 @@ QList<QByteArray> HttpRequestParser::splitMultipartData(const QByteArray& data,
return ret; return ret;
} }
bool HttpRequestParser::parseContent(const QByteArray& data) bool RequestParser::parseContent(const QByteArray& data)
{ {
// Parse message content // Parse message content
qDebug() << Q_FUNC_INFO << "Content-Length: " << request_.headers["content-length"]; qDebug() << Q_FUNC_INFO << "Content-Length: " << m_request.headers["content-length"];
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size(); qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
// Parse url-encoded POST data // Parse url-encoded POST data
if (request_.headers["content-type"].startsWith("application/x-www-form-urlencoded")) if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded"))
{ {
QUrl url; QUrl url;
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
@ -243,7 +245,7 @@ bool HttpRequestParser::parseContent(const QByteArray& data)
while (i.hasNext()) while (i.hasNext())
{ {
QPair<QString, QString> pair = i.next(); QPair<QString, QString> pair = i.next();
request_.posts[pair.first.toLower()] = pair.second; m_request.posts[pair.first.toLower()] = pair.second;
} }
return true; return true;
@ -268,7 +270,7 @@ Content-Disposition: form-data; name=\"Upload\"
Submit Query Submit Query
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5-- --cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
**/ **/
QString content_type = request_.headers["content-type"]; QString content_type = m_request.headers["content-type"];
if (content_type.startsWith("multipart/form-data")) if (content_type.startsWith("multipart/form-data"))
{ {
const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\""); const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\"");
@ -309,7 +311,7 @@ Submit Query
return false; return false;
} }
bool HttpRequestParser::parseFormData(const QByteArray& data) bool RequestParser::parseFormData(const QByteArray& data)
{ {
// Parse form data header // Parse form data header
const int header_end = data.indexOf(EOH); const int header_end = data.indexOf(EOH);
@ -347,17 +349,17 @@ bool HttpRequestParser::parseFormData(const QByteArray& data)
ufile.type = disposition["content-type"]; ufile.type = disposition["content-type"];
ufile.data = data.mid(header_end + EOH.length()); ufile.data = data.mid(header_end + EOH.length());
request_.files[disposition["name"]] = ufile; m_request.files[disposition["name"]] = ufile;
} }
else else
{ {
request_.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length())); m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
} }
return true; return true;
} }
bool HttpRequestParser::parseHeaderValue(const QString& value, QStringMap& out) bool RequestParser::parseHeaderValue(const QString& value, QStringMap& out)
{ {
QStringList items = value.split(QLatin1Char(';')); QStringList items = value.split(QLatin1Char(';'));
out[""] = items[0]; out[""] = items[0];

25
src/core/httprequestparser.h → src/core/http/requestparser.h

@ -29,24 +29,27 @@
* Contact : chris@qbittorrent.org * Contact : chris@qbittorrent.org
*/ */
#ifndef HTTPREQUESTPARSER_H #ifndef HTTP_REQUESTPARSER_H
#define HTTPREQUESTPARSER_H #define HTTP_REQUESTPARSER_H
#include "httptypes.h" #include "types.h"
class HttpRequestParser namespace Http
{
class RequestParser
{ {
public: public:
enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest }; enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest };
// when result != NoError parsed request is undefined // when result != NoError parsed request is undefined
// Warning! Header names are converted to lower-case. // Warning! Header names are converted to lower-case.
static ErrorCode parse(const QByteArray& data, HttpRequest& request, uint maxContentLength = 10000000 /* ~10MB */); static ErrorCode parse(const QByteArray& data, Request& request, uint maxContentLength = 10000000 /* ~10MB */);
private: private:
HttpRequestParser(uint maxContentLength); RequestParser(uint maxContentLength);
ErrorCode parseHttpRequest(const QByteArray& data, HttpRequest& request); ErrorCode parseHttpRequest(const QByteArray& data, Request& request);
bool parseHttpHeader(const QByteArray& data); bool parseHttpHeader(const QByteArray& data);
bool parseStartingLine(const QString &line); bool parseStartingLine(const QString &line);
@ -57,8 +60,10 @@ private:
static bool parseHeaderLine(const QString& line, QPair<QString, QString>& out); static bool parseHeaderLine(const QString& line, QPair<QString, QString>& out);
static bool parseHeaderValue(const QString& value, QStringMap& out); static bool parseHeaderValue(const QString& value, QStringMap& out);
const uint maxContentLength_; const uint m_maxContentLength;
HttpRequest request_; Request m_request;
}; };
#endif }
#endif // HTTP_REQUESTPARSER_H

74
src/core/http/responsebuilder.cpp

@ -0,0 +1,74 @@
/*
* 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 "responsebuilder.h"
using namespace Http;
ResponseBuilder::ResponseBuilder(QObject *parent)
: QObject(parent)
{
}
void ResponseBuilder::status(uint code, const QString &text)
{
m_response.status = ResponseStatus(code, text);
}
void ResponseBuilder::header(const QString &name, const QString &value)
{
m_response.headers[name] = value;
}
void ResponseBuilder::print(const QString &text, const QString &type)
{
print_impl(text.toUtf8(), type);
}
void ResponseBuilder::print(const QByteArray &data, const QString &type)
{
print_impl(data, type);
}
void ResponseBuilder::clear()
{
m_response = Response();
}
Response ResponseBuilder::response() const
{
return m_response;
}
void ResponseBuilder::print_impl(const QByteArray &data, const QString &type)
{
if (!m_response.headers.contains(HEADER_CONTENT_TYPE))
m_response.headers[HEADER_CONTENT_TYPE] = type;
m_response.content += data;
}

60
src/core/http/responsebuilder.h

@ -0,0 +1,60 @@
/*
* 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 HTTP_RESPONSEBUILDER_H
#define HTTP_RESPONSEBUILDER_H
#include <QObject>
#include "types.h"
namespace Http
{
class ResponseBuilder : public QObject
{
public:
explicit ResponseBuilder(QObject *parent = 0);
protected:
void status(uint code = 200, const QString &text = QLatin1String("OK"));
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 clear();
Response response() const;
private:
void print_impl(const QByteArray &data, const QString &type);
Response m_response;
};
}
#endif // HTTP_RESPONSEBUILDER_H

6
src/core/httpresponsegenerator.cpp → src/core/http/responsegenerator.cpp

@ -30,11 +30,13 @@
*/ */
#include <zlib.h> #include <zlib.h>
#include "httpresponsegenerator.h" #include "responsegenerator.h"
bool gCompress(QByteArray data, QByteArray& dest_buffer); bool gCompress(QByteArray data, QByteArray& dest_buffer);
QByteArray HttpResponseGenerator::generate(HttpResponse response) using namespace Http;
QByteArray ResponseGenerator::generate(Response response)
{ {
if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") if (response.headers[HEADER_CONTENT_ENCODING] == "gzip")
{ {

17
src/core/httpresponsegenerator.h → src/core/http/responsegenerator.h

@ -30,15 +30,20 @@
*/ */
#ifndef HTTPRESPONSEGENERATOR_H #ifndef HTTP_RESPONSEGENERATOR_H
#define HTTPRESPONSEGENERATOR_H #define HTTP_RESPONSEGENERATOR_H
#include "httptypes.h" #include "types.h"
class HttpResponseGenerator namespace Http
{
class ResponseGenerator
{ {
public: public:
static QByteArray generate(HttpResponse response); static QByteArray generate(Response response);
}; };
#endif }
#endif // HTTP_RESPONSEGENERATOR_H

21
src/webui/httpserver.cpp → src/core/http/server.cpp

@ -33,30 +33,33 @@
#else #else
#include <QTcpSocket> #include <QTcpSocket>
#endif #endif
#include "httpconnection.h" #include "connection.h"
#include "httpserver.h" #include "server.h"
HttpServer::HttpServer(QObject* parent) using namespace Http;
Server::Server(IRequestHandler *requestHandler, QObject* parent)
: QTcpServer(parent) : QTcpServer(parent)
, m_requestHandler(requestHandler)
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
, m_https(false) , m_https(false)
#endif #endif
{ {
} }
HttpServer::~HttpServer() Server::~Server()
{ {
} }
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
void HttpServer::enableHttps(const QSslCertificate &certificate, const QSslKey &key) void Server::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
{ {
m_certificate = certificate; m_certificate = certificate;
m_key = key; m_key = key;
m_https = true; m_https = true;
} }
void HttpServer::disableHttps() void Server::disableHttps()
{ {
m_https = false; m_https = false;
m_certificate.clear(); m_certificate.clear();
@ -65,9 +68,9 @@ void HttpServer::disableHttps()
#endif #endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
void HttpServer::incomingConnection(qintptr socketDescriptor) void Server::incomingConnection(qintptr socketDescriptor)
#else #else
void HttpServer::incomingConnection(int socketDescriptor) void Server::incomingConnection(int socketDescriptor)
#endif #endif
{ {
QTcpSocket *serverSocket; QTcpSocket *serverSocket;
@ -88,7 +91,7 @@ void HttpServer::incomingConnection(int socketDescriptor)
static_cast<QSslSocket*>(serverSocket)->startServerEncryption(); static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
} }
#endif #endif
new HttpConnection(serverSocket, this); new Connection(serverSocket, m_requestHandler, this);
} }
else else
{ {

23
src/webui/httpserver.h → src/core/http/server.h

@ -30,8 +30,8 @@
*/ */
#ifndef HTTPSERVER_H #ifndef HTTP_SERVER_H
#define HTTPSERVER_H #define HTTP_SERVER_H
#include <QTcpServer> #include <QTcpServer>
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
@ -39,14 +39,20 @@
#include <QSslKey> #include <QSslKey>
#endif #endif
class HttpServer : public QTcpServer namespace Http
{
class IRequestHandler;
class Connection;
class Server : public QTcpServer
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(HttpServer) Q_DISABLE_COPY(Server)
public: public:
HttpServer(QObject* parent = 0); Server(IRequestHandler *requestHandler, QObject *parent = 0);
~HttpServer(); ~Server();
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
void enableHttps(const QSslCertificate &certificate, const QSslKey &key); void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
@ -61,6 +67,7 @@ private:
#endif #endif
private: private:
IRequestHandler *m_requestHandler;
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
bool m_https; bool m_https;
QSslCertificate m_certificate; QSslCertificate m_certificate;
@ -68,4 +75,6 @@ private:
#endif #endif
}; };
#endif }
#endif // HTTP_SERVER_H

25
src/core/httptypes.h → src/core/http/types.h

@ -26,8 +26,8 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifndef HTTPTYPES_H #ifndef HTTP_TYPES_H
#define HTTPTYPES_H #define HTTP_TYPES_H
#include <QString> #include <QString>
#include <QMap> #include <QMap>
@ -35,6 +35,9 @@
typedef QMap<QString, QString> QStringMap; typedef QMap<QString, QString> QStringMap;
namespace Http
{
const QString HEADER_SET_COOKIE = "Set-Cookie"; const QString HEADER_SET_COOKIE = "Set-Cookie";
const QString HEADER_CONTENT_TYPE = "Content-Type"; const QString HEADER_CONTENT_TYPE = "Content-Type";
const QString HEADER_CONTENT_ENCODING = "Content-Encoding"; const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
@ -48,7 +51,7 @@ const QString CONTENT_TYPE_JS = "text/javascript; charset=UTF-8";
const QString CONTENT_TYPE_PNG = "image/png"; const QString CONTENT_TYPE_PNG = "image/png";
const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8"; const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8";
struct HttpEnvironment struct Environment
{ {
QHostAddress clientAddress; QHostAddress clientAddress;
}; };
@ -60,7 +63,7 @@ struct UploadedFile
QByteArray data; // File data QByteArray data; // File data
}; };
struct HttpRequest struct Request
{ {
QString method; QString method;
QString path; QString path;
@ -70,21 +73,23 @@ struct HttpRequest
QMap<QString, UploadedFile> files; QMap<QString, UploadedFile> files;
}; };
struct HttpResponseStatus struct ResponseStatus
{ {
uint code; uint code;
QString text; QString text;
HttpResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {} ResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
}; };
struct HttpResponse struct Response
{ {
HttpResponseStatus status; ResponseStatus status;
QStringMap headers; QStringMap headers;
QByteArray content; QByteArray content;
HttpResponse(uint code = 200, const QString& text = "OK"): status(code, text) {} Response(uint code = 200, const QString& text = "OK"): status(code, text) {}
}; };
#endif // HTTPTYPES_H }
#endif // HTTP_TYPES_H

69
src/core/qtlibtorrent/qbtsession.cpp

@ -57,7 +57,6 @@
#include "geoipmanager.h" #include "geoipmanager.h"
#endif #endif
#include "torrentpersistentdata.h" #include "torrentpersistentdata.h"
#include "httpserver.h"
#include "bandwidthscheduler.h" #include "bandwidthscheduler.h"
#include <libtorrent/version.hpp> #include <libtorrent/version.hpp>
#include <libtorrent/extensions/ut_metadata.hpp> #include <libtorrent/extensions/ut_metadata.hpp>
@ -79,7 +78,6 @@
#include <libtorrent/magnet_uri.hpp> #include <libtorrent/magnet_uri.hpp>
#include <queue> #include <queue>
#include <string.h> #include <string.h>
#include "dnsupdater.h"
#if LIBTORRENT_VERSION_NUM < 10000 #if LIBTORRENT_VERSION_NUM < 10000
#include <libtorrent/upnp.hpp> #include <libtorrent/upnp.hpp>
@ -117,7 +115,6 @@ QBtSession::QBtSession()
#if LIBTORRENT_VERSION_NUM < 10000 #if LIBTORRENT_VERSION_NUM < 10000
, m_upnp(0), m_natpmp(0) , m_upnp(0), m_natpmp(0)
#endif #endif
, m_dynDNSUpdater(0)
, m_alertDispatcher(0) , m_alertDispatcher(0)
{ {
BigRatioTimer = new QTimer(this); BigRatioTimer = new QTimer(this);
@ -159,6 +156,7 @@ QBtSession::QBtSession()
connect(m_scanFolders, SIGNAL(torrentsAdded(QStringList&)), SLOT(addTorrentsFromScanFolder(QStringList&))); connect(m_scanFolders, SIGNAL(torrentsAdded(QStringList&)), SLOT(addTorrentsFromScanFolder(QStringList&)));
// Apply user settings to Bittorrent session // Apply user settings to Bittorrent session
configureSession(); configureSession();
connect(pref, SIGNAL(changed()), SLOT(configureSession()));
// Torrent speed monitor // Torrent speed monitor
m_speedMonitor = new TorrentSpeedMonitor(this); m_speedMonitor = new TorrentSpeedMonitor(this);
m_torrentStatistics = new TorrentStatistics(this, this); m_torrentStatistics = new TorrentStatistics(this, this);
@ -191,9 +189,6 @@ QBtSession::~QBtSession() {
delete downloader; delete downloader;
if (bd_scheduler) if (bd_scheduler)
delete bd_scheduler; delete bd_scheduler;
// HTTP Server
if (httpServer)
delete httpServer;
delete m_alertDispatcher; delete m_alertDispatcher;
delete m_torrentStatistics; delete m_torrentStatistics;
qDebug("Deleting the session"); qDebug("Deleting the session");
@ -583,9 +578,6 @@ void QBtSession::configureSession() {
}else{ }else{
disableIPFilter(); disableIPFilter();
} }
// Update Web UI
// Use a QTimer because the function can be called from qBtSession constructor
QTimer::singleShot(0, this, SLOT(initWebUi()));
// * Proxy settings // * Proxy settings
proxy_settings proxySettings; proxy_settings proxySettings;
if (pref->isProxyEnabled()) { if (pref->isProxyEnabled()) {
@ -655,64 +647,6 @@ void QBtSession::configureSession() {
qDebug("Session configured"); qDebug("Session configured");
} }
void QBtSession::initWebUi() {
Preferences* const pref = Preferences::instance();
if (pref->isWebUiEnabled()) {
const quint16 port = pref->getWebUiPort();
if (httpServer) {
if (httpServer->serverPort() != port) {
httpServer->close();
}
} else {
httpServer = new HttpServer(this);
}
#ifndef QT_NO_OPENSSL
if (pref->isWebUiHttpsEnabled()) {
QSslCertificate cert(pref->getWebUiHttpsCertificate());
QSslKey key;
const QByteArray raw_key = pref->getWebUiHttpsKey();
key = QSslKey(raw_key, QSsl::Rsa);
if (!cert.isNull() && !key.isNull())
httpServer->enableHttps(cert, key);
else
httpServer->disableHttps();
} else {
httpServer->disableHttps();
}
#endif
if (!httpServer->isListening()) {
Logger* const logger = Logger::instance();
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 (!m_dynDNSUpdater)
m_dynDNSUpdater = new DNSUpdater(this);
else
m_dynDNSUpdater->updateCredentials();
} else {
if (m_dynDNSUpdater) {
delete m_dynDNSUpdater;
m_dynDNSUpdater = 0;
}
}
} else {
if (httpServer)
delete httpServer;
if (m_dynDNSUpdater) {
delete m_dynDNSUpdater;
m_dynDNSUpdater = 0;
}
}
}
void QBtSession::useAlternativeSpeedsLimit(bool alternative) { void QBtSession::useAlternativeSpeedsLimit(bool alternative) {
qDebug() << Q_FUNC_INFO << alternative; qDebug() << Q_FUNC_INFO << alternative;
// Save new state to remember it on startup // Save new state to remember it on startup
@ -1495,6 +1429,7 @@ void QBtSession::enableUPnP(bool b) {
s->start_upnp(); s->start_upnp();
s->start_natpmp(); s->start_natpmp();
#endif #endif
// TODO: Remove dependency from WebUI
// Use UPnP/NAT-PMP for Web UI too // Use UPnP/NAT-PMP for Web UI too
if (pref->isWebUiEnabled() && pref->useUPnPForWebUIPort()) { if (pref->isWebUiEnabled() && pref->useUPnPForWebUIPort()) {
const qint16 port = pref->getWebUiPort(); const qint16 port = pref->getWebUiPort();

9
src/core/qtlibtorrent/qbtsession.h

@ -90,12 +90,10 @@ namespace libtorrent {
class DownloadThread; class DownloadThread;
class FilterParserThread; class FilterParserThread;
class HttpServer;
class BandwidthScheduler; class BandwidthScheduler;
class ScanFoldersModel; class ScanFoldersModel;
class TorrentSpeedMonitor; class TorrentSpeedMonitor;
class TorrentStatistics; class TorrentStatistics;
class DNSUpdater;
class QAlertDispatcher; class QAlertDispatcher;
enum TorrentExportFolder { enum TorrentExportFolder {
@ -209,7 +207,6 @@ public slots:
#endif #endif
void addMagnetInteractive(const QString& uri); void addMagnetInteractive(const QString& uri);
void downloadFromURLList(const QStringList& urls); void downloadFromURLList(const QStringList& urls);
void configureSession();
void banIP(QString ip); void banIP(QString ip);
void recursiveTorrentDownload(const QTorrentHandle &h); void recursiveTorrentDownload(const QTorrentHandle &h);
void unhideMagnet(const QString &hash); void unhideMagnet(const QString &hash);
@ -264,9 +261,9 @@ private slots:
void mergeTorrents(QTorrentHandle& h_ex, boost::intrusive_ptr<libtorrent::torrent_info> t); void mergeTorrents(QTorrentHandle& h_ex, boost::intrusive_ptr<libtorrent::torrent_info> t);
void mergeTorrents(QTorrentHandle& h_ex, const QString& magnet_uri); void mergeTorrents(QTorrentHandle& h_ex, const QString& magnet_uri);
void exportTorrentFile(const QTorrentHandle &h, TorrentExportFolder folder = RegularTorrentExportFolder); void exportTorrentFile(const QTorrentHandle &h, TorrentExportFolder folder = RegularTorrentExportFolder);
void initWebUi();
void handleIPFilterParsed(int ruleCount); void handleIPFilterParsed(int ruleCount);
void handleIPFilterError(); void handleIPFilterError();
void configureSession();
signals: signals:
void addedTorrent(const QTorrentHandle& h); void addedTorrent(const QTorrentHandle& h);
@ -327,8 +324,6 @@ private:
// IP filtering // IP filtering
QPointer<FilterParserThread> filterParser; QPointer<FilterParserThread> filterParser;
QString filterPath; QString filterPath;
// Web UI
QPointer<HttpServer> httpServer;
QList<QUrl> url_skippingDlg; QList<QUrl> url_skippingDlg;
// GeoIP // GeoIP
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
@ -344,8 +339,6 @@ private:
libtorrent::upnp *m_upnp; libtorrent::upnp *m_upnp;
libtorrent::natpmp *m_natpmp; libtorrent::natpmp *m_natpmp;
#endif #endif
// DynDNS
DNSUpdater *m_dynDNSUpdater;
QAlertDispatcher* m_alertDispatcher; QAlertDispatcher* m_alertDispatcher;
TorrentStatistics* m_torrentStatistics; TorrentStatistics* m_torrentStatistics;
}; };

184
src/core/tracker/qtracker.cpp → src/core/qtracker.cpp

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez * Copyright (C) 2006 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -28,163 +29,171 @@
* Contact : chris@qbittorrent.org * Contact : chris@qbittorrent.org
*/ */
#include <QTcpSocket> #include <vector>
#include <libtorrent/bencode.hpp> #include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include "qtracker.h"
#include "preferences.h" #include "preferences.h"
#include "httpresponsegenerator.h" #include "http/server.h"
#include "httprequestparser.h" #include "qtracker.h"
#include <vector> // QPeer
bool QPeer::operator!=(const QPeer &other) const
{
return qhash() != other.qhash();
}
using namespace libtorrent; bool QPeer::operator==(const QPeer &other) const
{
return qhash() == other.qhash();
}
QTracker::QTracker(QObject *parent) : QString QPeer::qhash() const
QTcpServer(parent)
{ {
Q_ASSERT(Preferences::instance()->isTrackerEnabled()); return ip + ":" + QString::number(port);
connect(this, SIGNAL(newConnection()), this, SLOT(handlePeerConnection()));
} }
QTracker::~QTracker() { libtorrent::entry QPeer::toEntry(bool no_peer_id) const
if (isListening()) { {
qDebug("Shutting down the embedded tracker..."); libtorrent::entry::dictionary_type peer_map;
close(); if (!no_peer_id)
} peer_map["id"] = libtorrent::entry(peer_id.toStdString());
// TODO: Store the torrent list peer_map["ip"] = libtorrent::entry(ip.toStdString());
peer_map["port"] = libtorrent::entry(port);
return libtorrent::entry(peer_map);
} }
void QTracker::handlePeerConnection() // QTracker
QTracker::QTracker(QObject *parent)
: Http::ResponseBuilder(parent)
, m_server(new Http::Server(this, this))
{ {
QTcpSocket *socket; }
while((socket = nextPendingConnection()))
{ QTracker::~QTracker()
qDebug("QTracker: New peer connection"); {
connect(socket, SIGNAL(readyRead()), SLOT(readRequest())); if (m_server->isListening())
} qDebug("Shutting down the embedded tracker...");
// TODO: Store the torrent list
} }
bool QTracker::start() bool QTracker::start()
{ {
const int listen_port = Preferences::instance()->getTrackerPort(); const int listen_port = Preferences::instance()->getTrackerPort();
//
if (isListening()) { if (m_server->isListening()) {
if (serverPort() == listen_port) { if (m_server->serverPort() == listen_port) {
// Already listening on the right port, just return // Already listening on the right port, just return
return true; return true;
} }
// Wrong port, closing the server // Wrong port, closing the server
close(); m_server->close();
} }
qDebug("Starting the embedded tracker..."); qDebug("Starting the embedded tracker...");
// Listen on the predefined port // Listen on the predefined port
return listen(QHostAddress::Any, listen_port); return m_server->listen(QHostAddress::Any, listen_port);
} }
void QTracker::readRequest() Http::Response QTracker::processRequest(const Http::Request &request, const Http::Environment &env)
{ {
QTcpSocket *socket = static_cast<QTcpSocket*>(sender()); clear(); // clear response
QByteArray input = socket->readAll();
//qDebug("QTracker: Raw request:\n%s", input.data());
HttpRequest request;
if (HttpRequestParser::parse(input, request) != HttpRequestParser::NoError) {
qDebug("QTracker: Invalid HTTP Request:\n %s", qPrintable(input));
respondInvalidRequest(socket, 100, "Invalid request type");
return;
}
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString())); //qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
// Request is correct, is it a GET request? // Is request a GET request?
if (request.method != "GET") { if (request.method != "GET") {
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method)); qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
respondInvalidRequest(socket, 100, "Invalid request type"); status(100, "Invalid request type");
return;
} }
if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) { else if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path)); qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
respondInvalidRequest(socket, 100, "Invalid request type"); status(100, "Invalid request type");
return;
} }
else {
// OK, this is a GET request // OK, this is a GET request
respondToAnnounceRequest(socket, request.gets); m_request = request;
} m_env = env;
respondToAnnounceRequest();
}
void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg) return response();
{
HttpResponse response(code, msg);
socket->write(HttpResponseGenerator::generate(response));
socket->disconnectFromHost();
} }
void QTracker::respondToAnnounceRequest(QTcpSocket *socket, void QTracker::respondToAnnounceRequest()
const QMap<QString, QString>& get_parameters)
{ {
const QStringMap &gets = m_request.gets;
TrackerAnnounceRequest annonce_req; TrackerAnnounceRequest annonce_req;
// IP // IP
annonce_req.peer.ip = socket->peerAddress().toString(); annonce_req.peer.ip = m_env.clientAddress.toString();
// 1. Get info_hash // 1. Get info_hash
if (!get_parameters.contains("info_hash")) { if (!gets.contains("info_hash")) {
qDebug("QTracker: Missing info_hash"); qDebug("QTracker: Missing info_hash");
respondInvalidRequest(socket, 101, "Missing info_hash"); status(101, "Missing info_hash");
return; return;
} }
annonce_req.info_hash = get_parameters.value("info_hash"); annonce_req.info_hash = gets.value("info_hash");
// info_hash cannot be longer than 20 bytes // info_hash cannot be longer than 20 bytes
/*if (annonce_req.info_hash.toLatin1().length() > 20) { /*if (annonce_req.info_hash.toLatin1().length() > 20) {
qDebug("QTracker: Info_hash is not 20 byte long: %s (%d)", qPrintable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length()); qDebug("QTracker: Info_hash is not 20 byte long: %s (%d)", qPrintable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
respondInvalidRequest(socket, 150, "Invalid infohash"); status(150, "Invalid infohash");
return; return;
}*/ }*/
// 2. Get peer ID // 2. Get peer ID
if (!get_parameters.contains("peer_id")) { if (!gets.contains("peer_id")) {
qDebug("QTracker: Missing peer_id"); qDebug("QTracker: Missing peer_id");
respondInvalidRequest(socket, 102, "Missing peer_id"); status(102, "Missing peer_id");
return; return;
} }
annonce_req.peer.peer_id = get_parameters.value("peer_id"); annonce_req.peer.peer_id = gets.value("peer_id");
// peer_id cannot be longer than 20 bytes // peer_id cannot be longer than 20 bytes
/*if (annonce_req.peer.peer_id.length() > 20) { /*if (annonce_req.peer.peer_id.length() > 20) {
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id)); qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
respondInvalidRequest(socket, 151, "Invalid peerid"); status(151, "Invalid peerid");
return; return;
}*/ }*/
// 3. Get port // 3. Get port
if (!get_parameters.contains("port")) { if (!gets.contains("port")) {
qDebug("QTracker: Missing port"); qDebug("QTracker: Missing port");
respondInvalidRequest(socket, 103, "Missing port"); status(103, "Missing port");
return; return;
} }
bool ok = false; bool ok = false;
annonce_req.peer.port = get_parameters.value("port").toInt(&ok); annonce_req.peer.port = gets.value("port").toInt(&ok);
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) { if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port); qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
respondInvalidRequest(socket, 103, "Missing port"); status(103, "Missing port");
return; return;
} }
// 4. Get event // 4. Get event
annonce_req.event = ""; annonce_req.event = "";
if (get_parameters.contains("event")) { if (gets.contains("event")) {
annonce_req.event = get_parameters.value("event"); annonce_req.event = gets.value("event");
qDebug("QTracker: event is %s", qPrintable(annonce_req.event)); qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
} }
// 5. Get numwant // 5. Get numwant
annonce_req.numwant = 50; annonce_req.numwant = 50;
if (get_parameters.contains("numwant")) { if (gets.contains("numwant")) {
int tmp = get_parameters.value("numwant").toInt(); int tmp = gets.value("numwant").toInt();
if (tmp > 0) { if (tmp > 0) {
qDebug("QTracker: numwant=%d", tmp); qDebug("QTracker: numwant = %d", tmp);
annonce_req.numwant = tmp; annonce_req.numwant = tmp;
} }
} }
// 6. no_peer_id (extension) // 6. no_peer_id (extension)
annonce_req.no_peer_id = false; annonce_req.no_peer_id = false;
if (get_parameters.contains("no_peer_id")) { if (gets.contains("no_peer_id"))
annonce_req.no_peer_id = true; annonce_req.no_peer_id = true;
}
// 7. TODO: support "compact" extension // 7. TODO: support "compact" extension
// Done parsing, now let's reply // Done parsing, now let's reply
if (m_torrents.contains(annonce_req.info_hash)) { if (m_torrents.contains(annonce_req.info_hash)) {
if (annonce_req.event == "stopped") { if (annonce_req.event == "stopped") {
@ -207,33 +216,32 @@ void QTracker::respondToAnnounceRequest(QTcpSocket *socket,
} }
peers[annonce_req.peer.qhash()] = annonce_req.peer; peers[annonce_req.peer.qhash()] = annonce_req.peer;
m_torrents[annonce_req.info_hash] = peers; m_torrents[annonce_req.info_hash] = peers;
// Reply // Reply
ReplyWithPeerList(socket, annonce_req); replyWithPeerList(annonce_req);
} }
void QTracker::ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req) void QTracker::replyWithPeerList(const TrackerAnnounceRequest &annonce_req)
{ {
// Prepare the entry for bencoding // Prepare the entry for bencoding
entry::dictionary_type reply_dict; libtorrent::entry::dictionary_type reply_dict;
reply_dict["interval"] = entry(ANNOUNCE_INTERVAL); reply_dict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values(); QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
entry::list_type peer_list; libtorrent::entry::list_type peer_list;
foreach (const QPeer & p, peers) { foreach (const QPeer & p, peers) {
//if (p != annonce_req.peer) //if (p != annonce_req.peer)
peer_list.push_back(p.toEntry(annonce_req.no_peer_id)); peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
} }
reply_dict["peers"] = entry(peer_list); reply_dict["peers"] = libtorrent::entry(peer_list);
entry reply_entry(reply_dict); libtorrent::entry reply_entry(reply_dict);
// bencode // bencode
std::vector<char> buf; std::vector<char> buf;
bencode(std::back_inserter(buf), reply_entry); libtorrent::bencode(std::back_inserter(buf), reply_entry);
QByteArray reply(&buf[0], static_cast<int>(buf.size())); QByteArray reply(&buf[0], static_cast<int>(buf.size()));
qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData()); qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
// HTTP reply // HTTP reply
HttpResponse response(200, "OK"); print(reply, Http::CONTENT_TYPE_TXT);
response.content = reply;
socket->write(HttpResponseGenerator::generate(response));
socket->disconnectFromHost();
} }

52
src/core/tracker/qtracker.h → src/core/qtracker.h

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -31,11 +32,33 @@
#ifndef QTRACKER_H #ifndef QTRACKER_H
#define QTRACKER_H #define QTRACKER_H
#include <QTcpServer> #include <libtorrent/entry.hpp>
#include <QHash> #include <QHash>
#include "http/types.h"
#include "http/responsebuilder.h"
#include "http/irequesthandler.h"
#include "trackerannouncerequest.h" struct QPeer
#include "qpeer.h" {
QString ip;
QString peer_id;
int port;
bool operator!=(const QPeer &other) const;
bool operator==(const QPeer &other) const;
QString qhash() const;
libtorrent::entry toEntry(bool no_peer_id) const;
};
struct TrackerAnnounceRequest
{
QString info_hash;
QString event;
int numwant;
QPeer peer;
// Extensions
bool no_peer_id;
};
// static limits // static limits
const int MAX_TORRENTS = 100; const int MAX_TORRENTS = 100;
@ -45,9 +68,11 @@ const int ANNOUNCE_INTERVAL = 1800; // 30min
typedef QHash<QString, QPeer> PeerList; typedef QHash<QString, QPeer> PeerList;
typedef QHash<QString, PeerList> TorrentList; typedef QHash<QString, PeerList> TorrentList;
/* Basic Bittorrent tracker implementation in Qt4 */ namespace Http { class Server; }
/* Basic Bittorrent tracker implementation in Qt */
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */ /* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
class QTracker : public QTcpServer class QTracker : public Http::ResponseBuilder, public Http::IRequestHandler
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(QTracker) Q_DISABLE_COPY(QTracker)
@ -55,18 +80,19 @@ class QTracker : public QTcpServer
public: public:
explicit QTracker(QObject *parent = 0); explicit QTracker(QObject *parent = 0);
~QTracker(); ~QTracker();
bool start();
protected slots: bool start();
void readRequest(); Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
void handlePeerConnection();
void respondInvalidRequest(QTcpSocket *socket, int code, QString msg);
void respondToAnnounceRequest(QTcpSocket *socket, const QMap<QString, QString>& get_parameters);
void ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req);
private: private:
void respondToAnnounceRequest();
void replyWithPeerList(const TrackerAnnounceRequest &annonce_req);
Http::Server *m_server;
TorrentList m_torrents; TorrentList m_torrents;
Http::Request m_request;
Http::Environment m_env;
}; };
#endif // QTRACKER_H #endif // QTRACKER_H

35
src/core/tracker/qpeer.h

@ -1,35 +0,0 @@
#ifndef QPEER_H
#define QPEER_H
#include <libtorrent/entry.hpp>
#include <QString>
struct QPeer {
bool operator!=(const QPeer &other) const {
return qhash() != other.qhash();
}
bool operator==(const QPeer &other) const {
return qhash() == other.qhash();
}
QString qhash() const {
return ip+":"+QString::number(port);
}
libtorrent::entry toEntry(bool no_peer_id) const {
libtorrent::entry::dictionary_type peer_map;
if (!no_peer_id)
peer_map["id"] = libtorrent::entry(peer_id.toStdString());
peer_map["ip"] = libtorrent::entry(ip.toStdString());
peer_map["port"] = libtorrent::entry(port);
return libtorrent::entry(peer_map);
}
QString ip;
QString peer_id;
int port;
};
#endif // QPEER_H

9
src/core/tracker/tracker.pri

@ -1,9 +0,0 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/qtracker.h \
$$PWD/trackerannouncerequest.h \
$$PWD/qpeer.h
SOURCES += \
$$PWD/qtracker.cpp

15
src/core/tracker/trackerannouncerequest.h

@ -1,15 +0,0 @@
#ifndef TRACKERANNOUNCEREQUEST_H
#define TRACKERANNOUNCEREQUEST_H
#include <qpeer.h>
struct TrackerAnnounceRequest {
QString info_hash;
QString event;
int numwant;
QPeer peer;
// Extensions
bool no_peer_id;
};
#endif // TRACKERANNOUNCEREQUEST_H

3
src/gui/mainwindow.cpp

@ -1185,9 +1185,6 @@ void MainWindow::loadPreferences(bool configure_session)
IconProvider::instance()->useSystemIconTheme(pref->useSystemIconTheme()); IconProvider::instance()->useSystemIconTheme(pref->useSystemIconTheme());
#endif #endif
if (configure_session)
QBtSession::instance()->configureSession();
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
if (pref->isUpdateCheckEnabled()) if (pref->isUpdateCheckEnabled())
checkProgramUpdate(); checkProgramUpdate();

6
src/gui/options_imp.cpp

@ -70,7 +70,11 @@ options_imp::options_imp(QWidget *parent):
tabSelection->item(TAB_CONNECTION)->setIcon(IconProvider::instance()->getIcon("network-wired")); tabSelection->item(TAB_CONNECTION)->setIcon(IconProvider::instance()->getIcon("network-wired"));
tabSelection->item(TAB_DOWNLOADS)->setIcon(IconProvider::instance()->getIcon("download")); tabSelection->item(TAB_DOWNLOADS)->setIcon(IconProvider::instance()->getIcon("download"));
tabSelection->item(TAB_SPEED)->setIcon(IconProvider::instance()->getIcon("chronometer")); tabSelection->item(TAB_SPEED)->setIcon(IconProvider::instance()->getIcon("chronometer"));
#ifndef DISABLE_WEBUI
tabSelection->item(TAB_WEBUI)->setIcon(IconProvider::instance()->getIcon("network-server")); tabSelection->item(TAB_WEBUI)->setIcon(IconProvider::instance()->getIcon("network-server"));
#else
tabSelection->item(TAB_WEBUI)->setHidden(true);
#endif
tabSelection->item(TAB_ADVANCED)->setIcon(IconProvider::instance()->getIcon("preferences-other")); tabSelection->item(TAB_ADVANCED)->setIcon(IconProvider::instance()->getIcon("preferences-other"));
IpFilterRefreshBtn->setIcon(IconProvider::instance()->getIcon("view-refresh")); IpFilterRefreshBtn->setIcon(IconProvider::instance()->getIcon("view-refresh"));
@ -229,6 +233,7 @@ options_imp::options_imp(QWidget *parent):
connect(spinMaxActiveUploads, SIGNAL(valueChanged(QString)), this, SLOT(enableApplyButton())); connect(spinMaxActiveUploads, SIGNAL(valueChanged(QString)), this, SLOT(enableApplyButton()));
connect(spinMaxActiveTorrents, SIGNAL(valueChanged(QString)), this, SLOT(enableApplyButton())); connect(spinMaxActiveTorrents, SIGNAL(valueChanged(QString)), this, SLOT(enableApplyButton()));
connect(checkIgnoreSlowTorrentsForQueueing, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkIgnoreSlowTorrentsForQueueing, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
#ifndef DISABLE_WEBUI
// Web UI tab // Web UI tab
connect(checkWebUi, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkWebUi, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(spinWebUiPort, SIGNAL(valueChanged(int)), this, SLOT(enableApplyButton())); connect(spinWebUiPort, SIGNAL(valueChanged(int)), this, SLOT(enableApplyButton()));
@ -244,6 +249,7 @@ options_imp::options_imp(QWidget *parent):
connect(domainNameTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton())); connect(domainNameTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton()));
connect(DNSUsernameTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton())); connect(DNSUsernameTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton()));
connect(DNSPasswordTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton())); connect(DNSPasswordTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton()));
#endif
// Disable apply Button // Disable apply Button
applyButton->setEnabled(false); applyButton->setEnabled(false);
// Tab selection mecanism // Tab selection mecanism

167
src/webui/abstractrequesthandler.cpp

@ -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();
}

413
src/webui/abstractwebapplication.cpp

@ -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();

89
src/webui/abstractrequesthandler.h → src/webui/abstractwebapplication.h

@ -26,64 +26,83 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifndef ABSTRACTREQUESTHANDLER_H #ifndef ABSTRACTWEBAPPLICATION_H
#define ABSTRACTREQUESTHANDLER_H #define ABSTRACTWEBAPPLICATION_H
#include <QString> #include <QObject>
#include "httptypes.h" #include <QMap>
#include <QHash>
#include "http/types.h"
#include "http/responsebuilder.h"
#include "http/irequesthandler.h"
class WebApplication;
struct WebSession; 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: public:
AbstractRequestHandler( explicit AbstractWebApplication(QObject *parent = 0);
const HttpRequest& request, const HttpEnvironment& env, virtual ~AbstractWebApplication();
WebApplication* app);
HttpResponse run(); Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
protected: protected:
virtual void processRequest() = 0; 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; bool isBanned() const;
int failedAttempts() const; int failedAttempts() const;
void resetFailedAttempts(); void resetFailedAttempts();
void increaseFailedAttempts(); void increaseFailedAttempts();
void printFile(const QString &path);
// Session management
bool sessionActive() const { return session_ != 0; }
bool sessionStart();
bool sessionEnd();
bool isAuthNeeded(); bool isAuthNeeded();
bool readFile(const QString &path, QByteArray &data, QString &type);
// save data to temporary file on disk and return its name (or empty string if fails) // save data to temporary file on disk and return its name (or empty string if fails)
static QString saveTmpFile(const QByteArray& data); static QString saveTmpFile(const QByteArray &data);
WebSessionData *session();
Http::Request request() const { return request_; }
Http::Environment env() const { return env_; }
inline WebSession* session() { return session_; } private slots:
inline HttpRequest request() const { return request_; } void UnbanTimerEvent();
inline HttpEnvironment env() const { return env_; } void removeInactiveSessions();
private: private:
WebApplication* app_; // Persistent data
WebSession* session_; QMap<QString, WebSession *> sessions_;
const HttpRequest request_; QHash<QHostAddress, int> clientFailedAttempts_;
const HttpEnvironment env_; QMap<QString, QByteArray> translatedFiles_;
HttpResponse response_;
// Current data
WebSession *session_;
Http::Request request_;
Http::Environment env_;
QString generateSid();
bool sessionInitialize();
static void translateDocument(QString &data);
void print_impl(const QByteArray& data, const QString& type); static const QStringMap CONTENT_TYPE_BY_EXT;
static QStringMap initializeContentTypeByExtMap();
}; };
#endif // ABSTRACTREQUESTHANDLER_H #endif // ABSTRACTWEBAPPLICATION_H

4
src/webui/prefjson.cpp

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

716
src/webui/requesthandler.cpp

@ -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();

108
src/webui/requesthandler.h

@ -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

782
src/webui/webapplication.cpp

@ -26,285 +26,691 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifdef DISABLE_GUI #include <QDebug>
#include <QCoreApplication> #include <QCoreApplication>
#else
#include <QApplication>
#endif
#include <QDateTime>
#include <QTimer> #include <QTimer>
#include <QFile> #include <QCryptographicHash>
#include <QDebug> #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 "preferences.h"
#include "requesthandler.h" #include "btjson.h"
#include "prefjson.h"
#include "qbtsession.h"
#include "websessiondata.h"
#include "webapplication.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: QMap<QString,QMap<QString, WebApplication::Action> > actions;
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
: QTimer(parent), m_peerIp(peer_ip) ADD_ACTION(public, webui);
{ ADD_ACTION(public, index);
setSingleShot(true); ADD_ACTION(public, login);
setInterval(BAN_TIME); 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; \
} \
} }
inline const QHostAddress& peerIp() const { return m_peerIp; } void WebApplication::action_public_index()
{
QString path;
private: if (!args_.isEmpty()) {
QHostAddress m_peerIp; if (args_.back() == "favicon.ico")
}; path = ":/icons/skin/qbittorrent16.png";
else
path = WWW_FOLDER + args_.join("/");
}
// WebApplication printFile(path);
}
WebApplication::WebApplication(QObject *parent) void WebApplication::action_public_webui()
: QObject(parent)
{ {
if (!sessionActive())
printFile(PRIVATE_FOLDER + "login.html");
else
printFile(PRIVATE_FOLDER + "index.html");
} }
WebApplication::~WebApplication() void WebApplication::action_public_login()
{ {
// cleanup sessions data const Preferences* const pref = Preferences::instance();
foreach (WebSession* session, sessions_.values()) QCryptographicHash md5(QCryptographicHash::Md5);
delete session;
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);
}
} }
WebApplication *WebApplication::instance() void WebApplication::action_public_logout()
{ {
static WebApplication inst; CHECK_URI(0);
return &inst; sessionEnd();
} }
void WebApplication::UnbanTimerEvent() void WebApplication::action_public_theme()
{ {
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender()); if (args_.size() != 1) {
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString())); status(404, "Not Found");
clientFailedAttempts_.remove(ubantimer->peerIp()); return;
ubantimer->deleteLater(); }
#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);
} }
bool WebApplication::sessionInitialize(AbstractRequestHandler* _this) void WebApplication::action_public_images()
{ {
if (_this->session_ == 0) const QString path = ":/icons/" + args_.join("/");
{ printFile(path);
QString cookie = _this->request_.headers.value("cookie"); header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie; }
QString sessionId; // GET params:
const QString SID_START = C_SID + "="; // - filter (string): all, downloading, completed, paused, active, inactive
int pos = cookie.indexOf(SID_START); // - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label")
if (pos >= 0) // - sort (string): name of column for sorting by its value
{ // - reverse (bool): enable reverse sorting
pos += SID_START.length(); // - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
int end = cookie.indexOf(QRegExp("[,;]"), pos); // - offset (int): set offset (if less than 0 - offset from end)
sessionId = cookie.mid(pos, end >= 0 ? end - pos : 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);
}
// TODO: Additional session check void WebApplication::action_query_transferInfo()
{
CHECK_URI(0);
print(btjson::getTransferInfo(), Http::CONTENT_TYPE_JS);
}
if (!sessionId.isNull()) void WebApplication::action_query_propertiesGeneral()
{ {
if (sessions_.contains(sessionId)) CHECK_URI(1);
{ print(btjson::getPropertiesForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
_this->session_ = sessions_[sessionId]; }
return true;
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 else if (url.startsWith("magnet:", Qt::CaseInsensitive)) {
{ QBtSession::instance()->addMagnetSkipAddDlg(url);
qDebug() << Q_FUNC_INFO << "session does not exist!"; }
else {
qDebug("Downloading url: %s", qPrintable(url));
QBtSession::instance()->downloadUrlAndSkipDialog(url);
} }
} }
} }
return false;
} }
bool WebApplication::readFile(const QString& path, QByteArray &data, QString &type) void WebApplication::action_command_upload()
{ {
QString ext = ""; qDebug() << Q_FUNC_INFO;
int index = path.lastIndexOf('.') + 1; CHECK_URI(0);
if (index > 0)
ext = path.mid(index); foreach(const Http::UploadedFile& torrent, request().files) {
QString filePath = saveTmpFile(torrent.data);
// find translated file in cache if (!filePath.isEmpty()) {
if (translatedFiles_.contains(path)) QTorrentHandle h = QBtSession::instance()->addTorrent(filePath);
{ if (!h.is_valid()) {
data = translatedFiles_[path]; status(415, "Internal Server Error");
print(QObject::tr("Error: '%1' is not a valid torrent file.\n").arg(torrent.filename), Http::CONTENT_TYPE_TXT);
} }
else // Clean up
{ fsutils::forceRemove(filePath);
QFile file(path); }
if (!file.open(QIODevice::ReadOnly)) else {
{ qWarning() << "I/O Error: Could not create temporary file";
qDebug("File %s was not found!", qPrintable(path)); status(500, "Internal Server Error");
return false; print(QObject::tr("I/O Error: Could not create temporary file."), Http::CONTENT_TYPE_TXT);
} }
}
}
data = file.readAll(); void WebApplication::action_command_addTrackers()
file.close(); {
CHECK_URI(0);
CHECK_PARAMETERS("hash" << "urls");
QString hash = request().posts["hash"];
// Translate the file if (!hash.isEmpty()) {
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js"))) QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
{
QString dataStr = QString::fromUtf8(data.constData());
translateDocument(dataStr);
if (path.endsWith("about.html")) if (h.is_valid() && h.has_metadata()) {
{ QString urls = request().posts["urls"];
dataStr.replace("${VERSION}", VERSION); QStringList list = urls.split('\n');
}
data = dataStr.toUtf8(); foreach (const QString& url, list) {
translatedFiles_[path] = data; // cashing translated file 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();
}
type = CONTENT_TYPE_BY_EXT[ext]; void WebApplication::action_command_resume()
return true; {
CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->resumeTorrent(request().posts["hash"]);
} }
QString WebApplication::generateSid() void WebApplication::action_command_pause()
{ {
QString sid; CHECK_URI(0);
CHECK_PARAMETERS("hash");
QBtSession::instance()->pauseTorrent(request().posts["hash"]);
}
qsrand(QDateTime::currentDateTime().toTime_t()); void WebApplication::action_command_setPreferences()
do {
{ CHECK_URI(0);
const size_t size = 6; CHECK_PARAMETERS("json");
quint32 tmp[size]; prefjson::setPreferences(request().posts["json"]);
}
for (size_t i = 0; i < size; ++i) void WebApplication::action_command_setFilePrio()
tmp[i] = qrand(); {
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);
}
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64(); void WebApplication::action_command_getGlobalUpLimit()
} {
while (sessions_.contains(sid)); CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit));
}
return sid; void WebApplication::action_command_getGlobalDlLimit()
{
CHECK_URI(0);
print(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit));
} }
void WebApplication::translateDocument(QString& data) void WebApplication::action_command_setGlobalUpLimit()
{ {
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR"); CHECK_URI(0);
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?"); CHECK_PARAMETERS("limit");
const std::string contexts[] = { qlonglong limit = request().posts["limit"].toLongLong();
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget", if (limit == 0) limit = -1;
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel", QBtSession::instance()->setUploadRateLimit(limit);
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc", if (Preferences::instance()->isAltBandwidthEnabled())
"StatusBar" Preferences::instance()->setAltGlobalUploadLimit(limit / 1024.);
}; else
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]); Preferences::instance()->setGlobalUploadLimit(limit / 1024.);
int i = 0; }
bool found = true;
const QString locale = Preferences::instance()->getLocale(); void WebApplication::action_command_setGlobalDlLimit()
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB"); {
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.);
}
while(i < data.size() && found) void WebApplication::action_command_getTorrentUpLimit()
{ {
i = regex.indexIn(data, i); CHECK_URI(0);
if (i >= 0) CHECK_PARAMETERS("hash");
{ QString hash = request().posts["hash"];
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data()); QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
QByteArray word = regex.cap(1).toUtf8();
QString translation = word; if (h.is_valid())
if (isTranslationNeeded) print(QByteArray::number(h.upload_limit()));
{ }
size_t context_index = 0;
while ((context_index < context_count) && (translation == word)) void WebApplication::action_command_getTorrentDlLimit()
{ {
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) CHECK_URI(0);
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1); CHECK_PARAMETERS("hash");
#else QString hash = request().posts["hash"];
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1); QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
#endif
++context_index; 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&) {}
} }
// Remove keyboard shortcuts }
translation.replace(mnemonic, "");
data.replace(i, regex.matchedLength(), translation); void WebApplication::action_command_toggleFirstLastPiecePrio()
i += translation.length(); {
} CHECK_URI(0);
else CHECK_PARAMETERS("hashes");
{ QStringList hashes = request().posts["hashes"].split("|");
found = false; // no more translatable strings foreach (const QString &hash, hashes) {
try {
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
h.toggleFirstLastPiecePrio();
} }
catch(invalid_handle&) {}
} }
} }
bool WebApplication::isBanned(const AbstractRequestHandler *_this) const void WebApplication::action_command_delete()
{ {
return clientFailedAttempts_.value(_this->env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS; CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, false);
} }
int WebApplication::failedAttempts(const AbstractRequestHandler* _this) const void WebApplication::action_command_deletePerm()
{ {
return clientFailedAttempts_.value(_this->env_.clientAddress, 0); CHECK_URI(0);
CHECK_PARAMETERS("hashes");
QStringList hashes = request().posts["hashes"].split("|");
foreach (const QString &hash, hashes)
QBtSession::instance()->deleteTorrent(hash, true);
} }
void WebApplication::resetFailedAttempts(AbstractRequestHandler* _this) void WebApplication::action_command_increasePrio()
{ {
clientFailedAttempts_.remove(_this->env_.clientAddress); 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::increaseFailedAttempts(AbstractRequestHandler* _this) void WebApplication::action_command_decreasePrio()
{ {
const int nb_fail = clientFailedAttempts_.value(_this->env_.clientAddress, 0) + 1; 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();
}
}
clientFailedAttempts_[_this->env_.clientAddress] = nb_fail; void WebApplication::action_command_topPrio()
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS) {
{ CHECK_URI(0);
// Max number of failed attempts reached CHECK_PARAMETERS("hashes");
// Start ban period foreach (const QString &hash, request().posts["hashes"].split("|")) {
UnbanTimer* ubantimer = new UnbanTimer(_this->env_.clientAddress, this); QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent())); if (h.is_valid()) h.queue_position_top();
ubantimer->start();
} }
} }
bool WebApplication::sessionStart(AbstractRequestHandler *_this) void WebApplication::action_command_bottomPrio()
{ {
if (_this->session_ == 0) CHECK_URI(0);
{ CHECK_PARAMETERS("hashes");
_this->session_ = new WebSession(generateSid()); foreach (const QString &hash, request().posts["hashes"].split("|")) {
sessions_[_this->session_->id] = _this->session_; QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
return true; 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"]);
}
return false; bool WebApplication::isPublicScope()
{
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO);
} }
bool WebApplication::sessionEnd(AbstractRequestHandler *_this) void WebApplication::processRequest()
{ {
if ((_this->session_ != 0) && (sessions_.contains(_this->session_->id))) scope_ = DEFAULT_SCOPE;
{ action_ = DEFAULT_ACTION;
sessions_.remove(_this->session_->id);
delete _this->session_; parsePath();
_this->session_ = 0;
return true; 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;
} }
return false; 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;
}
} }
QStringMap WebApplication::initializeContentTypeByExtMap() void WebApplication::parsePath()
{ {
QStringMap map; 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();
}
}
map["htm"] = CONTENT_TYPE_HTML; args_ = pathItems;
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; WebApplication::WebApplication(QObject *parent)
: AbstractWebApplication(parent)
{
} }
const QStringMap WebApplication::CONTENT_TYPE_BY_EXT = WebApplication::initializeContentTypeByExtMap(); QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::actions_ = WebApplication::initializeActions();

112
src/webui/webapplication.h

@ -29,61 +29,77 @@
#ifndef WEBAPPLICATION_H #ifndef WEBAPPLICATION_H
#define WEBAPPLICATION_H #define WEBAPPLICATION_H
#include <QObject> #include <QStringList>
#include <QMap> #include "abstractwebapplication.h"
#include <QHash>
#include "httptypes.h"
#include <QVariant>
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) Q_DISABLE_COPY(WebApplication)
public: public:
WebApplication(QObject* parent = 0); explicit 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();
private: private:
QMap<QString, WebSession*> sessions_; // Actions
QHash<QHostAddress, int> clientFailedAttempts_; void action_public_webui();
QMap<QString, QByteArray> translatedFiles_; void action_public_index();
void action_public_login();
QString generateSid(); void action_public_logout();
static void translateDocument(QString& data); void action_public_theme();
void action_public_images();
static const QStringMap CONTENT_TYPE_BY_EXT; void action_query_torrents();
static QStringMap initializeContentTypeByExtMap(); 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 (WebApplication::*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 // WEBAPPLICATION_H #endif // WEBAPPLICATION_H

41
src/webui/websessiondata.h

@ -0,0 +1,41 @@
/*
* 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 WEBSESSIONDATA
#define WEBSESSIONDATA
#include <QVariant>
struct WebSessionData
{
QVariantMap syncMainDataLastResponse;
QVariantMap syncMainDataLastAcceptedResponse;
};
#endif // WEBSESSIONDATA

102
src/webui/webui.cpp

@ -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_;
}
}

55
src/webui/webui.h

@ -0,0 +1,55 @@
/*
* 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 WEBUI_H
#define WEBUI_H
#include <QObject>
#include <QPointer>
namespace Http { class Server; }
class DNSUpdater;
class AbstractWebApplication;
class WebUI : public QObject
{
Q_OBJECT
public:
explicit WebUI(QObject *parent = 0);
private slots:
void init();
private:
QPointer<Http::Server> httpServer_;
QPointer<DNSUpdater> dynDNSUpdater_;
QPointer<AbstractWebApplication> webapp_;
};
#endif // WEBUI_H

25
src/webui/webui.pri

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

Loading…
Cancel
Save