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
48 QT_BEGIN_NAMESPACE
49
50 namespace HPack
51 {
52
header_size(const HttpHeader & header)53 HeaderSize header_size(const HttpHeader &header)
54 {
55 HeaderSize size(true, 0);
56 for (const HeaderField &field : header) {
57 HeaderSize delta = entry_size(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
68 struct BitPattern
69 {
70 uchar value;
71 uchar bitLength;
72 };
73
operator ==(const BitPattern & lhs,const BitPattern & rhs)74 bool operator == (const BitPattern &lhs, const BitPattern &rhs)
75 {
76 return lhs.bitLength == rhs.bitLength && lhs.value == rhs.value;
77 }
78
79 namespace
80 {
81
82 using 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.
93 const BitPattern Indexed = {1, 1};
94 const BitPattern LiteralIncrementalIndexing = {1, 2};
95 const BitPattern LiteralNoIndexing = {0, 4};
96 const BitPattern LiteralNeverIndexing = {1, 4};
97 const BitPattern SizeUpdate = {1, 3};
98
is_literal_field(const BitPattern & pattern)99 bool is_literal_field(const BitPattern &pattern)
100 {
101 return pattern == LiteralIncrementalIndexing
102 || pattern == LiteralNoIndexing
103 || pattern == LiteralNeverIndexing;
104 }
105
write_bit_pattern(const BitPattern & pattern,BitOStream & outputStream)106 void write_bit_pattern(const BitPattern &pattern, BitOStream &outputStream)
107 {
108 outputStream.writeBits(pattern.value, pattern.bitLength);
109 }
110
read_bit_pattern(const BitPattern & pattern,BitIStream & inputStream)111 bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream)
112 {
113 uchar chunk = 0;
114
115 const quint32 bitsRead = inputStream.peekBits(inputStream.streamOffset(),
116 pattern.bitLength, &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(pattern.bitLength);
126
127 return true;
128 }
129
is_request_pseudo_header(const QByteArray & name)130 bool 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
Encoder(quint32 size,bool compress)138 Encoder::Encoder(quint32 size, bool compress)
139 : lookupTable(size, true /*encoder needs search index*/),
140 compressStrings(compress)
141 {
142 }
143
dynamicTableSize() const144 quint32 Encoder::dynamicTableSize() const
145 {
146 return lookupTable.dynamicDataSize();
147 }
148
encodeRequest(BitOStream & outputStream,const HttpHeader & header)149 bool Encoder::encodeRequest(BitOStream &outputStream, const HttpHeader &header)
150 {
151 if (!header.size()) {
152 qDebug("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(field.name))
161 continue;
162
163 if (!encodeHeaderField(outputStream, field))
164 return false;
165 }
166
167 return true;
168 }
169
encodeResponse(BitOStream & outputStream,const HttpHeader & header)170 bool Encoder::encodeResponse(BitOStream &outputStream, const HttpHeader &header)
171 {
172 if (!header.size()) {
173 qDebug("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
encodeSizeUpdate(BitOStream & outputStream,quint32 newSize)191 bool Encoder::encodeSizeUpdate(BitOStream &outputStream, quint32 newSize)
192 {
193 if (!lookupTable.updateDynamicTableSize(newSize)) {
194 qDebug("failed to update own table size");
195 return false;
196 }
197
198 write_bit_pattern(SizeUpdate, outputStream);
199 outputStream.write(newSize);
200
201 return true;
202 }
203
setMaxDynamicTableSize(quint32 size)204 void 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
setCompressStrings(bool compress)211 void Encoder::setCompressStrings(bool compress)
212 {
213 compressStrings = compress;
214 }
215
encodeRequestPseudoHeaders(BitOStream & outputStream,const HttpHeader & header)216 bool 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("invalid pseudo-header (:status) in a request");
240 return false;
241 }
242
243 if (field.name == ":method") {
244 if (methodFound) {
245 qCritical("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("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
encodeHeaderField(BitOStream & outputStream,const HeaderField & field)287 bool 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(field.name, field.value))
296 return encodeIndexedField(outputStream, index);
297
298 if (const auto index = lookupTable.indexOf(field.name)) {
299 return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
300 index, field.value, compressStrings);
301 }
302
303 return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
304 field.name, field.value, compressStrings);
305 }
306
encodeMethod(BitOStream & outputStream,const HeaderField & field)307 bool Encoder::encodeMethod(BitOStream &outputStream, const HeaderField &field)
308 {
309 Q_ASSERT(field.name == ":method");
310 quint32 index = lookupTable.indexOf(field.name, field.value);
311 if (index)
312 return encodeIndexedField(outputStream, index);
313
314 index = lookupTable.indexOf(field.name);
315 Q_ASSERT(index); // ":method" is always in the static table ...
316 return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
317 index, field.value, compressStrings);
318 }
319
encodeResponsePseudoHeaders(BitOStream & outputStream,const HttpHeader & header)320 bool Encoder::encodeResponsePseudoHeaders(BitOStream &outputStream, const HttpHeader &header)
321 {
322 bool statusFound = false;
323 for (const auto &field : header) {
324 if (is_request_pseudo_header(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("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("mandatory :status pseudo-header not found");
344
345 return statusFound;
346 }
347
encodeIndexedField(BitOStream & outputStream,quint32 index) const348 bool Encoder::encodeIndexedField(BitOStream &outputStream, quint32 index) const
349 {
350 Q_ASSERT(lookupTable.indexIsValid(index));
351
352 write_bit_pattern(Indexed, outputStream);
353 outputStream.write(index);
354
355 return true;
356 }
357
encodeLiteralField(BitOStream & outputStream,const BitPattern & fieldType,const QByteArray & name,const QByteArray & value,bool withCompression)358 bool 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("invalid bit offset");
369 return false;
370 }
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
379 outputStream.write(0);
380 outputStream.write(name, withCompression);
381 outputStream.write(value, withCompression);
382
383 return true;
384 }
385
encodeLiteralField(BitOStream & outputStream,const BitPattern & fieldType,quint32 nameIndex,const QByteArray & value,bool withCompression)386 bool 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(nameIndex, &name);
394 Q_UNUSED(found) Q_ASSERT(found);
395
396 if (fieldType == LiteralIncrementalIndexing) {
397 if (!lookupTable.prependField(name, value))
398 qDebug("failed to prepend a new field");
399 }
400
401 write_bit_pattern(fieldType, outputStream);
402 outputStream.write(nameIndex);
403 outputStream.write(value, withCompression);
404
405 return true;
406 }
407
Decoder(quint32 size)408 Decoder::Decoder(quint32 size)
409 : lookupTable{size, false /* we do not need search index ... */}
410 {
411 }
412
decodeHeaderFields(BitIStream & inputStream)413 bool Decoder::decodeHeaderFields(BitIStream &inputStream)
414 {
415 header.clear();
416 while (true) {
417 if (read_bit_pattern(Indexed, inputStream)) {
418 if (!decodeIndexedField(inputStream))
419 return false;
420 } else if (read_bit_pattern(LiteralIncrementalIndexing, inputStream)) {
421 if (!decodeLiteralField(LiteralIncrementalIndexing, inputStream))
422 return false;
423 } else if (read_bit_pattern(LiteralNoIndexing, inputStream)) {
424 if (!decodeLiteralField(LiteralNoIndexing, inputStream))
425 return false;
426 } else if (read_bit_pattern(LiteralNeverIndexing, inputStream)) {
427 if (!decodeLiteralField(LiteralNeverIndexing, inputStream))
428 return false;
429 } else if (read_bit_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
dynamicTableSize() const440 quint32 Decoder::dynamicTableSize() const
441 {
442 return lookupTable.dynamicDataSize();
443 }
444
setMaxDynamicTableSize(quint32 size)445 void 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
decodeIndexedField(BitIStream & inputStream)452 bool Decoder::decodeIndexedField(BitIStream &inputStream)
453 {
454 quint32 index = 0;
455 if (inputStream.read(&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, &value))
466 return processDecodedField(Indexed, name, value);
467 } else {
468 handleStreamError(inputStream);
469 }
470
471 return false;
472 }
473
decodeSizeUpdate(BitIStream & inputStream)474 bool Decoder::decodeSizeUpdate(BitIStream &inputStream)
475 {
476 // For now, just read and skip bits.
477 quint32 maxSize = 0;
478 if (inputStream.read(&maxSize)) {
479 if (!lookupTable.updateDynamicTableSize(maxSize))
480 return false;
481
482 return true;
483 }
484
485 handleStreamError(inputStream);
486 return false;
487 }
488
decodeLiteralField(const BitPattern & fieldType,BitIStream & inputStream)489 bool 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(&index)) {
497 QByteArray name;
498 if (!index) {
499 // Read a string.
500 if (!inputStream.read(&name)) {
501 handleStreamError(inputStream);
502 return false;
503 }
504 } else {
505 if (!lookupTable.fieldName(index, &name))
506 return false;
507 }
508
509 QByteArray value;
510 if (inputStream.read(&value))
511 return processDecodedField(fieldType, name, value);
512 }
513
514 handleStreamError(inputStream);
515
516 return false;
517 }
518
processDecodedField(const BitPattern & fieldType,const QByteArray & name,const QByteArray & value)519 bool 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(HeaderField(name, value));
529 return true;
530 }
531
handleStreamError(BitIStream & inputStream)532 void 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
544 QT_END_NAMESPACE
545