Class: HTTP2::Header::Decompressor

Inherits:
Object
  • Object
show all
Includes:
BufferUtils, Error
Defined in:
lib/http/2/header/decompressor.rb

Overview

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.

Examples:

server_role = Decompressor.new(:request)
client_role = Decompressor.new(:response)

Constant Summary collapse

FORBIDDEN_HEADERS =
%w[connection te].freeze

Instance Method Summary collapse

Methods included from BufferUtils

#append_str, #read_str, #read_uint32, #shift_byte

Constructor Details

#initialize(options = {}) ⇒ Decompressor

Returns a new instance of Decompressor.

Parameters:

  • options (Hash) (defaults to: {})

    decoding options. Only :table_size is effective.



19
20
21
# File 'lib/http/2/header/decompressor.rb', line 19

def initialize(options = {})
  @cc = EncodingContext.new(options)
end

Instance Method Details

#decode(buf, frame = nil) ⇒ Array

Decodes and processes header commands within provided buffer.

Parameters:

  • buf (Buffer)
  • frame (HTTP2::Frame, nil) (defaults to: nil)

Returns:

  • (Array)

    +[[name, value], …]



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
# File 'lib/http/2/header/decompressor.rb', line 116

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

#header(buf) ⇒ Hash

Decodes header command from provided buffer.

Parameters:

  • buf (Buffer)

Returns:

  • (Hash)

    command

Raises:



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
# File 'lib/http/2/header/decompressor.rb', line 78

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

#integer(buf, n) ⇒ Integer

Decodes integer value from provided buffer.

Parameters:

  • buf (String)
  • n (Integer)

    number of available bits

Returns:

  • (Integer)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/http/2/header/decompressor.rb', line 34

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

#string(buf) ⇒ String

Decodes string value from provided buffer.

Parameters:

  • buf (String)

Returns:

  • (String)

    UTF-8 encoded string

Raises:



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/http/2/header/decompressor.rb', line 62

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

#table_size=(size) ⇒ Object

Set dynamic table size in EncodingContext

Parameters:

  • size (Integer)

    new dynamic table size



25
26
27
# File 'lib/http/2/header/decompressor.rb', line 25

def table_size=(size)
  @cc.table_size = size
end