diff --git a/src/Icons/oxygen/system-log-out.png b/src/Icons/oxygen/system-log-out.png new file mode 100644 index 000000000..d8817b7b5 Binary files /dev/null and b/src/Icons/oxygen/system-log-out.png differ diff --git a/src/icons.qrc b/src/icons.qrc index 5a138af3b..e338bd2f7 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1,5 +1,5 @@ - - + + Icons/3-state-checkbox.gif Icons/L.gif Icons/loading.png @@ -349,5 +349,6 @@ Icons/skin/tabs.gif Icons/skin/toolbox-divider.gif Icons/skin/uploading.png + Icons/oxygen/system-log-out.png - \ No newline at end of file + diff --git a/src/preferences/preferences.cpp b/src/preferences/preferences.cpp index 2fcbe4cf2..542c9d312 100644 --- a/src/preferences/preferences.cpp +++ b/src/preferences/preferences.cpp @@ -951,7 +951,6 @@ QString Preferences::getWebUiPassword() const { QString pass_ha1 = value("Preferences/WebUI/Password_ha1").toString(); if (pass_ha1.isEmpty()) { QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData(getWebUiUsername().toLocal8Bit()+":"+QBT_REALM+":"); md5.addData("adminadmin"); pass_ha1 = md5.result().toHex(); } @@ -959,13 +958,8 @@ QString Preferences::getWebUiPassword() const { } void Preferences::setWebUiPassword(const QString &new_password) { - // Get current password md5 - QString current_pass_md5 = getWebUiPassword(); - // Check if password did not change - if (current_pass_md5 == new_password) return; // Encode to md5 and save QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData(getWebUiUsername().toLocal8Bit()+":"+QBT_REALM+":"); md5.addData(new_password.toLocal8Bit()); setValue("Preferences/WebUI/Password_ha1", md5.result().toHex()); diff --git a/src/preferences/preferences.h b/src/preferences/preferences.h index 272288890..2d8718201 100755 --- a/src/preferences/preferences.h +++ b/src/preferences/preferences.h @@ -44,7 +44,6 @@ #include -#define QBT_REALM "Web UI Access" enum scheduler_days { EVERY_DAY, WEEK_DAYS, WEEK_ENDS, MON, TUE, WED, THU, FRI, SAT, SUN }; enum maxRatioAction {PAUSE_ACTION, REMOVE_ACTION}; namespace Proxy { diff --git a/src/qtlibtorrent/qbtsession.cpp b/src/qtlibtorrent/qbtsession.cpp index 502c310bb..00021832c 100755 --- a/src/qtlibtorrent/qbtsession.cpp +++ b/src/qtlibtorrent/qbtsession.cpp @@ -671,8 +671,6 @@ void QBtSession::initWebUi() { } #endif - httpServer->setAuthorization(username, password); - httpServer->setlocalAuthEnabled(pref->isWebUiLocalAuthEnabled()); if (!httpServer->isListening()) { bool success = httpServer->listen(QHostAddress::Any, port); if (success) diff --git a/src/tracker/qtracker.cpp b/src/tracker/qtracker.cpp index cfab904f3..0e334ccda 100644 --- a/src/tracker/qtracker.cpp +++ b/src/tracker/qtracker.cpp @@ -29,18 +29,14 @@ */ #include -#include -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) -#include -#endif #include #include -#include "httprequestheader.h" -#include "httpresponseheader.h" #include "qtracker.h" #include "preferences.h" +#include "httpresponsegenerator.h" +#include "httprequestparser.h" #include @@ -93,53 +89,38 @@ void QTracker::readRequest() QTcpSocket *socket = static_cast(sender()); QByteArray input = socket->readAll(); //qDebug("QTracker: Raw request:\n%s", input.data()); - HttpRequestHeader http_request(input); - if (!http_request.isValid()) { - qDebug("QTracker: Invalid HTTP Request:\n %s", qPrintable(http_request.toString())); + 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 (http_request.method() != "GET") { - qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(http_request.method())); + if (request.method != "GET") { + qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method)); respondInvalidRequest(socket, 100, "Invalid request type"); return; } - if (!http_request.path().startsWith("/announce", Qt::CaseInsensitive)) { - qDebug("QTracker: Unrecognized path: %s", qPrintable(http_request.path())); + 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 - // Parse GET parameters - QHash get_parameters; - QUrl url = QUrl::fromEncoded(http_request.path().toLatin1()); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) - QUrlQuery query(url); - QListIterator > i(query.queryItems()); -#else - QListIterator > i(url.queryItems()); -#endif - while (i.hasNext()) { - QPair pair = i.next(); - get_parameters[pair.first] = pair.second; - } - - respondToAnnounceRequest(socket, get_parameters); + respondToAnnounceRequest(socket, request.gets); } void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg) { - HttpResponseHeader response; - response.setStatusLine(code, msg); - socket->write(response.toString().toLocal8Bit()); + HttpResponse response(code, msg); + socket->write(HttpResponseGenerator::generate(response)); socket->disconnectFromHost(); } void QTracker::respondToAnnounceRequest(QTcpSocket *socket, - const QHash& get_parameters) + const QMap& get_parameters) { TrackerAnnounceRequest annonce_req; // IP @@ -246,12 +227,12 @@ void QTracker::ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceReques // bencode std::vector buf; bencode(std::back_inserter(buf), reply_entry); - QByteArray reply(&buf[0], buf.size()); + QByteArray reply(&buf[0], static_cast(buf.size())); qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData()); // HTTP reply - HttpResponseHeader response; - response.setStatusLine(200, "OK"); - socket->write(response.toString().toLocal8Bit() + reply); + HttpResponse response(200, "OK"); + response.content = reply; + socket->write(HttpResponseGenerator::generate(response)); socket->disconnectFromHost(); } diff --git a/src/tracker/qtracker.h b/src/tracker/qtracker.h index 4d76bc91e..bf313ba18 100644 --- a/src/tracker/qtracker.h +++ b/src/tracker/qtracker.h @@ -61,7 +61,7 @@ protected slots: void readRequest(); void handlePeerConnection(); void respondInvalidRequest(QTcpSocket *socket, int code, QString msg); - void respondToAnnounceRequest(QTcpSocket *socket, const QHash& get_parameters); + void respondToAnnounceRequest(QTcpSocket *socket, const QMap& get_parameters); void ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req); private: diff --git a/src/webui/abstractrequesthandler.cpp b/src/webui/abstractrequesthandler.cpp new file mode 100644 index 000000000..8e6d97d19 --- /dev/null +++ b/src/webui/abstractrequesthandler.cpp @@ -0,0 +1,174 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2012, Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include +#include +#include +#include +#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) +{ + if (isBanned()) + { + status(403, "Forbidden"); + print(QObject::tr("Your IP address has been banned after too many failed authentication attempts.")); + return; + } + + sessionInitialize(); + + if (!sessionActive() && !isAuthNeeded()) sessionStart(); +} + +HttpResponse AbstractRequestHandler::run() +{ + response_ = HttpResponse(); + 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(); +} diff --git a/src/webui/abstractrequesthandler.h b/src/webui/abstractrequesthandler.h new file mode 100644 index 000000000..9f2c3b605 --- /dev/null +++ b/src/webui/abstractrequesthandler.h @@ -0,0 +1,89 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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 ABSTRACTREQUESTHANDLER_H +#define ABSTRACTREQUESTHANDLER_H + +#include +#include "httptypes.h" + +class WebApplication; +struct WebSession; + +class AbstractRequestHandler +{ + friend class WebApplication; + +public: + AbstractRequestHandler( + const HttpRequest& request, const HttpEnvironment& env, + WebApplication* app); + + HttpResponse run(); + +protected: + virtual void processRequest() = 0; + + void status(uint code, const QString& text); + void header(const QString& name, const QString& value); + void print(const QString& text, const QString& type = CONTENT_TYPE_HTML); + void print(const QByteArray& data, const QString& type = CONTENT_TYPE_HTML); + void printFile(const QString& path); + + // Session management + bool sessionActive() const { return session_ != 0; } + void sessionInitialize(); + void sessionStart(); + void sessionEnd(); + + // Ban management + bool isBanned() const; + int failedAttempts() const; + void resetFailedAttempts(); + void increaseFailedAttempts(); + + bool isAuthNeeded(); + + // save data to temporary file on disk and return its name (or empty string if fails) + static QString saveTmpFile(const QByteArray& data); + + inline WebSession* session() { return session_; } + inline HttpRequest request() const { return request_; } + inline HttpEnvironment env() const { return env_; } + +private: + WebApplication* app_; + WebSession* session_; + const HttpRequest request_; + const HttpEnvironment env_; + HttpResponse response_; + + void print_impl(const QByteArray& data, const QString& type); +}; + +#endif // ABSTRACTREQUESTHANDLER_H diff --git a/src/webui/extra_translations.h b/src/webui/extra_translations.h new file mode 100644 index 000000000..bd9018ca4 --- /dev/null +++ b/src/webui/extra_translations.h @@ -0,0 +1,75 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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 EXTRA_TRANSLATIONS_H +#define EXTRA_TRANSLATIONS_H + +#include + +// Additional translations for Web UI +static const char *__TRANSLATIONS__[] = { + QT_TRANSLATE_NOOP("HttpServer", "File"), + QT_TRANSLATE_NOOP("HttpServer", "Edit"), + QT_TRANSLATE_NOOP("HttpServer", "Help"), + QT_TRANSLATE_NOOP("HttpServer", "Download Torrents from their URL or Magnet link"), + QT_TRANSLATE_NOOP("HttpServer", "Only one link per line"), + QT_TRANSLATE_NOOP("HttpServer", "Download local torrent"), + QT_TRANSLATE_NOOP("HttpServer", "Torrent files were correctly added to download list."), + QT_TRANSLATE_NOOP("HttpServer", "Point to torrent file"), + QT_TRANSLATE_NOOP("HttpServer", "Download"), + QT_TRANSLATE_NOOP("HttpServer", "Are you sure you want to delete the selected torrents from the transfer list and hard disk?"), + QT_TRANSLATE_NOOP("HttpServer", "Download rate limit must be greater than 0 or disabled."), + QT_TRANSLATE_NOOP("HttpServer", "Upload rate limit must be greater than 0 or disabled."), + QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections limit must be greater than 0 or disabled."), + QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections per torrent limit must be greater than 0 or disabled."), + QT_TRANSLATE_NOOP("HttpServer", "Maximum number of upload slots per torrent limit must be greater than 0 or disabled."), + QT_TRANSLATE_NOOP("HttpServer", "Unable to save program preferences, qBittorrent is probably unreachable."), + QT_TRANSLATE_NOOP("HttpServer", "Language"), + QT_TRANSLATE_NOOP("HttpServer", "The port used for incoming connections must be greater than 1024 and less than 65535."), + QT_TRANSLATE_NOOP("HttpServer", "The port used for the Web UI must be greater than 1024 and less than 65535."), + QT_TRANSLATE_NOOP("HttpServer", "The Web UI username must be at least 3 characters long."), + QT_TRANSLATE_NOOP("HttpServer", "The Web UI password must be at least 3 characters long."), + QT_TRANSLATE_NOOP("HttpServer", "Save"), + QT_TRANSLATE_NOOP("HttpServer", "qBittorrent client is not reachable"), + QT_TRANSLATE_NOOP("HttpServer", "HTTP Server"), + QT_TRANSLATE_NOOP("HttpServer", "The following parameters are supported:"), + QT_TRANSLATE_NOOP("HttpServer", "Torrent path"), + QT_TRANSLATE_NOOP("HttpServer", "Torrent name"), + QT_TRANSLATE_NOOP("HttpServer", "qBittorrent has been shutdown."), + QT_TRANSLATE_NOOP("HttpServer", "Unable to log in, qBittorrent is probably unreachable."), + QT_TRANSLATE_NOOP("HttpServer", "Invalid Username or Password."), + QT_TRANSLATE_NOOP("HttpServer", "Password"), + QT_TRANSLATE_NOOP("HttpServer", "Login"), + QT_TRANSLATE_NOOP("HttpServer", "qBittorrent web User Interface") +}; + +static const struct { const char *source; const char *comment; } __COMMENTED_TRANSLATIONS__[] = { + QT_TRANSLATE_NOOP3("HttpServer", "Downloaded", "Is the file downloaded or not?") +}; + +#endif // EXTRA_TRANSLATIONS_H diff --git a/src/webui/httpconnection.cpp b/src/webui/httpconnection.cpp index 00ac1c636..196b1f5e7 100644 --- a/src/webui/httpconnection.cpp +++ b/src/webui/httpconnection.cpp @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,661 +25,84 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ - -#include "httpconnection.h" -#include "httpserver.h" -#include "httprequestheader.h" -#include "httpresponseheader.h" -#include "preferences.h" -#include "btjson.h" -#include "prefjson.h" -#include "qbtsession.h" -#include "misc.h" -#include "fs_utils.h" -#ifndef DISABLE_GUI -#include "iconprovider.h" -#endif #include -#include -#include -#include #include -#include -#include -#include -#include - -#include - -using namespace libtorrent; +#include "httptypes.h" +#include "httpserver.h" +#include "httprequestparser.h" +#include "httpresponsegenerator.h" +#include "webapplication.h" +#include "requesthandler.h" +#include "httpconnection.h" -HttpConnection::HttpConnection(QTcpSocket *socket, HttpServer *parent) - : QObject(parent), m_socket(socket), m_httpserver(parent) +HttpConnection::HttpConnection(QTcpSocket *socket, HttpServer *httpserver) + : QObject(httpserver), m_socket(socket) { m_socket->setParent(this); connect(m_socket, SIGNAL(readyRead()), SLOT(read())); connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater())); } -HttpConnection::~HttpConnection() { +HttpConnection::~HttpConnection() +{ delete m_socket; } -void HttpConnection::processDownloadedFile(const QString &url, - const QString &file_path) { - qDebug("URL %s successfully downloaded !", qPrintable(url)); - emit torrentReadyToBeDownloaded(file_path, false, url, false); -} - -void HttpConnection::handleDownloadFailure(const QString& url, - const QString& reason) { - std::cerr << "Could not download " << qPrintable(url) << ", reason: " - << qPrintable(reason) << std::endl; -} - void HttpConnection::read() { m_receivedData.append(m_socket->readAll()); - // Parse HTTP request header - const int header_end = m_receivedData.indexOf("\r\n\r\n"); - if (header_end < 0) { - qDebug() << "Partial request: \n" << m_receivedData; + HttpRequest request; + HttpRequestParser::ErrorCode err = HttpRequestParser::parse(m_receivedData, request); + switch (err) + { + case HttpRequestParser::IncompleteRequest: // Partial request waiting for the rest - return; - } - - const QByteArray header = m_receivedData.left(header_end); - m_parser.writeHeader(header); - if (m_parser.isError()) { - qWarning() << Q_FUNC_INFO << "header parsing error"; - m_receivedData.clear(); - m_generator.setStatusLine(400, "Bad Request"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - - // Parse HTTP request message - if (m_parser.header().hasContentLength()) { - const int expected_length = m_parser.header().contentLength(); - QByteArray message = m_receivedData.mid(header_end + 4, expected_length); - - if (expected_length > 10000000 /* ~10MB */) { - qWarning() << "Bad request: message too long"; - m_generator.setStatusLine(400, "Bad Request"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - m_receivedData.clear(); - write(); - return; - } - - if (message.length() < expected_length) { - // Message too short, waiting for the rest - qDebug() << "Partial message:\n" << message; - return; - } - - m_parser.writeMessage(message); - m_receivedData = m_receivedData.mid(header_end + 4 + expected_length); - } else { - m_receivedData.clear(); - } - - if (m_parser.isError()) { - qWarning() << Q_FUNC_INFO << "message parsing error"; - m_generator.setStatusLine(400, "Bad Request"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - } else { - respond(); - } -} - -void HttpConnection::write() + break; + case HttpRequestParser::BadRequest: + write(HttpResponse(400, "Bad Request")); + break; + case HttpRequestParser::NoError: + HttpEnvironment env; + env.clientAddress = m_socket->peerAddress(); + HttpResponse response = RequestHandler(request, env, WebApplication::instance()).run(); + if (acceptsGzipEncoding(request.headers["accept-encoding"])) + response.headers[HEADER_CONTENT_ENCODING] = "gzip"; + write(response); + break; + } +} + +void HttpConnection::write(const HttpResponse& response) { - m_socket->write(m_generator.toByteArray()); + m_socket->write(HttpResponseGenerator::generate(response)); m_socket->disconnectFromHost(); } -void HttpConnection::translateDocument(QString& data) { - static QRegExp regex(QString::fromUtf8("_\\(([\\w\\s?!:\\/\\(\\),%µ&\\-\\.]+)\\)")); - static QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?"); - const std::string contexts[] = {"TransferListFiltersWidget", "TransferListWidget", - "PropertiesWidget", "MainWindow", "HttpServer", - "confirmDeletionDlg", "TrackerList", "TorrentFilesModel", - "options_imp", "Preferences", "TrackersAdditionDlg", - "ScanFoldersModel", "PropTabBar", "TorrentModel", - "downloadFromURL", "misc"}; - 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 - } - } -} - -void HttpConnection::respond() { - if ((m_socket->peerAddress() != QHostAddress::LocalHost - && m_socket->peerAddress() != QHostAddress::LocalHostIPv6) - || m_httpserver->isLocalAuthEnabled()) { - // Authentication - const QString peer_ip = m_socket->peerAddress().toString(); - const int nb_fail = m_httpserver->NbFailedAttemptsForIp(peer_ip); - if (nb_fail >= MAX_AUTH_FAILED_ATTEMPTS) { - m_generator.setStatusLine(403, "Forbidden"); - m_generator.setMessage(tr("Your IP address has been banned after too many failed authentication attempts.")); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - QString auth = m_parser.header().value("Authorization"); - if (auth.isEmpty()) { - // Return unauthorized header - qDebug("Auth is Empty..."); - m_generator.setStatusLine(401, "Unauthorized"); - m_generator.setValue("WWW-Authenticate", "Digest realm=\""+QString(QBT_REALM)+"\", nonce=\""+m_httpserver->generateNonce()+"\", opaque=\""+m_httpserver->generateNonce()+"\", stale=\"false\", algorithm=\"MD5\", qop=\"auth\""); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - //qDebug("Auth: %s", qPrintable(auth.split(" ").first())); - if (QString::compare(auth.split(" ").first(), "Digest", Qt::CaseInsensitive) != 0 - || !m_httpserver->isAuthorized(auth.toUtf8(), m_parser.header().method())) { - // Update failed attempt counter - m_httpserver->increaseNbFailedAttemptsForIp(peer_ip); - qDebug("client IP: %s (%d failed attempts)", qPrintable(peer_ip), nb_fail); - // Return unauthorized header - m_generator.setStatusLine(401, "Unauthorized"); - m_generator.setValue("WWW-Authenticate", "Digest realm=\""+QString(QBT_REALM)+"\", nonce=\""+m_httpserver->generateNonce()+"\", opaque=\""+m_httpserver->generateNonce()+"\", stale=\"false\", algorithm=\"MD5\", qop=\"auth\""); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - // Client successfully authenticated, reset number of failed attempts - m_httpserver->resetNbFailedAttemptsForIp(peer_ip); - } - QString url = m_parser.url(); - // Favicon - if (url.endsWith("favicon.ico")) { - qDebug("Returning favicon"); - QFile favicon(":/Icons/skin/qbittorrent16.png"); - if (favicon.open(QIODevice::ReadOnly)) { - const QByteArray data = favicon.readAll(); - favicon.close(); - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("png"); - m_generator.setMessage(data); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - } else { - respondNotFound(); - } - return; - } - - QStringList list = url.split('/', QString::SkipEmptyParts); - if (list.contains(".") || list.contains("..")) { - respondNotFound(); - return; - } +bool HttpConnection::acceptsGzipEncoding(const QString& encoding) +{ + int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive); + if (pos == -1) + return false; - if (list.isEmpty()) - list.append("index.html"); + // Let's see if there's a qvalue of 0.0 following + if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway + return true; - if (list.size() >= 2) { - if (list[0] == "json") { - if (list[1] == "torrents") { - respondTorrentsJson(); - return; - } - if (list.size() > 2) { - if (list[1] == "propertiesGeneral") { - const QString& hash = list[2]; - respondGenPropertiesJson(hash); - return; - } - if (list[1] == "propertiesTrackers") { - const QString& hash = list[2]; - respondTrackersPropertiesJson(hash); - return; - } - if (list[1] == "propertiesFiles") { - const QString& hash = list[2]; - respondFilesPropertiesJson(hash); - return; - } - } else { - if (list[1] == "preferences") { - respondPreferencesJson(); - return; - } else { - if (list[1] == "transferInfo") { - respondGlobalTransferInfoJson(); - return; - } - } - } - } - if (list[0] == "command") { - const QString& command = list[1]; - if (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. - m_generator.setStatusLine(200, "OK"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - qApp->processEvents(); - // Exit application - qApp->exit(); - } else { - respondCommand(command); - m_generator.setStatusLine(200, "OK"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - } - return; - } - } + //So let's find = and the next comma + pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive); + int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive); - // Icons from theme - //qDebug() << "list[0]" << list[0]; - if (list[0] == "theme" && list.size() == 2) { -#ifdef DISABLE_GUI - url = ":/Icons/oxygen/"+list[1]+".png"; -#else - url = IconProvider::instance()->getIconPath(list[1]); -#endif - qDebug() << "There icon:" << url; - } else { - if (list[0] == "images") { - list[0] = "Icons"; - } else { - if (list.last().endsWith(".html")) - list.prepend("html"); - list.prepend("webui"); - } - url = ":/" + list.join("/"); - } - QFile file(url); - if (!file.open(QIODevice::ReadOnly)) { - qDebug("File %s was not found!", qPrintable(url)); - respondNotFound(); - return; - } - QString ext = list.last(); - int index = ext.lastIndexOf('.') + 1; - if (index > 0) - ext.remove(0, index); + QString value; + if (comma_pos == -1) + value = encoding.mid(pos + 1, comma_pos); else - ext.clear(); - QByteArray data = file.readAll(); - file.close(); - - // Translate the page - if (ext == "html" || (ext == "js" && !list.last().startsWith("excanvas"))) { - QString dataStr = QString::fromUtf8(data.constData()); - translateDocument(dataStr); - if (url.endsWith("about.html")) { - dataStr.replace("${VERSION}", VERSION); - } - data = dataStr.toUtf8(); - } - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt(ext); - m_generator.setMessage(data); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondNotFound() { - m_generator.setStatusLine(404, "File not found"); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} + value = encoding.mid(pos + 1, comma_pos - (pos + 1)); -void HttpConnection::respondTorrentsJson() { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(btjson::getTorrents()); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondGenPropertiesJson(const QString& hash) { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(btjson::getPropertiesForTorrent(hash)); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondTrackersPropertiesJson(const QString& hash) { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(btjson::getTrackersForTorrent(hash)); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondFilesPropertiesJson(const QString& hash) { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(btjson::getFilesForTorrent(hash)); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondPreferencesJson() { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(prefjson::getPreferences()); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} + if (value.toDouble() == 0.0) + return false; -void HttpConnection::respondGlobalTransferInfoJson() { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("js"); - m_generator.setMessage(btjson::getTransferInfo()); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); -} - -void HttpConnection::respondCommand(const QString& command) { - qDebug() << Q_FUNC_INFO << command; - if (command == "download") { - QString urls = m_parser.post("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); - } - if (url.startsWith("magnet:", Qt::CaseInsensitive)) { - emit MagnetReadyToBeDownloaded(url); - } else { - qDebug("Downloading url: %s", qPrintable(url)); - emit UrlReadyToBeDownloaded(url); - } - } - } - return; - } - - if (command == "addTrackers") { - QString hash = m_parser.post("hash"); - if (!hash.isEmpty()) { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid() && h.has_metadata()) { - QString urls = m_parser.post("urls"); - QStringList list = urls.split('\n'); - foreach (const QString& url, list) { - announce_entry e(url.toStdString()); - h.add_tracker(e); - } - } - } - return; - } - if (command == "upload") { - qDebug() << Q_FUNC_INFO << "upload"; - const QList& torrents = m_parser.torrents(); - foreach(const QByteArray& torrentContent, torrents) { - // Get a unique filename - QTemporaryFile *tmpfile = new QTemporaryFile(QDir::temp().absoluteFilePath("qBT-XXXXXX.torrent")); - tmpfile->setAutoRemove(false); - if (tmpfile->open()) { - QString filePath = tmpfile->fileName(); - tmpfile->write(torrentContent); - tmpfile->close(); - // XXX: tmpfile needs to be deleted on Windows before using the file - // or it will complain that the file is used by another process. - delete tmpfile; - emit torrentReadyToBeDownloaded(filePath, false, QString(), false); - // Clean up - fsutils::forceRemove(filePath); - } else { - std::cerr << "I/O Error: Could not create temporary file" << std::endl; - delete tmpfile; - return; - } - } - // Prepare response - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("html"); - m_generator.setMessage(QString("")); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - if (command == "resumeall") { - emit resumeAllTorrents(); - return; - } - if (command == "pauseall") { - emit pauseAllTorrents(); - return; - } - if (command == "resume") { - emit resumeTorrent(m_parser.post("hash")); - return; - } - if (command == "setPreferences") { - prefjson::setPreferences(m_parser.post("json")); - return; - } - if (command == "setFilePrio") { - QString hash = m_parser.post("hash"); - int file_id = m_parser.post("id").toInt(); - int priority = m_parser.post("priority").toInt(); - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid() && h.has_metadata()) { - h.file_priority(file_id, priority); - } - return; - } - if (command == "getGlobalUpLimit") { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("html"); - m_generator.setMessage(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit)); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - if (command == "getGlobalDlLimit") { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("html"); - m_generator.setMessage(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit)); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - return; - } - if (command == "getTorrentUpLimit") { - QString hash = m_parser.post("hash"); - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("html"); - m_generator.setMessage(QByteArray::number(h.upload_limit())); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - } - return; - } - if (command == "getTorrentDlLimit") { - QString hash = m_parser.post("hash"); - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) { - m_generator.setStatusLine(200, "OK"); - m_generator.setContentTypeByExt("html"); - m_generator.setMessage(QByteArray::number(h.download_limit())); - m_generator.setContentEncoding(m_parser.acceptsEncoding()); - write(); - } - return; - } - if (command == "setTorrentUpLimit") { - QString hash = m_parser.post("hash"); - qlonglong limit = m_parser.post("limit").toLongLong(); - if (limit == 0) limit = -1; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) { - h.set_upload_limit(limit); - } - return; - } - if (command == "setTorrentDlLimit") { - QString hash = m_parser.post("hash"); - qlonglong limit = m_parser.post("limit").toLongLong(); - if (limit == 0) limit = -1; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) { - h.set_download_limit(limit); - } - return; - } - if (command == "setGlobalUpLimit") { - qlonglong limit = m_parser.post("limit").toLongLong(); - if (limit == 0) limit = -1; - QBtSession::instance()->setUploadRateLimit(limit); - Preferences::instance()->setGlobalUploadLimit(limit/1024.); - return; - } - if (command == "setGlobalDlLimit") { - qlonglong limit = m_parser.post("limit").toLongLong(); - if (limit == 0) limit = -1; - QBtSession::instance()->setDownloadRateLimit(limit); - Preferences::instance()->setGlobalDownloadLimit(limit/1024.); - return; - } - if (command == "pause") { - emit pauseTorrent(m_parser.post("hash")); - return; - } - if (command == "delete") { - QStringList hashes = m_parser.post("hashes").split("|"); - foreach (const QString &hash, hashes) { - emit deleteTorrent(hash, false); - } - return; - } - if (command == "deletePerm") { - QStringList hashes = m_parser.post("hashes").split("|"); - foreach (const QString &hash, hashes) { - emit deleteTorrent(hash, true); - } - return; - } - if (command == "increasePrio") { - increaseTorrentsPriority(m_parser.post("hashes").split("|")); - return; - } - if (command == "decreasePrio") { - decreaseTorrentsPriority(m_parser.post("hashes").split("|")); - return; - } - if (command == "topPrio") { - foreach (const QString &hash, m_parser.post("hashes").split("|")) { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) h.queue_position_top(); - } - return; - } - if (command == "bottomPrio") { - foreach (const QString &hash, m_parser.post("hashes").split("|")) { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (h.is_valid()) h.queue_position_bottom(); - } - return; - } - if (command == "recheck") { - QBtSession::instance()->recheckTorrent(m_parser.post("hash")); - return; - } -} - -void HttpConnection::decreaseTorrentsPriority(const QStringList &hashes) { - qDebug() << Q_FUNC_INFO << hashes; - std::priority_queue, - std::vector >, - std::less > > 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& h) {} - torrent_queue.pop(); - } -} - -void HttpConnection::increaseTorrentsPriority(const QStringList &hashes) -{ - qDebug() << Q_FUNC_INFO << hashes; - std::priority_queue, - std::vector >, - std::greater > > 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& h) {} - torrent_queue.pop(); - } + return true; } diff --git a/src/webui/httpconnection.h b/src/webui/httpconnection.h index 1af5fcc04..e1a9ed82f 100644 --- a/src/webui/httpconnection.h +++ b/src/webui/httpconnection.h @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,17 +25,14 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ #ifndef HTTPCONNECTION_H #define HTTPCONNECTION_H -#include "httprequestparser.h" -#include "httpresponsegenerator.h" #include +#include "httptypes.h" class HttpServer; @@ -48,46 +46,18 @@ class HttpConnection : public QObject Q_DISABLE_COPY(HttpConnection) public: - HttpConnection(QTcpSocket *m_socket, HttpServer *m_httpserver); + HttpConnection(QTcpSocket* socket, HttpServer* httpserver); ~HttpConnection(); - void translateDocument(QString& data); - -protected slots: - void write(); - void respond(); - void respondTorrentsJson(); - void respondGenPropertiesJson(const QString& hash); - void respondTrackersPropertiesJson(const QString& hash); - void respondFilesPropertiesJson(const QString& hash); - void respondPreferencesJson(); - void respondGlobalTransferInfoJson(); - void respondCommand(const QString& command); - void respondNotFound(); - void processDownloadedFile(const QString& url, const QString& file_path); - void handleDownloadFailure(const QString& url, const QString& reason); - void decreaseTorrentsPriority(const QStringList& hashes); - void increaseTorrentsPriority(const QStringList& hashes); private slots: void read(); -signals: - void UrlReadyToBeDownloaded(const QString& url); - void MagnetReadyToBeDownloaded(const QString& uri); - void torrentReadyToBeDownloaded(const QString&, bool, const QString&, bool); - void deleteTorrent(const QString& hash, bool permanently); - void resumeTorrent(const QString& hash); - void pauseTorrent(const QString& hash); - void increasePrioTorrent(const QString& hash); - void decreasePrioTorrent(const QString& hash); - void resumeAllTorrents(); - void pauseAllTorrents(); - private: + void write(const HttpResponse& response); + + static bool acceptsGzipEncoding(const QString& encoding); + QTcpSocket *m_socket; - HttpServer *m_httpserver; - HttpRequestParser m_parser; - HttpResponseGenerator m_generator; QByteArray m_receivedData; }; diff --git a/src/webui/httpheader.cpp b/src/webui/httpheader.cpp deleted file mode 100644 index e681c25b1..000000000 --- a/src/webui/httpheader.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#include "httpheader.h" - -HttpHeader::HttpHeader() : m_valid(true) {} - -HttpHeader::HttpHeader(const QString &str) : m_valid(true) { - parse(str); -} - -HttpHeader::~HttpHeader() {} - -void HttpHeader::addValue(const QString &key, const QString &value) { - m_headers.insert(key.toLower(), value.trimmed()); -} - -QStringList HttpHeader::allValues(const QString &key) const { - QStringList list = m_headers.values(key); - return list; -} - -uint HttpHeader::contentLength() const { - QString lenVal = m_headers.value("content-length"); - return lenVal.toUInt(); -} - -QString HttpHeader::contentType() const { - QString str = m_headers.value("content-type"); - // content-type might have a parameter so we need to strip it. - // eg. application/x-www-form-urlencoded; charset=utf-8 - int index = str.indexOf(';'); - if (index == -1) - return str; - else - return str.left(index); -} - -bool HttpHeader::hasContentLength() const { - return m_headers.contains("content-length"); -} - -bool HttpHeader::hasContentType() const { - return m_headers.contains("content-type"); -} - -bool HttpHeader::hasKey(const QString &key) const { - return m_headers.contains(key.toLower()); -} - -bool HttpHeader::isValid() const { - return m_valid; -} - -QStringList HttpHeader::keys() const { - QStringList list = m_headers.keys(); - return list; -} - -void HttpHeader::removeAllValues(const QString &key) { - m_headers.remove(key); -} - -void HttpHeader::removeValue(const QString &key) { - m_headers.remove(key); -} - -void HttpHeader::setContentLength(int len) { - m_headers.replace("content-length", QString::number(len)); -} - -void HttpHeader::setContentType(const QString &type) { - m_headers.replace("content-type", type.trimmed()); -} - -void HttpHeader::setValue(const QString &key, const QString &value) { - m_headers.replace(key.toLower(), value.trimmed()); -} - -void HttpHeader::setValues(const QList > &values) { - for (int i=0; i < values.size(); ++i) { - setValue(values[i].first, values[i].second); - } -} - -QString HttpHeader::toString() const { - QString str; - typedef QMultiHash::const_iterator h_it; - - for (h_it it = m_headers.begin(), itend = m_headers.end(); - it != itend; ++it) { - str = str + it.key() + ": " + it.value() + "\r\n"; - } - - str += "\r\n"; - return str; -} - -QString HttpHeader::value(const QString &key) const { - return m_headers.value(key.toLower()); -} - -QList > HttpHeader::values() const { - QList > list; - typedef QMultiHash::const_iterator h_it; - - for (h_it it = m_headers.begin(), itend = m_headers.end(); - it != itend; ++it) { - list.append(qMakePair(it.key(), it.value())); - } - - return list; -} - -void HttpHeader::parse(const QString &str) { - QStringList headers = str.split("\r\n", QString::SkipEmptyParts, Qt::CaseInsensitive); - for (int i=0; i < headers.size(); ++i) { - int index = headers[i].indexOf(':', Qt::CaseInsensitive); - if (index == -1) { - setValid(false); - break; - } - - QString key = headers[i].left(index); - QString value = headers[i].right(headers[i].size() - index - 1); - m_headers.insert(key.toLower(), value.trimmed()); - } -} - -void HttpHeader::setValid(bool valid) { - m_valid = valid; - if (!m_valid) - m_headers.clear(); -} diff --git a/src/webui/httpheader.h b/src/webui/httpheader.h deleted file mode 100644 index 7a7ac8b2f..000000000 --- a/src/webui/httpheader.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#ifndef HTTPHEADER_H -#define HTTPHEADER_H - -#include -#include -#include -#include - -class HttpHeader { - -public: - HttpHeader(); - HttpHeader(const QString &str); - virtual ~HttpHeader(); - void addValue(const QString &key, const QString &value); - QStringList allValues(const QString &key) const; - uint contentLength() const; - QString contentType() const; - bool hasContentLength() const; - bool hasContentType() const; - bool hasKey(const QString &key) const; - bool isValid() const; - QStringList keys() const; - virtual int majorVersion() const =0; - virtual int minorVersion() const =0; - void removeAllValues(const QString &key); - void removeValue(const QString &key); - void setContentLength(int len); - void setContentType(const QString &type); - void setValue(const QString &key, const QString &value); - void setValues(const QList > &values); - virtual QString toString() const; - QString value(const QString &key) const; - QList > values() const; - -protected: - void parse(const QString &str); - void setValid(bool valid = true); - -private: - QMultiHash m_headers; - bool m_valid; -}; - -#endif // HTTPHEADER_H diff --git a/src/webui/httprequestheader.cpp b/src/webui/httprequestheader.cpp deleted file mode 100644 index f799ac8e3..000000000 --- a/src/webui/httprequestheader.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#include "httprequestheader.h" - - -HttpRequestHeader::HttpRequestHeader() : HttpHeader(), m_majorVersion(1), m_minorVersion(1) {} - -HttpRequestHeader::HttpRequestHeader(const QString &method, const QString &path, int majorVer, int minorVer) : - HttpHeader(), m_method(method), m_path(path), m_majorVersion(majorVer), m_minorVersion(minorVer) {} - -HttpRequestHeader::HttpRequestHeader(const QString &str): HttpHeader() { - int line = str.indexOf("\r\n", 0, Qt::CaseInsensitive); - QString req = str.left(line); - QString headers = str.right(str.size() - line - 2); //"\r\n" == 2 - QStringList method = req.split(" ", QString::SkipEmptyParts, Qt::CaseInsensitive); - if (method.size() != 3) - setValid(false); - else { - m_method = method[0]; - m_path = method[1]; - if (parseVersions(method[2])) - parse(headers); - else - setValid(false); - } -} - -int HttpRequestHeader::majorVersion() const { - return m_majorVersion; -} - -int HttpRequestHeader::minorVersion() const { - return m_minorVersion; -} - -QString HttpRequestHeader::method() const { - return m_method; -} - -QString HttpRequestHeader::path() const { - return m_path; -} - -void HttpRequestHeader::setRequest(const QString &method, const QString &path, int majorVer, int minorVer) { - m_method = method; - m_path = path; - m_majorVersion = majorVer; - m_minorVersion = minorVer; -} - -QString HttpRequestHeader::toString() const { - QString str = m_method + " "; - str+= m_path + " "; - str+= "HTTP/" + QString::number(m_majorVersion) + "." + QString::number(m_minorVersion) + "\r\n"; - str += HttpHeader::toString(); - return str; -} - -bool HttpRequestHeader::parseVersions(const QString &str) { - if (str.size() <= 5) // HTTP/ which means version missing - return false; - - if (str.left(4) != "HTTP") - return false; - - QString versions = str.right(str.size() - 5); // Strip "HTTP/" - - int decPoint = versions.indexOf('.'); - if (decPoint == -1) - return false; - - bool ok; - - m_majorVersion = versions.left(decPoint).toInt(&ok); - if (!ok) - return false; - - m_minorVersion = versions.right(versions.size() - decPoint - 1).toInt(&ok); - if (!ok) - return false; - - return true; - -} diff --git a/src/webui/httprequestheader.h b/src/webui/httprequestheader.h deleted file mode 100644 index 0723eca60..000000000 --- a/src/webui/httprequestheader.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#ifndef HTTPREQUESTHEADER_H -#define HTTPREQUESTHEADER_H - -#include "httpheader.h" - -class HttpRequestHeader: public HttpHeader { -public: - HttpRequestHeader(); - // The path argument must be properly encoded for an HTTP request. - HttpRequestHeader(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); - HttpRequestHeader(const QString &str); - virtual int majorVersion() const; - virtual int minorVersion() const; - QString method() const; - // This is the raw path from the header. No decoding/encoding is performed. - QString path() const; - // The path argument must be properly encoded for an HTTP request. - void setRequest(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); - virtual QString toString() const; - -private: - bool parseVersions(const QString &str); - QString m_method; - QString m_path; - int m_majorVersion; - int m_minorVersion; -}; - -#endif // HTTPREQUESTHEADER_H diff --git a/src/webui/httprequestparser.cpp b/src/webui/httprequestparser.cpp index 4f185c7b3..262e93123 100644 --- a/src/webui/httprequestparser.cpp +++ b/src/webui/httprequestparser.cpp @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,110 +25,231 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ - -#include "httprequestparser.h" +#include #include +//#include #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include #endif +#include +#include #include +#include "httprequestparser.h" + +const QByteArray EOL("\r\n"); +const QByteArray EOH("\r\n\r\n"); -HttpRequestParser::HttpRequestParser(): m_error(false) +inline QString unquoted(const QString& str) { + if ((str[0] == '\"') && (str[str.length() - 1] == '\"')) + return str.mid(1, str.length() - 2); + + return str; } -HttpRequestParser::~HttpRequestParser() +HttpRequestParser::ErrorCode HttpRequestParser::parse(const QByteArray& data, HttpRequest& request, uint maxContentLength) { + return HttpRequestParser(maxContentLength).parseHttpRequest(data, request); } -bool HttpRequestParser::isError() const { - return m_error; +HttpRequestParser::HttpRequestParser(uint maxContentLength) + : maxContentLength_(maxContentLength) +{ } -const QString& HttpRequestParser::url() const { - return m_path; -} +HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArray& data, HttpRequest& request) +{ + request_ = HttpRequest(); -const QByteArray& HttpRequestParser::message() const { - return m_data; -} + // Parse HTTP request header + const int header_end = data.indexOf(EOH); + if (header_end < 0) + { + qDebug() << Q_FUNC_INFO << "incomplete request"; + return IncompleteRequest; + } -QString HttpRequestParser::get(const QString& key) const { - return m_getMap.value(key); -} + if (!parseHttpHeader(data.left(header_end))) + { + qWarning() << Q_FUNC_INFO << "header parsing error"; + return BadRequest; + } -QString HttpRequestParser::post(const QString& key) const { - return m_postMap.value(key); -} + // Parse HTTP request message + int content_length = 0; + if (request_.headers.contains("content-length")) + { + content_length = request_.headers["content-length"].toInt(); + if (content_length > static_cast(maxContentLength_)) + { + qWarning() << Q_FUNC_INFO << "bad request: message too long"; + return BadRequest; + } -const QList& HttpRequestParser::torrents() const { - return m_torrents; + QByteArray content = data.mid(header_end + EOH.length(), content_length); + if (content.length() < content_length) + { + qDebug() << Q_FUNC_INFO << "incomplete request"; + return IncompleteRequest; + } + + if (!parseContent(content)) + { + qWarning() << Q_FUNC_INFO << "message parsing error"; + return BadRequest; + } + } + +// qDebug() << Q_FUNC_INFO; +// qDebug() << "HTTP Request header:"; +// qDebug() << data.left(header_end) << "\n"; + + request = request_; + return NoError; } -void HttpRequestParser::writeHeader(const QByteArray& ba) { - m_error = false; - // Parse header - m_header = HttpRequestHeader(ba); - QUrl url = QUrl::fromEncoded(m_header.path().toLatin1()); - m_path = url.path(); +bool HttpRequestParser::parseStartingLine(const QString &line) +{ + const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$"); - // Parse GET parameters -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) - QUrlQuery query(url); - QListIterator > i(query.queryItems()); + if (rx.indexIn(line.trimmed()) >= 0) + { + request_.method = rx.cap(1); + + QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1()); + request_.path = url.path(); // Path + + // Parse GET parameters +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) + QListIterator > i(url.queryItems()); #else - QListIterator > i(url.queryItems()); + QListIterator > i(QUrlQuery(url).queryItems()); #endif - while (i.hasNext()) { - QPair pair = i.next(); - m_getMap[pair.first] = pair.second; + while (i.hasNext()) + { + QPair pair = i.next(); + request_.gets[pair.first] = pair.second; + } + + return true; + } + + qWarning() << Q_FUNC_INFO << "invalid http header:" << line; + return false; +} + +bool HttpRequestParser::parseHeaderLine(const QString &line, QPair& out) +{ + int i = line.indexOf(QLatin1Char(':')); + if (i == -1) + { + qWarning() << Q_FUNC_INFO << "invalid http header:" << line; + return false; + } + + out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed()); + return true; +} + +bool HttpRequestParser::parseHttpHeader(const QByteArray &data) +{ + QString str = QString::fromUtf8(data); + QStringList lines = str.trimmed().split(EOL); + + QStringList headerLines; + foreach (const QString& line, lines) + { + if (line[0].isSpace()) // header line continuation + { + if (!headerLines.isEmpty()) // really continuation + { + headerLines.last() += QLatin1Char(' '); + headerLines.last() += line.trimmed(); + } + } + else + { + headerLines.append(line); + } + } + + if (headerLines.isEmpty()) + return false; // Empty header + + QStringList::Iterator it = headerLines.begin(); + if (!parseStartingLine(*it)) + return false; + + ++it; + for (; it != headerLines.end(); ++it) + { + QPair header; + if (!parseHeaderLine(*it, header)) + return false; + + request_.headers[header.first] = header.second; } + + return true; } -static QList splitRawData(QByteArray rawData, const QByteArray& sep) +QList HttpRequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary) { QList ret; + QByteArray sep = boundary + EOL; const int sepLength = sep.size(); - int index = 0; - while ((index = rawData.indexOf(sep)) >= 0) { - ret << rawData.left(index); - rawData = rawData.mid(index + sepLength); + + int start = 0, end = 0; + if ((end = data.indexOf(sep, start)) >= 0) + { + start = end + sepLength; // skip first boundary + + while ((end = data.indexOf(sep, start)) >= 0) + { + ret << data.mid(start, end - start); + start = end + sepLength; + } + + // last or single part + sep = boundary + "--" + EOL; + if ((end = data.indexOf(sep, start)) >= 0) + ret << data.mid(start, end - start); } + return ret; } -void HttpRequestParser::writeMessage(const QByteArray& ba) { +bool HttpRequestParser::parseContent(const QByteArray& data) +{ // Parse message content - Q_ASSERT (m_header.hasContentLength()); - m_error = false; - m_data = ba; - qDebug() << Q_FUNC_INFO << "m_data.size(): " << m_data.size(); + qDebug() << Q_FUNC_INFO << "Content-Length: " << request_.headers["content-length"]; + qDebug() << Q_FUNC_INFO << "data.size(): " << data.size(); - // Parse POST data - if (m_header.contentType() == "application/x-www-form-urlencoded") { + // Parse url-encoded POST data + if (request_.headers["content-type"].startsWith("application/x-www-form-urlencoded")) + { QUrl url; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) - QString tmp(m_data); - QUrlQuery query(tmp); - QListIterator > i(query.queryItems(QUrl::FullyDecoded)); -#else - url.setEncodedQuery(m_data); +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) + url.setEncodedQuery(data); QListIterator > i(url.queryItems()); +#else + url.setQuery(data); + QListIterator > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded)); #endif - while (i.hasNext()) { + while (i.hasNext()) + { QPair pair = i.next(); - m_postMap[pair.first] = pair.second; + request_.posts[pair.first] = pair.second; } - return; + + return true; } // Parse multipart/form data (torrent file) /** - m_data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5") + data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5") --cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5 Content-Disposition: form-data; name=\"Filename\" @@ -144,58 +266,108 @@ Content-Disposition: form-data; name=\"Upload\" Submit Query --cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5-- **/ - if (m_header.contentType().startsWith("multipart/form-data")) { - qDebug() << Q_FUNC_INFO << "header is: " << m_header.toString(); - static QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\""); - static QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)"); + QString content_type = request_.headers["content-type"]; + if (content_type.startsWith("multipart/form-data")) + { + const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\""); + const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)"); + QByteArray boundary; - if (boundaryRegexQuoted.indexIn(m_header.toString()) < 0) { - if (boundaryRegexNotQuoted.indexIn(m_header.toString()) < 0) { + if (boundaryRegexQuoted.indexIn(content_type) < 0) + { + if (boundaryRegexNotQuoted.indexIn(content_type) < 0) + { qWarning() << "Could not find boundary in multipart/form-data header!"; - m_error = true; - return; - } else { + return false; + } + else + { boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1(); } - } else { + } + else + { boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1(); } + qDebug() << "Boundary is " << boundary; - QList parts = splitRawData(m_data, boundary); + QList parts = splitMultipartData(data, boundary); qDebug() << parts.size() << "parts in data"; - foreach (const QByteArray& part, parts) { - const int filenameIndex = part.indexOf("filename="); - if (filenameIndex < 0) - continue; - qDebug() << "Found a torrent"; - m_torrents << part.mid(part.indexOf("\r\n\r\n", filenameIndex + 9) + 4); + + foreach (const QByteArray& part, parts) + { + if (!parseFormData(part)) + return false; } + + return true; } -} -bool HttpRequestParser::acceptsEncoding() { - QString encoding = m_header.value("Accept-Encoding"); + qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type); + return false; +} - int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive); - if (pos == -1) +bool HttpRequestParser::parseFormData(const QByteArray& data) +{ + // Parse form data header + const int header_end = data.indexOf(EOH); + if (header_end < 0) + { + qDebug() << "Invalid form data: \n" << data; return false; + } - // Let's see if there's a qvalue of 0.0 following - if (encoding[pos+4] != ';') //there isn't, so it accepts gzip anyway - return true; - - //So let's find = and the next comma - pos = encoding.indexOf("=", pos+4, Qt::CaseInsensitive); - int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive); + QString header_str = QString::fromUtf8(data.left(header_end)); + QStringList lines = header_str.trimmed().split(EOL); + QStringMap headers; + foreach (const QString& line, lines) + { + QPair header; + if (!parseHeaderLine(line, header)) + return false; - QString value; - if (comma_pos == -1) - value = encoding.mid(pos+1, comma_pos); - else - value = encoding.mid(pos+1, comma_pos-(pos+1)); + headers[header.first] = header.second; + } - if (value.toDouble() == 0.0) + QStringMap disposition; + if (!headers.contains("content-disposition") || + !parseHeaderValue(headers["content-disposition"], disposition) || + !disposition.contains("name")) + { + qDebug() << "Invalid form data header: \n" << header_str; return false; + } + + if (disposition.contains("filename")) + { + UploadedFile ufile; + ufile.filename = disposition["filename"]; + ufile.type = disposition["content-type"]; + ufile.data = data.mid(header_end + EOH.length()); + + request_.files[disposition["name"]] = ufile; + } else - return true; + { + request_.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length())); + } + + return true; +} + +bool HttpRequestParser::parseHeaderValue(const QString& value, QStringMap& out) +{ + QStringList items = value.split(QLatin1Char(';')); + out[""] = items[0]; + + for (QStringList::size_type i = 1; i < items.size(); ++i) + { + int pos = items[i].indexOf("="); + if (pos < 0) + return false; + + out[items[i].left(pos).trimmed()] = unquoted(items[i].mid(pos + 1).trimmed()); + } + + return true; } diff --git a/src/webui/httprequestparser.h b/src/webui/httprequestparser.h index 180f3c1cf..55a21be36 100644 --- a/src/webui/httprequestparser.h +++ b/src/webui/httprequestparser.h @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,41 +25,38 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ - #ifndef HTTPREQUESTPARSER_H #define HTTPREQUESTPARSER_H -#include -#include "httprequestheader.h" - -class HttpRequestParser { +#include "httptypes.h" +class HttpRequestParser +{ public: - HttpRequestParser(); - ~HttpRequestParser(); - bool isError() const; - const QString& url() const; - const QByteArray& message() const; - QString get(const QString& key) const; - QString post(const QString& key) const; - const QList& torrents() const; - void writeHeader(const QByteArray& ba); - void writeMessage(const QByteArray& ba); - bool acceptsEncoding(); - inline const HttpRequestHeader& header() const { return m_header; } + enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest }; + + // when result != NoError parsed request is undefined + // Warning! Header names are converted to lower-case. + static ErrorCode parse(const QByteArray& data, HttpRequest& request, uint maxContentLength = 10000000 /* ~10MB */); private: - HttpRequestHeader m_header; - bool m_error; - QByteArray m_data; - QString m_path; - QHash m_postMap; - QHash m_getMap; - QList m_torrents; + HttpRequestParser(uint maxContentLength); + + ErrorCode parseHttpRequest(const QByteArray& data, HttpRequest& request); + + bool parseHttpHeader(const QByteArray& data); + bool parseStartingLine(const QString &line); + bool parseContent(const QByteArray& data); + bool parseFormData(const QByteArray& data); + QList splitMultipartData(const QByteArray& data, const QByteArray& boundary); + + static bool parseHeaderLine(const QString& line, QPair& out); + static bool parseHeaderValue(const QString& value, QStringMap& out); + + const uint maxContentLength_; + HttpRequest request_; }; #endif diff --git a/src/webui/httpresponsegenerator.cpp b/src/webui/httpresponsegenerator.cpp index f5ace37fe..9ebb14d41 100644 --- a/src/webui/httpresponsegenerator.cpp +++ b/src/webui/httpresponsegenerator.cpp @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,48 +25,52 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ - -#include "httpresponsegenerator.h" #include +#include "httpresponsegenerator.h" -void HttpResponseGenerator::setMessage(const QByteArray& message) -{ - m_message = message; -} +bool gCompress(QByteArray data, QByteArray& dest_buffer); -void HttpResponseGenerator::setMessage(const QString& message) +QByteArray HttpResponseGenerator::generate(HttpResponse response) { - setMessage(message.toUtf8()); -} + if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") + { + // A gzip seems to have 23 bytes overhead. + // Also "Content-Encoding: gzip\r\n" is 26 bytes long + // So we only benefit from gzip if the message is bigger than 23+26 = 49 + // If the message is smaller than 49 bytes we actually send MORE data if we gzip + QByteArray dest_buf; + if ((response.content.size() > 49) && (gCompress(response.content, dest_buf))) + { + response.content = dest_buf; + } + else + { + response.headers.remove(HEADER_CONTENT_ENCODING); + } + } + + if (response.content.length() > 0) + response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length()); + + QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n")); + + QString header; + foreach (const QString& key, response.headers.keys()) + header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]); -void HttpResponseGenerator::setContentTypeByExt(const QString& ext) { - if (ext == "css") { - setContentType("text/css"); - return; - } - if (ext == "gif") { - setContentType("image/gif"); - return; - } - if (ext == "htm" || ext == "html") { - setContentType("text/html"); - return; - } - if (ext == "js") { - setContentType("text/javascript"); - return; - } - if (ext == "png") { - setContentType("image/png"); - return; - } + ret = ret.arg(response.status.code).arg(response.status.text).arg(header); + +// qDebug() << Q_FUNC_INFO; +// qDebug() << "HTTP Response header:"; +// qDebug() << ret; + + return ret.toUtf8() + response.content; } -bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { +bool gCompress(QByteArray data, QByteArray& dest_buffer) +{ static const int BUFSIZE = 128 * 1024; char tmp_buf[BUFSIZE]; int ret; @@ -74,8 +79,8 @@ bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; - strm.next_in = reinterpret_cast(m_message.data()); - strm.avail_in = m_message.length(); + strm.next_in = reinterpret_cast(data.data()); + strm.avail_in = data.length(); strm.next_out = reinterpret_cast(tmp_buf); strm.avail_out = BUFSIZE; @@ -88,53 +93,37 @@ bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { return false; while (strm.avail_in != 0) - { + { ret = deflate(&strm, Z_NO_FLUSH); if (ret != Z_OK) return false; + if (strm.avail_out == 0) { - dest_buffer.append(tmp_buf, BUFSIZE); - strm.next_out = reinterpret_cast(tmp_buf); - strm.avail_out = BUFSIZE; + dest_buffer.append(tmp_buf, BUFSIZE); + strm.next_out = reinterpret_cast(tmp_buf); + strm.avail_out = BUFSIZE; } - } + } int deflate_res = Z_OK; - while (deflate_res == Z_OK) { - if (strm.avail_out == 0) { + while (deflate_res == Z_OK) + { + if (strm.avail_out == 0) + { dest_buffer.append(tmp_buf, BUFSIZE); strm.next_out = reinterpret_cast(tmp_buf); strm.avail_out = BUFSIZE; } + deflate_res = deflate(&strm, Z_FINISH); } if (deflate_res != Z_STREAM_END) return false; + dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out); deflateEnd(&strm); return true; } - -QByteArray HttpResponseGenerator::toByteArray() { - // A gzip seems to have 23 bytes overhead. - // Also "content-encoding: gzip\r\n" is 26 bytes long - // So we only benefit from gzip if the message is bigger than 23+26 = 49 - // If the message is smaller than 49 bytes we actually send MORE data if we gzip - if (m_gzip && m_message.size() > 49) { - QByteArray dest_buf; - if (gCompress(dest_buf)) { - setValue("content-encoding", "gzip"); -#if QT_VERSION < 0x040800 - m_message = dest_buf; -#else - m_message.swap(dest_buf); -#endif - } - } - - setContentLength(m_message.size()); - return HttpResponseHeader::toString().toUtf8() + m_message; -} diff --git a/src/webui/httpresponsegenerator.h b/src/webui/httpresponsegenerator.h index 4d8525a26..057016589 100644 --- a/src/webui/httpresponsegenerator.h +++ b/src/webui/httpresponsegenerator.h @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,32 +25,18 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ #ifndef HTTPRESPONSEGENERATOR_H #define HTTPRESPONSEGENERATOR_H -#include "httpresponseheader.h" +#include "httptypes.h" -class HttpResponseGenerator : public HttpResponseHeader +class HttpResponseGenerator { - public: - HttpResponseGenerator(): m_gzip(false) {} - void setMessage(const QByteArray& message); - void setMessage(const QString& message); - void setContentTypeByExt(const QString& ext); - void setContentEncoding(bool gzip) { m_gzip = gzip; } - QByteArray toByteArray(); - -private: - bool gCompress(QByteArray &dest_buffer); - QByteArray m_message; - bool m_gzip; - + static QByteArray generate(HttpResponse response); }; #endif diff --git a/src/webui/httpresponseheader.cpp b/src/webui/httpresponseheader.cpp deleted file mode 100644 index f95abc20d..000000000 --- a/src/webui/httpresponseheader.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#include "httpresponseheader.h" - -HttpResponseHeader::HttpResponseHeader(): HttpHeader(), m_code(200), m_majorVersion(1), m_minorVersion(1) {} - -HttpResponseHeader::HttpResponseHeader(int code, const QString &text, int majorVer, int minorVer): - HttpHeader(), m_code(code), m_text(text), m_majorVersion(majorVer), m_minorVersion(minorVer) {} - -HttpResponseHeader::HttpResponseHeader(const QString &str): HttpHeader() { - int line = str.indexOf("\r\n", 0, Qt::CaseInsensitive); - QString res = str.left(line); - QString headers = str.right(str.size() - line - 2); //"\r\n" == 2 - QStringList status = res.split(" ", QString::SkipEmptyParts, Qt::CaseInsensitive); - if (status.size() != 3 || status.size() != 2) //reason-phrase could be empty - setValid(false); - else { - if (parseVersions(status[0])) { - bool ok; - m_code = status[1].toInt(&ok); - if (ok) { - m_text = status[2]; - parse(headers); - } - else - setValid(false); - } - else - setValid(false); - } -} - -int HttpResponseHeader::majorVersion() const { - return m_majorVersion; -} - -int HttpResponseHeader::minorVersion() const { - return m_minorVersion; -} - -QString HttpResponseHeader::reasonPhrase() const { - return m_text; -} - -void HttpResponseHeader::setStatusLine(int code, const QString &text, int majorVer, int minorVer) { - m_code = code; - m_text = text; - m_majorVersion = majorVer; - m_minorVersion = minorVer; -} - -int HttpResponseHeader::statusCode() const { - return m_code; -} - -QString HttpResponseHeader::toString() const { - QString str = "HTTP/" + QString::number(m_majorVersion) + "." + QString::number(m_minorVersion) + " "; - - QString code = QString::number(m_code); - if (code.size() > 3) { - str+= code.left(3) + " "; - } - else if (code.size() < 3) { - int padding = 3 - code.size(); - for (int i = 0; i < padding; ++i) - code.push_back("0"); - str += code + " "; - } - else { - str += code + " "; - } - - str += m_text + "\r\n"; - str += HttpHeader::toString(); - return str; -} - -bool HttpResponseHeader::parseVersions(const QString &str) { - if (str.size() <= 5) // HTTP/ which means version missing - return false; - - if (str.left(4) != "HTTP") - return false; - - QString versions = str.right(str.size() - 5); // Strip "HTTP/" - - int decPoint = versions.indexOf('.'); - if (decPoint == -1) - return false; - - bool ok; - - m_majorVersion = versions.left(decPoint).toInt(&ok); - if (!ok) - return false; - - m_minorVersion = versions.right(versions.size() - decPoint - 1).toInt(&ok); - if (!ok) - return false; - - return true; - -} diff --git a/src/webui/httpresponseheader.h b/src/webui/httpresponseheader.h deleted file mode 100644 index 4a54ef5a9..000000000 --- a/src/webui/httpresponseheader.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : hammered999@gmail.com - */ - -#ifndef HTTPRESPONSEHEADER_H -#define HTTPRESPONSEHEADER_H - -#include "httpheader.h" - -class HttpResponseHeader: public HttpHeader { -public: - HttpResponseHeader(); - HttpResponseHeader(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); - HttpResponseHeader(const QString &str); - virtual int majorVersion() const; - virtual int minorVersion() const; - QString reasonPhrase() const; - void setStatusLine(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); - int statusCode() const; - virtual QString toString() const; - -private: - bool parseVersions(const QString &str); - int m_code; - QString m_text; - int m_majorVersion; - int m_minorVersion; -}; - -#endif // HTTPRESPONSEHEADER_H diff --git a/src/webui/httpserver.cpp b/src/webui/httpserver.cpp index 7aadb8905..c37c5301c 100644 --- a/src/webui/httpserver.cpp +++ b/src/webui/httpserver.cpp @@ -1,6 +1,8 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2006 Ishan Arora and Christophe Dumez + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * Copyright (C) 2006 Ishan Arora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,132 +26,38 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ - -#include "httpserver.h" -#include "httpconnection.h" -#include "qbtsession.h" -#include -#include -#include -#include - #ifndef QT_NO_OPENSSL #include #else #include #endif +#include "httpconnection.h" +#include "httpserver.h" -using namespace libtorrent; - -const int BAN_TIME = 3600000; // 1 hour - -class UnbanTimer: public QTimer { -public: - UnbanTimer(const QString& peer_ip, QObject *parent): QTimer(parent), - m_peerIp(peer_ip) { - setSingleShot(true); - setInterval(BAN_TIME); - } - - inline const QString& peerIp() const { return m_peerIp; } - -private: - QString m_peerIp; -}; - -void HttpServer::UnbanTimerEvent() { - UnbanTimer* ubantimer = static_cast(sender()); - qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp())); - m_clientFailedAttempts.remove(ubantimer->peerIp()); - ubantimer->deleteLater(); -} - -int HttpServer::NbFailedAttemptsForIp(const QString& ip) const { - return m_clientFailedAttempts.value(ip, 0); -} - -void HttpServer::increaseNbFailedAttemptsForIp(const QString& ip) { - const int nb_fail = m_clientFailedAttempts.value(ip, 0) + 1; - m_clientFailedAttempts.insert(ip, nb_fail); - if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS) { - // Max number of failed attempts reached - // Start ban period - UnbanTimer* ubantimer = new UnbanTimer(ip, this); - connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent())); - ubantimer->start(); - } -} - -void HttpServer::resetNbFailedAttemptsForIp(const QString& ip) { - m_clientFailedAttempts.remove(ip); -} - -HttpServer::HttpServer(QObject* parent) : QTcpServer(parent) -{ - - const Preferences* const pref = Preferences::instance(); - - m_username = pref->getWebUiUsername().toUtf8(); - m_passwordSha1 = pref->getWebUiPassword().toUtf8(); - m_localAuthEnabled = pref->isWebUiLocalAuthEnabled(); - - // HTTPS-related +HttpServer::HttpServer(QObject* parent) + : QTcpServer(parent) #ifndef QT_NO_OPENSSL - m_https = pref->isWebUiHttpsEnabled(); - if (m_https) { - m_certificate = QSslCertificate(pref->getWebUiHttpsCertificate()); - m_key = QSslKey(pref->getWebUiHttpsKey(), QSsl::Rsa); - } + , m_https(false) #endif - - // Additional translations for Web UI - QString a = tr("File"); - a = tr("Edit"); - a = tr("Help"); - a = tr("Download Torrents from their URL or Magnet link"); - a = tr("Only one link per line"); - a = tr("Download local torrent"); - a = tr("Torrent files were correctly added to download list."); - a = tr("Point to torrent file"); - a = tr("Download"); - a = tr("Are you sure you want to delete the selected torrents from the transfer list and hard disk?"); - a = tr("Download rate limit must be greater than 0 or disabled."); - a = tr("Upload rate limit must be greater than 0 or disabled."); - a = tr("Maximum number of connections limit must be greater than 0 or disabled."); - a = tr("Maximum number of connections per torrent limit must be greater than 0 or disabled."); - a = tr("Maximum number of upload slots per torrent limit must be greater than 0 or disabled."); - a = tr("Unable to save program preferences, qBittorrent is probably unreachable."); - a = tr("Language"); - a = tr("Downloaded", "Is the file downloaded or not?"); - a = tr("The port used for incoming connections must be greater than 1024 and less than 65535."); - a = tr("The port used for the Web UI must be greater than 1024 and less than 65535."); - a = tr("The Web UI username must be at least 3 characters long."); - a = tr("The Web UI password must be at least 3 characters long."); - a = tr("Save"); - a = tr("qBittorrent client is not reachable"); - a = tr("HTTP Server"); - a = tr("The following parameters are supported:"); - a = tr("Torrent path"); - a = tr("Torrent name"); - a = tr("qBittorrent has been shutdown."); +{ } -HttpServer::~HttpServer() { +HttpServer::~HttpServer() +{ } #ifndef QT_NO_OPENSSL -void HttpServer::enableHttps(const QSslCertificate &certificate, - const QSslKey &key) { +void HttpServer::enableHttps(const QSslCertificate &certificate, const QSslKey &key) +{ m_certificate = certificate; m_key = key; m_https = true; } -void HttpServer::disableHttps() { +void HttpServer::disableHttps() +{ m_https = false; m_certificate.clear(); m_key.clear(); @@ -169,143 +77,21 @@ void HttpServer::incomingConnection(int socketDescriptor) else #endif serverSocket = new QTcpSocket(this); - if (serverSocket->setSocketDescriptor(socketDescriptor)) { + if (serverSocket->setSocketDescriptor(socketDescriptor)) + { #ifndef QT_NO_OPENSSL - if (m_https) { + if (m_https) + { static_cast(serverSocket)->setProtocol(QSsl::AnyProtocol); static_cast(serverSocket)->setPrivateKey(m_key); static_cast(serverSocket)->setLocalCertificate(m_certificate); static_cast(serverSocket)->startServerEncryption(); } #endif - handleNewConnection(serverSocket); - } else { - serverSocket->deleteLater(); - } -} - -void HttpServer::handleNewConnection(QTcpSocket *socket) -{ - HttpConnection *connection = new HttpConnection(socket, this); - //connect connection to QBtSession::instance() - connect(connection, SIGNAL(UrlReadyToBeDownloaded(QString)), QBtSession::instance(), SLOT(downloadUrlAndSkipDialog(QString))); - connect(connection, SIGNAL(MagnetReadyToBeDownloaded(QString)), QBtSession::instance(), SLOT(addMagnetSkipAddDlg(QString))); - connect(connection, SIGNAL(torrentReadyToBeDownloaded(QString, bool, QString, bool)), QBtSession::instance(), SLOT(addTorrent(QString, bool, QString, bool))); - connect(connection, SIGNAL(deleteTorrent(QString, bool)), QBtSession::instance(), SLOT(deleteTorrent(QString, bool))); - connect(connection, SIGNAL(pauseTorrent(QString)), QBtSession::instance(), SLOT(pauseTorrent(QString))); - connect(connection, SIGNAL(resumeTorrent(QString)), QBtSession::instance(), SLOT(resumeTorrent(QString))); - connect(connection, SIGNAL(pauseAllTorrents()), QBtSession::instance(), SLOT(pauseAllTorrents())); - connect(connection, SIGNAL(resumeAllTorrents()), QBtSession::instance(), SLOT(resumeAllTorrents())); -} - -QString HttpServer::generateNonce() const { - QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData(QTime::currentTime().toString("hhmmsszzz").toUtf8()); - md5.addData(":"); - md5.addData(QBT_REALM); - return md5.result().toHex(); -} - -void HttpServer::setAuthorization(const QString& username, - const QString& password_sha1) { - m_username = username.toUtf8(); - m_passwordSha1 = password_sha1.toUtf8(); -} - -// Parse HTTP AUTH string -// http://tools.ietf.org/html/rfc2617 -bool HttpServer::isAuthorized(const QByteArray& auth, - const QString& method) const { - //qDebug("AUTH string is %s", auth.data()); - // Get user name - QRegExp regex_user(".*username=\"([^\"]+)\".*"); // Must be a quoted string - if (regex_user.indexIn(auth) < 0) return false; - QString prop_user = regex_user.cap(1); - //qDebug("AUTH: Proposed username is %s, real username is %s", qPrintable(prop_user), username.data()); - if (prop_user != m_username) { - // User name is invalid, we can reject already - qDebug("AUTH-PROB: Username is invalid"); - return false; + new HttpConnection(serverSocket, this); } - // Get realm - QRegExp regex_realm(".*realm=\"([^\"]+)\".*"); // Must be a quoted string - if (regex_realm.indexIn(auth) < 0) { - qDebug("AUTH-PROB: Missing realm"); - return false; - } - QByteArray prop_realm = regex_realm.cap(1).toUtf8(); - if (prop_realm != QBT_REALM) { - qDebug("AUTH-PROB: Wrong realm"); - return false; - } - // get nonce - QRegExp regex_nonce(".*nonce=[\"]?([\\w=]+)[\"]?.*"); - if (regex_nonce.indexIn(auth) < 0) { - qDebug("AUTH-PROB: missing nonce"); - return false; - } - QByteArray prop_nonce = regex_nonce.cap(1).toUtf8(); - //qDebug("prop nonce is: %s", prop_nonce.data()); - // get uri - QRegExp regex_uri(".*uri=\"([^\"]+)\".*"); - if (regex_uri.indexIn(auth) < 0) { - qDebug("AUTH-PROB: Missing uri"); - return false; - } - QByteArray prop_uri = regex_uri.cap(1).toUtf8(); - //qDebug("prop uri is: %s", prop_uri.data()); - // get response - QRegExp regex_response(".*response=[\"]?([\\w=]+)[\"]?.*"); - if (regex_response.indexIn(auth) < 0) { - qDebug("AUTH-PROB: Missing response"); - return false; - } - QByteArray prop_response = regex_response.cap(1).toUtf8(); - //qDebug("prop response is: %s", prop_response.data()); - // Compute correct reponse - QCryptographicHash md5_ha2(QCryptographicHash::Md5); - md5_ha2.addData(method.toUtf8() + ":" + prop_uri); - QByteArray ha2 = md5_ha2.result().toHex(); - QByteArray response = ""; - if (auth.contains("qop=")) { - QCryptographicHash md5_ha(QCryptographicHash::Md5); - // Get nc - QRegExp regex_nc(".*nc=[\"]?([\\w=]+)[\"]?.*"); - if (regex_nc.indexIn(auth) < 0) { - qDebug("AUTH-PROB: qop but missing nc"); - return false; - } - QByteArray prop_nc = regex_nc.cap(1).toUtf8(); - //qDebug("prop nc is: %s", prop_nc.data()); - QRegExp regex_cnonce(".*cnonce=[\"]?([\\w=]+)[\"]?.*"); - if (regex_cnonce.indexIn(auth) < 0) { - qDebug("AUTH-PROB: qop but missing cnonce"); - return false; - } - QByteArray prop_cnonce = regex_cnonce.cap(1).toUtf8(); - //qDebug("prop cnonce is: %s", prop_cnonce.data()); - QRegExp regex_qop(".*qop=[\"]?(\\w+)[\"]?.*"); - if (regex_qop.indexIn(auth) < 0) { - qDebug("AUTH-PROB: missing qop"); - return false; - } - QByteArray prop_qop = regex_qop.cap(1).toUtf8(); - //qDebug("prop qop is: %s", prop_qop.data()); - md5_ha.addData(m_passwordSha1+":"+prop_nonce+":"+prop_nc+":"+prop_cnonce+":"+prop_qop+":"+ha2); - response = md5_ha.result().toHex(); - } else { - QCryptographicHash md5_ha(QCryptographicHash::Md5); - md5_ha.addData(m_passwordSha1+":"+prop_nonce+":"+ha2); - response = md5_ha.result().toHex(); + else + { + serverSocket->deleteLater(); } - //qDebug("AUTH: comparing reponses: (%d)", static_cast(prop_response == response)); - return prop_response == response; -} - -void HttpServer::setlocalAuthEnabled(bool enabled) { - m_localAuthEnabled = enabled; -} - -bool HttpServer::isLocalAuthEnabled() const { - return m_localAuthEnabled; } diff --git a/src/webui/httpserver.h b/src/webui/httpserver.h index d596d090f..170cceec8 100644 --- a/src/webui/httpserver.h +++ b/src/webui/httpserver.h @@ -1,6 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2006 Ishan Arora and Christophe Dumez + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,50 +25,26 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : chris@qbittorrent.org */ #ifndef HTTPSERVER_H #define HTTPSERVER_H -#include #include -#include -#include -#include - #ifndef QT_NO_OPENSSL #include #include #endif -#include "preferences.h" - -class EventManager; - -QT_BEGIN_NAMESPACE -class QTimer; -QT_END_NAMESPACE - -const int MAX_AUTH_FAILED_ATTEMPTS = 5; - -class HttpServer : public QTcpServer { +class HttpServer : public QTcpServer +{ Q_OBJECT Q_DISABLE_COPY(HttpServer) public: HttpServer(QObject* parent = 0); ~HttpServer(); - void setAuthorization(const QString& username, const QString& password_sha1); - bool isAuthorized(const QByteArray& auth, const QString& method) const; - void setlocalAuthEnabled(bool enabled); - bool isLocalAuthEnabled() const; - QString generateNonce() const; - int NbFailedAttemptsForIp(const QString& ip) const; - void increaseNbFailedAttemptsForIp(const QString& ip); - void resetNbFailedAttemptsForIp(const QString& ip); #ifndef QT_NO_OPENSSL void enableHttps(const QSslCertificate &certificate, const QSslKey &key); @@ -81,17 +58,7 @@ private: void incomingConnection(int socketDescriptor); #endif -private slots: - void UnbanTimerEvent(); - -private: - void handleNewConnection(QTcpSocket *socket); - private: - QByteArray m_username; - QByteArray m_passwordSha1; - QHash m_clientFailedAttempts; - bool m_localAuthEnabled; #ifndef QT_NO_OPENSSL bool m_https; QSslCertificate m_certificate; diff --git a/src/webui/httptypes.h b/src/webui/httptypes.h new file mode 100644 index 000000000..614e9a4d2 --- /dev/null +++ b/src/webui/httptypes.h @@ -0,0 +1,89 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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 HTTPTYPES_H +#define HTTPTYPES_H + +#include +#include +#include + +typedef QMap QStringMap; + +const QString HEADER_SET_COOKIE = "Set-Cookie"; +const QString HEADER_CONTENT_TYPE = "Content-Type"; +const QString HEADER_CONTENT_ENCODING = "Content-Encoding"; +const QString HEADER_CONTENT_LENGTH = "Content-Length"; + +const QString CONTENT_TYPE_CSS = "text/css"; +const QString CONTENT_TYPE_GIF = "image/gif"; +const QString CONTENT_TYPE_HTML = "text/html"; +const QString CONTENT_TYPE_JS = "text/javascript"; +const QString CONTENT_TYPE_PNG = "image/png"; +const QString CONTENT_TYPE_TXT = "text/plain"; + +struct HttpEnvironment +{ + QHostAddress clientAddress; +}; + +struct UploadedFile +{ + QString filename; // original filename + QString type; // MIME type + QByteArray data; // File data +}; + +struct HttpRequest +{ + QString method; + QString path; + QStringMap headers; + QStringMap gets; + QStringMap posts; + QMap files; +}; + +struct HttpResponseStatus +{ + uint code; + QString text; + + HttpResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {} +}; + +struct HttpResponse +{ + HttpResponseStatus status; + QStringMap headers; + QByteArray content; + + HttpResponse(uint code = 200, const QString& text = "OK"): status(code, text) {} +}; + +#endif // HTTPTYPES_H diff --git a/src/webui/jsonutils.h b/src/webui/jsonutils.h index b08cfb0d8..5668b356b 100644 --- a/src/webui/jsonutils.h +++ b/src/webui/jsonutils.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2014 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,8 +24,6 @@ * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. - * - * Contact : glassez@yandex.ru */ #ifndef JSONUTILS_H diff --git a/src/webui/requesthandler.cpp b/src/webui/requesthandler.cpp new file mode 100644 index 000000000..26633bd4d --- /dev/null +++ b/src/webui/requesthandler.cpp @@ -0,0 +1,588 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2012, Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include +#ifdef DISABLE_GUI +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#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" + +using namespace libtorrent; + +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"; + +#define ADD_ACTION(scope, action) actions[#scope][#action] = &RequestHandler::action_##scope##_##action + +QMap > RequestHandler::initializeActions() +{ + QMap > 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(json, torrents); + ADD_ACTION(json, preferences); + ADD_ACTION(json, transferInfo); + ADD_ACTION(json, propertiesGeneral); + ADD_ACTION(json, propertiesTrackers); + ADD_ACTION(json, propertiesFiles); + 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, 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); + + return actions; +} + +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(); + + if ((request().posts["username"] == pref->getWebUiUsername()) && (pass == pref->getWebUiPassword())) + { + 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() +{ + 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); +} + +void RequestHandler::action_public_images() +{ + const QString path = ":/Icons/" + args_.join("/"); + printFile(path); +} + +void RequestHandler::action_json_torrents() +{ + print(btjson::getTorrents(), CONTENT_TYPE_JS); +} + +void RequestHandler::action_json_preferences() +{ + print(prefjson::getPreferences(), CONTENT_TYPE_JS); +} + +void RequestHandler::action_json_transferInfo() +{ + print(btjson::getTransferInfo(), CONTENT_TYPE_JS); +} + +void RequestHandler::action_json_propertiesGeneral() +{ + print(btjson::getPropertiesForTorrent(args_.front()), CONTENT_TYPE_JS); +} + +void RequestHandler::action_json_propertiesTrackers() +{ + print(btjson::getTrackersForTorrent(args_.front()), CONTENT_TYPE_JS); +} + +void RequestHandler::action_json_propertiesFiles() +{ + print(btjson::getFilesForTorrent(args_.front()), CONTENT_TYPE_JS); +} + +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. + + QTimer::singleShot(0, qApp, SLOT(quit())); +} + +void RequestHandler::action_command_download() +{ + 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; + + foreach(const UploadedFile& torrent, request().files) + { + QString filePath = saveTmpFile(torrent.data); + + if (!filePath.isEmpty()) + { + QBtSession::instance()->addTorrent(filePath); + // Clean up + fsutils::forceRemove(filePath); + print(QLatin1String("")); + } + else + { + qWarning() << "I/O Error: Could not create temporary file"; + } + } +} + +void RequestHandler::action_command_addTrackers() +{ + 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() +{ + QBtSession::instance()->resumeAllTorrents(); +} + +void RequestHandler::action_command_pauseAll() +{ + QBtSession::instance()->pauseAllTorrents(); +} + +void RequestHandler::action_command_resume() +{ + QBtSession::instance()->resumeTorrent(request().posts["hash"]); +} + +void RequestHandler::action_command_pause() +{ + QBtSession::instance()->pauseTorrent(request().posts["hash"]); +} + +void RequestHandler::action_command_setPreferences() +{ + prefjson::setPreferences(request().posts["json"]); +} + +void RequestHandler::action_command_setFilePrio() +{ + 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() +{ + print(QByteArray::number(QBtSession::instance()->getSession()->settings().upload_rate_limit)); +} + +void RequestHandler::action_command_getGlobalDlLimit() +{ + print(QByteArray::number(QBtSession::instance()->getSession()->settings().download_rate_limit)); +} + +void RequestHandler::action_command_setGlobalUpLimit() +{ + qlonglong limit = request().posts["limit"].toLongLong(); + if (limit == 0) limit = -1; + + QBtSession::instance()->setUploadRateLimit(limit); + Preferences::instance()->setGlobalUploadLimit(limit/1024.); +} + +void RequestHandler::action_command_setGlobalDlLimit() +{ + qlonglong limit = request().posts["limit"].toLongLong(); + if (limit == 0) limit = -1; + + QBtSession::instance()->setDownloadRateLimit(limit); + Preferences::instance()->setGlobalDownloadLimit(limit/1024.); +} + +void RequestHandler::action_command_getTorrentUpLimit() +{ + 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() +{ + 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() +{ + 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() +{ + 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_delete() +{ + QStringList hashes = request().posts["hashes"].split("|"); + + foreach (const QString &hash, hashes) + { + QBtSession::instance()->deleteTorrent(hash, false); + } +} + +void RequestHandler::action_command_deletePerm() +{ + QStringList hashes = request().posts["hashes"].split("|"); + + foreach (const QString &hash, hashes) + { + QBtSession::instance()->deleteTorrent(hash, true); + } +} + +void RequestHandler::action_command_increasePrio() +{ + QStringList hashes = request().posts["hashes"].split("|"); + std::priority_queue, + std::vector >, + std::greater > > 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() +{ + QStringList hashes = request().posts["hashes"].split("|"); + std::priority_queue, + std::vector >, + std::less > > 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() +{ + 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() +{ + 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() +{ + QBtSession::instance()->recheckTorrent(request().posts["hash"]); +} + +bool RequestHandler::isPublicScope() +{ + return (scope_ == DEFAULT_SCOPE); +} + +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 > RequestHandler::actions_ = RequestHandler::initializeActions(); diff --git a/src/webui/requesthandler.h b/src/webui/requesthandler.h new file mode 100644 index 000000000..a92f85269 --- /dev/null +++ b/src/webui/requesthandler.h @@ -0,0 +1,100 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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 +#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_json_torrents(); + void action_json_preferences(); + void action_json_transferInfo(); + void action_json_propertiesGeneral(); + void action_json_propertiesTrackers(); + void action_json_propertiesFiles(); + 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_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(); + + typedef void (RequestHandler::*Action)(); + + QString scope_; + QString action_; + QStringList args_; + + void processRequest(); + + bool isPublicScope(); + void parsePath(); + + static QMap > initializeActions(); + static QMap > actions_; +}; + +#endif // REQUESTHANDLER_H diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp new file mode 100644 index 000000000..2ada7817c --- /dev/null +++ b/src/webui/webapplication.cpp @@ -0,0 +1,309 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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. + */ + +#ifdef DISABLE_GUI +#include +#else +#include +#endif +#include +#include +#include +#include +#include "preferences.h" +#include "requesthandler.h" +#include "webapplication.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; +}; + +// WebApplication + +WebApplication::WebApplication(QObject *parent) + : QObject(parent) +{ +} + +WebApplication::~WebApplication() +{ + // cleanup sessions data + foreach (WebSession* session, sessions_.values()) + delete session; +} + +WebApplication *WebApplication::instance() +{ + static WebApplication inst; + return &inst; +} + +void WebApplication::UnbanTimerEvent() +{ + UnbanTimer* ubantimer = static_cast(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(tmp), sizeof(quint32) * size).toBase64(); + } + while (sessions_.contains(sid)); + + return sid; +} + +void WebApplication::translateDocument(QString& data) +{ + const QRegExp regex(QString::fromUtf8("_\\(([\\w\\s?!:\\/\\(\\),%µ&\\-\\.]+)\\)")); + 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" + }; + 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(); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h new file mode 100644 index 000000000..0e005f0b3 --- /dev/null +++ b/src/webui/webapplication.h @@ -0,0 +1,87 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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 WEBAPPLICATION_H +#define WEBAPPLICATION_H + +#include +#include +#include +#include "httptypes.h" + +struct WebSession +{ + const QString id; + + WebSession(const QString& id): id(id) {} +}; + +const QString C_SID = "SID"; // name of session id cookie +const int BAN_TIME = 3600000; // 1 hour +const int MAX_AUTH_FAILED_ATTEMPTS = 5; + +class AbstractRequestHandler; + +class WebApplication: public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(WebApplication) + +public: + WebApplication(QObject* parent = 0); + virtual ~WebApplication(); + + static WebApplication* instance(); + + bool isBanned(const AbstractRequestHandler* _this) const; + int failedAttempts(const AbstractRequestHandler *_this) const; + void resetFailedAttempts(AbstractRequestHandler* _this); + void increaseFailedAttempts(AbstractRequestHandler* _this); + + bool sessionStart(AbstractRequestHandler* _this); + bool sessionEnd(AbstractRequestHandler* _this); + bool sessionInitialize(AbstractRequestHandler* _this); + + bool readFile(const QString &path, QByteArray& data, QString& type); + +private slots: + void UnbanTimerEvent(); + +private: + QMap sessions_; + QHash clientFailedAttempts_; + QMap translatedFiles_; + + QString generateSid(); + static void translateDocument(QString& data); + + static const QStringMap CONTENT_TYPE_BY_EXT; + static QStringMap initializeContentTypeByExtMap(); +}; + +#endif // WEBAPPLICATION_H diff --git a/src/webui/webui.pri b/src/webui/webui.pri index d5153d7d5..be4989a22 100644 --- a/src/webui/webui.pri +++ b/src/webui/webui.pri @@ -6,10 +6,12 @@ HEADERS += $$PWD/httpserver.h \ $$PWD/httpresponsegenerator.h \ $$PWD/btjson.h \ $$PWD/prefjson.h \ - $$PWD/httpheader.h \ - $$PWD/httprequestheader.h \ - $$PWD/httpresponseheader.h \ - $$PWD/jsonutils.h + $$PWD/jsonutils.h \ + $$PWD/httptypes.h \ + $$PWD/extra_translations.h \ + $$PWD/webapplication.h \ + $$PWD/abstractrequesthandler.h \ + $$PWD/requesthandler.h SOURCES += $$PWD/httpserver.cpp \ $$PWD/httpconnection.cpp \ @@ -17,9 +19,9 @@ SOURCES += $$PWD/httpserver.cpp \ $$PWD/httpresponsegenerator.cpp \ $$PWD/btjson.cpp \ $$PWD/prefjson.cpp \ - $$PWD/httpheader.cpp \ - $$PWD/httprequestheader.cpp \ - $$PWD/httpresponseheader.cpp + $$PWD/webapplication.cpp \ + $$PWD/abstractrequesthandler.cpp \ + $$PWD/requesthandler.cpp # QJson JSON parser/serializer for using with Qt4 lessThan(QT_MAJOR_VERSION, 5) { diff --git a/src/webui/webui.qrc b/src/webui/webui.qrc index 08d58de79..035301d86 100644 --- a/src/webui/webui.qrc +++ b/src/webui/webui.qrc @@ -1,37 +1,39 @@ - - - html/index.html - html/download.html - html/addtrackers.html - html/upload.html - html/about.html - html/filters.html - html/transferlist.html - html/prop-general.html - html/prop-trackers.html - html/prop-files.html - html/properties.html - html/uploadlimit.html - html/downloadlimit.html - html/preferences.html - html/preferences_content.html - html/confirmdeletion.html - css/Core.css - css/Layout.css - css/Window.css - css/Tabs.css - css/dynamicTable.css - css/style.css - scripts/excanvas-compressed.js - scripts/mocha-yc.js - scripts/mocha-init.js - scripts/mootools-1.2-core-yc.js - scripts/mootools-1.2-more.js - scripts/dynamicTable.js - scripts/client.js - scripts/download.js - scripts/progressbar.js - scripts/contextmenu.js - scripts/parametrics.js - + + + www/private/index.html + www/private/login.html + www/public/css/Core.css + www/public/css/dynamicTable.css + www/public/css/Layout.css + www/public/css/style.css + www/public/css/Tabs.css + www/public/css/Window.css + www/public/scripts/client.js + www/public/scripts/contextmenu.js + www/public/scripts/download.js + www/public/scripts/dynamicTable.js + www/public/scripts/excanvas-compressed.js + www/public/scripts/mocha.js + www/public/scripts/mocha-init.js + www/public/scripts/mocha-yc.js + www/public/scripts/mootools-1.2-core-yc.js + www/public/scripts/mootools-1.2-more.js + www/public/scripts/parametrics.js + www/public/scripts/progressbar.js + www/public/about.html + www/public/addtrackers.html + www/public/confirmdeletion.html + www/public/download.html + www/public/downloadlimit.html + www/public/filters.html + www/public/preferences.html + www/public/preferences_content.html + www/public/properties.html + www/public/prop-files.html + www/public/prop-general.html + www/public/prop-trackers.html + www/public/transferlist.html + www/public/upload.html + www/public/uploadlimit.html + diff --git a/src/webui/html/index.html b/src/webui/www/private/index.html similarity index 67% rename from src/webui/html/index.html rename to src/webui/www/private/index.html index 44f0cae9c..33f5fded8 100644 --- a/src/webui/html/index.html +++ b/src/webui/www/private/index.html @@ -8,10 +8,10 @@ - - - - + + + +