diff --git a/src/util/qhttpparser.cpp b/src/util/qhttpparser.cpp index cc8acf9..bdf7c8a 100644 --- a/src/util/qhttpparser.cpp +++ b/src/util/qhttpparser.cpp @@ -26,9 +26,8 @@ #include "qhttpparser.h" -QList QHttpParser::split(const QByteArray &data, const QByteArray &delim, int maxSplit) +void QHttpParser::split(const QByteArray &data, const QByteArray &delim, int maxSplit, QList &parts) { - QList parts; int index = 0; for(int i = 0; !maxSplit || i < maxSplit; ++i) { @@ -44,57 +43,37 @@ QList QHttpParser::split(const QByteArray &data, const QByteArray &d // Append whatever remains to the list parts.append(data.mid(index)); - - return parts; } -bool QHttpParser::parseHeaderList(const QList &lines, QList &headers) +bool QHttpParser::parseHeaderList(const QList &lines, QHttpHeaderMap &headers) { - for(QList::const_iterator i = lines.constBegin(); - i != lines.constEnd(); ++i) { + for(QList::const_iterator i = lines.constBegin(); i != lines.constEnd(); ++i) { - QList parts = split(*i, ":", 1); + QList parts; + split(*i, ":", 1, parts); + + // Ensure that the delimiter (":") was encountered at least once if(parts.count() != 2) { return false; } - headers.append(QHttpHeader(parts[0].trimmed(), parts[1].trimmed())); + headers.insert(parts[0].trimmed(), parts[1].trimmed()); } return true; } -bool QHttpParser::parseRequest(const QByteArray &data, QByteArray &method, QByteArray &path, QList &headers) +bool QHttpParser::parseHeaders(const QByteArray &data, QList &parts, QHttpHeaderMap &headers) { // Split the data into individual lines - QList lines = split(data, "\r\n"); + QList lines; + split(data, "\r\n", 0, lines); - // Separate the status line into its three components - QList parts = split(lines.takeFirst(), " "); + // Split the status line into a maximum of three parts + split(lines.takeFirst(), " ", 2, parts); if(parts.count() != 3) { return false; } - method = parts[0]; - path = parts[1]; - - // Parse the request headers - return parseHeaderList(lines, headers); -} - -bool QHttpParser::parseResponse(const QByteArray &data, QByteArray &statusCode, QList &headers) -{ - // Split the data into individual lines - QList lines = split(data, "\r\n"); - - // Separate the status line into its three components - QList parts = split(lines.takeFirst(), " ", 1); - if(parts.count() != 2) { - return false; - } - - statusCode = parts[1]; - - // Parse the response headers return parseHeaderList(lines, headers); } diff --git a/src/util/qhttpparser.h b/src/util/qhttpparser.h index 52fe4b9..c1ceb2f 100644 --- a/src/util/qhttpparser.h +++ b/src/util/qhttpparser.h @@ -26,12 +26,25 @@ #define QHTTPENGINE_QHTTPPARSER_H #include +#include -#include "../core/qhttpheader.h" #include "config.h" +#include "qibytearray.h" + +/** + * @brief Map consisting of HTTP headers + * + * The key used for the map is the QIByteArray class, which allows for + * case-insensitive comparison. + */ +typedef QMap QHttpHeaderMap; /** * @brief Utility methods for parsing HTTP requests and responses + * + * This class provides a set of static methods for parsing HTTP request and + * response headers. Functionality is broken up into smaller methods in order + * to make the unit tests simpler. */ class QHTTPENGINE_EXPORT QHttpParser { @@ -44,33 +57,27 @@ public: * returned containing the original QByteArray as its only element. The * delimiter must not be empty. * - * If a value is provided for maxSplit, the list will contain no more than - * maxSplit + 1 items. + * If maxSplit is nonzero, the list will contain no more than maxSplit + 1 + * items. */ - static QList split(const QByteArray &data, const QByteArray &delim, int maxSplit = 0); + static void split(const QByteArray &data, const QByteArray &delim, int maxSplit, QList &parts); /** * @brief Parse a list of lines containing HTTP headers + * + * Each line is expected to be in the format "name: value". Parsing is + * immediately aborted if an invalid line is encountered. */ - static bool parseHeaderList(const QList &lines, QList &headers); + static bool parseHeaderList(const QList &lines, QHttpHeaderMap &headers); /** - * @brief Parse an HTTP request into its components + * @brief Parse a request or response header * - * This method will parse the headers from an HTTP request (everything up - * to the "\r\n\r\n" terminator) and store the values in the specified - * references. + * The specified header data is parsed into a status line and HTTP + * headers. The parts list will contain the three parts of the status + * line. */ - static bool parseRequest(const QByteArray &data, QByteArray &method, QByteArray &path, QList &headers); - - /** - * @brief Parse an HTTP response into its components - * - * This method will parse the headers from an HTTP response (everything up - * to the "\r\n\r\n" terminator) and store the values in the specified - * references. - */ - static bool parseResponse(const QByteArray &data, QByteArray &statusCode, QList &headers); + static bool parseHeaders(const QByteArray &data, QList &parts, QHttpHeaderMap &headers); }; #endif // QHTTPENGINE_QHTTPPARSER_H diff --git a/tests/TestQHttpParser.cpp b/tests/TestQHttpParser.cpp index 917bc71..e0e127e 100644 --- a/tests/TestQHttpParser.cpp +++ b/tests/TestQHttpParser.cpp @@ -22,22 +22,30 @@ * IN THE SOFTWARE. */ -#include #include #include -#include "core/qhttpheader.h" #include "util/qhttpparser.h" +#include "util/qibytearray.h" -typedef QList QByteArrayList; -typedef QList QHttpHeaderList; +Q_DECLARE_METATYPE(QHttpHeaderMap) -Q_DECLARE_METATYPE(QHttpHeaderList) +const QByteArray Line1 = "a: b"; +const QIByteArray Key1 = "a"; +const QByteArray Value1 = "b"; + +const QByteArray Line2 = "c: d"; +const QIByteArray Key2 = "c"; +const QByteArray Value2 = "d"; class TestQHttpParser : public QObject { Q_OBJECT +public: + + TestQHttpParser(); + private Q_SLOTS: void testSplit_data(); @@ -46,19 +54,26 @@ private Q_SLOTS: void testParseHeaderList_data(); void testParseHeaderList(); - void testParseRequest_data(); - void testParseRequest(); + void testParseHeaders_data(); + void testParseHeaders(); - void testParseResponse_data(); - void testParseResponse(); +private: + + QHttpHeaderMap headers; }; +TestQHttpParser::TestQHttpParser() +{ + headers.insert(Key1, Value1); + headers.insert(Key2, Value2); +} + void TestQHttpParser::testSplit_data() { - QTest::addColumn("original"); - QTest::addColumn("delimiter"); + QTest::addColumn("data"); + QTest::addColumn("delim"); QTest::addColumn("maxSplit"); - QTest::addColumn("list"); + QTest::addColumn("parts"); QTest::newRow("empty string") << QByteArray() @@ -72,13 +87,7 @@ void TestQHttpParser::testSplit_data() << 0 << (QByteArrayList() << "a"); - QTest::newRow("single-char delimiter") - << QByteArray("a,b,c") - << QByteArray(",") - << 0 - << (QByteArrayList() << "a" << "b" << "c"); - - QTest::newRow("multi-char delimiter") + QTest::newRow("delimiter") << QByteArray("a::b::c") << QByteArray("::") << 0 @@ -99,32 +108,31 @@ void TestQHttpParser::testSplit_data() void TestQHttpParser::testSplit() { - QFETCH(QByteArray, original); - QFETCH(QByteArray, delimiter); + QFETCH(QByteArray, data); + QFETCH(QByteArray, delim); QFETCH(int, maxSplit); - QFETCH(QByteArrayList, list); + QFETCH(QByteArrayList, parts); - QCOMPARE(QHttpParser::split(original, delimiter, maxSplit), list); + QByteArrayList outParts; + QHttpParser::split(data, delim, maxSplit, outParts); + + QCOMPARE(outParts, parts); } void TestQHttpParser::testParseHeaderList_data() { QTest::addColumn("lines"); QTest::addColumn("success"); - QTest::addColumn("headers"); + QTest::addColumn("headers"); QTest::newRow("empty line") << (QByteArrayList() << "") << false; - QTest::newRow("malformed line") - << (QByteArrayList() << "malformed") - << false; - QTest::newRow("multiple lines") - << (QByteArrayList() << "a: b" << "c: d") + << (QByteArrayList() << Line1 << Line2) << true - << (QHttpHeaderList() << QHttpHeader("a", "b") << QHttpHeader("c", "d")); + << headers; } void TestQHttpParser::testParseHeaderList() @@ -132,90 +140,54 @@ void TestQHttpParser::testParseHeaderList() QFETCH(QByteArrayList, lines); QFETCH(bool, success); - QHttpHeaderList outHeaders; + QHttpHeaderMap outHeaders; QCOMPARE(QHttpParser::parseHeaderList(lines, outHeaders), success); if(success) { - QFETCH(QHttpHeaderList, headers); + QFETCH(QHttpHeaderMap, headers); QCOMPARE(outHeaders, headers); } } -void TestQHttpParser::testParseRequest_data() +void TestQHttpParser::testParseHeaders_data() { QTest::addColumn("data"); QTest::addColumn("success"); - QTest::addColumn("method"); - QTest::addColumn("path"); - QTest::addColumn("headers"); + QTest::addColumn("parts"); + QTest::addColumn("headers"); - QTest::newRow("empty request") + QTest::newRow("empty headers") << QByteArray("") << false; - QTest::newRow("simple request") - << QByteArray("GET / HTTP/1.0\r\na: b\r\nc: d") + QTest::newRow("request") + << QByteArray("GET / HTTP/1.0\r\n" + Line1 + "\r\n" + Line2) << true - << QByteArray("GET") - << QByteArray("/") - << (QHttpHeaderList() << QHttpHeader("a", "b") << QHttpHeader("c", "d")); + << (QByteArrayList() << "GET" << "/" << "HTTP/1.0") + << headers; + + QTest::newRow("response") + << QByteArray("HTTP/1.0 404 NOT FOUND\r\n" + Line1 + "\r\n" + Line2) + << true + << (QByteArrayList() << "HTTP/1.0" << "404" << "NOT FOUND") + << headers; } -void TestQHttpParser::testParseRequest() +void TestQHttpParser::testParseHeaders() { QFETCH(QByteArray, data); QFETCH(bool, success); - QByteArray outMethod; - QByteArray outPath; - QHttpHeaderList outHeaders; + QByteArrayList outParts; + QHttpHeaderMap outHeaders; - QCOMPARE(QHttpParser::parseRequest(data, outMethod, outPath, outHeaders), success); + QCOMPARE(QHttpParser::parseHeaders(data, outParts, outHeaders), success); if(success) { - QFETCH(QByteArray, method); - QFETCH(QByteArray, path); - QFETCH(QHttpHeaderList, headers); + QFETCH(QByteArrayList, parts); + QFETCH(QHttpHeaderMap, headers); - QCOMPARE(outMethod, method); - QCOMPARE(outPath, path); - QCOMPARE(outHeaders, headers); - } -} - -void TestQHttpParser::testParseResponse_data() -{ - QTest::addColumn("data"); - QTest::addColumn("success"); - QTest::addColumn("statusCode"); - QTest::addColumn("headers"); - - QTest::newRow("empty response") - << QByteArray("") - << false; - - QTest::newRow("simple response") - << QByteArray("HTTP/1.0 200 OK\r\na: b\r\nc: d") - << true - << QByteArray("200 OK") - << (QHttpHeaderList() << QHttpHeader("a", "b") << QHttpHeader("c", "d")); -} - -void TestQHttpParser::testParseResponse() -{ - QFETCH(QByteArray, data); - QFETCH(bool, success); - - QByteArray outStatusCode; - QHttpHeaderList outHeaders; - - QCOMPARE(QHttpParser::parseResponse(data, outStatusCode, outHeaders), success); - - if(success) { - QFETCH(QByteArray, statusCode); - QFETCH(QHttpHeaderList, headers); - - QCOMPARE(outStatusCode, statusCode); + QCOMPARE(outParts, parts); QCOMPARE(outHeaders, headers); } }