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"
37 assert_nothing_raised { @parser.parse }
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 assert_nothing_raised do
98 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
101 assert @parser.keepalive?
103 assert ! @parser.keepalive?
104 assert ! @parser.next?
107 def test_identity_byte_headers
109 str = "PUT / HTTP/1.1\r\n"
110 str << "Content-Length: 123\r\n"
113 str.each_byte { |byte|
115 assert_nil @parser.parse
118 assert_equal req.object_id, @parser.parse.object_id
119 assert_equal '123', req['CONTENT_LENGTH']
120 assert_equal 0, hdr.size
121 assert ! @parser.keepalive?
122 assert @parser.headers?
123 assert_equal 123, @parser.content_length
126 @parser.filter_body(dst, buf)
127 assert_equal '.' * 123, dst
129 assert @parser.keepalive?
132 def test_identity_step_headers
135 str << "PUT / HTTP/1.1\r\n"
136 assert ! @parser.parse
137 str << "Content-Length: 123\r\n"
138 assert ! @parser.parse
140 assert_equal req.object_id, @parser.parse.object_id
141 assert_equal '123', req['CONTENT_LENGTH']
142 assert_equal 0, str.size
143 assert ! @parser.keepalive?
144 assert @parser.headers?
147 @parser.filter_body(dst, buf)
148 assert_equal '.' * 123, dst
150 assert @parser.keepalive?
153 def test_identity_oneshot_header
156 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
157 assert_equal req.object_id, @parser.parse.object_id
158 assert_equal '123', req['CONTENT_LENGTH']
159 assert_equal 0, str.size
160 assert ! @parser.keepalive?
161 assert @parser.headers?
164 @parser.filter_body(dst, buf)
165 assert_equal '.' * 123, dst
169 def test_identity_oneshot_header_with_body
170 body = ('a' * 123).freeze
173 str << "PUT / HTTP/1.1\r\n" \
174 "Content-Length: #{body.length}\r\n" \
176 assert_equal req.object_id, @parser.parse.object_id
177 assert_equal '123', req['CONTENT_LENGTH']
178 assert_equal 123, str.size
179 assert_equal body, str
181 assert_nil @parser.filter_body(tmp, str)
182 assert_equal 0, str.size
183 assert_equal tmp, body
184 assert_equal "", @parser.filter_body(tmp, str)
185 assert @parser.keepalive?
188 def test_identity_oneshot_header_with_body_partial
190 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
191 assert_equal Hash, @parser.parse.class
192 assert_equal 1, str.size
193 assert_equal 'a', str
195 assert_nil @parser.filter_body(tmp, str)
197 assert_equal "a", tmp
199 rv = @parser.filter_body(tmp, str)
200 assert_equal 122, tmp.size
203 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
204 assert @parser.keepalive?
207 def test_identity_oneshot_header_with_body_slop
209 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
210 assert_equal Hash, @parser.parse.class
211 assert_equal 2, str.size
212 assert_equal 'aG', str
214 assert_nil @parser.filter_body(tmp, str)
215 assert_equal "G", str
216 assert_equal "G", @parser.filter_body(tmp, str)
217 assert_equal 1, tmp.size
218 assert_equal "a", tmp
219 assert @parser.keepalive?
225 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
226 assert_equal req, @parser.parse, "msg=#{str}"
227 assert_equal 0, str.size
229 assert_nil @parser.filter_body(tmp, str << "6")
230 assert_equal 0, tmp.size
231 assert_nil @parser.filter_body(tmp, str << "\r\n")
232 assert_equal 0, str.size
233 assert_equal 0, tmp.size
235 assert_nil @parser.filter_body(tmp, str << "..")
236 assert_equal "..", tmp
237 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
238 assert_equal "abcd", tmp
239 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
240 assert_equal "PUT", str
241 assert ! @parser.keepalive?
242 str << "TY: FOO\r\n\r\n"
243 assert_equal req, @parser.parse
244 assert_equal "FOO", req["HTTP_PUTTY"]
245 assert @parser.keepalive?
248 def test_chunked_empty
251 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
252 assert_equal req, @parser.parse, "msg=#{str}"
253 assert_equal 0, str.size
255 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
261 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
263 assert_equal req, @parser.parse
264 assert_equal 0, str.size
266 assert_nil @parser.filter_body(tmp, str << "6")
267 assert_equal 0, tmp.size
268 assert_nil @parser.filter_body(tmp, str << "\r\n")
270 assert_equal 0, tmp.size
272 assert_nil @parser.filter_body(tmp, str << "..")
273 assert_equal 2, tmp.size
274 assert_equal "..", tmp
275 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
276 assert_equal "abcd", tmp
277 assert_nil @parser.filter_body(tmp, str << "\r")
279 assert_nil @parser.filter_body(tmp, str << "\n")
281 assert_nil @parser.filter_body(tmp, str << "z")
282 assert_equal "z", tmp
283 assert_nil @parser.filter_body(tmp, str << "\r\n")
284 assert_nil @parser.filter_body(tmp, str << "0")
285 assert_nil @parser.filter_body(tmp, str << "\r")
286 rv = @parser.filter_body(tmp, str << "\nGET")
287 assert_equal "GET", rv
288 assert_equal str.object_id, rv.object_id
289 assert ! @parser.keepalive?
294 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
297 assert_equal req, @parser.parse
299 assert_nil @parser.filter_body(tmp, str)
302 assert_nil @parser.filter_body(tmp, str)
305 assert_nil @parser.filter_body(tmp, str)
307 assert ! @parser.body_eof?
308 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
310 assert @parser.body_eof?
312 assert_equal req, @parser.parse
314 assert @parser.body_eof?
315 assert @parser.keepalive?
318 def test_two_chunks_oneshot
321 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
322 "1\r\na\r\n2\r\n..\r\n0\r\n"
323 assert_equal req, @parser.parse
325 assert_nil @parser.filter_body(tmp, str)
326 assert_equal 'a..', tmp
327 rv = @parser.filter_body(tmp, str)
328 assert_equal rv.object_id, str.object_id
329 assert ! @parser.keepalive?
332 def test_chunks_bytewise
333 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
334 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
338 assert_equal req, @parser.parse
343 str.each_byte { |byte|
344 assert_nil @parser.filter_body(tmp, buf << byte.chr)
347 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
348 rv = @parser.filter_body(tmp, buf<< "\n")
349 assert_equal rv.object_id, buf.object_id
350 assert ! @parser.keepalive?
356 str << "PUT / HTTP/1.1\r\n" \
357 "Trailer: Content-MD5\r\n" \
358 "transfer-Encoding: chunked\r\n\r\n" \
359 "1\r\na\r\n2\r\n..\r\n0\r\n"
360 assert_equal req, @parser.parse
361 assert_equal 'Content-MD5', req['HTTP_TRAILER']
362 assert_nil req['HTTP_CONTENT_MD5']
364 assert_nil @parser.filter_body(tmp, str)
365 assert_equal 'a..', tmp
366 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
367 rv = @parser.filter_body(tmp, str)
368 assert_equal rv.object_id, str.object_id
370 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
372 assert_nil @parser.trailers(req, str)
373 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
374 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
376 assert_nil @parser.parse
378 assert_equal req, @parser.parse
379 assert_equal "GET / ", str
380 assert @parser.keepalive?
383 def test_trailers_slowly
385 str << "PUT / HTTP/1.1\r\n" \
386 "Trailer: Content-MD5\r\n" \
387 "transfer-Encoding: chunked\r\n\r\n" \
388 "1\r\na\r\n2\r\n..\r\n0\r\n"
390 assert_equal req, @parser.parse
391 assert_equal 'Content-MD5', req['HTTP_TRAILER']
392 assert_nil req['HTTP_CONTENT_MD5']
394 assert_nil @parser.filter_body(tmp, str)
395 assert_equal 'a..', tmp
396 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
397 rv = @parser.filter_body(tmp, str)
398 assert_equal rv.object_id, str.object_id
400 assert_nil @parser.trailers(req, str)
401 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
402 md5_hdr.each_byte { |byte|
404 assert_nil @parser.trailers(req, str)
406 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
407 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
409 assert_nil @parser.parse
411 assert_equal req, @parser.parse
416 str << "PUT / HTTP/1.1\r\n" \
417 "transfer-Encoding: chunked\r\n\r\n" \
418 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
420 assert_equal req, @parser.parse
421 assert_nil @parser.content_length
422 assert_nothing_raised { @parser.filter_body('', str) }
423 assert ! @parser.keepalive?
427 n = HttpParser::LENGTH_MAX
428 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
430 assert_nothing_raised { @parser.headers(req, @parser.buf) }
431 assert_equal n, req['CONTENT_LENGTH'].to_i
432 assert ! @parser.keepalive?
435 def test_overflow_chunk
436 n = HttpParser::CHUNK_MAX + 1
439 str << "PUT / HTTP/1.1\r\n" \
440 "transfer-Encoding: chunked\r\n\r\n" \
441 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
442 assert_equal req, @parser.parse
443 assert_nil @parser.content_length
444 assert_raise(HttpParserError) { @parser.filter_body('', str) }
447 def test_overflow_content_length
448 n = HttpParser::LENGTH_MAX + 1
449 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
450 assert_raise(HttpParserError) { @parser.parse }
454 @parser.buf << "PUT / HTTP/1.1\r\n" \
455 "transfer-Encoding: chunked\r\n\r\n" \
456 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
458 assert_equal req, @parser.parse
459 assert_nil @parser.content_length
460 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
463 def test_bad_content_length
464 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
465 assert_raise(HttpParserError) { @parser.parse }
468 def test_bad_trailers
471 str << "PUT / HTTP/1.1\r\n" \
472 "Trailer: Transfer-Encoding\r\n" \
473 "transfer-Encoding: chunked\r\n\r\n" \
474 "1\r\na\r\n2\r\n..\r\n0\r\n"
475 assert_equal req, @parser.parse
476 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
478 assert_nil @parser.filter_body(tmp, str)
479 assert_equal 'a..', tmp
481 str << "Transfer-Encoding: identity\r\n\r\n"
482 assert_raise(HttpParserError) { @parser.parse }
485 def test_repeat_headers
486 str = "PUT / HTTP/1.1\r\n" \
487 "Trailer: Content-MD5\r\n" \
488 "Trailer: Content-SHA1\r\n" \
489 "transfer-Encoding: chunked\r\n\r\n" \
490 "1\r\na\r\n2\r\n..\r\n0\r\n"
493 assert_equal req, @parser.parse
494 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
495 assert ! @parser.keepalive?
498 def test_parse_simple_request
499 parser = HttpParser.new
501 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
502 assert_equal req, parser.parse
503 assert_equal '', parser.buf
505 "SERVER_NAME"=>"localhost",
506 "rack.url_scheme"=>"http",
507 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
508 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
509 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
511 "SERVER_PROTOCOL"=>"HTTP/0.9",
512 "REQUEST_METHOD"=>"GET",
515 assert_equal expect, req
516 assert ! parser.headers?
519 def test_path_info_semicolon
523 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
525 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
526 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
527 "/1;a=b" => { qs => "", pi => "/1;a=b" },
528 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
529 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
530 "*" => { qs => "", pi => "" },
531 }.each do |uri,expect|
532 assert_equal req, @parser.headers(req.clear, str % [ uri ])
535 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
536 assert_equal expect[qs], req[qs], "#{qs} mismatch"
537 assert_equal expect[pi], req[pi], "#{pi} mismatch"
539 uri = URI.parse("http://example.com#{uri}")
540 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
541 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
545 def test_path_info_semicolon_absolute
549 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
551 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
552 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
553 "/1;a=b" => { qs => "", pi => "/1;a=b" },
554 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
555 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
556 }.each do |uri,expect|
557 assert_equal req, @parser.headers(req.clear, str % [ uri ])
560 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
561 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
562 assert_equal expect[qs], req[qs], "#{qs} mismatch"
563 assert_equal expect[pi], req[pi], "#{pi} mismatch"
567 def test_negative_content_length
569 str = "PUT / HTTP/1.1\r\n" \
570 "Content-Length: -1\r\n" \
572 assert_raises(HttpParserError) do
573 @parser.headers(req, str)
577 def test_invalid_content_length
579 str = "PUT / HTTP/1.1\r\n" \
580 "Content-Length: zzzzz\r\n" \
582 assert_raises(HttpParserError) do
583 @parser.headers(req, str)
587 def test_backtrace_is_empty
589 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
590 assert false, "should never get here line:#{__LINE__}"
591 rescue HttpParserError => e
592 assert_equal [], e.backtrace
595 assert false, "should never get here line:#{__LINE__}"
598 def test_ignore_version_header
599 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
601 assert_equal req, @parser.parse
602 assert_equal '', @parser.buf
604 "SERVER_NAME" => "localhost",
605 "rack.url_scheme" => "http",
606 "REQUEST_PATH" => "/",
607 "SERVER_PROTOCOL" => "HTTP/1.1",
609 "HTTP_VERSION" => "HTTP/1.1",
610 "REQUEST_URI" => "/",
611 "SERVER_PORT" => "80",
612 "REQUEST_METHOD" => "GET",
615 assert_equal expect, req
618 def test_pipelined_requests
622 "SERVER_NAME" => host,
623 "REQUEST_PATH" => "/",
624 "rack.url_scheme" => "http",
625 "SERVER_PROTOCOL" => "HTTP/1.1",
627 "HTTP_VERSION" => "HTTP/1.1",
628 "REQUEST_URI" => "/",
629 "SERVER_PORT" => "80",
630 "REQUEST_METHOD" => "GET",
633 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
634 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
635 @parser.buf << (req1 + req2)
636 env1 = @parser.parse.dup
637 assert_equal expect, env1
638 assert_equal req2, @parser.buf
639 assert ! @parser.env.empty?
641 assert @parser.keepalive?
642 assert @parser.headers?
643 assert_equal expect, @parser.env
644 env2 = @parser.parse.dup
645 host.replace "www.example.com"
646 assert_equal "www.example.com", expect["HTTP_HOST"]
647 assert_equal "www.example.com", expect["SERVER_NAME"]
648 assert_equal expect, env2
649 assert_equal "", @parser.buf
652 def test_keepalive_requests_disabled
653 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
655 "SERVER_NAME" => "example.com",
656 "HTTP_HOST" => "example.com",
657 "rack.url_scheme" => "http",
658 "REQUEST_PATH" => "/",
659 "SERVER_PROTOCOL" => "HTTP/1.1",
661 "HTTP_VERSION" => "HTTP/1.1",
662 "REQUEST_URI" => "/",
663 "SERVER_PORT" => "80",
664 "REQUEST_METHOD" => "GET",
667 HttpParser.keepalive_requests = 0
668 @parser = HttpParser.new
670 assert_equal expect, @parser.parse
671 assert ! @parser.next?
676 assert_equal @parser, @parser.dechunk!
677 assert_nil @parser.filter_body(tmp, "6\r\n")
679 assert_nil @parser.filter_body(tmp, "abcdef")
680 assert_equal "abcdef", tmp
681 assert_nil @parser.filter_body(tmp, "\r\n")
684 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
688 def test_chunk_only_bad_align
690 assert_equal @parser, @parser.dechunk!
691 assert_nil @parser.filter_body(tmp, "6\r\na")
692 assert_equal "a", tmp
693 assert_nil @parser.filter_body(tmp, "bcde")
694 assert_equal "bcde", tmp
695 assert_nil @parser.filter_body(tmp, "f\r")
696 assert_equal "f", tmp
698 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
702 def test_chunk_only_reset_ok
704 assert_equal @parser, @parser.dechunk!
705 src = "1\r\na\r\n0\r\n\r\n"
706 assert_nil @parser.filter_body(tmp, src)
707 assert_equal "a", tmp
708 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
710 assert_equal @parser, @parser.dechunk!
712 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
714 assert_equal src, @parser.filter_body(tmp, src)