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