From 9853a9fec99e1599c5d6940629b118d418db9998 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 22 Aug 2014 22:07:19 +0400 Subject: [PATCH] Modify Http parser/generator classes. --- src/webui/httpheader.cpp | 161 ------------- src/webui/httpheader.h | 75 ------ src/webui/httprequestheader.cpp | 112 --------- src/webui/httprequestheader.h | 59 ----- src/webui/httprequestparser.cpp | 362 ++++++++++++++++++++-------- src/webui/httprequestparser.h | 52 ++-- src/webui/httpresponsegenerator.cpp | 117 ++++----- src/webui/httpresponsegenerator.h | 23 +- src/webui/httpresponseheader.cpp | 131 ---------- src/webui/httpresponseheader.h | 56 ----- src/webui/httptypes.h | 89 +++++++ 11 files changed, 439 insertions(+), 798 deletions(-) delete mode 100644 src/webui/httpheader.cpp delete mode 100644 src/webui/httpheader.h delete mode 100644 src/webui/httprequestheader.cpp delete mode 100644 src/webui/httprequestheader.h delete mode 100644 src/webui/httpresponseheader.cpp delete mode 100644 src/webui/httpresponseheader.h create mode 100644 src/webui/httptypes.h diff --git a/src/webui/httpheader.cpp b/src/webui/httpheader.cpp deleted file mode 100644 index e681c25b1..000000000 --- a/src/webui/httpheader.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#include "httpheader.h" - -HttpHeader::HttpHeader() : m_valid(true) {} - -HttpHeader::HttpHeader(const QString &str) : m_valid(true) { - parse(str); -} - -HttpHeader::~HttpHeader() {} - -void HttpHeader::addValue(const QString &key, const QString &value) { - m_headers.insert(key.toLower(), value.trimmed()); -} - -QStringList HttpHeader::allValues(const QString &key) const { - QStringList list = m_headers.values(key); - return list; -} - -uint HttpHeader::contentLength() const { - QString lenVal = m_headers.value("content-length"); - return lenVal.toUInt(); -} - -QString HttpHeader::contentType() const { - QString str = m_headers.value("content-type"); - // content-type might have a parameter so we need to strip it. - // eg. application/x-www-form-urlencoded; charset=utf-8 - int index = str.indexOf(';'); - if (index == -1) - return str; - else - return str.left(index); -} - -bool HttpHeader::hasContentLength() const { - return m_headers.contains("content-length"); -} - -bool HttpHeader::hasContentType() const { - return m_headers.contains("content-type"); -} - -bool HttpHeader::hasKey(const QString &key) const { - return m_headers.contains(key.toLower()); -} - -bool HttpHeader::isValid() const { - return m_valid; -} - -QStringList HttpHeader::keys() const { - QStringList list = m_headers.keys(); - return list; -} - -void HttpHeader::removeAllValues(const QString &key) { - m_headers.remove(key); -} - -void HttpHeader::removeValue(const QString &key) { - m_headers.remove(key); -} - -void HttpHeader::setContentLength(int len) { - m_headers.replace("content-length", QString::number(len)); -} - -void HttpHeader::setContentType(const QString &type) { - m_headers.replace("content-type", type.trimmed()); -} - -void HttpHeader::setValue(const QString &key, const QString &value) { - m_headers.replace(key.toLower(), value.trimmed()); -} - -void HttpHeader::setValues(const QList > &values) { - for (int i=0; i < values.size(); ++i) { - setValue(values[i].first, values[i].second); - } -} - -QString HttpHeader::toString() const { - QString str; - typedef QMultiHash::const_iterator h_it; - - for (h_it it = m_headers.begin(), itend = m_headers.end(); - it != itend; ++it) { - str = str + it.key() + ": " + it.value() + "\r\n"; - } - - str += "\r\n"; - return str; -} - -QString HttpHeader::value(const QString &key) const { - return m_headers.value(key.toLower()); -} - -QList > HttpHeader::values() const { - QList > list; - typedef QMultiHash::const_iterator h_it; - - for (h_it it = m_headers.begin(), itend = m_headers.end(); - it != itend; ++it) { - list.append(qMakePair(it.key(), it.value())); - } - - return list; -} - -void HttpHeader::parse(const QString &str) { - QStringList headers = str.split("\r\n", QString::SkipEmptyParts, Qt::CaseInsensitive); - for (int i=0; i < headers.size(); ++i) { - int index = headers[i].indexOf(':', Qt::CaseInsensitive); - if (index == -1) { - setValid(false); - break; - } - - QString key = headers[i].left(index); - QString value = headers[i].right(headers[i].size() - index - 1); - m_headers.insert(key.toLower(), value.trimmed()); - } -} - -void HttpHeader::setValid(bool valid) { - m_valid = valid; - if (!m_valid) - m_headers.clear(); -} diff --git a/src/webui/httpheader.h b/src/webui/httpheader.h deleted file mode 100644 index 7a7ac8b2f..000000000 --- a/src/webui/httpheader.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#ifndef HTTPHEADER_H -#define HTTPHEADER_H - -#include -#include -#include -#include - -class HttpHeader { - -public: - HttpHeader(); - HttpHeader(const QString &str); - virtual ~HttpHeader(); - void addValue(const QString &key, const QString &value); - QStringList allValues(const QString &key) const; - uint contentLength() const; - QString contentType() const; - bool hasContentLength() const; - bool hasContentType() const; - bool hasKey(const QString &key) const; - bool isValid() const; - QStringList keys() const; - virtual int majorVersion() const =0; - virtual int minorVersion() const =0; - void removeAllValues(const QString &key); - void removeValue(const QString &key); - void setContentLength(int len); - void setContentType(const QString &type); - void setValue(const QString &key, const QString &value); - void setValues(const QList > &values); - virtual QString toString() const; - QString value(const QString &key) const; - QList > values() const; - -protected: - void parse(const QString &str); - void setValid(bool valid = true); - -private: - QMultiHash m_headers; - bool m_valid; -}; - -#endif // HTTPHEADER_H diff --git a/src/webui/httprequestheader.cpp b/src/webui/httprequestheader.cpp deleted file mode 100644 index f799ac8e3..000000000 --- a/src/webui/httprequestheader.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#include "httprequestheader.h" - - -HttpRequestHeader::HttpRequestHeader() : HttpHeader(), m_majorVersion(1), m_minorVersion(1) {} - -HttpRequestHeader::HttpRequestHeader(const QString &method, const QString &path, int majorVer, int minorVer) : - HttpHeader(), m_method(method), m_path(path), m_majorVersion(majorVer), m_minorVersion(minorVer) {} - -HttpRequestHeader::HttpRequestHeader(const QString &str): HttpHeader() { - int line = str.indexOf("\r\n", 0, Qt::CaseInsensitive); - QString req = str.left(line); - QString headers = str.right(str.size() - line - 2); //"\r\n" == 2 - QStringList method = req.split(" ", QString::SkipEmptyParts, Qt::CaseInsensitive); - if (method.size() != 3) - setValid(false); - else { - m_method = method[0]; - m_path = method[1]; - if (parseVersions(method[2])) - parse(headers); - else - setValid(false); - } -} - -int HttpRequestHeader::majorVersion() const { - return m_majorVersion; -} - -int HttpRequestHeader::minorVersion() const { - return m_minorVersion; -} - -QString HttpRequestHeader::method() const { - return m_method; -} - -QString HttpRequestHeader::path() const { - return m_path; -} - -void HttpRequestHeader::setRequest(const QString &method, const QString &path, int majorVer, int minorVer) { - m_method = method; - m_path = path; - m_majorVersion = majorVer; - m_minorVersion = minorVer; -} - -QString HttpRequestHeader::toString() const { - QString str = m_method + " "; - str+= m_path + " "; - str+= "HTTP/" + QString::number(m_majorVersion) + "." + QString::number(m_minorVersion) + "\r\n"; - str += HttpHeader::toString(); - return str; -} - -bool HttpRequestHeader::parseVersions(const QString &str) { - if (str.size() <= 5) // HTTP/ which means version missing - return false; - - if (str.left(4) != "HTTP") - return false; - - QString versions = str.right(str.size() - 5); // Strip "HTTP/" - - int decPoint = versions.indexOf('.'); - if (decPoint == -1) - return false; - - bool ok; - - m_majorVersion = versions.left(decPoint).toInt(&ok); - if (!ok) - return false; - - m_minorVersion = versions.right(versions.size() - decPoint - 1).toInt(&ok); - if (!ok) - return false; - - return true; - -} diff --git a/src/webui/httprequestheader.h b/src/webui/httprequestheader.h deleted file mode 100644 index 0723eca60..000000000 --- a/src/webui/httprequestheader.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#ifndef HTTPREQUESTHEADER_H -#define HTTPREQUESTHEADER_H - -#include "httpheader.h" - -class HttpRequestHeader: public HttpHeader { -public: - HttpRequestHeader(); - // The path argument must be properly encoded for an HTTP request. - HttpRequestHeader(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); - HttpRequestHeader(const QString &str); - virtual int majorVersion() const; - virtual int minorVersion() const; - QString method() const; - // This is the raw path from the header. No decoding/encoding is performed. - QString path() const; - // The path argument must be properly encoded for an HTTP request. - void setRequest(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); - virtual QString toString() const; - -private: - bool parseVersions(const QString &str); - QString m_method; - QString m_path; - int m_majorVersion; - int m_minorVersion; -}; - -#endif // HTTPREQUESTHEADER_H diff --git a/src/webui/httprequestparser.cpp b/src/webui/httprequestparser.cpp index 4f185c7b3..262e93123 100644 --- a/src/webui/httprequestparser.cpp +++ b/src/webui/httprequestparser.cpp @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,110 +25,231 @@ * 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 "httprequestparser.h" +#include #include +//#include #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include #endif +#include +#include #include +#include "httprequestparser.h" + +const QByteArray EOL("\r\n"); +const QByteArray EOH("\r\n\r\n"); -HttpRequestParser::HttpRequestParser(): m_error(false) +inline QString unquoted(const QString& str) { + if ((str[0] == '\"') && (str[str.length() - 1] == '\"')) + return str.mid(1, str.length() - 2); + + return str; } -HttpRequestParser::~HttpRequestParser() +HttpRequestParser::ErrorCode HttpRequestParser::parse(const QByteArray& data, HttpRequest& request, uint maxContentLength) { + return HttpRequestParser(maxContentLength).parseHttpRequest(data, request); } -bool HttpRequestParser::isError() const { - return m_error; +HttpRequestParser::HttpRequestParser(uint maxContentLength) + : maxContentLength_(maxContentLength) +{ } -const QString& HttpRequestParser::url() const { - return m_path; -} +HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArray& data, HttpRequest& request) +{ + request_ = HttpRequest(); -const QByteArray& HttpRequestParser::message() const { - return m_data; -} + // Parse HTTP request header + const int header_end = data.indexOf(EOH); + if (header_end < 0) + { + qDebug() << Q_FUNC_INFO << "incomplete request"; + return IncompleteRequest; + } -QString HttpRequestParser::get(const QString& key) const { - return m_getMap.value(key); -} + if (!parseHttpHeader(data.left(header_end))) + { + qWarning() << Q_FUNC_INFO << "header parsing error"; + return BadRequest; + } -QString HttpRequestParser::post(const QString& key) const { - return m_postMap.value(key); -} + // 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(maxContentLength_)) + { + qWarning() << Q_FUNC_INFO << "bad request: message too long"; + return BadRequest; + } -const QList& HttpRequestParser::torrents() const { - return m_torrents; + 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; } -void HttpRequestParser::writeHeader(const QByteArray& ba) { - m_error = false; - // Parse header - m_header = HttpRequestHeader(ba); - QUrl url = QUrl::fromEncoded(m_header.path().toLatin1()); - m_path = url.path(); +bool HttpRequestParser::parseStartingLine(const QString &line) +{ + const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$"); - // Parse GET parameters -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) - QUrlQuery query(url); - QListIterator > i(query.queryItems()); + 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 > i(url.queryItems()); #else - QListIterator > i(url.queryItems()); + QListIterator > i(QUrlQuery(url).queryItems()); #endif - while (i.hasNext()) { - QPair pair = i.next(); - m_getMap[pair.first] = pair.second; + while (i.hasNext()) + { + QPair 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& 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 header; + if (!parseHeaderLine(*it, header)) + return false; + + request_.headers[header.first] = header.second; } + + return true; } -static QList splitRawData(QByteArray rawData, const QByteArray& sep) +QList HttpRequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary) { QList ret; + QByteArray sep = boundary + EOL; const int sepLength = sep.size(); - int index = 0; - while ((index = rawData.indexOf(sep)) >= 0) { - ret << rawData.left(index); - rawData = rawData.mid(index + sepLength); + + 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; } -void HttpRequestParser::writeMessage(const QByteArray& ba) { +bool HttpRequestParser::parseContent(const QByteArray& data) +{ // Parse message content - Q_ASSERT (m_header.hasContentLength()); - m_error = false; - m_data = ba; - qDebug() << Q_FUNC_INFO << "m_data.size(): " << m_data.size(); + qDebug() << Q_FUNC_INFO << "Content-Length: " << request_.headers["content-length"]; + qDebug() << Q_FUNC_INFO << "data.size(): " << data.size(); - // Parse POST data - if (m_header.contentType() == "application/x-www-form-urlencoded") { + // 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)) - QString tmp(m_data); - QUrlQuery query(tmp); - QListIterator > i(query.queryItems(QUrl::FullyDecoded)); -#else - url.setEncodedQuery(m_data); +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) + url.setEncodedQuery(data); QListIterator > i(url.queryItems()); +#else + url.setQuery(data); + QListIterator > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded)); #endif - while (i.hasNext()) { + while (i.hasNext()) + { QPair pair = i.next(); - m_postMap[pair.first] = pair.second; + request_.posts[pair.first] = pair.second; } - return; + + return true; } // Parse multipart/form data (torrent file) /** - m_data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5") + data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5") --cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5 Content-Disposition: form-data; name=\"Filename\" @@ -144,58 +266,108 @@ Content-Disposition: form-data; name=\"Upload\" Submit Query --cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5-- **/ - if (m_header.contentType().startsWith("multipart/form-data")) { - qDebug() << Q_FUNC_INFO << "header is: " << m_header.toString(); - static QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\""); - static QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)"); + 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(m_header.toString()) < 0) { - if (boundaryRegexNotQuoted.indexIn(m_header.toString()) < 0) { + if (boundaryRegexQuoted.indexIn(content_type) < 0) + { + if (boundaryRegexNotQuoted.indexIn(content_type) < 0) + { qWarning() << "Could not find boundary in multipart/form-data header!"; - m_error = true; - return; - } else { + return false; + } + else + { boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1(); } - } else { + } + else + { boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1(); } + qDebug() << "Boundary is " << boundary; - QList parts = splitRawData(m_data, boundary); + QList parts = splitMultipartData(data, boundary); qDebug() << parts.size() << "parts in data"; - foreach (const QByteArray& part, parts) { - const int filenameIndex = part.indexOf("filename="); - if (filenameIndex < 0) - continue; - qDebug() << "Found a torrent"; - m_torrents << part.mid(part.indexOf("\r\n\r\n", filenameIndex + 9) + 4); + + foreach (const QByteArray& part, parts) + { + if (!parseFormData(part)) + return false; } + + return true; } -} -bool HttpRequestParser::acceptsEncoding() { - QString encoding = m_header.value("Accept-Encoding"); + qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type); + return false; +} - int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive); - if (pos == -1) +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; + } - // Let's see if there's a qvalue of 0.0 following - if (encoding[pos+4] != ';') //there isn't, so it accepts gzip anyway - return true; - - //So let's find = and the next comma - pos = encoding.indexOf("=", pos+4, Qt::CaseInsensitive); - int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive); + QString header_str = QString::fromUtf8(data.left(header_end)); + QStringList lines = header_str.trimmed().split(EOL); + QStringMap headers; + foreach (const QString& line, lines) + { + QPair header; + if (!parseHeaderLine(line, header)) + return false; - QString value; - if (comma_pos == -1) - value = encoding.mid(pos+1, comma_pos); - else - value = encoding.mid(pos+1, comma_pos-(pos+1)); + headers[header.first] = header.second; + } - if (value.toDouble() == 0.0) + 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 - return true; + { + 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; } diff --git a/src/webui/httprequestparser.h b/src/webui/httprequestparser.h index 180f3c1cf..55a21be36 100644 --- a/src/webui/httprequestparser.h +++ b/src/webui/httprequestparser.h @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,41 +25,38 @@ * 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 */ - #ifndef HTTPREQUESTPARSER_H #define HTTPREQUESTPARSER_H -#include -#include "httprequestheader.h" - -class HttpRequestParser { +#include "httptypes.h" +class HttpRequestParser +{ public: - HttpRequestParser(); - ~HttpRequestParser(); - bool isError() const; - const QString& url() const; - const QByteArray& message() const; - QString get(const QString& key) const; - QString post(const QString& key) const; - const QList& torrents() const; - void writeHeader(const QByteArray& ba); - void writeMessage(const QByteArray& ba); - bool acceptsEncoding(); - inline const HttpRequestHeader& header() const { return m_header; } + enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest }; + + // when result != NoError parsed request is undefined + // Warning! Header names are converted to lower-case. + static ErrorCode parse(const QByteArray& data, HttpRequest& request, uint maxContentLength = 10000000 /* ~10MB */); private: - HttpRequestHeader m_header; - bool m_error; - QByteArray m_data; - QString m_path; - QHash m_postMap; - QHash m_getMap; - QList m_torrents; + HttpRequestParser(uint maxContentLength); + + ErrorCode parseHttpRequest(const QByteArray& data, HttpRequest& request); + + bool parseHttpHeader(const QByteArray& data); + bool parseStartingLine(const QString &line); + bool parseContent(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 maxContentLength_; + HttpRequest request_; }; #endif diff --git a/src/webui/httpresponsegenerator.cpp b/src/webui/httpresponsegenerator.cpp index f5ace37fe..9ebb14d41 100644 --- a/src/webui/httpresponsegenerator.cpp +++ b/src/webui/httpresponsegenerator.cpp @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,48 +25,52 @@ * 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 "httpresponsegenerator.h" #include +#include "httpresponsegenerator.h" -void HttpResponseGenerator::setMessage(const QByteArray& message) -{ - m_message = message; -} +bool gCompress(QByteArray data, QByteArray& dest_buffer); -void HttpResponseGenerator::setMessage(const QString& message) +QByteArray HttpResponseGenerator::generate(HttpResponse response) { - setMessage(message.toUtf8()); -} + if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") + { + // A gzip seems to have 23 bytes overhead. + // Also "Content-Encoding: gzip\r\n" is 26 bytes long + // So we only benefit from gzip if the message is bigger than 23+26 = 49 + // If the message is smaller than 49 bytes we actually send MORE data if we gzip + QByteArray dest_buf; + if ((response.content.size() > 49) && (gCompress(response.content, dest_buf))) + { + response.content = dest_buf; + } + else + { + response.headers.remove(HEADER_CONTENT_ENCODING); + } + } + + if (response.content.length() > 0) + response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length()); + + QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n")); + + QString header; + foreach (const QString& key, response.headers.keys()) + header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]); -void HttpResponseGenerator::setContentTypeByExt(const QString& ext) { - if (ext == "css") { - setContentType("text/css"); - return; - } - if (ext == "gif") { - setContentType("image/gif"); - return; - } - if (ext == "htm" || ext == "html") { - setContentType("text/html"); - return; - } - if (ext == "js") { - setContentType("text/javascript"); - return; - } - if (ext == "png") { - setContentType("image/png"); - return; - } + ret = ret.arg(response.status.code).arg(response.status.text).arg(header); + +// qDebug() << Q_FUNC_INFO; +// qDebug() << "HTTP Response header:"; +// qDebug() << ret; + + return ret.toUtf8() + response.content; } -bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { +bool gCompress(QByteArray data, QByteArray& dest_buffer) +{ static const int BUFSIZE = 128 * 1024; char tmp_buf[BUFSIZE]; int ret; @@ -74,8 +79,8 @@ bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; - strm.next_in = reinterpret_cast(m_message.data()); - strm.avail_in = m_message.length(); + strm.next_in = reinterpret_cast(data.data()); + strm.avail_in = data.length(); strm.next_out = reinterpret_cast(tmp_buf); strm.avail_out = BUFSIZE; @@ -88,53 +93,37 @@ bool HttpResponseGenerator::gCompress(QByteArray &dest_buffer) { return false; while (strm.avail_in != 0) - { + { ret = deflate(&strm, Z_NO_FLUSH); if (ret != Z_OK) return false; + if (strm.avail_out == 0) { - dest_buffer.append(tmp_buf, BUFSIZE); - strm.next_out = reinterpret_cast(tmp_buf); - strm.avail_out = BUFSIZE; + dest_buffer.append(tmp_buf, BUFSIZE); + strm.next_out = reinterpret_cast(tmp_buf); + strm.avail_out = BUFSIZE; } - } + } int deflate_res = Z_OK; - while (deflate_res == Z_OK) { - if (strm.avail_out == 0) { + while (deflate_res == Z_OK) + { + if (strm.avail_out == 0) + { dest_buffer.append(tmp_buf, BUFSIZE); strm.next_out = reinterpret_cast(tmp_buf); strm.avail_out = BUFSIZE; } + deflate_res = deflate(&strm, Z_FINISH); } if (deflate_res != Z_STREAM_END) return false; + dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out); deflateEnd(&strm); return true; } - -QByteArray HttpResponseGenerator::toByteArray() { - // A gzip seems to have 23 bytes overhead. - // Also "content-encoding: gzip\r\n" is 26 bytes long - // So we only benefit from gzip if the message is bigger than 23+26 = 49 - // If the message is smaller than 49 bytes we actually send MORE data if we gzip - if (m_gzip && m_message.size() > 49) { - QByteArray dest_buf; - if (gCompress(dest_buf)) { - setValue("content-encoding", "gzip"); -#if QT_VERSION < 0x040800 - m_message = dest_buf; -#else - m_message.swap(dest_buf); -#endif - } - } - - setContentLength(m_message.size()); - return HttpResponseHeader::toString().toUtf8() + m_message; -} diff --git a/src/webui/httpresponsegenerator.h b/src/webui/httpresponsegenerator.h index 4d8525a26..057016589 100644 --- a/src/webui/httpresponsegenerator.h +++ b/src/webui/httpresponsegenerator.h @@ -1,5 +1,6 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Ishan Arora and Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -24,32 +25,18 @@ * 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 */ #ifndef HTTPRESPONSEGENERATOR_H #define HTTPRESPONSEGENERATOR_H -#include "httpresponseheader.h" +#include "httptypes.h" -class HttpResponseGenerator : public HttpResponseHeader +class HttpResponseGenerator { - public: - HttpResponseGenerator(): m_gzip(false) {} - void setMessage(const QByteArray& message); - void setMessage(const QString& message); - void setContentTypeByExt(const QString& ext); - void setContentEncoding(bool gzip) { m_gzip = gzip; } - QByteArray toByteArray(); - -private: - bool gCompress(QByteArray &dest_buffer); - QByteArray m_message; - bool m_gzip; - + static QByteArray generate(HttpResponse response); }; #endif diff --git a/src/webui/httpresponseheader.cpp b/src/webui/httpresponseheader.cpp deleted file mode 100644 index f95abc20d..000000000 --- a/src/webui/httpresponseheader.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#include "httpresponseheader.h" - -HttpResponseHeader::HttpResponseHeader(): HttpHeader(), m_code(200), m_majorVersion(1), m_minorVersion(1) {} - -HttpResponseHeader::HttpResponseHeader(int code, const QString &text, int majorVer, int minorVer): - HttpHeader(), m_code(code), m_text(text), m_majorVersion(majorVer), m_minorVersion(minorVer) {} - -HttpResponseHeader::HttpResponseHeader(const QString &str): HttpHeader() { - int line = str.indexOf("\r\n", 0, Qt::CaseInsensitive); - QString res = str.left(line); - QString headers = str.right(str.size() - line - 2); //"\r\n" == 2 - QStringList status = res.split(" ", QString::SkipEmptyParts, Qt::CaseInsensitive); - if (status.size() != 3 || status.size() != 2) //reason-phrase could be empty - setValid(false); - else { - if (parseVersions(status[0])) { - bool ok; - m_code = status[1].toInt(&ok); - if (ok) { - m_text = status[2]; - parse(headers); - } - else - setValid(false); - } - else - setValid(false); - } -} - -int HttpResponseHeader::majorVersion() const { - return m_majorVersion; -} - -int HttpResponseHeader::minorVersion() const { - return m_minorVersion; -} - -QString HttpResponseHeader::reasonPhrase() const { - return m_text; -} - -void HttpResponseHeader::setStatusLine(int code, const QString &text, int majorVer, int minorVer) { - m_code = code; - m_text = text; - m_majorVersion = majorVer; - m_minorVersion = minorVer; -} - -int HttpResponseHeader::statusCode() const { - return m_code; -} - -QString HttpResponseHeader::toString() const { - QString str = "HTTP/" + QString::number(m_majorVersion) + "." + QString::number(m_minorVersion) + " "; - - QString code = QString::number(m_code); - if (code.size() > 3) { - str+= code.left(3) + " "; - } - else if (code.size() < 3) { - int padding = 3 - code.size(); - for (int i = 0; i < padding; ++i) - code.push_back("0"); - str += code + " "; - } - else { - str += code + " "; - } - - str += m_text + "\r\n"; - str += HttpHeader::toString(); - return str; -} - -bool HttpResponseHeader::parseVersions(const QString &str) { - if (str.size() <= 5) // HTTP/ which means version missing - return false; - - if (str.left(4) != "HTTP") - return false; - - QString versions = str.right(str.size() - 5); // Strip "HTTP/" - - int decPoint = versions.indexOf('.'); - if (decPoint == -1) - return false; - - bool ok; - - m_majorVersion = versions.left(decPoint).toInt(&ok); - if (!ok) - return false; - - m_minorVersion = versions.right(versions.size() - decPoint - 1).toInt(&ok); - if (!ok) - return false; - - return true; - -} diff --git a/src/webui/httpresponseheader.h b/src/webui/httpresponseheader.h deleted file mode 100644 index 4a54ef5a9..000000000 --- a/src/webui/httpresponseheader.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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 : hammered999@gmail.com - */ - -#ifndef HTTPRESPONSEHEADER_H -#define HTTPRESPONSEHEADER_H - -#include "httpheader.h" - -class HttpResponseHeader: public HttpHeader { -public: - HttpResponseHeader(); - HttpResponseHeader(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); - HttpResponseHeader(const QString &str); - virtual int majorVersion() const; - virtual int minorVersion() const; - QString reasonPhrase() const; - void setStatusLine(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); - int statusCode() const; - virtual QString toString() const; - -private: - bool parseVersions(const QString &str); - int m_code; - QString m_text; - int m_majorVersion; - int m_minorVersion; -}; - -#endif // HTTPRESPONSEHEADER_H diff --git a/src/webui/httptypes.h b/src/webui/httptypes.h new file mode 100644 index 000000000..614e9a4d2 --- /dev/null +++ b/src/webui/httptypes.h @@ -0,0 +1,89 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * 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. + */ + +#ifndef HTTPTYPES_H +#define HTTPTYPES_H + +#include +#include +#include + +typedef QMap QStringMap; + +const QString HEADER_SET_COOKIE = "Set-Cookie"; +const QString HEADER_CONTENT_TYPE = "Content-Type"; +const QString HEADER_CONTENT_ENCODING = "Content-Encoding"; +const QString HEADER_CONTENT_LENGTH = "Content-Length"; + +const QString CONTENT_TYPE_CSS = "text/css"; +const QString CONTENT_TYPE_GIF = "image/gif"; +const QString CONTENT_TYPE_HTML = "text/html"; +const QString CONTENT_TYPE_JS = "text/javascript"; +const QString CONTENT_TYPE_PNG = "image/png"; +const QString CONTENT_TYPE_TXT = "text/plain"; + +struct HttpEnvironment +{ + QHostAddress clientAddress; +}; + +struct UploadedFile +{ + QString filename; // original filename + QString type; // MIME type + QByteArray data; // File data +}; + +struct HttpRequest +{ + QString method; + QString path; + QStringMap headers; + QStringMap gets; + QStringMap posts; + QMap files; +}; + +struct HttpResponseStatus +{ + uint code; + QString text; + + HttpResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {} +}; + +struct HttpResponse +{ + HttpResponseStatus status; + QStringMap headers; + QByteArray content; + + HttpResponse(uint code = 200, const QString& text = "OK"): status(code, text) {} +}; + +#endif // HTTPTYPES_H