diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 74d02ba10..17d01fb0b 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -47,6 +47,7 @@ rss/rss_session.h search/searchdownloadhandler.h search/searchhandler.h search/searchpluginmanager.h +utils/bytearray.h utils/fs.h utils/gzip.h utils/misc.h @@ -114,6 +115,7 @@ rss/rss_session.cpp search/searchdownloadhandler.cpp search/searchhandler.cpp search/searchpluginmanager.cpp +utils/bytearray.cpp utils/fs.cpp utils/gzip.cpp utils/misc.cpp diff --git a/src/base/base.pri b/src/base/base.pri index c89da2dbd..c59ae21bc 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -62,6 +62,7 @@ HEADERS += \ $$PWD/tristatebool.h \ $$PWD/types.h \ $$PWD/unicodestrings.h \ + $$PWD/utils/bytearray.h \ $$PWD/utils/fs.h \ $$PWD/utils/gzip.h \ $$PWD/utils/misc.h \ @@ -124,6 +125,7 @@ SOURCES += \ $$PWD/torrentfileguard.cpp \ $$PWD/torrentfilter.cpp \ $$PWD/tristatebool.cpp \ + $$PWD/utils/bytearray.cpp \ $$PWD/utils/fs.cpp \ $$PWD/utils/gzip.cpp \ $$PWD/utils/misc.cpp \ diff --git a/src/base/bittorrent/tracker.cpp b/src/base/bittorrent/tracker.cpp index 6429016e6..7a1294cd6 100644 --- a/src/base/bittorrent/tracker.cpp +++ b/src/base/bittorrent/tracker.cpp @@ -32,8 +32,10 @@ #include #include +#include "base/global.h" #include "base/http/server.h" #include "base/preferences.h" +#include "base/utils/bytearray.h" #include "base/utils/string.h" #include "tracker.h" @@ -130,19 +132,30 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http: void Tracker::respondToAnnounceRequest() { - const QStringMap &gets = m_request.gets; + QMap queryParams; + // Parse GET parameters + using namespace Utils::ByteArray; + for (const QByteArray ¶m : copyAsConst(splitToViews(m_request.query, "&"))) { + const int sepPos = param.indexOf('='); + if (sepPos <= 0) continue; // ignores params without name + + const QString paramName {QString::fromUtf8(param.constData(), sepPos)}; + const QByteArray paramValue {param.mid(sepPos + 1)}; + queryParams[paramName] = paramValue; + } + TrackerAnnounceRequest annonceReq; // IP annonceReq.peer.ip = m_env.clientAddress.toString(); // 1. Get info_hash - if (!gets.contains("info_hash")) { + if (!queryParams.contains("info_hash")) { qDebug("Tracker: Missing info_hash"); status(101, "Missing info_hash"); return; } - annonceReq.infoHash = gets.value("info_hash"); + annonceReq.infoHash = queryParams.value("info_hash"); // info_hash cannot be longer than 20 bytes /*if (annonce_req.info_hash.toLatin1().length() > 20) { qDebug("Tracker: Info_hash is not 20 byte long: %s (%d)", qUtf8Printable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length()); @@ -151,12 +164,12 @@ void Tracker::respondToAnnounceRequest() }*/ // 2. Get peer ID - if (!gets.contains("peer_id")) { + if (!queryParams.contains("peer_id")) { qDebug("Tracker: Missing peer_id"); status(102, "Missing peer_id"); return; } - annonceReq.peer.peerId = gets.value("peer_id"); + annonceReq.peer.peerId = queryParams.value("peer_id"); // peer_id cannot be longer than 20 bytes /*if (annonce_req.peer.peer_id.length() > 20) { qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id)); @@ -165,13 +178,13 @@ void Tracker::respondToAnnounceRequest() }*/ // 3. Get port - if (!gets.contains("port")) { + if (!queryParams.contains("port")) { qDebug("Tracker: Missing port"); status(103, "Missing port"); return; } bool ok = false; - annonceReq.peer.port = gets.value("port").toInt(&ok); + annonceReq.peer.port = queryParams.value("port").toInt(&ok); if (!ok || annonceReq.peer.port < 1 || annonceReq.peer.port > 65535) { qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port); status(103, "Missing port"); @@ -180,15 +193,15 @@ void Tracker::respondToAnnounceRequest() // 4. Get event annonceReq.event = ""; - if (gets.contains("event")) { - annonceReq.event = gets.value("event"); + if (queryParams.contains("event")) { + annonceReq.event = queryParams.value("event"); qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event)); } // 5. Get numwant annonceReq.numwant = 50; - if (gets.contains("numwant")) { - int tmp = gets.value("numwant").toInt(); + if (queryParams.contains("numwant")) { + int tmp = queryParams.value("numwant").toInt(); if (tmp > 0) { qDebug("Tracker: numwant = %d", tmp); annonceReq.numwant = tmp; @@ -197,7 +210,7 @@ void Tracker::respondToAnnounceRequest() // 6. no_peer_id (extension) annonceReq.noPeerId = false; - if (gets.contains("no_peer_id")) + if (queryParams.contains("no_peer_id")) annonceReq.noPeerId = true; // 7. TODO: support "compact" extension diff --git a/src/base/bittorrent/tracker.h b/src/base/bittorrent/tracker.h index 0f03fb5b2..e80ba0a85 100644 --- a/src/base/bittorrent/tracker.h +++ b/src/base/bittorrent/tracker.h @@ -52,7 +52,7 @@ namespace BitTorrent struct Peer { QString ip; - QString peerId; + QByteArray peerId; int port; bool operator!=(const Peer &other) const; @@ -63,7 +63,7 @@ namespace BitTorrent struct TrackerAnnounceRequest { - QString infoHash; + QByteArray infoHash; QString event; int numwant; Peer peer; @@ -72,7 +72,7 @@ namespace BitTorrent }; typedef QHash PeerList; - typedef QHash TorrentList; + typedef QHash TorrentList; /* Basic Bittorrent tracker implementation in Qt */ /* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */ diff --git a/src/base/http/requestparser.cpp b/src/base/http/requestparser.cpp index 269885292..e7de473d2 100644 --- a/src/base/http/requestparser.cpp +++ b/src/base/http/requestparser.cpp @@ -36,9 +36,11 @@ #include #include +#include "base/utils/bytearray.h" #include "base/utils/string.h" using namespace Http; +using namespace Utils::ByteArray; using QStringPair = QPair; namespace @@ -52,46 +54,6 @@ namespace return in; } - QList splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior = QString::KeepEmptyParts) - { - // mimic QString::split(sep, behavior) - - if (sep.isEmpty()) - return {in}; - - QList ret; - - int head = 0; - while (head < in.size()) { - int end = in.indexOf(sep, head); - if (end < 0) - end = in.size(); - - // omit empty parts - const QByteArray part = QByteArray::fromRawData((in.constData() + head), (end - head)); - if (!part.isEmpty() || (behavior == QString::KeepEmptyParts)) - ret += part; - - head = end + sep.size(); - } - - return ret; - } - - const QByteArray viewMid(const QByteArray &in, const int pos, const int len = -1) - { - // mimic QByteArray::mid(pos, len) but instead of returning a full-copy, - // we only return a partial view - - if ((pos < 0) || (pos >= in.size()) || (len == 0)) - return {}; - - const int validLen = ((len < 0) || (pos + len) >= in.size()) - ? in.size() - pos - : len; - return QByteArray::fromRawData(in.constData() + pos, validLen); - } - bool parseHeaderLine(const QString &line, QStringMap &out) { // [rfc7230] 3.2. Header Fields @@ -152,7 +114,7 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data) } if (contentLength > 0) { - const QByteArray httpBodyView = viewMid(data, headerLength, contentLength); + const QByteArray httpBodyView = midView(data, headerLength, contentLength); if (httpBodyView.length() < contentLength) { qDebug() << Q_FUNC_INFO << "incomplete request"; return {ParseStatus::Incomplete, Request(), 0}; @@ -218,15 +180,11 @@ bool RequestParser::parseRequestLine(const QString &line) m_request.method = match.captured(1); // Request Target - const QUrl url = QUrl::fromEncoded(match.captured(2).toLatin1()); - m_request.path = url.path(); - - // parse queries - QListIterator i(QUrlQuery(url).queryItems()); - while (i.hasNext()) { - const QStringPair pair = i.next(); - m_request.gets[pair.first] = pair.second; - } + const QByteArray decodedUrl {QByteArray::fromPercentEncoding(match.captured(2).toLatin1())}; + const int sepPos = decodedUrl.indexOf('?'); + m_request.path = QString::fromUtf8(decodedUrl.constData(), (sepPos == -1 ? decodedUrl.size() : sepPos)); + if (sepPos >= 0) + m_request.query = decodedUrl.mid(sepPos + 1); // HTTP-version m_request.version = match.captured(3); diff --git a/src/base/http/types.h b/src/base/http/types.h index 41b72b3f3..c817dfa9b 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -98,8 +98,8 @@ namespace Http QString version; QString method; QString path; + QByteArray query; QStringMap headers; - QStringMap gets; QStringMap posts; QVector files; }; diff --git a/src/base/utils/bytearray.cpp b/src/base/utils/bytearray.cpp new file mode 100644 index 000000000..25603646f --- /dev/null +++ b/src/base/utils/bytearray.cpp @@ -0,0 +1,65 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) + * + * 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 "bytearray.h" +#include + +QList Utils::ByteArray::splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior) +{ + if (sep.isEmpty()) + return {in}; + + QList ret; + + int head = 0; + while (head < in.size()) { + int end = in.indexOf(sep, head); + if (end < 0) + end = in.size(); + + // omit empty parts + const QByteArray part = QByteArray::fromRawData((in.constData() + head), (end - head)); + if (!part.isEmpty() || (behavior == QString::KeepEmptyParts)) + ret += part; + + head = end + sep.size(); + } + + return ret; +} + +const QByteArray Utils::ByteArray::midView(const QByteArray &in, const int pos, const int len) +{ + if ((pos < 0) || (pos >= in.size()) || (len == 0)) + return {}; + + const int validLen = ((len < 0) || (pos + len) >= in.size()) + ? in.size() - pos + : len; + return QByteArray::fromRawData(in.constData() + pos, validLen); +} diff --git a/src/base/utils/bytearray.h b/src/base/utils/bytearray.h new file mode 100644 index 000000000..e0f46abf3 --- /dev/null +++ b/src/base/utils/bytearray.h @@ -0,0 +1,45 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) + * + * 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. + */ + +#pragma once + +#include +#include + +namespace Utils +{ + namespace ByteArray + { + // Mimic QString::split(sep, behavior) + QList splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior = QString::KeepEmptyParts); + + // Mimic QByteArray::mid(pos, len) but instead of returning a full-copy, + // we only return a partial view + const QByteArray midView(const QByteArray &in, const int pos, const int len = -1); + } +} diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 7bd56e753..dd8606ccb 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -46,10 +46,12 @@ #include #include +#include "base/global.h" #include "base/http/httperror.h" #include "base/iconprovider.h" #include "base/logger.h" #include "base/preferences.h" +#include "base/utils/bytearray.h" #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/utils/net.h" @@ -245,8 +247,6 @@ const Http::Environment &WebApplication::env() const void WebApplication::doProcessRequest() { - QStringMap *params = &((request().method == QLatin1String("GET")) - ? m_request.gets : m_request.posts); QString scope, action; const auto findAPICall = [&]() -> bool @@ -337,13 +337,13 @@ void WebApplication::doProcessRequest() action = compatInfo.action; if (legacyAction == QLatin1String("command/delete")) - (*params)["deleteFiles"] = "false"; + m_params["deleteFiles"] = "false"; else if (legacyAction == QLatin1String("command/deletePerm")) - (*params)["deleteFiles"] = "true"; + m_params["deleteFiles"] = "true"; const QString hash {match.captured(QLatin1String("hash"))}; if (!hash.isEmpty()) - (*params)[QLatin1String("hash")] = hash; + m_params[QLatin1String("hash")] = hash; return true; }; @@ -379,7 +379,7 @@ void WebApplication::doProcessRequest() data[torrent.filename] = torrent.data; try { - const QVariant result = controller->run(action, *params, data); + const QVariant result = controller->run(action, m_params, data); switch (result.userType()) { case QMetaType::QString: print(result.toString(), Http::CONTENT_TYPE_TXT); @@ -486,6 +486,24 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons m_currentSession = nullptr; m_request = request; m_env = env; + m_params.clear(); + if (m_request.method == Http::METHOD_GET) { + // Parse GET parameters + using namespace Utils::ByteArray; + for (const QByteArray ¶m : copyAsConst(splitToViews(m_request.query, "&"))) { + const int sepPos = param.indexOf('='); + if (sepPos <= 0) continue; // ignores params without name + + const QString paramName {QString::fromUtf8(param.constData(), sepPos)}; + const int valuePos = sepPos + 1; + const QString paramValue { + QString::fromUtf8(param.constData() + valuePos, param.size() - valuePos)}; + m_params[paramName] = paramValue; + } + } + else { + m_params = m_request.posts; + } // clear response clear(); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 73a36a07c..f01b1bd04 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -124,6 +124,7 @@ private: WebSession *m_currentSession = nullptr; Http::Request m_request; Http::Environment m_env; + QMap m_params; const QRegularExpression m_apiPathPattern {(QLatin1String("^/api/v2/(?[A-Za-z_][A-Za-z_0-9]*)/(?[A-Za-z_][A-Za-z_0-9]*)$"))}; const QRegularExpression m_apiLegacyPathPattern {QLatin1String("^/(?((sync|command|query)/[A-Za-z_][A-Za-z_0-9]*|login|logout))(/(?[^/]+))?$")};