Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/network/access/http2/http2frames.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 "http2frames_p.h"
6
7
#include <QtNetwork/qabstractsocket.h>
8
9
#include <algorithm>
10
#include <utility>
11
12
QT_BEGIN_NAMESPACE
13
14
namespace Http2
15
{
16
17
// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
18
19
Frame::Frame()
20
0
    : buffer(frameHeaderSize)
21
0
{
22
0
}
23
24
FrameType Frame::type() const
25
0
{
26
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
27
28
0
    if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
29
0
        return FrameType::LAST_FRAME_TYPE;
30
31
0
    return FrameType(buffer[3]);
32
0
}
33
34
quint32 Frame::streamID() const
35
0
{
36
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
37
    // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
38
0
    return qFromBigEndian<quint32>(&buffer[5]) & lastValidStreamID;
39
0
}
40
41
FrameFlags Frame::flags() const
42
0
{
43
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
44
0
    return FrameFlags(buffer[4]);
45
0
}
46
47
quint32 Frame::payloadSize() const
48
0
{
49
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
50
0
    return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
51
0
}
52
53
uchar Frame::padding() const
54
0
{
55
0
    Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
56
57
0
    if (!flags().testFlag(FrameFlag::PADDED))
58
0
        return 0;
59
60
0
    switch (type()) {
61
0
    case FrameType::DATA:
62
0
    case FrameType::PUSH_PROMISE:
63
0
    case FrameType::HEADERS:
64
0
        Q_ASSERT(buffer.size() > frameHeaderSize);
65
0
        return buffer[frameHeaderSize];
66
0
    default:
67
0
        return 0;
68
0
    }
69
0
}
70
71
bool Frame::priority(quint32 *streamID, uchar *weight) const
72
0
{
73
0
    Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
74
75
0
    if (buffer.size() <= frameHeaderSize)
76
0
        return false;
77
78
0
    const uchar *src = &buffer[0] + frameHeaderSize;
79
0
    if (type() == FrameType::HEADERS && flags().testFlag(FrameFlag::PADDED))
80
0
        ++src;
81
82
0
    if ((type() == FrameType::HEADERS && flags().testFlag(FrameFlag::PRIORITY))
83
0
        || type() == FrameType::PRIORITY) {
84
0
        if (streamID)
85
0
            *streamID = qFromBigEndian<quint32>(src);
86
0
        if (weight)
87
0
            *weight = src[4];
88
0
        return true;
89
0
    }
90
91
0
    return false;
92
0
}
93
94
FrameStatus Frame::validateHeader() const
95
0
{
96
    // Should be called only on a frame with
97
    // a complete header.
98
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
99
100
0
    const auto framePayloadSize = payloadSize();
101
    // 4.2 Frame Size
102
0
    if (framePayloadSize > maxPayloadSize)
103
0
        return FrameStatus::sizeError;
104
105
0
    switch (type()) {
106
0
    case FrameType::SETTINGS:
107
        // SETTINGS ACK can not have any payload.
108
        // The payload of a SETTINGS frame consists of zero
109
        // or more parameters, each consisting of an unsigned
110
        // 16-bit setting identifier and an unsigned 32-bit value.
111
        // Thus the payload size must be a multiple of 6.
112
0
        if (flags().testFlag(FrameFlag::ACK) ? framePayloadSize : framePayloadSize % 6)
113
0
            return FrameStatus::sizeError;
114
0
        break;
115
0
    case FrameType::PRIORITY:
116
        // 6.3 PRIORITY
117
0
        if (framePayloadSize != 5)
118
0
            return FrameStatus::sizeError;
119
0
        break;
120
0
    case FrameType::PING:
121
        // 6.7 PING
122
0
        if (framePayloadSize != 8)
123
0
            return FrameStatus::sizeError;
124
0
        break;
125
0
    case FrameType::GOAWAY:
126
        // 6.8 GOAWAY
127
0
        if (framePayloadSize < 8)
128
0
            return FrameStatus::sizeError;
129
0
        break;
130
0
    case FrameType::RST_STREAM:
131
0
    case FrameType::WINDOW_UPDATE:
132
        // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
133
0
        if (framePayloadSize != 4)
134
0
            return FrameStatus::sizeError;
135
0
        break;
136
0
    case FrameType::PUSH_PROMISE:
137
        // 6.6 PUSH_PROMISE
138
0
        if (framePayloadSize < 4)
139
0
            return FrameStatus::sizeError;
140
0
        break;
141
0
    default:
142
        // DATA/HEADERS/CONTINUATION will be verified
143
        // when we have payload.
144
        // Frames of unknown types are ignored (5.1)
145
0
        break;
146
0
    }
147
148
0
    return FrameStatus::goodFrame;
149
0
}
150
151
FrameStatus Frame::validatePayload() const
152
0
{
153
    // Should be called only on a complete frame with a valid header.
154
0
    Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
155
156
    // Ignored, 5.1
157
0
    if (type() == FrameType::LAST_FRAME_TYPE)
158
0
        return FrameStatus::goodFrame;
159
160
0
    auto size = payloadSize();
161
0
    Q_ASSERT(buffer.size() >= frameHeaderSize && size == buffer.size() - frameHeaderSize);
162
163
0
    const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
164
0
    const auto frameFlags = flags();
165
0
    switch (type()) {
166
    // 6.1 DATA, 6.2 HEADERS
167
0
    case FrameType::DATA:
168
0
    case FrameType::HEADERS:
169
0
        if (frameFlags.testFlag(FrameFlag::PADDED)) {
170
0
            if (!size || size < src[0])
171
0
                return FrameStatus::sizeError;
172
0
            size -= src[0];
173
0
        }
174
0
        if (type() == FrameType::HEADERS && frameFlags.testFlag(FrameFlag::PRIORITY)) {
175
0
            if (size < 5)
176
0
                return FrameStatus::sizeError;
177
0
        }
178
0
        break;
179
    // 6.6 PUSH_PROMISE
180
0
    case FrameType::PUSH_PROMISE:
181
0
        if (frameFlags.testFlag(FrameFlag::PADDED)) {
182
0
            if (!size || size < src[0])
183
0
                return FrameStatus::sizeError;
184
0
            size -= src[0];
185
0
        }
186
187
0
        if (size < 4)
188
0
            return FrameStatus::sizeError;
189
0
        break;
190
0
    default:
191
0
        break;
192
0
    }
193
194
0
    return FrameStatus::goodFrame;
195
0
}
196
197
198
quint32 Frame::dataSize() const
199
0
{
200
0
    Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
201
202
0
    quint32 size = payloadSize();
203
0
    if (flags().testFlag(FrameFlag::PADDED)) {
204
0
        const uchar pad = padding();
205
        // + 1 one for a byte with padding number itself:
206
0
        size -= pad + 1;
207
0
    }
208
209
0
    if (priority())
210
0
        size -= 5;
211
212
0
    return size;
213
0
}
214
215
quint32 Frame::hpackBlockSize() const
216
0
{
217
0
    Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
218
219
0
    const auto frameType = type();
220
0
    Q_ASSERT(frameType == FrameType::HEADERS ||
221
0
             frameType == FrameType::PUSH_PROMISE ||
222
0
             frameType == FrameType::CONTINUATION);
223
224
0
    quint32 size = dataSize();
225
0
    if (frameType == FrameType::PUSH_PROMISE) {
226
0
        Q_ASSERT(size >= 4);
227
0
        size -= 4;
228
0
    }
229
230
0
    return size;
231
0
}
232
233
const uchar *Frame::dataBegin() const
234
0
{
235
0
    Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
236
0
    if (buffer.size() <= frameHeaderSize)
237
0
        return nullptr;
238
239
0
    const uchar *src = &buffer[0] + frameHeaderSize;
240
0
    if (flags().testFlag(FrameFlag::PADDED))
241
0
        ++src;
242
243
0
    if (priority())
244
0
        src += 5;
245
246
0
    return src;
247
0
}
248
249
const uchar *Frame::hpackBlockBegin() const
250
0
{
251
0
    Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
252
253
0
    const auto frameType = type();
254
0
    Q_ASSERT(frameType == FrameType::HEADERS ||
255
0
             frameType == FrameType::PUSH_PROMISE ||
256
0
             frameType == FrameType::CONTINUATION);
257
258
0
    const uchar *begin = dataBegin();
259
0
    if (frameType == FrameType::PUSH_PROMISE)
260
0
        begin += 4; // That's a promised stream, skip it.
261
0
    return begin;
262
0
}
263
264
FrameStatus FrameReader::read(QIODevice &socket)
265
0
{
266
0
    if (offset < frameHeaderSize) {
267
0
        if (!readHeader(socket))
268
0
            return FrameStatus::incompleteFrame;
269
0
    }
270
271
0
    const auto status = frame.validateHeader();
272
0
    if (status != FrameStatus::goodFrame) {
273
0
        if (status == FrameStatus::sizeError && frame.streamID() != connectionStreamID) {
274
0
            if (!discardPayload(socket))
275
0
                return FrameStatus::incompleteFrame;
276
0
        }
277
0
        return status;
278
0
    }
279
280
0
    Q_ASSERT(maxPayloadSize >= frame.payloadSize());
281
0
    frame.buffer.resize(frame.payloadSize() + frameHeaderSize);
282
283
0
    if (offset < frame.buffer.size() && !readPayload(socket))
284
0
        return FrameStatus::incompleteFrame;
285
286
    // Reset the offset, our frame can be re-used
287
    // now (re-read):
288
0
    offset = 0;
289
290
0
    return frame.validatePayload();
291
0
}
292
293
bool FrameReader::readHeader(QIODevice &socket)
294
0
{
295
0
    Q_ASSERT(offset < frameHeaderSize);
296
297
0
    auto &buffer = frame.buffer;
298
0
    if (buffer.size() < frameHeaderSize)
299
0
        buffer.resize(frameHeaderSize);
300
301
0
    const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
302
0
                                       frameHeaderSize - offset);
303
0
    if (chunkSize > 0)
304
0
        offset += chunkSize;
305
306
0
    return offset == frameHeaderSize;
307
0
}
308
309
bool FrameReader::readPayload(QIODevice &socket)
310
0
{
311
0
    Q_ASSERT(offset < frame.buffer.size());
312
0
    Q_ASSERT(frame.buffer.size() > frameHeaderSize);
313
314
0
    auto &buffer = frame.buffer;
315
    // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
316
0
    const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
317
0
                                       qint64(buffer.size() - offset));
