diff options
Diffstat (limited to 'src/network/access/qhttpheaderparser.cpp')
-rw-r--r-- | src/network/access/qhttpheaderparser.cpp | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/network/access/qhttpheaderparser.cpp b/src/network/access/qhttpheaderparser.cpp new file mode 100644 index 0000000000..0b7882c18a --- /dev/null +++ b/src/network/access/qhttpheaderparser.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhttpheaderparser_p.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +QHttpHeaderParser::QHttpHeaderParser() + : statusCode(100) // Required by tst_QHttpNetworkConnection::ignoresslerror(failure) + , majorVersion(0) + , minorVersion(0) +{ +} + +void QHttpHeaderParser::clear() +{ + statusCode = 100; + majorVersion = 0; + minorVersion = 0; + reasonPhrase.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.empty() && 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 + 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.empty() && !header.endsWith('\n'))) + return false; + + while (int tail = header.endsWith("\n\r\n") ? 2 : header.endsWith("\n\n") ? 1 : 0) + header.chop(tail); + + if (header.size() - (header.endsWith("\r\n") ? 2 : 1) > maxTotalSize) + return false; + + QHttpHeaders result; + while (!header.empty()) { + const qsizetype colon = header.indexOf(':'); + if (colon == -1) // if no colon check if empty headers + return result.isEmpty() && (header == "\n" || header == "\r\n"); + if (result.size() >= maxFieldCount) + return false; + QByteArrayView name = header.first(colon); + if (!fieldNameCheck(name)) + return false; + header = header.sliced(colon + 1); + QByteArray value; + qsizetype valueSpace = maxFieldSize - name.size() - 1; + do { + const qsizetype 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.empty()) { + if (value.size()) + value += ' ' + line; + else + value = line.toByteArray(); + } + header = header.sliced(endLine + 1); + } while (hSpaceStart(header)); + Q_ASSERT(name.size() + 1 + value.size() <= maxFieldSize); + result.append(name, value); + } + + fields = result; + return true; +} + +bool QHttpHeaderParser::parseStatus(QByteArrayView status) +{ + // from RFC 2616: + // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF + // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT + // that makes: 'HTTP/n.n xxx Message' + // byte count: 0123456789012 + + static const int minLength = 11; + static const int dotPos = 6; + static const int spacePos = 8; + static const char httpMagic[] = "HTTP/"; + + if (status.size() < minLength + || !status.startsWith(httpMagic) + || status.at(dotPos) != '.' + || status.at(spacePos) != ' ') { + // I don't know how to parse this status line + return false; + } + + // optimize for the valid case: defer checking until the end + majorVersion = status.at(dotPos - 1) - '0'; + minorVersion = status.at(dotPos + 1) - '0'; + + int i = spacePos; + qsizetype j = status.indexOf(' ', i + 1); + const QByteArrayView code = j > i ? status.sliced(i + 1, j - i - 1) + : status.sliced(i + 1); + + bool ok = false; + statusCode = code.toInt(&ok); + + reasonPhrase = j > i ? QString::fromLatin1(status.sliced(j + 1)) + : QString(); + + return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9; +} + +const QHttpHeaders& QHttpHeaderParser::headers() const +{ + return fields; +} + +QByteArray QHttpHeaderParser::firstHeaderField(QByteArrayView name, + const QByteArray &defaultValue) const +{ + return fields.value(name, defaultValue).toByteArray(); +} + +QByteArray QHttpHeaderParser::combinedHeaderValue(QByteArrayView name, const QByteArray &defaultValue) const +{ + const QList<QByteArray> allValues = headerFieldValues(name); + if (allValues.isEmpty()) + return defaultValue; + return allValues.join(", "); +} + +QList<QByteArray> QHttpHeaderParser::headerFieldValues(QByteArrayView name) const +{ + return fields.values(name); +} + +void QHttpHeaderParser::removeHeaderField(QByteArrayView name) +{ + fields.removeAll(name); +} + +void QHttpHeaderParser::setHeaderField(const QByteArray &name, const QByteArray &data) +{ + removeHeaderField(name); + fields.append(name, data); +} + +void QHttpHeaderParser::prependHeaderField(const QByteArray &name, const QByteArray &data) +{ + fields.insert(0, name, data); +} + +void QHttpHeaderParser::appendHeaderField(const QByteArray &name, const QByteArray &data) +{ + fields.append(name, data); +} + +void QHttpHeaderParser::clearHeaders() +{ + fields.clear(); +} + +int QHttpHeaderParser::getStatusCode() const +{ + return statusCode; +} + +void QHttpHeaderParser::setStatusCode(int code) +{ + statusCode = code; +} + +int QHttpHeaderParser::getMajorVersion() const +{ + return majorVersion; +} + +void QHttpHeaderParser::setMajorVersion(int version) +{ + majorVersion = version; +} + +int QHttpHeaderParser::getMinorVersion() const +{ + return minorVersion; +} + +void QHttpHeaderParser::setMinorVersion(int version) +{ + minorVersion = version; +} + +QString QHttpHeaderParser::getReasonPhrase() const +{ + return reasonPhrase; +} + +void QHttpHeaderParser::setReasonPhrase(const QString &reason) +{ + reasonPhrase = reason; +} + +QT_END_NAMESPACE |