http-2 0.11.0 → 1.0.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -9
  3. data/lib/http/2/base64.rb +45 -0
  4. data/lib/http/2/client.rb +19 -6
  5. data/lib/http/2/connection.rb +235 -163
  6. data/lib/http/2/emitter.rb +7 -5
  7. data/lib/http/2/error.rb +24 -1
  8. data/lib/http/2/extensions.rb +53 -0
  9. data/lib/http/2/flow_buffer.rb +91 -33
  10. data/lib/http/2/framer.rb +184 -157
  11. data/lib/http/2/header/compressor.rb +157 -0
  12. data/lib/http/2/header/decompressor.rb +144 -0
  13. data/lib/http/2/header/encoding_context.rb +337 -0
  14. data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
  15. data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
  16. data/lib/http/2/header.rb +35 -0
  17. data/lib/http/2/server.rb +47 -20
  18. data/lib/http/2/stream.rb +130 -61
  19. data/lib/http/2/version.rb +3 -1
  20. data/lib/http/2.rb +14 -13
  21. data/sig/client.rbs +9 -0
  22. data/sig/connection.rbs +93 -0
  23. data/sig/emitter.rbs +13 -0
  24. data/sig/error.rbs +35 -0
  25. data/sig/extensions.rbs +5 -0
  26. data/sig/flow_buffer.rbs +21 -0
  27. data/sig/frame_buffer.rbs +13 -0
  28. data/sig/framer.rbs +54 -0
  29. data/sig/header/compressor.rbs +27 -0
  30. data/sig/header/decompressor.rbs +22 -0
  31. data/sig/header/encoding_context.rbs +34 -0
  32. data/sig/header/huffman.rbs +9 -0
  33. data/sig/header.rbs +27 -0
  34. data/sig/next.rbs +101 -0
  35. data/sig/server.rbs +12 -0
  36. data/sig/stream.rbs +91 -0
  37. metadata +38 -79
  38. data/.autotest +0 -20
  39. data/.coveralls.yml +0 -1
  40. data/.gitignore +0 -20
  41. data/.gitmodules +0 -3
  42. data/.rspec +0 -5
  43. data/.rubocop.yml +0 -93
  44. data/.rubocop_todo.yml +0 -131
  45. data/.travis.yml +0 -17
  46. data/Gemfile +0 -16
  47. data/Guardfile +0 -18
  48. data/Guardfile.h2spec +0 -12
  49. data/LICENSE +0 -21
  50. data/Rakefile +0 -49
  51. data/example/Gemfile +0 -3
  52. data/example/README.md +0 -44
  53. data/example/client.rb +0 -122
  54. data/example/helper.rb +0 -19
  55. data/example/keys/server.crt +0 -20
  56. data/example/keys/server.key +0 -27
  57. data/example/server.rb +0 -139
  58. data/example/upgrade_client.rb +0 -153
  59. data/example/upgrade_server.rb +0 -203
  60. data/http-2.gemspec +0 -22
  61. data/lib/http/2/buffer.rb +0 -76
  62. data/lib/http/2/compressor.rb +0 -572
  63. data/lib/tasks/generate_huffman_table.rb +0 -166
  64. data/spec/buffer_spec.rb +0 -28
  65. data/spec/client_spec.rb +0 -188
  66. data/spec/compressor_spec.rb +0 -666
  67. data/spec/connection_spec.rb +0 -681
  68. data/spec/emitter_spec.rb +0 -54
  69. data/spec/framer_spec.rb +0 -487
  70. data/spec/h2spec/h2spec.darwin +0 -0
  71. data/spec/h2spec/output/non_secure.txt +0 -317
  72. data/spec/helper.rb +0 -147
  73. data/spec/hpack_test_spec.rb +0 -84
  74. data/spec/huffman_spec.rb +0 -68
  75. data/spec/server_spec.rb +0 -52
  76. data/spec/stream_spec.rb +0 -878
  77. data/spec/support/deep_dup.rb +0 -55
  78. data/spec/support/duplicable.rb +0 -98
