1 # -*- encoding: binary -*-
3 require 'test/test_helper'
8 class HttpParserNgTest < Test::Unit::TestCase
11 @parser = HttpParser.new
15 r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
18 @parser.response_start_sent = true
19 assert @parser.keepalive?
21 assert @parser.response_start_sent
23 # persistent client makes another request:
26 assert @parser.keepalive?
28 assert_equal false, @parser.response_start_sent
31 def test_connection_TE
32 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
33 @parser.buf << "TE: trailers\r\n\r\n"
35 assert @parser.keepalive?
39 def test_keepalive_requests_with_next?
40 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
42 "SERVER_NAME" => "example.com",
43 "HTTP_HOST" => "example.com",
44 "rack.url_scheme" => "http",
45 "REQUEST_PATH" => "/",
46 "SERVER_PROTOCOL" => "HTTP/1.1",
48 "HTTP_VERSION" => "HTTP/1.1",
50 "SERVER_PORT" => "80",
51 "REQUEST_METHOD" => "GET",
56 assert_equal expect, @parser.parse
61 def test_default_keepalive_is_off
62 assert ! @parser.keepalive?
63 assert ! @parser.next?
64 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
66 assert @parser.keepalive?
68 assert ! @parser.keepalive?
69 assert ! @parser.next?
72 def test_identity_byte_headers
74 str = "PUT / HTTP/1.1\r\n"
75 str << "Content-Length: 123\r\n"
78 str.each_byte { |byte|
80 assert_nil @parser.parse
83 assert_equal req.object_id, @parser.parse.object_id
84 assert_equal '123', req['CONTENT_LENGTH']
85 assert_equal 0, hdr.size
86 assert ! @parser.keepalive?
87 assert @parser.headers?
88 assert_equal 123, @parser.content_length
91 @parser.filter_body(dst, buf)
92 assert_equal '.' * 123, dst
94 assert @parser.keepalive?
97 def test_identity_step_headers
100 str << "PUT / HTTP/1.1\r\n"
101 assert ! @parser.parse
102 str << "Content-Length: 123\r\n"
103 assert ! @parser.parse
105 assert_equal req.object_id, @parser.parse.object_id
106 assert_equal '123', req['CONTENT_LENGTH']
107 assert_equal 0, str.size
108 assert ! @parser.keepalive?
109 assert @parser.headers?
112 @parser.filter_body(dst, buf)
113 assert_equal '.' * 123, dst
115 assert @parser.keepalive?
118 def test_identity_oneshot_header
121 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
122 assert_equal req.object_id, @parser.parse.object_id
123 assert_equal '123', req['CONTENT_LENGTH']
124 assert_equal 0, str.size
125 assert ! @parser.keepalive?
126 assert @parser.headers?
129 @parser.filter_body(dst, buf)
130 assert_equal '.' * 123, dst
134 def test_identity_oneshot_header_with_body
135 body = ('a' * 123).freeze
138 str << "PUT / HTTP/1.1\r\n" \
139 "Content-Length: #{body.length}\r\n" \
141 assert_equal req.object_id, @parser.parse.object_id
142 assert_equal '123', req['CONTENT_LENGTH']
143 assert_equal 123, str.size
144 assert_equal body, str
146 assert_nil @parser.filter_body(tmp, str)
147 assert_equal 0, str.size
148 assert_equal tmp, body
149 assert_equal "", @parser.filter_body(tmp, str)
150 assert @parser.keepalive?
153 def test_identity_oneshot_header_with_body_partial
155 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
156 assert_equal Hash, @parser.parse.class
157 assert_equal 1, str.size
158 assert_equal 'a', str
160 assert_nil @parser.filter_body(tmp, str)
162 assert_equal "a", tmp
164 rv = @parser.filter_body(tmp, str)
165 assert_equal 122, tmp.size
168 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
169 assert @parser.keepalive?
172 def test_identity_oneshot_header_with_body_slop
174 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
175 assert_equal Hash, @parser.parse.class
176 assert_equal 2, str.size
177 assert_equal 'aG', str
179 assert_nil @parser.filter_body(tmp, str)
180 assert_equal "G", str
181 assert_equal "G", @parser.filter_body(tmp, str)
182 assert_equal 1, tmp.size
183 assert_equal "a", tmp
184 assert @parser.keepalive?
190 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
191 assert_equal req, @parser.parse, "msg=#{str}"
192 assert_equal 0, str.size
194 assert_nil @parser.filter_body(tmp, str << "6")
195 assert_equal 0, tmp.size
196 assert_nil @parser.filter_body(tmp, str << "\r\n")
197 assert_equal 0, str.size
198 assert_equal 0, tmp.size
200 assert_nil @parser.filter_body(tmp, str << "..")
201 assert_equal "..", tmp
202 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
203 assert_equal "abcd", tmp
204 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
205 assert_equal "PUT", str
206 assert ! @parser.keepalive?
207 str << "TY: FOO\r\n\r\n"
208 assert_equal req, @parser.parse
209 assert_equal "FOO", req["HTTP_PUTTY"]
210 assert @parser.keepalive?
213 def test_chunked_empty
216 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
217 assert_equal req, @parser.parse, "msg=#{str}"
218 assert_equal 0, str.size
220 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
226 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
228 assert_equal req, @parser.parse
229 assert_equal 0, str.size
231 assert_nil @parser.filter_body(tmp, str << "6")
232 assert_equal 0, tmp.size
233 assert_nil @parser.filter_body(tmp, str << "\r\n")
235 assert_equal 0, tmp.size
237 assert_nil @parser.filter_body(tmp, str << "..")
238 assert_equal 2, tmp.size
239 assert_equal "..", tmp
240 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
241 assert_equal "abcd", tmp
242 assert_nil @parser.filter_body(tmp, str << "\r")
244 assert_nil @parser.filter_body(tmp, str << "\n")
246 assert_nil @parser.filter_body(tmp, str << "z")
247 assert_equal "z", tmp
248 assert_nil @parser.filter_body(tmp, str << "\r\n")
249 assert_nil @parser.filter_body(tmp, str << "0")
250 assert_nil @parser.filter_body(tmp, str << "\r")
251 rv = @parser.filter_body(tmp, str << "\nGET")
252 assert_equal "GET", rv
253 assert_equal str.object_id, rv.object_id
254 assert ! @parser.keepalive?
259 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
262 assert_equal req, @parser.parse
264 assert_nil @parser.filter_body(tmp, str)
267 assert_nil @parser.filter_body(tmp, str)
270 assert_nil @parser.filter_body(tmp, str)
272 assert ! @parser.body_eof?
273 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
275 assert @parser.body_eof?
277 assert_equal req, @parser.parse
279 assert @parser.body_eof?
280 assert @parser.keepalive?
283 def test_two_chunks_oneshot
286 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
287 "1\r\na\r\n2\r\n..\r\n0\r\n"
288 assert_equal req, @parser.parse
290 assert_nil @parser.filter_body(tmp, str)
291 assert_equal 'a..', tmp
292 rv = @parser.filter_body(tmp, str)
293 assert_equal rv.object_id, str.object_id
294 assert ! @parser.keepalive?
297 def test_chunks_bytewise
298 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
299 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
303 assert_equal req, @parser.parse
308 str.each_byte { |byte|
309 assert_nil @parser.filter_body(tmp, buf << byte.chr)
312 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
313 rv = @parser.filter_body(tmp, buf<< "\n")
314 assert_equal rv.object_id, buf.object_id
315 assert ! @parser.keepalive?
321 str << "PUT / HTTP/1.1\r\n" \
322 "Trailer: Content-MD5\r\n" \
323 "transfer-Encoding: chunked\r\n\r\n" \
324 "1\r\na\r\n2\r\n..\r\n0\r\n"
325 assert_equal req, @parser.parse
326 assert_equal 'Content-MD5', req['HTTP_TRAILER']
327 assert_nil req['HTTP_CONTENT_MD5']
329 assert_nil @parser.filter_body(tmp, str)
330 assert_equal 'a..', tmp
331 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
332 rv = @parser.filter_body(tmp, str)
333 assert_equal rv.object_id, str.object_id
335 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
337 assert_nil @parser.trailers(req, str)
338 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
339 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
341 assert_nil @parser.parse
343 assert_equal req, @parser.parse
344 assert_equal "GET / ", str
345 assert @parser.keepalive?
348 def test_trailers_slowly
350 str << "PUT / HTTP/1.1\r\n" \
351 "Trailer: Content-MD5\r\n" \
352 "transfer-Encoding: chunked\r\n\r\n" \
353 "1\r\na\r\n2\r\n..\r\n0\r\n"
355 assert_equal req, @parser.parse
356 assert_equal 'Content-MD5', req['HTTP_TRAILER']
357 assert_nil req['HTTP_CONTENT_MD5']
359 assert_nil @parser.filter_body(tmp, str)
360 assert_equal 'a..', tmp
361 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
362 rv = @parser.filter_body(tmp, str)
363 assert_equal rv.object_id, str.object_id
365 assert_nil @parser.trailers(req, str)
366 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
367 md5_hdr.each_byte { |byte|
369 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
381 str << "PUT / HTTP/1.1\r\n" \
382 "transfer-Encoding: chunked\r\n\r\n" \
383 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
385 assert_equal req, @parser.parse
386 assert_nil @parser.content_length
387 @parser.filter_body('', str)
388 assert ! @parser.keepalive?
392 n = HttpParser::LENGTH_MAX
393 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
395 @parser.headers(req, @parser.buf)
396 assert_equal n, req['CONTENT_LENGTH'].to_i
397 assert ! @parser.keepalive?
400 def test_overflow_chunk
401 n = HttpParser::CHUNK_MAX + 1
404 str << "PUT / HTTP/1.1\r\n" \
405 "transfer-Encoding: chunked\r\n\r\n" \
406 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
407 assert_equal req, @parser.parse
408 assert_nil @parser.content_length
409 assert_raise(HttpParserError) { @parser.filter_body('', str) }
412 def test_overflow_content_length
413 n = HttpParser::LENGTH_MAX + 1
414 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
415 assert_raise(HttpParserError) { @parser.parse }
419 @parser.buf << "PUT / HTTP/1.1\r\n" \
420 "transfer-Encoding: chunked\r\n\r\n" \
421 "#zzz\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("", @parser.buf) }
428 def test_bad_content_length
429 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
430 assert_raise(HttpParserError) { @parser.parse }
433 def test_bad_trailers
436 str << "PUT / HTTP/1.1\r\n" \
437 "Trailer: Transfer-Encoding\r\n" \
438 "transfer-Encoding: chunked\r\n\r\n" \
439 "1\r\na\r\n2\r\n..\r\n0\r\n"
440 assert_equal req, @parser.parse
441 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
443 assert_nil @parser.filter_body(tmp, str)
444 assert_equal 'a..', tmp
446 str << "Transfer-Encoding: identity\r\n\r\n"
447 assert_raise(HttpParserError) { @parser.parse }
450 def test_repeat_headers
451 str = "PUT / HTTP/1.1\r\n" \
452 "Trailer: Content-MD5\r\n" \
453 "Trailer: Content-SHA1\r\n" \
454 "transfer-Encoding: chunked\r\n\r\n" \
455 "1\r\na\r\n2\r\n..\r\n0\r\n"
458 assert_equal req, @parser.parse
459 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
460 assert ! @parser.keepalive?
463 def test_parse_simple_request
464 parser = HttpParser.new
466 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
467 assert_equal req, parser.parse
468 assert_equal '', parser.buf
470 "SERVER_NAME"=>"localhost",
471 "rack.url_scheme"=>"http",
472 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
473 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
474 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
476 "SERVER_PROTOCOL"=>"HTTP/0.9",
477 "REQUEST_METHOD"=>"GET",
480 assert_equal expect, req
481 assert ! parser.headers?
484 def test_path_info_semicolon
488 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
490 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
491 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
492 "/1;a=b" => { qs => "", pi => "/1;a=b" },
493 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
494 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
495 "*" => { qs => "", pi => "" },
496 }.each do |uri,expect|
497 assert_equal req, @parser.headers(req.clear, str % [ uri ])
500 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
501 assert_equal expect[qs], req[qs], "#{qs} mismatch"
502 assert_equal expect[pi], req[pi], "#{pi} mismatch"
504 uri = URI.parse("http://example.com#{uri}")
505 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
506 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
510 def test_path_info_semicolon_absolute
514 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
516 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
517 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
518 "/1;a=b" => { qs => "", pi => "/1;a=b" },
519 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
520 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
521 }.each do |uri,expect|
522 assert_equal req, @parser.headers(req.clear, str % [ uri ])
525 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
526 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
527 assert_equal expect[qs], req[qs], "#{qs} mismatch"
528 assert_equal expect[pi], req[pi], "#{pi} mismatch"
532 def test_negative_content_length
534 str = "PUT / HTTP/1.1\r\n" \
535 "Content-Length: -1\r\n" \
537 assert_raises(HttpParserError) do
538 @parser.headers(req, str)
542 def test_invalid_content_length
544 str = "PUT / HTTP/1.1\r\n" \
545 "Content-Length: zzzzz\r\n" \
547 assert_raises(HttpParserError) do
548 @parser.headers(req, str)
552 def test_backtrace_is_empty
554 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
555 assert false, "should never get here line:#{__LINE__}"
556 rescue HttpParserError => e
557 assert_equal [], e.backtrace
560 assert false, "should never get here line:#{__LINE__}"
563 def test_ignore_version_header
564 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
566 assert_equal req, @parser.parse
567 assert_equal '', @parser.buf
569 "SERVER_NAME" => "localhost",
570 "rack.url_scheme" => "http",
571 "REQUEST_PATH" => "/",
572 "SERVER_PROTOCOL" => "HTTP/1.1",
574 "HTTP_VERSION" => "HTTP/1.1",
575 "REQUEST_URI" => "/",
576 "SERVER_PORT" => "80",
577 "REQUEST_METHOD" => "GET",
580 assert_equal expect, req
583 def test_pipelined_requests
587 "SERVER_NAME" => host,
588 "REQUEST_PATH" => "/",
589 "rack.url_scheme" => "http",
590 "SERVER_PROTOCOL" => "HTTP/1.1",
592 "HTTP_VERSION" => "HTTP/1.1",
593 "REQUEST_URI" => "/",
594 "SERVER_PORT" => "80",
595 "REQUEST_METHOD" => "GET",
598 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
599 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
600 @parser.buf << (req1 + req2)
601 env1 = @parser.parse.dup
602 assert_equal expect, env1
603 assert_equal req2, @parser.buf
604 assert ! @parser.env.empty?
606 assert @parser.keepalive?
607 assert @parser.headers?
608 assert_equal expect, @parser.env
609 env2 = @parser.parse.dup
610 host.replace "www.example.com"
611 assert_equal "www.example.com", expect["HTTP_HOST"]
612 assert_equal "www.example.com", expect["SERVER_NAME"]
613 assert_equal expect, env2
614 assert_equal "", @parser.buf
619 assert_equal @parser, @parser.dechunk!
620 assert_nil @parser.filter_body(tmp, "6\r\n")
622 assert_nil @parser.filter_body(tmp, "abcdef")
623 assert_equal "abcdef", tmp
624 assert_nil @parser.filter_body(tmp, "\r\n")
627 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
631 def test_chunk_only_bad_align
633 assert_equal @parser, @parser.dechunk!
634 assert_nil @parser.filter_body(tmp, "6\r\na")
635 assert_equal "a", tmp
636 assert_nil @parser.filter_body(tmp, "bcde")
637 assert_equal "bcde", tmp
638 assert_nil @parser.filter_body(tmp, "f\r")
639 assert_equal "f", tmp
641 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
645 def test_chunk_only_reset_ok
647 assert_equal @parser, @parser.dechunk!
648 src = "1\r\na\r\n0\r\n\r\n"
649 assert_nil @parser.filter_body(tmp, src)
650 assert_equal "a", tmp
651 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
653 assert_equal @parser, @parser.dechunk!
655 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
657 assert_equal src, @parser.filter_body(tmp, src)