| 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 | const QByteArray = 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 | |
| 92 | void 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 | |
| 169 | QString 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 | |
| 177 | QNetworkReply::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 | |
| 185 | bool 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 | |
| 199 | std::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 | |
| 228 | QT_END_NAMESPACE |
| 229 | |