From 1aca3b0adcdea05437d0c0b2f7f9c66fdf783d66 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Thu, 1 Mar 2018 16:57:44 +0300 Subject: [PATCH] Parse URL query string at application level --- src/base/bittorrent/tracker.cpp | 37 ++++++++++++++++++++++----------- src/base/bittorrent/tracker.h | 6 +++--- src/base/http/requestparser.cpp | 17 ++++++--------- src/base/http/types.h | 2 +- src/webui/webapplication.cpp | 30 ++++++++++++++++++++------ src/webui/webapplication.h | 1 + 6 files changed, 60 insertions(+), 33 deletions(-) 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 35645f74b..e7de473d2 100644 --- a/src/base/http/requestparser.cpp +++ b/src/base/http/requestparser.cpp @@ -40,14 +40,13 @@ #include "base/utils/string.h" using namespace Http; +using namespace Utils::ByteArray; using QStringPair = QPair; namespace { const QByteArray EOH = QByteArray(CRLF).repeated(2); - using namespace Utils::ByteArray; - const QByteArray viewWithoutEndingWith(const QByteArray &in, const QByteArray &str) { if (in.endsWith(str)) @@ -181,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/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))(/(?[^/]+))?$")};