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