1 # -*- encoding: binary -*-
3 require './test/test_helper'
8 class HttpParserNgTest < Test::Unit::TestCase
11 @parser = HttpParser.new
14 def test_parser_max_len
15 assert_raises(RangeError) do
16 HttpParser.max_header_len = 0xffffffff + 1
21 r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
24 @parser.response_start_sent = true
25 assert @parser.keepalive?
27 assert @parser.response_start_sent
29 # persistent client makes another request:
32 assert @parser.keepalive?
34 assert_equal false, @parser.response_start_sent
37 def test_connection_TE
38 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
39 @parser.buf << "TE: trailers\r\n\r\n"
41 assert @parser.keepalive?
45 def test_keepalive_requests_with_next?
46 req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
48 "SERVER_NAME" => "example.com",
49 "HTTP_HOST" => "example.com",
50 "rack.url_scheme" => "http",
51 "REQUEST_PATH" => "/",
52 "SERVER_PROTOCOL" => "HTTP/1.1",
54 "HTTP_VERSION" => "HTTP/1.1",
56 "SERVER_PORT" => "80",
57 "REQUEST_METHOD" => "GET",
62 assert_equal expect, @parser.parse
67 def test_default_keepalive_is_off
68 assert ! @parser.keepalive?
69 assert ! @parser.next?
70 @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
72 assert @parser.keepalive?
74 assert ! @parser.keepalive?
75 assert ! @parser.next?
78 def test_identity_byte_headers
80 str = "PUT / HTTP/1.1\r\n"
81 str << "Content-Length: 123\r\n"
84 str.each_byte { |byte|
86 assert_nil @parser.parse
89 assert_equal req.object_id, @parser.parse.object_id
90 assert_equal '123', req['CONTENT_LENGTH']
91 assert_equal 0, hdr.size
92 assert ! @parser.keepalive?
93 assert @parser.headers?
94 assert_equal 123, @parser.content_length
97 @parser.filter_body(dst, buf)
98 assert_equal '.' * 123, dst
100 assert @parser.keepalive?
103 def test_identity_step_headers
106 str << "PUT / HTTP/1.1\r\n"
107 assert ! @parser.parse
108 str << "Content-Length: 123\r\n"
109 assert ! @parser.parse
111 assert_equal req.object_id, @parser.parse.object_id
112 assert_equal '123', req['CONTENT_LENGTH']
113 assert_equal 0, str.size
114 assert ! @parser.keepalive?
115 assert @parser.headers?
118 @parser.filter_body(dst, buf)
119 assert_equal '.' * 123, dst
121 assert @parser.keepalive?
124 def test_identity_oneshot_header
127 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
128 assert_equal req.object_id, @parser.parse.object_id
129 assert_equal '123', req['CONTENT_LENGTH']
130 assert_equal 0, str.size
131 assert ! @parser.keepalive?
132 assert @parser.headers?
135 @parser.filter_body(dst, buf)
136 assert_equal '.' * 123, dst
140 def test_identity_oneshot_header_with_body
141 body = ('a' * 123).freeze
144 str << "PUT / HTTP/1.1\r\n" \
145 "Content-Length: #{body.length}\r\n" \
147 assert_equal req.object_id, @parser.parse.object_id
148 assert_equal '123', req['CONTENT_LENGTH']
149 assert_equal 123, str.size
150 assert_equal body, str
152 assert_nil @parser.filter_body(tmp, str)
153 assert_equal 0, str.size
154 assert_equal tmp, body
155 assert_equal "", @parser.filter_body(tmp, str)
156 assert @parser.keepalive?
159 def test_identity_oneshot_header_with_body_partial
161 str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
162 assert_equal Hash, @parser.parse.class
163 assert_equal 1, str.size
164 assert_equal 'a', str
166 assert_nil @parser.filter_body(tmp, str)
168 assert_equal "a", tmp
170 rv = @parser.filter_body(tmp, str)
171 assert_equal 122, tmp.size
174 assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
175 assert @parser.keepalive?
178 def test_identity_oneshot_header_with_body_slop
180 str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
181 assert_equal Hash, @parser.parse.class
182 assert_equal 2, str.size
183 assert_equal 'aG', str
185 assert_nil @parser.filter_body(tmp, str)
186 assert_equal "G", str
187 assert_equal "G", @parser.filter_body(tmp, str)
188 assert_equal 1, tmp.size
189 assert_equal "a", tmp
190 assert @parser.keepalive?
196 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
197 assert_equal req, @parser.parse, "msg=#{str}"
198 assert_equal 0, str.size
200 assert_nil @parser.filter_body(tmp, str << "6")
201 assert_equal 0, tmp.size
202 assert_nil @parser.filter_body(tmp, str << "\r\n")
203 assert_equal 0, str.size
204 assert_equal 0, tmp.size
206 assert_nil @parser.filter_body(tmp, str << "..")
207 assert_equal "..", tmp
208 assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
209 assert_equal "abcd", tmp
210 assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
211 assert_equal "PUT", str
212 assert ! @parser.keepalive?
213 str << "TY: FOO\r\n\r\n"
214 assert_equal req, @parser.parse
215 assert_equal "FOO", req["HTTP_PUTTY"]
216 assert @parser.keepalive?
219 def test_chunked_empty
222 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
223 assert_equal req, @parser.parse, "msg=#{str}"
224 assert_equal 0, str.size
226 assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
232 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
234 assert_equal req, @parser.parse
235 assert_equal 0, str.size
237 assert_nil @parser.filter_body(tmp, str << "6")
238 assert_equal 0, tmp.size
239 assert_nil @parser.filter_body(tmp, str << "\r\n")
241 assert_equal 0, tmp.size
243 assert_nil @parser.filter_body(tmp, str << "..")
244 assert_equal 2, tmp.size
245 assert_equal "..", tmp
246 assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
247 assert_equal "abcd", tmp
248 assert_nil @parser.filter_body(tmp, str << "\r")
250 assert_nil @parser.filter_body(tmp, str << "\n")
252 assert_nil @parser.filter_body(tmp, str << "z")
253 assert_equal "z", tmp
254 assert_nil @parser.filter_body(tmp, str << "\r\n")
255 assert_nil @parser.filter_body(tmp, str << "0")
256 assert_nil @parser.filter_body(tmp, str << "\r")
257 rv = @parser.filter_body(tmp, str << "\nGET")
258 assert_equal "GET", rv
259 assert_equal str.object_id, rv.object_id
260 assert ! @parser.keepalive?
265 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
268 assert_equal req, @parser.parse
270 assert_nil @parser.filter_body(tmp, str)
273 assert_nil @parser.filter_body(tmp, str)
276 assert_nil @parser.filter_body(tmp, str)
278 assert ! @parser.body_eof?
279 assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
281 assert @parser.body_eof?
283 assert_equal req, @parser.parse
285 assert @parser.body_eof?
286 assert @parser.keepalive?
289 def test_two_chunks_oneshot
292 str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
293 "1\r\na\r\n2\r\n..\r\n0\r\n"
294 assert_equal req, @parser.parse
296 assert_nil @parser.filter_body(tmp, str)
297 assert_equal 'a..', tmp
298 rv = @parser.filter_body(tmp, str)
299 assert_equal rv.object_id, str.object_id
300 assert ! @parser.keepalive?
303 def test_chunks_bytewise
304 chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
305 str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
309 assert_equal req, @parser.parse
314 str.each_byte { |byte|
315 assert_nil @parser.filter_body(tmp, buf << byte.chr)
318 assert_equal 'abcdefghijklmnop0123456789abcdefg', body
319 rv = @parser.filter_body(tmp, buf<< "\n")
320 assert_equal rv.object_id, buf.object_id
321 assert ! @parser.keepalive?
327 str << "PUT / HTTP/1.1\r\n" \
328 "Trailer: Content-MD5\r\n" \
329 "transfer-Encoding: chunked\r\n\r\n" \
330 "1\r\na\r\n2\r\n..\r\n0\r\n"
331 assert_equal req, @parser.parse
332 assert_equal 'Content-MD5', req['HTTP_TRAILER']
333 assert_nil req['HTTP_CONTENT_MD5']
335 assert_nil @parser.filter_body(tmp, str)
336 assert_equal 'a..', tmp
337 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
338 rv = @parser.filter_body(tmp, str)
339 assert_equal rv.object_id, str.object_id
341 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
343 assert_nil @parser.trailers(req, str)
344 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
345 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
347 assert_nil @parser.parse
349 assert_equal req, @parser.parse
350 assert_equal "GET / ", str
351 assert @parser.keepalive?
354 def test_trailers_slowly
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"
361 assert_equal req, @parser.parse
362 assert_equal 'Content-MD5', req['HTTP_TRAILER']
363 assert_nil req['HTTP_CONTENT_MD5']
365 assert_nil @parser.filter_body(tmp, str)
366 assert_equal 'a..', tmp
367 md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
368 rv = @parser.filter_body(tmp, str)
369 assert_equal rv.object_id, str.object_id
371 assert_nil @parser.trailers(req, str)
372 md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
373 md5_hdr.each_byte { |byte|
375 assert_nil @parser.trailers(req, str)
377 assert_equal md5_b64, req['HTTP_CONTENT_MD5']
378 assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
380 assert_nil @parser.parse
382 assert_equal req, @parser.parse
387 str << "PUT / HTTP/1.1\r\n" \
388 "transfer-Encoding: chunked\r\n\r\n" \
389 "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
391 assert_equal req, @parser.parse
392 assert_nil @parser.content_length
393 @parser.filter_body('', str)
394 assert ! @parser.keepalive?
398 n = HttpParser::LENGTH_MAX
399 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
401 @parser.headers(req, @parser.buf)
402 assert_equal n, req['CONTENT_LENGTH'].to_i
403 assert ! @parser.keepalive?
406 def test_overflow_chunk
407 n = HttpParser::CHUNK_MAX + 1
410 str << "PUT / HTTP/1.1\r\n" \
411 "transfer-Encoding: chunked\r\n\r\n" \
412 "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
413 assert_equal req, @parser.parse
414 assert_nil @parser.content_length
415 assert_raise(HttpParserError) { @parser.filter_body('', str) }
418 def test_overflow_content_length
419 n = HttpParser::LENGTH_MAX + 1
420 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
421 assert_raise(HttpParserError) { @parser.parse }
425 @parser.buf << "PUT / HTTP/1.1\r\n" \
426 "transfer-Encoding: chunked\r\n\r\n" \
427 "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
429 assert_equal req, @parser.parse
430 assert_nil @parser.content_length
431 assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
434 def test_bad_content_length
435 @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
436 assert_raise(HttpParserError) { @parser.parse }
439 def test_bad_trailers
442 str << "PUT / HTTP/1.1\r\n" \
443 "Trailer: Transfer-Encoding\r\n" \
444 "transfer-Encoding: chunked\r\n\r\n" \
445 "1\r\na\r\n2\r\n..\r\n0\r\n"
446 assert_equal req, @parser.parse
447 assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
449 assert_nil @parser.filter_body(tmp, str)
450 assert_equal 'a..', tmp
452 str << "Transfer-Encoding: identity\r\n\r\n"
453 assert_raise(HttpParserError) { @parser.parse }
456 def test_repeat_headers
457 str = "PUT / HTTP/1.1\r\n" \
458 "Trailer: Content-MD5\r\n" \
459 "Trailer: Content-SHA1\r\n" \
460 "transfer-Encoding: chunked\r\n\r\n" \
461 "1\r\na\r\n2\r\n..\r\n0\r\n"
464 assert_equal req, @parser.parse
465 assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
466 assert ! @parser.keepalive?
469 def test_parse_simple_request
470 parser = HttpParser.new
472 parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
473 assert_equal req, parser.parse
474 assert_equal '', parser.buf
476 "SERVER_NAME"=>"localhost",
477 "rack.url_scheme"=>"http",
478 "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
479 "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
480 "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
482 "SERVER_PROTOCOL"=>"HTTP/0.9",
483 "REQUEST_METHOD"=>"GET",
486 assert_equal expect, req
487 assert ! parser.headers?
490 def test_path_info_semicolon
494 str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
496 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
497 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
498 "/1;a=b" => { qs => "", pi => "/1;a=b" },
499 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
500 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
501 "*" => { qs => "", pi => "" },
502 }.each do |uri,expect|
503 assert_equal req, @parser.headers(req.clear, str % [ uri ])
506 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
507 assert_equal expect[qs], req[qs], "#{qs} mismatch"
508 assert_equal expect[pi], req[pi], "#{pi} mismatch"
510 uri = URI.parse("http://example.com#{uri}")
511 assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
512 assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
516 def test_path_info_semicolon_absolute
520 str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
522 "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
523 "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
524 "/1;a=b" => { qs => "", pi => "/1;a=b" },
525 "/1;a=b?" => { qs => "", pi => "/1;a=b" },
526 "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
527 }.each do |uri,expect|
528 assert_equal req, @parser.headers(req.clear, str % [ uri ])
531 assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
532 assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
533 assert_equal expect[qs], req[qs], "#{qs} mismatch"
534 assert_equal expect[pi], req[pi], "#{pi} mismatch"
538 def test_negative_content_length
540 str = "PUT / HTTP/1.1\r\n" \
541 "Content-Length: -1\r\n" \
543 assert_raises(HttpParserError) do
544 @parser.headers(req, str)
548 def test_invalid_content_length
550 str = "PUT / HTTP/1.1\r\n" \
551 "Content-Length: zzzzz\r\n" \
553 assert_raises(HttpParserError) do
554 @parser.headers(req, str)
558 def test_backtrace_is_empty
560 @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
561 assert false, "should never get here line:#{__LINE__}"
562 rescue HttpParserError => e
563 assert_equal [], e.backtrace
566 assert false, "should never get here line:#{__LINE__}"
569 def test_ignore_version_header
570 @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
572 assert_equal req, @parser.parse
573 assert_equal '', @parser.buf
575 "SERVER_NAME" => "localhost",
576 "rack.url_scheme" => "http",
577 "REQUEST_PATH" => "/",
578 "SERVER_PROTOCOL" => "HTTP/1.1",
580 "HTTP_VERSION" => "HTTP/1.1",
581 "REQUEST_URI" => "/",
582 "SERVER_PORT" => "80",
583 "REQUEST_METHOD" => "GET",
586 assert_equal expect, req
589 def test_pipelined_requests
593 "SERVER_NAME" => host,
594 "REQUEST_PATH" => "/",
595 "rack.url_scheme" => "http",
596 "SERVER_PROTOCOL" => "HTTP/1.1",
598 "HTTP_VERSION" => "HTTP/1.1",
599 "REQUEST_URI" => "/",
600 "SERVER_PORT" => "80",
601 "REQUEST_METHOD" => "GET",
604 req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
605 req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
606 @parser.buf << (req1 + req2)
607 env1 = @parser.parse.dup
608 assert_equal expect, env1
609 assert_equal req2, @parser.buf
610 assert ! @parser.env.empty?
612 assert @parser.keepalive?
613 assert @parser.headers?
614 assert_equal expect, @parser.env
615 env2 = @parser.parse.dup
616 host.replace "www.example.com"
617 assert_equal "www.example.com", expect["HTTP_HOST"]
618 assert_equal "www.example.com", expect["SERVER_NAME"]
619 assert_equal expect, env2
620 assert_equal "", @parser.buf
625 assert_equal @parser, @parser.dechunk!
626 assert_nil @parser.filter_body(tmp, "6\r\n")
628 assert_nil @parser.filter_body(tmp, "abcdef")
629 assert_equal "abcdef", tmp
630 assert_nil @parser.filter_body(tmp, "\r\n")
633 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
637 def test_chunk_only_bad_align
639 assert_equal @parser, @parser.dechunk!
640 assert_nil @parser.filter_body(tmp, "6\r\na")
641 assert_equal "a", tmp
642 assert_nil @parser.filter_body(tmp, "bcde")
643 assert_equal "bcde", tmp
644 assert_nil @parser.filter_body(tmp, "f\r")
645 assert_equal "f", tmp
647 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
651 def test_chunk_only_reset_ok
653 assert_equal @parser, @parser.dechunk!
654 src = "1\r\na\r\n0\r\n\r\n"
655 assert_nil @parser.filter_body(tmp, src)
656 assert_equal "a", tmp
657 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
659 assert_equal @parser, @parser.dechunk!
661 assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
663 assert_equal src, @parser.filter_body(tmp, src)