mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-29 22:20:53 +03:00
Merge pull request #8525 from glassez/url-query
Parse URL query string at application level
This commit is contained in:
commit
e22946ef61
10 changed files with 176 additions and 72 deletions
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -32,8 +32,10 @@
|
|||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#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<QString, QByteArray> 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
|
||||
|
|
|
@ -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<QString, Peer> PeerList;
|
||||
typedef QHash<QString, PeerList> TorrentList;
|
||||
typedef QHash<QByteArray, PeerList> TorrentList;
|
||||
|
||||
/* Basic Bittorrent tracker implementation in Qt */
|
||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
||||
|
|
|
@ -36,9 +36,11 @@
|
|||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
using namespace Http;
|
||||
using namespace Utils::ByteArray;
|
||||
using QStringPair = QPair<QString, QString>;
|
||||
|
||||
namespace
|
||||
|
@ -52,46 +54,6 @@ namespace
|
|||
return in;
|
||||
}
|
||||
|
||||
QList<QByteArray> splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior = QString::KeepEmptyParts)
|
||||
{
|
||||
// mimic QString::split(sep, behavior)
|
||||
|
||||
if (sep.isEmpty())
|
||||
return {in};
|
||||
|
||||
QList<QByteArray> 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<QStringPair> 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);
|
||||
|
|
|
@ -98,8 +98,8 @@ namespace Http
|
|||
QString version;
|
||||
QString method;
|
||||
QString path;
|
||||
QByteArray query;
|
||||
QStringMap headers;
|
||||
QStringMap gets;
|
||||
QStringMap posts;
|
||||
QVector<UploadedFile> files;
|
||||
};
|
||||
|
|
65
src/base/utils/bytearray.cpp
Normal file
65
src/base/utils/bytearray.cpp
Normal file
|
@ -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>
|
||||
|
||||
QList<QByteArray> Utils::ByteArray::splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior)
|
||||
{
|
||||
if (sep.isEmpty())
|
||||
return {in};
|
||||
|
||||
QList<QByteArray> 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);
|
||||
}
|
45
src/base/utils/bytearray.h
Normal file
45
src/base/utils/bytearray.h
Normal file
|
@ -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 <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace ByteArray
|
||||
{
|
||||
// Mimic QString::split(sep, behavior)
|
||||
QList<QByteArray> 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);
|
||||
}
|
||||
}
|
|
@ -46,10 +46,12 @@
|
|||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#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();
|
||||
|
|
|
@ -124,6 +124,7 @@ private:
|
|||
WebSession *m_currentSession = nullptr;
|
||||
Http::Request m_request;
|
||||
Http::Environment m_env;
|
||||
QMap<QString, QString> m_params;
|
||||
|
||||
const QRegularExpression m_apiPathPattern {(QLatin1String("^/api/v2/(?<scope>[A-Za-z_][A-Za-z_0-9]*)/(?<action>[A-Za-z_][A-Za-z_0-9]*)$"))};
|
||||
const QRegularExpression m_apiLegacyPathPattern {QLatin1String("^/(?<action>((sync|command|query)/[A-Za-z_][A-Za-z_0-9]*|login|logout))(/(?<hash>[^/]+))?$")};
|
||||
|
|
Loading…
Reference in a new issue