std/hpack
import std/hpack to use it. Source: src/std/hpack.iyi at v0.18.0.
Implements:
- RFC 7541 61-entry Static Table (Appendix A)
- Dynamic Table with FIFO eviction by size (Section 4)
- N-bit prefix integer encoding and decoding for 1..8 bits (Section 5.1)
- String literals, both raw and canonical Huffman-coded (Section 5.2)
- Canonical Huffman table (Appendix B, 257 symbols)
- Prefix-tree / state-machine Huffman decoder with strict padding validation:
* Rejects EOS symbol in stream
* Rejects padding longer than 7 bits
* Rejects padding containing 0 bits (must be all 1s)- Four field representations (Section 6):
* Indexed Header Field (6.1)
* Literal Header Field with Incremental Indexing (6.2.1)
* Literal Header Field without Indexing (6.2.2)
* Literal Header Field Never Indexed (6.2.3)- Dynamic Table Size Update (Section 6.3), including RFC 4.2:
* encoder emits the interval minimum then the final size
* decoder refuses a size update after a header field- Security defenses:
* Decompression bomb protection: bounds the decoded header list size (RFC 7540 6.5.2: name, value and 32 octets a field) & count
* Respects peer SETTINGS_HEADER_TABLE_SIZE limit
* Invalid index detection (index 0 or index > static + dynamic length)
* Integer encodings that overflow UInt64 or Int32 indexes are HpackError
* Never-indexed representation honoured on re-encodeBytesalias
pub alias Bytes = Std::Slice::BytesDecoderclass
pub class Decoder
def decode(payload : Bytes) : Array(HeaderField) | HpackError
def dynamic_table : DynamicTable
def initialize(max_table_size : Int32 = 4096, protocol_max_table_size : Int32 = 4096, max_header_list_size : Int32 = 65536, max_header_count : Int32 = 1000)
def max_header_count : Int32
def max_header_count=(value : Int32) : Int32
def max_header_list_size : Int32
def max_header_list_size=(value : Int32) : Int32
def protocol_max_table_size : Int32
# A SETTINGS_HEADER_TABLE_SIZE change reaches the table too: the limit
# was only this field's, and the table went on refusing an update above
# the size it was made with.
def protocol_max_table_size=(limit : Int32) : Nil
endDynamicTableclass
pub class DynamicTable
def add(name : String, value : String, never_indexed : Bool = false) : Nil
def add(field : HeaderField) : Nil
def empty? : Bool
def entries : Array(HeaderField)
def find(name : String, value : String) : Tuple(Int32, Bool)
def find_name(name : String) : Int32
def get(relative_index : Int32) : HeaderField | ::Nil
# A table is made within the limit it is resized within: made above it,
# an encoder's table outgrew the peer's, and an Encoder made with 8192
# sent blocks a default Decoder refused ("Index 121 out of range (max:
# 103)") while `set_max_size(8192)` on it was refused.
def initialize(max_size : Int32 = 4096, settings_max_size : Int32 = 4096)
def length : Int32
def max_size : Int32
def set_max_size(new_max : Int32) : Nil | HpackError
def settings_max_size : Int32
# The protocol's limit (SETTINGS_HEADER_TABLE_SIZE) as it changes during
# a connection; the size in use still moves only by `set_max_size`.
def settings_max_size=(limit : Int32) : Nil
def size : Int32
def self.size_error(new_max : Int32, limit : Int32) : HpackError | ::Nil
endEncoderclass
pub class Encoder
def dynamic_table : DynamicTable
def encode(headers : Array(HeaderField), force_huffman : Bool | ::Nil = nil) : Bytes
def initialize(max_table_size : Int32 = 4096, protocol_max_table_size : Int32 = 4096, use_huffman : Bool = true)
def protocol_max_table_size : Int32
# A SETTINGS_HEADER_TABLE_SIZE change reaches the table too: the limit
# was only this field's, so a raised one was refused by the table and a
# lowered one left the table above it (RFC 7541 4.2), with no update.
# A lowered limit is always signalled: the peer's table may still be the
# size the old limit let it start at, which this encoder never sent.
def protocol_max_table_size=(limit : Int32) : Nil
def set_max_table_size(new_max : Int32) : Nil | HpackError
def use_huffman : Bool
def use_huffman=(value : Bool) : Bool
endHeaderFieldstruct
pub struct HeaderField
def ==(other : HeaderField) : Bool
def indexing : IndexingMode
def initialize(name : String, value : String, indexing : IndexingMode = IndexingMode::Incremental)
def initialize(name : String, value : String, never_indexed : Bool)
def name : String
def never_indexed : Bool
def size : Int32
def to_s : String
def value : String
endHpackErrorstruct
pub struct HpackError
def initialize(message : String)
def to_s : String
endHuffmanclass
pub class Huffman
def self.decode(bytes : Bytes, offset : Int32, length : Int32) : String | HpackError
def self.decode(bytes : Bytes) : String | HpackError
def self.encode(input : String, buffer : Array(UInt8)) : Nil
def self.encode(input : String) : Bytes
def self.encode(input : Bytes) : Bytes
def self.encoded_size(input : String) : Int32
endIndexingModestruct
pub struct IndexingMode
def ==(other : IndexingMode) : Bool
def initialize(value : Int32)
def value : Int32
endIntegerclass
pub class Integer
def self.decode(bytes : Bytes, offset : Int32, prefix_bits : Int32) : Tuple(UInt64, Int32) | HpackError
def self.encode(val : UInt64, prefix_bits : Int32, prefix_byte : UInt8, buffer : Array(UInt8)) : Nil
def self.encode(val : UInt64, prefix_bits : Int32, prefix_byte : UInt8 = 0_u8) : Bytes
endIntegerCodecalias
pub alias IntegerCodec = IntegerStaticTableclass
pub class StaticTable
def self.find(name : String, value : String) : Tuple(Int32, Bool)
def self.find_name(name : String) : Int32
def self.get(index : Int32) : HeaderField | ::Nil
def self.size : Int32
endTableclass
pub class Table
def self.find(dynamic_table : DynamicTable, name : String, value : String) : Tuple(Int32, Bool)
def self.find_name(dynamic_table : DynamicTable, name : String) : Int32
def self.get(dynamic_table : DynamicTable, global_index : Int32) : HeaderField | ::Nil
endError for HpackErrorimpl
impl Error for HpackError
def message : String
end
Standard HPACK: Header Compression for HTTP/2 (RFC 7541)