http-parser 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.
@@ -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
+