Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
hpack.cpp
Go to the documentation of this file.
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
"hpack_p.h"
7
8
#
include
<
QtCore
/
qbytearray
.
h
>
9
#
include
<
QtCore
/
qdebug
.
h
>
10
11
#
include
<
limits
>
12
13
QT_BEGIN_NAMESPACE
14
15
namespace
HPack
16
{
17
18
HeaderSize
header_size
(
const
HttpHeader &header)
19
{
20
HeaderSize size(
true
, 0);
21
for
(
const
HeaderField &field : header) {
22
HeaderSize delta = entry_size(field);
23
if
(!delta.first)
24
return
HeaderSize();
25
if
(std::numeric_limits<quint32>::max() - size.second < delta.second)
26
return
HeaderSize();
27
size.second += delta.second;
28
}
29
30
return
size;
31
}
32
33
struct
BitPattern
34
{
35
uchar
value
;
36
uchar
bitLength
;
37
};
38
39
bool
operator
==(
BitPattern
lhs,
BitPattern
rhs)
40
{
41
return
lhs.bitLength == rhs.bitLength && lhs.value == rhs.value;
42
}
43
44
namespace
45
{
46
47
using
StreamError = BitIStream::Error;
48
49
// There are several bit patterns to distinguish header fields:
50
// 1 - indexed
51
// 01 - literal with incremented indexing
52
// 0000 - literal without indexing
53
// 0001 - literal, never indexing
54
// 001 - dynamic table size update.
55
56
// It's always 1 or 0 actually, but the number of bits to extract
57
// from the input stream - differs.
58
constexpr
BitPattern
Indexed = {1, 1};
59
constexpr
BitPattern
LiteralIncrementalIndexing = {1, 2};
60
constexpr
BitPattern
LiteralNoIndexing = {0, 4};
61
constexpr
BitPattern
LiteralNeverIndexing = {1, 4};
62
constexpr
BitPattern
SizeUpdate = {1, 3};
63
64
bool
is_literal_field(
BitPattern
pattern)
65
{
66
return
pattern
==
LiteralIncrementalIndexing
67
|| pattern
==
LiteralNoIndexing
68
|| pattern
==
LiteralNeverIndexing;
69
}
70
71
void
write_bit_pattern(
BitPattern
pattern, BitOStream &outputStream)
72
{
73
outputStream.writeBits(pattern.value, pattern.bitLength);
74
}
75
76
bool
read_bit_pattern(
BitPattern
pattern, BitIStream &inputStream)
77
{
78
uchar chunk = 0;
79
80
const
quint32 bitsRead = inputStream.peekBits(inputStream.streamOffset(),
81
pattern.bitLength, &chunk);
82
if
(bitsRead != pattern.bitLength)
83
return
false
;
84
85
// Since peekBits packs in the most significant bits, shift it!
86
chunk >>= (8 - bitsRead);
87
if
(chunk != pattern.value)
88
return
false
;
89
90
inputStream.skipBits(pattern.bitLength);
91
92
return
true
;
93
}
94
95
bool
is_request_pseudo_header(QByteArrayView name)
96
{
97
return
name ==
":method"
|| name ==
":scheme"
||
98
name ==
":authority"
|| name ==
":path"
;
99
}
100
101
}
// unnamed namespace
102
103
Encoder
::
Encoder
(
quint32
size
,
bool
compress
)
104
:
lookupTable
(
size
,
true
/*encoder needs search index*/
),
105
compressStrings
(
compress
)
106
{
107
}
108
109
quint32
Encoder
::
dynamicTableSize
()
const
110
{
111
return
lookupTable
.
dynamicDataSize
();
112
}
113
114
quint32
Encoder
::
dynamicTableCapacity
()
const
115
{
116
return
lookupTable
.
dynamicDataCapacity
();
117
}
118
119
quint32
Encoder
::
maxDynamicTableCapacity
()
const
120
{
121
return
lookupTable
.
maxDynamicDataCapacity
();
122
}
123
124
bool
Encoder
::
encodeRequest
(
BitOStream
&
outputStream
,
const
HttpHeader
&
header
)
125
{
126
if
(!
header
.
size
()) {
127
qDebug
(
"empty header"
);
128
return
false
;
129
}
130
131
if
(!
encodeRequestPseudoHeaders
(
outputStream
,
header
))
132
return
false
;
133
134
for
(
const
auto
&
field
:
header
) {
135
if
(
is_request_pseudo_header
(
field
.
name
))
136
continue
;
137
138
if
(!
encodeHeaderField
(
outputStream
,
field
))
139
return
false
;
140
}
141
142
return
true
;
143
}
144
145
bool
Encoder
::
encodeResponse
(
BitOStream
&
outputStream
,
const
HttpHeader
&
header
)
146
{
147
if
(!
header
.
size
()) {
148
qDebug
(
"empty header"
);
149
return
false
;
150
}
151
152
if
(!
encodeResponsePseudoHeaders
(
outputStream
,
header
))
153
return
false
;
154
155
for
(
const
auto
&
field
:
header
) {
156
if
(
field
.
name
==
":status"
)
157
continue
;
158
159
if
(!
encodeHeaderField
(
outputStream
,
field
))
160
return
false
;
161
}
162
163
return
true
;
164
}
165
166
bool
Encoder
::
encodeSizeUpdate
(
BitOStream
&
outputStream
,
quint32
newSize
)
167
{
168
if
(!
lookupTable
.
updateDynamicTableSize
(
newSize
)) {
169
qDebug
(
"failed to update own table size"
);
170
return
false
;
171
}
172
173
write_bit_pattern
(
SizeUpdate
,
outputStream
);
174
outputStream
.
write
(
newSize
);
175
176
return
true
;
177
}
178
179
void
Encoder
::
setMaxDynamicTableSize
(
quint32
size
)
180
{
181
// Up to a caller (HTTP2 protocol handler)
182
// to validate this size first.
183
lookupTable
.
setMaxDynamicTableSize
(
size
);
184
}
185
186
void
Encoder
::
setCompressStrings
(
bool
compress
)
187
{
188
compressStrings
=
compress
;
189
}
190
191
bool
Encoder
::
encodeRequestPseudoHeaders
(
BitOStream
&
outputStream
,
192
const
HttpHeader
&
header
)
193
{
194
// The following pseudo-header fields are defined for HTTP/2 requests:
195
// - The :method pseudo-header field includes the HTTP method
196
// - The :scheme pseudo-header field includes the scheme portion of the target URI
197
// - The :authority pseudo-header field includes the authority portion of the target URI
198
// - The :path pseudo-header field includes the path and query parts of the target URI
199
200
// All HTTP/2 requests MUST include exactly one valid value for the :method,
201
// :scheme, and :path pseudo-header fields, unless it is a CONNECT request
202
// (Section 8.3). An HTTP request that omits mandatory pseudo-header fields
203
// is malformed (Section 8.1.2.6).
204
205
using
size_type
=
decltype
(
header
.
size
());
206
207
bool
methodFound
=
false
;
208
constexpr
QByteArrayView
headerName
[] = {
":authority"
,
":scheme"
,
":path"
};
209
constexpr
size_type
nHeaders
=
std
::
size
(
headerName
);
210
bool
headerFound
[
nHeaders
] = {};
211
212
for
(
const
auto
&
field
:
header
) {
213
if
(
field
.
name
==
":status"
) {
214
qCritical
(
"invalid pseudo-header (:status) in a request"
);
215
return
false
;
216
}
217
218
if
(
field
.
name
==
":method"
) {
219
if
(
methodFound
) {
220
qCritical
(
"only one :method pseudo-header is allowed"
);
221
return
false
;
222
}
223
224
if
(!
encodeMethod
(
outputStream
,
field
))
225
return
false
;
226
methodFound
=
true
;
227
}
else
if
(
field
.
name
==
"cookie"
) {
228
// "crumbs" ...
229
}
else
{
230
for
(
size_type
j
= 0;
j
<
nHeaders
; ++
j
) {
231
if
(
field
.
name
==
headerName
[
j
]) {
232
if
(
headerFound
[
j
]) {
233
qCritical
() <<
"only one"
<<
headerName
[
j
] <<
"pseudo-header is allowed"
;
234
return
false
;
235
}
236
if
(!
encodeHeaderField
(
outputStream
,
field
))
237
return
false
;
238
headerFound
[
j
] =
true
;
239
break
;
240
}
241
}
242
}
243
}
244
245
if
(!
methodFound
) {
246
qCritical
(
"mandatory :method pseudo-header not found"
);
247
return
false
;
248
}
249
250
// 1: don't demand headerFound[0], as :authority isn't mandatory.
251
for
(
size_type
i
= 1;
i
<
nHeaders
; ++
i
) {
252
if
(!
headerFound
[
i
]) {
253
qCritical
() <<
"mandatory"
<<
headerName
[
i
]
254
<<
"pseudo-header not found"
;
255
return
false
;
256
}
257
}
258
259
return
true
;
260
}
261
262
bool
Encoder
::
encodeHeaderField
(
BitOStream
&
outputStream
,
const
HeaderField
&
field
)
263
{
264
// TODO: at the moment we never use LiteralNo/Never Indexing ...
265
266
// Here we try:
267
// 1. indexed
268
// 2. literal indexed with indexed name/literal value
269
// 3. literal indexed with literal name/literal value
270
if
(
const
auto
index
=
lookupTable
.
indexOf
(
field
.
name
,
field
.
value
))
271
return
encodeIndexedField
(
outputStream
,
index
);
272
273
if
(
const
auto
index
=
lookupTable
.
indexOf
(
field
.
name
)) {
274
return
encodeLiteralField
(
outputStream
,
LiteralIncrementalIndexing
,
275
index
,
field
.
value
,
compressStrings
);
276
}
277
278
return
encodeLiteralField
(
outputStream
,
LiteralIncrementalIndexing
,
279
field
.
name
,
field
.
value
,
compressStrings
);
280
}
281
282
bool
Encoder
::
encodeMethod
(
BitOStream
&
outputStream
,
const
HeaderField
&
field
)
283
{
284
Q_ASSERT
(
field
.
name
==
":method"
);
285
quint32
index
=
lookupTable
.
indexOf
(
field
.
name
,
field
.
value
);
286
if
(
index
)
287
return
encodeIndexedField
(
outputStream
,
index
);
288
289
index
=
lookupTable
.
indexOf
(
field
.
name
);
290
Q_ASSERT
(
index
);
// ":method" is always in the static table ...
291
return
encodeLiteralField
(
outputStream
,
LiteralIncrementalIndexing
,
292
index
,
field
.
value
,
compressStrings
);
293
}
294
295
bool
Encoder
::
encodeResponsePseudoHeaders
(
BitOStream
&
outputStream
,
const
HttpHeader
&
header
)
296
{
297
bool
statusFound
=
false
;
298
for
(
const
auto
&
field
:
header
) {
299
if
(
is_request_pseudo_header
(
field
.
name
)) {
300
qCritical
() <<
"invalid pseudo-header"
<<
field
.
name
<<
"in http response"
;
301
return
false
;
302
}
303
304
if
(
field
.
name
==
":status"
) {
305
if
(
statusFound
) {
306
qDebug
(
"only one :status pseudo-header is allowed"
);
307
return
false
;
308
}
309
if
(!
encodeHeaderField
(
outputStream
,
field
))
310
return
false
;
311
statusFound
=
true
;
312
}
else
if
(
field
.
name
==
"cookie"
) {
313
// "crumbs"..
314
}
315
}
316
317
if
(!
statusFound
)
318
qCritical
(
"mandatory :status pseudo-header not found"
);
319
320
return
statusFound
;
321
}
322
323
bool
Encoder
::
encodeIndexedField
(
BitOStream
&
outputStream
,
quint32
index
)
const
324
{
325
Q_ASSERT
(
lookupTable
.
indexIsValid
(
index
));
326
327
write_bit_pattern
(
Indexed
,
outputStream
);
328
outputStream
.
write
(
index
);
329
330
return
true
;
331
}
332
333
bool
Encoder
::
encodeLiteralField
(
BitOStream
&
outputStream
,
BitPattern
fieldType
,
334
const
QByteArray
&
name
,
const
QByteArray
&
value
,
335
bool
withCompression
)
336
{
337
Q_ASSERT
(
is_literal_field
(
fieldType
));
338
// According to HPACK, the bit pattern is
339
// 01 | 000000 (integer 0 that fits into 6-bit prefix),
340
// since integers always end on byte boundary,
341
// this also implies that we always start at bit offset == 0.
342
if
(
outputStream
.
bitLength
() % 8) {
343
qCritical
(
"invalid bit offset"
);
344
return
false
;
345
}
346
347
if
(
fieldType
==
LiteralIncrementalIndexing
) {
348
if
(!
lookupTable
.
prependField
(
name
,
value
))
349
qDebug
(
"failed to prepend a new field"
);
350
}
351
352
write_bit_pattern
(
fieldType
,
outputStream
);
353
354
outputStream
.
write
(0);
355
outputStream
.
write
(
name
,
withCompression
);
356
outputStream
.
write
(
value
,
withCompression
);
357
358
return
true
;
359
}
360
361
bool
Encoder
::
encodeLiteralField
(
BitOStream
&
outputStream
,
BitPattern
fieldType
,
362
quint32
nameIndex
,
const
QByteArray
&
value
,
363
bool
withCompression
)
364
{
365
Q_ASSERT
(
is_literal_field
(
fieldType
));
366
367
QByteArray
name
;
368
const
bool
found
=
lookupTable
.
fieldName
(
nameIndex
, &
name
);
369
Q_UNUSED
(
found
);
370
Q_ASSERT
(
found
);
371
372
if
(
fieldType
==
LiteralIncrementalIndexing
) {
373
if
(!
lookupTable
.
prependField
(
name
,
value
))
374
qDebug
(
"failed to prepend a new field"
);
375
}
376
377
write_bit_pattern
(
fieldType
,
outputStream
);
378
outputStream
.
write
(
nameIndex
);
379
outputStream
.
write
(
value
,
withCompression
);
380
381
return
true
;
382
}
383
384
Decoder
::
Decoder
(
quint32
size
)
385
:
lookupTable
{
size
,
false
/* we do not need search index ... */
}
386
{
387
}
388
389
bool
Decoder
::
decodeHeaderFields
(
BitIStream
&
inputStream
)
390
{
391
header
.
clear
();
392
headerListSize
= 0;
393
while
(
true
) {
394
if
(
read_bit_pattern
(
Indexed
,
inputStream
)) {
395
if
(!
decodeIndexedField
(
inputStream
))
396
return
false
;
397
}
else
if
(
read_bit_pattern
(
LiteralIncrementalIndexing
,
inputStream
)) {
398
if
(!
decodeLiteralField
(
LiteralIncrementalIndexing
,
inputStream
))
399
return
false
;
400
}
else
if
(
read_bit_pattern
(
LiteralNoIndexing
,
inputStream
)) {
401
if
(!
decodeLiteralField
(
LiteralNoIndexing
,
inputStream
))
402
return
false
;
403
}
else
if
(
read_bit_pattern
(
LiteralNeverIndexing
,
inputStream
)) {
404
if
(!
decodeLiteralField
(
LiteralNeverIndexing
,
inputStream
))
405
return
false
;
406
}
else
if
(
read_bit_pattern
(
SizeUpdate
,
inputStream
)) {
407
if
(!
decodeSizeUpdate
(
inputStream
))
408
return
false
;
409
}
else
{
410
return
inputStream
.
bitLength
() ==
inputStream
.
streamOffset
();
411
}
412
}
413
414
return
false
;
415
}
416
417
quint32
Decoder
::
dynamicTableSize
()
const
418
{
419
return
lookupTable
.
dynamicDataSize
();
420
}
421
422
quint32
Decoder
::
dynamicTableCapacity
()
const
423
{
424
return
lookupTable
.
dynamicDataCapacity
();
425
}
426
427
quint32
Decoder
::
maxDynamicTableCapacity
()
const
428
{
429
return
lookupTable
.
maxDynamicDataCapacity
();
430
}
431
432
void
Decoder
::
setMaxDynamicTableSize
(
quint32
size
)
433
{
434
// Up to a caller (HTTP2 protocol handler)
435
// to validate this size first.
436
lookupTable
.
setMaxDynamicTableSize
(
size
);
437
}
438
439
void
Decoder
::
setMaxHeaderListSize
(
quint32
size
)
440
{
441
maxHeaderListSize
=
size
;
442
}
443
444
bool
Decoder
::
decodeIndexedField
(
BitIStream
&
inputStream
)
445
{
446
quint32
index
= 0;
447
if
(
inputStream
.
read
(&
index
)) {
448
if
(!
index
) {
449
// "The index value of 0 is not used.
450
// It MUST be treated as a decoding
451
// error if found in an indexed header
452
// field representation."
453
return
false
;
454
}
455
456
QByteArray
name
,
value
;
457
if
(
lookupTable
.
field
(
index
, &
name
, &
value
))
458
return
processDecodedField
(
Indexed
,
name
,
value
);
459
}
else
{
460
handleStreamError
(
inputStream
);
461
}
462
463
return
false
;
464
}
465
466
bool
Decoder
::
decodeSizeUpdate
(
BitIStream
&
inputStream
)
467
{
468
// For now, just read and skip bits.
469
quint32
maxSize
= 0;
470
if
(
inputStream
.
read
(&
maxSize
)) {
471
if
(!
lookupTable
.
updateDynamicTableSize
(
maxSize
))
472
return
false
;
473
474
return
true
;
475
}
476
477
handleStreamError
(
inputStream
);
478
return
false
;
479
}
480
481
bool
Decoder
::
decodeLiteralField
(
BitPattern
fieldType
,
BitIStream
&
inputStream
)
482
{
483
// https://http2.github.io/http2-spec/compression.html
484
// 6.2.1, 6.2.2, 6.2.3
485
// Format for all 'literal' is similar,
486
// the difference - is how we update/not our lookup table.
487
quint32
index
= 0;
488
if
(
inputStream
.
read
(&
index
)) {
489
QByteArray
name
;
490
if
(!
index
) {
491
// Read a string.
492
if
(!
inputStream
.
read
(&
name
)) {
493
handleStreamError
(
inputStream
);
494
return
false
;
495
}
496
}
else
{
497
if
(!
lookupTable
.
fieldName
(
index
, &
name
))
498
return
false
;
499
}
500
501
QByteArray
value
;
502
if
(
inputStream
.
read
(&
value
))
503
return
processDecodedField
(
fieldType
,
name
,
value
);
504
}
505
506
handleStreamError
(
inputStream
);
507
508
return
false
;
509
}
510
511
bool
Decoder
::
processDecodedField
(
BitPattern
fieldType
,
512
const
QByteArray
&
name
,
513
const
QByteArray
&
value
)
514
{
515
if
(
fieldType
==
LiteralIncrementalIndexing
) {
516
if
(!
lookupTable
.
prependField
(
name
,
value
))
517
return
false
;
518
}
519
520
if
(
lookupTable
.
maxDynamicDataCapacity
() <
lookupTable
.
dynamicDataCapacity
()) {
521
qDebug
(
"about to add a new field, but expected a Dynamic Table Size Update"
);
522
return
false
;
// We expected a dynamic table size update.
523
}
524
525
const
auto
[
sizeOk
,
fieldSize
] =
entry_size
(
name
,
value
);
526
headerListSize
+=
fieldSize
;
527
if
(!
sizeOk
||
headerListSize
>
maxHeaderListSize
)
528
return
false
;
529
530
header
.
push_back
(
HeaderField
(
name
,
value
));
531
return
true
;
532
}
533
534
void
Decoder
::
handleStreamError
(
BitIStream
&
inputStream
)
535
{
536
const
auto
errorCode
(
inputStream
.
error
());
537
if
(
errorCode
==
StreamError
::
NoError
)
538
return
;
539
540
// For now error handling not needed here,
541
// HTTP2 layer will end with session error/COMPRESSION_ERROR.
542
}
543
544
std
::
optional
<
QUrl
>
makePromiseKeyUrl
(
const
HttpHeader &requestHeader)
545
{
546
constexpr
QByteArrayView names[] = {
":authority"
,
":method"
,
":path"
,
":scheme"
};
547
enum
PseudoHeaderEnum
548
{
549
Authority,
550
Method,
551
Path,
552
Scheme
553
};
554
std
::array<
std
::optional<QByteArrayView>,
std
::size(names)> pseudoHeaders{};
555
for
(
const
auto
&field : requestHeader) {
556
const
auto
*it = std::find(std::begin(names), std::end(names), QByteArrayView(field.name));
557
if
(it != std::end(names)) {
558
const
auto
index = std::distance(std::begin(names), it);
559
if
(field.value.isEmpty() || pseudoHeaders.at(index).has_value())
560
return
{};
561
pseudoHeaders[index] = field.value;
562
}
563
}
564
565
auto
optionalIsSet = [](
const
auto
&x) {
return
x.has_value(); };
566
if
(!
std
::all_of(pseudoHeaders.begin(), pseudoHeaders.end(), optionalIsSet)) {
567
// All four required, HTTP/2 8.1.2.3.
568
return
{};
569
}
570
571
const
QByteArrayView method = pseudoHeaders[Method].value();
572
if
(method.compare(
"get"
, Qt::CaseInsensitive) != 0 &&
573
method.compare(
"head"
, Qt::CaseInsensitive) != 0) {
574
return
{};
575
}
576
577
QUrl url;
578
url.setScheme(QLatin1StringView(pseudoHeaders[Scheme].value()));
579
url.setAuthority(QLatin1StringView(pseudoHeaders[Authority].value()));
580
url.setPath(QLatin1StringView(pseudoHeaders[Path].value()));
581
582
if
(!url.isValid())
583
return
{};
584
return
url;
585
}
586
587
}
588
589
QT_END_NAMESPACE
HPack
Definition
bitstreams.cpp:17
HPack::header_size
HeaderSize header_size(const HttpHeader &header)
Definition
hpack.cpp:18
HPack::operator==
bool operator==(BitPattern lhs, BitPattern rhs)
Definition
hpack.cpp:39
HPack::makePromiseKeyUrl
std::optional< QUrl > makePromiseKeyUrl(const HttpHeader &requestHeader)
Definition
hpack.cpp:544
QT_BEGIN_NAMESPACE
Combined button and popup list for selecting options.
Definition
qsequentialanimationgroup.cpp:47
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
HPack::BitPattern
Definition
hpack.cpp:34
HPack::BitPattern::bitLength
uchar bitLength
Definition
hpack.cpp:36
HPack::BitPattern::value
uchar value
Definition
hpack.cpp:35
qtbase
src
network
access
http2
hpack.cpp
Generated on
for Qt by
1.18.0