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