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_connection_TE
35 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
36 @parser.buf << "TE: trailers\r\n\r\n"
38 assert @parser.keepalive?
42 def test_keepalive_requests_with_next?
43 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
45 "SERVER_NAME" => "example.com",
46 "HTTP_HOST" => "example.com",
47 "rack.url_scheme" => "http",
48 "REQUEST_PATH" => "/",
49 "SERVER_PROTOCOL" => "HTTP/1.1",
51 "HTTP_VERSION" => "HTTP/1.1",
53 "SERVER_PORT" => "80",
54 "REQUEST_METHOD" => "GET",
57 HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
59 assert_equal expect, @parser.parse
63 assert_equal expect, @parser.parse
64 assert ! @parser.next?
67 def test_fewer_keepalive_requests_with_next?
68 HttpParser.keepalive_requests = 5
69 @parser = HttpParser.new
70 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
72 "SERVER_NAME" => "example.com",
73 "HTTP_HOST" => "example.com",
74 "rack.url_scheme" => "http",
75 "REQUEST_PATH" => "/",
76 "SERVER_PROTOCOL" => "HTTP/1.1",
78 "HTTP_VERSION" => "HTTP/1.1",
80 "SERVER_PORT" => "80",
81 "REQUEST_METHOD" => "GET",
86 assert_equal expect, @parser.parse
90 assert_equal expect, @parser.parse
91 assert ! @parser.next?
94 def test_default_keepalive_is_off
95 assert ! @parser.keepalive?
96 assert ! @parser.next?
97 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
99 assert @parser.keepalive?
101 assert ! @parser.keepalive?
102 assert ! @parser.next?
105 def test_identity_byte_headers
107 str = "PUT / HTTP/1.1\r\n"
108 str << "Content-Length: 123\r\n"
111 str.each_byte { |byte|
113 assert_nil @parser.parse
116 assert_equal req.object_id, @parser.parse.object_id
117 assert_equal '123', req['CONTENT_LENGTH']
118 assert_equal 0, hdr.size
119 assert ! @parser.keepalive?
120 assert @parser.headers?
121 assert_equal 123, @parser.content_length
124 @parser.filter_body(dst, buf)
125 assert_equal '.' * 123, dst
127 assert @parser.keepalive?
130 def test_identity_step_headers
133 str << "PUT / HTTP/1.1\r\n"
134 assert ! @parser.parse
135 str << "Content-Length: 123\r\n"
136 assert ! @parser.parse
138 assert_equal req.object_id, @parser.parse.object_id
139 assert_equal '123', req['CONTENT_LENGTH']
140 assert_equal 0, str.size
141 assert ! @parser.keepalive?
142 assert @parser.headers?
145 @parser.filter_body(dst, buf)
146 assert_equal '.' * 123, dst
148 assert @parser.keepalive?
151 def test_identity_oneshot_header
154 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
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
167 def test_identity_oneshot_header_with_body
168 body = ('a' * 123).freeze
171 str << "PUT / HTTP/1.1\r\n" \
172 "Content-Length: #{body.length}\r\n" \
174 assert_equal req.object_id, @parser.parse.object_id
175 assert_equal '123', req['CONTENT_LENGTH']
176 assert_equal 123, str.size
177 assert_equal body, str
179 assert_nil @parser.filter_body(tmp, str)
180 assert_equal 0, str.size
181 assert_equal tmp, body
182 assert_equal "", @parser.filter_body(tmp, str)
183 assert @parser.keepalive?
186 def test_identity_oneshot_header_with_body_partial
188 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
189 assert_equal Hash, @parser.parse.class
190 assert_equal 1, str.size
191 assert_equal 'a', str
193 assert_nil @parser.filter_body(tmp, str)
195 assert_equal "a", tmp
197 rv = @parser.filter_body(tmp, str)
198 assert_equal 122, tmp.size
201 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
202 assert @parser.keepalive?
205 def test_identity_oneshot_header_with_body_slop
207 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
208 assert_equal Hash, @parser.parse.class
209 assert_equal 2, str.size
210 assert_equal 'aG', str
212 assert_nil @parser.filter_body(tmp, str)
213 assert_equal "G", str
214 assert_equal "G", @parser.filter_body(tmp, str)
215 assert_equal 1, tmp.size
216 assert_equal "a", tmp
217 assert @parser.keepalive?
223 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
224 assert_equal req, @parser.parse, "msg=#{str}"
225 assert_equal 0, str.size
227 assert_nil @parser.filter_body(tmp, str << "6")
228 assert_equal 0, tmp.size
229 assert_nil @parser.filter_body(tmp, str << "\r\n")
230 assert_equal 0, str.size
231 assert_equal 0, tmp.size
233 assert_nil @parser.filter_body(tmp, str << "..")
234 assert_equal "..", tmp
235 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
236 assert_equal "abcd", tmp
237 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
238 assert_equal "PUT", str
239 assert ! @parser.keepalive?
240 str << "TY: FOO\r\n\r\n"
241 assert_equal req, @parser.parse
242 assert_equal "FOO", req["HTTP_PUTTY"]
243 assert @parser.keepalive?
246 def test_chunked_empty
249 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
250 assert_equal req, @parser.parse, "msg=#{str}"
251 assert_equal 0, str.size
253 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
259 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
261 assert_equal req, @parser.parse
262 assert_equal 0, str.size
264 assert_nil @parser.filter_body(tmp, str << "6")
265 assert_equal 0, tmp.size
266 assert_nil @parser.filter_body(tmp, str << "\r\n")
268 assert_equal 0, tmp.size
270 assert_nil @parser.filter_body(tmp, str << "..")
271 assert_equal 2, tmp.size
272 assert_equal "..", tmp
273 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
274 assert_equal "abcd", tmp
275 assert_nil @parser.filter_body(tmp, str << "\r")
277 assert_nil @parser.filter_body(tmp, str << "\n")
279 assert_nil @parser.filter_body(tmp, str << "z")
280 assert_equal "z", tmp
281 assert_nil @parser.filter_body(tmp, str << "\r\n")
282 assert_nil @parser.filter_body(tmp, str << "0")
283 assert_nil @parser.filter_body(tmp, str << "\r")
284 rv = @parser.filter_body(tmp, str << "\nGET")
285 assert_equal "GET", rv
286 assert_equal str.object_id, rv.object_id
287 assert ! @parser.keepalive?
292 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
295 assert_equal req, @parser.parse
297 assert_nil @parser.filter_body(tmp, str)
300 assert_nil @parser.filter_body(tmp, str)
303 assert_nil @parser.filter_body(tmp, str)
305 assert ! @parser.body_eof?
306 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
308 assert @parser.body_eof?
310 assert_equal req, @parser.parse
312 assert @parser.body_eof?
313 assert @parser.keepalive?
316 def test_two_chunks_oneshot
319 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
320 "1\r\na\r\n2\r\n..\r\n0\r\n"
321 assert_equal req, @parser.parse
323 assert_nil @parser.filter_body(tmp, str)
324 assert_equal 'a..', tmp
325 rv = @parser.filter_body(tmp, str)
326 assert_equal rv.object_id, str.object_id
327 assert ! @parser.keepalive?
330 def test_chunks_bytewise
331 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
332 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
336 assert_equal req, @parser.parse
341 str.each_byte { |byte|
342 assert_nil @parser.filter_body(tmp, buf << byte.chr)
345 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
346 rv = @parser.filter_body(tmp, buf<< "\n")
347 assert_equal rv.object_id, buf.object_id
348 assert ! @parser.keepalive?
354 str << "PUT / HTTP/1.1\r\n" \
355 "Trailer: Content-MD5\r\n" \
356 "transfer-Encoding: chunked\r\n\r\n" \
357 "1\r\na\r\n2\r\n..\r\n0\r\n"
358 assert_equal req, @parser.parse
359 assert_equal 'Content-MD5', req['HTTP_TRAILER']
360 assert_nil req['HTTP_CONTENT_MD5']
362 assert_nil @parser.filter_body(tmp, str)
363 assert_equal 'a..', tmp
364 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
365 rv = @parser.filter_body(tmp, str)
366 assert_equal rv.object_id, str.object_id
368 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
370 assert_nil @parser.trailers(req, str)
371 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
372 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
374 assert_nil @parser.parse
376 assert_equal req, @parser.parse
377 assert_equal "GET / ", str
378 assert @parser.keepalive?
381 def test_trailers_slowly
383 str << "PUT / HTTP/1.1\r\n" \
384 "Trailer: Content-MD5\r\n" \
385 "transfer-Encoding: chunked\r\n\r\n" \
386 "1\r\na\r\n2\r\n..\r\n0\r\n"
388 assert_equal req, @parser.parse
389 assert_equal 'Content-MD5', req['HTTP_TRAILER']
390 assert_nil req['HTTP_CONTENT_MD5']
392 assert_nil @parser.filter_body(tmp, str)
393 assert_equal 'a..', tmp
394 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
395 rv = @parser.filter_body(tmp, str)
396 assert_equal rv.object_id, str.object_id
398 assert_nil @parser.trailers(req, str)
399 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
400 md5_hdr.each_byte { |byte|
402 assert_nil @parser.trailers(req, str)
404 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
405 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
407 assert_nil @parser.parse
409 assert_equal req, @parser.parse
414 str << "PUT / HTTP/1.1\r\n" \
415 "transfer-Encoding: chunked\r\n\r\n" \
416 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
418 assert_equal req, @parser.parse
419 assert_nil @parser.content_length
420 @parser.filter_body('', str)
421 assert ! @parser.keepalive?
425 n = HttpParser::LENGTH_MAX
426 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
428 @parser.headers(req, @parser.buf)
429 assert_equal n, req['CONTENT_LENGTH'].to_i
430 assert ! @parser.keepalive?
433 def test_overflow_chunk
434 n = HttpParser::CHUNK_MAX + 1
437 str << "PUT / HTTP/1.1\r\n" \
438 "transfer-Encoding: chunked\r\n\r\n" \
439 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
440 assert_equal req, @parser.parse
441 assert_nil @parser.content_length
442 assert_raise(HttpParserError) { @parser.filter_body('', str) }
445 def test_overflow_content_length
446 n = HttpParser::LENGTH_MAX + 1
447 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
448 assert_raise(HttpParserError) { @parser.parse }
452 @parser.buf << "PUT / HTTP/1.1\r\n" \
453 "transfer-Encoding: chunked\r\n\r\n" \
454 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
456 assert_equal req, @parser.parse
457 assert_nil @parser.content_length
458 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
461 def test_bad_content_length
462 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
463 assert_raise(HttpParserError) { @parser.parse }
466 def test_bad_trailers
469 str << "PUT / HTTP/1.1\r\n" \
470 "Trailer: Transfer-Encoding\r\n" \
471 "transfer-Encoding: chunked\r\n\r\n" \
472 "1\r\na\r\n2\r\n..\r\n0\r\n"
473 assert_equal req, @parser.parse
474 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
476 assert_nil @parser.filter_body(tmp, str)
477 assert_equal 'a..', tmp
479 str << "Transfer-Encoding: identity\r\n\r\n"
480 assert_raise(HttpParserError) { @parser.parse }
483 def test_repeat_headers
484 str = "PUT / HTTP/1.1\r\n" \
485 "Trailer: Content-MD5\r\n" \
486 "Trailer: Content-SHA1\r\n" \
487 "transfer-Encoding: chunked\r\n\r\n" \
488 "1\r\na\r\n2\r\n..\r\n0\r\n"
491 assert_equal req, @parser.parse
492 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
493 assert ! @parser.keepalive?
496 def test_parse_simple_request
497 parser = HttpParser.new
499 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
500 assert_equal req, parser.parse
501 assert_equal '', parser.buf
503 "SERVER_NAME"=>"localhost",
504 "rack.url_scheme"=>"http",
505 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
506 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
507 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
509 "SERVER_PROTOCOL"=>"HTTP/0.9",
510 "REQUEST_METHOD"=>"GET",
513 assert_equal expect, req
514 assert ! parser.headers?
517 def test_path_info_semicolon
521 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
523 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
524 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
525 "/1;a=b" => { qs => "", pi => "/1;a=b" },
526 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
527 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
528 "*" => { qs => "", pi => "" },
529 }.each do |uri,expect|
530 assert_equal req, @parser.headers(req.clear, str % [ uri ])
533 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
534 assert_equal expect[qs], req[qs], "#{qs} mismatch"
535 assert_equal expect[pi], req[pi], "#{pi} mismatch"
537 uri = URI.parse("http://example.com#{uri}")
538 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
539 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
543 def test_path_info_semicolon_absolute
547 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
549 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
550 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
551 "/1;a=b" => { qs => "", pi => "/1;a=b" },
552 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
553 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
554 }.each do |uri,expect|
555 assert_equal req, @parser.headers(req.clear, str % [ uri ])
558 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
559 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
560 assert_equal expect[qs], req[qs], "#{qs} mismatch"
561 assert_equal expect[pi], req[pi], "#{pi} mismatch"
565 def test_negative_content_length
567 str = "PUT / HTTP/1.1\r\n" \
568 "Content-Length: -1\r\n" \
570 assert_raises(HttpParserError) do
571 @parser.headers(req, str)
575 def test_invalid_content_length
577 str = "PUT / HTTP/1.1\r\n" \
578 "Content-Length: zzzzz\r\n" \
580 assert_raises(HttpParserError) do
581 @parser.headers(req, str)
585 def test_backtrace_is_empty
587 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
588 assert false, "should never get here line:#{__LINE__}"
589 rescue HttpParserError => e
590 assert_equal [], e.backtrace
593 assert false, "should never get here line:#{__LINE__}"
596 def test_ignore_version_header
597 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
599 assert_equal req, @parser.parse
600 assert_equal '', @parser.buf
602 "SERVER_NAME" => "localhost",
603 "rack.url_scheme" => "http",
604 "REQUEST_PATH" => "/",
605 "SERVER_PROTOCOL" => "HTTP/1.1",
607 "HTTP_VERSION" => "HTTP/1.1",
608 "REQUEST_URI" => "/",
609 "SERVER_PORT" => "80",
610 "REQUEST_METHOD" => "GET",
613 assert_equal expect, req
616 def test_pipelined_requests
620 "SERVER_NAME" => host,
621 "REQUEST_PATH" => "/",
622 "rack.url_scheme" => "http",
623 "SERVER_PROTOCOL" => "HTTP/1.1",
625 "HTTP_VERSION" => "HTTP/1.1",
626 "REQUEST_URI" => "/",
627 "SERVER_PORT" => "80",
628 "REQUEST_METHOD" => "GET",
631 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
632 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
633 @parser.buf << (req1 + req2)
634 env1 = @parser.parse.dup
635 assert_equal expect, env1
636 assert_equal req2, @parser.buf
637 assert ! @parser.env.empty?
639 assert @parser.keepalive?
640 assert @parser.headers?
641 assert_equal expect, @parser.env
642 env2 = @parser.parse.dup
643 host.replace "www.example.com"
644 assert_equal "www.example.com", expect["HTTP_HOST"]
645 assert_equal "www.example.com", expect["SERVER_NAME"]
646 assert_equal expect, env2
647 assert_equal "", @parser.buf
650 def test_keepalive_requests_disabled
651 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
653 "SERVER_NAME" => "example.com",
654 "HTTP_HOST" => "example.com",
655 "rack.url_scheme" => "http",
656 "REQUEST_PATH" => "/",
657 "SERVER_PROTOCOL" => "HTTP/1.1",
659 "HTTP_VERSION" => "HTTP/1.1",
660 "REQUEST_URI" => "/",
661 "SERVER_PORT" => "80",
662 "REQUEST_METHOD" => "GET",
665 HttpParser.keepalive_requests = 0
666 @parser = HttpParser.new
668 assert_equal expect, @parser.parse
669 assert ! @parser.next?
674 assert_equal @parser, @parser.dechunk!
675 assert_nil @parser.filter_body(tmp, "6\r\n")
677 assert_nil @parser.filter_body(tmp, "abcdef")
678 assert_equal "abcdef", tmp
679 assert_nil @parser.filter_body(tmp, "\r\n")
682 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
686 def test_chunk_only_bad_align
688 assert_equal @parser, @parser.dechunk!
689 assert_nil @parser.filter_body(tmp, "6\r\na")
690 assert_equal "a", tmp
691 assert_nil @parser.filter_body(tmp, "bcde")
692 assert_equal "bcde", tmp
693 assert_nil @parser.filter_body(tmp, "f\r")
694 assert_equal "f", tmp
696 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
700 def test_chunk_only_reset_ok
702 assert_equal @parser, @parser.dechunk!
703 src = "1\r\na\r\n0\r\n\r\n"
704 assert_nil @parser.filter_body(tmp, src)
705 assert_equal "a", tmp
706 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
708 assert_equal @parser, @parser.dechunk!
710 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
712 assert_equal src, @parser.filter_body(tmp, src)