Add some tolerance (RFC2616 sec. 19.3)
[unicorn.git] / test / unit / test_http_parser.rb
blob7cbc0f89d61f3978a5ffc00a135822d0716d674d
1 # -*- encoding: binary -*-
3 # Copyright (c) 2005 Zed A. Shaw
4 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5 # the GPLv2+ (GPLv3+ preferred)
7 # Additional work donated by contributors.  See git history
8 # for more information.
10 require './test/test_helper'
12 include Unicorn
14 class HttpParserTest < Test::Unit::TestCase
16   def test_parse_simple
17     parser = HttpParser.new
18     req = parser.env
19     http = parser.buf
20     http << "GET / HTTP/1.1\r\n\r\n"
21     assert_equal req, parser.parse
22     assert_equal '', http
24     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
25     assert_equal '/', req['REQUEST_PATH']
26     assert_equal 'HTTP/1.1', req['HTTP_VERSION']
27     assert_equal '/', req['REQUEST_URI']
28     assert_equal 'GET', req['REQUEST_METHOD']
29     assert_nil req['FRAGMENT']
30     assert_equal '', req['QUERY_STRING']
32     assert parser.keepalive?
33     parser.clear
34     req.clear
36     http << "G"
37     assert_nil parser.parse
38     assert_equal "G", http
39     assert req.empty?
41     # try parsing again to ensure we were reset correctly
42     http << "ET /hello-world HTTP/1.1\r\n\r\n"
43     assert parser.parse
45     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
46     assert_equal '/hello-world', req['REQUEST_PATH']
47     assert_equal 'HTTP/1.1', req['HTTP_VERSION']
48     assert_equal '/hello-world', req['REQUEST_URI']
49     assert_equal 'GET', req['REQUEST_METHOD']
50     assert_nil req['FRAGMENT']
51     assert_equal '', req['QUERY_STRING']
52     assert_equal '', http
53     assert parser.keepalive?
54   end
56   def test_tab_lws
57     parser = HttpParser.new
58     req = parser.env
59     parser.buf << "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n"
60     assert_equal req.object_id, parser.parse.object_id
61     assert_equal "foo.bar", req['HTTP_HOST']
62   end
64   def test_connection_close_no_ka
65     parser = HttpParser.new
66     req = parser.env
67     parser.buf << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
68     assert_equal req.object_id, parser.parse.object_id
69     assert_equal "GET", req['REQUEST_METHOD']
70     assert ! parser.keepalive?
71   end
73   def test_connection_keep_alive_ka
74     parser = HttpParser.new
75     req = parser.env
76     parser.buf << "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
77     assert_equal req.object_id, parser.parse.object_id
78     assert parser.keepalive?
79   end
81   def test_connection_keep_alive_no_body
82     parser = HttpParser.new
83     req = parser.env
84     parser.buf << "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
85     assert_equal req.object_id, parser.parse.object_id
86     assert parser.keepalive?
87   end
89   def test_connection_keep_alive_no_body_empty
90     parser = HttpParser.new
91     req = parser.env
92     parser.buf << "POST / HTTP/1.1\r\n" \
93                   "Content-Length: 0\r\n" \
94                   "Connection: keep-alive\r\n\r\n"
95     assert_equal req.object_id, parser.parse.object_id
96     assert parser.keepalive?
97   end
99   def test_connection_keep_alive_ka_bad_version
100     parser = HttpParser.new
101     req = parser.env
102     parser.buf << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
103     assert_equal req.object_id, parser.parse.object_id
104     assert parser.keepalive?
105   end
107   def test_parse_server_host_default_port
108     parser = HttpParser.new
109     req = parser.env
110     parser.buf << "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
111     assert_equal req, parser.parse
112     assert_equal 'foo', req['SERVER_NAME']
113     assert_equal '80', req['SERVER_PORT']
114     assert_equal '', parser.buf
115     assert parser.keepalive?
116   end
118   def test_parse_server_host_alt_port
119     parser = HttpParser.new
120     req = parser.env
121     parser.buf << "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
122     assert_equal req, parser.parse
123     assert_equal 'foo', req['SERVER_NAME']
124     assert_equal '999', req['SERVER_PORT']
125     assert_equal '', parser.buf
126     assert parser.keepalive?
127   end
129   def test_parse_server_host_empty_port
130     parser = HttpParser.new
131     req = parser.env
132     parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
133     assert_equal req, parser.parse
134     assert_equal 'foo', req['SERVER_NAME']
135     assert_equal '80', req['SERVER_PORT']
136     assert_equal '', parser.buf
137     assert parser.keepalive?
138   end
140   def test_parse_server_host_xfp_https
141     parser = HttpParser.new
142     req = parser.env
143     parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \
144                   "X-Forwarded-Proto: https\r\n\r\n"
145     assert_equal req, parser.parse
146     assert_equal 'foo', req['SERVER_NAME']
147     assert_equal '443', req['SERVER_PORT']
148     assert_equal '', parser.buf
149     assert parser.keepalive?
150   end
152   def test_parse_xfp_https_chained
153     parser = HttpParser.new
154     req = parser.env
155     parser.buf << "GET / HTTP/1.0\r\n" \
156                   "X-Forwarded-Proto: https,http\r\n\r\n"
157     assert_equal req, parser.parse
158     assert_equal '443', req['SERVER_PORT'], req.inspect
159     assert_equal 'https', req['rack.url_scheme'], req.inspect
160     assert_equal '', parser.buf
161   end
163   def test_parse_xfp_https_chained_backwards
164     parser = HttpParser.new
165     req = parser.env
166     parser.buf << "GET / HTTP/1.0\r\n" \
167           "X-Forwarded-Proto: http,https\r\n\r\n"
168     assert_equal req, parser.parse
169     assert_equal '80', req['SERVER_PORT'], req.inspect
170     assert_equal 'http', req['rack.url_scheme'], req.inspect
171     assert_equal '', parser.buf
172   end
174   def test_parse_xfp_gopher_is_ignored
175     parser = HttpParser.new
176     req = parser.env
177     parser.buf << "GET / HTTP/1.0\r\n" \
178                   "X-Forwarded-Proto: gopher\r\n\r\n"
179     assert_equal req, parser.parse
180     assert_equal '80', req['SERVER_PORT'], req.inspect
181     assert_equal 'http', req['rack.url_scheme'], req.inspect
182     assert_equal '', parser.buf
183   end
185   def test_parse_x_forwarded_ssl_on
186     parser = HttpParser.new
187     req = parser.env
188     parser.buf << "GET / HTTP/1.0\r\n" \
189                   "X-Forwarded-Ssl: on\r\n\r\n"
190     assert_equal req, parser.parse
191     assert_equal '443', req['SERVER_PORT'], req.inspect
192     assert_equal 'https', req['rack.url_scheme'], req.inspect
193     assert_equal '', parser.buf
194   end
196   def test_parse_x_forwarded_ssl_off
197     parser = HttpParser.new
198     req = parser.env
199     parser.buf << "GET / HTTP/1.0\r\nX-Forwarded-Ssl: off\r\n\r\n"
200     assert_equal req, parser.parse
201     assert_equal '80', req['SERVER_PORT'], req.inspect
202     assert_equal 'http', req['rack.url_scheme'], req.inspect
203     assert_equal '', parser.buf
204   end
206   def test_parse_strange_headers
207     parser = HttpParser.new
208     req = parser.env
209     should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
210     parser.buf << should_be_good
211     assert_equal req, parser.parse
212     assert_equal '', parser.buf
213     assert parser.keepalive?
214   end
216   # legacy test case from Mongrel that we never supported before...
217   # I still consider Pound irrelevant, unfortunately stupid clients that
218   # send extremely big headers do exist and they've managed to find Unicorn...
219   def test_nasty_pound_header
220     parser = HttpParser.new
221     nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
222     req = parser.env
223     parser.buf << nasty_pound_header.dup
225     assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
226     expect = $1.dup
227     expect.gsub!(/\r\n\t/, ' ')
228     assert_equal req, parser.parse
229     assert_equal '', parser.buf
230     assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
231   end
233   def test_multiline_header_0d0a
234     parser = HttpParser.new
235     parser.buf << "GET / HTTP/1.0\r\n" \
236       "X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n"
237     req = parser.env
238     assert_equal req, parser.parse
239     assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
240   end
242   def test_multiline_header_0a
243     parser = HttpParser.new
244     parser.buf << "GET / HTTP/1.0\n" \
245       "X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n"
246     req = parser.env
247     assert_equal req, parser.parse
248     assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
249   end
251   def test_continuation_eats_leading_spaces
252     parser = HttpParser.new
253     header = "GET / HTTP/1.1\r\n" \
254              "X-ASDF:      \r\n" \
255              "\t\r\n" \
256              "    \r\n" \
257              "  ASDF\r\n\r\n"
258     parser.buf << header
259     req = parser.env
260     assert_equal req, parser.parse
261     assert_equal '', parser.buf
262     assert_equal 'ASDF', req['HTTP_X_ASDF']
263   end
265   def test_continuation_eats_scattered_leading_spaces
266     parser = HttpParser.new
267     header = "GET / HTTP/1.1\r\n" \
268              "X-ASDF:   hi\r\n" \
269              "    y\r\n" \
270              "\t\r\n" \
271              "       x\r\n" \
272              "  ASDF\r\n\r\n"
273     req = parser.env
274     parser.buf << header
275     assert_equal req, parser.parse
276     assert_equal '', parser.buf
277     assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
278   end
280   def test_continuation_eats_trailing_spaces
281     parser = HttpParser.new
282     header = "GET / HTTP/1.1\r\n" \
283              "X-ASDF:      \r\n" \
284              "\t\r\n" \
285              "  b  \r\n" \
286              "  ASDF\r\n\r\n"
287     parser.buf << header
288     req = parser.env
289     assert_equal req, parser.parse
290     assert_equal '', parser.buf
291     assert_equal 'b ASDF', req['HTTP_X_ASDF']
292   end
294   def test_continuation_with_absolute_uri_and_ignored_host_header
295     parser = HttpParser.new
296     header = "GET http://example.com/ HTTP/1.1\r\n" \
297              "Host: \r\n" \
298              "    YHBT.net\r\n" \
299              "\r\n"
300     parser.buf << header
301     req = parser.env
302     assert_equal req, parser.parse
303     assert_equal 'example.com', req['HTTP_HOST']
304   end
306   # this may seem to be testing more of an implementation detail, but
307   # it also helps ensure we're safe in the presence of multiple parsers
308   # in case we ever go multithreaded/evented...
309   def test_resumable_continuations
310     nr = 1000
311     header = "GET / HTTP/1.1\r\n" \
312              "X-ASDF:      \r\n" \
313              "  hello\r\n"
314     tmp = []
315     nr.times { |i|
316       parser = HttpParser.new
317       req = parser.env
318       parser.buf << "#{header} #{i}\r\n"
319       assert parser.parse.nil?
320       asdf = req['HTTP_X_ASDF']
321       assert_equal "hello #{i}", asdf
322       tmp << [ parser, asdf ]
323     }
324     tmp.each_with_index { |(parser, asdf), i|
325       parser.buf << " .\r\n\r\n"
326       assert parser.parse
327       assert_equal "hello #{i} .", asdf
328     }
329   end
331   def test_invalid_continuation
332     parser = HttpParser.new
333     header = "GET / HTTP/1.1\r\n" \
334              "    y\r\n" \
335              "Host: hello\r\n" \
336              "\r\n"
337     parser.buf << header
338     assert_raises(HttpParserError) { parser.parse }
339   end
341   def test_parse_ie6_urls
342     %w(/some/random/path"
343        /some/random/path>
344        /some/random/path<
345        /we/love/you/ie6?q=<"">
346        /url?<="&>="
347        /mal"formed"?
348     ).each do |path|
349       parser = HttpParser.new
350       req = parser.env
351       sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
352       assert_equal req, parser.headers(req, sorta_safe)
353       assert_equal path, req['REQUEST_URI']
354       assert_equal '', sorta_safe
355       assert parser.keepalive?
356     end
357   end
358   
359   def test_parse_error
360     parser = HttpParser.new
361     req = parser.env
362     bad_http = "GET / SsUTF/1.1"
364     assert_raises(HttpParserError) { parser.headers(req, bad_http) }
366     # make sure we can recover
367     parser.clear
368     req.clear
369     assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
370     assert ! parser.keepalive?
371   end
373   def test_piecemeal
374     parser = HttpParser.new
375     req = parser.env
376     http = "GET"
377     assert_nil parser.headers(req, http)
378     assert_nil parser.headers(req, http)
379     assert_nil parser.headers(req, http << " / HTTP/1.0")
380     assert_equal '/', req['REQUEST_PATH']
381     assert_equal '/', req['REQUEST_URI']
382     assert_equal 'GET', req['REQUEST_METHOD']
383     assert_nil parser.headers(req, http << "\r\n")
384     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
385     assert_nil parser.headers(req, http << "\r")
386     assert_equal req, parser.headers(req, http << "\n")
387     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
388     assert_nil req['FRAGMENT']
389     assert_equal '', req['QUERY_STRING']
390     assert_equal "", http
391     assert ! parser.keepalive?
392   end
394   # not common, but underscores do appear in practice
395   def test_absolute_uri_underscores
396     parser = HttpParser.new
397     req = parser.env
398     http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
399     parser.buf << http
400     assert_equal req, parser.parse
401     assert_equal 'http', req['rack.url_scheme']
402     assert_equal '/foo?q=bar', req['REQUEST_URI']
403     assert_equal '/foo', req['REQUEST_PATH']
404     assert_equal 'q=bar', req['QUERY_STRING']
406     assert_equal 'under_score.example.com', req['HTTP_HOST']
407     assert_equal 'under_score.example.com', req['SERVER_NAME']
408     assert_equal '80', req['SERVER_PORT']
409     assert_equal "", parser.buf
410     assert ! parser.keepalive?
411   end
413   # some dumb clients add users because they're stupid
414   def test_absolute_uri_w_user
415     parser = HttpParser.new
416     req = parser.env
417     http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
418     parser.buf << http
419     assert_equal req, parser.parse
420     assert_equal 'http', req['rack.url_scheme']
421     assert_equal '/foo?q=bar', req['REQUEST_URI']
422     assert_equal '/foo', req['REQUEST_PATH']
423     assert_equal 'q=bar', req['QUERY_STRING']
425     assert_equal 'example.com', req['HTTP_HOST']
426     assert_equal 'example.com', req['SERVER_NAME']
427     assert_equal '80', req['SERVER_PORT']
428     assert_equal "", parser.buf
429     assert ! parser.keepalive?
430   end
432   # since Mongrel supported anything URI.parse supported, we're stuck
433   # supporting everything URI.parse supports
434   def test_absolute_uri_uri_parse
435     "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
436       parser = HttpParser.new
437       req = parser.env
438       http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
439       assert_equal req, parser.headers(req, http)
440       assert_equal 'http', req['rack.url_scheme']
441       assert_equal '/', req['REQUEST_URI']
442       assert_equal '/', req['REQUEST_PATH']
443       assert_equal '', req['QUERY_STRING']
445       assert_equal 'example.com', req['HTTP_HOST']
446       assert_equal 'example.com', req['SERVER_NAME']
447       assert_equal '80', req['SERVER_PORT']
448       assert_equal "", http
449       assert ! parser.keepalive?
450     end
451   end
453   def test_absolute_uri
454     parser = HttpParser.new
455     req = parser.env
456     parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
457     assert_equal req, parser.parse
458     assert_equal 'http', req['rack.url_scheme']
459     assert_equal '/foo?q=bar', req['REQUEST_URI']
460     assert_equal '/foo', req['REQUEST_PATH']
461     assert_equal 'q=bar', req['QUERY_STRING']
463     assert_equal 'example.com', req['HTTP_HOST']
464     assert_equal 'example.com', req['SERVER_NAME']
465     assert_equal '80', req['SERVER_PORT']
466     assert_equal "", parser.buf
467     assert ! parser.keepalive?
468   end
470   # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
471   def test_absolute_uri_https
472     parser = HttpParser.new
473     req = parser.env
474     http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
475            "X-Forwarded-Proto: http\r\n\r\n"
476     parser.buf << http
477     assert_equal req, parser.parse
478     assert_equal 'https', req['rack.url_scheme']
479     assert_equal '/foo?q=bar', req['REQUEST_URI']
480     assert_equal '/foo', req['REQUEST_PATH']
481     assert_equal 'q=bar', req['QUERY_STRING']
483     assert_equal 'example.com', req['HTTP_HOST']
484     assert_equal 'example.com', req['SERVER_NAME']
485     assert_equal '443', req['SERVER_PORT']
486     assert_equal "", parser.buf
487     assert parser.keepalive?
488   end
490   # Host: header should be ignored for absolute URIs
491   def test_absolute_uri_with_port
492     parser = HttpParser.new
493     req = parser.env
494     parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
495            "Host: bad.example.com\r\n\r\n"
496     assert_equal req, parser.parse
497     assert_equal 'http', req['rack.url_scheme']
498     assert_equal '/foo?q=bar', req['REQUEST_URI']
499     assert_equal '/foo', req['REQUEST_PATH']
500     assert_equal 'q=bar', req['QUERY_STRING']
502     assert_equal 'example.com:8080', req['HTTP_HOST']
503     assert_equal 'example.com', req['SERVER_NAME']
504     assert_equal '8080', req['SERVER_PORT']
505     assert_equal "", parser.buf
506     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
507   end
509   def test_absolute_uri_with_empty_port
510     parser = HttpParser.new
511     req = parser.env
512     parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
513            "Host: bad.example.com\r\n\r\n"
514     assert_equal req, parser.parse
515     assert_equal 'https', req['rack.url_scheme']
516     assert_equal '/foo?q=bar', req['REQUEST_URI']
517     assert_equal '/foo', req['REQUEST_PATH']
518     assert_equal 'q=bar', req['QUERY_STRING']
520     assert_equal 'example.com:', req['HTTP_HOST']
521     assert_equal 'example.com', req['SERVER_NAME']
522     assert_equal '443', req['SERVER_PORT']
523     assert_equal "", parser.buf
524     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
525   end
527   def test_absolute_ipv6_uri
528     parser = HttpParser.new
529     req = parser.env
530     url = "http://[::1]/foo?q=bar"
531     http = "GET #{url} HTTP/1.1\r\n" \
532            "Host: bad.example.com\r\n\r\n"
533     assert_equal req, parser.headers(req, http)
534     assert_equal 'http', req['rack.url_scheme']
535     assert_equal '/foo?q=bar', req['REQUEST_URI']
536     assert_equal '/foo', req['REQUEST_PATH']
537     assert_equal 'q=bar', req['QUERY_STRING']
539     uri = URI.parse(url)
540     assert_equal "[::1]", uri.host,
541                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
542     assert_equal "[::1]", req['HTTP_HOST']
543     assert_equal "[::1]", req['SERVER_NAME']
544     assert_equal '80', req['SERVER_PORT']
545     assert_equal "", http
546     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
547   end
549   def test_absolute_ipv6_uri_alpha
550     parser = HttpParser.new
551     req = parser.env
552     url = "http://[::a]/"
553     http = "GET #{url} HTTP/1.1\r\n" \
554            "Host: bad.example.com\r\n\r\n"
555     assert_equal req, parser.headers(req, http)
556     assert_equal 'http', req['rack.url_scheme']
558     uri = URI.parse(url)
559     assert_equal "[::a]", uri.host,
560                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
561     assert_equal "[::a]", req['HTTP_HOST']
562     assert_equal "[::a]", req['SERVER_NAME']
563     assert_equal '80', req['SERVER_PORT']
564   end
566   def test_absolute_ipv6_uri_alpha_2
567     parser = HttpParser.new
568     req = parser.env
569     url = "http://[::B]/"
570     http = "GET #{url} HTTP/1.1\r\n" \
571            "Host: bad.example.com\r\n\r\n"
572     assert_equal req, parser.headers(req, http)
573     assert_equal 'http', req['rack.url_scheme']
575     uri = URI.parse(url)
576     assert_equal "[::B]", uri.host,
577                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
578     assert_equal "[::B]", req['HTTP_HOST']
579     assert_equal "[::B]", req['SERVER_NAME']
580     assert_equal '80', req['SERVER_PORT']
581   end
583   def test_absolute_ipv6_uri_with_empty_port
584     parser = HttpParser.new
585     req = parser.env
586     url = "https://[::1]:/foo?q=bar"
587     http = "GET #{url} HTTP/1.1\r\n" \
588            "Host: bad.example.com\r\n\r\n"
589     assert_equal req, parser.headers(req, http)
590     assert_equal 'https', req['rack.url_scheme']
591     assert_equal '/foo?q=bar', req['REQUEST_URI']
592     assert_equal '/foo', req['REQUEST_PATH']
593     assert_equal 'q=bar', req['QUERY_STRING']
595     uri = URI.parse(url)
596     assert_equal "[::1]", uri.host,
597                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
598     assert_equal "[::1]:", req['HTTP_HOST']
599     assert_equal "[::1]", req['SERVER_NAME']
600     assert_equal '443', req['SERVER_PORT']
601     assert_equal "", http
602     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
603   end
605   def test_absolute_ipv6_uri_with_port
606     parser = HttpParser.new
607     req = parser.env
608     url = "https://[::1]:666/foo?q=bar"
609     http = "GET #{url} HTTP/1.1\r\n" \
610            "Host: bad.example.com\r\n\r\n"
611     assert_equal req, parser.headers(req, http)
612     assert_equal 'https', req['rack.url_scheme']
613     assert_equal '/foo?q=bar', req['REQUEST_URI']
614     assert_equal '/foo', req['REQUEST_PATH']
615     assert_equal 'q=bar', req['QUERY_STRING']
617     uri = URI.parse(url)
618     assert_equal "[::1]", uri.host,
619                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
620     assert_equal "[::1]:666", req['HTTP_HOST']
621     assert_equal "[::1]", req['SERVER_NAME']
622     assert_equal '666', req['SERVER_PORT']
623     assert_equal "", http
624     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
625   end
627   def test_ipv6_host_header
628     parser = HttpParser.new
629     req = parser.env
630     parser.buf << "GET / HTTP/1.1\r\n" \
631                   "Host: [::1]\r\n\r\n"
632     assert_equal req, parser.parse
633     assert_equal "[::1]", req['HTTP_HOST']
634     assert_equal "[::1]", req['SERVER_NAME']
635     assert_equal '80', req['SERVER_PORT']
636     assert_equal "", parser.buf
637     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
638   end
640   def test_ipv6_host_header_with_port
641     parser = HttpParser.new
642     req = parser.env
643     parser.buf << "GET / HTTP/1.1\r\n" \
644                   "Host: [::1]:666\r\n\r\n"
645     assert_equal req, parser.parse
646     assert_equal "[::1]", req['SERVER_NAME']
647     assert_equal '666', req['SERVER_PORT']
648     assert_equal "[::1]:666", req['HTTP_HOST']
649     assert_equal "", parser.buf
650     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
651   end
653   def test_ipv6_host_header_with_empty_port
654     parser = HttpParser.new
655     req = parser.env
656     parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n"
657     assert_equal req, parser.parse
658     assert_equal "[::1]", req['SERVER_NAME']
659     assert_equal '80', req['SERVER_PORT']
660     assert_equal "[::1]:", req['HTTP_HOST']
661     assert_equal "", parser.buf
662     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
663   end
665   # XXX Highly unlikely..., just make sure we don't segfault or assert on it
666   def test_broken_ipv6_host_header
667     parser = HttpParser.new
668     req = parser.env
669     parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n"
670     assert_equal req, parser.parse
671     assert_equal "[", req['SERVER_NAME']
672     assert_equal ':1:', req['SERVER_PORT']
673     assert_equal "[::1:", req['HTTP_HOST']
674     assert_equal "", parser.buf
675   end
677   def test_put_body_oneshot
678     parser = HttpParser.new
679     req = parser.env
680     parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
681     assert_equal req, parser.parse
682     assert_equal '/', req['REQUEST_PATH']
683     assert_equal '/', req['REQUEST_URI']
684     assert_equal 'PUT', req['REQUEST_METHOD']
685     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
686     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
687     assert_equal "abcde", parser.buf
688     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
689   end
691   def test_put_body_later
692     parser = HttpParser.new
693     req = parser.env
694     parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
695     assert_equal req, parser.parse
696     assert_equal '/l', req['REQUEST_PATH']
697     assert_equal '/l', req['REQUEST_URI']
698     assert_equal 'PUT', req['REQUEST_METHOD']
699     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
700     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
701     assert_equal "", parser.buf
702     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
703   end
705   def test_unknown_methods
706     %w(GETT HEADR XGET XHEAD).each { |m|
707       parser = HttpParser.new
708       req = parser.env
709       s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
710       ok = parser.headers(req, s)
711       assert ok
712       assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
713       assert_equal 'posts-17408', req['FRAGMENT']
714       assert_equal 'page=1', req['QUERY_STRING']
715       assert_equal "", s
716       assert_equal m, req['REQUEST_METHOD']
717       assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
718     }
719   end
721   def test_fragment_in_uri
722     parser = HttpParser.new
723     req = parser.env
724     get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
725     parser.buf << get
726     ok = parser.parse
727     assert ok
728     assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
729     assert_equal 'posts-17408', req['FRAGMENT']
730     assert_equal 'page=1', req['QUERY_STRING']
731     assert_equal '', parser.buf
732     assert parser.keepalive?
733   end
735   # lame random garbage maker
736   def rand_data(min, max, readable=true)
737     count = min + ((rand(max)+1) *10).to_i
738     res = count.to_s + "/"
739     
740     if readable
741       res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
742     else
743       res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
744     end
746     return res
747   end
748   
750   def test_horrible_queries
751     parser = HttpParser.new
753     # then that large header names are caught
754     10.times do |c|
755       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
756       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
757         parser.buf << get
758         parser.parse
759         parser.clear
760       end
761     end
763     # then that large mangled field values are caught
764     10.times do |c|
765       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
766       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
767         parser.buf << get
768         parser.parse
769         parser.clear
770       end
771     end
773     # then large headers are rejected too
774     get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
775     get << "X-Test: test\r\n" * (80 * 1024)
776     parser.buf << get
777     assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
778       parser.parse
779     end
780     parser.clear
782     # finally just that random garbage gets blocked all the time
783     10.times do |c|
784       get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
785       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
786         parser.buf << get
787         parser.parse
788         parser.clear
789       end
790     end
792   end
794   def test_leading_tab
795     parser = HttpParser.new
796     get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
797     assert parser.add_parse(get)
798     assert_equal 'example.com', parser.env['HTTP_HOST']
799   end
801   def test_trailing_whitespace
802     parser = HttpParser.new
803     get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
804     assert parser.add_parse(get)
805     assert_equal 'example.com', parser.env['HTTP_HOST']
806   end
808   def test_trailing_tab
809     parser = HttpParser.new
810     get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
811     assert parser.add_parse(get)
812     assert_equal 'example.com', parser.env['HTTP_HOST']
813   end
815   def test_trailing_multiple_linear_whitespace
816     parser = HttpParser.new
817     get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
818     assert parser.add_parse(get)
819     assert_equal 'example.com', parser.env['HTTP_HOST']
820   end
822   def test_embedded_linear_whitespace_ok
823     parser = HttpParser.new
824     get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
825     assert parser.add_parse(get)
826     assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
827   end
829   def test_null_byte_header
830     parser = HttpParser.new
831     get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
832     assert_raises(HttpParserError) { parser.add_parse(get) }
833   end
835   def test_null_byte_in_middle
836     parser = HttpParser.new
837     get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
838     assert_raises(HttpParserError) { parser.add_parse(get) }
839   end
841   def test_null_byte_at_end
842     parser = HttpParser.new
843     get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
844     assert_raises(HttpParserError) { parser.add_parse(get) }
845   end
847   def test_empty_header
848     parser = HttpParser.new
849     get = "GET / HTTP/1.1\r\nHost:  \r\n\r\n"
850     assert parser.add_parse(get)
851     assert_equal '', parser.env['HTTP_HOST']
852   end
854   # so we don't  care about the portability of this test
855   # if it doesn't leak on Linux, it won't leak anywhere else
856   # unless your C compiler or platform is otherwise broken
857   LINUX_PROC_PID_STATUS = "/proc/self/status"
858   def test_memory_leak
859     match_rss = /^VmRSS:\s+(\d+)/
860     if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
861       before = $1.to_i
862       1000000.times { Unicorn::HttpParser.new }
863       File.read(LINUX_PROC_PID_STATUS) =~ match_rss
864       after = $1.to_i
865       diff = after - before
866       assert(diff < 10000, "memory grew more than 10M: #{diff}")
867     end
868   end if RUBY_PLATFORM =~ /linux/ &&
869          File.readable?(LINUX_PROC_PID_STATUS) &&
870          !defined?(RUBY_ENGINE)
872   def test_memsize
873     require 'objspace'
874     if ObjectSpace.respond_to?(:memsize_of)
875       n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
876       assert_kind_of Integer, n
877       # need to update this when 128-bit machines come out
878       # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
879       # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
880       assert_operator n, :<=, 96
881       assert_operator n, :>, 0
882     end
883   rescue LoadError
884     # not all Ruby implementations have objspace
885   end