1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qhttpheaderparser_p.h"
5
6#include <algorithm>
7
8QT_BEGIN_NAMESPACE
9
10QHttpHeaderParser::QHttpHeaderParser()
11 : statusCode(100) // Required by tst_QHttpNetworkConnection::ignoresslerror(failure)
12 , majorVersion(0)
13 , minorVersion(0)
14{
15}
16
17void QHttpHeaderParser::clear()
18{
19 statusCode = 100;
20 majorVersion = 0;
21 minorVersion = 0;
22 reasonPhrase.clear();
23 fields.clear();
24}
25
26static bool fieldNameCheck(QByteArrayView name)
27{
28 static constexpr QByteArrayView otherCharacters("!#$%&'*+-.^_`|~");
29 static const auto fieldNameChar = [](char c) {
30 return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
31 || otherCharacters.contains(c);
32 };
33
34 return !name.empty() && std::all_of(first: name.begin(), last: name.end(), pred: fieldNameChar);
35}
36
37bool QHttpHeaderParser::parseHeaders(QByteArrayView header)
38{
39 // see rfc2616, sec 4 for information about HTTP/1.1 headers.
40 // allows relaxed parsing here, accepts both CRLF & LF line endings
41 Q_ASSERT(fields.isEmpty());
42 const auto hSpaceStart = [](QByteArrayView h) {
43 return h.startsWith(c: ' ') || h.startsWith(c: '\t');
44 };
45 // Headers, if non-empty, start with a non-space and end with a newline:
46 if (hSpaceStart(header) || (!header.empty() && !header.endsWith(c: '\n')))
47 return false;
48
49 while (int tail = header.endsWith(other: "\n\r\n") ? 2 : header.endsWith(other: "\n\n") ? 1 : 0)
50 header.chop(n: tail);
51
52 if (header.size() - (header.endsWith(other: "\r\n") ? 2 : 1) > maxTotalSize)
53 return false;
54
55 QList<QPair<QByteArray, QByteArray>> result;
56 while (!header.empty()) {
57 const qsizetype colon = header.indexOf(ch: ':');
58 if (colon == -1) // if no colon check if empty headers
59 return result.empty() && (header == "\n" || header == "\r\n");
60 if (result.size() >= maxFieldCount)
61 return false;
62 QByteArrayView name = header.first(n: colon);
63 if (!fieldNameCheck(name))
64 return false;
65 header = header.sliced(pos: colon + 1);
66 QByteArray value;
67 qsizetype valueSpace = maxFieldSize - name.size() - 1;
68 do {
69 const qsizetype endLine = header.indexOf(ch: '\n');
70 Q_ASSERT(endLine != -1);
71 auto line = header.first(n: endLine); // includes space
72 valueSpace -= line.size() - (line.endsWith(c: '\r') ? 1 : 0);
73 if (valueSpace < 0)
74 return false;
75 line = line.trimmed();
76 if (!line.empty()) {
77 if (value.size())
78 value += ' ' + line.toByteArray();
79 else
80 value = line.toByteArray();
81 }
82 header = header.sliced(pos: endLine + 1);
83 } while (hSpaceStart(header));
84 Q_ASSERT(name.size() + 1 + value.size() <= maxFieldSize);
85 result.append(t: qMakePair(value1: name.toByteArray(), value2&: value));
86 }
87
88 fields = result;
89 return true;
90}
91
92bool QHttpHeaderParser::parseStatus(QByteArrayView status)
93{
94 // from RFC 2616:
95 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
96 // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
97 // that makes: 'HTTP/n.n xxx Message'
98 // byte count: 0123456789012
99
100 static const int minLength = 11;
101 static const int dotPos = 6;
102 static const int spacePos = 8;
103 static const char httpMagic[] = "HTTP/";
104
105 if (status.size() < minLength
106 || !status.startsWith(other: httpMagic)
107 || status.at(n: dotPos) != '.'
108 || status.at(n: spacePos) != ' ') {
109 // I don't know how to parse this status line
110 return false;
111 }
112
113 // optimize for the valid case: defer checking until the end
114 majorVersion = status.at(n: dotPos - 1) - '0';
115 minorVersion = status.at(n: dotPos + 1) - '0';
116
117 int i = spacePos;
118 qsizetype j = status.indexOf(ch: ' ', from: i + 1);
119 const QByteArrayView code = j > i ? status.sliced(pos: i + 1, n: j - i - 1)
120 : status.sliced(pos: i + 1);
121
122 bool ok = false;
123 statusCode = code.toInt(ok: &ok);
124
125 reasonPhrase = j > i ? QString::fromLatin1(ba: status.sliced(pos: j + 1))
126 : QString();
127
128 return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9;
129}
130
131const QList<QPair<QByteArray, QByteArray> >& QHttpHeaderParser::headers() const
132{
133 return fields;
134}
135
136QByteArray QHttpHeaderParser::firstHeaderField(const QByteArray &name,
137 const QByteArray &defaultValue) const
138{
139 for (auto it = fields.constBegin(); it != fields.constEnd(); ++it) {
140 if (name.compare(a: it->first, cs: Qt::CaseInsensitive) == 0)
141 return it->second;
142 }
143 return defaultValue;
144}
145
146QByteArray QHttpHeaderParser::combinedHeaderValue(const QByteArray &name, const QByteArray &defaultValue) const
147{
148 const QList<QByteArray> allValues = headerFieldValues(name);
149 if (allValues.isEmpty())
150 return defaultValue;
151 return allValues.join(sep: ", ");
152}
153
154QList<QByteArray> QHttpHeaderParser::headerFieldValues(const QByteArray &name) const
155{
156 QList<QByteArray> result;
157 for (auto it = fields.constBegin(); it != fields.constEnd(); ++it)
158 if (name.compare(a: it->first, cs: Qt::CaseInsensitive) == 0)
159 result += it->second;
160
161 return result;
162}
163
164void QHttpHeaderParser::removeHeaderField(const QByteArray &name)
165{
166 auto firstEqualsName = [&name](const QPair<QByteArray, QByteArray> &header) {
167 return name.compare(a: header.first, cs: Qt::CaseInsensitive) == 0;
168 };
169 fields.removeIf(pred: firstEqualsName);
170}
171
172void QHttpHeaderParser::setHeaderField(const QByteArray &name, const QByteArray &data)
173{
174 removeHeaderField(name);
175 fields.append(t: qMakePair(value1: name, value2: data));
176}
177
178void QHttpHeaderParser::prependHeaderField(const QByteArray &name, const QByteArray &data)
179{
180 fields.prepend(t: qMakePair(value1: name, value2: data));
181}
182
183void QHttpHeaderParser::appendHeaderField(const QByteArray &name, const QByteArray &data)
184{
185 fields.append(t: qMakePair(value1: name, value2: data));
186}
187
188void QHttpHeaderParser::clearHeaders()
189{
190 fields.clear();
191}
192
193int QHttpHeaderParser::getStatusCode() const
194{
195 return statusCode;
196}
197
198void QHttpHeaderParser::setStatusCode(int code)
199{
200 statusCode = code;
201}
202
203int QHttpHeaderParser::getMajorVersion() const
204{
205 return majorVersion;
206}
207
208void QHttpHeaderParser::setMajorVersion(int version)
209{
210 majorVersion = version;
211}
212
213int QHttpHeaderParser::getMinorVersion() const
214{
215 return minorVersion;
216}
217
218void QHttpHeaderParser::setMinorVersion(int version)
219{
220 minorVersion = version;
221}
222
223QString QHttpHeaderParser::getReasonPhrase() const
224{
225 return reasonPhrase;
226}
227
228void QHttpHeaderParser::setReasonPhrase(const QString &reason)
229{
230 reasonPhrase = reason;
231}
232
233QT_END_NAMESPACE
234

source code of qtbase/src/network/access/qhttpheaderparser.cpp