1
- # -*- coding: utf-8 -*-
2
1
"""
3
2
hpack/hpack
4
3
~~~~~~~~~~~
5
4
6
5
Implements the HPACK header compression algorithm as detailed by the IETF.
7
6
"""
8
7
import logging
8
+ from typing import Any , Generator , Union
9
9
10
10
from .table import HeaderTable , table_entry_size
11
11
from .exceptions import (
16
16
REQUEST_CODES , REQUEST_CODES_LENGTH
17
17
)
18
18
from .huffman_table import decode_huffman
19
- from .struct import HeaderTuple , NeverIndexedHeaderTuple
19
+ from .struct import HeaderTuple , NeverIndexedHeaderTuple , Headers
20
20
21
21
log = logging .getLogger (__name__ )
22
22
29
29
# as prefix numbers are not zero indexed.
30
30
_PREFIX_BIT_MAX_NUMBERS = [(2 ** i ) - 1 for i in range (9 )]
31
31
32
- try : # pragma: no cover
33
- basestring = basestring
34
- except NameError : # pragma: no cover
35
- basestring = (str , bytes )
36
-
37
-
38
32
# We default the maximum header list we're willing to accept to 64kB. That's a
39
33
# lot of headers, but if applications want to raise it they can do.
40
34
DEFAULT_MAX_HEADER_LIST_SIZE = 2 ** 16
41
35
42
36
43
- def _unicode_if_needed (header , raw ) :
37
+ def _unicode_if_needed (header : HeaderTuple , raw : bool ) -> HeaderTuple :
44
38
"""
45
39
Provides a header as a unicode string if raw is False, otherwise returns
46
40
it as a bytestring.
47
41
"""
48
- name = bytes (header [0 ])
49
- value = bytes (header [1 ])
42
+ name = bytes (header [0 ]) # type: ignore
43
+ value = bytes (header [1 ]) # type: ignore
50
44
if not raw :
51
- name = name .decode ('utf-8' )
52
- value = value . decode ( 'utf-8' )
53
- return header .__class__ (name , value )
45
+ return header . __class__ ( name .decode ('utf-8' ), value . decode ( 'utf-8' ) )
46
+ else :
47
+ return header .__class__ (name , value )
54
48
55
49
56
- def encode_integer (integer , prefix_bits ) :
50
+ def encode_integer (integer : int , prefix_bits : int ) -> bytearray :
57
51
"""
58
52
This encodes an integer according to the wacky integer encoding rules
59
53
defined in the HPACK spec.
@@ -87,7 +81,7 @@ def encode_integer(integer, prefix_bits):
87
81
return bytearray (elements )
88
82
89
83
90
- def decode_integer (data , prefix_bits ) :
84
+ def decode_integer (data : bytes , prefix_bits : int ) -> tuple [ int , int ] :
91
85
"""
92
86
This decodes an integer according to the wacky integer encoding rules
93
87
defined in the HPACK spec. Returns a tuple of the decoded integer and the
@@ -128,7 +122,7 @@ def decode_integer(data, prefix_bits):
128
122
return number , index
129
123
130
124
131
- def _dict_to_iterable (header_dict ) :
125
+ def _dict_to_iterable (header_dict : Union [ dict [ bytes , bytes ], dict [ str , str ]]) -> Generator [ Union [ tuple [ bytes , bytes ], tuple [ str , str ]], None , None ] :
132
126
"""
133
127
This converts a dictionary to an iterable of two-tuples. This is a
134
128
HPACK-specific function because it pulls "special-headers" out first and
@@ -140,19 +134,19 @@ def _dict_to_iterable(header_dict):
140
134
key = lambda k : not _to_bytes (k ).startswith (b':' )
141
135
)
142
136
for key in keys :
143
- yield key , header_dict [key ]
137
+ yield key , header_dict [key ] # type: ignore
144
138
145
139
146
- def _to_bytes (value ) :
140
+ def _to_bytes (value : Union [ bytes , str , Any ]) -> bytes :
147
141
"""
148
142
Convert anything to bytes through a UTF-8 encoded string
149
143
"""
150
144
t = type (value )
151
145
if t is bytes :
152
- return value
146
+ return value # type: ignore
153
147
if t is not str :
154
148
value = str (value )
155
- return value .encode ("utf-8" )
149
+ return value .encode ("utf-8" ) # type: ignore
156
150
157
151
158
152
class Encoder :
@@ -161,27 +155,29 @@ class Encoder:
161
155
HTTP/2 header blocks.
162
156
"""
163
157
164
- def __init__ (self ):
158
+ def __init__ (self ) -> None :
165
159
self .header_table = HeaderTable ()
166
160
self .huffman_coder = HuffmanEncoder (
167
161
REQUEST_CODES , REQUEST_CODES_LENGTH
168
162
)
169
- self .table_size_changes = []
163
+ self .table_size_changes : list [ int ] = []
170
164
171
165
@property
172
- def header_table_size (self ):
166
+ def header_table_size (self ) -> int :
173
167
"""
174
168
Controls the size of the HPACK header table.
175
169
"""
176
170
return self .header_table .maxsize
177
171
178
172
@header_table_size .setter
179
- def header_table_size (self , value ) :
173
+ def header_table_size (self , value : int ) -> None :
180
174
self .header_table .maxsize = value
181
175
if self .header_table .resized :
182
176
self .table_size_changes .append (value )
183
177
184
- def encode (self , headers , huffman = True ):
178
+ def encode (self ,
179
+ headers : Headers ,
180
+ huffman : bool = True ) -> bytes :
185
181
"""
186
182
Takes a set of headers and encodes them into a HPACK-encoded header
187
183
block.
@@ -256,13 +252,13 @@ def encode(self, headers, huffman=True):
256
252
header = (_to_bytes (header [0 ]), _to_bytes (header [1 ]))
257
253
header_block .append (self .add (header , sensitive , huffman ))
258
254
259
- header_block = b'' .join (header_block )
255
+ encoded = b'' .join (header_block )
260
256
261
- log .debug ("Encoded header block to %s" , header_block )
257
+ log .debug ("Encoded header block to %s" , encoded )
262
258
263
- return header_block
259
+ return encoded
264
260
265
- def add (self , to_add , sensitive , huffman = False ):
261
+ def add (self , to_add : tuple [ bytes , bytes ], sensitive : bool , huffman : bool = False ) -> bytes :
266
262
"""
267
263
This function takes a header key-value tuple and serializes it.
268
264
"""
@@ -311,15 +307,15 @@ def add(self, to_add, sensitive, huffman=False):
311
307
312
308
return encoded
313
309
314
- def _encode_indexed (self , index ) :
310
+ def _encode_indexed (self , index : int ) -> bytes :
315
311
"""
316
312
Encodes a header using the indexed representation.
317
313
"""
318
314
field = encode_integer (index , 7 )
319
315
field [0 ] |= 0x80 # we set the top bit
320
316
return bytes (field )
321
317
322
- def _encode_literal (self , name , value , indexbit , huffman = False ):
318
+ def _encode_literal (self , name : bytes , value : bytes , indexbit : bytes , huffman : bool = False ) -> bytes :
323
319
"""
324
320
Encodes a header with a literal name and literal value. If ``indexing``
325
321
is True, the header will be added to the header table: otherwise it
@@ -340,7 +336,7 @@ def _encode_literal(self, name, value, indexbit, huffman=False):
340
336
[indexbit , bytes (name_len ), name , bytes (value_len ), value ]
341
337
)
342
338
343
- def _encode_indexed_literal (self , index , value , indexbit , huffman = False ):
339
+ def _encode_indexed_literal (self , index : int , value : bytes , indexbit : bytes , huffman : bool = False ) -> bytes :
344
340
"""
345
341
Encodes a header with an indexed name and a literal value and performs
346
342
incremental indexing.
@@ -362,16 +358,16 @@ def _encode_indexed_literal(self, index, value, indexbit, huffman=False):
362
358
363
359
return b'' .join ([bytes (prefix ), bytes (value_len ), value ])
364
360
365
- def _encode_table_size_change (self ):
361
+ def _encode_table_size_change (self ) -> bytes :
366
362
"""
367
363
Produces the encoded form of all header table size change context
368
364
updates.
369
365
"""
370
366
block = b''
371
367
for size_bytes in self .table_size_changes :
372
- size_bytes = encode_integer (size_bytes , 5 )
373
- size_bytes [0 ] |= 0x20
374
- block += bytes (size_bytes )
368
+ b = encode_integer (size_bytes , 5 )
369
+ b [0 ] |= 0x20
370
+ block += bytes (b )
375
371
self .table_size_changes = []
376
372
return block
377
373
@@ -397,7 +393,7 @@ class Decoder:
397
393
Defaults to 64kB.
398
394
:type max_header_list_size: ``int``
399
395
"""
400
- def __init__ (self , max_header_list_size = DEFAULT_MAX_HEADER_LIST_SIZE ):
396
+ def __init__ (self , max_header_list_size : int = DEFAULT_MAX_HEADER_LIST_SIZE ) -> None :
401
397
self .header_table = HeaderTable ()
402
398
403
399
#: The maximum decompressed size we will allow for any single header
@@ -426,17 +422,17 @@ def __init__(self, max_header_list_size=DEFAULT_MAX_HEADER_LIST_SIZE):
426
422
self .max_allowed_table_size = self .header_table .maxsize
427
423
428
424
@property
429
- def header_table_size (self ):
425
+ def header_table_size (self ) -> int :
430
426
"""
431
427
Controls the size of the HPACK header table.
432
428
"""
433
429
return self .header_table .maxsize
434
430
435
431
@header_table_size .setter
436
- def header_table_size (self , value ) :
432
+ def header_table_size (self , value : int ) -> None :
437
433
self .header_table .maxsize = value
438
434
439
- def decode (self , data , raw = False ):
435
+ def decode (self , data : bytes , raw : bool = False ) -> Headers :
440
436
"""
441
437
Takes an HPACK-encoded header block and decodes it into a header set.
442
438
@@ -454,7 +450,7 @@ def decode(self, data, raw=False):
454
450
log .debug ("Decoding %s" , data )
455
451
456
452
data_mem = memoryview (data )
457
- headers = []
453
+ headers : list [ HeaderTuple ] = []
458
454
data_len = len (data )
459
455
inflated_size = 0
460
456
current_index = 0
@@ -501,7 +497,7 @@ def decode(self, data, raw=False):
501
497
502
498
if header :
503
499
headers .append (header )
504
- inflated_size += table_entry_size (* header )
500
+ inflated_size += table_entry_size (header [ 0 ], header [ 1 ] )
505
501
506
502
if inflated_size > self .max_header_list_size :
507
503
raise OversizedHeaderListError (
@@ -521,7 +517,7 @@ def decode(self, data, raw=False):
521
517
except UnicodeDecodeError :
522
518
raise HPACKDecodingError ("Unable to decode headers as UTF-8." )
523
519
524
- def _assert_valid_table_size (self ):
520
+ def _assert_valid_table_size (self ) -> None :
525
521
"""
526
522
Check that the table size set by the encoder is lower than the maximum
527
523
we expect to have.
@@ -531,7 +527,7 @@ def _assert_valid_table_size(self):
531
527
"Encoder did not shrink table size to within the max"
532
528
)
533
529
534
- def _update_encoding_context (self , data ) :
530
+ def _update_encoding_context (self , data : bytes ) -> int :
535
531
"""
536
532
Handles a byte that updates the encoding context.
537
533
"""
@@ -544,7 +540,7 @@ def _update_encoding_context(self, data):
544
540
self .header_table_size = new_size
545
541
return consumed
546
542
547
- def _decode_indexed (self , data ) :
543
+ def _decode_indexed (self , data : bytes ) -> tuple [ HeaderTuple , int ] :
548
544
"""
549
545
Decodes a header represented using the indexed representation.
550
546
"""
@@ -553,13 +549,13 @@ def _decode_indexed(self, data):
553
549
log .debug ("Decoded %s, consumed %d" , header , consumed )
554
550
return header , consumed
555
551
556
- def _decode_literal_no_index (self , data ) :
552
+ def _decode_literal_no_index (self , data : bytes ) -> tuple [ HeaderTuple , int ] :
557
553
return self ._decode_literal (data , False )
558
554
559
- def _decode_literal_index (self , data ) :
555
+ def _decode_literal_index (self , data : bytes ) -> tuple [ HeaderTuple , int ] :
560
556
return self ._decode_literal (data , True )
561
557
562
- def _decode_literal (self , data , should_index ) :
558
+ def _decode_literal (self , data : bytes , should_index : bool ) -> tuple [ HeaderTuple , int ] :
563
559
"""
564
560
Decodes a header represented with a literal.
565
561
"""
@@ -577,7 +573,7 @@ def _decode_literal(self, data, should_index):
577
573
high_byte = data [0 ]
578
574
indexed_name = high_byte & 0x0F
579
575
name_len = 4
580
- not_indexable = high_byte & 0x10
576
+ not_indexable = bool ( high_byte & 0x10 )
581
577
582
578
if indexed_name :
583
579
# Indexed header name.
@@ -616,6 +612,7 @@ def _decode_literal(self, data, should_index):
616
612
617
613
# If we have been told never to index the header field, encode that in
618
614
# the tuple we use.
615
+ header : HeaderTuple
619
616
if not_indexable :
620
617
header = NeverIndexedHeaderTuple (name , value )
621
618
else :
0 commit comments