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/bitstreams.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 "bitstreams_p.h"
6
#include "huffman_p.h"
7
8
#include <QtCore/qbytearray.h>
9
10
#include <limits>
11
12
QT_BEGIN_NAMESPACE
13
14
static_assert(std::numeric_limits<uchar>::digits == 8, "octets expected");
15
16
namespace HPack
17
{
18
19
BitOStream::BitOStream(std::vector<uchar> &b)
20
0
    : buffer(b),
21
      // All data 'packed' before:
22
0
      bitsSet(8 * quint64(b.size()))
23
0
{
24
0
}
25
26
void BitOStream::writeBits(uchar bits, quint8 bitLength)
27
0
{
28
0
    Q_ASSERT(bitLength <= 8);
29
30
0
    quint8 count = bitsSet % 8; // bits used in buffer.back(), but 0 means 8
31
0
    bits <<= 8 - bitLength; // at top of byte, lower bits clear
32
0
    if (count) { // we have a part-used byte; fill it some more:
33
0
        buffer.back() |= bits >> count;
34
0
        count = 8 - count;
35
0
    } // count bits have been consumed (and 0 now means 0)
36
0
    if (bitLength > count)
37
0
        buffer.push_back(bits << count);
38
39
0
    bitsSet += bitLength;
40
0
}
41
42
void BitOStream::write(quint32 src)
43
0
{
44
0
    const quint8 prefixLen = 8 - bitsSet % 8;
45
0
    const quint32 fullPrefix = (1 << prefixLen) - 1;
46
47
    // https://http2.github.io/http2-spec/compression.html#low-level.representation,
48
    // 5.1
49
0
    if (src < fullPrefix) {
50
0
        writeBits(uchar(src), prefixLen);
51
0
    } else {
52
0
        writeBits(uchar(fullPrefix), prefixLen);
53
        // We're on the byte boundary now,
54
        // so we can just 'push_back'.
55
0
        Q_ASSERT(!(bitsSet % 8));
56
0
        src -= fullPrefix;
57
0
        while (src >= 128) {
58
0
            buffer.push_back(uchar(src % 128 + 128));
59
0
            src /= 128;
60
0
            bitsSet += 8;
61
0
        }
62
0
        buffer.push_back(src);
63
0
        bitsSet += 8;
64
0
    }
65
0
}
66
67
void BitOStream::write(QByteArrayView src, bool compressed)
68
0
{
69
0
    quint32 byteLen = src.size();
70
0
    if (compressed && byteLen) {
71
0
        const auto bitLen = huffman_encoded_bit_length(src);
72
0
        Q_ASSERT(bitLen && std::numeric_limits<quint32>::max() >= (bitLen + 7) / 8);
73
0
        byteLen = (bitLen + 7) / 8;
74
0
        writeBits(uchar(1), 1); // bit set - compressed
75
0
    } else {
76
0
        writeBits(uchar(0), 1); // no compression.
77
0
    }
78
79
0
    write(byteLen);
80
81
0
    if (compressed) {
82
0
        huffman_encode_string(src, *this);
83
0
    } else {
84
0
        bitsSet += quint64(src.size()) * 8;
85
0
        buffer.insert(buffer.end(), src.begin(), src.end());
86
0
    }
87
0
}
88
89
quint64 BitOStream::bitLength() const
90
0
{
91
0
    return bitsSet;
92
0
}
93
94
quint64 BitOStream::byteLength() const
95
0
{
96
0
    return buffer.size();
97
0
}
98
99
const uchar *BitOStream::begin() const
100
0
{
101
0
    return &buffer[0];
102
0
}
103
104
const uchar *BitOStream::end() const
105
0
{
106
0
    return &buffer[0] + buffer.size();
107
0
}
108
109
void BitOStream::clear()
110
0
{
111
0
    buffer.clear();
112
0
    bitsSet = 0;
113
0
}
114
115
BitIStream::BitIStream()
116
    : first(),
117
      last(),
118
      offset(),
119
0
      streamError(Error::NoError)
120
0
{
121
0
}
122
123
BitIStream::BitIStream(const uchar *begin, const uchar *end)
124
0
                 : first(begin),
125
0
                   last(end),
126
                   offset(),
127
0
                   streamError(Error::NoError)
128
0
{
129
0
}
130
131
quint64 BitIStream::bitLength() const
132
0
{
133
0
    return quint64(last - first) * 8;
134
0
}
135
136
bool BitIStream::hasMoreBits() const
137
0
{
138
0
    return offset < bitLength();
139
0
}
140
141
bool BitIStream::skipBits(quint64 nBits)
142
0
{
143
0
    if (nBits > bitLength() || bitLength() - nBits < offset)
144
0
        return false;
145
146
0
    offset += nBits;
147
0
    return true;
148
0
}
149
150
bool BitIStream::rewindOffset(quint64 nBits)
151
0
{
152
0
    if (nBits > offset)
153
0
        return false;
154
155
0
    offset -= nBits;
156
0
    return true;
157
0
}
158
159
bool BitIStream::read(quint32 *dstPtr)
160
0
{
161
0
    Q_ASSERT(dstPtr);
162
0
    quint32 &dst = *dstPtr;
163
164
    // 5.1 Integer Representation
165
    //
166
    // Integers are used to represent name indexes, header field indexes, or string lengths.
167
    // An integer representation can start anywhere within an octet.
168
    // To allow for optimized processing, an integer representation always finishes at the end of an octet.
169
    // An integer is represented in two parts: a prefix that fills the current octet and an optional
170
    // list of octets that are used if the integer value does not fit within the prefix.
171
    // The number of bits of the prefix (called N) is a parameter of the integer representation.
172
    // If the integer value is small enough, i.e., strictly less than 2N-1, it is compressed within the N-bit prefix.
173
    // ...
174
    // The prefix size, N, is always between 1 and 8 bits. An integer
175
    // starting at an octet boundary will have an 8-bit prefix.
176
177
    // Technically, such integers can be of any size, but as we do not have arbitrary-long integers,
178
    // everything that does not fit into 'dst' we consider as an error (after all, try to allocate a string
179
    // of such size and ... hehehe - send it as a part of a header!
180
181
    // This function updates the offset _only_ if the read was successful.
182
0
    if (offset >= bitLength()) {
183
0
        setError(Error::NotEnoughData);
184
0
        return false;
185
0
    }
186
187
0
    setError(Error::NoError);
188
189
0
    const quint32 prefixLen = 8 - offset % 8;
190
0
    const quint32 fullPrefix = (1 << prefixLen) - 1;
191
192
0
    const uchar prefix = uchar(first[offset / 8] & fullPrefix);
193
0
    if (prefix < fullPrefix) {
194
        // The number fitted into the prefix bits.
195
0
        dst = prefix;
196
0
        offset += prefixLen;
197
0
        return true;
198
0
    }
199
200
0
    quint32 newOffset = offset + prefixLen;
201
    // We have a list of bytes representing an integer ...
202
0
    quint64 val = prefix;
203
0
    quint32 octetPower = 0;
204
205
0
    while (true) {
206
0
        if (newOffset >= bitLength()) {
207
0
            setError(Error::NotEnoughData);
208
0
            return false;
209
0
        }
210
211
0
        const uchar octet = first[newOffset / 8];
212
213
0
        if (octetPower == 28 && octet > 15) {
214
0
            qCritical("integer is too big");
215
0
            setError(Error::InvalidInteger);
216
0
            return false;
217
0
        }
218
219
0
        val += quint32(octet & 0x7f) << octetPower;
220
0
        newOffset += 8;
221
222
0
        if (!(octet & 0x80)) {
223
            // The most significant bit of each octet is used
224
            // as a continuation flag: its value is set to 1
225
            // except for the last octet in the list.
226
0
            break;
227
0
        }
228
229
0
        octetPower += 7;
230
0
    }
231
232
0
    dst = val;
233
0
    offset = newOffset;
234
0
    Q_ASSERT(!(offset % 8));
235
236
0
    return true;
237
0
}
238
239
bool BitIStream::read(QByteArray *dstPtr)
240
0
{
241
0
    Q_ASSERT(dstPtr);
242
0
    QByteArray &dst = *dstPtr;
243
    //5.2 String Literal Representation
244
    //
245
    // Header field names and header field values can be represented as string literals.
246
    // A string literal is compressed as a sequence of octets, either by directly encoding
247
    // the string literal's octets or by using a Huffman code.
248
249
    // We update the offset _only_ if the read was successful.
250
251
0
    const quint64 oldOffset = offset;
252
0
    uchar compressed = 0;
253
0
    if (peekBits(offset, 1, &compressed) != 1 || !skipBits(1)) {
254
0
        setError(Error::NotEnoughData);
255
0
        return false;
256
0
    }
257
258
0
    setError(Error::NoError);
259
260
0
    quint32 len = 0;
261
0
    if (read(&len)) {
262
0
        Q_ASSERT(!(offset % 8));
263
0
        if (len <= (bitLength() - offset) / 8) { // We have enough data to read a string ...
264
0
            if (!compressed) {
265
                // Now good news, integer always ends on a byte boundary.
266
                // We can read 'len' bytes without any bit magic.
267
0
                const char *src = reinterpret_cast<const char *>(first + offset / 8);
268
0
                dst = QByteArray(src, len);
269
0
                offset += quint64(len) * 8;
270
0
                return true;
271
0
            }
272
273
0
            BitIStream slice(first + offset / 8, first + offset / 8 + len);
274
0
            if (huffman_decode_string(slice, &dst)) {
275
0
                offset += quint64(len) * 8;
276
0
                return true;
277
0
            }
278
279
0
            setError(Error::CompressionError);
280
0
        } else {
281
0
            setError(Error::NotEnoughData);
282
0
        }
283
0
    } // else the exact reason was set by read(quint32).
284
285
0
    offset = oldOffset;
286
0
    return false;
287
0
}
288
289
BitIStream::Error BitIStream::error() const
290
0
{
291
0
    return streamError;
292
0
}
293
294
void BitIStream::setError(Error newState)
295
0
{
296
0
    streamError = newState;
297
0
}
298
299
} // namespace HPack
300
301
QT_END_NAMESPACE