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