diff options
author | Øystein Heskestad <oystein.heskestad@qt.io> | 2021-11-12 17:33:35 +0100 |
---|---|---|
committer | Øystein Heskestad <oystein.heskestad@qt.io> | 2021-11-19 19:31:34 +0100 |
commit | 18aff2b424577b4560b32698038e9bcf68a54b88 (patch) | |
tree | d13809ee83ec77ae9f25cb041e7b86ba9c4aee27 /src/network | |
parent | d62e9d3c5bd9d3aae5f4b72ed0688dcbd02134a7 (diff) |
Add more verification when parsing http headers and add tests
Adding tests from QtWebSockets that will reuse QHttpHeaderParser
Task-number: QTBUG-80700
Change-Id: I76294a9156173314a3cf09160d0ca4e0d7c6ef3a
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/access/qhttpheaderparser.cpp | 84 |
1 files changed, 61 insertions, 23 deletions
diff --git a/src/network/access/qhttpheaderparser.cpp b/src/network/access/qhttpheaderparser.cpp index 4a6bbd49a7..5caa541cb2 100644 --- a/src/network/access/qhttpheaderparser.cpp +++ b/src/network/access/qhttpheaderparser.cpp @@ -39,8 +39,16 @@ #include "qhttpheaderparser_p.h" +#include <algorithm> + QT_BEGIN_NAMESPACE +// both constants are taken from the default settings of Apache +// see: http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfieldsize and +// http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfields +static const int MAX_HEADER_FIELD_SIZE = 8 * 1024; +static const int MAX_HEADER_FIELDS = 100; + QHttpHeaderParser::QHttpHeaderParser() : statusCode(100) // Required by tst_QHttpNetworkConnection::ignoresslerror(failure) , majorVersion(0) @@ -57,36 +65,66 @@ void QHttpHeaderParser::clear() fields.clear(); } +static bool fieldNameCheck(QByteArrayView name) +{ + static constexpr QByteArrayView otherCharacters("!#$%&'*+-.^_`|~"); + static const auto fieldNameChar = [](char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') + || otherCharacters.contains(c); + }; + + return name.size() > 0 && std::all_of(name.begin(), name.end(), fieldNameChar); +} + bool QHttpHeaderParser::parseHeaders(QByteArrayView header) { // see rfc2616, sec 4 for information about HTTP/1.1 headers. // allows relaxed parsing here, accepts both CRLF & LF line endings - int i = 0; - while (i < header.size()) { - int j = header.indexOf(':', i); // field-name - if (j == -1) - break; - QByteArrayView field = header.sliced(i, j - i).trimmed(); - j++; - // any number of LWS is allowed before and after the value + Q_ASSERT(fields.isEmpty()); + const auto hSpaceStart = [](QByteArrayView h) { + return h.startsWith(' ') || h.startsWith('\t'); + }; + // Headers, if non-empty, start with a non-space and end with a newline: + if (hSpaceStart(header) || (header.size() && !header.endsWith('\n'))) + return false; + + while (int tail = header.endsWith("\n\r\n") ? 2 : header.endsWith("\n\n") ? 1 : 0) + header.chop(tail); + + QList<QPair<QByteArray, QByteArray>> result; + while (header.size()) { + const int colon = header.indexOf(':'); + if (colon == -1) // if no colon check if empty headers + return result.size() == 0 && (header == "\n" || header == "\r\n"); + if (result.size() >= MAX_HEADER_FIELDS) + return false; + QByteArrayView name = header.first(colon); + if (!fieldNameCheck(name)) + return false; + header = header.sliced(colon + 1); QByteArray value; + int valueSpace = MAX_HEADER_FIELD_SIZE - name.size() - 1; do { - i = header.indexOf('\n', j); - if (i == -1) - break; - if (!value.isEmpty()) - value += ' '; - // check if we have CRLF or only LF - bool hasCR = i && header[i - 1] == '\r'; - int length = i - (hasCR ? 1: 0) - j; - value += header.sliced(j, length).trimmed(); - j = ++i; - } while (i < header.size() && (header.at(i) == ' ' || header.at(i) == '\t')); - if (i == -1) - return false; // something is wrong - - fields.append(qMakePair(field.toByteArray(), value)); + const int endLine = header.indexOf('\n'); + Q_ASSERT(endLine != -1); + auto line = header.first(endLine); // includes space + valueSpace -= line.size() - (line.endsWith('\r') ? 1 : 0); + if (valueSpace < 0) + return false; + line = line.trimmed(); + if (line.size()) { + if (value.size()) + value += ' ' + line.toByteArray(); + else + value = line.toByteArray(); + } + header = header.sliced(endLine + 1); + } while (hSpaceStart(header)); + Q_ASSERT(name.size() + 1 + value.size() <= MAX_HEADER_FIELD_SIZE); + result.append(qMakePair(name.toByteArray(), value)); } + + fields = result; return true; } |