http-hpack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,154 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # Copyrigh, 2013, by Ilya Grigorik.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require_relative 'context'
23
+ require_relative 'huffman'
24
+
25
+ module HTTP
26
+ module HPACK
27
+ # Responsible for decoding received headers and maintaining compression
28
+ # context of the opposing peer. Decompressor must be initialized with
29
+ # appropriate starting context based on local role: client or server.
30
+ class Decompressor
31
+ def initialize(buffer, context = Context.new)
32
+ @buffer = buffer
33
+ @context = context
34
+ @offset = 0
35
+ end
36
+
37
+ attr :buffer
38
+ attr :context
39
+ attr :offset
40
+
41
+ def end?
42
+ @offset >= @buffer.bytesize
43
+ end
44
+
45
+ def read_byte
46
+ if byte = @buffer.getbyte(@offset)
47
+ @offset += 1
48
+ end
49
+
50
+ return byte
51
+ end
52
+
53
+
54
+ def peek_byte
55
+ @buffer.getbyte(@offset)
56
+ end
57
+
58
+ def read_bytes(length)
59
+ slice = @buffer.byteslice(@offset, length)
60
+
61
+ @offset += length
62
+
63
+ return slice
64
+ end
65
+
66
+ # Decodes integer value from provided buffer.
67
+ #
68
+ # @param bits [Integer] number of available bits
69
+ # @return [Integer]
70
+ def read_integer(bits)
71
+ limit = 2**bits - 1
72
+ value = !bits.zero? ? (read_byte & limit) : 0
73
+
74
+ shift = 0
75
+
76
+ while byte = read_byte
77
+ value += ((byte & 127) << shift)
78
+ shift += 7
79
+
80
+ break if (byte & 128).zero?
81
+ end if (value == limit)
82
+
83
+ return value
84
+ end
85
+
86
+ # Decodes string value from provided buffer.
87
+ #
88
+ # @return [String] UTF-8 encoded string
89
+ # @raise [CompressionError] when input is malformed
90
+ def read_string
91
+ huffman = (peek_byte & 0x80) == 0x80
92
+
93
+ length = read_integer(7)
94
+ string = read_bytes(length)
95
+
96
+ raise CompressionError, "Invalid string length, got #{string.bytesize}, expecting #{length}!" unless string.bytesize == length
97
+
98
+ string = Huffman.new.decode(string) if huffman
99
+
100
+ return string.force_encoding(Encoding::UTF_8)
101
+ end
102
+
103
+ # Decodes header command from provided buffer.
104
+ #
105
+ # @param buffer [Buffer]
106
+ # @return [Hash] command
107
+ def read_header
108
+ pattern = peek_byte
109
+
110
+ header = {}
111
+ header[:type], type = HEADER_REPRESENTATION.find do |_t, desc|
112
+ mask = (pattern >> desc[:prefix]) << desc[:prefix]
113
+ mask == desc[:pattern]
114
+ end
115
+
116
+ raise CompressionError unless header[:type]
117
+
118
+ header[:name] = read_integer(type[:prefix])
119
+
120
+ case header[:type]
121
+ when :indexed
122
+ raise CompressionError if header[:name].zero?
123
+ header[:name] -= 1
124
+ when :changetablesize
125
+ header[:value] = header[:name]
126
+ else
127
+ if (header[:name]).zero?
128
+ header[:name] = read_string
129
+ else
130
+ header[:name] -= 1
131
+ end
132
+
133
+ header[:value] = read_string
134
+ end
135
+
136
+ return header
137
+ end
138
+
139
+ # Decodes and processes header commands within provided buffer.
140
+ #
141
+ # @param buffer [Buffer]
142
+ # @return [Array] +[[name, value], ...]+
143
+ def decode
144
+ list = []
145
+
146
+ while !end?
147
+ list << @context.decode(read_header)
148
+ end
149
+
150
+ list.compact
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,343 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # Copyrigh, 2013, by Ilya Grigorik.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require_relative 'huffman/machine'
23
+
24
+ module HTTP
25
+ module HPACK
26
+ class CompressionError < RuntimeError
27
+ end
28
+
29
+ # Implementation of huffman encoding for HPACK
30
+ #
31
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
32
+ class Huffman
33
+ BITS_AT_ONCE = 4
34
+ EOS = 256
35
+
36
+ # Encodes provided value via huffman encoding.
37
+ # Length is not encoded in this method.
38
+ #
39
+ # @param str [String]
40
+ # @return [String] binary string
41
+ def encode(str)
42
+ bitstring = str.each_byte.map {|chr| ENCODE_TABLE[chr]}.join
43
+ bitstring << '1' * ((8 - bitstring.size) % 8)
44
+ [bitstring].pack('B*')
45
+ end
46
+
47
+ # Decodes provided Huffman coded string.
48
+ #
49
+ # @param buf [Buffer]
50
+ # @return [String] binary string
51
+ # @raise [CompressionError] when Huffman coded string is malformed
52
+ def decode(buf)
53
+ emit = ''
54
+ state = 0 # start state
55
+
56
+ mask = (1 << BITS_AT_ONCE) - 1
57
+ buf.each_byte do |chr|
58
+ (8 / BITS_AT_ONCE - 1).downto(0) do |shift|
59
+ branch = (chr >> (shift * BITS_AT_ONCE)) & mask
60
+ # MACHINE[state] = [final, [transitions]]
61
+ # [final] unfinished bits so far are prefix of the EOS code.
62
+ # Each transition is [emit, next]
63
+ # [emit] character to be emitted on this transition, empty string, or EOS.
64
+ # [next] next state number.
65
+ trans = MACHINE[state][branch]
66
+ raise CompressionError, 'Huffman decode error (EOS found)' if trans.first == EOS
67
+ emit << trans.first.chr if trans.first
68
+ state = trans.last
69
+ end
70
+ end
71
+ # Check whether partial input is correctly filled
72
+ unless state <= MAX_FINAL_STATE
73
+ raise CompressionError, 'Huffman decode error (EOS invalid)'
74
+ end
75
+ emit.force_encoding(Encoding::BINARY)
76
+ end
77
+
78
+ # Huffman table as specified in
79
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#appendix-B
80
+ CODES = [
81
+ [0x1ff8, 13],
82
+ [0x7fffd8, 23],
83
+ [0xfffffe2, 28],
84
+ [0xfffffe3, 28],
85
+ [0xfffffe4, 28],
86
+ [0xfffffe5, 28],
87
+ [0xfffffe6, 28],
88
+ [0xfffffe7, 28],
89
+ [0xfffffe8, 28],
90
+ [0xffffea, 24],
91
+ [0x3ffffffc, 30],
92
+ [0xfffffe9, 28],
93
+ [0xfffffea, 28],
94
+ [0x3ffffffd, 30],
95
+ [0xfffffeb, 28],
96
+ [0xfffffec, 28],
97
+ [0xfffffed, 28],
98
+ [0xfffffee, 28],
99
+ [0xfffffef, 28],
100
+ [0xffffff0, 28],
101
+ [0xffffff1, 28],
102
+ [0xffffff2, 28],
103
+ [0x3ffffffe, 30],
104
+ [0xffffff3, 28],
105
+ [0xffffff4, 28],
106
+ [0xffffff5, 28],
107
+ [0xffffff6, 28],
108
+ [0xffffff7, 28],
109
+ [0xffffff8, 28],
110
+ [0xffffff9, 28],
111
+ [0xffffffa, 28],
112
+ [0xffffffb, 28],
113
+ [0x14, 6],
114
+ [0x3f8, 10],
115
+ [0x3f9, 10],
116
+ [0xffa, 12],
117
+ [0x1ff9, 13],
118
+ [0x15, 6],
119
+ [0xf8, 8],
120
+ [0x7fa, 11],
121
+ [0x3fa, 10],
122
+ [0x3fb, 10],
123
+ [0xf9, 8],
124
+ [0x7fb, 11],
125
+ [0xfa, 8],
126
+ [0x16, 6],
127
+ [0x17, 6],
128
+ [0x18, 6],
129
+ [0x0, 5],
130
+ [0x1, 5],
131
+ [0x2, 5],
132
+ [0x19, 6],
133
+ [0x1a, 6],
134
+ [0x1b, 6],
135
+ [0x1c, 6],
136
+ [0x1d, 6],
137
+ [0x1e, 6],
138
+ [0x1f, 6],
139
+ [0x5c, 7],
140
+ [0xfb, 8],
141
+ [0x7ffc, 15],
142
+ [0x20, 6],
143
+ [0xffb, 12],
144
+ [0x3fc, 10],
145
+ [0x1ffa, 13],
146
+ [0x21, 6],
147
+ [0x5d, 7],
148
+ [0x5e, 7],
149
+ [0x5f, 7],
150
+ [0x60, 7],
151
+ [0x61, 7],
152
+ [0x62, 7],
153
+ [0x63, 7],
154
+ [0x64, 7],
155
+ [0x65, 7],
156
+ [0x66, 7],
157
+ [0x67, 7],
158
+ [0x68, 7],
159
+ [0x69, 7],
160
+ [0x6a, 7],
161
+ [0x6b, 7],
162
+ [0x6c, 7],
163
+ [0x6d, 7],
164
+ [0x6e, 7],
165
+ [0x6f, 7],
166
+ [0x70, 7],
167
+ [0x71, 7],
168
+ [0x72, 7],
169
+ [0xfc, 8],
170
+ [0x73, 7],
171
+ [0xfd, 8],
172
+ [0x1ffb, 13],
173
+ [0x7fff0, 19],
174
+ [0x1ffc, 13],
175
+ [0x3ffc, 14],
176
+ [0x22, 6],
177
+ [0x7ffd, 15],
178
+ [0x3, 5],
179
+ [0x23, 6],
180
+ [0x4, 5],
181
+ [0x24, 6],
182
+ [0x5, 5],
183
+ [0x25, 6],
184
+ [0x26, 6],
185
+ [0x27, 6],
186
+ [0x6, 5],
187
+ [0x74, 7],
188
+ [0x75, 7],
189
+ [0x28, 6],
190
+ [0x29, 6],
191
+ [0x2a, 6],
192
+ [0x7, 5],
193
+ [0x2b, 6],
194
+ [0x76, 7],
195
+ [0x2c, 6],
196
+ [0x8, 5],
197
+ [0x9, 5],
198
+ [0x2d, 6],
199
+ [0x77, 7],
200
+ [0x78, 7],
201
+ [0x79, 7],
202
+ [0x7a, 7],
203
+ [0x7b, 7],
204
+ [0x7ffe, 15],
205
+ [0x7fc, 11],
206
+ [0x3ffd, 14],
207
+ [0x1ffd, 13],
208
+ [0xffffffc, 28],
209
+ [0xfffe6, 20],
210
+ [0x3fffd2, 22],
211
+ [0xfffe7, 20],
212
+ [0xfffe8, 20],
213
+ [0x3fffd3, 22],
214
+ [0x3fffd4, 22],
215
+ [0x3fffd5, 22],
216
+ [0x7fffd9, 23],
217
+ [0x3fffd6, 22],
218
+ [0x7fffda, 23],
219
+ [0x7fffdb, 23],
220
+ [0x7fffdc, 23],
221
+ [0x7fffdd, 23],
222
+ [0x7fffde, 23],
223
+ [0xffffeb, 24],
224
+ [0x7fffdf, 23],
225
+ [0xffffec, 24],
226
+ [0xffffed, 24],
227
+ [0x3fffd7, 22],
228
+ [0x7fffe0, 23],
229
+ [0xffffee, 24],
230
+ [0x7fffe1, 23],
231
+ [0x7fffe2, 23],
232
+ [0x7fffe3, 23],
233
+ [0x7fffe4, 23],
234
+ [0x1fffdc, 21],
235
+ [0x3fffd8, 22],
236
+ [0x7fffe5, 23],
237
+ [0x3fffd9, 22],
238
+ [0x7fffe6, 23],
239
+ [0x7fffe7, 23],
240
+ [0xffffef, 24],
241
+ [0x3fffda, 22],
242
+ [0x1fffdd, 21],
243
+ [0xfffe9, 20],
244
+ [0x3fffdb, 22],
245
+ [0x3fffdc, 22],
246
+ [0x7fffe8, 23],
247
+ [0x7fffe9, 23],
248
+ [0x1fffde, 21],
249
+ [0x7fffea, 23],
250
+ [0x3fffdd, 22],
251
+ [0x3fffde, 22],
252
+ [0xfffff0, 24],
253
+ [0x1fffdf, 21],
254
+ [0x3fffdf, 22],
255
+ [0x7fffeb, 23],
256
+ [0x7fffec, 23],
257
+ [0x1fffe0, 21],
258
+ [0x1fffe1, 21],
259
+ [0x3fffe0, 22],
260
+ [0x1fffe2, 21],
261
+ [0x7fffed, 23],
262
+ [0x3fffe1, 22],
263
+ [0x7fffee, 23],
264
+ [0x7fffef, 23],
265
+ [0xfffea, 20],
266
+ [0x3fffe2, 22],
267
+ [0x3fffe3, 22],
268
+ [0x3fffe4, 22],
269
+ [0x7ffff0, 23],
270
+ [0x3fffe5, 22],
271
+ [0x3fffe6, 22],
272
+ [0x7ffff1, 23],
273
+ [0x3ffffe0, 26],
274
+ [0x3ffffe1, 26],
275
+ [0xfffeb, 20],
276
+ [0x7fff1, 19],
277
+ [0x3fffe7, 22],
278
+ [0x7ffff2, 23],
279
+ [0x3fffe8, 22],
280
+ [0x1ffffec, 25],
281
+ [0x3ffffe2, 26],
282
+ [0x3ffffe3, 26],
283
+ [0x3ffffe4, 26],
284
+ [0x7ffffde, 27],
285
+ [0x7ffffdf, 27],
286
+ [0x3ffffe5, 26],
287
+ [0xfffff1, 24],
288
+ [0x1ffffed, 25],
289
+ [0x7fff2, 19],
290
+ [0x1fffe3, 21],
291
+ [0x3ffffe6, 26],
292
+ [0x7ffffe0, 27],
293
+ [0x7ffffe1, 27],
294
+ [0x3ffffe7, 26],
295
+ [0x7ffffe2, 27],
296
+ [0xfffff2, 24],
297
+ [0x1fffe4, 21],
298
+ [0x1fffe5, 21],
299
+ [0x3ffffe8, 26],
300
+ [0x3ffffe9, 26],
301
+ [0xffffffd, 28],
302
+ [0x7ffffe3, 27],
303
+ [0x7ffffe4, 27],
304
+ [0x7ffffe5, 27],
305
+ [0xfffec, 20],
306
+ [0xfffff3, 24],
307
+ [0xfffed, 20],
308
+ [0x1fffe6, 21],
309
+ [0x3fffe9, 22],
310
+ [0x1fffe7, 21],
311
+ [0x1fffe8, 21],
312
+ [0x7ffff3, 23],
313
+ [0x3fffea, 22],
314
+ [0x3fffeb, 22],
315
+ [0x1ffffee, 25],
316
+ [0x1ffffef, 25],
317
+ [0xfffff4, 24],
318
+ [0xfffff5, 24],
319
+ [0x3ffffea, 26],
320
+ [0x7ffff4, 23],
321
+ [0x3ffffeb, 26],
322
+ [0x7ffffe6, 27],
323
+ [0x3ffffec, 26],
324
+ [0x3ffffed, 26],
325
+ [0x7ffffe7, 27],
326
+ [0x7ffffe8, 27],
327
+ [0x7ffffe9, 27],
328
+ [0x7ffffea, 27],
329
+ [0x7ffffeb, 27],
330
+ [0xffffffe, 28],
331
+ [0x7ffffec, 27],
332
+ [0x7ffffed, 27],
333
+ [0x7ffffee, 27],
334
+ [0x7ffffef, 27],
335
+ [0x7fffff0, 27],
336
+ [0x3ffffee, 26],
337
+ [0x3fffffff, 30],
338
+ ].each(&:freeze).freeze
339
+
340
+ ENCODE_TABLE = CODES.map {|c, l| [c].pack('N').unpack('B*').first[-l..-1]}.each(&:freeze).freeze
341
+ end
342
+ end
343
+ end