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
hpacktable.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 "hpacktable_p.h"
6
7#include <QtCore/qdebug.h>
8
9#include <algorithm>
10#include <cstddef>
11#include <cstring>
12#include <limits>
13
14
15QT_BEGIN_NAMESPACE
16
17namespace HPack
18{
19
20HeaderSize entry_size(QByteArrayView name, QByteArrayView value)
21{
22 // 32 comes from HPACK:
23 // "4.1 Calculating Table Size
24 // Note: The additional 32 octets account for an estimated overhead associated
25 // with an entry. For example, an entry structure using two 64-bit pointers
26 // to reference the name and the value of the entry and two 64-bit integers
27 // for counting the number of references to the name and value would have
28 // 32 octets of overhead."
29
30 size_t sum;
31 if (qAddOverflow(size_t(name.size()), size_t(value.size()), &sum))
32 return HeaderSize();
33 if (sum > (std::numeric_limits<unsigned>::max() - 32))
34 return HeaderSize();
35 return HeaderSize(true, quint32(sum + 32));
36}
37
38namespace
39{
40
41int compare(const QByteArray &lhs, const QByteArray &rhs)
42{
43 if (const int minLen = std::min(lhs.size(), rhs.size())) {
44 // We use memcmp, since strings in headers are allowed
45 // to contain '\0'.
46 const int cmp = std::memcmp(lhs.constData(), rhs.constData(), std::size_t(minLen));
47 if (cmp)
48 return cmp;
49 }
50
51 return lhs.size() - rhs.size();
52}
53
54} // unnamed namespace
55
57 : field(),
58 chunk(),
59 offset(),
60 table()
61{
62}
63
65 const Chunk *c,
66 quint32 o,
67 const FieldLookupTable *t)
68 : field(f),
69 chunk(c),
70 offset(o),
71 table(t)
72{
74}
75
77{
80
81 int cmp = compare(field->name, rhs.field->name);
82 if (cmp)
83 return cmp < 0;
84
86 if (cmp)
87 return cmp < 0;
88
89 if (!chunk) // 'this' is not in the searchIndex.
90 return rhs.chunk;
91
92 if (!rhs.chunk) // not in the searchIndex.
93 return false;
94
97
100
101 // Later added - smaller is chunk index (since we push_front).
104
105 // Later added - smaller is offset.
106 return offset > rhs.offset;
107}
108
119
120
122{
123 const auto entrySize = entry_size(name, value);
124 if (!entrySize.first)
125 return false;
126
129 return true;
130 }
131
133 evictEntry();
134
135 if (!begin) {
136 // Either no more space or empty table ...
138 end += ChunkSize;
140 }
141
142 --begin;
143
145 ++nDynamic;
146
147 auto &newField = front();
150
151 if (useIndex) {
152 const auto result = searchIndex.insert(frontKey());
155 }
156
157 return true;
158}
159
161{
162 if (!nDynamic)
163 return;
164
165 Q_ASSERT(end != begin);
166
167 if (useIndex) {
168 const auto res = searchIndex.erase(backKey());
169 Q_UNUSED(res);
170 Q_ASSERT(res == 1);
171 }
172
173 const HeaderField &field = back();
174 const auto entrySize = entry_size(field);
178
179 --nDynamic;
180 --end;
181
182 if (end == begin) {
183 Q_ASSERT(chunks.size() == 1);
184 end = ChunkSize;
185 begin = end;
186 } else if (!(end % ChunkSize)) {
188 }
189}
190
195
200
205
207{
208 return dataSize;
209}
210
215
220
222{
224 chunks.clear();
225 begin = 0;
226 end = 0;
227 nDynamic = 0;
228 dataSize = 0;
229}
230
232{
233 return index && index <= staticPart().size() + nDynamic;
234}
235
237{
238 // Start from the static part first:
239 const auto &table = staticPart();
240 const HeaderField field(name, value);
242 if (staticPos != table.end()) {
243 if (staticPos->name == name && staticPos->value == value)
244 return quint32(staticPos - table.begin() + 1);
245 }
246
247 // Now we have to lookup in our dynamic part ...
248 if (!useIndex) {
249 qCritical("lookup in dynamic table requires search index enabled");
250 return 0;
251 }
252
253 const SearchEntry key(&field, nullptr, 0, this);
254 const auto pos = searchIndex.lower_bound(key);
255 if (pos != searchIndex.end()) {
256 const HeaderField &found = *pos->field;
257 if (found.name == name && found.value == value)
258 return keyToIndex(*pos);
259 }
260
261 return 0;
262}
263
265{
266 // Start from the static part first:
267 const auto &table = staticPart();
270 if (staticPos != table.end()) {
271 if (staticPos->name == name)
272 return quint32(staticPos - table.begin() + 1);
273 }
274
275 // Now we have to lookup in our dynamic part ...
276 if (!useIndex) {
277 qCritical("lookup in dynamic table requires search index enabled");
278 return 0;
279 }
280
281 const SearchEntry key(&field, nullptr, 0, this);
282 const auto pos = searchIndex.lower_bound(key);
283 if (pos != searchIndex.end()) {
284 const HeaderField &found = *pos->field;
285 if (found.name == name)
286 return keyToIndex(*pos);
287 }
288
289 return 0;
290}
291
293{
294 Q_ASSERT(name);
296
297 if (!indexIsValid(index))
298 return false;
299
300 const auto &table = staticPart();
301 if (index - 1 < table.size()) {
302 *name = table[index - 1].name;
303 *value = table[index - 1].value;
304 return true;
305 }
306
307 index = index - 1 - quint32(table.size()) + begin;
308 const auto chunkIndex = index / ChunkSize;
310 const auto offset = index % ChunkSize;
312 *name = found.name;
313 *value = found.value;
314
315 return true;
316}
317
319{
320 Q_ASSERT(dst);
321 return field(index, dst, &dummyDst);
322}
323
325{
326 Q_ASSERT(dst);
327 return field(index, &dummyDst, dst);
328}
329
330const HeaderField &FieldLookupTable::front() const
331{
332 Q_ASSERT(nDynamic && begin != end && chunks.size());
333 return (*chunks[0])[begin];
334}
335
337{
338 Q_ASSERT(nDynamic && begin != end && chunks.size());
339 return (*chunks[0])[begin];
340}
341
342const HeaderField &FieldLookupTable::back() const
343{
344 Q_ASSERT(nDynamic && end && end != begin);
345
346 const quint32 absIndex = end - 1;
350 return (*chunks[chunkIndex])[offset];
351}
352
354{
356
357 for (size_type i = 0; i < chunks.size(); ++i) {
358 if (chunks[i].get() == chunk)
359 return quint32(i);
360 }
361
363}
364
366{
368
369 const auto chunkIndex = indexOfChunk(key.chunk);
370 const auto offset = key.offset;
373
374 return quint32(offset + chunkIndex * ChunkSize - begin + 1 + staticPart().size());
375}
376
378{
379 Q_ASSERT(chunks.size() && end != begin);
380 return SearchEntry(&front(), chunks.front().get(), begin, this);
381}
382
384{
385 Q_ASSERT(chunks.size() && end != begin);
386
387 const HeaderField &field = back();
388 const quint32 absIndex = end - 1;
389 const auto offset = absIndex % ChunkSize;
390 const auto chunk = chunks[absIndex / ChunkSize].get();
391
392 return SearchEntry(&field, chunk, offset, this);
393}
394
396{
397 if (!size) {
399 tableCapacity = 0;
400 return true;
401 }
402
403 if (size > maxTableSize)
404 return false;
405
407 while (nDynamic && dataSize > tableCapacity)
408 evictEntry();
409
410 return true;
411}
412
414{
415 // This is for an external user, for example, HTTP2 protocol
416 // layer that can receive SETTINGS frame from its peer.
417 // No validity checks here, up to this external user.
418 // We update max size only, the capacity will be updated
419 // later through the Dynamic Table Size Update mechanism
420 // in HPack.
422}
423
424// This data is from the HPACK's specs and it's quite conveniently sorted,
425// except ... 'accept' is in the wrong position, see how we handle it below.
427{
428 static std::vector<HeaderField> table = {
429 {":authority", ""},
430 {":method", "GET"},
431 {":method", "POST"},
432 {":path", "/"},
433 {":path", "/index.html"},
434 {":scheme", "http"},
435 {":scheme", "https"},
436 {":status", "200"},
437 {":status", "204"},
438 {":status", "206"},
439 {":status", "304"},
440 {":status", "400"},
441 {":status", "404"},
442 {":status", "500"},
443 {"accept-charset", ""},
444 {"accept-encoding", "gzip, deflate"},
445 {"accept-language", ""},
446 {"accept-ranges", ""},
447 {"accept", ""},
448 {"access-control-allow-origin", ""},
449 {"age", ""},
450 {"allow", ""},
451 {"authorization", ""},
452 {"cache-control", ""},
453 {"content-disposition", ""},
454 {"content-encoding", ""},
455 {"content-language", ""},
456 {"content-length", ""},
457 {"content-location", ""},
458 {"content-range", ""},
459 {"content-type", ""},
460 {"cookie", ""},
461 {"date", ""},
462 {"etag", ""},
463 {"expect", ""},
464 {"expires", ""},
465 {"from", ""},
466 {"host", ""},
467 {"if-match", ""},
468 {"if-modified-since", ""},
469 {"if-none-match", ""},
470 {"if-range", ""},
471 {"if-unmodified-since", ""},
472 {"last-modified", ""},
473 {"link", ""},
474 {"location", ""},
475 {"max-forwards", ""},
476 {"proxy-authenticate", ""},
477 {"proxy-authorization", ""},
478 {"range", ""},
479 {"referer", ""},
480 {"refresh", ""},
481 {"retry-after", ""},
482 {"server", ""},
483 {"set-cookie", ""},
484 {"strict-transport-security", ""},
485 {"transfer-encoding", ""},
486 {"user-agent", ""},
487 {"vary", ""},
488 {"via", ""},
489 {"www-authenticate", ""}
490 };
491
492 return table;
493}
494
496{
497 const auto &table = staticPart();
498 const auto acceptPos = table.begin() + 18;
499 if (field.name == "accept") {
500 if (mode == CompareMode::nameAndValue && field.value != "")
501 return table.end();
502 return acceptPos;
503 }
504
505 auto predicate = [mode](const HeaderField &lhs, const HeaderField &rhs) {
506 const int cmp = compare(lhs.name, rhs.name);
507 if (cmp)
508 return cmp < 0;
509 else if (mode == CompareMode::nameAndValue)
510 return compare(lhs.value, rhs.value) < 0;
511 return false;
512 };
513
515 if (staticPos != acceptPos)
516 return staticPos;
517
518 return std::lower_bound(acceptPos + 1, table.end(), field, predicate);
519}
520
521}
522
523QT_END_NAMESPACE
HeaderSize entry_size(QByteArrayView name, QByteArrayView value)