318
0
    if (chunkSize > 0)
319
0
        offset += quint32(chunkSize);
320
321
0
    return offset == buffer.size();
322
0
}
323
324
// Returns true if there is nothing more to discard
325
bool FrameReader::discardPayload(QIODevice &socket)
326
0
{
327
0
    Q_ASSERT(offset >= frameHeaderSize); // Frame header is already read when this is called
328
329
0
    using namespace Http2;
330
0
    auto frameType = frame.type();
331
0
    if (frameType != FrameType::DATA && frameType != FrameType::PRIORITY &&
332
0
        frameType != FrameType::WINDOW_UPDATE)
333
0
        return true; // Connection will be closed, nothing needs to be discarded
334
335
0
    const quint32 payload = frame.payloadSize();
336
0
    Q_ASSERT(maxPayloadSize >= payload);
337
0
    const quint32 totalFrameSize = frameHeaderSize + payload;
338
0
    while (totalFrameSize > offset) {
339
0
        const quint32 remainingSize = totalFrameSize - offset;
340
0
        const auto skipped = socket.skip(remainingSize);
341
0
        if (skipped > 0)
342
0
            offset += quint32(skipped);
343
0
        else
344
0
            return false;
345
0
    }
346
0
    offset = 0;
347
0
    return true;
348
0
}
349
350
FrameWriter::FrameWriter()
351
0
{
352
0
}
353
354
FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
355
0
{
356
0
    start(type, flags, streamID);
357
0
}
358
359
void FrameWriter::setOutboundFrame(Frame &&newFrame)
360
0
{
361
0
    frame = std::move(newFrame);
362
0
    updatePayloadSize();
363
0
}
364
365
void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
366
0
{
367
0
    auto &buffer = frame.buffer;
368
369
0
    buffer.resize(frameHeaderSize);
370
    // The first three bytes - payload size, which is 0 for now.
371
0
    buffer[0] = 0;
372
0
    buffer[1] = 0;
373
0
    buffer[2] = 0;
374
375
0
    buffer[3] = uchar(type);
376
0
    buffer[4] = uchar(flags);
377
378
0
    qToBigEndian(streamID, &buffer[5]);
379
0
}
380
381
void FrameWriter::setPayloadSize(quint32 size)
382
0
{
383
0
    auto &buffer = frame.buffer;
384
385
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
386
0
    Q_ASSERT(size <= maxPayloadSize);
387
388
0
    buffer[0] = size >> 16;
389
0
    buffer[1] = size >> 8;
390
0
    buffer[2] = size;
391
0
}
392
393
void FrameWriter::setType(FrameType type)
394
0
{
395
0
    Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
396
0
    frame.buffer[3] = uchar(type);
397
0
}
398
399
void FrameWriter::setFlags(FrameFlags flags)
400
0
{
401
0
    Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
402
0
    frame.buffer[4] = uchar(flags);
403
0
}
404
405
void FrameWriter::addFlag(FrameFlag flag)
406
0
{
407
0
    setFlags(frame.flags() | flag);
408
0
}
409
410
void FrameWriter::append(const uchar *begin, const uchar *end)
411
0
{
412
0
    Q_ASSERT(begin && end);
413
0
    Q_ASSERT(begin < end);
414
415
0
    frame.buffer.insert(frame.buffer.end(), begin, end);
416
0
    updatePayloadSize();
417
0
}
418
419
void FrameWriter::updatePayloadSize()
420
0
{
421
0
    const quint32 size = quint32(frame.buffer.size() - frameHeaderSize);
422
0
    Q_ASSERT(size <= maxPayloadSize);
423
0
    setPayloadSize(size);
424
0
}
425
426
bool FrameWriter::write(QIODevice &socket) const
427
0
{
428
0
    auto &buffer = frame.buffer;
429
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
430
    // Do some sanity check first:
431
432
0
    Q_ASSERT(int(frame.type()) < int(FrameType::LAST_FRAME_TYPE));
433
0
    Q_ASSERT(frame.validateHeader() == FrameStatus::goodFrame);
434
435
0
    const auto nWritten = socket.write(reinterpret_cast<const char *>(&buffer[0]),
436
0
                                       buffer.size());
437
0
    return nWritten != -1 && size_type(nWritten) == buffer.size();
438
0
}
439
440
bool FrameWriter::writeHEADERS(QIODevice &socket, quint32 sizeLimit)
441
0
{
442
0
    auto &buffer = frame.buffer;
443
0
    Q_ASSERT(buffer.size() >= frameHeaderSize);
444
445
0
    if (sizeLimit > quint32(maxPayloadSize))
446
0
        sizeLimit = quint32(maxPayloadSize);
447
448
0
    if (quint32(buffer.size() - frameHeaderSize) <= sizeLimit) {
449
0
        addFlag(FrameFlag::END_HEADERS);
450
0
        updatePayloadSize();
451
0
        return write(socket);
452
0
    }
453
454
    // Our HPACK block does not fit into the size limit, remove
455
    // END_HEADERS bit from the first frame, we'll later set
456
    // it on the last CONTINUATION frame:
457
0
    setFlags(frame.flags() & ~FrameFlags(FrameFlag::END_HEADERS));
458
    // Write a frame's header (not controlled by sizeLimit) and
459
    // as many bytes of payload as we can within sizeLimit,
460
    // then send CONTINUATION frames, as needed.
461
0
    setPayloadSize(sizeLimit);
462
0
    const quint32 firstChunkSize = frameHeaderSize + sizeLimit;
463
0
    qint64 written = socket.write(reinterpret_cast<const char *>(&buffer[0]),
464
0
                                  firstChunkSize);
465
466
0
    if (written != qint64(firstChunkSize))
467
0
        return false;
468
469
0
    FrameWriter continuationWriter(FrameType::CONTINUATION, FrameFlag::EMPTY, frame.streamID());
470
0
    quint32 offset = firstChunkSize;
471
472
0
    while (offset != buffer.size()) {
473
0
        const auto chunkSize = std::min(sizeLimit, quint32(buffer.size() - offset));
474
0
        if (chunkSize + offset == buffer.size())
475
0
            continuationWriter.addFlag(FrameFlag::END_HEADERS);
476
0
        continuationWriter.setPayloadSize(chunkSize);
477
0
        if (!continuationWriter.write(socket))
478
0
            return false;
479
0
        written = socket.write(reinterpret_cast<const char *>(&buffer[offset]),
480
0
                               chunkSize);
481
0
        if (written != qint64(chunkSize))
482
0
            return false;
483
484
0
        offset += chunkSize;
485
0
    }
486
487
0
    return true;
488
0
}
489
490
bool FrameWriter::writeDATA(QIODevice &socket, quint32 sizeLimit,
491
                            const uchar *src, quint32 size)
