http-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,312 @@
1
+
2
+ module HttpParser
3
+ HTTP_MAX_HEADER_SIZE = (80 * 1024)
4
+
5
+ #
6
+ # These share a byte of data as a bitmap
7
+ #
8
+ TYPES = enum :http_parser_type, [
9
+ :request, 0,
10
+ :response,
11
+ :both
12
+ ]
13
+ FLAG = {
14
+ :CHUNKED => 1 << 2,
15
+ :CONNECTION_KEEP_ALIVE => 1 << 3,
16
+ :CONNECTION_CLOSE => 1 << 4,
17
+ :TRAILING => 1 << 5,
18
+ :UPGRADE => 1 << 6,
19
+ :SKIPBODY => 1 << 7
20
+ }
21
+
22
+ #
23
+ # Request Methods
24
+ #
25
+ METHODS = enum :http_method, [
26
+ :DELETE, 0,
27
+ :GET,
28
+ :HEAD,
29
+ :POST,
30
+ :PUT,
31
+ # pathological
32
+ :CONNECT,
33
+ :OPTIONS,
34
+ :TRACE,
35
+ # webdav
36
+ :COPY,
37
+ :LOCK,
38
+ :MKCOL,
39
+ :MOVE,
40
+ :PROPFIND,
41
+ :PROPPATCH,
42
+ :SEARCH,
43
+ :UNLOCK,
44
+ # subversion
45
+ :REPORT,
46
+ :MKACTIVITY,
47
+ :CHECKOUT,
48
+ :MERGE,
49
+ # upnp
50
+ :MSEARCH,
51
+ :NOTIFY,
52
+ :SUBSCRIBE,
53
+ :UNSUBSCRIBE,
54
+ # RFC-5789
55
+ :PATCH,
56
+ :PURGE
57
+ ]
58
+
59
+
60
+ UrlFields = enum :http_parser_url_fields, [
61
+ :SCHEMA, 0,
62
+ :HOST,
63
+ :PORT,
64
+ :PATH,
65
+ :QUERY,
66
+ :FRAGMENT,
67
+ :USERINFO,
68
+ :MAX
69
+ ]
70
+
71
+
72
+ #
73
+ # Effectively this represents a request instance
74
+ #
75
+ class Instance < FFI::Struct
76
+ layout :type_flags, :uchar,
77
+ :state, :uchar,
78
+ :header_state, :uchar,
79
+ :index, :uchar,
80
+
81
+ :nread, :uint32,
82
+ :content_length, :int64,
83
+
84
+ # READ-ONLY
85
+ :http_major, :ushort,
86
+ :http_minor, :ushort,
87
+ :status_code, :ushort, # responses only
88
+ :method, :uchar, # requests only
89
+ :error_upgrade, :uchar, # errno = first 7bits, upgrade = last bit
90
+
91
+ # PUBLIC
92
+ :data, :pointer
93
+
94
+
95
+ def initialize(ptr = nil)
96
+ if ptr then super(ptr)
97
+ else
98
+ super()
99
+ self.type = :both
100
+ end
101
+
102
+ yield self if block_given?
103
+
104
+ ::HttpParser.http_parser_init(self, self.type) unless ptr
105
+ end
106
+
107
+ #
108
+ # Resets the parser.
109
+ #
110
+ # @param [:request, :response, :both] new_type
111
+ # The new type for the parser.
112
+ #
113
+ def reset!(new_type = type)
114
+ ::HttpParser.http_parser_init(self, new_type)
115
+ end
116
+ alias reset reset!
117
+
118
+ #
119
+ # The type of the parser.
120
+ #
121
+ # @return [:request, :response, :both]
122
+ # The parser type.
123
+ #
124
+ def type
125
+ TYPES[self[:type_flags] & 0x3]
126
+ end
127
+
128
+ #
129
+ # Sets the type of the parser.
130
+ #
131
+ # @param [:request, :response, :both] new_type
132
+ # The new parser type.
133
+ #
134
+ def type=(new_type)
135
+ self[:type_flags] = (flags | TYPES[new_type])
136
+ end
137
+
138
+ #
139
+ # Flags for the parser.
140
+ #
141
+ # @return [Integer]
142
+ # Parser flags.
143
+ #
144
+ def flags
145
+ (self[:type_flags] & 0xfc)
146
+ end
147
+
148
+ #
149
+ # The parsed HTTP major version number.
150
+ #
151
+ # @return [Integer]
152
+ # The HTTP major version number.
153
+ #
154
+ def http_major
155
+ self[:http_major]
156
+ end
157
+
158
+ #
159
+ # The parsed HTTP minor version number.
160
+ #
161
+ # @return [Integer]
162
+ # The HTTP minor version number.
163
+ #
164
+ def http_minor
165
+ self[:http_minor]
166
+ end
167
+
168
+ #
169
+ # The parsed HTTP version.
170
+ #
171
+ # @return [String]
172
+ # The HTTP version.
173
+ #
174
+ def http_version
175
+ "%d.%d" % [self[:http_major], self[:http_minor]]
176
+ end
177
+
178
+ #
179
+ # The parsed HTTP response Status Code.
180
+ #
181
+ # @return [Integer]
182
+ # The HTTP Status Code.
183
+ #
184
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
185
+ #
186
+ def http_status
187
+ self[:status_code]
188
+ end
189
+
190
+ #
191
+ # The parsed HTTP Method.
192
+ #
193
+ # @return [Symbol]
194
+ # The HTTP Method name.
195
+ #
196
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
197
+ #
198
+ def http_method
199
+ METHODS[self[:method]]
200
+ end
201
+
202
+ #
203
+ # Determines whether the `Upgrade` header has been parsed.
204
+ #
205
+ # @return [Boolean]
206
+ # Specifies whether the `Upgrade` header has been seen.
207
+ #
208
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42
209
+ #
210
+ def upgrade?
211
+ (self[:error_upgrade] & 0b10000000) > 0
212
+ end
213
+
214
+ #
215
+ # Determines whether an error occurred during processing.
216
+ #
217
+ # @return [Boolean]
218
+ # Did a parsing error occur with the request?
219
+ #
220
+ def error?
221
+ error = (self[:error_upgrade] & 0b1111111)
222
+ return error != 0
223
+ end
224
+
225
+ #
226
+ # Returns the error that occurred during processing.
227
+ #
228
+ # @return [StandarError]
229
+ # Returns the error that occurred.
230
+ #
231
+ def error
232
+ error = (self[:error_upgrade] & 0b1111111)
233
+ return nil if error == 0
234
+
235
+ err = ::HttpParser.err_name(error)[4..-1] # HPE_ is at the start of all these errors
236
+ klass = ERRORS[err.to_sym]
237
+ err = "#{::HttpParser.err_desc(error)} (#{err})"
238
+ return klass.nil? ? Error::UNKNOWN.new(err) : klass.new(err)
239
+ end
240
+
241
+ #
242
+ # Additional data attached to the parser.
243
+ #
244
+ # @return [FFI::Pointer]
245
+ # Pointer to the additional data.
246
+ #
247
+ def data
248
+ self[:data]
249
+ end
250
+
251
+ #
252
+ # Determines whether the `Connection: keep-alive` header has been
253
+ # parsed.
254
+ #
255
+ # @return [Boolean]
256
+ # Specifies whether the Connection should be kept alive.
257
+ #
258
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
259
+ #
260
+ def keep_alive?
261
+ ::HttpParser.http_should_keep_alive(self) > 0
262
+ end
263
+
264
+ #
265
+ # Halts the parser if called in a callback
266
+ #
267
+ def stop!
268
+ throw :return, 1
269
+ end
270
+
271
+ #
272
+ # Indicates an error has occurred when called in a callback
273
+ #
274
+ def error!
275
+ throw :return, -1
276
+ end
277
+ end
278
+
279
+ class FieldData < FFI::Struct
280
+ layout :off, :uint16,
281
+ :len, :uint16
282
+ end
283
+
284
+ class HttpParserUrl < FFI::Struct
285
+ layout :field_set, :uint16,
286
+ :port, :uint16,
287
+ :field_data, [FieldData, UrlFields[:MAX]]
288
+ end
289
+
290
+
291
+ callback :http_data_cb, [Instance.ptr, :pointer, :size_t], :int
292
+ callback :http_cb, [Instance.ptr], :int
293
+
294
+
295
+ class Settings < FFI::Struct
296
+ layout :on_message_begin, :http_cb,
297
+ :on_url, :http_data_cb,
298
+ :on_status_complete, :http_cb,
299
+ :on_header_field, :http_data_cb,
300
+ :on_header_value, :http_data_cb,
301
+ :on_headers_complete, :http_cb,
302
+ :on_body, :http_data_cb,
303
+ :on_message_complete, :http_cb
304
+ end
305
+
306
+
307
+ attach_function :http_parser_init, [Instance.by_ref, :http_parser_type], :void, :blocking => true
308
+ attach_function :http_parser_execute, [Instance.by_ref, Settings.by_ref, :pointer, :size_t], :size_t, :blocking => true
309
+
310
+ attach_function :http_should_keep_alive, [Instance.by_ref], :int, :blocking => true
311
+ attach_function :http_method_str, [:http_method], :string, :blocking => true
312
+ end
@@ -0,0 +1,3 @@
1
+ module HttpParser
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'http-parser'
2
+
3
+ describe HttpParser::Parser, "#initialize" do
4
+ before :each do
5
+ @inst = HttpParser::Parser.new_instance
6
+ end
7
+
8
+ it "should return true when no error" do
9
+ subject.parse(@inst, "GET / HTTP/1.1\r\n").should be_true
10
+ @inst.error?.should be_false
11
+ end
12
+
13
+ it "should return false on error" do
14
+ subject.parse(@inst, "GETS / HTTP/1.1\r\n").should be_false
15
+ @inst.error?.should be_true
16
+ end
17
+
18
+ it "the error should be inspectable" do
19
+ subject.parse(@inst, "GETS / HTTP/1.1\r\n").should be_false
20
+ @inst.error.should be_kind_of(::HttpParser::Error::INVALID_METHOD)
21
+ @inst.error?.should be_true
22
+ end
23
+
24
+ it "raises different error types depending on the error" do
25
+ subject.parse(@inst, "GET / HTTP/23\r\n").should be_false
26
+ @inst.error.should be_kind_of(::HttpParser::Error::INVALID_VERSION)
27
+ @inst.error?.should be_true
28
+ end
29
+
30
+ context "callback errors" do
31
+ subject do
32
+ described_class.new do |parser|
33
+ parser.on_url { |inst, data| raise 'unhandled' }
34
+ end
35
+ end
36
+
37
+ it "should handle unhandled errors gracefully" do
38
+ subject.parse(@inst, "GET /foo?q=1 HTTP/1.1").should be_false
39
+
40
+ @inst.error?.should be_true
41
+ @inst.error.should be_kind_of(::HttpParser::Error::CALLBACK)
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,98 @@
1
+ require 'http-parser'
2
+
3
+ describe ::HttpParser::Instance, "#initialize" do
4
+ context "when initialized from a pointer" do
5
+ it "should not call http_parser_init" do
6
+ ptr = described_class.new.to_ptr
7
+
8
+ ::HttpParser.should_not_receive(:http_parser_init)
9
+
10
+ described_class.new(ptr)
11
+ end
12
+ end
13
+
14
+ context "when given a block" do
15
+ it "should yield the new Instance" do
16
+ expected = nil
17
+
18
+ described_class.new { |inst| expected = inst }
19
+
20
+ expected.should be_kind_of(described_class)
21
+ end
22
+
23
+ it "should allow changing the parser type" do
24
+ inst = described_class.new do |inst|
25
+ inst.type = :request
26
+ end
27
+
28
+ inst.type.should == :request
29
+ end
30
+ end
31
+
32
+ describe "#type" do
33
+ it "should default to :both" do
34
+ subject.type.should == :both
35
+ end
36
+
37
+ it "should convert the type to a Symbol" do
38
+ subject[:type_flags] = ::HttpParser::TYPES[:request]
39
+
40
+ subject.type.should == :request
41
+ end
42
+
43
+ it "should extract the type from the type_flags field" do
44
+ subject[:type_flags] = ((0xff & ~0x3) | ::HttpParser::TYPES[:response])
45
+
46
+ subject.type.should == :response
47
+ end
48
+ end
49
+
50
+ describe "#type=" do
51
+ it "should set the type" do
52
+ subject.type = :response
53
+
54
+ subject.type.should == :response
55
+ end
56
+
57
+ it "should not change flags" do
58
+ flags = (0xff & ~0x3)
59
+ subject[:type_flags] = flags
60
+
61
+ subject.type = :request
62
+
63
+ subject[:type_flags].should == (flags | ::HttpParser::TYPES[:request])
64
+ end
65
+ end
66
+
67
+ describe "#stop!" do
68
+ it "should throw :return, 1" do
69
+ lambda { subject.stop! }.should throw_symbol(:return,1)
70
+ end
71
+ end
72
+
73
+ describe "#error!" do
74
+ it "should throw :return, -1" do
75
+ lambda { subject.error! }.should throw_symbol(:return,-1)
76
+ end
77
+ end
78
+
79
+ describe "#reset!" do
80
+ it "should call http_parser_init" do
81
+ inst = described_class.new
82
+
83
+ ::HttpParser.should_receive(:http_parser_init)
84
+
85
+ inst.reset!
86
+ end
87
+ end
88
+
89
+ it "should not change the type" do
90
+ inst = described_class.new do |inst|
91
+ inst.type = :request
92
+ end
93
+
94
+ inst.reset!
95
+ inst.type.should == :request
96
+ end
97
+ end
98
+