http-2-next 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/http/2/next/base64.rb +8 -0
- data/lib/http/2/next/extensions.rb +29 -0
- data/lib/http/2/next/framer.rb +36 -36
- data/lib/http/2/next/header/compressor.rb +37 -17
- data/lib/http/2/next/header/huffman.rb +1 -0
- data/lib/http/2/next/server.rb +8 -6
- data/lib/http/2/next/version.rb +1 -1
- data/sig/extensions.rbs +5 -0
- data/sig/framer.rbs +4 -2
- data/sig/header/compressor.rbs +9 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a97513d9cfdae554f85ed0cbd3d0818ae0c3c6dc5bdf418a126d3a1993f1c52
|
4
|
+
data.tar.gz: a8712f76a43721ca5d67f281d2689853f7d6cf4ca224704e500c34ae03e10a53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 303e98009c8a47229747659e4dfc4bd33636a52de907bc1164f1f33b4a4ba4a0e051459e30853cae3e88a5fdbb9b84d7e6fbbe387b51f8870e16d7923a28e757
|
7
|
+
data.tar.gz: c40a73c72fe006f2a371dda23adb7ba09dc11e791a4b7a7781a6e5e4593bbfd75e4a5a06e8e6b7755c1335857c84b3077430b6261337e039578f3978937f9e82
|
data/lib/http/2/next/base64.rb
CHANGED
@@ -16,6 +16,14 @@ elsif !defined?(Base64)
|
|
16
16
|
str.unpack1("m")
|
17
17
|
end
|
18
18
|
|
19
|
+
def strict_encode64(bin)
|
20
|
+
[bin].pack("m0")
|
21
|
+
end
|
22
|
+
|
23
|
+
def strict_decode64(str)
|
24
|
+
str.unpack1("m0")
|
25
|
+
end
|
26
|
+
|
19
27
|
def urlsafe_encode64(bin, padding: true)
|
20
28
|
str = strict_encode64(bin)
|
21
29
|
str.chomp!("==") or str.chomp!("=") unless padding
|
@@ -21,4 +21,33 @@ module HTTP2Next
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
# this mixin handles backwards-compatibility for the new packing options
|
26
|
+
# shipping with ruby 3.3 (see https://docs.ruby-lang.org/en/3.3/packed_data_rdoc.html)
|
27
|
+
module PackingExtensions
|
28
|
+
if RUBY_VERSION < "3.3.0"
|
29
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
30
|
+
packed_str = array_to_pack.pack(template)
|
31
|
+
case offset
|
32
|
+
when -1
|
33
|
+
buffer << packed_str
|
34
|
+
when 0
|
35
|
+
buffer.prepend(packed_str)
|
36
|
+
else
|
37
|
+
buffer.insert(offset, packed_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
42
|
+
case offset
|
43
|
+
when -1
|
44
|
+
array_to_pack.pack(template, buffer: buffer)
|
45
|
+
when 0
|
46
|
+
buffer.prepend(array_to_pack.pack(template))
|
47
|
+
else
|
48
|
+
buffer.insert(offset, array_to_pack.pack(template))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
24
53
|
end
|
data/lib/http/2/next/framer.rb
CHANGED
@@ -7,6 +7,7 @@ module HTTP2Next
|
|
7
7
|
using StringExtensions
|
8
8
|
|
9
9
|
include Error
|
10
|
+
include PackingExtensions
|
10
11
|
|
11
12
|
# Default value of max frame size (16384 bytes)
|
12
13
|
DEFAULT_MAX_FRAME_SIZE = 2 << 13
|
@@ -123,10 +124,9 @@ module HTTP2Next
|
|
123
124
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
|
124
125
|
#
|
125
126
|
# @param frame [Hash]
|
127
|
+
# @param buffer [String] buffer to pack bytes into
|
126
128
|
# @return [String]
|
127
|
-
def common_header(frame)
|
128
|
-
header = []
|
129
|
-
|
129
|
+
def common_header(frame, buffer:)
|
130
130
|
raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
|
131
131
|
|
132
132
|
raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @remote_max_frame_size
|
@@ -139,18 +139,18 @@ module HTTP2Next
|
|
139
139
|
raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
|
140
140
|
end
|
141
141
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
142
|
+
pack([
|
143
|
+
(frame[:length] >> FRAME_LENGTH_HISHIFT),
|
144
|
+
(frame[:length] & FRAME_LENGTH_LOMASK),
|
145
|
+
FRAME_TYPES[frame[:type]],
|
146
|
+
frame[:flags].reduce(0) do |acc, f|
|
147
|
+
position = FRAME_FLAGS[frame[:type]][f]
|
148
|
+
raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
|
149
|
+
|
150
|
+
acc | (1 << position)
|
151
|
+
end,
|
152
|
+
frame[:stream]
|
153
|
+
], HEADERPACK, buffer: buffer, offset: 0) # 8+16,8,8,32
|
154
154
|
end
|
155
155
|
|
156
156
|
# Decodes common 9-byte header.
|
@@ -200,8 +200,8 @@ module HTTP2Next
|
|
200
200
|
end
|
201
201
|
|
202
202
|
if frame[:flags].include? :priority
|
203
|
-
|
204
|
-
|
203
|
+
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
204
|
+
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
205
205
|
length += 5
|
206
206
|
end
|
207
207
|
|
@@ -213,12 +213,12 @@ module HTTP2Next
|
|
213
213
|
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
214
214
|
end
|
215
215
|
|
216
|
-
|
217
|
-
|
216
|
+
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
217
|
+
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
218
218
|
length += 5
|
219
219
|
|
220
220
|
when :rst_stream
|
221
|
-
|
221
|
+
pack_error(frame[:error], buffer: bytes)
|
222
222
|
length += 4
|
223
223
|
|
224
224
|
when :settings
|
@@ -233,13 +233,13 @@ module HTTP2Next
|
|
233
233
|
raise CompressionError, "Unknown settings ID for #{k}" if k.nil?
|
234
234
|
end
|
235
235
|
|
236
|
-
|
237
|
-
|
236
|
+
pack([k], UINT16, buffer: bytes)
|
237
|
+
pack([v], UINT32, buffer: bytes)
|
238
238
|
length += 6
|
239
239
|
end
|
240
240
|
|
241
241
|
when :push_promise
|
242
|
-
|
242
|
+
pack([frame[:promise_stream] & RBIT], UINT32, buffer: bytes)
|
243
243
|
bytes << frame[:payload]
|
244
244
|
length += 4 + frame[:payload].bytesize
|
245
245
|
|
@@ -250,8 +250,8 @@ module HTTP2Next
|
|
250
250
|
length += 8
|
251
251
|
|
252
252
|
when :goaway
|
253
|
-
|
254
|
-
|
253
|
+
pack([frame[:last_stream] & RBIT], UINT32, buffer: bytes)
|
254
|
+
pack_error(frame[:error], buffer: bytes)
|
255
255
|
length += 8
|
256
256
|
|
257
257
|
if frame[:payload]
|
@@ -260,7 +260,7 @@ module HTTP2Next
|
|
260
260
|
end
|
261
261
|
|
262
262
|
when :window_update
|
263
|
-
|
263
|
+
pack([frame[:increment] & RBIT], UINT32, buffer: bytes)
|
264
264
|
length += 4
|
265
265
|
|
266
266
|
when :continuation
|
@@ -268,26 +268,26 @@ module HTTP2Next
|
|
268
268
|
length += frame[:payload].bytesize
|
269
269
|
|
270
270
|
when :altsvc
|
271
|
-
|
271
|
+
pack([frame[:max_age], frame[:port]], UINT32 + UINT16, buffer: bytes)
|
272
272
|
length += 6
|
273
273
|
if frame[:proto]
|
274
274
|
raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
|
275
275
|
|
276
|
-
|
276
|
+
pack([frame[:proto].bytesize], UINT8, buffer: bytes)
|
277
277
|
bytes << frame[:proto]
|
278
278
|
length += 1 + frame[:proto].bytesize
|
279
279
|
else
|
280
|
-
|
280
|
+
pack([0], UINT8, buffer: bytes)
|
281
281
|
length += 1
|
282
282
|
end
|
283
283
|
if frame[:host]
|
284
284
|
raise CompressionError, "Host too long" if frame[:host].bytesize > 255
|
285
285
|
|
286
|
-
|
286
|
+
pack([frame[:host].bytesize], UINT8, buffer: bytes)
|
287
287
|
bytes << frame[:host]
|
288
288
|
length += 1 + frame[:host].bytesize
|
289
289
|
else
|
290
|
-
|
290
|
+
pack([0], UINT8, buffer: bytes)
|
291
291
|
length += 1
|
292
292
|
end
|
293
293
|
if frame[:origin]
|
@@ -297,7 +297,7 @@ module HTTP2Next
|
|
297
297
|
|
298
298
|
when :origin
|
299
299
|
frame[:payload].each do |origin|
|
300
|
-
|
300
|
+
pack([origin.bytesize], UINT16, buffer: bytes)
|
301
301
|
bytes << origin
|
302
302
|
length += 2 + origin.bytesize
|
303
303
|
end
|
@@ -319,7 +319,7 @@ module HTTP2Next
|
|
319
319
|
end
|
320
320
|
|
321
321
|
length += padlen
|
322
|
-
|
322
|
+
pack([padlen -= 1], UINT8, buffer: bytes, offset: 0)
|
323
323
|
frame[:flags] << :padded
|
324
324
|
|
325
325
|
# Padding: Padding octets that contain no application semantic value.
|
@@ -329,7 +329,7 @@ module HTTP2Next
|
|
329
329
|
end
|
330
330
|
|
331
331
|
frame[:length] = length
|
332
|
-
|
332
|
+
common_header(frame, buffer: bytes)
|
333
333
|
end
|
334
334
|
|
335
335
|
# Decodes complete HTTP/2 frame from provided buffer. If the buffer
|
@@ -456,14 +456,14 @@ module HTTP2Next
|
|
456
456
|
|
457
457
|
private
|
458
458
|
|
459
|
-
def pack_error(error)
|
459
|
+
def pack_error(error, buffer:)
|
460
460
|
unless error.is_a? Integer
|
461
461
|
error = DEFINED_ERRORS[error]
|
462
462
|
|
463
463
|
raise CompressionError, "Unknown error ID for #{error}" unless error
|
464
464
|
end
|
465
465
|
|
466
|
-
[error]
|
466
|
+
pack([error], UINT32, buffer: buffer)
|
467
467
|
end
|
468
468
|
|
469
469
|
def unpack_error(error)
|
@@ -4,6 +4,8 @@ module HTTP2Next
|
|
4
4
|
module Header
|
5
5
|
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
6
6
|
class Compressor
|
7
|
+
include PackingExtensions
|
8
|
+
|
7
9
|
# @param options [Hash] encoding options
|
8
10
|
def initialize(options = {})
|
9
11
|
@cc = EncodingContext.new(options)
|
@@ -29,10 +31,12 @@ module HTTP2Next
|
|
29
31
|
#
|
30
32
|
# @param i [Integer] value to encode
|
31
33
|
# @param n [Integer] number of available bits
|
34
|
+
# @param buffer [String] buffer to pack bytes into
|
35
|
+
# @param offset [Integer] offset to insert packed bytes in buffer
|
32
36
|
# @return [String] binary string
|
33
|
-
def integer(i, n)
|
37
|
+
def integer(i, n, buffer:, offset: 0)
|
34
38
|
limit = (2**n) - 1
|
35
|
-
return [i]
|
39
|
+
return pack([i], "C", buffer: buffer, offset: offset) if i < limit
|
36
40
|
|
37
41
|
bytes = []
|
38
42
|
bytes.push limit unless n.zero?
|
@@ -44,7 +48,7 @@ module HTTP2Next
|
|
44
48
|
end
|
45
49
|
|
46
50
|
bytes.push i
|
47
|
-
|
51
|
+
pack(bytes, "C*", buffer: buffer, offset: offset)
|
48
52
|
end
|
49
53
|
|
50
54
|
# Encodes provided value via string literal representation.
|
@@ -68,20 +72,16 @@ module HTTP2Next
|
|
68
72
|
# @param str [String]
|
69
73
|
# @return [String] binary string
|
70
74
|
def string(str)
|
71
|
-
plain = nil
|
72
|
-
huffman = nil
|
73
|
-
plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY) unless @cc.options[:huffman] == :always
|
74
|
-
unless @cc.options[:huffman] == :never
|
75
|
-
huffman = Huffman.new.encode(str)
|
76
|
-
huffman = integer(huffman.bytesize, 7) << huffman
|
77
|
-
huffman.setbyte(0, huffman.ord | 0x80)
|
78
|
-
end
|
79
75
|
case @cc.options[:huffman]
|
80
76
|
when :always
|
81
|
-
|
77
|
+
huffman_string(str)
|
82
78
|
when :never
|
83
|
-
|
79
|
+
plain_string(str)
|
84
80
|
else
|
81
|
+
huffman = huffman_string(str)
|
82
|
+
|
83
|
+
plain = plain_string(str)
|
84
|
+
|
85
85
|
huffman.bytesize < plain.bytesize ? huffman : plain
|
86
86
|
end
|
87
87
|
end
|
@@ -96,14 +96,14 @@ module HTTP2Next
|
|
96
96
|
|
97
97
|
case h[:type]
|
98
98
|
when :indexed
|
99
|
-
|
99
|
+
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
100
100
|
when :changetablesize
|
101
|
-
|
101
|
+
integer(h[:value], rep[:prefix], buffer: buffer)
|
102
102
|
else
|
103
103
|
if h[:name].is_a? Integer
|
104
|
-
|
104
|
+
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
105
105
|
else
|
106
|
-
|
106
|
+
integer(0, rep[:prefix], buffer: buffer)
|
107
107
|
buffer << string(h[:name])
|
108
108
|
end
|
109
109
|
|
@@ -132,6 +132,26 @@ module HTTP2Next
|
|
132
132
|
|
133
133
|
buffer
|
134
134
|
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# @param str [String]
|
139
|
+
# @return [String] binary string
|
140
|
+
def huffman_string(str)
|
141
|
+
huffman = Huffman.new.encode(str)
|
142
|
+
integer(huffman.bytesize, 7, buffer: huffman, offset: 0)
|
143
|
+
huffman.setbyte(0, huffman.ord | 0x80)
|
144
|
+
huffman
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param str [String]
|
148
|
+
# @return [String] binary string
|
149
|
+
def plain_string(str)
|
150
|
+
plain = "".b
|
151
|
+
integer(str.bytesize, 7, buffer: plain)
|
152
|
+
plain << str.dup.force_encoding(Encoding::BINARY)
|
153
|
+
plain
|
154
|
+
end
|
135
155
|
end
|
136
156
|
end
|
137
157
|
end
|
data/lib/http/2/next/server.rb
CHANGED
@@ -80,13 +80,15 @@ module HTTP2Next
|
|
80
80
|
# Process received HTTP2-Settings payload
|
81
81
|
buf = "".b
|
82
82
|
buf << Base64.urlsafe_decode64(settings.to_s)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
@framer.common_header(
|
84
|
+
{
|
85
|
+
length: buf.bytesize,
|
86
|
+
type: :settings,
|
87
|
+
stream: 0,
|
88
|
+
flags: []
|
89
|
+
},
|
90
|
+
buffer: buf
|
88
91
|
)
|
89
|
-
buf.prepend(header)
|
90
92
|
receive(buf)
|
91
93
|
|
92
94
|
# Activate stream (id: 1) with on HTTP/1.1 request parameters
|
data/lib/http/2/next/version.rb
CHANGED
data/sig/extensions.rbs
ADDED
data/sig/framer.rbs
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module HTTP2Next
|
2
2
|
class Framer
|
3
|
+
include PackingExtensions
|
4
|
+
|
3
5
|
DEFAULT_MAX_FRAME_SIZE: Integer
|
4
6
|
|
5
7
|
MAX_STREAM_ID: Integer
|
@@ -33,7 +35,7 @@ module HTTP2Next
|
|
33
35
|
|
34
36
|
attr_accessor remote_max_frame_size: Integer
|
35
37
|
|
36
|
-
def common_header: (frame) -> String
|
38
|
+
def common_header: (frame, buffer: String) -> String
|
37
39
|
|
38
40
|
def read_common_frame: (String) -> frame
|
39
41
|
|
@@ -45,7 +47,7 @@ module HTTP2Next
|
|
45
47
|
|
46
48
|
def initialize: (?Integer local_max_frame_size, ?Integer remote_max_frame_size) -> untyped
|
47
49
|
|
48
|
-
def pack_error: (Integer | Symbol error) -> String
|
50
|
+
def pack_error: (Integer | Symbol error, buffer: String) -> String
|
49
51
|
|
50
52
|
def unpack_error: (Integer) -> (Symbol | Integer)
|
51
53
|
end
|
data/sig/header/compressor.rbs
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
module HTTP2Next
|
2
2
|
module Header
|
3
3
|
class Compressor
|
4
|
+
include PackingExtensions
|
5
|
+
|
4
6
|
@cc: EncodingContext
|
5
7
|
|
6
8
|
def table_size=: (Integer) -> void
|
7
9
|
|
8
|
-
def integer: (Integer, Integer) -> String
|
10
|
+
def integer: (Integer, Integer, buffer: String, ?offset: Integer) -> String
|
9
11
|
|
10
12
|
def string: (String) -> String
|
11
13
|
|
12
|
-
def header: (header_command, String) -> String
|
13
|
-
| (header_command) -> String
|
14
|
+
def header: (header_command, ?String) -> String
|
14
15
|
|
15
16
|
def encode: (Enumerable[header_pair]) -> String
|
16
17
|
|
17
18
|
private
|
18
19
|
|
19
|
-
def initialize: (context_hash options) ->
|
20
|
-
|
20
|
+
def initialize: (?context_hash options) -> void
|
21
|
+
|
22
|
+
def huffman_string: (String str) -> String
|
23
|
+
|
24
|
+
def plain_string: (String str) -> String
|
21
25
|
end
|
22
26
|
end
|
23
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http-2-next
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-01-11 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Pure-ruby HTTP 2.0 protocol implementation
|
16
16
|
email:
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- sig/connection.rbs
|
44
44
|
- sig/emitter.rbs
|
45
45
|
- sig/error.rbs
|
46
|
+
- sig/extensions.rbs
|
46
47
|
- sig/flow_buffer.rbs
|
47
48
|
- sig/frame_buffer.rbs
|
48
49
|
- sig/framer.rbs
|