1 # -*- encoding: binary -*-
3 require 'test/test_helper'
8 class HttpParserNgTest < Test::Unit::TestCase
11 @parser = HttpParser.new
14 def test_identity_byte_headers
16 str = "PUT / HTTP/1.1\r\n"
17 str << "Content-Length: 123\r\n"
20 str.each_byte { |byte|
21 assert_nil @parser.headers(req, hdr << byte.chr)
24 assert_equal req.object_id, @parser.headers(req, hdr).object_id
25 assert_equal '123', req['CONTENT_LENGTH']
26 assert_equal 0, hdr.size
27 assert ! @parser.keepalive?
28 assert @parser.headers?
29 assert_equal 123, @parser.content_length
32 def test_identity_step_headers
34 str = "PUT / HTTP/1.1\r\n"
35 assert ! @parser.headers(req, str)
36 str << "Content-Length: 123\r\n"
37 assert ! @parser.headers(req, str)
39 assert_equal req.object_id, @parser.headers(req, str).object_id
40 assert_equal '123', req['CONTENT_LENGTH']
41 assert_equal 0, str.size
42 assert ! @parser.keepalive?
43 assert @parser.headers?
46 def test_identity_oneshot_header
48 str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
49 assert_equal req.object_id, @parser.headers(req, str).object_id
50 assert_equal '123', req['CONTENT_LENGTH']
51 assert_equal 0, str.size
52 assert ! @parser.keepalive?
55 def test_identity_oneshot_header_with_body
56 body = ('a' * 123).freeze
58 str = "PUT / HTTP/1.1\r\n" \
59 "Content-Length: #{body.length}\r\n" \
61 assert_equal req.object_id, @parser.headers(req, str).object_id
62 assert_equal '123', req['CONTENT_LENGTH']
63 assert_equal 123, str.size
64 assert_equal body, str
66 assert_nil @parser.filter_body(tmp, str)
67 assert_equal 0, str.size
68 assert_equal tmp, body
69 assert_equal "", @parser.filter_body(tmp, str)
70 assert ! @parser.keepalive?
73 def test_identity_oneshot_header_with_body_partial
74 str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
75 assert_equal Hash, @parser.headers({}, str).class
76 assert_equal 1, str.size
79 assert_nil @parser.filter_body(tmp, str)
83 rv = @parser.filter_body(tmp, str)
84 assert_equal 122, tmp.size
87 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
88 assert ! @parser.keepalive?
91 def test_identity_oneshot_header_with_body_slop
92 str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
93 assert_equal Hash, @parser.headers({}, str).class
94 assert_equal 2, str.size
95 assert_equal 'aG', str
97 assert_nil @parser.filter_body(tmp, str)
99 assert_equal "G", @parser.filter_body(tmp, str)
100 assert_equal 1, tmp.size
101 assert_equal "a", tmp
102 assert ! @parser.keepalive?
106 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
108 assert_equal req, @parser.headers(req, str)
109 assert_equal 0, str.size
111 assert_nil @parser.filter_body(tmp, "6")
112 assert_equal 0, tmp.size
113 assert_nil @parser.filter_body(tmp, rv = "\r\n")
114 assert_equal 0, rv.size
115 assert_equal 0, tmp.size
117 assert_nil @parser.filter_body(tmp, "..")
118 assert_equal "..", tmp
119 assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n")
120 assert_equal "abcd", tmp
122 assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
123 assert_equal "PUT", rv
124 assert ! @parser.keepalive?
128 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
130 assert_equal req, @parser.headers(req, str)
131 assert_equal 0, str.size
133 assert_nil @parser.filter_body(tmp, "6")
134 assert_equal 0, tmp.size
135 assert_nil @parser.filter_body(tmp, rv = "\r\n")
137 assert_equal 0, tmp.size
139 assert_nil @parser.filter_body(tmp, "..")
140 assert_equal 2, tmp.size
141 assert_equal "..", tmp
142 assert_nil @parser.filter_body(tmp, "abcd\r\n1")
143 assert_equal "abcd", tmp
144 assert_nil @parser.filter_body(tmp, "\r")
146 assert_nil @parser.filter_body(tmp, "\n")
148 assert_nil @parser.filter_body(tmp, "z")
149 assert_equal "z", tmp
150 assert_nil @parser.filter_body(tmp, "\r\n")
151 assert_nil @parser.filter_body(tmp, "0")
152 assert_nil @parser.filter_body(tmp, "\r")
153 rv = @parser.filter_body(tmp, buf = "\nGET")
154 assert_equal "GET", rv
155 assert_equal buf.object_id, rv.object_id
156 assert ! @parser.keepalive?
160 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
163 assert_equal req, @parser.headers(req, str)
165 assert_nil @parser.filter_body(tmp, str)
168 assert_nil @parser.filter_body(tmp, str)
171 assert_nil @parser.filter_body(tmp, str)
173 assert ! @parser.body_eof?
174 assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n")
176 assert @parser.body_eof?
177 assert_equal req, @parser.trailers(req, moo = "\r\n")
179 assert @parser.body_eof?
180 assert ! @parser.keepalive?
183 def test_two_chunks_oneshot
184 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
185 "1\r\na\r\n2\r\n..\r\n0\r\n"
187 assert_equal req, @parser.headers(req, str)
189 assert_nil @parser.filter_body(tmp, str)
190 assert_equal 'a..', tmp
191 rv = @parser.filter_body(tmp, str)
192 assert_equal rv.object_id, str.object_id
193 assert ! @parser.keepalive?
196 def test_chunks_bytewise
197 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
198 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n#{chunked}"
200 assert_equal req, @parser.headers(req, str)
201 assert_equal chunked, str
206 str.each_byte { |byte|
207 assert_nil @parser.filter_body(tmp, buf << byte.chr)
210 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
211 rv = @parser.filter_body(tmp, buf << "\n")
212 assert_equal rv.object_id, buf.object_id
213 assert ! @parser.keepalive?
217 str = "PUT / HTTP/1.1\r\n" \
218 "Trailer: Content-MD5\r\n" \
219 "transfer-Encoding: chunked\r\n\r\n" \
220 "1\r\na\r\n2\r\n..\r\n0\r\n"
222 assert_equal req, @parser.headers(req, str)
223 assert_equal 'Content-MD5', req['HTTP_TRAILER']
224 assert_nil req['HTTP_CONTENT_MD5']
226 assert_nil @parser.filter_body(tmp, str)
227 assert_equal 'a..', tmp
228 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
229 rv = @parser.filter_body(tmp, str)
230 assert_equal rv.object_id, str.object_id
232 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
234 assert_nil @parser.trailers(req, str)
235 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
236 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
237 assert_nil @parser.trailers(req, str << "\r")
238 assert_equal req, @parser.trailers(req, str << "\nGET / ")
239 assert_equal "GET / ", str
240 assert ! @parser.keepalive?
243 def test_trailers_slowly
244 str = "PUT / HTTP/1.1\r\n" \
245 "Trailer: Content-MD5\r\n" \
246 "transfer-Encoding: chunked\r\n\r\n" \
247 "1\r\na\r\n2\r\n..\r\n0\r\n"
249 assert_equal req, @parser.headers(req, str)
250 assert_equal 'Content-MD5', req['HTTP_TRAILER']
251 assert_nil req['HTTP_CONTENT_MD5']
253 assert_nil @parser.filter_body(tmp, str)
254 assert_equal 'a..', tmp
255 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
256 rv = @parser.filter_body(tmp, str)
257 assert_equal rv.object_id, str.object_id
259 assert_nil @parser.trailers(req, str)
260 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
261 md5_hdr.each_byte { |byte|
263 assert_nil @parser.trailers(req, str)
265 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
266 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
267 assert_nil @parser.trailers(req, str << "\r")
268 assert_equal req, @parser.trailers(req, str << "\n")
272 str = "PUT / HTTP/1.1\r\n" \
273 "transfer-Encoding: chunked\r\n\r\n" \
274 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
276 assert_equal req, @parser.headers(req, str)
277 assert_nil @parser.content_length
278 assert_nothing_raised { @parser.filter_body('', str) }
279 assert ! @parser.keepalive?
283 n = HttpParser::LENGTH_MAX
284 str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
286 assert_nothing_raised { @parser.headers(req, str) }
287 assert_equal n, req['CONTENT_LENGTH'].to_i
288 assert ! @parser.keepalive?
291 def test_overflow_chunk
292 n = HttpParser::CHUNK_MAX + 1
293 str = "PUT / HTTP/1.1\r\n" \
294 "transfer-Encoding: chunked\r\n\r\n" \
295 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
297 assert_equal req, @parser.headers(req, str)
298 assert_nil @parser.content_length
299 assert_raise(HttpParserError) { @parser.filter_body('', str) }
300 assert ! @parser.keepalive?
303 def test_overflow_content_length
304 n = HttpParser::LENGTH_MAX + 1
305 str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
306 assert_raise(HttpParserError) { @parser.headers({}, str) }
307 assert ! @parser.keepalive?
311 str = "PUT / HTTP/1.1\r\n" \
312 "transfer-Encoding: chunked\r\n\r\n" \
313 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
315 assert_equal req, @parser.headers(req, str)
316 assert_nil @parser.content_length
317 assert_raise(HttpParserError) { @parser.filter_body('', str) }
318 assert ! @parser.keepalive?
321 def test_bad_content_length
322 str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
323 assert_raise(HttpParserError) { @parser.headers({}, str) }
324 assert ! @parser.keepalive?
327 def test_bad_trailers
328 str = "PUT / HTTP/1.1\r\n" \
329 "Trailer: Transfer-Encoding\r\n" \
330 "transfer-Encoding: chunked\r\n\r\n" \
331 "1\r\na\r\n2\r\n..\r\n0\r\n"
333 assert_equal req, @parser.headers(req, str)
334 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
336 assert_nil @parser.filter_body(tmp, str)
337 assert_equal 'a..', tmp
339 str << "Transfer-Encoding: identity\r\n\r\n"
340 assert_raise(HttpParserError) { @parser.trailers(req, str) }
341 assert ! @parser.keepalive?
344 def test_repeat_headers
345 str = "PUT / HTTP/1.1\r\n" \
346 "Trailer: Content-MD5\r\n" \
347 "Trailer: Content-SHA1\r\n" \
348 "transfer-Encoding: chunked\r\n\r\n" \
349 "1\r\na\r\n2\r\n..\r\n0\r\n"
351 assert_equal req, @parser.headers(req, str)
352 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
353 assert ! @parser.keepalive?
356 def test_parse_simple_request
357 parser = HttpParser.new
359 http = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
360 assert_equal req, parser.headers(req, http)
361 assert_equal '', http
363 "SERVER_NAME"=>"localhost",
364 "rack.url_scheme"=>"http",
365 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
366 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
367 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
369 "SERVER_PROTOCOL"=>"HTTP/0.9",
370 "REQUEST_METHOD"=>"GET",
373 assert_equal expect, req
374 assert ! parser.headers?
377 def test_path_info_semicolon
381 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
383 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
384 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
385 "/1;a=b" => { qs => "", pi => "/1;a=b" },
386 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
387 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
388 "*" => { qs => "", pi => "" },
389 }.each do |uri,expect|
390 assert_equal req, @parser.headers(req.clear, str % [ uri ])
392 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
393 assert_equal expect[qs], req[qs], "#{qs} mismatch"
394 assert_equal expect[pi], req[pi], "#{pi} mismatch"
396 uri = URI.parse("http://example.com#{uri}")
397 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
398 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
402 def test_path_info_semicolon_absolute
406 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
408 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
409 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
410 "/1;a=b" => { qs => "", pi => "/1;a=b" },
411 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
412 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
413 }.each do |uri,expect|
414 assert_equal req, @parser.headers(req.clear, str % [ uri ])
416 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
417 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
418 assert_equal expect[qs], req[qs], "#{qs} mismatch"
419 assert_equal expect[pi], req[pi], "#{pi} mismatch"
423 def test_negative_content_length
425 str = "PUT / HTTP/1.1\r\n" \
426 "Content-Length: -1\r\n" \
428 assert_raises(HttpParserError) do
429 @parser.headers(req, str)
433 def test_invalid_content_length
435 str = "PUT / HTTP/1.1\r\n" \
436 "Content-Length: zzzzz\r\n" \
438 assert_raises(HttpParserError) do
439 @parser.headers(req, str)
443 def test_ignore_version_header
444 http = "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
446 assert_equal req, @parser.headers(req, http)
447 assert_equal '', http
449 "SERVER_NAME" => "localhost",
450 "rack.url_scheme" => "http",
451 "REQUEST_PATH" => "/",
452 "SERVER_PROTOCOL" => "HTTP/1.1",
454 "HTTP_VERSION" => "HTTP/1.1",
455 "REQUEST_URI" => "/",
456 "SERVER_PORT" => "80",
457 "REQUEST_METHOD" => "GET",
460 assert_equal expect, req