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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/network/access/http2/http2protocol.cpp