1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "bitstreams_p.h"
41#include "hpack_p.h"
42
43#include <QtCore/qbytearray.h>
44#include <QtCore/qdebug.h>
45
46#include <limits>
47
48QT_BEGIN_NAMESPACE
49
50namespace HPack
51{
52
53HeaderSize header_size(const HttpHeader &header)
54{
55 HeaderSize size(true, 0);
56 for (const HeaderField &field : header) {
57 HeaderSize delta = entry_size(entry: field);
58 if (!delta.first)
59 return HeaderSize();
60 if (std::numeric_limits<quint32>::max() - size.second < delta.second)
61 return HeaderSize();
62 size.second += delta.second;
63 }
64
65 return size;
66}
67
68struct BitPattern
69{
70 uchar value;
71 uchar bitLength;
72};
73
74bool operator == (const BitPattern &lhs, const BitPattern &rhs)
75{
76 return lhs.bitLength == rhs.bitLength && lhs.value == rhs.value;
77}
78
79namespace
80{
81
82using StreamError = BitIStream::Error;
83
84// There are several bit patterns to distinguish header fields:
85// 1 - indexed
86// 01 - literal with incremented indexing
87// 0000 - literal without indexing
88// 0001 - literal, never indexing
89// 001 - dynamic table size update.
90
91// It's always 1 or 0 actually, but the number of bits to extract
92// from the input stream - differs.
93const BitPattern Indexed = {.value: 1, .bitLength: 1};
94const BitPattern LiteralIncrementalIndexing = {.value: 1, .bitLength: 2};
95const BitPattern LiteralNoIndexing = {.value: 0, .bitLength: 4};
96const BitPattern LiteralNeverIndexing = {.value: 1, .bitLength: 4};
97const BitPattern SizeUpdate = {.value: 1, .bitLength: 3};
98
99bool is_literal_field(const BitPattern &pattern)
100{
101 return pattern == LiteralIncrementalIndexing
102 || pattern == LiteralNoIndexing
103 || pattern == LiteralNeverIndexing;
104}
105
106void write_bit_pattern(const BitPattern &pattern, BitOStream &outputStream)
107{
108 outputStream.writeBits(bits: pattern.value, bitLength: pattern.bitLength);
109}
110
111bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream)
112{
113 uchar chunk = 0;
114
115 const quint32 bitsRead = inputStream.peekBits(from: inputStream.streamOffset(),
116 length: pattern.bitLength, dstPtr: &chunk);
117 if (bitsRead != pattern.bitLength)
118 return false;
119
120 // Since peekBits packs in the most significant bits, shift it!
121 chunk >>= (8 - bitsRead);
122 if (chunk != pattern.value)
123 return false;
124
125 inputStream.skipBits(nBits: pattern.bitLength);
126
127 return true;
128}
129
130bool is_request_pseudo_header(const QByteArray &name)
131{
132 return name == ":method" || name == ":scheme" ||
133 name == ":authority" || name == ":path";
134}
135
136} // unnamed namespace
137
138Encoder::Encoder(quint32 size, bool compress)
139 : lookupTable(size, true /*encoder needs search index*/),
140 compressStrings(compress)
141{
142}
143
144quint32 Encoder::dynamicTableSize() const
145{
146 return lookupTable.dynamicDataSize();
147}
148
149bool Encoder::encodeRequest(BitOStream &outputStream, const HttpHeader &header)
150{
151 if (!header.size()) {
152 qDebug(msg: "empty header");
153 return false;
154 }
155
156 if (!encodeRequestPseudoHeaders(outputStream, header))
157 return false;
158
159 for (const auto &field : header) {
160 if (is_request_pseudo_header(name: field.name))
161 continue;
162
163 if (!encodeHeaderField(outputStream, field))
164 return false;
165 }
166
167 return true;
168}
169
170bool Encoder::encodeResponse(BitOStream &outputStream, const HttpHeader &header)
171{
172 if (!header.size()) {
173 qDebug(msg: "empty header");
174 return false;
175 }
176
177 if (!encodeResponsePseudoHeaders(outputStream, header))
178 return false;
179
180 for (const auto &field : header) {
181 if (field.name == ":status")
182 continue;
183
184 if (!encodeHeaderField(outputStream, field))
185 return false;
186 }
187
188 return true;
189}
190
191bool Encoder::encodeSizeUpdate(BitOStream &outputStream, quint32 newSize)
192{
193 if (!lookupTable.updateDynamicTableSize(size: newSize)) {
194 qDebug(msg: "failed to update own table size");
195 return false;
196 }
197
198 write_bit_pattern(pattern: SizeUpdate, outputStream);
199 outputStream.write(src: newSize);
200
201 return true;
202}
203
204void Encoder::setMaxDynamicTableSize(quint32 size)
205{
206 // Up to a caller (HTTP2 protocol handler)
207 // to validate this size first.
208 lookupTable.setMaxDynamicTableSize(size);
209}
210
211void Encoder::setCompressStrings(bool compress)
212{
213 compressStrings = compress;
214}
215
216bool Encoder::encodeRequestPseudoHeaders(BitOStream &outputStream,
217 const HttpHeader &header)
218{
219 // The following pseudo-header fields are defined for HTTP/2 requests:
220 // - The :method pseudo-header field includes the HTTP method
221 // - The :scheme pseudo-header field includes the scheme portion of the target URI
222 // - The :authority pseudo-header field includes the authority portion of the target URI
223 // - The :path pseudo-header field includes the path and query parts of the target URI
224
225 // All HTTP/2 requests MUST include exactly one valid value for the :method,
226 // :scheme, and :path pseudo-header fields, unless it is a CONNECT request
227 // (Section 8.3). An HTTP request that omits mandatory pseudo-header fields
228 // is malformed (Section 8.1.2.6).
229
230 using size_type = decltype(header.size());
231
232 bool methodFound = false;
233 const char *headerName[] = {":authority", ":scheme", ":path"};
234 const size_type nHeaders = sizeof headerName / sizeof headerName[0];
235 bool headerFound[nHeaders] = {};
236
237 for (const auto &field : header) {
238 if (field.name == ":status") {
239 qCritical(msg: "invalid pseudo-header (:status) in a request");
240 return false;
241 }
242
243 if (field.name == ":method") {
244 if (methodFound) {
245 qCritical(msg: "only one :method pseudo-header is allowed");
246 return false;
247 }
248
249 if (!encodeMethod(outputStream, field))
250 return false;
251 methodFound = true;
252 } else if (field.name == "cookie") {
253 // "crumbs" ...
254 } else {
255 for (size_type j = 0; j < nHeaders; ++j) {
256 if (field.name == headerName[j]) {
257 if (headerFound[j]) {
258 qCritical() << "only one" << headerName[j] << "pseudo-header is allowed";
259 return false;
260 }
261 if (!encodeHeaderField(outputStream, field))
262 return false;
263 headerFound[j] = true;
264 break;
265 }
266 }
267 }
268 }
269
270 if (!methodFound) {
271 qCritical(msg: "mandatory :method pseudo-header not found");
272 return false;
273 }
274
275 // 1: don't demand headerFound[0], as :authority isn't mandatory.
276 for (size_type i = 1; i < nHeaders; ++i) {
277 if (!headerFound[i]) {
278 qCritical() << "mandatory" << headerName[i]
279 << "pseudo-header not found";
280 return false;
281 }
282 }
283
284 return true;
285}
286
287bool Encoder::encodeHeaderField(BitOStream &outputStream, const HeaderField &field)
288{
289 // TODO: at the moment we never use LiteralNo/Never Indexing ...
290
291 // Here we try:
292 // 1. indexed
293 // 2. literal indexed with indexed name/literal value
294 // 3. literal indexed with literal name/literal value
295 if (const auto index = lookupTable.indexOf(name: field.name, value: field.value))
296 return encodeIndexedField(outputStream, index);
297
298 if (const auto index = lookupTable.indexOf(name: field.name)) {
299 return encodeLiteralField(outputStream, fieldType: LiteralIncrementalIndexing,
300 nameIndex: index, value: field.value, withCompression: compressStrings);
301 }
302
303 return encodeLiteralField(outputStream, fieldType: LiteralIncrementalIndexing,
304 name: field.name, value: field.value, withCompression: compressStrings);
305}
306
307bool Encoder::encodeMethod(BitOStream &outputStream, const HeaderField &field)
308{
309 Q_ASSERT(field.name == ":method");
310 quint32 index = lookupTable.indexOf(name: field.name, value: field.value);
311 if (index)
312 return encodeIndexedField(outputStream, index);
313
314 index = lookupTable.indexOf(name: field.name);
315 Q_ASSERT(index); // ":method" is always in the static table ...
316 return encodeLiteralField(outputStream, fieldType: LiteralIncrementalIndexing,
317 nameIndex: index, value: field.value, withCompression: compressStrings);
318}
319
320bool Encoder::encodeResponsePseudoHeaders(BitOStream &outputStream, const HttpHeader &header)
321{
322 bool statusFound = false;
323 for (const auto &field : header) {
324 if (is_request_pseudo_header(name: field.name)) {
325 qCritical() << "invalid pseudo-header" << field.name << "in http response";
326 return false;
327 }
328
329 if (field.name == ":status") {
330 if (statusFound) {
331 qDebug(msg: "only one :status pseudo-header is allowed");
332 return false;
333 }
334 if (!encodeHeaderField(outputStream, field))
335 return false;
336 statusFound = true;
337 } else if (field.name == "cookie") {
338 // "crumbs"..
339 }
340 }
341
342 if (!statusFound)
343 qCritical(msg: "mandatory :status pseudo-header not found");
344
345 return statusFound;
346}
347
348bool Encoder::encodeIndexedField(BitOStream &outputStream, quint32 index) const
349{
350 Q_ASSERT(lookupTable.indexIsValid(index));
351
352 write_bit_pattern(pattern: Indexed, outputStream);
353 outputStream.write(src: index);
354
355 return true;
356}
357
358bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType,
359 const QByteArray &name, const QByteArray &value,
360 bool withCompression)
361{
362 Q_ASSERT(is_literal_field(fieldType));
363 // According to HPACK, the bit pattern is
364 // 01 | 000000 (integer 0 that fits into 6-bit prefix),
365 // since integers always end on byte boundary,
366 // this also implies that we always start at bit offset == 0.
367 if (outputStream.bitLength() % 8) {
368 qCritical(msg: "invalid bit offset");
369 return false;
370 }
371
372 if (fieldType == LiteralIncrementalIndexing) {
373 if (!lookupTable.prependField(name, value))
374 qDebug(msg: "failed to prepend a new field");
375 }
376
377 write_bit_pattern(pattern: fieldType, outputStream);
378
379 outputStream.write(src: 0);
380 outputStream.write(src: name, compressed: withCompression);
381 outputStream.write(src: value, compressed: withCompression);
382
383 return true;
384}
385
386bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType,
387 quint32 nameIndex, const QByteArray &value,
388 bool withCompression)
389{
390 Q_ASSERT(is_literal_field(fieldType));
391
392 QByteArray name;
393 const bool found = lookupTable.fieldName(index: nameIndex, dst: &name);
394 Q_UNUSED(found) Q_ASSERT(found);
395
396 if (fieldType == LiteralIncrementalIndexing) {
397 if (!lookupTable.prependField(name, value))
398 qDebug(msg: "failed to prepend a new field");
399 }
400
401 write_bit_pattern(pattern: fieldType, outputStream);
402 outputStream.write(src: nameIndex);
403 outputStream.write(src: value, compressed: withCompression);
404
405 return true;
406}
407
408Decoder::Decoder(quint32 size)
409 : lookupTable{size, false /* we do not need search index ... */}
410{
411}
412
413bool Decoder::decodeHeaderFields(BitIStream &inputStream)
414{
415 header.clear();
416 while (true) {
417 if (read_bit_pattern(pattern: Indexed, inputStream)) {
418 if (!decodeIndexedField(inputStream))
419 return false;
420 } else if (read_bit_pattern(pattern: LiteralIncrementalIndexing, inputStream)) {
421 if (!decodeLiteralField(fieldType: LiteralIncrementalIndexing, inputStream))
422 return false;
423 } else if (read_bit_pattern(pattern: LiteralNoIndexing, inputStream)) {
424 if (!decodeLiteralField(fieldType: LiteralNoIndexing, inputStream))
425 return false;
426 } else if (read_bit_pattern(pattern: LiteralNeverIndexing, inputStream)) {
427 if (!decodeLiteralField(fieldType: LiteralNeverIndexing, inputStream))
428 return false;
429 } else if (read_bit_pattern(pattern: SizeUpdate, inputStream)) {
430 if (!decodeSizeUpdate(inputStream))
431 return false;
432 } else {
433 return inputStream.bitLength() == inputStream.streamOffset();
434 }
435 }
436
437 return false;
438}
439
440quint32 Decoder::dynamicTableSize() const
441{
442 return lookupTable.dynamicDataSize();
443}
444
445void Decoder::setMaxDynamicTableSize(quint32 size)
446{
447 // Up to a caller (HTTP2 protocol handler)
448 // to validate this size first.
449 lookupTable.setMaxDynamicTableSize(size);
450}
451
452bool Decoder::decodeIndexedField(BitIStream &inputStream)
453{
454 quint32 index = 0;
455 if (inputStream.read(dstPtr: &index)) {
456 if (!index) {
457 // "The index value of 0 is not used.
458 // It MUST be treated as a decoding
459 // error if found in an indexed header
460 // field representation."
461 return false;
462 }
463
464 QByteArray name, value;
465 if (lookupTable.field(index, name: &name, value: &value))
466 return processDecodedField(fieldType: Indexed, name, value);
467 } else {
468 handleStreamError(inputStream);
469 }
470
471 return false;
472}
473
474bool Decoder::decodeSizeUpdate(BitIStream &inputStream)
475{
476 // For now, just read and skip bits.
477 quint32 maxSize = 0;
478 if (inputStream.read(dstPtr: &maxSize)) {
479 if (!lookupTable.updateDynamicTableSize(size: maxSize))
480 return false;
481
482 return true;
483 }
484
485 handleStreamError(inputStream);
486 return false;
487}
488
489bool Decoder::decodeLiteralField(const BitPattern &fieldType, BitIStream &inputStream)
490{
491 // https://http2.github.io/http2-spec/compression.html
492 // 6.2.1, 6.2.2, 6.2.3
493 // Format for all 'literal' is similar,
494 // the difference - is how we update/not our lookup table.
495 quint32 index = 0;
496 if (inputStream.read(dstPtr: &index)) {
497 QByteArray name;
498 if (!index) {
499 // Read a string.
500 if (!inputStream.read(dstPtr: &name)) {
501 handleStreamError(inputStream);
502 return false;
503 }
504 } else {
505 if (!lookupTable.fieldName(index, dst: &name))
506 return false;
507 }
508
509 QByteArray value;
510 if (inputStream.read(dstPtr: &value))
511 return processDecodedField(fieldType, name, value);
512 }
513
514 handleStreamError(inputStream);
515
516 return false;
517}
518
519bool Decoder::processDecodedField(const BitPattern &fieldType,
520 const QByteArray &name,
521 const QByteArray &value)
522{
523 if (fieldType == LiteralIncrementalIndexing) {
524 if (!lookupTable.prependField(name, value))
525 return false;
526 }
527
528 header.push_back(x: HeaderField(name, value));
529 return true;
530}
531
532void Decoder::handleStreamError(BitIStream &inputStream)
533{
534 const auto errorCode(inputStream.error());
535 if (errorCode == StreamError::NoError)
536 return;
537
538 // For now error handling not needed here,
539 // HTTP2 layer will end with session error/COMPRESSION_ERROR.
540}
541
542}
543
544QT_END_NAMESPACE
545

source code of qtbase/src/network/access/http2/hpack.cpp