Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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