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