1 # -*- encoding: binary -*-
3 require 'test/test_helper'
8 class HttpParserNgTest < Test::Unit::TestCase
11 HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
12 @parser = HttpParser.new
15 def test_keepalive_requests_default_constant
16 assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
17 assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
20 def test_keepalive_requests_setting
21 HttpParser.keepalive_requests = 0
22 assert_equal 0, HttpParser.keepalive_requests
23 HttpParser.keepalive_requests = nil
24 assert HttpParser.keepalive_requests >= 0xffffffff
25 HttpParser.keepalive_requests = 1
26 assert_equal 1, HttpParser.keepalive_requests
27 HttpParser.keepalive_requests = 666
28 assert_equal 666, HttpParser.keepalive_requests
30 assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
31 assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
34 def test_keepalive_requests_with_next?
35 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
37 "SERVER_NAME" => "example.com",
38 "HTTP_HOST" => "example.com",
39 "rack.url_scheme" => "http",
40 "REQUEST_PATH" => "/",
41 "SERVER_PROTOCOL" => "HTTP/1.1",
43 "HTTP_VERSION" => "HTTP/1.1",
45 "SERVER_PORT" => "80",
46 "REQUEST_METHOD" => "GET",
49 HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
51 assert_equal expect, @parser.parse
55 assert_equal expect, @parser.parse
56 assert ! @parser.next?
59 def test_fewer_keepalive_requests_with_next?
60 HttpParser.keepalive_requests = 5
61 @parser = HttpParser.new
62 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
64 "SERVER_NAME" => "example.com",
65 "HTTP_HOST" => "example.com",
66 "rack.url_scheme" => "http",
67 "REQUEST_PATH" => "/",
68 "SERVER_PROTOCOL" => "HTTP/1.1",
70 "HTTP_VERSION" => "HTTP/1.1",
72 "SERVER_PORT" => "80",
73 "REQUEST_METHOD" => "GET",
78 assert_equal expect, @parser.parse
82 assert_equal expect, @parser.parse
83 assert ! @parser.next?
86 def test_default_keepalive_is_off
87 assert ! @parser.keepalive?
88 assert ! @parser.next?
89 assert_nothing_raised do
90 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
93 assert @parser.keepalive?
95 assert ! @parser.keepalive?
96 assert ! @parser.next?
99 def test_identity_byte_headers
101 str = "PUT / HTTP/1.1\r\n"
102 str << "Content-Length: 123\r\n"
105 str.each_byte { |byte|
107 assert_nil @parser.parse
110 assert_equal req.object_id, @parser.parse.object_id
111 assert_equal '123', req['CONTENT_LENGTH']
112 assert_equal 0, hdr.size
113 assert ! @parser.keepalive?
114 assert @parser.headers?
115 assert_equal 123, @parser.content_length
118 @parser.filter_body(dst, buf)
119 assert_equal '.' * 123, dst
121 assert @parser.keepalive?
124 def test_identity_step_headers
127 str << "PUT / HTTP/1.1\r\n"
128 assert ! @parser.parse
129 str << "Content-Length: 123\r\n"
130 assert ! @parser.parse
132 assert_equal req.object_id, @parser.parse.object_id
133 assert_equal '123', req['CONTENT_LENGTH']
134 assert_equal 0, str.size
135 assert ! @parser.keepalive?
136 assert @parser.headers?
139 @parser.filter_body(dst, buf)
140 assert_equal '.' * 123, dst
142 assert @parser.keepalive?
145 def test_identity_oneshot_header
148 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
149 assert_equal req.object_id, @parser.parse.object_id
150 assert_equal '123', req['CONTENT_LENGTH']
151 assert_equal 0, str.size
152 assert ! @parser.keepalive?
153 assert @parser.headers?
156 @parser.filter_body(dst, buf)
157 assert_equal '.' * 123, dst
161 def test_identity_oneshot_header_with_body
162 body = ('a' * 123).freeze
165 str << "PUT / HTTP/1.1\r\n" \
166 "Content-Length: #{body.length}\r\n" \
168 assert_equal req.object_id, @parser.parse.object_id
169 assert_equal '123', req['CONTENT_LENGTH']
170 assert_equal 123, str.size
171 assert_equal body, str
173 assert_nil @parser.filter_body(tmp, str)
174 assert_equal 0, str.size
175 assert_equal tmp, body
176 assert_equal "", @parser.filter_body(tmp, str)
177 assert @parser.keepalive?
180 def test_identity_oneshot_header_with_body_partial
182 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
183 assert_equal Hash, @parser.parse.class
184 assert_equal 1, str.size
185 assert_equal 'a', str
187 assert_nil @parser.filter_body(tmp, str)
189 assert_equal "a", tmp
191 rv = @parser.filter_body(tmp, str)
192 assert_equal 122, tmp.size
195 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
196 assert @parser.keepalive?
199 def test_identity_oneshot_header_with_body_slop
201 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
202 assert_equal Hash, @parser.parse.class
203 assert_equal 2, str.size
204 assert_equal 'aG', str
206 assert_nil @parser.filter_body(tmp, str)
207 assert_equal "G", str
208 assert_equal "G", @parser.filter_body(tmp, str)
209 assert_equal 1, tmp.size
210 assert_equal "a", tmp
211 assert @parser.keepalive?
217 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
218 assert_equal req, @parser.parse, "msg=#{str}"
219 assert_equal 0, str.size
221 assert_nil @parser.filter_body(tmp, str << "6")
222 assert_equal 0, tmp.size
223 assert_nil @parser.filter_body(tmp, str << "\r\n")
224 assert_equal 0, str.size
225 assert_equal 0, tmp.size
227 assert_nil @parser.filter_body(tmp, str << "..")
228 assert_equal "..", tmp
229 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
230 assert_equal "abcd", tmp
231 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
232 assert_equal "PUT", str
233 assert ! @parser.keepalive?
234 str << "TY: FOO\r\n\r\n"
235 assert_equal req, @parser.parse
236 assert_equal "FOO", req["HTTP_PUTTY"]
237 assert @parser.keepalive?
242 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
244 assert_equal req, @parser.parse
245 assert_equal 0, str.size
247 assert_nil @parser.filter_body(tmp, str << "6")
248 assert_equal 0, tmp.size
249 assert_nil @parser.filter_body(tmp, str << "\r\n")
251 assert_equal 0, tmp.size
253 assert_nil @parser.filter_body(tmp, str << "..")
254 assert_equal 2, tmp.size
255 assert_equal "..", tmp
256 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
257 assert_equal "abcd", tmp
258 assert_nil @parser.filter_body(tmp, str << "\r")
260 assert_nil @parser.filter_body(tmp, str << "\n")
262 assert_nil @parser.filter_body(tmp, str << "z")
263 assert_equal "z", tmp
264 assert_nil @parser.filter_body(tmp, str << "\r\n")
265 assert_nil @parser.filter_body(tmp, str << "0")
266 assert_nil @parser.filter_body(tmp, str << "\r")
267 rv = @parser.filter_body(tmp, str << "\nGET")
268 assert_equal "GET", rv
269 assert_equal str.object_id, rv.object_id
270 assert ! @parser.keepalive?
275 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
278 assert_equal req, @parser.parse
280 assert_nil @parser.filter_body(tmp, str)
283 assert_nil @parser.filter_body(tmp, str)
286 assert_nil @parser.filter_body(tmp, str)
288 assert ! @parser.body_eof?
289 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
291 assert @parser.body_eof?
293 assert_equal req, @parser.parse
295 assert @parser.body_eof?
296 assert @parser.keepalive?
299 def test_two_chunks_oneshot
302 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
303 "1\r\na\r\n2\r\n..\r\n0\r\n"
304 assert_equal req, @parser.parse
306 assert_nil @parser.filter_body(tmp, str)
307 assert_equal 'a..', tmp
308 rv = @parser.filter_body(tmp, str)
309 assert_equal rv.object_id, str.object_id
310 assert ! @parser.keepalive?
313 def test_chunks_bytewise
314 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
315 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
319 assert_equal req, @parser.parse
324 str.each_byte { |byte|
325 assert_nil @parser.filter_body(tmp, buf << byte.chr)
328 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
329 rv = @parser.filter_body(tmp, buf<< "\n")
330 assert_equal rv.object_id, buf.object_id
331 assert ! @parser.keepalive?
337 str << "PUT / HTTP/1.1\r\n" \
338 "Trailer: Content-MD5\r\n" \
339 "transfer-Encoding: chunked\r\n\r\n" \
340 "1\r\na\r\n2\r\n..\r\n0\r\n"
341 assert_equal req, @parser.parse
342 assert_equal 'Content-MD5', req['HTTP_TRAILER']
343 assert_nil req['HTTP_CONTENT_MD5']
345 assert_nil @parser.filter_body(tmp, str)
346 assert_equal 'a..', tmp
347 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
348 rv = @parser.filter_body(tmp, str)
349 assert_equal rv.object_id, str.object_id
351 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
353 assert_nil @parser.trailers(req, str)
354 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
355 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
357 assert_nil @parser.parse
359 assert_equal req, @parser.parse
360 assert_equal "GET / ", str
361 assert @parser.keepalive?
364 def test_trailers_slowly
366 str << "PUT / HTTP/1.1\r\n" \
367 "Trailer: Content-MD5\r\n" \
368 "transfer-Encoding: chunked\r\n\r\n" \
369 "1\r\na\r\n2\r\n..\r\n0\r\n"
371 assert_equal req, @parser.parse
372 assert_equal 'Content-MD5', req['HTTP_TRAILER']
373 assert_nil req['HTTP_CONTENT_MD5']
375 assert_nil @parser.filter_body(tmp, str)
376 assert_equal 'a..', tmp
377 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
378 rv = @parser.filter_body(tmp, str)
379 assert_equal rv.object_id, str.object_id
381 assert_nil @parser.trailers(req, str)
382 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
383 md5_hdr.each_byte { |byte|
385 assert_nil @parser.trailers(req, str)
387 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
388 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
390 assert_nil @parser.parse
392 assert_equal req, @parser.parse
397 str << "PUT / HTTP/1.1\r\n" \
398 "transfer-Encoding: chunked\r\n\r\n" \
399 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
401 assert_equal req, @parser.parse
402 assert_nil @parser.content_length
403 assert_nothing_raised { @parser.filter_body('', str) }
404 assert ! @parser.keepalive?
408 n = HttpParser::LENGTH_MAX
409 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
411 assert_nothing_raised { @parser.headers(req, @parser.buf) }
412 assert_equal n, req['CONTENT_LENGTH'].to_i
413 assert ! @parser.keepalive?
416 def test_overflow_chunk
417 n = HttpParser::CHUNK_MAX + 1
420 str << "PUT / HTTP/1.1\r\n" \
421 "transfer-Encoding: chunked\r\n\r\n" \
422 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
423 assert_equal req, @parser.parse
424 assert_nil @parser.content_length
425 assert_raise(HttpParserError) { @parser.filter_body('', str) }
428 def test_overflow_content_length
429 n = HttpParser::LENGTH_MAX + 1
430 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
431 assert_raise(HttpParserError) { @parser.parse }
435 @parser.buf << "PUT / HTTP/1.1\r\n" \
436 "transfer-Encoding: chunked\r\n\r\n" \
437 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
439 assert_equal req, @parser.parse
440 assert_nil @parser.content_length
441 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
444 def test_bad_content_length
445 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
446 assert_raise(HttpParserError) { @parser.parse }
449 def test_bad_trailers
452 str << "PUT / HTTP/1.1\r\n" \
453 "Trailer: Transfer-Encoding\r\n" \
454 "transfer-Encoding: chunked\r\n\r\n" \
455 "1\r\na\r\n2\r\n..\r\n0\r\n"
456 assert_equal req, @parser.parse
457 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
459 assert_nil @parser.filter_body(tmp, str)
460 assert_equal 'a..', tmp
462 str << "Transfer-Encoding: identity\r\n\r\n"
463 assert_raise(HttpParserError) { @parser.parse }
466 def test_repeat_headers
467 str = "PUT / HTTP/1.1\r\n" \
468 "Trailer: Content-MD5\r\n" \
469 "Trailer: Content-SHA1\r\n" \
470 "transfer-Encoding: chunked\r\n\r\n" \
471 "1\r\na\r\n2\r\n..\r\n0\r\n"
474 assert_equal req, @parser.parse
475 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
476 assert ! @parser.keepalive?
479 def test_parse_simple_request
480 parser = HttpParser.new
482 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
483 assert_equal req, parser.parse
484 assert_equal '', parser.buf
486 "SERVER_NAME"=>"localhost",
487 "rack.url_scheme"=>"http",
488 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
489 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
490 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
492 "SERVER_PROTOCOL"=>"HTTP/0.9",
493 "REQUEST_METHOD"=>"GET",
496 assert_equal expect, req
497 assert ! parser.headers?
500 def test_path_info_semicolon
504 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
506 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
507 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
508 "/1;a=b" => { qs => "", pi => "/1;a=b" },
509 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
510 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
511 "*" => { qs => "", pi => "" },
512 }.each do |uri,expect|
513 assert_equal req, @parser.headers(req.clear, str % [ uri ])
516 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
517 assert_equal expect[qs], req[qs], "#{qs} mismatch"
518 assert_equal expect[pi], req[pi], "#{pi} mismatch"
520 uri = URI.parse("http://example.com#{uri}")
521 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
522 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
526 def test_path_info_semicolon_absolute
530 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
532 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
533 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
534 "/1;a=b" => { qs => "", pi => "/1;a=b" },
535 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
536 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
537 }.each do |uri,expect|
538 assert_equal req, @parser.headers(req.clear, str % [ uri ])
541 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
542 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
543 assert_equal expect[qs], req[qs], "#{qs} mismatch"
544 assert_equal expect[pi], req[pi], "#{pi} mismatch"
548 def test_negative_content_length
550 str = "PUT / HTTP/1.1\r\n" \
551 "Content-Length: -1\r\n" \
553 assert_raises(HttpParserError) do
554 @parser.headers(req, str)
558 def test_invalid_content_length
560 str = "PUT / HTTP/1.1\r\n" \
561 "Content-Length: zzzzz\r\n" \
563 assert_raises(HttpParserError) do
564 @parser.headers(req, str)
568 def test_backtrace_is_empty
570 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
571 assert false, "should never get here line:#{__LINE__}"
572 rescue HttpParserError => e
573 assert_equal [], e.backtrace
576 assert false, "should never get here line:#{__LINE__}"
579 def test_ignore_version_header
580 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
582 assert_equal req, @parser.parse
583 assert_equal '', @parser.buf
585 "SERVER_NAME" => "localhost",
586 "rack.url_scheme" => "http",
587 "REQUEST_PATH" => "/",
588 "SERVER_PROTOCOL" => "HTTP/1.1",
590 "HTTP_VERSION" => "HTTP/1.1",
591 "REQUEST_URI" => "/",
592 "SERVER_PORT" => "80",
593 "REQUEST_METHOD" => "GET",
596 assert_equal expect, req
599 def test_pipelined_requests
603 "SERVER_NAME" => host,
604 "REQUEST_PATH" => "/",
605 "rack.url_scheme" => "http",
606 "SERVER_PROTOCOL" => "HTTP/1.1",
608 "HTTP_VERSION" => "HTTP/1.1",
609 "REQUEST_URI" => "/",
610 "SERVER_PORT" => "80",
611 "REQUEST_METHOD" => "GET",
614 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
615 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
616 @parser.buf << (req1 + req2)
617 env1 = @parser.parse.dup
618 assert_equal expect, env1
619 assert_equal req2, @parser.buf
620 assert ! @parser.env.empty?
622 assert @parser.keepalive?
623 assert @parser.headers?
624 assert_equal expect, @parser.env
625 env2 = @parser.parse.dup
626 host.replace "www.example.com"
627 assert_equal "www.example.com", expect["HTTP_HOST"]
628 assert_equal "www.example.com", expect["SERVER_NAME"]
629 assert_equal expect, env2
630 assert_equal "", @parser.buf
633 def test_keepalive_requests_disabled
634 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
636 "SERVER_NAME" => "example.com",
637 "HTTP_HOST" => "example.com",
638 "rack.url_scheme" => "http",
639 "REQUEST_PATH" => "/",
640 "SERVER_PROTOCOL" => "HTTP/1.1",
642 "HTTP_VERSION" => "HTTP/1.1",
643 "REQUEST_URI" => "/",
644 "SERVER_PORT" => "80",
645 "REQUEST_METHOD" => "GET",
648 HttpParser.keepalive_requests = 0
649 @parser = HttpParser.new
651 assert_equal expect, @parser.parse
652 assert ! @parser.next?