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
16 r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
19 @parser.response_start_sent = true
20 assert @parser.keepalive?
22 assert @parser.response_start_sent
24 # persistent client makes another request:
27 assert @parser.keepalive?
29 assert_equal false, @parser.response_start_sent
32 def test_keepalive_requests_default_constant
33 assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
34 assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
37 def test_keepalive_requests_setting
38 HttpParser.keepalive_requests = 0
39 assert_equal 0, HttpParser.keepalive_requests
40 HttpParser.keepalive_requests = nil
41 assert HttpParser.keepalive_requests >= 0xffffffff
42 HttpParser.keepalive_requests = 1
43 assert_equal 1, HttpParser.keepalive_requests
44 HttpParser.keepalive_requests = 666
45 assert_equal 666, HttpParser.keepalive_requests
47 assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
48 assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
51 def test_connection_TE
52 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
53 @parser.buf << "TE: trailers\r\n\r\n"
55 assert @parser.keepalive?
59 def test_keepalive_requests_with_next?
60 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
62 "SERVER_NAME" => "example.com",
63 "HTTP_HOST" => "example.com",
64 "rack.url_scheme" => "http",
65 "REQUEST_PATH" => "/",
66 "SERVER_PROTOCOL" => "HTTP/1.1",
68 "HTTP_VERSION" => "HTTP/1.1",
70 "SERVER_PORT" => "80",
71 "REQUEST_METHOD" => "GET",
74 HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
76 assert_equal expect, @parser.parse
80 assert_equal expect, @parser.parse
81 assert ! @parser.next?
84 def test_fewer_keepalive_requests_with_next?
85 HttpParser.keepalive_requests = 5
86 @parser = HttpParser.new
87 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
89 "SERVER_NAME" => "example.com",
90 "HTTP_HOST" => "example.com",
91 "rack.url_scheme" => "http",
92 "REQUEST_PATH" => "/",
93 "SERVER_PROTOCOL" => "HTTP/1.1",
95 "HTTP_VERSION" => "HTTP/1.1",
97 "SERVER_PORT" => "80",
98 "REQUEST_METHOD" => "GET",
103 assert_equal expect, @parser.parse
107 assert_equal expect, @parser.parse
108 assert ! @parser.next?
111 def test_default_keepalive_is_off
112 assert ! @parser.keepalive?
113 assert ! @parser.next?
114 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
116 assert @parser.keepalive?
118 assert ! @parser.keepalive?
119 assert ! @parser.next?
122 def test_identity_byte_headers
124 str = "PUT / HTTP/1.1\r\n"
125 str << "Content-Length: 123\r\n"
128 str.each_byte { |byte|
130 assert_nil @parser.parse
133 assert_equal req.object_id, @parser.parse.object_id
134 assert_equal '123', req['CONTENT_LENGTH']
135 assert_equal 0, hdr.size
136 assert ! @parser.keepalive?
137 assert @parser.headers?
138 assert_equal 123, @parser.content_length
141 @parser.filter_body(dst, buf)
142 assert_equal '.' * 123, dst
144 assert @parser.keepalive?
147 def test_identity_step_headers
150 str << "PUT / HTTP/1.1\r\n"
151 assert ! @parser.parse
152 str << "Content-Length: 123\r\n"
153 assert ! @parser.parse
155 assert_equal req.object_id, @parser.parse.object_id
156 assert_equal '123', req['CONTENT_LENGTH']
157 assert_equal 0, str.size
158 assert ! @parser.keepalive?
159 assert @parser.headers?
162 @parser.filter_body(dst, buf)
163 assert_equal '.' * 123, dst
165 assert @parser.keepalive?
168 def test_identity_oneshot_header
171 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
172 assert_equal req.object_id, @parser.parse.object_id
173 assert_equal '123', req['CONTENT_LENGTH']
174 assert_equal 0, str.size
175 assert ! @parser.keepalive?
176 assert @parser.headers?
179 @parser.filter_body(dst, buf)
180 assert_equal '.' * 123, dst
184 def test_identity_oneshot_header_with_body
185 body = ('a' * 123).freeze
188 str << "PUT / HTTP/1.1\r\n" \
189 "Content-Length: #{body.length}\r\n" \
191 assert_equal req.object_id, @parser.parse.object_id
192 assert_equal '123', req['CONTENT_LENGTH']
193 assert_equal 123, str.size
194 assert_equal body, str
196 assert_nil @parser.filter_body(tmp, str)
197 assert_equal 0, str.size
198 assert_equal tmp, body
199 assert_equal "", @parser.filter_body(tmp, str)
200 assert @parser.keepalive?
203 def test_identity_oneshot_header_with_body_partial
205 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
206 assert_equal Hash, @parser.parse.class
207 assert_equal 1, str.size
208 assert_equal 'a', str
210 assert_nil @parser.filter_body(tmp, str)
212 assert_equal "a", tmp
214 rv = @parser.filter_body(tmp, str)
215 assert_equal 122, tmp.size
218 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
219 assert @parser.keepalive?
222 def test_identity_oneshot_header_with_body_slop
224 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
225 assert_equal Hash, @parser.parse.class
226 assert_equal 2, str.size
227 assert_equal 'aG', str
229 assert_nil @parser.filter_body(tmp, str)
230 assert_equal "G", str
231 assert_equal "G", @parser.filter_body(tmp, str)
232 assert_equal 1, tmp.size
233 assert_equal "a", tmp
234 assert @parser.keepalive?
240 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
241 assert_equal req, @parser.parse, "msg=#{str}"
242 assert_equal 0, str.size
244 assert_nil @parser.filter_body(tmp, str << "6")
245 assert_equal 0, tmp.size
246 assert_nil @parser.filter_body(tmp, str << "\r\n")
247 assert_equal 0, str.size
248 assert_equal 0, tmp.size
250 assert_nil @parser.filter_body(tmp, str << "..")
251 assert_equal "..", tmp
252 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
253 assert_equal "abcd", tmp
254 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
255 assert_equal "PUT", str
256 assert ! @parser.keepalive?
257 str << "TY: FOO\r\n\r\n"
258 assert_equal req, @parser.parse
259 assert_equal "FOO", req["HTTP_PUTTY"]
260 assert @parser.keepalive?
263 def test_chunked_empty
266 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
267 assert_equal req, @parser.parse, "msg=#{str}"
268 assert_equal 0, str.size
270 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
276 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
278 assert_equal req, @parser.parse
279 assert_equal 0, str.size
281 assert_nil @parser.filter_body(tmp, str << "6")
282 assert_equal 0, tmp.size
283 assert_nil @parser.filter_body(tmp, str << "\r\n")
285 assert_equal 0, tmp.size
287 assert_nil @parser.filter_body(tmp, str << "..")
288 assert_equal 2, tmp.size
289 assert_equal "..", tmp
290 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
291 assert_equal "abcd", tmp
292 assert_nil @parser.filter_body(tmp, str << "\r")
294 assert_nil @parser.filter_body(tmp, str << "\n")
296 assert_nil @parser.filter_body(tmp, str << "z")
297 assert_equal "z", tmp
298 assert_nil @parser.filter_body(tmp, str << "\r\n")
299 assert_nil @parser.filter_body(tmp, str << "0")
300 assert_nil @parser.filter_body(tmp, str << "\r")
301 rv = @parser.filter_body(tmp, str << "\nGET")
302 assert_equal "GET", rv
303 assert_equal str.object_id, rv.object_id
304 assert ! @parser.keepalive?
309 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
312 assert_equal req, @parser.parse
314 assert_nil @parser.filter_body(tmp, str)
317 assert_nil @parser.filter_body(tmp, str)
320 assert_nil @parser.filter_body(tmp, str)
322 assert ! @parser.body_eof?
323 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
325 assert @parser.body_eof?
327 assert_equal req, @parser.parse
329 assert @parser.body_eof?
330 assert @parser.keepalive?
333 def test_two_chunks_oneshot
336 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
337 "1\r\na\r\n2\r\n..\r\n0\r\n"
338 assert_equal req, @parser.parse
340 assert_nil @parser.filter_body(tmp, str)
341 assert_equal 'a..', tmp
342 rv = @parser.filter_body(tmp, str)
343 assert_equal rv.object_id, str.object_id
344 assert ! @parser.keepalive?
347 def test_chunks_bytewise
348 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
349 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
353 assert_equal req, @parser.parse
358 str.each_byte { |byte|
359 assert_nil @parser.filter_body(tmp, buf << byte.chr)
362 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
363 rv = @parser.filter_body(tmp, buf<< "\n")
364 assert_equal rv.object_id, buf.object_id
365 assert ! @parser.keepalive?
371 str << "PUT / HTTP/1.1\r\n" \
372 "Trailer: Content-MD5\r\n" \
373 "transfer-Encoding: chunked\r\n\r\n" \
374 "1\r\na\r\n2\r\n..\r\n0\r\n"
375 assert_equal req, @parser.parse
376 assert_equal 'Content-MD5', req['HTTP_TRAILER']
377 assert_nil req['HTTP_CONTENT_MD5']
379 assert_nil @parser.filter_body(tmp, str)
380 assert_equal 'a..', tmp
381 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
382 rv = @parser.filter_body(tmp, str)
383 assert_equal rv.object_id, str.object_id
385 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
387 assert_nil @parser.trailers(req, str)
388 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
389 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
391 assert_nil @parser.parse
393 assert_equal req, @parser.parse
394 assert_equal "GET / ", str
395 assert @parser.keepalive?
398 def test_trailers_slowly
400 str << "PUT / HTTP/1.1\r\n" \
401 "Trailer: Content-MD5\r\n" \
402 "transfer-Encoding: chunked\r\n\r\n" \
403 "1\r\na\r\n2\r\n..\r\n0\r\n"
405 assert_equal req, @parser.parse
406 assert_equal 'Content-MD5', req['HTTP_TRAILER']
407 assert_nil req['HTTP_CONTENT_MD5']
409 assert_nil @parser.filter_body(tmp, str)
410 assert_equal 'a..', tmp
411 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
412 rv = @parser.filter_body(tmp, str)
413 assert_equal rv.object_id, str.object_id
415 assert_nil @parser.trailers(req, str)
416 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
417 md5_hdr.each_byte { |byte|
419 assert_nil @parser.trailers(req, str)
421 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
422 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
424 assert_nil @parser.parse
426 assert_equal req, @parser.parse
431 str << "PUT / HTTP/1.1\r\n" \
432 "transfer-Encoding: chunked\r\n\r\n" \
433 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
435 assert_equal req, @parser.parse
436 assert_nil @parser.content_length
437 @parser.filter_body('', str)
438 assert ! @parser.keepalive?
442 n = HttpParser::LENGTH_MAX
443 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
445 @parser.headers(req, @parser.buf)
446 assert_equal n, req['CONTENT_LENGTH'].to_i
447 assert ! @parser.keepalive?
450 def test_overflow_chunk
451 n = HttpParser::CHUNK_MAX + 1
454 str << "PUT / HTTP/1.1\r\n" \
455 "transfer-Encoding: chunked\r\n\r\n" \
456 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
457 assert_equal req, @parser.parse
458 assert_nil @parser.content_length
459 assert_raise(HttpParserError) { @parser.filter_body('', str) }
462 def test_overflow_content_length
463 n = HttpParser::LENGTH_MAX + 1
464 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
465 assert_raise(HttpParserError) { @parser.parse }
469 @parser.buf << "PUT / HTTP/1.1\r\n" \
470 "transfer-Encoding: chunked\r\n\r\n" \
471 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
473 assert_equal req, @parser.parse
474 assert_nil @parser.content_length
475 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
478 def test_bad_content_length
479 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
480 assert_raise(HttpParserError) { @parser.parse }
483 def test_bad_trailers
486 str << "PUT / HTTP/1.1\r\n" \
487 "Trailer: Transfer-Encoding\r\n" \
488 "transfer-Encoding: chunked\r\n\r\n" \
489 "1\r\na\r\n2\r\n..\r\n0\r\n"
490 assert_equal req, @parser.parse
491 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
493 assert_nil @parser.filter_body(tmp, str)
494 assert_equal 'a..', tmp
496 str << "Transfer-Encoding: identity\r\n\r\n"
497 assert_raise(HttpParserError) { @parser.parse }
500 def test_repeat_headers
501 str = "PUT / HTTP/1.1\r\n" \
502 "Trailer: Content-MD5\r\n" \
503 "Trailer: Content-SHA1\r\n" \
504 "transfer-Encoding: chunked\r\n\r\n" \
505 "1\r\na\r\n2\r\n..\r\n0\r\n"
508 assert_equal req, @parser.parse
509 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
510 assert ! @parser.keepalive?
513 def test_parse_simple_request
514 parser = HttpParser.new
516 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
517 assert_equal req, parser.parse
518 assert_equal '', parser.buf
520 "SERVER_NAME"=>"localhost",
521 "rack.url_scheme"=>"http",
522 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
523 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
524 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
526 "SERVER_PROTOCOL"=>"HTTP/0.9",
527 "REQUEST_METHOD"=>"GET",
530 assert_equal expect, req
531 assert ! parser.headers?
534 def test_path_info_semicolon
538 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
540 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
541 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
542 "/1;a=b" => { qs => "", pi => "/1;a=b" },
543 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
544 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
545 "*" => { qs => "", pi => "" },
546 }.each do |uri,expect|
547 assert_equal req, @parser.headers(req.clear, str % [ uri ])
550 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
551 assert_equal expect[qs], req[qs], "#{qs} mismatch"
552 assert_equal expect[pi], req[pi], "#{pi} mismatch"
554 uri = URI.parse("http://example.com#{uri}")
555 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
556 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
560 def test_path_info_semicolon_absolute
564 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
566 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
567 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
568 "/1;a=b" => { qs => "", pi => "/1;a=b" },
569 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
570 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
571 }.each do |uri,expect|
572 assert_equal req, @parser.headers(req.clear, str % [ uri ])
575 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
576 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
577 assert_equal expect[qs], req[qs], "#{qs} mismatch"
578 assert_equal expect[pi], req[pi], "#{pi} mismatch"
582 def test_negative_content_length
584 str = "PUT / HTTP/1.1\r\n" \
585 "Content-Length: -1\r\n" \
587 assert_raises(HttpParserError) do
588 @parser.headers(req, str)
592 def test_invalid_content_length
594 str = "PUT / HTTP/1.1\r\n" \
595 "Content-Length: zzzzz\r\n" \
597 assert_raises(HttpParserError) do
598 @parser.headers(req, str)
602 def test_backtrace_is_empty
604 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
605 assert false, "should never get here line:#{__LINE__}"
606 rescue HttpParserError => e
607 assert_equal [], e.backtrace
610 assert false, "should never get here line:#{__LINE__}"
613 def test_ignore_version_header
614 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
616 assert_equal req, @parser.parse
617 assert_equal '', @parser.buf
619 "SERVER_NAME" => "localhost",
620 "rack.url_scheme" => "http",
621 "REQUEST_PATH" => "/",
622 "SERVER_PROTOCOL" => "HTTP/1.1",
624 "HTTP_VERSION" => "HTTP/1.1",
625 "REQUEST_URI" => "/",
626 "SERVER_PORT" => "80",
627 "REQUEST_METHOD" => "GET",
630 assert_equal expect, req
633 def test_pipelined_requests
637 "SERVER_NAME" => host,
638 "REQUEST_PATH" => "/",
639 "rack.url_scheme" => "http",
640 "SERVER_PROTOCOL" => "HTTP/1.1",
642 "HTTP_VERSION" => "HTTP/1.1",
643 "REQUEST_URI" => "/",
644 "SERVER_PORT" => "80",
645 "REQUEST_METHOD" => "GET",
648 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
649 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
650 @parser.buf << (req1 + req2)
651 env1 = @parser.parse.dup
652 assert_equal expect, env1
653 assert_equal req2, @parser.buf
654 assert ! @parser.env.empty?
656 assert @parser.keepalive?
657 assert @parser.headers?
658 assert_equal expect, @parser.env
659 env2 = @parser.parse.dup
660 host.replace "www.example.com"
661 assert_equal "www.example.com", expect["HTTP_HOST"]
662 assert_equal "www.example.com", expect["SERVER_NAME"]
663 assert_equal expect, env2
664 assert_equal "", @parser.buf
667 def test_keepalive_requests_disabled
668 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
670 "SERVER_NAME" => "example.com",
671 "HTTP_HOST" => "example.com",
672 "rack.url_scheme" => "http",
673 "REQUEST_PATH" => "/",
674 "SERVER_PROTOCOL" => "HTTP/1.1",
676 "HTTP_VERSION" => "HTTP/1.1",
677 "REQUEST_URI" => "/",
678 "SERVER_PORT" => "80",
679 "REQUEST_METHOD" => "GET",
682 HttpParser.keepalive_requests = 0
683 @parser = HttpParser.new
685 assert_equal expect, @parser.parse
686 assert ! @parser.next?
691 assert_equal @parser, @parser.dechunk!
692 assert_nil @parser.filter_body(tmp, "6\r\n")
694 assert_nil @parser.filter_body(tmp, "abcdef")
695 assert_equal "abcdef", tmp
696 assert_nil @parser.filter_body(tmp, "\r\n")
699 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
703 def test_chunk_only_bad_align
705 assert_equal @parser, @parser.dechunk!
706 assert_nil @parser.filter_body(tmp, "6\r\na")
707 assert_equal "a", tmp
708 assert_nil @parser.filter_body(tmp, "bcde")
709 assert_equal "bcde", tmp
710 assert_nil @parser.filter_body(tmp, "f\r")
711 assert_equal "f", tmp
713 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
717 def test_chunk_only_reset_ok
719 assert_equal @parser, @parser.dechunk!
720 src = "1\r\na\r\n0\r\n\r\n"
721 assert_nil @parser.filter_body(tmp, src)
722 assert_equal "a", tmp
723 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
725 assert_equal @parser, @parser.dechunk!
727 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
729 assert_equal src, @parser.filter_body(tmp, src)