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 QHttpHeaders 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.isEmpty() && (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;
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(name, 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 QHttpHeaders& QHttpHeaderParser::headers() const &
132{
133 return fields;
134}
135
136QHttpHeaders QHttpHeaderParser::headers() &&
137{
138 return std::move(fields);
139}
140
141QByteArray QHttpHeaderParser::firstHeaderField(QByteArrayView name,
142 const QByteArray &defaultValue) const
143{
144 return fields.value(name, defaultValue).toByteArray();
145}
146
147QByteArray QHttpHeaderParser::combinedHeaderValue(QByteArrayView name, const QByteArray &defaultValue) const
148{
149 const QList<QByteArray> allValues = headerFieldValues(name);
150 if (allValues.isEmpty())
151 return defaultValue;
152 return allValues.join(sep: ", ");
153}
154
155QList<QByteArray> QHttpHeaderParser::headerFieldValues(QByteArrayView name) const
156{
157 return fields.values(name);
158}
159
160void QHttpHeaderParser::removeHeaderField(QByteArrayView name)
161{
162 fields.removeAll(name);
163}
164
165void QHttpHeaderParser::setHeaderField(const QByteArray &name, const QByteArray &data)
166{
167 removeHeaderField(name);
168 fields.append(name, value: data);
169}
170
171void QHttpHeaderParser::prependHeaderField(const QByteArray &name, const QByteArray &data)
172{
173 fields.insert(i: 0, name, value: data);
174}
175
176void QHttpHeaderParser::appendHeaderField(const QByteArray &name, const QByteArray &data)
177{
178 fields.append(name, value: data);
179}
180
181void QHttpHeaderParser::clearHeaders()
182{
183 fields.clear();
184}
185
186int QHttpHeaderParser::getStatusCode() const
187{
188 return statusCode;
189}
190
191void QHttpHeaderParser::setStatusCode(int code)
192{
193 statusCode = code;
194}
195
196int QHttpHeaderParser::getMajorVersion() const
197{
198 return majorVersion;
199}
200
201void QHttpHeaderParser::setMajorVersion(int version)
202{
203 majorVersion = version;
204}
205
206int QHttpHeaderParser::getMinorVersion() const
207{
208 return minorVersion;
209}
210
211void QHttpHeaderParser::setMinorVersion(int version)
212{
213 minorVersion = version;
214}
215
216QString QHttpHeaderParser::getReasonPhrase() const
217{
218 return reasonPhrase;
219}
220
221void QHttpHeaderParser::setReasonPhrase(const QString &reason)
222{
223 reasonPhrase = reason;
224}
225
226QT_END_NAMESPACE
227

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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