1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "http2protocol_p.h"
41#include "http2frames_p.h"
42
43#include "private/qhttpnetworkrequest_p.h"
44#include "private/qhttpnetworkreply_p.h"
45
46#include <QtCore/qbytearray.h>
47#include <QtCore/qstring.h>
48
49QT_BEGIN_NAMESPACE
50
51Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2")
52
53namespace Http2
54{
55
56// 3.5 HTTP/2 Connection Preface:
57// "That is, the connection preface starts with the string
58// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)."
59const char Http2clientPreface[clientPrefaceLength] =
60 {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20,
61 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
62 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
63 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
64
65// TODO: (in 5.11) - remove it!
66const char *http2ParametersPropertyName = "QT_HTTP2_PARAMETERS_PROPERTY";
67
68ProtocolParameters::ProtocolParameters()
69{
70 settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID] = qtDefaultStreamReceiveWindowSize;
71 settingsFrameData[Settings::ENABLE_PUSH_ID] = 0;
72}
73
74bool ProtocolParameters::validate() const
75{
76 // 0. Huffman/indexing: any values are valid and allowed.
77
78 // 1. Session receive window size (client side): HTTP/2 starts from the
79 // default value of 64Kb, if a client code tries to set lesser value,
80 // the delta would become negative, but this is not allowed.
81 if (maxSessionReceiveWindowSize < qint32(defaultSessionWindowSize)) {
82 qCWarning(QT_HTTP2, "Session receive window must be at least 65535 bytes");
83 return false;
84 }
85
86 // 2. HEADER_TABLE_SIZE: we do not validate HEADER_TABLE_SIZE, considering
87 // all values as valid. RFC 7540 and 7541 do not provide any lower/upper
88 // limits. If it's 0 - we do not index anything, if it's too huge - a user
89 // who provided such a value can potentially have a huge memory footprint,
90 // up to them to decide.
91
92 // 3. SETTINGS_ENABLE_PUSH: RFC 7540, 6.5.2, a value other than 0 or 1 will
93 // be treated by our peer as a PROTOCOL_ERROR.
94 if (settingsFrameData.contains(Settings::ENABLE_PUSH_ID)
95 && settingsFrameData[Settings::ENABLE_PUSH_ID] > 1) {
96 qCWarning(QT_HTTP2, "SETTINGS_ENABLE_PUSH can be only 0 or 1");
97 return false;
98 }
99
100 // 4. SETTINGS_MAX_CONCURRENT_STREAMS : RFC 7540 recommends 100 as the lower
101 // limit, says nothing about the upper limit. The RFC allows 0, but this makes
102 // no sense to us at all: there is no way a user can change this later and
103 // we'll not be able to get any responses on such a connection.
104 if (settingsFrameData.contains(Settings::MAX_CONCURRENT_STREAMS_ID)
105 && !settingsFrameData[Settings::MAX_CONCURRENT_STREAMS_ID]) {
106 qCWarning(QT_HTTP2, "MAX_CONCURRENT_STREAMS must be a positive number");
107 return false;
108 }
109
110 // 5. SETTINGS_INITIAL_WINDOW_SIZE.
111 if (settingsFrameData.contains(Settings::INITIAL_WINDOW_SIZE_ID)) {
112 const quint32 value = settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID];
113 // RFC 7540, 6.5.2 (the upper limit). The lower limit is our own - we send
114 // SETTINGS frame only once and will not be able to change this 0, thus
115 // we'll suspend all streams.
116 if (!value || value > quint32(maxSessionReceiveWindowSize)) {
117 qCWarning(QT_HTTP2, "INITIAL_WINDOW_SIZE must be in the range "
118 "(0, 2^31-1]");
119 return false;
120 }
121 }
122
123 // 6. SETTINGS_MAX_FRAME_SIZE: RFC 7540, 6.5.2, a value outside of the range
124 // [2^14-1, 2^24-1] will be treated by our peer as a PROTOCOL_ERROR.
125 if (settingsFrameData.contains(Settings::MAX_FRAME_SIZE_ID)) {
126 const quint32 value = settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID];
127 if (value < maxFrameSize || value > maxPayloadSize) {
128 qCWarning(QT_HTTP2, "MAX_FRAME_SIZE must be in the range [2^14, 2^24-1]");
129 return false;
130 }
131 }
132
133 // For SETTINGS_MAX_HEADER_LIST_SIZE RFC 7540 does not provide any specific
134 // numbers. It's clear, if a value is too small, no header can ever be sent
135 // by our peer at all. The default value is unlimited and we normally do not
136 // change this.
137 //
138 // Note: the size is calculated as the length of uncompressed (no HPACK)
139 // name + value + 32 bytes.
140
141 return true;
142}
143
144QByteArray ProtocolParameters::settingsFrameToBase64() const
145{
146 Frame frame(settingsFrame());
147 // SETTINGS frame's payload consists of pairs:
148 // 2-byte-identifier | 4-byte-value == multiple of 6.
149 Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6));
150 const char *src = reinterpret_cast<const char *>(frame.dataBegin());
151 const QByteArray wrapper(QByteArray::fromRawData(src, int(frame.dataSize())));
152 // 3.2.1
153 // The content of the HTTP2-Settings header field is the payload
154 // of a SETTINGS frame (Section 6.5), encoded as a base64url string
155 // (that is, the URL- and filename-safe Base64 encoding described in
156 // Section 5 of [RFC4648], with any trailing '=' characters omitted).
157 return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
158}
159
160Frame ProtocolParameters::settingsFrame() const
161{
162 // 6.5 SETTINGS
163 FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
164 for (auto it = settingsFrameData.cbegin(), end = settingsFrameData.cend();
165 it != end; ++it) {
166 builder.append(it.key());
167 builder.append(it.value());
168 }
169
170 return builder.outboundFrame();
171}
172
173void ProtocolParameters::addProtocolUpgradeHeaders(QHttpNetworkRequest *request) const
174{
175 Q_ASSERT(request);
176 // RFC 2616, 14.10
177 // RFC 7540, 3.2
178 QByteArray value(request->headerField("Connection"));
179 // We _append_ 'Upgrade':
180 if (value.size())
181 value += ", ";
182
183 value += "Upgrade, HTTP2-Settings";
184 request->setHeaderField("Connection", value);
185 // This we just (re)write.
186 request->setHeaderField("Upgrade", "h2c");
187 // This we just (re)write.
188 request->setHeaderField("HTTP2-Settings", settingsFrameToBase64());
189}
190
191void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
192 QString &errorMessage)
193{
194 if (errorCode > quint32(HTTP_1_1_REQUIRED)) {
195 error = QNetworkReply::ProtocolFailure;
196 errorMessage = QLatin1String("RST_STREAM with unknown error code (%1)");
197 errorMessage = errorMessage.arg(errorCode);
198 return;
199 }
200
201 const Http2Error http2Error = Http2Error(errorCode);
202
203 switch (http2Error) {
204 case HTTP2_NO_ERROR:
205 error = QNetworkReply::NoError;
206 errorMessage.clear();
207 break;
208 case PROTOCOL_ERROR:
209 error = QNetworkReply::ProtocolFailure;
210 errorMessage = QLatin1String("HTTP/2 protocol error");
211 break;
212 case INTERNAL_ERROR:
213 error = QNetworkReply::InternalServerError;
214 errorMessage = QLatin1String("Internal server error");
215 break;
216 case FLOW_CONTROL_ERROR:
217 error = QNetworkReply::ProtocolFailure;
218 errorMessage = QLatin1String("Flow control error");
219 break;
220 case SETTINGS_TIMEOUT:
221 error = QNetworkReply::TimeoutError;
222 errorMessage = QLatin1String("SETTINGS ACK timeout error");
223 break;
224 case STREAM_CLOSED:
225 error = QNetworkReply::ProtocolFailure;
226 errorMessage = QLatin1String("Server received frame(s) on a half-closed stream");
227 break;
228 case FRAME_SIZE_ERROR:
229 error = QNetworkReply::ProtocolFailure;
230 errorMessage = QLatin1String("Server received a frame with an invalid size");
231 break;
232 case REFUSE_STREAM:
233 error = QNetworkReply::ProtocolFailure;
234 errorMessage = QLatin1String("Server refused a stream");
235 break;
236 case CANCEL:
237 error = QNetworkReply::ProtocolFailure;
238 errorMessage = QLatin1String("Stream is no longer needed");
239 break;
240 case COMPRESSION_ERROR:
241 error = QNetworkReply::ProtocolFailure;
242 errorMessage = QLatin1String("Server is unable to maintain the "
243 "header compression context for the connection");
244 break;
245 case CONNECT_ERROR:
246 // TODO: in Qt6 we'll have to add more error codes in QNetworkReply.
247 error = QNetworkReply::UnknownNetworkError;
248 errorMessage = QLatin1String("The connection established in response "
249 "to a CONNECT request was reset or abnormally closed");
250 break;
251 case ENHANCE_YOUR_CALM:
252 error = QNetworkReply::UnknownServerError;
253 errorMessage = QLatin1String("Server dislikes our behavior, excessive load detected.");
254 break;
255 case INADEQUATE_SECURITY:
256 error = QNetworkReply::ContentAccessDenied;
257 errorMessage = QLatin1String("The underlying transport has properties "
258 "that do not meet minimum security "
259 "requirements");
260 break;
261 case HTTP_1_1_REQUIRED:
262 error = QNetworkReply::ProtocolFailure;
263 errorMessage = QLatin1String("Server requires that HTTP/1.1 "
264 "be used instead of HTTP/2.");
265 }
266}
267
268QString qt_error_string(quint32 errorCode)
269{
270 QNetworkReply::NetworkError error = QNetworkReply::NoError;
271 QString message;
272 qt_error(errorCode, error, message);
273 return message;
274}
275
276QNetworkReply::NetworkError qt_error(quint32 errorCode)
277{
278 QNetworkReply::NetworkError error = QNetworkReply::NoError;
279 QString message;
280 qt_error(errorCode, error, message);
281 return error;
282}
283
284bool is_protocol_upgraded(const QHttpNetworkReply &reply)
285{
286 if (reply.statusCode() == 101) {
287 // Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
288 const auto &header = reply.header();
289 for (const QPair<QByteArray, QByteArray> &field : header) {
290 if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 &&
291 field.second.compare("h2c", Qt::CaseInsensitive) == 0)
292 return true;
293 }
294 }
295
296 return false;
297}
298
299} // namespace Http2
300
301QT_END_NAMESPACE
302