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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | using namespace Qt::StringLiterals; |
18 | |
19 | QT_IMPL_METATYPE_EXTERN_TAGGED(Http2::Settings, Http2__Settings) |
20 | |
21 | Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2" ) |
22 | |
23 | namespace 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)." |
29 | const 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 | |
35 | Frame 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 | |
59 | QByteArray 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 | |
74 | void (const QHttp2Configuration &config, QHttpNetworkRequest *request) |
75 | { |
76 | Q_ASSERT(request); |
77 | // RFC 2616, 14.10 |
78 | // RFC 7540, 3.2 |
79 | QByteArray value(request->headerField(name: "Connection" )); |
80 | // We _append_ 'Upgrade': |
81 | if (value.size()) |
82 | value += ", " ; |
83 | |
84 | value += "Upgrade, HTTP2-Settings" ; |
85 | request->setHeaderField(name: "Connection" , data: value); |
86 | // This we just (re)write. |
87 | request->setHeaderField(name: "Upgrade" , data: "h2c" ); |
88 | |
89 | const Frame frame(configurationToSettingsFrame(config)); |
90 | // This we just (re)write. |
91 | request->setHeaderField(name: "HTTP2-Settings" , data: settingsFrameToBase64(frame)); |
92 | } |
93 | |
94 | void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, |
95 | QString &errorMessage) |
96 | { |
97 | if (errorCode > quint32(HTTP_1_1_REQUIRED)) { |
98 | error = QNetworkReply::ProtocolFailure; |
99 | errorMessage = "RST_STREAM with unknown error code (%1)"_L1 ; |
100 | errorMessage = errorMessage.arg(a: errorCode); |
101 | return; |
102 | } |
103 | |
104 | const Http2Error http2Error = Http2Error(errorCode); |
105 | |
106 | switch (http2Error) { |
107 | case HTTP2_NO_ERROR: |
108 | error = QNetworkReply::NoError; |
109 | errorMessage.clear(); |
110 | break; |
111 | case PROTOCOL_ERROR: |
112 | error = QNetworkReply::ProtocolFailure; |
113 | errorMessage = "HTTP/2 protocol error"_L1 ; |
114 | break; |
115 | case INTERNAL_ERROR: |
116 | error = QNetworkReply::InternalServerError; |
117 | errorMessage = "Internal server error"_L1 ; |
118 | break; |
119 | case FLOW_CONTROL_ERROR: |
120 | error = QNetworkReply::ProtocolFailure; |
121 | errorMessage = "Flow control error"_L1 ; |
122 | break; |
123 | case SETTINGS_TIMEOUT: |
124 | error = QNetworkReply::TimeoutError; |
125 | errorMessage = "SETTINGS ACK timeout error"_L1 ; |
126 | break; |
127 | case STREAM_CLOSED: |
128 | error = QNetworkReply::ProtocolFailure; |
129 | errorMessage = "Server received frame(s) on a half-closed stream"_L1 ; |
130 | break; |
131 | case FRAME_SIZE_ERROR: |
132 | error = QNetworkReply::ProtocolFailure; |
133 | errorMessage = "Server received a frame with an invalid size"_L1 ; |
134 | break; |
135 | case REFUSE_STREAM: |
136 | error = QNetworkReply::ProtocolFailure; |
137 | errorMessage = "Server refused a stream"_L1 ; |
138 | break; |
139 | case CANCEL: |
140 | error = QNetworkReply::ProtocolFailure; |
141 | errorMessage = "Stream is no longer needed"_L1 ; |
142 | break; |
143 | case COMPRESSION_ERROR: |
144 | error = QNetworkReply::ProtocolFailure; |
145 | errorMessage = "Server is unable to maintain the " |
146 | "header compression context for the connection"_L1 ; |
147 | break; |
148 | case CONNECT_ERROR: |
149 | // TODO: in Qt6 we'll have to add more error codes in QNetworkReply. |
150 | error = QNetworkReply::UnknownNetworkError; |
151 | errorMessage = "The connection established in response " |
152 | "to a CONNECT request was reset or abnormally closed"_L1 ; |
153 | break; |
154 | case ENHANCE_YOUR_CALM: |
155 | error = QNetworkReply::UnknownServerError; |
156 | errorMessage = "Server dislikes our behavior, excessive load detected."_L1 ; |
157 | break; |
158 | case INADEQUATE_SECURITY: |
159 | error = QNetworkReply::ContentAccessDenied; |
160 | errorMessage = "The underlying transport has properties " |
161 | "that do not meet minimum security " |
162 | "requirements"_L1 ; |
163 | break; |
164 | case HTTP_1_1_REQUIRED: |
165 | error = QNetworkReply::ProtocolFailure; |
166 | errorMessage = "Server requires that HTTP/1.1 " |
167 | "be used instead of HTTP/2."_L1 ; |
168 | } |
169 | } |
170 | |
171 | QString qt_error_string(quint32 errorCode) |
172 | { |
173 | QNetworkReply::NetworkError error = QNetworkReply::NoError; |
174 | QString message; |
175 | qt_error(errorCode, error, errorMessage&: message); |
176 | return message; |
177 | } |
178 | |
179 | QNetworkReply::NetworkError qt_error(quint32 errorCode) |
180 | { |
181 | QNetworkReply::NetworkError error = QNetworkReply::NoError; |
182 | QString message; |
183 | qt_error(errorCode, error, errorMessage&: message); |
184 | return error; |
185 | } |
186 | |
187 | bool is_protocol_upgraded(const QHttpNetworkReply &reply) |
188 | { |
189 | if (reply.statusCode() == 101) { |
190 | // Do some minimal checks here - we expect 'Upgrade: h2c' to be found. |
191 | const auto & = reply.header(); |
192 | for (const QPair<QByteArray, QByteArray> &field : header) { |
193 | if (field.first.compare(a: "upgrade" , cs: Qt::CaseInsensitive) == 0 && |
194 | field.second.compare(a: "h2c" , cs: Qt::CaseInsensitive) == 0) |
195 | return true; |
196 | } |
197 | } |
198 | |
199 | return false; |
200 | } |
201 | |
202 | } // namespace Http2 |
203 | |
204 | QT_END_NAMESPACE |
205 | |