1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
# frozen_string_literal: true
module HTTP2
module Header
# Responsible for decoding received headers and maintaining compression
# context of the opposing peer. Decompressor must be initialized with
# appropriate starting context based on local role: client or server.
#
# @example
# server_role = Decompressor.new(:request)
# client_role = Decompressor.new(:response)
class Decompressor
include Error
include BufferUtils
FORBIDDEN_HEADERS = %w[connection te].freeze
# @param options [Hash] decoding options. Only :table_size is effective.
def initialize(options = {})
@cc = EncodingContext.new(options)
end
# Set dynamic table size in EncodingContext
# @param size [Integer] new dynamic table size
def table_size=(size)
@cc.table_size = size
end
# Decodes integer value from provided buffer.
#
# @param buf [String]
# @param n [Integer] number of available bits
# @return [Integer]
def integer(buf, n)
limit = (1 << n) - 1
i = n.zero? ? 0 : (shift_byte(buf) & limit)
m = 0
if i == limit
offset = 0
buf.each_byte.with_index do |byte, idx|
offset = idx
# while (byte = shift_byte(buf))
i += ((byte & 127) << m)
m += 7
break if byte.nobits?(128)
end
read_str(buf, offset + 1)
end
i
end
# Decodes string value from provided buffer.
#
# @param buf [String]
# @return [String] UTF-8 encoded string
# @raise [CompressionError] when input is malformed
def string(buf)
raise CompressionError, "invalid header block fragment" if buf.empty?
huffman = buf.getbyte(0).allbits?(0x80)
len = integer(buf, 7)
str = read_str(buf, len)
raise CompressionError, "string too short" unless str.bytesize == len
str = Huffman.decode(str) if huffman
str.force_encoding(Encoding::UTF_8)
end
# Decodes header command from provided buffer.
#
# @param buf [Buffer]
# @return [Hash] command
def header(buf)
peek = buf.getbyte(0)
header_type, type = HEADREP.find do |_, desc|
mask = (peek >> desc[:prefix]) << desc[:prefix]
mask == desc[:pattern]
end
raise CompressionError unless header_type && type
header_name = integer(buf, type[:prefix])
case header_type
when :indexed
raise CompressionError if header_name.zero?
header_name -= 1
{ type: header_type, name: header_name }
when :changetablesize
{ type: header_type, name: header_name, value: header_name }
else
if header_name.zero?
header_name = string(buf)
else
header_name -= 1
end
header_value = string(buf)
{ type: header_type, name: header_name, value: header_value }
end
end
# Decodes and processes header commands within provided buffer.
#
# @param buf [Buffer]
# @param frame [HTTP2::Frame, nil]
# @return [Array] +[[name, value], ...]
def decode(buf, frame = nil)
list = []
decoding_pseudo_headers = true
@cc.listen_on_table do
until buf.empty?
field, value = @cc.process(header(buf))
next if field.nil?
is_pseudo_header = field.start_with?(":")
if !decoding_pseudo_headers && is_pseudo_header
raise ProtocolError, "one or more pseudo headers encountered after regular headers"
end
decoding_pseudo_headers = is_pseudo_header
raise ProtocolError, "invalid header received: #{field}" if FORBIDDEN_HEADERS.include?(field)
if frame
case field
when ":status"
frame[:status] = Integer(value)
when ":method"
frame[:method] = value
when "content-length"
frame[:content_length] = Integer(value)
when "trailer"
(frame[:trailer] ||= []) << value
end
end
list << [field, value]
end
end
list
end
end
end
end
|