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 <access/qhttp2configuration.h>
47
48#include <QtCore/qbytearray.h>
49#include <QtCore/qstring.h>
50
51QT_BEGIN_NAMESPACE
52
53Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2")
54
55namespace Http2
56{
57
58// 3.5 HTTP/2 Connection Preface:
59// "That is, the connection preface starts with the string
60// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)."
61const char Http2clientPreface[clientPrefaceLength] =
62 {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20,
63 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
64 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
65 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
66
67Frame configurationToSettingsFrame(const QHttp2Configuration &config)
68{
69 // 6.5 SETTINGS
70 FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
71 // Server push:
72 builder.append(Settings::ENABLE_PUSH_ID);
73 builder.append(int(config.serverPushEnabled()));
74 // Stream receive window size:
75 builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
76 builder.append(config.streamReceiveWindowSize());
77
78 if (config.maxFrameSize() != minPayloadLimit) {
79 builder.append(Settings::MAX_FRAME_SIZE_ID);
80 builder.append(config.maxFrameSize());
81 }
82 // TODO: In future, if the need is proven, we can
83 // also send decoding table size and header list size.
84 // For now, defaults suffice.
85 return builder.outboundFrame();
86}
87
88QByteArray settingsFrameToBase64(const Frame &frame)
89{
90 // SETTINGS frame's payload consists of pairs:
91 // 2-byte-identifier | 4-byte-value == multiple of 6.
92 Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6));
93 const char *src = reinterpret_cast<const char *>(frame.dataBegin());
94 const QByteArray wrapper(QByteArray::fromRawData(src, int(frame.dataSize())));
95 // 3.2.1
96 // The content of the HTTP2-Settings header field is the payload
97 // of a SETTINGS frame (Section 6.5), encoded as a base64url string
98 // (that is, the URL- and filename-safe Base64 encoding described in
99 // Section 5 of [RFC4648], with any trailing '=' characters omitted).
100 return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
101}
102
103void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetworkRequest *request)
104{
105 Q_ASSERT(request);
106 // RFC 2616, 14.10
107 // RFC 7540, 3.2
108 QByteArray value(request->headerField("Connection"));
109 // We _append_ 'Upgrade':
110 if (value.size())
111 value += ", ";
112
113 value += "Upgrade, HTTP2-Settings";
114 request->setHeaderField("Connection", value);
115 // This we just (re)write.
116 request->setHeaderField("Upgrade", "h2c");
117
118 const Frame frame(configurationToSettingsFrame(config));
119 // This we just (re)write.
120 request->setHeaderField("HTTP2-Settings", settingsFrameToBase64(frame));
121}
122
123void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
124 QString &errorMessage)
125{
126 if (errorCode > quint32(HTTP_1_1_REQUIRED)) {
127 error = QNetworkReply::ProtocolFailure;
128 errorMessage = QLatin1String("RST_STREAM with unknown error code (%1)");
129 errorMessage = errorMessage.arg(errorCode);
130 return;
131 }
132
133 const Http2Error http2Error = Http2Error(errorCode);
134
135 switch (http2Error) {
136 case HTTP2_NO_ERROR:
137 error = QNetworkReply::NoError;
138 errorMessage.clear();
139 break;
140 case PROTOCOL_ERROR:
141 error = QNetworkReply::ProtocolFailure;
142 errorMessage = QLatin1String("HTTP/2 protocol error");
143 break;
144 case INTERNAL_ERROR:
145 error = QNetworkReply::InternalServerError;
146 errorMessage = QLatin1String("Internal server error");
147 break;
148 case FLOW_CONTROL_ERROR:
149 error = QNetworkReply::ProtocolFailure;
150 errorMessage = QLatin1String("Flow control error");
151 break;
152 case SETTINGS_TIMEOUT:
153 error = QNetworkReply::TimeoutError;
154 errorMessage = QLatin1String("SETTINGS ACK timeout error");
155 break;
156 case STREAM_CLOSED:
157 error = QNetworkReply::ProtocolFailure;
158 errorMessage = QLatin1String("Server received frame(s) on a half-closed stream");
159 break;
160 case FRAME_SIZE_ERROR:
161 error = QNetworkReply::ProtocolFailure;
162 errorMessage = QLatin1String("Server received a frame with an invalid size");
163 break;
164 case REFUSE_STREAM:
165 error = QNetworkReply::ProtocolFailure;
166 errorMessage = QLatin1String("Server refused a stream");
167 break;
168 case CANCEL:
169 error = QNetworkReply::ProtocolFailure;
170 errorMessage = QLatin1String("Stream is no longer needed");
171 break;
172 case COMPRESSION_ERROR:
173 error = QNetworkReply::ProtocolFailure;
174 errorMessage = QLatin1String("Server is unable to maintain the "
175 "header compression context for the connection");
176 break;
177 case CONNECT_ERROR:
178 // TODO: in Qt6 we'll have to add more error codes in QNetworkReply.
179 error = QNetworkReply::UnknownNetworkError;
180 errorMessage = QLatin1String("The connection established in response "
181 "to a CONNECT request was reset or abnormally closed");
182 break;
183 case ENHANCE_YOUR_CALM:
184 error = QNetworkReply::UnknownServerError;
185 errorMessage = QLatin1String("Server dislikes our behavior, excessive load detected.");
186 break;
187 case INADEQUATE_SECURITY:
188 error = QNetworkReply::ContentAccessDenied;
189 errorMessage = QLatin1String("The underlying transport has properties "
190 "that do not meet minimum security "
191 "requirements");
192 break;
193 case HTTP_1_1_REQUIRED:
194 error = QNetworkReply::ProtocolFailure;
195 errorMessage = QLatin1String("Server requires that HTTP/1.1 "
196 "be used instead of HTTP/2.");
197 }
198}
199
200QString qt_error_string(quint32 errorCode)
201{
202 QNetworkReply::NetworkError error = QNetworkReply::NoError;
203 QString message;
204 qt_error(errorCode, error, message);
205 return message;
206}
207
208QNetworkReply::NetworkError qt_error(quint32 errorCode)
209{
210 QNetworkReply::NetworkError error = QNetworkReply::NoError;
211 QString message;
212 qt_error(errorCode, error, message);
213 return error;
214}
215
216bool is_protocol_upgraded(const QHttpNetworkReply &reply)
217{
218 if (reply.statusCode() == 101) {
219 // Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
220 const auto &header = reply.header();
221 for (const QPair<QByteArray, QByteArray> &field : header) {
222 if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 &&
223 field.second.compare("h2c", Qt::CaseInsensitive) == 0)
224 return true;
225 }
226 }
227
228 return false;
229}
230
231} // namespace Http2
232
233QT_END_NAMESPACE
234