data/lib/http/2/buffer.rb DELETED
@@ -1,76 +0,0 @@
1
- require 'forwardable'
2
-
3
- module HTTP2
4
- # Binary buffer wraps String.
5
- #
6
- class Buffer
7
- extend Forwardable
8
-
9
- def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
10
- :size, :each_byte, :to_str, :to_s, :length, :inspect,
11
- :[], :[]=, :empty?, :bytesize, :include?
12
-
13
- UINT32 = 'N'.freeze
14
- private_constant :UINT32
15
-
16
- # Forces binary encoding on the string
17
- def initialize(str = '')
18
- str = str.dup if str.frozen?
19
- @buffer = str.force_encoding(Encoding::BINARY)
20
- end
21
-
22
- # Emulate StringIO#read: slice first n bytes from the buffer.
23
- #
24
- # @param n [Integer] number of bytes to slice from the buffer
25
- def read(n)
26
- Buffer.new(@buffer.slice!(0, n))
27
- end
28
-
29
- # Emulate StringIO#getbyte: slice first byte from buffer.
30
- def getbyte
31
- read(1).ord
32
- end
33
-
34
- def slice!(*args)
35
- Buffer.new(@buffer.slice!(*args))
36
- end
37
-
38
- def slice(*args)
39
- Buffer.new(@buffer.slice(*args))
40
- end
41
-
42
- def force_encoding(*args)
43
- @buffer = @buffer.force_encoding(*args)
44
- end
45
-
46
- def ==(other)
47
- @buffer == other
48
- end
49
-
50
- def +(other)
51
- @buffer += other
52
- end
53
-
54
- # Emulate String#getbyte: return nth byte from buffer.
55
- def readbyte(n)
56
- @buffer[n].ord
57
- end
58
-
59
- # Slice unsigned 32-bit integer from buffer.
60
- # @return [Integer]
61
- def read_uint32
62
- read(4).unpack(UINT32).first
63
- end
64
-
65
- # Ensures that data that is added is binary encoded as well,
66
- # otherwise this could lead to the Buffer instance changing its encoding.
67
- [:<<, :prepend].each do |mutating_method|
68
- define_method(mutating_method) do |string|
69
- string = string.dup if string.frozen?
70
- @buffer.send mutating_method, string.force_encoding(Encoding::BINARY)
71
-
72
- self
73
- end
74
- end
75
- end
76
- end
@@ -1,572 +0,0 @@
1
- module HTTP2
2
- # Implementation of header compression for HTTP 2.0 (HPACK) format adapted
3
- # to efficiently represent HTTP headers in the context of HTTP 2.0.
4
- #
5
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
6
- module Header
7
- # To decompress header blocks, a decoder only needs to maintain a
8
- # dynamic table as a decoding context.
9
- # No other state information is needed.
10
- class EncodingContext
11
- include Error
12
-
13
- # @private
14
- # Static table
15
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#appendix-A
16
- STATIC_TABLE = [
17
- [':authority', ''],
18
- [':method', 'GET'],
19
- [':method', 'POST'],
20
- [':path', '/'],
21
- [':path', '/index.html'],
22
- [':scheme', 'http'],
23
- [':scheme', 'https'],
24
- [':status', '200'],
25
- [':status', '204'],
26
- [':status', '206'],
27
- [':status', '304'],
28
- [':status', '400'],
29
- [':status', '404'],
30
- [':status', '500'],
31
- ['accept-charset', ''],
32
- ['accept-encoding', 'gzip, deflate'],
33
- ['accept-language', ''],
34
- ['accept-ranges', ''],
35
- ['accept', ''],
36
- ['access-control-allow-origin', ''],
37
- ['age', ''],
38
- ['allow', ''],
39
- ['authorization', ''],
40
- ['cache-control', ''],
41
- ['content-disposition', ''],
42
- ['content-encoding', ''],
43
- ['content-language', ''],
44
- ['content-length', ''],
45
- ['content-location', ''],
46
- ['content-range', ''],
47
- ['content-type', ''],
48
- ['cookie', ''],
49
- ['date', ''],
50
- ['etag', ''],
51
- ['expect', ''],
52
- ['expires', ''],
53
- ['from', ''],
54
- ['host', ''],
55
- ['if-match', ''],
56
- ['if-modified-since', ''],
57
- ['if-none-match', ''],
58
- ['if-range', ''],
59
- ['if-unmodified-since', ''],
60
- ['last-modified', ''],
61
- ['link', ''],
62
- ['location', ''],
63
- ['max-forwards', ''],
64
- ['proxy-authenticate', ''],
65
- ['proxy-authorization', ''],
66
- ['range', ''],
67
- ['referer', ''],
68
- ['refresh', ''],
69
- ['retry-after', ''],
70
- ['server', ''],
71
- ['set-cookie', ''],
72
- ['strict-transport-security', ''],
73
- ['transfer-encoding', ''],
74
- ['user-agent', ''],
75
- ['vary', ''],
76
- ['via', ''],
77
- ['www-authenticate', ''],
78
- ].each { |pair| pair.each(&:freeze).freeze }.freeze
79
-
80
- # Current table of header key-value pairs.
81
- attr_reader :table
82
-
83
- # Current encoding options
84
- #
85
- # :table_size Integer maximum dynamic table size in bytes
86
- # :huffman Symbol :always, :never, :shorter
87
- # :index Symbol :all, :static, :never
88
- attr_reader :options
89
-
90
- # Initializes compression context with appropriate client/server
91
- # defaults and maximum size of the dynamic table.
92
- #
93
- # @param options [Hash] encoding options
94
- # :table_size Integer maximum dynamic table size in bytes
95
- # :huffman Symbol :always, :never, :shorter
96
- # :index Symbol :all, :static, :never
97
- def initialize(**options)
98
- default_options = {
99
- huffman: :shorter,
100
- index: :all,
101
- table_size: 4096,
102
- }
103
- @table = []
104
- @options = default_options.merge(options)
105
- @limit = @options[:table_size]
106
- end
107
-
108
- # Duplicates current compression context
109
- # @return [EncodingContext]
110
- def dup
111
- other = EncodingContext.new(@options)
112
- t = @table
113
- l = @limit
114
- other.instance_eval do
115
- @table = t.dup # shallow copy
116
- @limit = l
117
- end
118
- other
119
- end
120
-
121
- # Finds an entry in current dynamic table by index.
122
- # Note that index is zero-based in this module.
123
- #
124
- # If the index is greater than the last index in the static table,
125
- # an entry in the dynamic table is dereferenced.
126
- #
127
- # If the index is greater than the last header index, an error is raised.
128
- #
129
- # @param index [Integer] zero-based index in the dynamic table.
130
- # @return [Array] +[key, value]+
131
- def dereference(index)
132
- # NOTE: index is zero-based in this module.
133
- value = STATIC_TABLE[index] || @table[index - STATIC_TABLE.size]
134
- fail CompressionError, 'Index too large' unless value
135
- value
136
- end
137
-
138
- # Header Block Processing
139
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-4.1
140
- #
141
- # @param cmd [Hash] { type:, name:, value:, index: }
142
- # @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
143
- # or nil if +cmd[:type]+ is +:changetablesize+
144
- def process(cmd)
145
- emit = nil
146
-
147
- case cmd[:type]
148
- when :changetablesize
149
- if cmd[:value] > @limit
150
- fail CompressionError, 'dynamic table size update exceed limit'
151
- end
152
- self.table_size = cmd[:value]
153
-
154
- when :indexed
155
- # Indexed Representation
156
- # An _indexed representation_ entails the following actions:
157
- # o The header field corresponding to the referenced entry in either
158
- # the static table or dynamic table is added to the decoded header
159
- # list.
160
- idx = cmd[:name]
161
-
162
- k, v = dereference(idx)
163
- emit = [k, v]
164
-
165
- when :incremental, :noindex, :neverindexed
166
- # A _literal representation_ that is _not added_ to the dynamic table
167
- # entails the following action:
168
- # o The header field is added to the decoded header list.
169
-
170
- # A _literal representation_ that is _added_ to the dynamic table
171
- # entails the following actions:
172
- # o The header field is added to the decoded header list.
173
- # o The header field is inserted at the beginning of the dynamic table.
174
-
175
- if cmd[:name].is_a? Integer
176
- k, v = dereference(cmd[:name])
177
-
178
- cmd = cmd.dup
179
- cmd[:index] ||= cmd[:name]
180
- cmd[:value] ||= v
181
- cmd[:name] = k
182
- end
183
-
184
- emit = [cmd[:name], cmd[:value]]
185
-
186
- add_to_table(emit) if cmd[:type] == :incremental
187
-
188
- else
189
- fail CompressionError, "Invalid type: #{cmd[:type]}"
190
- end
191
-
192
- emit
193
- end
194
-
195
- # Plan header compression according to +@options [:index]+
196
- # :never Do not use dynamic table or static table reference at all.
197
- # :static Use static table only.
198
- # :all Use all of them.
199
- #
200
- # @param headers [Array] +[[name, value], ...]+
201
- # @return [Array] array of commands
202
- def encode(headers)
203
- commands = []
204
- # Literals commands are marked with :noindex when index is not used
205
- noindex = [:static, :never].include?(@options[:index])
206
- headers.each do |field, value|
207
- # Literal header names MUST be translated to lowercase before
208
- # encoding and transmission.
209
- field = field.downcase
210
- value = '/' if field == ':path' && value.empty?
211
- cmd = addcmd(field, value)
212
- cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
213
- commands << cmd
214
- process(cmd)
215
- end
216
- commands
217
- end
218
-
219
- # Emits command for a header.
220
- # Prefer static table over dynamic table.
221
- # Prefer exact match over name-only match.
222
- #
223
- # +@options [:index]+ controls whether to use the dynamic table,
224
- # static table, or both.
225
- # :never Do not use dynamic table or static table reference at all.
226
- # :static Use static table only.
227
- # :all Use all of them.
228
- #
229
- # @param header [Array] +[name, value]+
230
- # @return [Hash] command
231
- def addcmd(*header)
232
- exact = nil
233
- name_only = nil
234
-
235
- if [:all, :static].include?(@options[:index])
236
- STATIC_TABLE.each_index do |i|
237
- if STATIC_TABLE[i] == header
238
- exact ||= i
239
- break
240
- elsif STATIC_TABLE[i].first == header.first
241
- name_only ||= i
242
- end
243
- end
244
- end
245
- if [:all].include?(@options[:index]) && !exact
246
- @table.each_index do |i|
247
- if @table[i] == header
248
- exact ||= i + STATIC_TABLE.size
249
- break
250
- elsif @table[i].first == header.first
251
- name_only ||= i + STATIC_TABLE.size
252
- end
253
- end
254
- end
255
-
256
- if exact
257
- { name: exact, type: :indexed }
258
- elsif name_only
259
- { name: name_only, value: header.last, type: :incremental }
260
- else
261
- { name: header.first, value: header.last, type: :incremental }
262
- end
263
- end
264
-
265
- # Alter dynamic table size.
266
- # When the size is reduced, some headers might be evicted.
267
- def table_size=(size)
268
- @limit = size
269
- size_check(nil)
270
- end
271
-
272
- # Returns current table size in octets
273
- # @return [Integer]
274
- def current_table_size
275
- @table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
276
- end
277
-
278
- private
279
-
280
- # Add a name-value pair to the dynamic table.
281
- # Older entries might have been evicted so that
282
- # the new entry fits in the dynamic table.
283
- #
284
- # @param cmd [Array] +[name, value]+
285
- def add_to_table(cmd)
286
- return unless size_check(cmd)
287
- @table.unshift(cmd)
288
- end
289
-
290
- # To keep the dynamic table size lower than or equal to @limit,
291
- # remove one or more entries at the end of the dynamic table.
292
- #
293
- # @param cmd [Hash]
294
- # @return [Boolean] whether +cmd+ fits in the dynamic table.
295
- def size_check(cmd)
296
- cursize = current_table_size
297
- cmdsize = cmd.nil? ? 0 : cmd[0].bytesize + cmd[1].bytesize + 32
298
-
299
- while cursize + cmdsize > @limit
300
- break if @table.empty?
301
-
302
- e = @table.pop
303
- cursize -= e[0].bytesize + e[1].bytesize + 32
304
- end
305
-
306
- cmdsize <= @limit
307
- end
308
- end
309
-
310
- # Header representation as defined by the spec.
311
- HEADREP = {
312
- indexed: { prefix: 7, pattern: 0x80 },
313
- incremental: { prefix: 6, pattern: 0x40 },
314
- noindex: { prefix: 4, pattern: 0x00 },
315
- neverindexed: { prefix: 4, pattern: 0x10 },
316
- changetablesize: { prefix: 5, pattern: 0x20 },
317
- }.each_value(&:freeze).freeze
318
-
319
- # Predefined options set for Compressor
320
- # http://mew.org/~kazu/material/2014-hpack.pdf
321
- NAIVE = { index: :never, huffman: :never }.freeze
322
- LINEAR = { index: :all, huffman: :never }.freeze
323
- STATIC = { index: :static, huffman: :never }.freeze
324
- SHORTER = { index: :all, huffman: :never }.freeze
325
- NAIVEH = { index: :never, huffman: :always }.freeze
326
- LINEARH = { index: :all, huffman: :always }.freeze
327
- STATICH = { index: :static, huffman: :always }.freeze
328
- SHORTERH = { index: :all, huffman: :shorter }.freeze
329
-
330
- # Responsible for encoding header key-value pairs using HPACK algorithm.
331
- class Compressor
332
- # @param options [Hash] encoding options
333
- def initialize(**options)
334
- @cc = EncodingContext.new(**options)
335
- end
336
-
337
- # Set dynamic table size in EncodingContext
338
- # @param size [Integer] new dynamic table size
339
- def table_size=(size)
340
- @cc.table_size = size
341
- end
342
-
343
- # Encodes provided value via integer representation.
344
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
345
- #
346
- # If I < 2^N - 1, encode I on N bits
347
- # Else
348
- # encode 2^N - 1 on N bits
349
- # I = I - (2^N - 1)
350
- # While I >= 128
351
- # Encode (I % 128 + 128) on 8 bits
352
- # I = I / 128
353
- # encode (I) on 8 bits
354
- #
355
- # @param i [Integer] value to encode
356
- # @param n [Integer] number of available bits
357
- # @return [String] binary string
358
- def integer(i, n)
359
- limit = 2**n - 1
360
- return [i].pack('C') if i < limit
361
-
362
- bytes = []
363
- bytes.push limit unless n.zero?
364
-
365
- i -= limit
366
- while (i >= 128)
367
- bytes.push((i % 128) + 128)
368
- i /= 128
369
- end
370
-
371
- bytes.push i
372
- bytes.pack('C*')
373
- end
374
-
375
- # Encodes provided value via string literal representation.
376
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
377
- #
378
- # * The string length, defined as the number of bytes needed to store
379
- # its UTF-8 representation, is represented as an integer with a seven
380
- # bits prefix. If the string length is strictly less than 127, it is
381
- # represented as one byte.
382
- # * If the bit 7 of the first byte is 1, the string value is represented
383
- # as a list of Huffman encoded octets
384
- # (padded with bit 1's until next octet boundary).
385
- # * If the bit 7 of the first byte is 0, the string value is
386
- # represented as a list of UTF-8 encoded octets.
387
- #
388
- # +@options [:huffman]+ controls whether to use Huffman encoding:
389
- # :never Do not use Huffman encoding
390
- # :always Always use Huffman encoding
391
- # :shorter Use Huffman when the result is strictly shorter
392
- #
393
- # @param str [String]
394
- # @return [String] binary string
395
- def string(str)
396
- plain, huffman = nil, nil
397
- unless @cc.options[:huffman] == :always
398
- plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY)
399
- end
400
- unless @cc.options[:huffman] == :never
401
- huffman = Huffman.new.encode(str)
402
- huffman = integer(huffman.bytesize, 7) << huffman
403
- huffman.setbyte(0, huffman.ord | 0x80)
404
- end
405
- case @cc.options[:huffman]
406
- when :always
407
- huffman
408
- when :never
409
- plain
410
- else
411
- huffman.bytesize < plain.bytesize ? huffman : plain
412
- end
413
- end
414
-
415
- # Encodes header command with appropriate header representation.
416
- #
417
- # @param h [Hash] header command
418
- # @param buffer [String]
419
- # @return [Buffer]
420
- def header(h, buffer = Buffer.new)
421
- rep = HEADREP[h[:type]]
422
-
423
- case h[:type]
424
- when :indexed
425
- buffer << integer(h[:name] + 1, rep[:prefix])
426
- when :changetablesize
427
- buffer << integer(h[:value], rep[:prefix])
428
- else
429
- if h[:name].is_a? Integer
430
- buffer << integer(h[:name] + 1, rep[:prefix])
431
- else
432
- buffer << integer(0, rep[:prefix])
433
- buffer << string(h[:name])
434
- end
435
-
436
- buffer << string(h[:value])
437
- end
438
-
439
- # set header representation pattern on first byte
440
- fb = buffer.ord | rep[:pattern]
441
- buffer.setbyte(0, fb)
442
-
443
- buffer
444
- end
445
-
446
- # Encodes provided list of HTTP headers.
447
- #
448
- # @param headers [Array] +[[name, value], ...]+
449
- # @return [Buffer]
450
- def encode(headers)
451
- buffer = Buffer.new
452
- pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ':' }
453
- headers = [*pseudo_headers, *regular_headers]
454
- commands = @cc.encode(headers)
455
- commands.each do |cmd|
456
- buffer << header(cmd)
457
- end
458
-
459
- buffer
460
- end
461
- end
462
-
463
- # Responsible for decoding received headers and maintaining compression
464
- # context of the opposing peer. Decompressor must be initialized with
465
- # appropriate starting context based on local role: client or server.
466
- #
467
- # @example
468
- # server_role = Decompressor.new(:request)
469
- # client_role = Decompressor.new(:response)
470
- class Decompressor
471
- # @param options [Hash] decoding options. Only :table_size is effective.
472
- def initialize(**options)
473
- @cc = EncodingContext.new(**options)
474
- end
475
-
476
- # Set dynamic table size in EncodingContext
477
- # @param size [Integer] new dynamic table size
478
- def table_size=(size)
479
- @cc.table_size = size
480
- end
481
-
482
- # Decodes integer value from provided buffer.
483
- #
484
- # @param buf [String]
485
- # @param n [Integer] number of available bits
486
- # @return [Integer]
487
- def integer(buf, n)
488
- limit = 2**n - 1
489
- i = !n.zero? ? (buf.getbyte & limit) : 0
490
-
491
- m = 0
492
- while (byte = buf.getbyte)
493
- i += ((byte & 127) << m)
494
- m += 7
495
-
496
- break if (byte & 128).zero?
497
- end if (i == limit)
498
-
499
- i
500
- end
501
-
502
- # Decodes string value from provided buffer.
503
- #
504
- # @param buf [String]
505
- # @return [String] UTF-8 encoded string
506
- # @raise [CompressionError] when input is malformed
507
- def string(buf)
508
- huffman = (buf.readbyte(0) & 0x80) == 0x80
509
- len = integer(buf, 7)
510
- str = buf.read(len)
511
- fail CompressionError, 'string too short' unless str.bytesize == len
512
- str = Huffman.new.decode(Buffer.new(str)) if huffman
513
- str.force_encoding(Encoding::UTF_8)
514
- end
515
-
516
- # Decodes header command from provided buffer.
517
- #
518
- # @param buf [Buffer]
519
- # @return [Hash] command
520
- def header(buf)
521
- peek = buf.readbyte(0)
522
-
523
- header = {}
524
- header[:type], type = HEADREP.find do |_t, desc|
525
- mask = (peek >> desc[:prefix]) << desc[:prefix]
526
- mask == desc[:pattern]
527
- end
528
-
529
- fail CompressionError unless header[:type]
530
-
531
- header[:name] = integer(buf, type[:prefix])
532
-
533
- case header[:type]
534
- when :indexed
535
- fail CompressionError if (header[:name]).zero?
536
- header[:name] -= 1
537
- when :changetablesize
538
- header[:value] = header[:name]
539
- else
540
- if (header[:name]).zero?
541
- header[:name] = string(buf)
542
- else
543
- header[:name] -= 1
544
- end
545
- header[:value] = string(buf)
546
- end
547
-
548
- header
549
- end
550
-
551
- # Decodes and processes header commands within provided buffer.
552
- #
553
- # @param buf [Buffer]
554
- # @return [Array] +[[name, value], ...]+
555
- def decode(buf)
556
- list = []
557
- decoding_pseudo_headers = true
558
- until buf.empty?
559
- next_header = @cc.process(header(buf))
560
- next if next_header.nil?
561
- is_pseudo_header = next_header.first.start_with? ':'
562
- if !decoding_pseudo_headers && is_pseudo_header
563
- fail ProtocolError, 'one or more pseudo headers encountered after regular headers'
564
- end
565
- decoding_pseudo_headers = is_pseudo_header
566
- list << next_header
567
- end
568
- list
569
- end
570
- end
571
- end
572
- end