def test_invalid_indexed_header(self): d = Decoder() # Refer to an indexed header that is too large. data = b'\xBE\x86\x84\x01\x0fwww.example.com' with pytest.raises(InvalidTableIndex): d.decode(data)
def test_invalid_indexed_literal(self): d = Decoder() # Refer to an index that is too large. data = b'\x82\x86\x84\x7f\x0a\x0fwww.example.com' with pytest.raises(InvalidTableIndex): d.decode(data)
def test_can_decode_a_story(self, story): d = Decoder() # We test against draft 9 of the HPACK spec. if story['draft'] != 9: skip("We test against draft 9, not draft %d" % story['draft']) for case in story['cases']: try: d.header_table_size = case['header_table_size'] except KeyError: pass decoded_headers = d.decode(unhexlify(case['wire'])) # The correct headers are a list of dicts, which is annoying. correct_headers = [ (item[0], item[1]) for header in case['headers'] for item in header.items() ] correct_headers = correct_headers assert correct_headers == decoded_headers assert all( isinstance(header, HeaderTuple) for header in decoded_headers )
def test_utf8_errors_raise_hpack_decoding_error(self): d = Decoder() # Invalid UTF-8 data. data = b'\x82\x86\x84\x01\x10www.\x07\xaa\xd7\x95\xd7\xa8\xd7\x94.com' with pytest.raises(HPACKDecodingError): d.decode(data)
def parse_header(self,data): d = Decoder() decoded_headers = d.decode(data) dic_header = {} for str in decoded_headers: dic_header[str[0]] = str[1] return dic_header
def test_table_size_middle_rejected(self): """ If a header table size change comes anywhere but first in the header block, it is forbidden. """ d = Decoder() data = b'\x82?a\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' with pytest.raises(HPACKDecodingError): d.decode(data)
def test_max_header_list_size(self): """ If the header block is larger than the max_header_list_size, the HPACK decoder throws an OversizedHeaderListError. """ d = Decoder(max_header_list_size=44) data = b'\x14\x0c/sample/path' with pytest.raises(OversizedHeaderListError): d.decode(data)
def test_indexed_header_field(self): """ The header field representation uses an indexed header field, from the static table. """ d = Decoder() header_set = [(':method', 'GET')] data = b'\x82' assert d.decode(data) == header_set assert list(d.header_table.dynamic_entries) == []
def test_literal_header_field_without_indexing(self): """ The header field representation uses an indexed name and a literal value. """ d = Decoder() header_set = [(':path', '/sample/path')] data = b'\x04\x0c/sample/path' assert d.decode(data) == header_set assert list(d.header_table.dynamic_entries) == []
def test_table_size_not_adjusting(self): """ If the header table size is shrunk, and then the remote peer doesn't join in the shrinking, then an error is raised. """ d = Decoder() d.max_allowed_table_size = 128 data = b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' with pytest.raises(InvalidTableSizeError): d.decode(data)
def test_header_table_size_change_above_maximum(self): """ If a header table size change is received that exceeds the maximum allowed table size, it is rejected. """ d = Decoder() d.max_allowed_table_size = 127 data = b'?a\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' with pytest.raises(InvalidTableSizeError): d.decode(data)
def save(self): encoder = Encoder() payload = encoder.encode(self) self.length = len(payload) decoder = Decoder() check = decoder.decode(payload) logger.info('payload of the header is {}'.format(check)) base = super().save() return base + payload
def test_indexed_never_indexed_emits_neverindexedheadertuple(self): """ A header field with an indexed name that must never be indexed emits a NeverIndexedHeaderTuple. """ d = Decoder() data = b'\x14\x0c/sample/path' headers = d.decode(data) assert len(headers) == 1 header = headers[0] assert isinstance(header, NeverIndexedHeaderTuple)
def test_literal_header_field_with_indexing(self): """ The header field representation uses a literal name and a literal value. """ d = Decoder() header_set = [('custom-key', 'custom-header')] data = b'\x40\x0acustom-key\x0dcustom-header' assert d.decode(data) == header_set assert list(d.header_table.dynamic_entries) == [ (n.encode('utf-8'), v.encode('utf-8')) for n, v in header_set ]
def test_literal_never_indexed_emits_neverindexedheadertuple(self): """ A literal header field that must never be indexed emits a NeverIndexedHeaderTuple. """ d = Decoder() data = b'\x10\x0acustom-key\x0dcustom-header' headers = d.decode(data) assert len(headers) == 1 header = headers[0] assert isinstance(header, NeverIndexedHeaderTuple)
def test_literal_header_field_with_indexing_emits_headertuple(self): """ A header field with indexing emits a HeaderTuple. """ d = Decoder() data = b'\x00\x0acustom-key\x0dcustom-header' headers = d.decode(data) assert len(headers) == 1 header = headers[0] assert isinstance(header, HeaderTuple) assert not isinstance(header, NeverIndexedHeaderTuple)
def test_request_examples_without_huffman(self): """ This section shows several consecutive header sets, corresponding to HTTP requests, on the same connection. """ d = Decoder() first_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com'), ] # The first_header_table doesn't contain 'authority' first_data = b'\x82\x86\x84\x01\x0fwww.example.com' assert d.decode(first_data) == first_header_set assert list(d.header_table.dynamic_entries) == [] # This request takes advantage of the differential encoding of header # sets. second_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com',), ('cache-control', 'no-cache'), ] second_data = ( b'\x82\x86\x84\x01\x0fwww.example.com\x0f\t\x08no-cache' ) assert d.decode(second_data) == second_header_set assert list(d.header_table.dynamic_entries) == [] third_header_set = [ (':method', 'GET',), (':scheme', 'https',), (':path', '/index.html',), (':authority', 'www.example.com',), ('custom-key', 'custom-value'), ] third_data = ( b'\x82\x87\x85\x01\x0fwww.example.com@\ncustom-key\x0ccustom-value' ) assert d.decode(third_data) == third_header_set # Don't check the header table here, it's just too complex to be # reliable. Check its length though. assert len(d.header_table.dynamic_entries) == 1
def test_request_examples_with_huffman(self): """ This section shows the same examples as the previous section, but using Huffman encoding for the literal values. """ d = Decoder() first_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com'), ] first_data = ( b'\x82\x86\x84\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' ) assert d.decode(first_data) == first_header_set assert list(d.header_table.dynamic_entries) == [] second_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com',), ('cache-control', 'no-cache'), ] second_data = ( b'\x82\x86\x84\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' b'\x0f\t\x86\xa8\xeb\x10d\x9c\xbf' ) assert d.decode(second_data) == second_header_set assert list(d.header_table.dynamic_entries) == [] third_header_set = [ (':method', 'GET',), (':scheme', 'https',), (':path', '/index.html',), (':authority', 'www.example.com',), ('custom-key', 'custom-value'), ] third_data = ( b'\x82\x87\x85\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff@' b'\x88%\xa8I\xe9[\xa9}\x7f\x89%\xa8I\xe9[\xb8\xe8\xb4\xbf' ) assert d.decode(third_data) == third_header_set assert len(d.header_table.dynamic_entries) == 1
def test_can_decode_multiple_header_table_size_changes(self): """ If multiple header table size changes are sent in at once, they are successfully decoded. """ d = Decoder() data = b'?a?\xe1\x1f\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' expect = [ (':method', 'GET'), (':scheme', 'https'), (':path', '/'), (':authority', '127.0.0.1:8443') ] assert d.decode(data) == expect
def test_raw_decoding(self): """ The header field representation is decoded as a raw byte string instead of UTF-8 """ d = Decoder() header_set = [ (b'\x00\x01\x99\x30\x11\x22\x55\x21\x89\x14', b'custom-header') ] data = ( b'\x40\x0a\x00\x01\x99\x30\x11\x22\x55\x21\x89\x14\x0d' b'custom-header' ) assert d.decode(data, raw=True) == header_set
def test_can_encode_a_story_with_huffman(self, raw_story): d = Decoder() e = Encoder() for case in raw_story['cases']: # The input headers are a list of dicts, which is annoying. input_headers = [ (item[0], item[1]) for header in case['headers'] for item in header.items() ] encoded = e.encode(input_headers, huffman=True) decoded_headers = d.decode(encoded) assert input_headers == decoded_headers
def fingerprint(self, data, flow_key, mode): flow_key += mode offset = 0 data_offset = None if flow_key not in self.h2_decoder: self.h2_decoder[flow_key] = Decoder() self.h2_decoder[flow_key].max_allowed_table_size = 65536 if flow_key in self.data_cache and self.data_cache[flow_key][2]: if self.data_cache[flow_key][0] + len( data) >= self.data_cache[flow_key][1]: data_offset = self.data_cache[flow_key][1] - self.data_cache[ flow_key][0] self.data_cache[flow_key] = [0, 0, False] else: self.data_cache[flow_key][0] += len(data) data_offset = None offset_ = self.check_magic(data) offset += offset_ http2 = self.parse_iterate(data, offset, flow_key) if data_offset and http2 != None: http2 = self.parse_iterate(data, data_offset, flow_key) return http2
def test_truncated_header_name(self): """ If a header name is truncated an error is raised. """ d = Decoder() # This is a simple header block that has a bad ending. The interesting # part begins on the second line. This indicates a string that has # literal name and value. The name is a 5 character huffman-encoded # string that is only three bytes long. data = ( b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' b'\x00\x85\xf2\xb2J' ) with pytest.raises(HPACKDecodingError): d.decode(data)
class ContinuationFrame(Frame): type = 0x9 allowed_flags = [Flag('END_HEADERS', 0x4)] header_decoder = Decoder() def parse_payload(self, payload): self.header_block = payload[0:len(payload)] self.headers = self.header_decoder.decode(self.header_block)
def test_headers_generator(self): e = Encoder() def headers_generator(): return (("k" + str(i), "v" + str(i)) for i in range(3)) header_set = headers_generator() out = e.encode(header_set) assert Decoder().decode(out) == list(headers_generator())
def test_truncated_header_value(self): """ If a header value is truncated an error is raised. """ d = Decoder() # This is a simple header block that has a bad ending. The interesting # part begins on the second line. This indicates a string that has # literal name and value. The name is a 5 character huffman-encoded # string, but the entire EOS character has been written over the end. # This causes hpack to see the header value as being supposed to be # 622462 bytes long, which it clearly is not, and so this must fail. data = ( b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' b'\x00\x85\xf2\xb2J\x87\xff\xff\xff\xfd%B\x7f' ) with pytest.raises(HPACKDecodingError): d.decode(data)
def test_can_encode_a_story_no_huffman(self, raw_story): d = Decoder() e = Encoder() for case in raw_story['cases']: # The input headers are a list of dicts, which is annoying. input_headers = [ (item[0], item[1]) for header in case['headers'] for item in header.items() ] encoded = e.encode(input_headers, huffman=False) decoded_headers = d.decode(encoded) assert input_headers == decoded_headers assert all( isinstance(header, HeaderTuple) for header in decoded_headers )
def defuck(data): next_f = 0 errn = 0 while len(data) > next_f + 9: if errn > 2: break try: nframe, _len = Frame.parse_frame_header(data[next_f:next_f + 9]) nframe.parse_body(memoryview(data[next_f + 9:next_f + 9 + _len])) print(nframe) for i in nframe.__dict__: if i == 'data': data = nframe.data dd = Decoder() print('frame->data->decode : ', dd.decode(data)) next_f += _len + 9 except Exception as e: print(e) errn += 1 next_f += _len + 9 continue
def test_resizing_header_table(self): # We need to decode a substantial number of headers, to populate the # header table. This string isn't magic: it's the output from the # equivalent test for the Encoder. d = Decoder() data = ( b'\x82\x87D\x87a\x07\xa4\xacV4\xcfA\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0' b'\xab\x90\xf4\xff@\x88%\xa8I\xe9[\xa9}\x7f\x89%\xa8I\xe9[\xb8\xe8' b'\xb4\xbfz\xbc\xd0\x7ff\xa2\x81\xb0\xda\xe0S\xfa\xd02\x1a\xa4\x9d' b'\x13\xfd\xa9\x92\xa4\x96\x854\x0c\x8aj\xdc\xa7\xe2\x81\x02\xef}' b'\xa9g{\x81qp\x7fjb):\x9d\x81\x00 \x00@\x150\x9a\xc2\xca\x7f,\x05' b'\xc5\xc1S\xb0I|\xa5\x89\xd3M\x1fC\xae\xba\x0cA\xa4\xc7\xa9\x8f3' b'\xa6\x9a?\xdf\x9ah\xfa\x1du\xd0b\r&=Ly\xa6\x8f\xbe\xd0\x01w\xfe' b'\xbeX\xf9\xfb\xed\x00\x17{@\x8a\xfc[=\xbdF\x81\xad\xbc\xa8O\x84y' b'\xe7\xde\x7f' ) d.decode(data) # Resize the header table to a size so small that nothing can be in it. d.header_table_size = 40 assert len(d.header_table.dynamic_entries) == 0
class Demo(object): def __init__(self): self.encoder = Encoder() self.decoder = Decoder() def run(self, headers): origin_len = 0 encoded_len = 0 print "=" * 16 for header in headers: header_tuple = header_dict_to_tuple(header) encoded = self.encoder.encode([header_tuple]) encoded_len += len(encoded) origin_len += len(header_tuple[0]) + len(header_tuple[1]) match = self.decoder.header_table.search(header_tuple[0], header_tuple[1]) print "{0}=>{1}".format(header, binascii.hexlify(encoded), translate_match(match)) print translate_match(match) curr_state = None length = 0 for b in encoded: one_byte_data = bin(struct.unpack("B", b)[0])[2:].zfill(8) curr_state, content, length = translate_byte(one_byte_data, match, curr_state, length) if content: print "{0} ({1})".format(one_byte_data, content) self.decoder.decode(encoded) print print "Decompressed from {0} to {1}".format(origin_len, encoded_len) print "=" * 16 def pretty_print_table(self, table): for (k, v) in table.dynamic_entries: print "{0}=>{1}".format(k, v) def tables(self): self.pretty_print_table(self.encoder.header_table)
def update_http2(self, packet): ''' Decodes the http2 header and updates the AppTracker with the relevant information (domain name, cookies, post data, custom headers, and uri query parameters) from the http2 packet capture. Ignores any packet that fails to decode. @params: packet is a http2 header packet. ''' try: d = Decoder() decoded_headers = d.decode( bytes.fromhex(packet.http2.headers.raw_value)) #print(decoded_headers) for (key, value) in decoded_headers: if key == ":authority": self.domain = value elif key == ":path": idx = value.find('?') if idx != -1: params = value[idx + 1:] if params not in self.uris: self.uris.append(params) elif key == "cookie": if value not in self.cookies: self.cookies.append(value) elif key.lower() not in http_headers \ and key.lower() not in http2_headers: h = key.lower() + ": " + value if h not in self.headers: self.headers.append(h) elif key == "user_agent": print(value) elif key == ":scheme" and value == "https": self.isSSL = True except Exception as e: #print(e) pass
def __init__(self): self.encoder = Encoder() self.decoder = Decoder()
from hpack import Encoder, Decoder e = Encoder() d = Decoder() with open('hpack.res', 'r') as f: print d.decode(f.read()) # [(u':status', u'200'), (u'server', u'nginx/1.14.0 (Ubuntu)'), (u'date', u'Wed, 30 Jan 2019 12:40:04 GMT'), (u'content-type', u'text/html; charset=UTF-8'), (u'set-cookie', u'F1ag:flag{Http2_Mak3_a_Differ3nce}=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0')]