mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-23 16:58:06 -06:00
Change project directory structure according to application structure. Change 'nox' configuration option to something more meaningful 'nogui'. Rename 'Icons' folder to 'icons' (similar to other folders). Partially add 'nowebui' option support. Remove QConf project file.
376 lines
10 KiB
C++
376 lines
10 KiB
C++
/*
|
|
* Bittorrent Client using Qt and libtorrent.
|
|
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
|
* 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
|
|
* 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 : chris@qbittorrent.org
|
|
*/
|
|
|
|
#include <QStringList>
|
|
#include <QUrl>
|
|
//#include <QVariant>
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
|
#include <QUrlQuery>
|
|
#endif
|
|
#include <QDir>
|
|
#include <QTemporaryFile>
|
|
#include <QDebug>
|
|
#include "httprequestparser.h"
|
|
|
|
const QByteArray EOL("\r\n");
|
|
const QByteArray EOH("\r\n\r\n");
|
|
|
|
inline QString unquoted(const QString& str)
|
|
{
|
|
if ((str[0] == '\"') && (str[str.length() - 1] == '\"'))
|
|
return str.mid(1, str.length() - 2);
|
|
|
|
return str;
|
|
}
|
|
|
|
HttpRequestParser::ErrorCode HttpRequestParser::parse(const QByteArray& data, HttpRequest& request, uint maxContentLength)
|
|
{
|
|
return HttpRequestParser(maxContentLength).parseHttpRequest(data, request);
|
|
}
|
|
|
|
HttpRequestParser::HttpRequestParser(uint maxContentLength)
|
|
: maxContentLength_(maxContentLength)
|
|
{
|
|
}
|
|
|
|
HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArray& data, HttpRequest& request)
|
|
{
|
|
request_ = HttpRequest();
|
|
|
|
// Parse HTTP request header
|
|
const int header_end = data.indexOf(EOH);
|
|
if (header_end < 0)
|
|
{
|
|
qDebug() << Q_FUNC_INFO << "incomplete request";
|
|
return IncompleteRequest;
|
|
}
|
|
|
|
if (!parseHttpHeader(data.left(header_end)))
|
|
{
|
|
qWarning() << Q_FUNC_INFO << "header parsing error";
|
|
return BadRequest;
|
|
}
|
|
|
|
// 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<int>(maxContentLength_))
|
|
{
|
|
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
|
return BadRequest;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool HttpRequestParser::parseStartingLine(const QString &line)
|
|
{
|
|
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
|
|
|
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<QPair<QString, QString> > i(url.queryItems());
|
|
#else
|
|
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
|
#endif
|
|
while (i.hasNext())
|
|
{
|
|
QPair<QString, QString> 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<QString, QString>& 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<QString, QString> header;
|
|
if (!parseHeaderLine(*it, header))
|
|
return false;
|
|
|
|
request_.headers[header.first] = header.second;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QList<QByteArray> HttpRequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary)
|
|
{
|
|
QList<QByteArray> ret;
|
|
QByteArray sep = boundary + EOL;
|
|
const int sepLength = sep.size();
|
|
|
|
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;
|
|
}
|
|
|
|
bool HttpRequestParser::parseContent(const QByteArray& data)
|
|
{
|
|
// Parse message content
|
|
qDebug() << Q_FUNC_INFO << "Content-Length: " << request_.headers["content-length"];
|
|
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
|
|
|
|
// 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))
|
|
url.setEncodedQuery(data);
|
|
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
|
#else
|
|
url.setQuery(data);
|
|
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded));
|
|
#endif
|
|
while (i.hasNext())
|
|
{
|
|
QPair<QString, QString> pair = i.next();
|
|
request_.posts[pair.first.toLower()] = pair.second;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse multipart/form data (torrent file)
|
|
/**
|
|
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
|
|
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
|
Content-Disposition: form-data; name=\"Filename\"
|
|
|
|
PB020344.torrent
|
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
|
Content-Disposition: form-data; name=\"torrentfile"; filename=\"PB020344.torrent\"
|
|
Content-Type: application/x-bittorrent
|
|
|
|
BINARY DATA IS HERE
|
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
|
Content-Disposition: form-data; name=\"Upload\"
|
|
|
|
Submit Query
|
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
|
|
**/
|
|
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(content_type) < 0)
|
|
{
|
|
if (boundaryRegexNotQuoted.indexIn(content_type) < 0)
|
|
{
|
|
qWarning() << "Could not find boundary in multipart/form-data header!";
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
|
|
}
|
|
|
|
qDebug() << "Boundary is " << boundary;
|
|
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
|
qDebug() << parts.size() << "parts in data";
|
|
|
|
foreach (const QByteArray& part, parts)
|
|
{
|
|
if (!parseFormData(part))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type);
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
QString header_str = QString::fromUtf8(data.left(header_end));
|
|
QStringList lines = header_str.trimmed().split(EOL);
|
|
QStringMap headers;
|
|
foreach (const QString& line, lines)
|
|
{
|
|
QPair<QString, QString> header;
|
|
if (!parseHeaderLine(line, header))
|
|
return false;
|
|
|
|
headers[header.first] = header.second;
|
|
}
|
|
|
|
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
|
|
{
|
|
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;
|
|
}
|