mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-02-02 09:55:55 +00:00
Fix prefjson::setPreferences() doesn't actually save.
This commit is contained in:
parent
8e1698d563
commit
2707f5205f
@ -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 ¶ms)
|
bool Application::sendParams(const QStringList ¶ms)
|
||||||
{
|
{
|
||||||
return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR)));
|
return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR)));
|
||||||
@ -159,7 +150,12 @@ int Application::exec(const QStringList ¶ms)
|
|||||||
// 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 ¶ms)
|
|||||||
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();
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
@ -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
Normal file
47
src/core/http/irequesthandler.h
Normal file
@ -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
|
||||||
|
|
@ -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];
|
@ -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
Normal file
74
src/core/http/responsebuilder.cpp
Normal file
@ -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
Normal file
60
src/core/http/responsebuilder.h
Normal file
@ -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
|
@ -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")
|
||||||
{
|
{
|
@ -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
|
@ -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
|
||||||
{
|
{
|
@ -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
|
@ -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,43 +51,45 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UploadedFile
|
struct UploadedFile
|
||||||
{
|
{
|
||||||
QString filename; // original filename
|
QString filename; // original filename
|
||||||
QString type; // MIME type
|
QString type; // MIME type
|
||||||
QByteArray data; // File data
|
QByteArray data; // File data
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HttpRequest
|
struct Request
|
||||||
{
|
{
|
||||||
QString method;
|
QString method;
|
||||||
QString path;
|
QString path;
|
||||||
QStringMap headers;
|
QStringMap headers;
|
||||||
QStringMap gets;
|
QStringMap gets;
|
||||||
QStringMap posts;
|
QStringMap posts;
|
||||||
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
|
@ -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();
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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 <libtorrent/bencode.hpp>
|
|
||||||
#include <libtorrent/entry.hpp>
|
|
||||||
|
|
||||||
#include "qtracker.h"
|
|
||||||
#include "preferences.h"
|
|
||||||
#include "httpresponsegenerator.h"
|
|
||||||
#include "httprequestparser.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <libtorrent/bencode.hpp>
|
||||||
|
|
||||||
using namespace libtorrent;
|
#include "preferences.h"
|
||||||
|
#include "http/server.h"
|
||||||
|
#include "qtracker.h"
|
||||||
|
|
||||||
QTracker::QTracker(QObject *parent) :
|
// QPeer
|
||||||
QTcpServer(parent)
|
bool QPeer::operator!=(const QPeer &other) const
|
||||||
{
|
{
|
||||||
Q_ASSERT(Preferences::instance()->isTrackerEnabled());
|
return qhash() != other.qhash();
|
||||||
connect(this, SIGNAL(newConnection()), this, SLOT(handlePeerConnection()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTracker::~QTracker() {
|
bool QPeer::operator==(const QPeer &other) const
|
||||||
if (isListening()) {
|
{
|
||||||
|
return qhash() == other.qhash();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QPeer::qhash() const
|
||||||
|
{
|
||||||
|
return ip + ":" + QString::number(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
libtorrent::entry QPeer::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// QTracker
|
||||||
|
|
||||||
|
QTracker::QTracker(QObject *parent)
|
||||||
|
: Http::ResponseBuilder(parent)
|
||||||
|
, m_server(new Http::Server(this, this))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QTracker::~QTracker()
|
||||||
|
{
|
||||||
|
if (m_server->isListening())
|
||||||
qDebug("Shutting down the embedded tracker...");
|
qDebug("Shutting down the embedded tracker...");
|
||||||
close();
|
|
||||||
}
|
|
||||||
// TODO: Store the torrent list
|
// TODO: Store the torrent list
|
||||||
}
|
}
|
||||||
|
|
||||||
void QTracker::handlePeerConnection()
|
|
||||||
{
|
|
||||||
QTcpSocket *socket;
|
|
||||||
while((socket = nextPendingConnection()))
|
|
||||||
{
|
|
||||||
qDebug("QTracker: New peer connection");
|
|
||||||
connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()));
|
|
||||||
// Request is correct, is it a GET request?
|
|
||||||
if (request.method != "GET") {
|
|
||||||
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
|
|
||||||
respondInvalidRequest(socket, 100, "Invalid request type");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
|
|
||||||
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
|
|
||||||
respondInvalidRequest(socket, 100, "Invalid request type");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK, this is a GET request
|
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
|
||||||
respondToAnnounceRequest(socket, request.gets);
|
// Is request a GET request?
|
||||||
|
if (request.method != "GET") {
|
||||||
|
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
|
||||||
|
status(100, "Invalid request type");
|
||||||
|
}
|
||||||
|
else if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
|
||||||
|
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
|
||||||
|
status(100, "Invalid request type");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// OK, this is a GET request
|
||||||
|
m_request = request;
|
||||||
|
m_env = env;
|
||||||
|
respondToAnnounceRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg)
|
void QTracker::respondToAnnounceRequest()
|
||||||
{
|
|
||||||
HttpResponse response(code, msg);
|
|
||||||
socket->write(HttpResponseGenerator::generate(response));
|
|
||||||
socket->disconnectFromHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QTracker::respondToAnnounceRequest(QTcpSocket *socket,
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
@ -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
|
|
@ -1,9 +0,0 @@
|
|||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/qtracker.h \
|
|
||||||
$$PWD/trackerannouncerequest.h \
|
|
||||||
$$PWD/qpeer.h
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
$$PWD/qtracker.cpp
|
|
@ -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
|
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
Normal file
413
src/webui/abstractwebapplication.cpp
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QNetworkCookie>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "websessiondata.h"
|
||||||
|
#include "abstractwebapplication.h"
|
||||||
|
|
||||||
|
// UnbanTimer
|
||||||
|
|
||||||
|
class UnbanTimer: public QTimer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
|
||||||
|
: QTimer(parent), m_peerIp(peer_ip)
|
||||||
|
{
|
||||||
|
setSingleShot(true);
|
||||||
|
setInterval(BAN_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const QHostAddress& peerIp() const { return m_peerIp; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHostAddress m_peerIp;
|
||||||
|
};
|
||||||
|
|
||||||
|
// WebSession
|
||||||
|
|
||||||
|
struct WebSession
|
||||||
|
{
|
||||||
|
const QString id;
|
||||||
|
uint timestamp;
|
||||||
|
WebSessionData data;
|
||||||
|
|
||||||
|
WebSession(const QString& id)
|
||||||
|
: id(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTimestamp()
|
||||||
|
{
|
||||||
|
timestamp = QDateTime::currentDateTime().toTime_t();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// AbstractWebApplication
|
||||||
|
|
||||||
|
AbstractWebApplication::AbstractWebApplication(QObject *parent)
|
||||||
|
: Http::ResponseBuilder(parent)
|
||||||
|
, session_(0)
|
||||||
|
{
|
||||||
|
QTimer *timer = new QTimer(this);
|
||||||
|
timer->setInterval(60000); // 1 min.
|
||||||
|
connect(timer, SIGNAL(timeout()), SLOT(removeInactiveSessions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractWebApplication::~AbstractWebApplication()
|
||||||
|
{
|
||||||
|
// cleanup sessions data
|
||||||
|
qDeleteAll(sessions_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Http::Response AbstractWebApplication::processRequest(const Http::Request &request, const Http::Environment &env)
|
||||||
|
{
|
||||||
|
request_ = request;
|
||||||
|
env_ = env;
|
||||||
|
|
||||||
|
clear(); // clear response
|
||||||
|
|
||||||
|
sessionInitialize();
|
||||||
|
if (!sessionActive() && !isAuthNeeded())
|
||||||
|
sessionStart();
|
||||||
|
|
||||||
|
if (isBanned()) {
|
||||||
|
status(403, "Forbidden");
|
||||||
|
print(QObject::tr("Your IP address has been banned after too many failed authentication attempts."), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::UnbanTimerEvent()
|
||||||
|
{
|
||||||
|
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
|
||||||
|
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
|
||||||
|
clientFailedAttempts_.remove(ubantimer->peerIp());
|
||||||
|
ubantimer->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::removeInactiveSessions()
|
||||||
|
{
|
||||||
|
const uint now = QDateTime::currentDateTime().toTime_t();
|
||||||
|
|
||||||
|
foreach (const QString &id, sessions_.keys()) {
|
||||||
|
if ((now - sessions_[id]->timestamp) > INACTIVE_TIME)
|
||||||
|
delete sessions_.take(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::sessionInitialize()
|
||||||
|
{
|
||||||
|
static const QString SID_START = QLatin1String(C_SID) + QLatin1String("=");
|
||||||
|
|
||||||
|
if (session_ == 0)
|
||||||
|
{
|
||||||
|
QString cookie = request_.headers.value("cookie");
|
||||||
|
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
|
||||||
|
|
||||||
|
QString sessionId;
|
||||||
|
int pos = cookie.indexOf(SID_START);
|
||||||
|
if (pos >= 0)
|
||||||
|
{
|
||||||
|
pos += SID_START.length();
|
||||||
|
int end = cookie.indexOf(QRegExp("[,;]"), pos);
|
||||||
|
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Additional session check
|
||||||
|
|
||||||
|
if (!sessionId.isNull())
|
||||||
|
{
|
||||||
|
if (sessions_.contains(sessionId))
|
||||||
|
{
|
||||||
|
session_ = sessions_[sessionId];
|
||||||
|
session_->updateTimestamp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO << "session does not exist!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::readFile(const QString& path, QByteArray &data, QString &type)
|
||||||
|
{
|
||||||
|
QString ext = "";
|
||||||
|
int index = path.lastIndexOf('.') + 1;
|
||||||
|
if (index > 0)
|
||||||
|
ext = path.mid(index);
|
||||||
|
|
||||||
|
// find translated file in cache
|
||||||
|
if (translatedFiles_.contains(path))
|
||||||
|
{
|
||||||
|
data = translatedFiles_[path];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
qDebug("File %s was not found!", qPrintable(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Translate the file
|
||||||
|
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js")))
|
||||||
|
{
|
||||||
|
QString dataStr = QString::fromUtf8(data.constData());
|
||||||
|
translateDocument(dataStr);
|
||||||
|
|
||||||
|
if (path.endsWith("about.html"))
|
||||||
|
{
|
||||||
|
dataStr.replace("${VERSION}", VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = dataStr.toUtf8();
|
||||||
|
translatedFiles_[path] = data; // cashing translated file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type = CONTENT_TYPE_BY_EXT[ext];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSessionData *AbstractWebApplication::session()
|
||||||
|
{
|
||||||
|
Q_ASSERT(session_ != 0);
|
||||||
|
return &session_->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString AbstractWebApplication::generateSid()
|
||||||
|
{
|
||||||
|
QString sid;
|
||||||
|
|
||||||
|
qsrand(QDateTime::currentDateTime().toTime_t());
|
||||||
|
do
|
||||||
|
{
|
||||||
|
const size_t size = 6;
|
||||||
|
quint32 tmp[size];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
tmp[i] = qrand();
|
||||||
|
|
||||||
|
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
|
||||||
|
}
|
||||||
|
while (sessions_.contains(sid));
|
||||||
|
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::translateDocument(QString& data)
|
||||||
|
{
|
||||||
|
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
|
||||||
|
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
|
||||||
|
const std::string contexts[] = {
|
||||||
|
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
|
||||||
|
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
|
||||||
|
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
|
||||||
|
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
|
||||||
|
"StatusBar"
|
||||||
|
};
|
||||||
|
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
|
||||||
|
int i = 0;
|
||||||
|
bool found = true;
|
||||||
|
|
||||||
|
const QString locale = Preferences::instance()->getLocale();
|
||||||
|
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
|
||||||
|
|
||||||
|
while(i < data.size() && found)
|
||||||
|
{
|
||||||
|
i = regex.indexIn(data, i);
|
||||||
|
if (i >= 0)
|
||||||
|
{
|
||||||
|
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
|
||||||
|
QByteArray word = regex.cap(1).toUtf8();
|
||||||
|
|
||||||
|
QString translation = word;
|
||||||
|
if (isTranslationNeeded)
|
||||||
|
{
|
||||||
|
size_t context_index = 0;
|
||||||
|
while ((context_index < context_count) && (translation == word))
|
||||||
|
{
|
||||||
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||||
|
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
|
||||||
|
#else
|
||||||
|
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
|
||||||
|
#endif
|
||||||
|
++context_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove keyboard shortcuts
|
||||||
|
translation.replace(mnemonic, "");
|
||||||
|
|
||||||
|
data.replace(i, regex.matchedLength(), translation);
|
||||||
|
i += translation.length();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
found = false; // no more translatable strings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::isBanned() const
|
||||||
|
{
|
||||||
|
return clientFailedAttempts_.value(env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AbstractWebApplication::failedAttempts() const
|
||||||
|
{
|
||||||
|
return clientFailedAttempts_.value(env_.clientAddress, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::resetFailedAttempts()
|
||||||
|
{
|
||||||
|
clientFailedAttempts_.remove(env_.clientAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::increaseFailedAttempts()
|
||||||
|
{
|
||||||
|
const int nb_fail = clientFailedAttempts_.value(env_.clientAddress, 0) + 1;
|
||||||
|
|
||||||
|
clientFailedAttempts_[env_.clientAddress] = nb_fail;
|
||||||
|
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS)
|
||||||
|
{
|
||||||
|
// Max number of failed attempts reached
|
||||||
|
// Start ban period
|
||||||
|
UnbanTimer* ubantimer = new UnbanTimer(env_.clientAddress, this);
|
||||||
|
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
|
||||||
|
ubantimer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::isAuthNeeded()
|
||||||
|
{
|
||||||
|
return (env_.clientAddress != QHostAddress::LocalHost
|
||||||
|
&& env_.clientAddress != QHostAddress::LocalHostIPv6)
|
||||||
|
|| Preferences::instance()->isWebUiLocalAuthEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractWebApplication::printFile(const QString& path)
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
QString type;
|
||||||
|
|
||||||
|
if (!readFile(path, data, type)) {
|
||||||
|
status(404, "Not Found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(data, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::sessionStart()
|
||||||
|
{
|
||||||
|
if (session_ == 0)
|
||||||
|
{
|
||||||
|
session_ = new WebSession(generateSid());
|
||||||
|
session_->updateTimestamp();
|
||||||
|
sessions_[session_->id] = session_;
|
||||||
|
|
||||||
|
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
||||||
|
cookie.setPath(QLatin1String("/"));
|
||||||
|
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractWebApplication::sessionEnd()
|
||||||
|
{
|
||||||
|
if ((session_ != 0) && (sessions_.contains(session_->id)))
|
||||||
|
{
|
||||||
|
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
||||||
|
cookie.setPath(QLatin1String("/"));
|
||||||
|
cookie.setExpirationDate(QDateTime::currentDateTime());
|
||||||
|
|
||||||
|
sessions_.remove(session_->id);
|
||||||
|
delete session_;
|
||||||
|
session_ = 0;
|
||||||
|
|
||||||
|
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
|
||||||
|
{
|
||||||
|
QTemporaryFile tmpfile(QDir::temp().absoluteFilePath("qBT-XXXXXX.torrent"));
|
||||||
|
tmpfile.setAutoRemove(false);
|
||||||
|
if (tmpfile.open()) {
|
||||||
|
tmpfile.write(data);
|
||||||
|
tmpfile.close();
|
||||||
|
return tmpfile.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "I/O Error: Could not create temporary file";
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringMap AbstractWebApplication::initializeContentTypeByExtMap()
|
||||||
|
{
|
||||||
|
QStringMap map;
|
||||||
|
|
||||||
|
map["htm"] = Http::CONTENT_TYPE_HTML;
|
||||||
|
map["html"] = Http::CONTENT_TYPE_HTML;
|
||||||
|
map["css"] = Http::CONTENT_TYPE_CSS;
|
||||||
|
map["gif"] = Http::CONTENT_TYPE_GIF;
|
||||||
|
map["png"] = Http::CONTENT_TYPE_PNG;
|
||||||
|
map["js"] = Http::CONTENT_TYPE_JS;
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = AbstractWebApplication::initializeContentTypeByExtMap();
|
@ -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();
|
||||||
|
|
||||||
// save data to temporary file on disk and return its name (or empty string if fails)
|
bool readFile(const QString &path, QByteArray &data, QString &type);
|
||||||
static QString saveTmpFile(const QByteArray& data);
|
|
||||||
|
|
||||||
inline WebSession* session() { return session_; }
|
// save data to temporary file on disk and return its name (or empty string if fails)
|
||||||
inline HttpRequest request() const { return request_; }
|
static QString saveTmpFile(const QByteArray &data);
|
||||||
inline HttpEnvironment env() const { return env_; }
|
|
||||||
|
WebSessionData *session();
|
||||||
|
Http::Request request() const { return request_; }
|
||||||
|
Http::Environment env() const { return env_; }
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void UnbanTimerEvent();
|
||||||
|
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_;
|
|
||||||
|
|
||||||
void print_impl(const QByteArray& data, const QString& type);
|
// Current data
|
||||||
|
WebSession *session_;
|
||||||
|
Http::Request request_;
|
||||||
|
Http::Environment env_;
|
||||||
|
|
||||||
|
QString generateSid();
|
||||||
|
bool sessionInitialize();
|
||||||
|
|
||||||
|
static void translateDocument(QString &data);
|
||||||
|
|
||||||
|
static const QStringMap CONTENT_TYPE_BY_EXT;
|
||||||
|
static QStringMap initializeContentTypeByExtMap();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ABSTRACTREQUESTHANDLER_H
|
#endif // ABSTRACTWEBAPPLICATION_H
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
@ -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
|
|
@ -26,285 +26,691 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef DISABLE_GUI
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#else
|
|
||||||
#include <QApplication>
|
|
||||||
#endif
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include <libtorrent/session.hpp>
|
||||||
|
#ifndef DISABLE_GUI
|
||||||
|
// TODO: Drop GUI dependency!
|
||||||
|
#include "iconprovider.h"
|
||||||
|
#endif
|
||||||
|
#include "misc.h"
|
||||||
|
#include "fs_utils.h"
|
||||||
#include "preferences.h"
|
#include "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)
|
|
||||||
{
|
|
||||||
setSingleShot(true);
|
|
||||||
setInterval(BAN_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const QHostAddress& peerIp() const { return m_peerIp; }
|
ADD_ACTION(public, webui);
|
||||||
|
ADD_ACTION(public, index);
|
||||||
|
ADD_ACTION(public, login);
|
||||||
|
ADD_ACTION(public, logout);
|
||||||
|
ADD_ACTION(public, theme);
|
||||||
|
ADD_ACTION(public, images);
|
||||||
|
ADD_ACTION(query, torrents);
|
||||||
|
ADD_ACTION(query, preferences);
|
||||||
|
ADD_ACTION(query, transferInfo);
|
||||||
|
ADD_ACTION(query, propertiesGeneral);
|
||||||
|
ADD_ACTION(query, propertiesTrackers);
|
||||||
|
ADD_ACTION(query, propertiesFiles);
|
||||||
|
ADD_ACTION(sync, maindata);
|
||||||
|
ADD_ACTION(command, shutdown);
|
||||||
|
ADD_ACTION(command, download);
|
||||||
|
ADD_ACTION(command, upload);
|
||||||
|
ADD_ACTION(command, addTrackers);
|
||||||
|
ADD_ACTION(command, resumeAll);
|
||||||
|
ADD_ACTION(command, pauseAll);
|
||||||
|
ADD_ACTION(command, resume);
|
||||||
|
ADD_ACTION(command, pause);
|
||||||
|
ADD_ACTION(command, setPreferences);
|
||||||
|
ADD_ACTION(command, setFilePrio);
|
||||||
|
ADD_ACTION(command, getGlobalUpLimit);
|
||||||
|
ADD_ACTION(command, getGlobalDlLimit);
|
||||||
|
ADD_ACTION(command, setGlobalUpLimit);
|
||||||
|
ADD_ACTION(command, setGlobalDlLimit);
|
||||||
|
ADD_ACTION(command, getTorrentUpLimit);
|
||||||
|
ADD_ACTION(command, getTorrentDlLimit);
|
||||||
|
ADD_ACTION(command, setTorrentUpLimit);
|
||||||
|
ADD_ACTION(command, setTorrentDlLimit);
|
||||||
|
ADD_ACTION(command, alternativeSpeedLimitsEnabled);
|
||||||
|
ADD_ACTION(command, toggleAlternativeSpeedLimits);
|
||||||
|
ADD_ACTION(command, toggleSequentialDownload);
|
||||||
|
ADD_ACTION(command, toggleFirstLastPiecePrio);
|
||||||
|
ADD_ACTION(command, delete);
|
||||||
|
ADD_ACTION(command, deletePerm);
|
||||||
|
ADD_ACTION(command, increasePrio);
|
||||||
|
ADD_ACTION(command, decreasePrio);
|
||||||
|
ADD_ACTION(command, topPrio);
|
||||||
|
ADD_ACTION(command, bottomPrio);
|
||||||
|
ADD_ACTION(command, recheck);
|
||||||
|
ADD_ACTION(version, api);
|
||||||
|
ADD_ACTION(version, api_min);
|
||||||
|
ADD_ACTION(version, qbittorrent);
|
||||||
|
|
||||||
private:
|
return actions;
|
||||||
QHostAddress m_peerIp;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// WebApplication
|
#define CHECK_URI(ARGS_NUM) \
|
||||||
|
if (args_.size() != ARGS_NUM) { \
|
||||||
|
status(404, "Not Found"); \
|
||||||
|
return; \
|
||||||
|
}
|
||||||
|
#define CHECK_PARAMETERS(PARAMETERS) \
|
||||||
|
QStringList parameters; \
|
||||||
|
parameters << PARAMETERS; \
|
||||||
|
if (parameters.size() != request().posts.size()) { \
|
||||||
|
status(400, "Bad Request"); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
foreach (QString key, request().posts.keys()) { \
|
||||||
|
if (!parameters.contains(key, Qt::CaseInsensitive)) { \
|
||||||
|
status(400, "Bad Request"); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_index()
|
||||||
|
{
|
||||||
|
QString path;
|
||||||
|
|
||||||
|
if (!args_.isEmpty()) {
|
||||||
|
if (args_.back() == "favicon.ico")
|
||||||
|
path = ":/icons/skin/qbittorrent16.png";
|
||||||
|
else
|
||||||
|
path = WWW_FOLDER + args_.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
printFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_webui()
|
||||||
|
{
|
||||||
|
if (!sessionActive())
|
||||||
|
printFile(PRIVATE_FOLDER + "login.html");
|
||||||
|
else
|
||||||
|
printFile(PRIVATE_FOLDER + "index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_login()
|
||||||
|
{
|
||||||
|
const Preferences* const pref = Preferences::instance();
|
||||||
|
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||||
|
|
||||||
|
md5.addData(request().posts["password"].toLocal8Bit());
|
||||||
|
QString pass = md5.result().toHex();
|
||||||
|
|
||||||
|
bool equalUser = misc::slowEquals(request().posts["username"].toUtf8(), pref->getWebUiUsername().toUtf8());
|
||||||
|
bool equalPass = misc::slowEquals(pass.toUtf8(), pref->getWebUiPassword().toUtf8());
|
||||||
|
|
||||||
|
if (equalUser && equalPass) {
|
||||||
|
sessionStart();
|
||||||
|
print(QByteArray("Ok."), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QString addr = env().clientAddress.toString();
|
||||||
|
increaseFailedAttempts();
|
||||||
|
qDebug("client IP: %s (%d failed attempts)", qPrintable(addr), failedAttempts());
|
||||||
|
print(QByteArray("Fails."), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_logout()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
sessionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_theme()
|
||||||
|
{
|
||||||
|
if (args_.size() != 1) {
|
||||||
|
status(404, "Not Found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DISABLE_GUI
|
||||||
|
QString url = ":/icons/oxygen/" + args_.front() + ".png";
|
||||||
|
#else
|
||||||
|
QString url = IconProvider::instance()->getIconPath(args_.front());
|
||||||
|
#endif
|
||||||
|
qDebug() << Q_FUNC_INFO << "There icon:" << url;
|
||||||
|
|
||||||
|
printFile(url);
|
||||||
|
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_public_images()
|
||||||
|
{
|
||||||
|
const QString path = ":/icons/" + args_.join("/");
|
||||||
|
printFile(path);
|
||||||
|
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET params:
|
||||||
|
// - filter (string): all, downloading, completed, paused, active, inactive
|
||||||
|
// - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label")
|
||||||
|
// - sort (string): name of column for sorting by its value
|
||||||
|
// - reverse (bool): enable reverse sorting
|
||||||
|
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
|
||||||
|
// - offset (int): set offset (if less than 0 - offset from end)
|
||||||
|
void WebApplication::action_query_torrents()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
const QStringMap& gets = request().gets;
|
||||||
|
|
||||||
|
print(btjson::getTorrents(
|
||||||
|
gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true",
|
||||||
|
gets["limit"].toInt(), gets["offset"].toInt()
|
||||||
|
), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_query_preferences()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(prefjson::getPreferences(), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_query_transferInfo()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(btjson::getTransferInfo(), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_query_propertiesGeneral()
|
||||||
|
{
|
||||||
|
CHECK_URI(1);
|
||||||
|
print(btjson::getPropertiesForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_query_propertiesTrackers()
|
||||||
|
{
|
||||||
|
CHECK_URI(1);
|
||||||
|
print(btjson::getTrackersForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_query_propertiesFiles()
|
||||||
|
{
|
||||||
|
CHECK_URI(1);
|
||||||
|
print(btjson::getFilesForTorrent(args_.front()), Http::CONTENT_TYPE_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET param:
|
||||||
|
// - rid (int): last response id
|
||||||
|
void WebApplication::action_sync_maindata()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(btjson::getSyncMainData(request().gets["rid"].toInt(), session()->syncMainDataLastResponse, session()->syncMainDataLastAcceptedResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_version_api()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QString::number(API_VERSION), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_version_api_min()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QString::number(API_VERSION_MIN), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_version_qbittorrent()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QString(VERSION), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_shutdown()
|
||||||
|
{
|
||||||
|
qDebug() << "Shutdown request from Web UI";
|
||||||
|
// Special case handling for shutdown, we
|
||||||
|
// need to reply to the Web UI before
|
||||||
|
// actually shutting down.
|
||||||
|
|
||||||
|
CHECK_URI(0);
|
||||||
|
QTimer::singleShot(0, qApp, SLOT(quit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_download()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("urls");
|
||||||
|
QString urls = request().posts["urls"];
|
||||||
|
QStringList list = urls.split('\n');
|
||||||
|
|
||||||
|
foreach (QString url, list) {
|
||||||
|
url = url.trimmed();
|
||||||
|
if (!url.isEmpty()) {
|
||||||
|
if (url.startsWith("bc://bt/", Qt::CaseInsensitive)) {
|
||||||
|
qDebug("Converting bc link to magnet link");
|
||||||
|
url = misc::bcLinkToMagnet(url);
|
||||||
|
}
|
||||||
|
else if (url.startsWith("magnet:", Qt::CaseInsensitive)) {
|
||||||
|
QBtSession::instance()->addMagnetSkipAddDlg(url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qDebug("Downloading url: %s", qPrintable(url));
|
||||||
|
QBtSession::instance()->downloadUrlAndSkipDialog(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_upload()
|
||||||
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO;
|
||||||
|
CHECK_URI(0);
|
||||||
|
|
||||||
|
foreach(const Http::UploadedFile& torrent, request().files) {
|
||||||
|
QString filePath = saveTmpFile(torrent.data);
|
||||||
|
|
||||||
|
if (!filePath.isEmpty()) {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->addTorrent(filePath);
|
||||||
|
if (!h.is_valid()) {
|
||||||
|
status(415, "Internal Server Error");
|
||||||
|
print(QObject::tr("Error: '%1' is not a valid torrent file.\n").arg(torrent.filename), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
// Clean up
|
||||||
|
fsutils::forceRemove(filePath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "I/O Error: Could not create temporary file";
|
||||||
|
status(500, "Internal Server Error");
|
||||||
|
print(QObject::tr("I/O Error: Could not create temporary file."), Http::CONTENT_TYPE_TXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_addTrackers()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash" << "urls");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
|
||||||
|
if (!hash.isEmpty()) {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid() && h.has_metadata()) {
|
||||||
|
QString urls = request().posts["urls"];
|
||||||
|
QStringList list = urls.split('\n');
|
||||||
|
|
||||||
|
foreach (const QString& url, list) {
|
||||||
|
announce_entry e(url.toStdString());
|
||||||
|
h.add_tracker(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_resumeAll()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
QBtSession::instance()->resumeAllTorrents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_pauseAll()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
QBtSession::instance()->pauseAllTorrents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_resume()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash");
|
||||||
|
QBtSession::instance()->resumeTorrent(request().posts["hash"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_pause()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash");
|
||||||
|
QBtSession::instance()->pauseTorrent(request().posts["hash"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setPreferences()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("json");
|
||||||
|
prefjson::setPreferences(request().posts["json"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setFilePrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash" << "id" << "priority");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
int file_id = request().posts["id"].toInt();
|
||||||
|
int priority = request().posts["priority"].toInt();
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid() && h.has_metadata())
|
||||||
|
h.file_priority(file_id, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_getGlobalUpLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_getGlobalDlLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setGlobalUpLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("limit");
|
||||||
|
qlonglong limit = request().posts["limit"].toLongLong();
|
||||||
|
if (limit == 0) limit = -1;
|
||||||
|
|
||||||
|
QBtSession::instance()->setUploadRateLimit(limit);
|
||||||
|
if (Preferences::instance()->isAltBandwidthEnabled())
|
||||||
|
Preferences::instance()->setAltGlobalUploadLimit(limit / 1024.);
|
||||||
|
else
|
||||||
|
Preferences::instance()->setGlobalUploadLimit(limit / 1024.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setGlobalDlLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("limit");
|
||||||
|
qlonglong limit = request().posts["limit"].toLongLong();
|
||||||
|
if (limit == 0) limit = -1;
|
||||||
|
|
||||||
|
QBtSession::instance()->setDownloadRateLimit(limit);
|
||||||
|
if (Preferences::instance()->isAltBandwidthEnabled())
|
||||||
|
Preferences::instance()->setAltGlobalDownloadLimit(limit / 1024.);
|
||||||
|
else
|
||||||
|
Preferences::instance()->setGlobalDownloadLimit(limit / 1024.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_getTorrentUpLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid())
|
||||||
|
print(QByteArray::number(h.upload_limit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_getTorrentDlLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid())
|
||||||
|
print(QByteArray::number(h.download_limit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setTorrentUpLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash" << "limit");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
qlonglong limit = request().posts["limit"].toLongLong();
|
||||||
|
if (limit == 0) limit = -1;
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid())
|
||||||
|
h.set_upload_limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_setTorrentDlLimit()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash" << "limit");
|
||||||
|
QString hash = request().posts["hash"];
|
||||||
|
qlonglong limit = request().posts["limit"].toLongLong();
|
||||||
|
if (limit == 0) limit = -1;
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (h.is_valid())
|
||||||
|
h.set_download_limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_toggleAlternativeSpeedLimits()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
QBtSession::instance()->useAlternativeSpeedsLimit(!Preferences::instance()->isAltBandwidthEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_alternativeSpeedLimitsEnabled()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(QByteArray::number(Preferences::instance()->isAltBandwidthEnabled()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_toggleSequentialDownload()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
foreach (const QString &hash, hashes) {
|
||||||
|
try {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
h.toggleSequentialDownload();
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_toggleFirstLastPiecePrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
foreach (const QString &hash, hashes) {
|
||||||
|
try {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
h.toggleFirstLastPiecePrio();
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_delete()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
foreach (const QString &hash, hashes)
|
||||||
|
QBtSession::instance()->deleteTorrent(hash, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_deletePerm()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
foreach (const QString &hash, hashes)
|
||||||
|
QBtSession::instance()->deleteTorrent(hash, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_increasePrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
|
||||||
|
std::priority_queue<QPair<int, QTorrentHandle>,
|
||||||
|
std::vector<QPair<int, QTorrentHandle> >,
|
||||||
|
std::greater<QPair<int, QTorrentHandle> > > torrent_queue;
|
||||||
|
|
||||||
|
// Sort torrents by priority
|
||||||
|
foreach (const QString &hash, hashes) {
|
||||||
|
try {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
if (!h.is_seed())
|
||||||
|
torrent_queue.push(qMakePair(h.queue_position(), h));
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase torrents priority (starting with the ones with highest priority)
|
||||||
|
while(!torrent_queue.empty()) {
|
||||||
|
QTorrentHandle h = torrent_queue.top().second;
|
||||||
|
|
||||||
|
try {
|
||||||
|
h.queue_position_up();
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
|
||||||
|
torrent_queue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_decreasePrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
QStringList hashes = request().posts["hashes"].split("|");
|
||||||
|
|
||||||
|
std::priority_queue<QPair<int, QTorrentHandle>,
|
||||||
|
std::vector<QPair<int, QTorrentHandle> >,
|
||||||
|
std::less<QPair<int, QTorrentHandle> > > torrent_queue;
|
||||||
|
|
||||||
|
// Sort torrents by priority
|
||||||
|
foreach (const QString &hash, hashes) {
|
||||||
|
try {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
|
||||||
|
if (!h.is_seed())
|
||||||
|
torrent_queue.push(qMakePair(h.queue_position(), h));
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease torrents priority (starting with the ones with lowest priority)
|
||||||
|
while(!torrent_queue.empty()) {
|
||||||
|
QTorrentHandle h = torrent_queue.top().second;
|
||||||
|
|
||||||
|
try {
|
||||||
|
h.queue_position_down();
|
||||||
|
}
|
||||||
|
catch(invalid_handle&) {}
|
||||||
|
|
||||||
|
torrent_queue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_topPrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
foreach (const QString &hash, request().posts["hashes"].split("|")) {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
if (h.is_valid()) h.queue_position_top();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_bottomPrio()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hashes");
|
||||||
|
foreach (const QString &hash, request().posts["hashes"].split("|")) {
|
||||||
|
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||||
|
if (h.is_valid()) h.queue_position_bottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::action_command_recheck()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
CHECK_PARAMETERS("hash");
|
||||||
|
QBtSession::instance()->recheckTorrent(request().posts["hash"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebApplication::isPublicScope()
|
||||||
|
{
|
||||||
|
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::processRequest()
|
||||||
|
{
|
||||||
|
scope_ = DEFAULT_SCOPE;
|
||||||
|
action_ = DEFAULT_ACTION;
|
||||||
|
|
||||||
|
parsePath();
|
||||||
|
|
||||||
|
if (args_.contains(".") || args_.contains("..")) {
|
||||||
|
qDebug() << Q_FUNC_INFO << "Invalid path:" << request().path;
|
||||||
|
status(404, "Not Found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPublicScope() && !sessionActive()) {
|
||||||
|
status(403, "Forbidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions_.value(scope_).value(action_) != 0) {
|
||||||
|
(this->*(actions_[scope_][action_]))();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
status(404, "Not Found");
|
||||||
|
qDebug() << Q_FUNC_INFO << "Resource not found:" << request().path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApplication::parsePath()
|
||||||
|
{
|
||||||
|
if(request().path == "/") action_ = WEBUI_ACTION;
|
||||||
|
|
||||||
|
// check action for requested path
|
||||||
|
QStringList pathItems = request().path.split('/', QString::SkipEmptyParts);
|
||||||
|
if (!pathItems.empty()) {
|
||||||
|
if (actions_.contains(pathItems.front())) {
|
||||||
|
scope_ = pathItems.front();
|
||||||
|
pathItems.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pathItems.empty()) {
|
||||||
|
if (actions_[scope_].contains(pathItems.front())) {
|
||||||
|
action_ = pathItems.front();
|
||||||
|
pathItems.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args_ = pathItems;
|
||||||
|
}
|
||||||
|
|
||||||
WebApplication::WebApplication(QObject *parent)
|
WebApplication::WebApplication(QObject *parent)
|
||||||
: QObject(parent)
|
: AbstractWebApplication(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
WebApplication::~WebApplication()
|
QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::actions_ = WebApplication::initializeActions();
|
||||||
{
|
|
||||||
// cleanup sessions data
|
|
||||||
foreach (WebSession* session, sessions_.values())
|
|
||||||
delete session;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebApplication *WebApplication::instance()
|
|
||||||
{
|
|
||||||
static WebApplication inst;
|
|
||||||
return &inst;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebApplication::UnbanTimerEvent()
|
|
||||||
{
|
|
||||||
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
|
|
||||||
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
|
|
||||||
clientFailedAttempts_.remove(ubantimer->peerIp());
|
|
||||||
ubantimer->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebApplication::sessionInitialize(AbstractRequestHandler* _this)
|
|
||||||
{
|
|
||||||
if (_this->session_ == 0)
|
|
||||||
{
|
|
||||||
QString cookie = _this->request_.headers.value("cookie");
|
|
||||||
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
|
|
||||||
|
|
||||||
QString sessionId;
|
|
||||||
const QString SID_START = C_SID + "=";
|
|
||||||
int pos = cookie.indexOf(SID_START);
|
|
||||||
if (pos >= 0)
|
|
||||||
{
|
|
||||||
pos += SID_START.length();
|
|
||||||
int end = cookie.indexOf(QRegExp("[,;]"), pos);
|
|
||||||
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Additional session check
|
|
||||||
|
|
||||||
if (!sessionId.isNull())
|
|
||||||
{
|
|
||||||
if (sessions_.contains(sessionId))
|
|
||||||
{
|
|
||||||
_this->session_ = sessions_[sessionId];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug() << Q_FUNC_INFO << "session does not exist!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebApplication::readFile(const QString& path, QByteArray &data, QString &type)
|
|
||||||
{
|
|
||||||
QString ext = "";
|
|
||||||
int index = path.lastIndexOf('.') + 1;
|
|
||||||
if (index > 0)
|
|
||||||
ext = path.mid(index);
|
|
||||||
|
|
||||||
// find translated file in cache
|
|
||||||
if (translatedFiles_.contains(path))
|
|
||||||
{
|
|
||||||
data = translatedFiles_[path];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QFile file(path);
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
{
|
|
||||||
qDebug("File %s was not found!", qPrintable(path));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = file.readAll();
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
// Translate the file
|
|
||||||
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js")))
|
|
||||||
{
|
|
||||||
QString dataStr = QString::fromUtf8(data.constData());
|
|
||||||
translateDocument(dataStr);
|
|
||||||
|
|
||||||
if (path.endsWith("about.html"))
|
|
||||||
{
|
|
||||||
dataStr.replace("${VERSION}", VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = dataStr.toUtf8();
|
|
||||||
translatedFiles_[path] = data; // cashing translated file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type = CONTENT_TYPE_BY_EXT[ext];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString WebApplication::generateSid()
|
|
||||||
{
|
|
||||||
QString sid;
|
|
||||||
|
|
||||||
qsrand(QDateTime::currentDateTime().toTime_t());
|
|
||||||
do
|
|
||||||
{
|
|
||||||
const size_t size = 6;
|
|
||||||
quint32 tmp[size];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
tmp[i] = qrand();
|
|
||||||
|
|
||||||
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
|
|
||||||
}
|
|
||||||
while (sessions_.contains(sid));
|
|
||||||
|
|
||||||
return sid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebApplication::translateDocument(QString& data)
|
|
||||||
{
|
|
||||||
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
|
|
||||||
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
|
|
||||||
const std::string contexts[] = {
|
|
||||||
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
|
|
||||||
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
|
|
||||||
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
|
|
||||||
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
|
|
||||||
"StatusBar"
|
|
||||||
};
|
|
||||||
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
|
|
||||||
int i = 0;
|
|
||||||
bool found = true;
|
|
||||||
|
|
||||||
const QString locale = Preferences::instance()->getLocale();
|
|
||||||
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
|
|
||||||
|
|
||||||
while(i < data.size() && found)
|
|
||||||
{
|
|
||||||
i = regex.indexIn(data, i);
|
|
||||||
if (i >= 0)
|
|
||||||
{
|
|
||||||
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
|
|
||||||
QByteArray word = regex.cap(1).toUtf8();
|
|
||||||
|
|
||||||
QString translation = word;
|
|
||||||
if (isTranslationNeeded)
|
|
||||||
{
|
|
||||||
size_t context_index = 0;
|
|
||||||
while ((context_index < context_count) && (translation == word))
|
|
||||||
{
|
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
|
||||||
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
|
|
||||||
#else
|
|
||||||
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
|
|
||||||
#endif
|
|
||||||
++context_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remove keyboard shortcuts
|
|
||||||
translation.replace(mnemonic, "");
|
|
||||||
|
|
||||||
data.replace(i, regex.matchedLength(), translation);
|
|
||||||
i += translation.length();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
found = false; // no more translatable strings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebApplication::isBanned(const AbstractRequestHandler *_this) const
|
|
||||||
{
|
|
||||||
return clientFailedAttempts_.value(_this->env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebApplication::failedAttempts(const AbstractRequestHandler* _this) const
|
|
||||||
{
|
|
||||||
return clientFailedAttempts_.value(_this->env_.clientAddress, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebApplication::resetFailedAttempts(AbstractRequestHandler* _this)
|
|
||||||
{
|
|
||||||
clientFailedAttempts_.remove(_this->env_.clientAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebApplication::increaseFailedAttempts(AbstractRequestHandler* _this)
|
|
||||||
{
|
|
||||||
const int nb_fail = clientFailedAttempts_.value(_this->env_.clientAddress, 0) + 1;
|
|
||||||
|
|
||||||
clientFailedAttempts_[_this->env_.clientAddress] = nb_fail;
|
|
||||||
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS)
|
|
||||||
{
|
|
||||||
// Max number of failed attempts reached
|
|
||||||
// Start ban period
|
|
||||||
UnbanTimer* ubantimer = new UnbanTimer(_this->env_.clientAddress, this);
|
|
||||||
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
|
|
||||||
ubantimer->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebApplication::sessionStart(AbstractRequestHandler *_this)
|
|
||||||
{
|
|
||||||
if (_this->session_ == 0)
|
|
||||||
{
|
|
||||||
_this->session_ = new WebSession(generateSid());
|
|
||||||
sessions_[_this->session_->id] = _this->session_;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebApplication::sessionEnd(AbstractRequestHandler *_this)
|
|
||||||
{
|
|
||||||
if ((_this->session_ != 0) && (sessions_.contains(_this->session_->id)))
|
|
||||||
{
|
|
||||||
sessions_.remove(_this->session_->id);
|
|
||||||
delete _this->session_;
|
|
||||||
_this->session_ = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringMap WebApplication::initializeContentTypeByExtMap()
|
|
||||||
{
|
|
||||||
QStringMap map;
|
|
||||||
|
|
||||||
map["htm"] = CONTENT_TYPE_HTML;
|
|
||||||
map["html"] = CONTENT_TYPE_HTML;
|
|
||||||
map["css"] = CONTENT_TYPE_CSS;
|
|
||||||
map["gif"] = CONTENT_TYPE_GIF;
|
|
||||||
map["png"] = CONTENT_TYPE_PNG;
|
|
||||||
map["js"] = CONTENT_TYPE_JS;
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QStringMap WebApplication::CONTENT_TYPE_BY_EXT = WebApplication::initializeContentTypeByExtMap();
|
|
||||||
|
@ -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();
|
||||||
|
void action_public_logout();
|
||||||
|
void action_public_theme();
|
||||||
|
void action_public_images();
|
||||||
|
void action_query_torrents();
|
||||||
|
void action_query_preferences();
|
||||||
|
void action_query_transferInfo();
|
||||||
|
void action_query_propertiesGeneral();
|
||||||
|
void action_query_propertiesTrackers();
|
||||||
|
void action_query_propertiesFiles();
|
||||||
|
void action_sync_maindata();
|
||||||
|
void action_command_shutdown();
|
||||||
|
void action_command_download();
|
||||||
|
void action_command_upload();
|
||||||
|
void action_command_addTrackers();
|
||||||
|
void action_command_resumeAll();
|
||||||
|
void action_command_pauseAll();
|
||||||
|
void action_command_resume();
|
||||||
|
void action_command_pause();
|
||||||
|
void action_command_setPreferences();
|
||||||
|
void action_command_setFilePrio();
|
||||||
|
void action_command_getGlobalUpLimit();
|
||||||
|
void action_command_getGlobalDlLimit();
|
||||||
|
void action_command_setGlobalUpLimit();
|
||||||
|
void action_command_setGlobalDlLimit();
|
||||||
|
void action_command_getTorrentUpLimit();
|
||||||
|
void action_command_getTorrentDlLimit();
|
||||||
|
void action_command_setTorrentUpLimit();
|
||||||
|
void action_command_setTorrentDlLimit();
|
||||||
|
void action_command_alternativeSpeedLimitsEnabled();
|
||||||
|
void action_command_toggleAlternativeSpeedLimits();
|
||||||
|
void action_command_toggleSequentialDownload();
|
||||||
|
void action_command_toggleFirstLastPiecePrio();
|
||||||
|
void action_command_delete();
|
||||||
|
void action_command_deletePerm();
|
||||||
|
void action_command_increasePrio();
|
||||||
|
void action_command_decreasePrio();
|
||||||
|
void action_command_topPrio();
|
||||||
|
void action_command_bottomPrio();
|
||||||
|
void action_command_recheck();
|
||||||
|
void action_version_api();
|
||||||
|
void action_version_api_min();
|
||||||
|
void action_version_qbittorrent();
|
||||||
|
|
||||||
QString generateSid();
|
typedef void (WebApplication::*Action)();
|
||||||
static void translateDocument(QString& data);
|
|
||||||
|
|
||||||
static const QStringMap CONTENT_TYPE_BY_EXT;
|
QString scope_;
|
||||||
static QStringMap initializeContentTypeByExtMap();
|
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
Normal file
41
src/webui/websessiondata.h
Normal file
@ -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
Normal file
102
src/webui/webui.cpp
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "webui.h"
|
||||||
|
#include "http/server.h"
|
||||||
|
#include "webapplication.h"
|
||||||
|
#include "dnsupdater.h"
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
WebUI::WebUI(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
connect(Preferences::instance(), SIGNAL(changed()), SLOT(init()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebUI::init()
|
||||||
|
{
|
||||||
|
Preferences* const pref = Preferences::instance();
|
||||||
|
Logger* const logger = Logger::instance();
|
||||||
|
|
||||||
|
if (pref->isWebUiEnabled()) {
|
||||||
|
const quint16 port = pref->getWebUiPort();
|
||||||
|
|
||||||
|
if (httpServer_) {
|
||||||
|
if (httpServer_->serverPort() != port)
|
||||||
|
httpServer_->close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
webapp_ = new WebApplication(this);
|
||||||
|
httpServer_ = new Http::Server(webapp_, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_OPENSSL
|
||||||
|
if (pref->isWebUiHttpsEnabled()) {
|
||||||
|
QSslCertificate cert(pref->getWebUiHttpsCertificate());
|
||||||
|
QSslKey key;
|
||||||
|
key = QSslKey(pref->getWebUiHttpsKey(), QSsl::Rsa);
|
||||||
|
if (!cert.isNull() && !key.isNull())
|
||||||
|
httpServer_->enableHttps(cert, key);
|
||||||
|
else
|
||||||
|
httpServer_->disableHttps();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpServer_->disableHttps();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!httpServer_->isListening()) {
|
||||||
|
bool success = httpServer_->listen(QHostAddress::Any, port);
|
||||||
|
if (success)
|
||||||
|
logger->addMessage(tr("The Web UI is listening on port %1").arg(port));
|
||||||
|
else
|
||||||
|
logger->addMessage(tr("Web User Interface Error - Unable to bind Web UI to port %1").arg(port), Log::CRITICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynDNS
|
||||||
|
if (pref->isDynDNSEnabled()) {
|
||||||
|
if (!dynDNSUpdater_)
|
||||||
|
dynDNSUpdater_ = new DNSUpdater(this);
|
||||||
|
else
|
||||||
|
dynDNSUpdater_->updateCredentials();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (dynDNSUpdater_)
|
||||||
|
delete dynDNSUpdater_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (httpServer_)
|
||||||
|
delete httpServer_;
|
||||||
|
if (webapp_)
|
||||||
|
delete webapp_;
|
||||||
|
if (dynDNSUpdater_)
|
||||||
|
delete dynDNSUpdater_;
|
||||||
|
}
|
||||||
|
}
|
55
src/webui/webui.h
Normal file
55
src/webui/webui.h
Normal file
@ -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
|
@ -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/btjson.cpp \
|
||||||
$$PWD/httpresponsegenerator.cpp \
|
$$PWD/prefjson.cpp \
|
||||||
$$PWD/btjson.cpp \
|
$$PWD/webapplication.cpp \
|
||||||
$$PWD/prefjson.cpp \
|
$$PWD/qtorrentfilter.cpp \
|
||||||
$$PWD/webapplication.cpp \
|
$$PWD/abstractwebapplication.cpp
|
||||||
$$PWD/abstractrequesthandler.cpp \
|
|
||||||
$$PWD/requesthandler.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…
x
Reference in New Issue
Block a user