Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
13QT_BEGIN_NAMESPACE
14
15namespace HPack
16{
17
18HeaderSize 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
38
40{
41 return lhs.bitLength == rhs.bitLength && lhs.value == rhs.value;
42}
43
44namespace
45{
46
47using 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.
58constexpr BitPattern Indexed = {1, 1};
59constexpr BitPattern LiteralIncrementalIndexing = {1, 2};
60constexpr BitPattern LiteralNoIndexing = {0, 4};
61constexpr BitPattern LiteralNeverIndexing = {1, 4};
62constexpr BitPattern SizeUpdate = {1, 3};
63
64bool is_literal_field(BitPattern pattern)
65{
66 return pattern == LiteralIncrementalIndexing
67 || pattern == LiteralNoIndexing
68 || pattern == LiteralNeverIndexing;
69}
70
71void write_bit_pattern(BitPattern pattern, BitOStream &outputStream)
72{
73 outputStream.writeBits(pattern.value, pattern.bitLength);
74}
75
76bool 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
95bool is_request_pseudo_header(QByteArrayView name)
96{
97 return name == ":method" || name == ":scheme" ||
98 name == ":authority" || name == ":path";
99}
100
101} // unnamed namespace
102
104 : lookupTable(size, true /*encoder needs search index*/),
106{
107}
108
113
118
123
125{
126 if (!header.size()) {
127 qDebug("empty header");
128 return false;
129 }
130
132 return false;
133
134 for (const auto &field : header) {
136 continue;
137
139 return false;
140 }
141
142 return true;
143}
144
146{
147 if (!header.size()) {
148 qDebug("empty header");
149 return false;
150 }
151
153 return false;
154
155 for (const auto &field : header) {
156 if (field.name == ":status")
157 continue;
158
160 return false;
161 }
162
163 return true;
164}
165
167{
169 qDebug("failed to update own table size");
170 return false;
171 }
172
175
176 return true;
177}
178
180{
181 // Up to a caller (HTTP2 protocol handler)
182 // to validate this size first.
184}
185
190
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
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 }
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
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))
272
273 if (const auto index = lookupTable.indexOf(field.name)) {
276 }
277
280}
281
283{
284 Q_ASSERT(field.name == ":method");
286 if (index)
288
290 Q_ASSERT(index); // ":method" is always in the static table ...
293}
294
296{
297 bool statusFound = false;
298 for (const auto &field : header) {
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 }
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
324{
326
329
330 return true;
331}
332
334 const QByteArray &name, const QByteArray &value,
335 bool withCompression)
336{
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
349 qDebug("failed to prepend a new field");
350 }
351
353
357
358 return true;
359}
360
363 bool withCompression)
364{
366
368 const bool found = lookupTable.fieldName(nameIndex, &name);
371
374 qDebug("failed to prepend a new field");
375 }
376
380
381 return true;
382}
383
385 : lookupTable{size, false /* we do not need search index ... */}
386{
387}
388
390{
391 header.clear();
392 while (true) {
395 return false;
398 return false;
401 return false;
404 return false;
407 return false;
408 } else {
410 }
411 }
412
413 return false;
414}
415
420
425
430
432{
433 // Up to a caller (HTTP2 protocol handler)
434 // to validate this size first.
436}
437
439{
440 quint32 index = 0;
441 if (inputStream.read(&index)) {
442 if (!index) {
443 // "The index value of 0 is not used.
444 // It MUST be treated as a decoding
445 // error if found in an indexed header
446 // field representation."
447 return false;
448 }
449
453 } else {
455 }
456
457 return false;
458}
459
461{
462 // For now, just read and skip bits.
463 quint32 maxSize = 0;
464 if (inputStream.read(&maxSize)) {
466 return false;
467
468 return true;
469 }
470
472 return false;
473}
474
476{
477 // https://http2.github.io/http2-spec/compression.html
478 // 6.2.1, 6.2.2, 6.2.3
479 // Format for all 'literal' is similar,
480 // the difference - is how we update/not our lookup table.
481 quint32 index = 0;
482 if (inputStream.read(&index)) {
484 if (!index) {
485 // Read a string.
486 if (!inputStream.read(&name)) {
488 return false;
489 }
490 } else {
492 return false;
493 }
494
496 if (inputStream.read(&value))
498 }
499
501
502 return false;
503}
504
506 const QByteArray &name,
507 const QByteArray &value)
508{
511 return false;
512 }
513
515 qDebug("about to add a new field, but expected a Dynamic Table Size Update");
516 return false; // We expected a dynamic table size update.
517 }
518
520 return true;
521}
522
524{
525 const auto errorCode(inputStream.error());
527 return;
528
529 // For now error handling not needed here,
530 // HTTP2 layer will end with session error/COMPRESSION_ERROR.
531}
532
533std::optional<QUrl> makePromiseKeyUrl(const HttpHeader &requestHeader)
534{
535 constexpr QByteArrayView names[] = { ":authority", ":method", ":path", ":scheme" };
536 enum PseudoHeaderEnum
537 {
538 Authority,
539 Method,
540 Path,
541 Scheme
542 };
543 std::array<std::optional<QByteArrayView>, std::size(names)> pseudoHeaders{};
544 for (const auto &field : requestHeader) {
545 const auto *it = std::find(std::begin(names), std::end(names), QByteArrayView(field.name));
546 if (it != std::end(names)) {
547 const auto index = std::distance(std::begin(names), it);
548 if (field.value.isEmpty() || pseudoHeaders.at(index).has_value())
549 return {};
550 pseudoHeaders[index] = field.value;
551 }
552 }
553
554 auto optionalIsSet = [](const auto &x) { return x.has_value(); };
555 if (!std::all_of(pseudoHeaders.begin(), pseudoHeaders.end(), optionalIsSet)) {
556 // All four required, HTTP/2 8.1.2.3.
557 return {};
558 }
559
560 const QByteArrayView method = pseudoHeaders[Method].value();
561 if (method.compare("get", Qt::CaseInsensitive) != 0 &&
562 method.compare("head", Qt::CaseInsensitive) != 0) {
563 return {};
564 }
565
566 QUrl url;
567 url.setScheme(QLatin1StringView(pseudoHeaders[Scheme].value()));
568 url.setAuthority(QLatin1StringView(pseudoHeaders[Authority].value()));
569 url.setPath(QLatin1StringView(pseudoHeaders[Path].value()));
570
571 if (!url.isValid())
572 return {};
573 return url;
574}
575
576}
577
578QT_END_NAMESPACE
HeaderSize header_size(const HttpHeader &header)
Definition hpack.cpp:18
bool operator==(BitPattern lhs, BitPattern rhs)
Definition hpack.cpp:39
std::optional< QUrl > makePromiseKeyUrl(const HttpHeader &requestHeader)
Definition hpack.cpp:533