http: improve RFC 7230 conformance
[unicorn.git] / test / unit / test_http_parser_ng.rb
blob425d5ad0d9378778dcd714fbf4a142bbeb9f91f3
1 # -*- encoding: binary -*-
3 require './test/test_helper'
4 require 'digest/md5'
6 include Unicorn
8 class HttpParserNgTest < Test::Unit::TestCase
10   def setup
11     @parser = HttpParser.new
12   end
14   # RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
15   # but "chunked" must be last if used
16   def test_is_chunked
17     [ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
18       assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
19     end
20     [ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
21       assert HttpParser.is_chunked?(x)
22     end
23     [ 'gzip', 'xhunked', 'xchunked' ].each do |x|
24       assert !HttpParser.is_chunked?(x)
25     end
26   end
28   def test_parser_max_len
29     assert_raises(RangeError) do
30       HttpParser.max_header_len = 0xffffffff + 1
31     end
32   end
34   def test_next_clear
35     r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
36     @parser.buf << r
37     @parser.parse
38     @parser.response_start_sent = true
39     assert @parser.keepalive?
40     assert @parser.next?
41     assert @parser.response_start_sent
43     # persistent client makes another request:
44     @parser.buf << r
45     @parser.parse
46     assert @parser.keepalive?
47     assert @parser.next?
48     assert_equal false, @parser.response_start_sent
49   end
51   def test_response_start_sent
52     assert_equal false, @parser.response_start_sent, "default is false"
53     @parser.response_start_sent = true
54     assert_equal true, @parser.response_start_sent
55     @parser.response_start_sent = false
56     assert_equal false, @parser.response_start_sent
57     @parser.response_start_sent = true
58     @parser.clear
59     assert_equal false, @parser.response_start_sent
60   end
62   def test_connection_TE
63     @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
64     @parser.buf << "TE: trailers\r\n\r\n"
65     @parser.parse
66     assert @parser.keepalive?
67     assert @parser.next?
68   end
70   def test_keepalive_requests_with_next?
71     req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
72     expect = {
73       "SERVER_NAME" => "example.com",
74       "HTTP_HOST" => "example.com",
75       "rack.url_scheme" => "http",
76       "REQUEST_PATH" => "/",
77       "SERVER_PROTOCOL" => "HTTP/1.1",
78       "PATH_INFO" => "/",
79       "HTTP_VERSION" => "HTTP/1.1",
80       "REQUEST_URI" => "/",
81       "SERVER_PORT" => "80",
82       "REQUEST_METHOD" => "GET",
83       "QUERY_STRING" => ""
84     }.freeze
85     100.times do |nr|
86       @parser.buf << req
87       assert_equal expect, @parser.parse
88       assert @parser.next?
89     end
90   end
92   def test_default_keepalive_is_off
93     assert ! @parser.keepalive?
94     assert ! @parser.next?
95     @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
96     @parser.parse
97     assert @parser.keepalive?
98     @parser.clear
99     assert ! @parser.keepalive?
100     assert ! @parser.next?
101   end
103   def test_identity_byte_headers
104     req = @parser.env
105     str = "PUT / HTTP/1.1\r\n"
106     str << "Content-Length: 123\r\n"
107     str << "\r"
108     hdr = @parser.buf
109     str.each_byte { |byte|
110       hdr << byte.chr
111       assert_nil @parser.parse
112     }
113     hdr << "\n"
114     assert_equal req.object_id, @parser.parse.object_id
115     assert_equal '123', req['CONTENT_LENGTH']
116     assert_equal 0, hdr.size
117     assert ! @parser.keepalive?
118     assert @parser.headers?
119     assert_equal 123, @parser.content_length
120     dst = ""
121     buf = '.' * 123
122     @parser.filter_body(dst, buf)
123     assert_equal '.' * 123, dst
124     assert_equal "", buf
125     assert @parser.keepalive?
126   end
128   def test_identity_step_headers
129     req = @parser.env
130     str = @parser.buf
131     str << "PUT / HTTP/1.1\r\n"
132     assert ! @parser.parse
133     str << "Content-Length: 123\r\n"
134     assert ! @parser.parse
135     str << "\r\n"
136     assert_equal req.object_id, @parser.parse.object_id
137     assert_equal '123', req['CONTENT_LENGTH']
138     assert_equal 0, str.size
139     assert ! @parser.keepalive?
140     assert @parser.headers?
141     dst = ""
142     buf = '.' * 123
143     @parser.filter_body(dst, buf)
144     assert_equal '.' * 123, dst
145     assert_equal "", buf
146     assert @parser.keepalive?
147   end
149   def test_identity_oneshot_header
150     req = @parser.env
151     str = @parser.buf
152     str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
153     assert_equal req.object_id, @parser.parse.object_id
154     assert_equal '123', req['CONTENT_LENGTH']
155     assert_equal 0, str.size
156     assert ! @parser.keepalive?
157     assert @parser.headers?
158     dst = ""
159     buf = '.' * 123
160     @parser.filter_body(dst, buf)
161     assert_equal '.' * 123, dst
162     assert_equal "", buf
163   end
165   def test_identity_oneshot_header_with_body
166     body = ('a' * 123).freeze
167     req = @parser.env
168     str = @parser.buf
169     str << "PUT / HTTP/1.1\r\n" \
170            "Content-Length: #{body.length}\r\n" \
171            "\r\n#{body}"
172     assert_equal req.object_id, @parser.parse.object_id
173     assert_equal '123', req['CONTENT_LENGTH']
174     assert_equal 123, str.size
175     assert_equal body, str
176     tmp = ''
177     assert_nil @parser.filter_body(tmp, str)
178     assert_equal 0, str.size
179     assert_equal tmp, body
180     assert_equal "", @parser.filter_body(tmp, str)
181     assert @parser.keepalive?
182   end
184   def test_identity_oneshot_header_with_body_partial
185     str = @parser.buf
186     str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
187     assert_equal Hash, @parser.parse.class
188     assert_equal 1, str.size
189     assert_equal 'a', str
190     tmp = ''
191     assert_nil @parser.filter_body(tmp, str)
192     assert_equal "", str
193     assert_equal "a", tmp
194     str << ' ' * 122
195     rv = @parser.filter_body(tmp, str)
196     assert_equal 122, tmp.size
197     assert_nil rv
198     assert_equal "", str
199     assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
200     assert @parser.keepalive?
201   end
203   def test_identity_oneshot_header_with_body_slop
204     str = @parser.buf
205     str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
206     assert_equal Hash, @parser.parse.class
207     assert_equal 2, str.size
208     assert_equal 'aG', str
209     tmp = ''
210     assert_nil @parser.filter_body(tmp, str)
211     assert_equal "G", str
212     assert_equal "G", @parser.filter_body(tmp, str)
213     assert_equal 1, tmp.size
214     assert_equal "a", tmp
215     assert @parser.keepalive?
216   end
218   def test_chunked
219     str = @parser.buf
220     req = @parser.env
221     str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
222     assert_equal req, @parser.parse, "msg=#{str}"
223     assert_equal 0, str.size
224     tmp = ""
225     assert_nil @parser.filter_body(tmp, str << "6")
226     assert_equal 0, tmp.size
227     assert_nil @parser.filter_body(tmp, str << "\r\n")
228     assert_equal 0, str.size
229     assert_equal 0, tmp.size
230     tmp = ""
231     assert_nil @parser.filter_body(tmp, str << "..")
232     assert_equal "..", tmp
233     assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
234     assert_equal "abcd", tmp
235     assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
236     assert_equal "PUT", str
237     assert ! @parser.keepalive?
238     str << "TY: FOO\r\n\r\n"
239     assert_equal req, @parser.parse
240     assert_equal "FOO", req["HTTP_PUTTY"]
241     assert @parser.keepalive?
242   end
244   def test_chunked_empty
245     str = @parser.buf
246     req = @parser.env
247     str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
248     assert_equal req, @parser.parse, "msg=#{str}"
249     assert_equal 0, str.size
250     tmp = ""
251     assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
252     assert_equal "", tmp
253   end
255   def test_two_chunks
256     str = @parser.buf
257     str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
258     req = @parser.env
259     assert_equal req, @parser.parse
260     assert_equal 0, str.size
261     tmp = ""
262     assert_nil @parser.filter_body(tmp, str << "6")
263     assert_equal 0, tmp.size
264     assert_nil @parser.filter_body(tmp, str << "\r\n")
265     assert_equal "", str
266     assert_equal 0, tmp.size
267     tmp = ""
268     assert_nil @parser.filter_body(tmp, str << "..")
269     assert_equal 2, tmp.size
270     assert_equal "..", tmp
271     assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
272     assert_equal "abcd", tmp
273     assert_nil @parser.filter_body(tmp, str << "\r")
274     assert_equal "", tmp
275     assert_nil @parser.filter_body(tmp, str << "\n")
276     assert_equal "", tmp
277     assert_nil @parser.filter_body(tmp, str << "z")
278     assert_equal "z", tmp
279     assert_nil @parser.filter_body(tmp, str << "\r\n")
280     assert_nil @parser.filter_body(tmp, str << "0")
281     assert_nil @parser.filter_body(tmp, str << "\r")
282     rv = @parser.filter_body(tmp, str << "\nGET")
283     assert_equal "GET", rv
284     assert_equal str.object_id, rv.object_id
285     assert ! @parser.keepalive?
286   end
288   def test_big_chunk
289     str = @parser.buf
290     str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
291            "4000\r\nabcd"
292     req = @parser.env
293     assert_equal req, @parser.parse
294     tmp = ''
295     assert_nil @parser.filter_body(tmp, str)
296     assert_equal '', str
297     str << ' ' * 16300
298     assert_nil @parser.filter_body(tmp, str)
299     assert_equal '', str
300     str << ' ' * 80
301     assert_nil @parser.filter_body(tmp, str)
302     assert_equal '', str
303     assert ! @parser.body_eof?
304     assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
305     assert_equal "", tmp
306     assert @parser.body_eof?
307     str << "\r\n"
308     assert_equal req, @parser.parse
309     assert_equal "", str
310     assert @parser.body_eof?
311     assert @parser.keepalive?
312   end
314   def test_two_chunks_oneshot
315     str = @parser.buf
316     req = @parser.env
317     str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
318            "1\r\na\r\n2\r\n..\r\n0\r\n"
319     assert_equal req, @parser.parse
320     tmp = ''
321     assert_nil @parser.filter_body(tmp, str)
322     assert_equal 'a..', tmp
323     rv = @parser.filter_body(tmp, str)
324     assert_equal rv.object_id, str.object_id
325     assert ! @parser.keepalive?
326   end
328   def test_chunks_bytewise
329     chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
330     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
331     buf = @parser.buf
332     buf << str
333     req = @parser.env
334     assert_equal req, @parser.parse
335     assert_equal "", buf
336     tmp = ''
337     body = ''
338     str = chunked[0..-2]
339     str.each_byte { |byte|
340       assert_nil @parser.filter_body(tmp, buf << byte.chr)
341       body << tmp
342     }
343     assert_equal 'abcdefghijklmnop0123456789abcdefg', body
344     rv = @parser.filter_body(tmp, buf<< "\n")
345     assert_equal rv.object_id, buf.object_id
346     assert ! @parser.keepalive?
347   end
349   def test_trailers
350     req = @parser.env
351     str = @parser.buf
352     str << "PUT / HTTP/1.1\r\n" \
353            "Trailer: Content-MD5\r\n" \
354            "transfer-Encoding: chunked\r\n\r\n" \
355            "1\r\na\r\n2\r\n..\r\n0\r\n"
356     assert_equal req, @parser.parse
357     assert_equal 'Content-MD5', req['HTTP_TRAILER']
358     assert_nil req['HTTP_CONTENT_MD5']
359     tmp = ''
360     assert_nil @parser.filter_body(tmp, str)
361     assert_equal 'a..', tmp
362     md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
363     rv = @parser.filter_body(tmp, str)
364     assert_equal rv.object_id, str.object_id
365     assert_equal '', str
366     md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
367     str << md5_hdr
368     assert_nil @parser.trailers(req, str)
369     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
370     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
371     str << "\r"
372     assert_nil @parser.parse
373     str << "\nGET / "
374     assert_equal req, @parser.parse
375     assert_equal "GET / ", str
376     assert @parser.keepalive?
377   end
379   def test_trailers_slowly
380     str = @parser.buf
381     str << "PUT / HTTP/1.1\r\n" \
382            "Trailer: Content-MD5\r\n" \
383            "transfer-Encoding: chunked\r\n\r\n" \
384            "1\r\na\r\n2\r\n..\r\n0\r\n"
385     req = @parser.env
386     assert_equal req, @parser.parse
387     assert_equal 'Content-MD5', req['HTTP_TRAILER']
388     assert_nil req['HTTP_CONTENT_MD5']
389     tmp = ''
390     assert_nil @parser.filter_body(tmp, str)
391     assert_equal 'a..', tmp
392     md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
393     rv = @parser.filter_body(tmp, str)
394     assert_equal rv.object_id, str.object_id
395     assert_equal '', str
396     assert_nil @parser.trailers(req, str)
397     md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
398     md5_hdr.each_byte { |byte|
399       str << byte.chr
400       assert_nil @parser.trailers(req, str)
401     }
402     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
403     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
404     str << "\r"
405     assert_nil @parser.parse
406     str << "\n"
407     assert_equal req, @parser.parse
408   end
410   def test_max_chunk
411     str = @parser.buf
412     str << "PUT / HTTP/1.1\r\n" \
413            "transfer-Encoding: chunked\r\n\r\n" \
414            "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
415     req = @parser.env
416     assert_equal req, @parser.parse
417     assert_nil @parser.content_length
418     @parser.filter_body('', str)
419     assert ! @parser.keepalive?
420   end
422   def test_max_body
423     n = HttpParser::LENGTH_MAX
424     @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
425     req = @parser.env
426     @parser.headers(req, @parser.buf)
427     assert_equal n, req['CONTENT_LENGTH'].to_i
428     assert ! @parser.keepalive?
429   end
431   def test_overflow_chunk
432     n = HttpParser::CHUNK_MAX + 1
433     str = @parser.buf
434     req = @parser.env
435     str << "PUT / HTTP/1.1\r\n" \
436            "transfer-Encoding: chunked\r\n\r\n" \
437            "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
438     assert_equal req, @parser.parse
439     assert_nil @parser.content_length
440     assert_raise(HttpParserError) { @parser.filter_body('', str) }
441   end
443   def test_overflow_content_length
444     n = HttpParser::LENGTH_MAX + 1
445     @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
446     assert_raise(HttpParserError) { @parser.parse }
447   end
449   def test_bad_chunk
450     @parser.buf << "PUT / HTTP/1.1\r\n" \
451                    "transfer-Encoding: chunked\r\n\r\n" \
452                    "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
453     req = @parser.env
454     assert_equal req, @parser.parse
455     assert_nil @parser.content_length
456     assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
457   end
459   def test_bad_content_length
460     @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
461     assert_raise(HttpParserError) { @parser.parse }
462   end
464   def test_bad_trailers
465     str = @parser.buf
466     req = @parser.env
467     str << "PUT / HTTP/1.1\r\n" \
468            "Trailer: Transfer-Encoding\r\n" \
469            "transfer-Encoding: chunked\r\n\r\n" \
470            "1\r\na\r\n2\r\n..\r\n0\r\n"
471     assert_equal req, @parser.parse
472     assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
473     tmp = ''
474     assert_nil @parser.filter_body(tmp, str)
475     assert_equal 'a..', tmp
476     assert_equal '', str
477     str << "Transfer-Encoding: identity\r\n\r\n"
478     assert_raise(HttpParserError) { @parser.parse }
479   end
481   def test_repeat_headers
482     str = "PUT / HTTP/1.1\r\n" \
483           "Trailer: Content-MD5\r\n" \
484           "Trailer: Content-SHA1\r\n" \
485           "transfer-Encoding: chunked\r\n\r\n" \
486           "1\r\na\r\n2\r\n..\r\n0\r\n"
487     req = @parser.env
488     @parser.buf << str
489     assert_equal req, @parser.parse
490     assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
491     assert ! @parser.keepalive?
492   end
494   def test_parse_simple_request
495     parser = HttpParser.new
496     req = parser.env
497     parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
498     assert_equal req, parser.parse
499     assert_equal '', parser.buf
500     expect = {
501       "SERVER_NAME"=>"localhost",
502       "rack.url_scheme"=>"http",
503       "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
504       "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
505       "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
506       "SERVER_PORT"=>"80",
507       "SERVER_PROTOCOL"=>"HTTP/0.9",
508       "REQUEST_METHOD"=>"GET",
509       "QUERY_STRING"=>""
510     }
511     assert_equal expect, req
512     assert ! parser.headers?
513   end
515   def test_path_info_semicolon
516     qs = "QUERY_STRING"
517     pi = "PATH_INFO"
518     req = {}
519     str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
520     {
521       "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
522       "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
523       "/1;a=b" => { qs => "", pi => "/1;a=b" },
524       "/1;a=b?" => { qs => "", pi => "/1;a=b" },
525       "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
526       "*" => { qs => "", pi => "" },
527     }.each do |uri,expect|
528       assert_equal req, @parser.headers(req.clear, str % [ uri ])
529       req = req.dup
530       @parser.clear
531       assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
532       assert_equal expect[qs], req[qs], "#{qs} mismatch"
533       assert_equal expect[pi], req[pi], "#{pi} mismatch"
534       next if uri == "*"
535       uri = URI.parse("http://example.com#{uri}")
536       assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
537       assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
538     end
539   end
541   def test_path_info_semicolon_absolute
542     qs = "QUERY_STRING"
543     pi = "PATH_INFO"
544     req = {}
545     str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
546     {
547       "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
548       "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
549       "/1;a=b" => { qs => "", pi => "/1;a=b" },
550       "/1;a=b?" => { qs => "", pi => "/1;a=b" },
551       "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
552     }.each do |uri,expect|
553       assert_equal req, @parser.headers(req.clear, str % [ uri ])
554       req = req.dup
555       @parser.clear
556       assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
557       assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
558       assert_equal expect[qs], req[qs], "#{qs} mismatch"
559       assert_equal expect[pi], req[pi], "#{pi} mismatch"
560     end
561   end
563   def test_negative_content_length
564     req = {}
565     str = "PUT / HTTP/1.1\r\n" \
566           "Content-Length: -1\r\n" \
567           "\r\n"
568     assert_raises(HttpParserError) do
569       @parser.headers(req, str)
570     end
571   end
573   def test_invalid_content_length
574     req = {}
575     str = "PUT / HTTP/1.1\r\n" \
576           "Content-Length: zzzzz\r\n" \
577           "\r\n"
578     assert_raises(HttpParserError) do
579       @parser.headers(req, str)
580     end
581   end
583   def test_duplicate_content_length
584     str = "PUT / HTTP/1.1\r\n" \
585           "Content-Length: 1\r\n" \
586           "Content-Length: 9\r\n" \
587           "\r\n"
588     assert_raises(HttpParserError) { @parser.headers({}, str) }
589   end
591   def test_chunked_overrides_content_length
592     order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
593     %w(a b).each do |x|
594       str = "PUT /#{x} HTTP/1.1\r\n" \
595             "#{order.join("\r\n")}" \
596             "\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
597       order.reverse!
598       env = @parser.headers({}, str)
599       assert_nil @parser.content_length
600       assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
601       assert_equal '666', env['CONTENT_LENGTH'],
602         'Content-Length logged so the app can log a possible client bug/attack'
603       @parser.filter_body(dst = '', str)
604       assert_equal 'helloworld', dst
605       @parser.parse # handle the non-existent trailer
606       assert @parser.next?
607     end
608   end
610   def test_chunked_order_good
611     str = "PUT /x HTTP/1.1\r\n" \
612           "Transfer-Encoding: gzip\r\n" \
613           "Transfer-Encoding: chunked\r\n" \
614           "\r\n"
615     env = @parser.headers({}, str)
616     assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
617     assert_nil @parser.content_length
619     @parser.clear
620     str = "PUT /x HTTP/1.1\r\n" \
621           "Transfer-Encoding: gzip, chunked\r\n" \
622           "\r\n"
623     env = @parser.headers({}, str)
624     assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
625     assert_nil @parser.content_length
626   end
628   def test_chunked_order_bad
629     str = "PUT /x HTTP/1.1\r\n" \
630           "Transfer-Encoding: chunked\r\n" \
631           "Transfer-Encoding: gzip\r\n" \
632           "\r\n"
633     assert_raise(HttpParserError) { @parser.headers({}, str) }
634   end
636   def test_double_chunked
637     str = "PUT /x HTTP/1.1\r\n" \
638           "Transfer-Encoding: chunked\r\n" \
639           "Transfer-Encoding: chunked\r\n" \
640           "\r\n"
641     assert_raise(HttpParserError) { @parser.headers({}, str) }
643     @parser.clear
644     str = "PUT /x HTTP/1.1\r\n" \
645           "Transfer-Encoding: chunked,chunked\r\n" \
646           "\r\n"
647     assert_raise(HttpParserError) { @parser.headers({}, str) }
648   end
650   def test_backtrace_is_empty
651     begin
652       @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
653       assert false, "should never get here line:#{__LINE__}"
654     rescue HttpParserError => e
655       assert_equal [], e.backtrace
656       return
657     end
658     assert false, "should never get here line:#{__LINE__}"
659   end
661   def test_ignore_version_header
662     @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
663     req = @parser.env
664     assert_equal req, @parser.parse
665     assert_equal '', @parser.buf
666     expect = {
667       "SERVER_NAME" => "localhost",
668       "rack.url_scheme" => "http",
669       "REQUEST_PATH" => "/",
670       "SERVER_PROTOCOL" => "HTTP/1.1",
671       "PATH_INFO" => "/",
672       "HTTP_VERSION" => "HTTP/1.1",
673       "REQUEST_URI" => "/",
674       "SERVER_PORT" => "80",
675       "REQUEST_METHOD" => "GET",
676       "QUERY_STRING" => ""
677     }
678     assert_equal expect, req
679   end
681   def test_pipelined_requests
682     host = "example.com"
683     expect = {
684       "HTTP_HOST" => host,
685       "SERVER_NAME" => host,
686       "REQUEST_PATH" => "/",
687       "rack.url_scheme" => "http",
688       "SERVER_PROTOCOL" => "HTTP/1.1",
689       "PATH_INFO" => "/",
690       "HTTP_VERSION" => "HTTP/1.1",
691       "REQUEST_URI" => "/",
692       "SERVER_PORT" => "80",
693       "REQUEST_METHOD" => "GET",
694       "QUERY_STRING" => ""
695     }
696     req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
697     req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
698     @parser.buf << (req1 + req2)
699     env1 = @parser.parse.dup
700     assert_equal expect, env1
701     assert_equal req2, @parser.buf
702     assert ! @parser.env.empty?
703     assert @parser.next?
704     assert @parser.keepalive?
705     assert @parser.headers?
706     assert_equal expect, @parser.env
707     env2 = @parser.parse.dup
708     host.replace "www.example.com"
709     assert_equal "www.example.com", expect["HTTP_HOST"]
710     assert_equal "www.example.com", expect["SERVER_NAME"]
711     assert_equal expect, env2
712     assert_equal "", @parser.buf
713   end