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?
240 def test_chunked_empty
243 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
244 assert_equal req, @parser.parse, "msg=#{str}"
245 assert_equal 0, str.size
247 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
253 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
255 assert_equal req, @parser.parse
256 assert_equal 0, str.size
258 assert_nil @parser.filter_body(tmp, str << "6")
259 assert_equal 0, tmp.size
260 assert_nil @parser.filter_body(tmp, str << "\r\n")
262 assert_equal 0, tmp.size
264 assert_nil @parser.filter_body(tmp, str << "..")
265 assert_equal 2, tmp.size
266 assert_equal "..", tmp
267 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
268 assert_equal "abcd", tmp
269 assert_nil @parser.filter_body(tmp, str << "\r")
271 assert_nil @parser.filter_body(tmp, str << "\n")
273 assert_nil @parser.filter_body(tmp, str << "z")
274 assert_equal "z", tmp
275 assert_nil @parser.filter_body(tmp, str << "\r\n")
276 assert_nil @parser.filter_body(tmp, str << "0")
277 assert_nil @parser.filter_body(tmp, str << "\r")
278 rv = @parser.filter_body(tmp, str << "\nGET")
279 assert_equal "GET", rv
280 assert_equal str.object_id, rv.object_id
281 assert ! @parser.keepalive?
286 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
289 assert_equal req, @parser.parse
291 assert_nil @parser.filter_body(tmp, str)
294 assert_nil @parser.filter_body(tmp, str)
297 assert_nil @parser.filter_body(tmp, str)
299 assert ! @parser.body_eof?
300 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
302 assert @parser.body_eof?
304 assert_equal req, @parser.parse
306 assert @parser.body_eof?
307 assert @parser.keepalive?
310 def test_two_chunks_oneshot
313 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
314 "1\r\na\r\n2\r\n..\r\n0\r\n"
315 assert_equal req, @parser.parse
317 assert_nil @parser.filter_body(tmp, str)
318 assert_equal 'a..', tmp
319 rv = @parser.filter_body(tmp, str)
320 assert_equal rv.object_id, str.object_id
321 assert ! @parser.keepalive?
324 def test_chunks_bytewise
325 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
326 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
330 assert_equal req, @parser.parse
335 str.each_byte { |byte|
336 assert_nil @parser.filter_body(tmp, buf << byte.chr)
339 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
340 rv = @parser.filter_body(tmp, buf<< "\n")
341 assert_equal rv.object_id, buf.object_id
342 assert ! @parser.keepalive?
348 str << "PUT / HTTP/1.1\r\n" \
349 "Trailer: Content-MD5\r\n" \
350 "transfer-Encoding: chunked\r\n\r\n" \
351 "1\r\na\r\n2\r\n..\r\n0\r\n"
352 assert_equal req, @parser.parse
353 assert_equal 'Content-MD5', req['HTTP_TRAILER']
354 assert_nil req['HTTP_CONTENT_MD5']
356 assert_nil @parser.filter_body(tmp, str)
357 assert_equal 'a..', tmp
358 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
359 rv = @parser.filter_body(tmp, str)
360 assert_equal rv.object_id, str.object_id
362 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
364 assert_nil @parser.trailers(req, str)
365 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
366 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
368 assert_nil @parser.parse
370 assert_equal req, @parser.parse
371 assert_equal "GET / ", str
372 assert @parser.keepalive?
375 def test_trailers_slowly
377 str << "PUT / HTTP/1.1\r\n" \
378 "Trailer: Content-MD5\r\n" \
379 "transfer-Encoding: chunked\r\n\r\n" \
380 "1\r\na\r\n2\r\n..\r\n0\r\n"
382 assert_equal req, @parser.parse
383 assert_equal 'Content-MD5', req['HTTP_TRAILER']
384 assert_nil req['HTTP_CONTENT_MD5']
386 assert_nil @parser.filter_body(tmp, str)
387 assert_equal 'a..', tmp
388 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
389 rv = @parser.filter_body(tmp, str)
390 assert_equal rv.object_id, str.object_id
392 assert_nil @parser.trailers(req, str)
393 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
394 md5_hdr.each_byte { |byte|
396 assert_nil @parser.trailers(req, str)
398 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
399 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
401 assert_nil @parser.parse
403 assert_equal req, @parser.parse
408 str << "PUT / HTTP/1.1\r\n" \
409 "transfer-Encoding: chunked\r\n\r\n" \
410 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
412 assert_equal req, @parser.parse
413 assert_nil @parser.content_length
414 assert_nothing_raised { @parser.filter_body('', str) }
415 assert ! @parser.keepalive?
419 n = HttpParser::LENGTH_MAX
420 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
422 assert_nothing_raised { @parser.headers(req, @parser.buf) }
423 assert_equal n, req['CONTENT_LENGTH'].to_i
424 assert ! @parser.keepalive?
427 def test_overflow_chunk
428 n = HttpParser::CHUNK_MAX + 1
431 str << "PUT / HTTP/1.1\r\n" \
432 "transfer-Encoding: chunked\r\n\r\n" \
433 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
434 assert_equal req, @parser.parse
435 assert_nil @parser.content_length
436 assert_raise(HttpParserError) { @parser.filter_body('', str) }
439 def test_overflow_content_length
440 n = HttpParser::LENGTH_MAX + 1
441 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
442 assert_raise(HttpParserError) { @parser.parse }
446 @parser.buf << "PUT / HTTP/1.1\r\n" \
447 "transfer-Encoding: chunked\r\n\r\n" \
448 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
450 assert_equal req, @parser.parse
451 assert_nil @parser.content_length
452 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
455 def test_bad_content_length
456 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
457 assert_raise(HttpParserError) { @parser.parse }
460 def test_bad_trailers
463 str << "PUT / HTTP/1.1\r\n" \
464 "Trailer: Transfer-Encoding\r\n" \
465 "transfer-Encoding: chunked\r\n\r\n" \
466 "1\r\na\r\n2\r\n..\r\n0\r\n"
467 assert_equal req, @parser.parse
468 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
470 assert_nil @parser.filter_body(tmp, str)
471 assert_equal 'a..', tmp
473 str << "Transfer-Encoding: identity\r\n\r\n"
474 assert_raise(HttpParserError) { @parser.parse }
477 def test_repeat_headers
478 str = "PUT / HTTP/1.1\r\n" \
479 "Trailer: Content-MD5\r\n" \
480 "Trailer: Content-SHA1\r\n" \
481 "transfer-Encoding: chunked\r\n\r\n" \
482 "1\r\na\r\n2\r\n..\r\n0\r\n"
485 assert_equal req, @parser.parse
486 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
487 assert ! @parser.keepalive?
490 def test_parse_simple_request
491 parser = HttpParser.new
493 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
494 assert_equal req, parser.parse
495 assert_equal '', parser.buf
497 "SERVER_NAME"=>"localhost",
498 "rack.url_scheme"=>"http",
499 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
500 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
501 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
503 "SERVER_PROTOCOL"=>"HTTP/0.9",
504 "REQUEST_METHOD"=>"GET",
507 assert_equal expect, req
508 assert ! parser.headers?
511 def test_path_info_semicolon
515 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
517 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
518 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
519 "/1;a=b" => { qs => "", pi => "/1;a=b" },
520 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
521 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
522 "*" => { qs => "", pi => "" },
523 }.each do |uri,expect|
524 assert_equal req, @parser.headers(req.clear, str % [ uri ])
527 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
528 assert_equal expect[qs], req[qs], "#{qs} mismatch"
529 assert_equal expect[pi], req[pi], "#{pi} mismatch"
531 uri = URI.parse("http://example.com#{uri}")
532 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
533 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
537 def test_path_info_semicolon_absolute
541 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
543 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
544 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
545 "/1;a=b" => { qs => "", pi => "/1;a=b" },
546 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
547 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
548 }.each do |uri,expect|
549 assert_equal req, @parser.headers(req.clear, str % [ uri ])
552 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
553 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
554 assert_equal expect[qs], req[qs], "#{qs} mismatch"
555 assert_equal expect[pi], req[pi], "#{pi} mismatch"
559 def test_negative_content_length
561 str = "PUT / HTTP/1.1\r\n" \
562 "Content-Length: -1\r\n" \
564 assert_raises(HttpParserError) do
565 @parser.headers(req, str)
569 def test_invalid_content_length
571 str = "PUT / HTTP/1.1\r\n" \
572 "Content-Length: zzzzz\r\n" \
574 assert_raises(HttpParserError) do
575 @parser.headers(req, str)
579 def test_backtrace_is_empty
581 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
582 assert false, "should never get here line:#{__LINE__}"
583 rescue HttpParserError => e
584 assert_equal [], e.backtrace
587 assert false, "should never get here line:#{__LINE__}"
590 def test_ignore_version_header
591 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
593 assert_equal req, @parser.parse
594 assert_equal '', @parser.buf
596 "SERVER_NAME" => "localhost",
597 "rack.url_scheme" => "http",
598 "REQUEST_PATH" => "/",
599 "SERVER_PROTOCOL" => "HTTP/1.1",
601 "HTTP_VERSION" => "HTTP/1.1",
602 "REQUEST_URI" => "/",
603 "SERVER_PORT" => "80",
604 "REQUEST_METHOD" => "GET",
607 assert_equal expect, req
610 def test_pipelined_requests
614 "SERVER_NAME" => host,
615 "REQUEST_PATH" => "/",
616 "rack.url_scheme" => "http",
617 "SERVER_PROTOCOL" => "HTTP/1.1",
619 "HTTP_VERSION" => "HTTP/1.1",
620 "REQUEST_URI" => "/",
621 "SERVER_PORT" => "80",
622 "REQUEST_METHOD" => "GET",
625 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
626 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
627 @parser.buf << (req1 + req2)
628 env1 = @parser.parse.dup
629 assert_equal expect, env1
630 assert_equal req2, @parser.buf
631 assert ! @parser.env.empty?
633 assert @parser.keepalive?
634 assert @parser.headers?
635 assert_equal expect, @parser.env
636 env2 = @parser.parse.dup
637 host.replace "www.example.com"
638 assert_equal "www.example.com", expect["HTTP_HOST"]
639 assert_equal "www.example.com", expect["SERVER_NAME"]
640 assert_equal expect, env2
641 assert_equal "", @parser.buf
644 def test_keepalive_requests_disabled
645 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
647 "SERVER_NAME" => "example.com",
648 "HTTP_HOST" => "example.com",
649 "rack.url_scheme" => "http",
650 "REQUEST_PATH" => "/",
651 "SERVER_PROTOCOL" => "HTTP/1.1",
653 "HTTP_VERSION" => "HTTP/1.1",
654 "REQUEST_URI" => "/",
655 "SERVER_PORT" => "80",
656 "REQUEST_METHOD" => "GET",
659 HttpParser.keepalive_requests = 0
660 @parser = HttpParser.new
662 assert_equal expect, @parser.parse
663 assert ! @parser.next?
668 assert_equal @parser, @parser.dechunk!
669 assert_nil @parser.filter_body(tmp, "6\r\n")
671 assert_nil @parser.filter_body(tmp, "abcdef")
672 assert_equal "abcdef", tmp
673 assert_nil @parser.filter_body(tmp, "\r\n")
676 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
680 def test_chunk_only_bad_align
682 assert_equal @parser, @parser.dechunk!
683 assert_nil @parser.filter_body(tmp, "6\r\na")
684 assert_equal "a", tmp
685 assert_nil @parser.filter_body(tmp, "bcde")
686 assert_equal "bcde", tmp
687 assert_nil @parser.filter_body(tmp, "f\r")
688 assert_equal "f", tmp
690 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
694 def test_chunk_only_reset_ok
696 assert_equal @parser, @parser.dechunk!
697 src = "1\r\na\r\n0\r\n\r\n"
698 assert_nil @parser.filter_body(tmp, src)
699 assert_equal "a", tmp
700 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
702 assert_equal @parser, @parser.dechunk!
704 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
706 assert_equal src, @parser.filter_body(tmp, src)