From cec68c3fd701751fc81a0e4b2b9e4ffa5c84572b Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 5 May 2017 14:18:01 +0800 Subject: [PATCH 1/2] Rewrite RequestParser * Add more checks and also more strict checks for invalid conditions * Add http version field * Raise max request size to 64 MB * Add author in license * Use Qt5 new connect syntax --- src/base/http/connection.cpp | 83 ++++-- src/base/http/connection.h | 2 +- src/base/http/requestparser.cpp | 433 ++++++++++++++++---------------- src/base/http/requestparser.h | 36 +-- src/base/http/types.h | 18 +- 5 files changed, 311 insertions(+), 261 deletions(-) diff --git a/src/base/http/connection.cpp b/src/base/http/connection.cpp index bb63093e6..fe74e7974 100644 --- a/src/base/http/connection.cpp +++ b/src/base/http/connection.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * @@ -34,6 +35,7 @@ #include #include +#include "base/logger.h" #include "irequesthandler.h" #include "requestparser.h" #include "responsegenerator.h" @@ -47,7 +49,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj { m_socket->setParent(this); m_idleTimer.start(); - connect(m_socket, SIGNAL(readyRead()), SLOT(read())); + connect(m_socket, &QTcpSocket::readyRead, this, &Connection::read); } Connection::~Connection() @@ -58,37 +60,66 @@ Connection::~Connection() void Connection::read() { m_idleTimer.restart(); - m_receivedData.append(m_socket->readAll()); - Request request; - RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request); // TODO: transform request headers to lowercase - - switch (err) { - case RequestParser::IncompleteRequest: - // Partial request waiting for the rest - break; - - case RequestParser::BadRequest: - sendResponse(Response(400, "Bad Request")); - m_receivedData.clear(); - break; - - case RequestParser::NoError: - const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()}; - - Response response = m_requestHandler->processRequest(request, env); - if (acceptsGzipEncoding(request.headers["accept-encoding"])) - response.headers[HEADER_CONTENT_ENCODING] = "gzip"; - sendResponse(response); - m_receivedData.clear(); - break; + + while (!m_receivedData.isEmpty()) { + const RequestParser::ParseResult result = RequestParser::parse(m_receivedData); + + switch (result.status) { + case RequestParser::ParseStatus::Incomplete: { + const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers + if (m_receivedData.size() > bufferLimit) { + Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %ld, IP: %s") + .arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING); + + Response resp(413, "Payload Too Large"); + resp.headers[HEADER_CONNECTION] = "close"; + + sendResponse(resp); + m_socket->close(); + } + } + return; + + case RequestParser::ParseStatus::BadRequest: { + Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %s") + .arg(m_socket->peerAddress().toString()), Log::WARNING); + + Response resp(400, "Bad Request"); + resp.headers[HEADER_CONNECTION] = "close"; + + sendResponse(resp); + m_socket->close(); + } + return; + + case RequestParser::ParseStatus::OK: { + const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()}; + + Response resp = m_requestHandler->processRequest(result.request, env); + + if (acceptsGzipEncoding(result.request.headers["accept-encoding"])) + resp.headers[HEADER_CONTENT_ENCODING] = "gzip"; + + resp.headers[HEADER_CONNECTION] = "close"; + + sendResponse(resp); + m_receivedData = m_receivedData.mid(result.frameSize); + + m_socket->close(); + } + break; + + default: + Q_ASSERT(false); + return; + } } } -void Connection::sendResponse(const Response &response) +void Connection::sendResponse(const Response &response) const { m_socket->write(toByteArray(response)); - m_socket->close(); // TODO: remove when HTTP pipelining is supported } bool Connection::hasExpired(const qint64 timeout) const diff --git a/src/base/http/connection.h b/src/base/http/connection.h index 81ec3ab80..7ca3ca845 100644 --- a/src/base/http/connection.h +++ b/src/base/http/connection.h @@ -61,7 +61,7 @@ namespace Http private: static bool acceptsGzipEncoding(QString codings); - void sendResponse(const Response &response); + void sendResponse(const Response &response) const; QTcpSocket *m_socket; IRequestHandler *m_requestHandler; diff --git a/src/base/http/requestparser.cpp b/src/base/http/requestparser.cpp index 9840fa88b..269885292 100644 --- a/src/base/http/requestparser.cpp +++ b/src/base/http/requestparser.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * @@ -30,242 +31,257 @@ #include "requestparser.h" #include -#include +#include #include #include #include -const QByteArray EOL("\r\n"); -const QByteArray EOH("\r\n\r\n"); +#include "base/utils/string.h" -inline QString unquoted(const QString &str) +using namespace Http; +using QStringPair = QPair; + +namespace { - if ((str[0] == '\"') && (str[str.length() - 1] == '\"')) - return str.mid(1, str.length() - 2); + const QByteArray EOH = QByteArray(CRLF).repeated(2); - return str; -} + const QByteArray viewWithoutEndingWith(const QByteArray &in, const QByteArray &str) + { + if (in.endsWith(str)) + return QByteArray::fromRawData(in.constData(), (in.size() - str.size())); + return in; + } -using namespace Http; + QList splitToViews(const QByteArray &in, const QByteArray &sep, const QString::SplitBehavior behavior = QString::KeepEmptyParts) + { + // mimic QString::split(sep, behavior) -RequestParser::ErrorCode RequestParser::parse(const QByteArray &data, Request &request, uint maxContentLength) -{ - return RequestParser(maxContentLength).parseHttpRequest(data, request); -} + if (sep.isEmpty()) + return {in}; -RequestParser::RequestParser(uint maxContentLength) - : m_maxContentLength(maxContentLength) -{ -} + QList ret; -RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray &data, Request &request) -{ - m_request = Request(); + int head = 0; + while (head < in.size()) { + int end = in.indexOf(sep, head); + if (end < 0) + end = in.size(); - // Parse HTTP request header - const int headerEnd = data.indexOf(EOH); - if (headerEnd < 0) { - qDebug() << Q_FUNC_INFO << "incomplete request"; - return IncompleteRequest; - } + // omit empty parts + const QByteArray part = QByteArray::fromRawData((in.constData() + head), (end - head)); + if (!part.isEmpty() || (behavior == QString::KeepEmptyParts)) + ret += part; - if (!parseHttpHeader(data.left(headerEnd))) { - qWarning() << Q_FUNC_INFO << "header parsing error"; - return BadRequest; + head = end + sep.size(); + } + + return ret; } - // Parse HTTP request message - if (m_request.headers.contains("content-length")) { - int contentLength = m_request.headers["content-length"].toInt(); - if (contentLength < 0) { - qWarning() << Q_FUNC_INFO << "bad request: content-length is negative"; - return BadRequest; - } + 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 (contentLength > static_cast(m_maxContentLength)) { - qWarning() << Q_FUNC_INFO << "bad request: message too long"; - return BadRequest; - } + if ((pos < 0) || (pos >= in.size()) || (len == 0)) + return {}; - QByteArray content = data.mid(headerEnd + EOH.length(), contentLength); - if (content.length() < contentLength) { - qDebug() << Q_FUNC_INFO << "incomplete request"; - return IncompleteRequest; - } + const int validLen = ((len < 0) || (pos + len) >= in.size()) + ? in.size() - pos + : len; + return QByteArray::fromRawData(in.constData() + pos, validLen); + } - if ((contentLength > 0) && !parseContent(content)) { - qWarning() << Q_FUNC_INFO << "message parsing error"; - return BadRequest; + bool parseHeaderLine(const QString &line, QStringMap &out) + { + // [rfc7230] 3.2. Header Fields + const int i = line.indexOf(':'); + if (i <= 0) { + qWarning() << Q_FUNC_INFO << "invalid http header:" << line; + return false; } - } - // qDebug() << Q_FUNC_INFO; - // qDebug() << "HTTP Request header:"; - // qDebug() << data.left(headerEnd) << "\n"; + const QString name = line.leftRef(i).trimmed().toString().toLower(); + const QString value = line.midRef(i + 1).trimmed().toString(); + out[name] = value; - request = m_request; - return NoError; + return true; + } } -bool RequestParser::parseStartingLine(const QString &line) +RequestParser::RequestParser() { - const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$"); +} - if (rx.indexIn(line.trimmed()) >= 0) { - m_request.method = rx.cap(1); +RequestParser::ParseResult RequestParser::parse(const QByteArray &data) +{ + // Warning! Header names are converted to lowercase + return RequestParser().doParse(data); +} - QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1()); - m_request.path = url.path(); // Path +RequestParser::ParseResult RequestParser::doParse(const QByteArray &data) +{ + // we don't handle malformed requests which use double `LF` as delimiter + const int headerEnd = data.indexOf(EOH); + if (headerEnd < 0) { + qDebug() << Q_FUNC_INFO << "incomplete request"; + return {ParseStatus::Incomplete, Request(), 0}; + } - // Parse GET parameters - QListIterator > i(QUrlQuery(url).queryItems()); - while (i.hasNext()) { - QPair pair = i.next(); - m_request.gets[pair.first] = pair.second; + const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd); + if (!parseStartLines(httpHeaders)) { + qWarning() << Q_FUNC_INFO << "header parsing error"; + return {ParseStatus::BadRequest, Request(), 0}; + } + + const int headerLength = headerEnd + EOH.length(); + + // handle supported methods + if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD)) + return {ParseStatus::OK, m_request, headerLength}; + if (m_request.method == HEADER_REQUEST_METHOD_POST) { + bool ok = false; + const int contentLength = m_request.headers[HEADER_CONTENT_LENGTH].toInt(&ok); + if (!ok || (contentLength < 0)) { + qWarning() << Q_FUNC_INFO << "bad request: content-length invalid"; + return {ParseStatus::BadRequest, Request(), 0}; + } + if (contentLength > MAX_CONTENT_SIZE) { + qWarning() << Q_FUNC_INFO << "bad request: message too long"; + return {ParseStatus::BadRequest, Request(), 0}; } - return true; - } + if (contentLength > 0) { + const QByteArray httpBodyView = viewMid(data, headerLength, contentLength); + if (httpBodyView.length() < contentLength) { + qDebug() << Q_FUNC_INFO << "incomplete request"; + return {ParseStatus::Incomplete, Request(), 0}; + } - qWarning() << Q_FUNC_INFO << "invalid http header:" << line; - return false; -} + if (!parsePostMessage(httpBodyView)) { + qWarning() << Q_FUNC_INFO << "message body parsing error"; + return {ParseStatus::BadRequest, Request(), 0}; + } + } -bool RequestParser::parseHeaderLine(const QString &line, QPair &out) -{ - int i = line.indexOf(QLatin1Char(':')); - if (i == -1) { - qWarning() << Q_FUNC_INFO << "invalid http header:" << line; - return false; + return {ParseStatus::OK, m_request, (headerLength + contentLength)}; } - out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed()); - return true; + qWarning() << Q_FUNC_INFO << "unsupported request method: " << m_request.method; + return {ParseStatus::BadRequest, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented" } -bool RequestParser::parseHttpHeader(const QByteArray &data) +bool RequestParser::parseStartLines(const QString &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(); - } + // we don't handle malformed request which uses `LF` for newline + const QVector lines = data.splitRef(CRLF, QString::SkipEmptyParts); + + // [rfc7230] 3.2.2. Field Order + QStringList requestLines; + for (const auto &line : lines) { + if (line.at(0).isSpace() && !requestLines.isEmpty()) { + // continuation of previous line + requestLines.last() += line.toString(); } else { - headerLines.append(line); + requestLines += line.toString(); } } - if (headerLines.isEmpty()) - return false; // Empty header + if (requestLines.isEmpty()) + return false; - QStringList::Iterator it = headerLines.begin(); - if (!parseStartingLine(*it)) + if (!parseRequestLine(requestLines[0])) return false; - ++it; - for (; it != headerLines.end(); ++it) { - QPair header; - if (!parseHeaderLine(*it, header)) + for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i) { + if (!parseHeaderLine(*i, m_request.headers)) return false; - - m_request.headers[header.first] = header.second; } return true; } -QList RequestParser::splitMultipartData(const QByteArray &data, const QByteArray &boundary) +bool RequestParser::parseRequestLine(const QString &line) { - QList ret; - QByteArray sep = boundary + EOL; - const int sepLength = sep.size(); + // [rfc7230] 3.1.1. Request Line - int start = 0, end = 0; - if ((end = data.indexOf(sep, start)) >= 0) { - start = end + sepLength; // skip first boundary + const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$")); + const QRegularExpressionMatch match = re.match(line); - while ((end = data.indexOf(sep, start)) >= 0) { - ret << data.mid(start, end - EOL.length() - start); - start = end + sepLength; - } + if (!match.hasMatch()) { + qWarning() << Q_FUNC_INFO << "invalid http header:" << line; + return false; + } + + // Request Methods + m_request.method = match.captured(1); - // last or single part - sep = boundary + "--" + EOL; - if ((end = data.indexOf(sep, start)) >= 0) - ret << data.mid(start, end - EOL.length() - start); + // 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; } - return ret; + // HTTP-version + m_request.version = match.captured(3); + + return true; } -bool RequestParser::parseContent(const QByteArray &data) +bool RequestParser::parsePostMessage(const QByteArray &data) { - // Parse message content - qDebug() << Q_FUNC_INFO << "Content-Length: " << m_request.headers["content-length"]; - qDebug() << Q_FUNC_INFO << "data.size(): " << data.size(); - - // Parse url-encoded POST data - if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded")) { - QUrl url; - url.setQuery(data); - QListIterator > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded)); + // parse POST message-body + const QString contentType = m_request.headers[HEADER_CONTENT_TYPE]; + const QString contentTypeLower = contentType.toLower(); + + // application/x-www-form-urlencoded + if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) { + QListIterator i(QUrlQuery(data).queryItems(QUrl::FullyDecoded)); while (i.hasNext()) { - QPair pair = i.next(); + const QStringPair pair = i.next(); m_request.posts[pair.first] = 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 contentType = m_request.headers["content-type"]; - if (contentType.startsWith("multipart/form-data")) { - const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\""); - const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)"); - - QByteArray boundary; - if (boundaryRegexQuoted.indexIn(contentType) < 0) { - if (boundaryRegexNotQuoted.indexIn(contentType) < 0) { - qWarning() << "Could not find boundary in multipart/form-data header!"; - return false; - } - else { - boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1(); - } + // multipart/form-data + if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA)) { + // [rfc2046] 5.1.1. Common Syntax + + // find boundary delimiter + const QLatin1String boundaryFieldName("boundary="); + const int idx = contentType.indexOf(boundaryFieldName); + if (idx < 0) { + qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!"; + return false; } - else { - boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1(); + + const QByteArray delimiter = Utils::String::unquote(contentType.midRef(idx + boundaryFieldName.size())).toLatin1(); + if (delimiter.isEmpty()) { + qWarning() << Q_FUNC_INFO << "boundary delimiter field emtpy!"; + return false; + } + + // split data by "dash-boundary" + const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF; + QList multipart = splitToViews(data, dashDelimiter, QString::SkipEmptyParts); + if (multipart.isEmpty()) { + qWarning() << Q_FUNC_INFO << "multipart empty"; + return false; } - qDebug() << "Boundary is " << boundary; - QList parts = splitMultipartData(data, boundary); - qDebug() << parts.size() << "parts in data"; + // remove the ending delimiter + const QByteArray endDelimiter = QByteArray("--") + delimiter + QByteArray("--") + CRLF; + multipart.push_back(viewWithoutEndingWith(multipart.takeLast(), endDelimiter)); - foreach (const QByteArray& part, parts) { + for (const auto &part : multipart) { if (!parseFormData(part)) return false; } @@ -273,71 +289,60 @@ Submit Query return true; } - qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(contentType); + qWarning() << Q_FUNC_INFO << "unknown content type:" << contentType; return false; } bool RequestParser::parseFormData(const QByteArray &data) { - // Parse form data header - const int headerEnd = data.indexOf(EOH); - if (headerEnd < 0) { - qDebug() << "Invalid form data: \n" << data; - return false; - } - - QString headerStr = QString::fromUtf8(data.left(headerEnd)); - QStringList lines = headerStr.trimmed().split(EOL); - QStringMap headers; - foreach (const QString& line, lines) { - QPair header; - if (!parseHeaderLine(line, header)) - return false; + const QList list = splitToViews(data, EOH, QString::KeepEmptyParts); - 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" << headerStr; + if (list.size() != 2) { + qWarning() << Q_FUNC_INFO << "multipart/form-data format error"; return false; } - if (disposition.contains("filename")) { - UploadedFile ufile; - ufile.filename = disposition["filename"]; - ufile.type = disposition["content-type"]; - ufile.data = data.mid(headerEnd + EOH.length()); + const QString headers = QString::fromLatin1(list[0]); + const QByteArray payload = viewWithoutEndingWith(list[1], CRLF); - m_request.files.append(ufile); - } - else { - m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(headerEnd + EOH.length())); - } + QStringMap headersMap; + const QVector headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts); + for (const auto &line : headerLines) { + if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive)) { + // extract out filename & name + const QVector directives = line.split(';', QString::SkipEmptyParts); - return true; -} + for (const auto &directive : directives) { + const int idx = directive.indexOf('='); + if (idx < 0) + continue; -bool RequestParser::parseHeaderValue(const QString &value, QStringMap &out) -{ - int pos = value.indexOf(QLatin1Char(';')); - if (pos == -1) { - out[""] = value.trimmed(); - return true; + const QString name = directive.left(idx).trimmed().toString().toLower(); + const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString(); + headersMap[name] = value; + } + } + else { + if (!parseHeaderLine(line.toString(), headersMap)) + return false; + } } - out[""] = value.left(pos).trimmed(); + // pick data + const QLatin1String filename("filename"); + const QLatin1String name("name"); - QRegExp rx(";\\s*([^=;\"]+)\\s*=\\s*(\"[^\"]*\"|[^\";\\s]+)\\s*"); - while (rx.indexIn(value, pos) == pos) { - out[rx.cap(1).trimmed()] = unquoted(rx.cap(2)); - pos += rx.cap(0).length(); + if (headersMap.contains(filename)) { + m_request.files.append({filename, headersMap[HEADER_CONTENT_TYPE], payload}); } - - if (pos != value.length()) + else if (headersMap.contains(name)) { + m_request.posts[headersMap[name]] = payload; + } + else { + // malformed + qWarning() << Q_FUNC_INFO << "multipart/form-data header error"; return false; + } return true; } diff --git a/src/base/http/requestparser.h b/src/base/http/requestparser.h index 9642bbf9e..b1e1d8cca 100644 --- a/src/base/http/requestparser.h +++ b/src/base/http/requestparser.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * @@ -37,32 +38,35 @@ namespace Http class RequestParser { public: - enum ErrorCode + enum class ParseStatus { - NoError = 0, - IncompleteRequest, + OK, + Incomplete, BadRequest }; - // when result != NoError parsed request is undefined - // Warning! Header names are converted to lower-case. - static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */); + struct ParseResult + { + // when `status != ParseStatus::OK`, `request` & `frameSize` are undefined + ParseStatus status; + Request request; + long frameSize; // http request frame size (bytes) + }; + + static ParseResult parse(const QByteArray &data); + + static const long MAX_CONTENT_SIZE = 64 * 1024 * 1024; // 64 MB private: - RequestParser(uint maxContentLength); + RequestParser(); - ErrorCode parseHttpRequest(const QByteArray &data, Request &request); + ParseResult doParse(const QByteArray &data); + bool parseStartLines(const QString &data); + bool parseRequestLine(const QString &line); - bool parseHttpHeader(const QByteArray &data); - bool parseStartingLine(const QString &line); - bool parseContent(const QByteArray &data); + bool parsePostMessage(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 m_maxContentLength; Request m_request; }; } diff --git a/src/base/http/types.h b/src/base/http/types.h index b7d5c9586..41b72b3f3 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2014 Vladimir Golovnev * * This program is free software; you can redistribute it and/or @@ -41,6 +42,8 @@ namespace Http const char METHOD_POST[] = "POST"; const char HEADER_CACHE_CONTROL[] = "cache-control"; + const char HEADER_CONNECTION[] = "connection"; + const char HEADER_CONTENT_DISPOSITION[] = "content-disposition"; const char HEADER_CONTENT_ENCODING[] = "content-encoding"; const char HEADER_CONTENT_LENGTH[] = "content-length"; const char HEADER_CONTENT_SECURITY_POLICY[] = "content-security-policy"; @@ -55,6 +58,10 @@ namespace Http const char HEADER_X_FRAME_OPTIONS[] = "x-frame-options"; const char HEADER_X_XSS_PROTECTION[] = "x-xss-protection"; + const char HEADER_REQUEST_METHOD_GET[] = "GET"; + const char HEADER_REQUEST_METHOD_HEAD[] = "HEAD"; + const char HEADER_REQUEST_METHOD_POST[] = "POST"; + const char CONTENT_TYPE_HTML[] = "text/html"; const char CONTENT_TYPE_JS[] = "application/javascript"; const char CONTENT_TYPE_JSON[] = "application/json"; @@ -64,8 +71,10 @@ namespace Http const char CONTENT_TYPE_PNG[] = "image/png"; const char CONTENT_TYPE_TXT[] = "text/plain"; const char CONTENT_TYPE_SVG[] = "image/svg+xml"; + const char CONTENT_TYPE_FORM_ENCODED[] = "application/x-www-form-urlencoded"; + const char CONTENT_TYPE_FORM_DATA[] = "multipart/form-data"; - // portability: "\r\n" doesn't guarantee mapping to the correct value + // portability: "\r\n" doesn't guarantee mapping to the correct symbol const char CRLF[] = {0x0D, 0x0A, '\0'}; struct Environment @@ -79,13 +88,14 @@ namespace Http struct UploadedFile { - QString filename; // original filename - QString type; // MIME type - QByteArray data; // File data + QString filename; + QString type; // MIME type + QByteArray data; }; struct Request { + QString version; QString method; QString path; QStringMap headers; From f34dfca5e6c8b04d7c3ce7501bf508a8a7f2a815 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 23 Feb 2018 14:41:08 +0800 Subject: [PATCH 2/2] Enable Http/1.1 persistence connection This enables reusing existing TCP connection instead of opening a new connection for every request --- src/base/http/connection.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/base/http/connection.cpp b/src/base/http/connection.cpp index fe74e7974..4ade26155 100644 --- a/src/base/http/connection.cpp +++ b/src/base/http/connection.cpp @@ -101,12 +101,10 @@ void Connection::read() if (acceptsGzipEncoding(result.request.headers["accept-encoding"])) resp.headers[HEADER_CONTENT_ENCODING] = "gzip"; - resp.headers[HEADER_CONNECTION] = "close"; + resp.headers[HEADER_CONNECTION] = "keep-alive"; sendResponse(resp); m_receivedData = m_receivedData.mid(result.frameSize); - - m_socket->close(); } break;