492
0
{
493
    // With DATA frame(s) we always have:
494
    // 1) frame's header (9 bytes)
495
    // 2) a separate payload (from QNonContiguousByteDevice).
496
    // We either fit within a sizeLimit, or split into several
497
    // DATA frames.
498
499
0
    Q_ASSERT(src);
500
501
0
    if (sizeLimit > quint32(maxPayloadSize))
502
0
        sizeLimit = quint32(maxPayloadSize);
503
    // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
504
    // QNonContiguousByteDevice and this 'writeDATA' is probably
505
    // not the last one for a given request.
506
    // This has to be done externally (sending an empty DATA frame with END_STREAM).
507
0
    for (quint32 offset = 0; offset != size;) {
508
0
        const auto chunkSize = std::min(size - offset, sizeLimit);
509
0
        setPayloadSize(chunkSize);
510
        // Frame's header first:
511
0
        if (!write(socket))
512
0
            return false;
513
        // Payload (if any):
514
0
        if (chunkSize) {
515
0
            const auto written = socket.write(reinterpret_cast<const char*>(src + offset),
516
0
                                              chunkSize);
517
0
            if (written != qint64(chunkSize))
518
0
                return false;
519
0
        }
520
521
0
        offset += chunkSize;
522
0
    }
523
524
0
    return true;
525
0
}
526
527
} // Namespace Http2
528
529
QT_END_NAMESPACE