http-2 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/http/2/client.rb +1 -0
- data/lib/http/2/connection.rb +118 -99
- data/lib/http/2/emitter.rb +2 -9
- data/lib/http/2/extensions.rb +33 -15
- data/lib/http/2/flow_buffer.rb +45 -35
- data/lib/http/2/framer.rb +134 -107
- data/lib/http/2/header/compressor.rb +47 -31
- data/lib/http/2/header/decompressor.rb +32 -25
- data/lib/http/2/header/encoding_context.rb +84 -85
- data/lib/http/2/header/huffman.rb +13 -11
- data/lib/http/2/header/huffman_statemachine.rb +2 -2
- data/lib/http/2/server.rb +7 -1
- data/lib/http/2/stream.rb +9 -4
- data/lib/http/2/version.rb +1 -1
- data/lib/http/2.rb +5 -0
- data/sig/{next.rbs → 2.rbs} +29 -18
- data/sig/client.rbs +2 -0
- data/sig/connection.rbs +21 -8
- data/sig/emitter.rbs +2 -4
- data/sig/extensions.rbs +11 -1
- data/sig/flow_buffer.rbs +7 -5
- data/sig/frame_buffer.rbs +1 -1
- data/sig/framer.rbs +6 -0
- data/sig/header/compressor.rbs +6 -4
- data/sig/header/decompressor.rbs +5 -2
- data/sig/header/encoding_context.rbs +24 -6
- data/sig/header/huffman.rbs +19 -3
- data/sig/header.rbs +11 -8
- data/sig/stream.rbs +8 -5
- metadata +4 -7
@@ -5,6 +5,7 @@ module HTTP2
|
|
5
5
|
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
6
6
|
class Compressor
|
7
7
|
include PackingExtensions
|
8
|
+
include BufferUtils
|
8
9
|
|
9
10
|
# @param options [Hash] encoding options
|
10
11
|
def initialize(options = {})
|
@@ -34,8 +35,8 @@ module HTTP2
|
|
34
35
|
# @param buffer [String] buffer to pack bytes into
|
35
36
|
# @param offset [Integer] offset to insert packed bytes in buffer
|
36
37
|
# @return [String] binary string
|
37
|
-
def integer(i, n, buffer:, offset:
|
38
|
-
limit = (
|
38
|
+
def integer(i, n, buffer:, offset: buffer.size)
|
39
|
+
limit = (1 << n) - 1
|
39
40
|
return pack([i], "C", buffer: buffer, offset: offset) if i < limit
|
40
41
|
|
41
42
|
bytes = []
|
@@ -70,19 +71,23 @@ module HTTP2
|
|
70
71
|
# :shorter Use Huffman when the result is strictly shorter
|
71
72
|
#
|
72
73
|
# @param str [String]
|
74
|
+
# @param buffer [String]
|
73
75
|
# @return [String] binary string
|
74
|
-
def string(str)
|
76
|
+
def string(str, buffer = "".b)
|
75
77
|
case @cc.options[:huffman]
|
76
78
|
when :always
|
77
|
-
huffman_string(str)
|
79
|
+
huffman_string(str, buffer)
|
78
80
|
when :never
|
79
|
-
plain_string(str)
|
81
|
+
plain_string(str, buffer)
|
80
82
|
else
|
81
|
-
huffman =
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
huffman = Huffman.encode(str)
|
84
|
+
if huffman.bytesize < str.bytesize
|
85
|
+
huffman_offset = buffer.bytesize
|
86
|
+
append_str(buffer, huffman)
|
87
|
+
set_huffman_size(buffer, huffman_offset)
|
88
|
+
else
|
89
|
+
plain_string(str, buffer)
|
90
|
+
end
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
@@ -92,27 +97,30 @@ module HTTP2
|
|
92
97
|
# @param buffer [String]
|
93
98
|
# @return [Buffer]
|
94
99
|
def header(h, buffer = "".b)
|
95
|
-
|
100
|
+
type = h[:type]
|
101
|
+
rep = HEADREP[type]
|
102
|
+
offset = buffer.size
|
96
103
|
|
97
|
-
case
|
104
|
+
case type
|
98
105
|
when :indexed
|
99
106
|
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
100
107
|
when :changetablesize
|
101
108
|
integer(h[:value], rep[:prefix], buffer: buffer)
|
102
109
|
else
|
103
|
-
|
104
|
-
|
110
|
+
name = h[:name]
|
111
|
+
if name.is_a? Integer
|
112
|
+
integer(name + 1, rep[:prefix], buffer: buffer)
|
105
113
|
else
|
106
114
|
integer(0, rep[:prefix], buffer: buffer)
|
107
|
-
|
115
|
+
string(name, buffer)
|
108
116
|
end
|
109
117
|
|
110
|
-
|
118
|
+
string(h[:value], buffer)
|
111
119
|
end
|
112
120
|
|
113
121
|
# set header representation pattern on first byte
|
114
|
-
fb = buffer.ord | rep[:pattern]
|
115
|
-
buffer.setbyte(
|
122
|
+
fb = buffer[offset].ord | rep[:pattern]
|
123
|
+
buffer.setbyte(offset, fb)
|
116
124
|
|
117
125
|
buffer
|
118
126
|
end
|
@@ -123,11 +131,10 @@ module HTTP2
|
|
123
131
|
# @return [Buffer]
|
124
132
|
def encode(headers)
|
125
133
|
buffer = "".b
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
buffer << header(cmd)
|
134
|
+
headers.partition { |f, _| f.start_with? ":" }.each do |hs|
|
135
|
+
@cc.encode(hs) do |cmd|
|
136
|
+
header(cmd, buffer)
|
137
|
+
end
|
131
138
|
end
|
132
139
|
|
133
140
|
buffer
|
@@ -136,22 +143,31 @@ module HTTP2
|
|
136
143
|
private
|
137
144
|
|
138
145
|
# @param str [String]
|
146
|
+
# @param buffer [String]
|
139
147
|
# @return [String] binary string
|
140
|
-
def huffman_string(str)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
huffman
|
148
|
+
def huffman_string(str, buffer = "".b)
|
149
|
+
huffman_offset = buffer.bytesize
|
150
|
+
Huffman.encode(str, buffer)
|
151
|
+
set_huffman_size(buffer, huffman_offset)
|
145
152
|
end
|
146
153
|
|
147
154
|
# @param str [String]
|
155
|
+
# @param buffer [String]
|
148
156
|
# @return [String] binary string
|
149
|
-
def plain_string(str)
|
150
|
-
plain = "".b
|
157
|
+
def plain_string(str, plain = "".b)
|
151
158
|
integer(str.bytesize, 7, buffer: plain)
|
152
|
-
plain
|
159
|
+
append_str(plain, str)
|
153
160
|
plain
|
154
161
|
end
|
162
|
+
|
163
|
+
# @param buffer [String]
|
164
|
+
# @param huffman_offset [Integer] buffer offset where huffman string was introduced
|
165
|
+
# @return [String] binary string
|
166
|
+
def set_huffman_size(buffer, huffman_offset)
|
167
|
+
integer(buffer.bytesize - huffman_offset, 7, buffer: buffer, offset: huffman_offset)
|
168
|
+
buffer.setbyte(huffman_offset, buffer[huffman_offset].ord | 0x80)
|
169
|
+
buffer
|
170
|
+
end
|
155
171
|
end
|
156
172
|
end
|
157
173
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module HTTP2
|
4
4
|
module Header
|
5
|
-
using StringExtensions
|
6
5
|
# Responsible for decoding received headers and maintaining compression
|
7
6
|
# context of the opposing peer. Decompressor must be initialized with
|
8
7
|
# appropriate starting context based on local role: client or server.
|
@@ -12,6 +11,9 @@ module HTTP2
|
|
12
11
|
# client_role = Decompressor.new(:response)
|
13
12
|
class Decompressor
|
14
13
|
include Error
|
14
|
+
include BufferUtils
|
15
|
+
|
16
|
+
FORBIDDEN_HEADERS = %w[connection te].freeze
|
15
17
|
|
16
18
|
# @param options [Hash] decoding options. Only :table_size is effective.
|
17
19
|
def initialize(options = {})
|
@@ -30,17 +32,23 @@ module HTTP2
|
|
30
32
|
# @param n [Integer] number of available bits
|
31
33
|
# @return [Integer]
|
32
34
|
def integer(buf, n)
|
33
|
-
limit = (
|
34
|
-
i = n.zero? ? 0 : (buf
|
35
|
+
limit = (1 << n) - 1
|
36
|
+
i = n.zero? ? 0 : (shift_byte(buf) & limit)
|
35
37
|
|
36
38
|
m = 0
|
37
39
|
if i == limit
|
38
|
-
|
40
|
+
offset = 0
|
41
|
+
|
42
|
+
buf.each_byte.with_index do |byte, idx|
|
43
|
+
offset = idx
|
44
|
+
# while (byte = shift_byte(buf))
|
39
45
|
i += ((byte & 127) << m)
|
40
46
|
m += 7
|
41
47
|
|
42
|
-
break if (
|
48
|
+
break if byte.nobits?(128)
|
43
49
|
end
|
50
|
+
|
51
|
+
read_str(buf, offset + 1)
|
44
52
|
end
|
45
53
|
|
46
54
|
i
|
@@ -54,12 +62,12 @@ module HTTP2
|
|
54
62
|
def string(buf)
|
55
63
|
raise CompressionError, "invalid header block fragment" if buf.empty?
|
56
64
|
|
57
|
-
huffman =
|
65
|
+
huffman = buf.getbyte(0).allbits?(0x80)
|
58
66
|
len = integer(buf, 7)
|
59
|
-
str = buf
|
67
|
+
str = read_str(buf, len)
|
60
68
|
raise CompressionError, "string too short" unless str.bytesize == len
|
61
69
|
|
62
|
-
str = Huffman.
|
70
|
+
str = Huffman.decode(str) if huffman
|
63
71
|
str.force_encoding(Encoding::UTF_8)
|
64
72
|
end
|
65
73
|
|
@@ -70,37 +78,36 @@ module HTTP2
|
|
70
78
|
def header(buf)
|
71
79
|
peek = buf.getbyte(0)
|
72
80
|
|
73
|
-
|
74
|
-
header[:type], type = HEADREP.find do |_t, desc|
|
81
|
+
header_type, type = HEADREP.find do |_, desc|
|
75
82
|
mask = (peek >> desc[:prefix]) << desc[:prefix]
|
76
83
|
mask == desc[:pattern]
|
77
84
|
end
|
78
85
|
|
79
|
-
raise CompressionError unless
|
86
|
+
raise CompressionError unless header_type && type
|
80
87
|
|
81
|
-
|
88
|
+
header_name = integer(buf, type[:prefix])
|
82
89
|
|
83
|
-
case
|
90
|
+
case header_type
|
84
91
|
when :indexed
|
85
|
-
raise CompressionError if
|
92
|
+
raise CompressionError if header_name.zero?
|
93
|
+
|
94
|
+
header_name -= 1
|
86
95
|
|
87
|
-
|
96
|
+
{ type: header_type, name: header_name }
|
88
97
|
when :changetablesize
|
89
|
-
|
98
|
+
{ type: header_type, name: header_name, value: header_name }
|
90
99
|
else
|
91
|
-
if
|
92
|
-
|
100
|
+
if header_name.zero?
|
101
|
+
header_name = string(buf)
|
93
102
|
else
|
94
|
-
|
103
|
+
header_name -= 1
|
95
104
|
end
|
96
|
-
|
97
|
-
end
|
105
|
+
header_value = string(buf)
|
98
106
|
|
99
|
-
|
107
|
+
{ type: header_type, name: header_name, value: header_value }
|
108
|
+
end
|
100
109
|
end
|
101
110
|
|
102
|
-
FORBIDDEN_HEADERS = %w[connection te].freeze
|
103
|
-
|
104
111
|
# Decodes and processes header commands within provided buffer.
|
105
112
|
#
|
106
113
|
# @param buf [Buffer]
|
@@ -114,7 +121,7 @@ module HTTP2
|
|
114
121
|
field, value = @cc.process(header(buf))
|
115
122
|
next if field.nil?
|
116
123
|
|
117
|
-
is_pseudo_header = field.start_with?
|
124
|
+
is_pseudo_header = field.start_with?(":")
|
118
125
|
if !decoding_pseudo_headers && is_pseudo_header
|
119
126
|
raise ProtocolError, "one or more pseudo headers encountered after regular headers"
|
120
127
|
end
|
@@ -75,15 +75,27 @@ module HTTP2
|
|
75
75
|
["vary", ""],
|
76
76
|
["via", ""],
|
77
77
|
["www-authenticate", ""]
|
78
|
-
].each
|
78
|
+
].each(&:freeze).freeze
|
79
79
|
|
80
|
-
STATIC_TABLE_BY_FIELD =
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
STATIC_TABLE_BY_FIELD =
|
81
|
+
STATIC_TABLE
|
82
|
+
.each_with_object({})
|
83
|
+
.with_index { |((field, value), hs), idx| (hs[field] ||= []) << [idx, value].freeze }
|
84
|
+
.each_value(&:freeze)
|
85
|
+
.freeze
|
84
86
|
|
85
87
|
STATIC_TABLE_SIZE = STATIC_TABLE.size
|
86
88
|
|
89
|
+
DEFAULT_OPTIONS = {
|
90
|
+
huffman: :shorter,
|
91
|
+
index: :all,
|
92
|
+
table_size: 4096
|
93
|
+
}.freeze
|
94
|
+
|
95
|
+
STATIC_ALL = %i[all static].freeze
|
96
|
+
|
97
|
+
STATIC_NEVER = %i[never static].freeze
|
98
|
+
|
87
99
|
# Current table of header key-value pairs.
|
88
100
|
attr_reader :table
|
89
101
|
|
@@ -94,6 +106,9 @@ module HTTP2
|
|
94
106
|
# :index Symbol :all, :static, :never
|
95
107
|
attr_reader :options
|
96
108
|
|
109
|
+
# Current table size in octets
|
110
|
+
attr_reader :current_table_size
|
111
|
+
|
97
112
|
# Initializes compression context with appropriate client/server
|
98
113
|
# defaults and maximum size of the dynamic table.
|
99
114
|
#
|
@@ -102,15 +117,11 @@ module HTTP2
|
|
102
117
|
# :huffman Symbol :always, :never, :shorter
|
103
118
|
# :index Symbol :all, :static, :never
|
104
119
|
def initialize(options = {})
|
105
|
-
default_options = {
|
106
|
-
huffman: :shorter,
|
107
|
-
index: :all,
|
108
|
-
table_size: 4096
|
109
|
-
}
|
110
120
|
@table = []
|
111
|
-
@options =
|
121
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
112
122
|
@limit = @options[:table_size]
|
113
123
|
@_table_updated = false
|
124
|
+
@current_table_size = 0
|
114
125
|
end
|
115
126
|
|
116
127
|
# Duplicates current compression context
|
@@ -138,10 +149,13 @@ module HTTP2
|
|
138
149
|
# @return [Array] +[key, value]+
|
139
150
|
def dereference(index)
|
140
151
|
# NOTE: index is zero-based in this module.
|
141
|
-
|
142
|
-
|
152
|
+
return STATIC_TABLE[index] if index < STATIC_TABLE_SIZE
|
153
|
+
|
154
|
+
idx = index - STATIC_TABLE_SIZE
|
155
|
+
|
156
|
+
raise CompressionError, "Index too large" if idx >= @table.size
|
143
157
|
|
144
|
-
|
158
|
+
@table[index - STATIC_TABLE_SIZE]
|
145
159
|
end
|
146
160
|
|
147
161
|
# Header Block Processing
|
@@ -151,9 +165,11 @@ module HTTP2
|
|
151
165
|
# @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
|
152
166
|
# or nil if +cmd[:type]+ is +:changetablesize+
|
153
167
|
def process(cmd)
|
154
|
-
|
168
|
+
type = cmd[:type]
|
169
|
+
name = cmd[:name]
|
170
|
+
value = cmd[:value]
|
155
171
|
|
156
|
-
case
|
172
|
+
case type
|
157
173
|
when :changetablesize
|
158
174
|
raise CompressionError, "tried to change table size after adding elements to table" if @_table_updated
|
159
175
|
|
@@ -161,21 +177,18 @@ module HTTP2
|
|
161
177
|
# we should blow up if we receive another frame where the new table size is bigger.
|
162
178
|
table_size_updated = @limit != @options[:table_size]
|
163
179
|
|
164
|
-
raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated &&
|
180
|
+
raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && value > @limit
|
165
181
|
|
166
|
-
self.table_size =
|
182
|
+
self.table_size = value
|
167
183
|
|
184
|
+
nil
|
168
185
|
when :indexed
|
169
186
|
# Indexed Representation
|
170
187
|
# An _indexed representation_ entails the following actions:
|
171
188
|
# o The header field corresponding to the referenced entry in either
|
172
189
|
# the static table or dynamic table is added to the decoded header
|
173
190
|
# list.
|
174
|
-
|
175
|
-
|
176
|
-
k, v = dereference(idx)
|
177
|
-
emit = [k, v]
|
178
|
-
|
191
|
+
dereference(name)
|
179
192
|
when :incremental, :noindex, :neverindexed
|
180
193
|
# A _literal representation_ that is _not added_ to the dynamic table
|
181
194
|
# entails the following action:
|
@@ -186,27 +199,28 @@ module HTTP2
|
|
186
199
|
# o The header field is added to the decoded header list.
|
187
200
|
# o The header field is inserted at the beginning of the dynamic table.
|
188
201
|
|
189
|
-
case
|
202
|
+
case name
|
190
203
|
when Integer
|
191
|
-
|
204
|
+
name, v = dereference(name)
|
192
205
|
|
193
|
-
|
194
|
-
cmd[:index] ||= cmd[:name]
|
195
|
-
cmd[:value] ||= v
|
196
|
-
cmd[:name] = k
|
206
|
+
value ||= v
|
197
207
|
when UPPER
|
198
|
-
raise ProtocolError, "Invalid uppercase key: #{
|
208
|
+
raise ProtocolError, "Invalid uppercase key: #{name}"
|
199
209
|
end
|
200
210
|
|
201
|
-
emit = [
|
211
|
+
emit = [name, value]
|
202
212
|
|
203
|
-
|
213
|
+
# add to table
|
214
|
+
if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
|
215
|
+
@table.unshift(emit)
|
216
|
+
@current_table_size += name.bytesize + value.bytesize + 32
|
217
|
+
@_table_updated = true
|
218
|
+
end
|
204
219
|
|
220
|
+
emit
|
205
221
|
else
|
206
|
-
raise CompressionError, "Invalid type: #{
|
222
|
+
raise CompressionError, "Invalid type: #{type}"
|
207
223
|
end
|
208
|
-
|
209
|
-
emit
|
210
224
|
end
|
211
225
|
|
212
226
|
# Plan header compression according to +@options [:index]+
|
@@ -217,9 +231,9 @@ module HTTP2
|
|
217
231
|
# @param headers [Array] +[[name, value], ...]+
|
218
232
|
# @return [Array] array of commands
|
219
233
|
def encode(headers)
|
220
|
-
commands = []
|
221
234
|
# Literals commands are marked with :noindex when index is not used
|
222
|
-
noindex =
|
235
|
+
noindex = STATIC_NEVER.include?(@options[:index])
|
236
|
+
|
223
237
|
headers.each do |field, value|
|
224
238
|
# Literal header names MUST be translated to lowercase before
|
225
239
|
# encoding and transmission.
|
@@ -227,10 +241,9 @@ module HTTP2
|
|
227
241
|
value = "/" if field == ":path" && value.empty?
|
228
242
|
cmd = addcmd(field, value)
|
229
243
|
cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
|
230
|
-
commands << cmd
|
231
244
|
process(cmd)
|
245
|
+
yield cmd
|
232
246
|
end
|
233
|
-
commands
|
234
247
|
end
|
235
248
|
|
236
249
|
# Emits command for a header.
|
@@ -243,30 +256,36 @@ module HTTP2
|
|
243
256
|
# :static Use static table only.
|
244
257
|
# :all Use all of them.
|
245
258
|
#
|
246
|
-
# @param
|
259
|
+
# @param field [String] the header field
|
260
|
+
# @param value [String] the header value
|
247
261
|
# @return [Hash] command
|
248
|
-
def addcmd(
|
262
|
+
def addcmd(field, value)
|
263
|
+
# @type var exact: Integer?
|
249
264
|
exact = nil
|
265
|
+
# @type var name_only: Integer?
|
250
266
|
name_only = nil
|
251
267
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
268
|
+
index_type = @options[:index]
|
269
|
+
|
270
|
+
if STATIC_ALL.include?(index_type) &&
|
271
|
+
STATIC_TABLE_BY_FIELD.key?(field)
|
272
|
+
STATIC_TABLE_BY_FIELD[field].each do |i, svalue|
|
273
|
+
name_only ||= i
|
274
|
+
if value == svalue
|
275
|
+
exact = i
|
276
|
+
break
|
261
277
|
end
|
262
278
|
end
|
263
279
|
end
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
280
|
+
|
281
|
+
if index_type == :all && !exact
|
282
|
+
@table.each_with_index do |(hfield, hvalue), i|
|
283
|
+
next unless field == hfield
|
284
|
+
|
285
|
+
if value == hvalue
|
286
|
+
exact = i + STATIC_TABLE_SIZE
|
268
287
|
break
|
269
|
-
|
288
|
+
else
|
270
289
|
name_only ||= i + STATIC_TABLE_SIZE
|
271
290
|
end
|
272
291
|
end
|
@@ -274,10 +293,8 @@ module HTTP2
|
|
274
293
|
|
275
294
|
if exact
|
276
295
|
{ name: exact, type: :indexed }
|
277
|
-
elsif name_only
|
278
|
-
{ name: name_only, value: header.last, type: :incremental }
|
279
296
|
else
|
280
|
-
{ name:
|
297
|
+
{ name: name_only || field, value: value, type: :incremental }
|
281
298
|
end
|
282
299
|
end
|
283
300
|
|
@@ -285,13 +302,7 @@ module HTTP2
|
|
285
302
|
# When the size is reduced, some headers might be evicted.
|
286
303
|
def table_size=(size)
|
287
304
|
@limit = size
|
288
|
-
size_check(
|
289
|
-
end
|
290
|
-
|
291
|
-
# Returns current table size in octets
|
292
|
-
# @return [Integer]
|
293
|
-
def current_table_size
|
294
|
-
@table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
|
305
|
+
size_check(0)
|
295
306
|
end
|
296
307
|
|
297
308
|
def listen_on_table
|
@@ -302,32 +313,20 @@ module HTTP2
|
|
302
313
|
|
303
314
|
private
|
304
315
|
|
305
|
-
# Add a name-value pair to the dynamic table.
|
306
|
-
# Older entries might have been evicted so that
|
307
|
-
# the new entry fits in the dynamic table.
|
308
|
-
#
|
309
|
-
# @param cmd [Array] +[name, value]+
|
310
|
-
def add_to_table(cmd)
|
311
|
-
return unless size_check(cmd)
|
312
|
-
|
313
|
-
@table.unshift(cmd)
|
314
|
-
@_table_updated = true
|
315
|
-
end
|
316
|
-
|
317
316
|
# To keep the dynamic table size lower than or equal to @limit,
|
318
317
|
# remove one or more entries at the end of the dynamic table.
|
319
318
|
#
|
320
|
-
# @param
|
319
|
+
# @param cmdsize [Integer]
|
321
320
|
# @return [Boolean] whether +cmd+ fits in the dynamic table.
|
322
|
-
def size_check(
|
323
|
-
|
324
|
-
|
321
|
+
def size_check(cmdsize)
|
322
|
+
unless @table.empty?
|
323
|
+
while @current_table_size + cmdsize > @limit
|
325
324
|
|
326
|
-
|
327
|
-
|
325
|
+
name, value = @table.pop
|
326
|
+
@current_table_size -= name.bytesize + value.bytesize + 32
|
327
|
+
break if @table.empty?
|
328
328
|
|
329
|
-
|
330
|
-
cursize -= e[0].bytesize + e[1].bytesize + 32
|
329
|
+
end
|
331
330
|
end
|
332
331
|
|
333
332
|
cmdsize <= @limit
|
@@ -9,11 +9,12 @@ module HTTP2
|
|
9
9
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
|
10
10
|
module Header
|
11
11
|
# Huffman encoder/decoder
|
12
|
-
|
13
|
-
|
12
|
+
module Huffman
|
13
|
+
module_function
|
14
14
|
|
15
15
|
include Error
|
16
|
-
|
16
|
+
extend PackingExtensions
|
17
|
+
extend BufferUtils
|
17
18
|
|
18
19
|
BITS_AT_ONCE = 4
|
19
20
|
EOS = 256
|
@@ -23,11 +24,13 @@ module HTTP2
|
|
23
24
|
# Length is not encoded in this method.
|
24
25
|
#
|
25
26
|
# @param str [String]
|
27
|
+
# @param buffer [String]
|
26
28
|
# @return [String] binary string
|
27
|
-
def encode(str)
|
28
|
-
bitstring = str.
|
29
|
-
|
30
|
-
|
29
|
+
def encode(str, buffer = "".b)
|
30
|
+
bitstring = String.new("", encoding: Encoding::BINARY, capacity: (str.bytesize * 30) + ((8 - str.size) % 8))
|
31
|
+
str.each_byte { |chr| append_str(bitstring, ENCODE_TABLE[chr]) }
|
32
|
+
append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
|
33
|
+
pack([bitstring], "B*", buffer: buffer)
|
31
34
|
end
|
32
35
|
|
33
36
|
# Decodes provided Huffman coded string.
|
@@ -48,11 +51,10 @@ module HTTP2
|
|
48
51
|
# Each transition is [emit, next]
|
49
52
|
# [emit] character to be emitted on this transition, empty string, or EOS.
|
50
53
|
# [next] next state number.
|
51
|
-
|
52
|
-
raise CompressionError, "Huffman decode error (EOS found)" if
|
54
|
+
first, state = MACHINE.dig(state, branch)
|
55
|
+
raise CompressionError, "Huffman decode error (EOS found)" if first == EOS
|
53
56
|
|
54
|
-
emit
|
55
|
-
state = trans.last
|
57
|
+
append_str(emit, first.chr) if first
|
56
58
|
end
|
57
59
|
end
|
58
60
|
# Check whether partial input is correctly filled
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
module HTTP2
|
10
10
|
module Header
|
11
|
-
|
11
|
+
module Huffman
|
12
12
|
# :nodoc:
|
13
13
|
MAX_FINAL_STATE = 7
|
14
14
|
MACHINE = [
|
@@ -268,7 +268,7 @@ module HTTP2
|
|
268
268
|
[[28, 29], [28, 5], [29, 29], [29, 5], [30, 29], [30, 5], [31, 29], [31, 5], [127, 29], [127, 5], [220, 29], [220, 5], [249, 29], [249, 5], [nil, 254], [nil, 255]],
|
269
269
|
[[10, 17], [10, 18], [10, 19], [10, 20], [10, 21], [10, 22], [10, 23], [10, 7], [13, 17], [13, 18], [13, 19], [13, 20], [13, 21], [13, 22], [13, 23], [13, 7]],
|
270
270
|
[[22, 17], [22, 18], [22, 19], [22, 20], [22, 21], [22, 22], [22, 23], [22, 7], [256, 17], [256, 18], [256, 19], [256, 20], [256, 21], [256, 22], [256, 23], [256, 7]],
|
271
|
-
].each { |arr| arr.each { |subarr| subarr.
|
271
|
+
].each { |arr| arr.each { |subarr| subarr.freeze }.freeze }.freeze
|
272
272
|
end
|
273
273
|
end
|
274
274
|
end
|
data/lib/http/2/server.rb
CHANGED
@@ -79,7 +79,7 @@ module HTTP2
|
|
79
79
|
|
80
80
|
# Process received HTTP2-Settings payload
|
81
81
|
buf = "".b
|
82
|
-
buf
|
82
|
+
append_str(buf, Base64.urlsafe_decode64(settings.to_s))
|
83
83
|
@framer.common_header(
|
84
84
|
{
|
85
85
|
length: buf.bytesize,
|
@@ -120,6 +120,12 @@ module HTTP2
|
|
120
120
|
@state = :waiting_magic
|
121
121
|
end
|
122
122
|
|
123
|
+
def activate_stream(**)
|
124
|
+
super.tap do |stream|
|
125
|
+
stream.on(:promise, &method(:promise))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
123
129
|
def origin_set=(origins)
|
124
130
|
@origin_set = Array(origins).map(&:to_s)
|
125
131
|
@origins_sent = @origin_set.empty?
|