797a449efe636e08475ebed6fd84b916bdbcc3f0
[unicorn.git] / test / unit / test_http_parser.rb
blob797a449efe636e08475ebed6fd84b916bdbcc3f0
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 GPLv3
7 # Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
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_continuation_eats_leading_spaces
234     parser = HttpParser.new
235     header = "GET / HTTP/1.1\r\n" \
236              "X-ASDF:      \r\n" \
237              "\t\r\n" \
238              "    \r\n" \
239              "  ASDF\r\n\r\n"
240     parser.buf << header
241     req = parser.env
242     assert_equal req, parser.parse
243     assert_equal '', parser.buf
244     assert_equal 'ASDF', req['HTTP_X_ASDF']
245   end
247   def test_continuation_eats_scattered_leading_spaces
248     parser = HttpParser.new
249     header = "GET / HTTP/1.1\r\n" \
250              "X-ASDF:   hi\r\n" \
251              "    y\r\n" \
252              "\t\r\n" \
253              "       x\r\n" \
254              "  ASDF\r\n\r\n"
255     req = parser.env
256     parser.buf << header
257     assert_equal req, parser.parse
258     assert_equal '', parser.buf
259     assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
260   end
262   def test_continuation_eats_trailing_spaces
263     parser = HttpParser.new
264     header = "GET / HTTP/1.1\r\n" \
265              "X-ASDF:      \r\n" \
266              "\t\r\n" \
267              "  b  \r\n" \
268              "  ASDF\r\n\r\n"
269     parser.buf << header
270     req = parser.env
271     assert_equal req, parser.parse
272     assert_equal '', parser.buf
273     assert_equal 'b ASDF', req['HTTP_X_ASDF']
274   end
276   def test_continuation_with_absolute_uri_and_ignored_host_header
277     parser = HttpParser.new
278     header = "GET http://example.com/ HTTP/1.1\r\n" \
279              "Host: \r\n" \
280              "    YHBT.net\r\n" \
281              "\r\n"
282     parser.buf << header
283     req = parser.env
284     assert_equal req, parser.parse
285     assert_equal 'example.com', req['HTTP_HOST']
286   end
288   # this may seem to be testing more of an implementation detail, but
289   # it also helps ensure we're safe in the presence of multiple parsers
290   # in case we ever go multithreaded/evented...
291   def test_resumable_continuations
292     nr = 1000
293     header = "GET / HTTP/1.1\r\n" \
294              "X-ASDF:      \r\n" \
295              "  hello\r\n"
296     tmp = []
297     nr.times { |i|
298       parser = HttpParser.new
299       req = parser.env
300       parser.buf << "#{header} #{i}\r\n"
301       assert parser.parse.nil?
302       asdf = req['HTTP_X_ASDF']
303       assert_equal "hello #{i}", asdf
304       tmp << [ parser, asdf ]
305     }
306     tmp.each_with_index { |(parser, asdf), i|
307       parser.buf << " .\r\n\r\n"
308       assert parser.parse
309       assert_equal "hello #{i} .", asdf
310     }
311   end
313   def test_invalid_continuation
314     parser = HttpParser.new
315     header = "GET / HTTP/1.1\r\n" \
316              "    y\r\n" \
317              "Host: hello\r\n" \
318              "\r\n"
319     parser.buf << header
320     assert_raises(HttpParserError) { parser.parse }
321   end
323   def test_parse_ie6_urls
324     %w(/some/random/path"
325        /some/random/path>
326        /some/random/path<
327        /we/love/you/ie6?q=<"">
328        /url?<="&>="
329        /mal"formed"?
330     ).each do |path|
331       parser = HttpParser.new
332       req = parser.env
333       sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
334       assert_equal req, parser.headers(req, sorta_safe)
335       assert_equal path, req['REQUEST_URI']
336       assert_equal '', sorta_safe
337       assert parser.keepalive?
338     end
339   end
340   
341   def test_parse_error
342     parser = HttpParser.new
343     req = parser.env
344     bad_http = "GET / SsUTF/1.1"
346     assert_raises(HttpParserError) { parser.headers(req, bad_http) }
348     # make sure we can recover
349     parser.clear
350     req.clear
351     assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
352     assert ! parser.keepalive?
353   end
355   def test_piecemeal
356     parser = HttpParser.new
357     req = parser.env
358     http = "GET"
359     assert_nil parser.headers(req, http)
360     assert_nil parser.headers(req, http)
361     assert_nil parser.headers(req, http << " / HTTP/1.0")
362     assert_equal '/', req['REQUEST_PATH']
363     assert_equal '/', req['REQUEST_URI']
364     assert_equal 'GET', req['REQUEST_METHOD']
365     assert_nil parser.headers(req, http << "\r\n")
366     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
367     assert_nil parser.headers(req, http << "\r")
368     assert_equal req, parser.headers(req, http << "\n")
369     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
370     assert_nil req['FRAGMENT']
371     assert_equal '', req['QUERY_STRING']
372     assert_equal "", http
373     assert ! parser.keepalive?
374   end
376   # not common, but underscores do appear in practice
377   def test_absolute_uri_underscores
378     parser = HttpParser.new
379     req = parser.env
380     http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
381     parser.buf << http
382     assert_equal req, parser.parse
383     assert_equal 'http', req['rack.url_scheme']
384     assert_equal '/foo?q=bar', req['REQUEST_URI']
385     assert_equal '/foo', req['REQUEST_PATH']
386     assert_equal 'q=bar', req['QUERY_STRING']
388     assert_equal 'under_score.example.com', req['HTTP_HOST']
389     assert_equal 'under_score.example.com', req['SERVER_NAME']
390     assert_equal '80', req['SERVER_PORT']
391     assert_equal "", parser.buf
392     assert ! parser.keepalive?
393   end
395   # some dumb clients add users because they're stupid
396   def test_absolute_uri_w_user
397     parser = HttpParser.new
398     req = parser.env
399     http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
400     parser.buf << http
401     assert_equal req, parser.parse
402     assert_equal 'http', req['rack.url_scheme']
403     assert_equal '/foo?q=bar', req['REQUEST_URI']
404     assert_equal '/foo', req['REQUEST_PATH']
405     assert_equal 'q=bar', req['QUERY_STRING']
407     assert_equal 'example.com', req['HTTP_HOST']
408     assert_equal 'example.com', req['SERVER_NAME']
409     assert_equal '80', req['SERVER_PORT']
410     assert_equal "", parser.buf
411     assert ! parser.keepalive?
412   end
414   # since Mongrel supported anything URI.parse supported, we're stuck
415   # supporting everything URI.parse supports
416   def test_absolute_uri_uri_parse
417     "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
418       parser = HttpParser.new
419       req = parser.env
420       http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
421       assert_equal req, parser.headers(req, http)
422       assert_equal 'http', req['rack.url_scheme']
423       assert_equal '/', req['REQUEST_URI']
424       assert_equal '/', req['REQUEST_PATH']
425       assert_equal '', req['QUERY_STRING']
427       assert_equal 'example.com', req['HTTP_HOST']
428       assert_equal 'example.com', req['SERVER_NAME']
429       assert_equal '80', req['SERVER_PORT']
430       assert_equal "", http
431       assert ! parser.keepalive?
432     end
433   end
435   def test_absolute_uri
436     parser = HttpParser.new
437     req = parser.env
438     parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
439     assert_equal req, parser.parse
440     assert_equal 'http', req['rack.url_scheme']
441     assert_equal '/foo?q=bar', req['REQUEST_URI']
442     assert_equal '/foo', req['REQUEST_PATH']
443     assert_equal 'q=bar', 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 "", parser.buf
449     assert ! parser.keepalive?
450   end
452   # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
453   def test_absolute_uri_https
454     parser = HttpParser.new
455     req = parser.env
456     http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
457            "X-Forwarded-Proto: http\r\n\r\n"
458     parser.buf << http
459     assert_equal req, parser.parse
460     assert_equal 'https', req['rack.url_scheme']
461     assert_equal '/foo?q=bar', req['REQUEST_URI']
462     assert_equal '/foo', req['REQUEST_PATH']
463     assert_equal 'q=bar', req['QUERY_STRING']
465     assert_equal 'example.com', req['HTTP_HOST']
466     assert_equal 'example.com', req['SERVER_NAME']
467     assert_equal '443', req['SERVER_PORT']
468     assert_equal "", parser.buf
469     assert parser.keepalive?
470   end
472   # Host: header should be ignored for absolute URIs
473   def test_absolute_uri_with_port
474     parser = HttpParser.new
475     req = parser.env
476     parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
477            "Host: bad.example.com\r\n\r\n"
478     assert_equal req, parser.parse
479     assert_equal 'http', req['rack.url_scheme']
480     assert_equal '/foo?q=bar', req['REQUEST_URI']
481     assert_equal '/foo', req['REQUEST_PATH']
482     assert_equal 'q=bar', req['QUERY_STRING']
484     assert_equal 'example.com:8080', req['HTTP_HOST']
485     assert_equal 'example.com', req['SERVER_NAME']
486     assert_equal '8080', req['SERVER_PORT']
487     assert_equal "", parser.buf
488     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
489   end
491   def test_absolute_uri_with_empty_port
492     parser = HttpParser.new
493     req = parser.env
494     parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
495            "Host: bad.example.com\r\n\r\n"
496     assert_equal req, parser.parse
497     assert_equal 'https', 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:', req['HTTP_HOST']
503     assert_equal 'example.com', req['SERVER_NAME']
504     assert_equal '443', 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_ipv6_uri
510     parser = HttpParser.new
511     req = parser.env
512     url = "http://[::1]/foo?q=bar"
513     http = "GET #{url} HTTP/1.1\r\n" \
514            "Host: bad.example.com\r\n\r\n"
515     assert_equal req, parser.headers(req, http)
516     assert_equal 'http', req['rack.url_scheme']
517     assert_equal '/foo?q=bar', req['REQUEST_URI']
518     assert_equal '/foo', req['REQUEST_PATH']
519     assert_equal 'q=bar', req['QUERY_STRING']
521     uri = URI.parse(url)
522     assert_equal "[::1]", uri.host,
523                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
524     assert_equal "[::1]", req['HTTP_HOST']
525     assert_equal "[::1]", req['SERVER_NAME']
526     assert_equal '80', req['SERVER_PORT']
527     assert_equal "", http
528     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
529   end
531   def test_absolute_ipv6_uri_alpha
532     parser = HttpParser.new
533     req = parser.env
534     url = "http://[::a]/"
535     http = "GET #{url} HTTP/1.1\r\n" \
536            "Host: bad.example.com\r\n\r\n"
537     assert_equal req, parser.headers(req, http)
538     assert_equal 'http', req['rack.url_scheme']
540     uri = URI.parse(url)
541     assert_equal "[::a]", uri.host,
542                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
543     assert_equal "[::a]", req['HTTP_HOST']
544     assert_equal "[::a]", req['SERVER_NAME']
545     assert_equal '80', req['SERVER_PORT']
546   end
548   def test_absolute_ipv6_uri_alpha_2
549     parser = HttpParser.new
550     req = parser.env
551     url = "http://[::B]/"
552     http = "GET #{url} HTTP/1.1\r\n" \
553            "Host: bad.example.com\r\n\r\n"
554     assert_equal req, parser.headers(req, http)
555     assert_equal 'http', req['rack.url_scheme']
557     uri = URI.parse(url)
558     assert_equal "[::B]", uri.host,
559                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
560     assert_equal "[::B]", req['HTTP_HOST']
561     assert_equal "[::B]", req['SERVER_NAME']
562     assert_equal '80', req['SERVER_PORT']
563   end
565   def test_absolute_ipv6_uri_with_empty_port
566     parser = HttpParser.new
567     req = parser.env
568     url = "https://[::1]:/foo?q=bar"
569     http = "GET #{url} HTTP/1.1\r\n" \
570            "Host: bad.example.com\r\n\r\n"
571     assert_equal req, parser.headers(req, http)
572     assert_equal 'https', req['rack.url_scheme']
573     assert_equal '/foo?q=bar', req['REQUEST_URI']
574     assert_equal '/foo', req['REQUEST_PATH']
575     assert_equal 'q=bar', req['QUERY_STRING']
577     uri = URI.parse(url)
578     assert_equal "[::1]", uri.host,
579                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
580     assert_equal "[::1]:", req['HTTP_HOST']
581     assert_equal "[::1]", req['SERVER_NAME']
582     assert_equal '443', req['SERVER_PORT']
583     assert_equal "", http
584     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
585   end
587   def test_absolute_ipv6_uri_with_port
588     parser = HttpParser.new
589     req = parser.env
590     url = "https://[::1]:666/foo?q=bar"
591     http = "GET #{url} HTTP/1.1\r\n" \
592            "Host: bad.example.com\r\n\r\n"
593     assert_equal req, parser.headers(req, http)
594     assert_equal 'https', req['rack.url_scheme']
595     assert_equal '/foo?q=bar', req['REQUEST_URI']
596     assert_equal '/foo', req['REQUEST_PATH']
597     assert_equal 'q=bar', req['QUERY_STRING']
599     uri = URI.parse(url)
600     assert_equal "[::1]", uri.host,
601                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
602     assert_equal "[::1]:666", req['HTTP_HOST']
603     assert_equal "[::1]", req['SERVER_NAME']
604     assert_equal '666', req['SERVER_PORT']
605     assert_equal "", http
606     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
607   end
609   def test_ipv6_host_header
610     parser = HttpParser.new
611     req = parser.env
612     parser.buf << "GET / HTTP/1.1\r\n" \
613                   "Host: [::1]\r\n\r\n"
614     assert_equal req, parser.parse
615     assert_equal "[::1]", req['HTTP_HOST']
616     assert_equal "[::1]", req['SERVER_NAME']
617     assert_equal '80', req['SERVER_PORT']
618     assert_equal "", parser.buf
619     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
620   end
622   def test_ipv6_host_header_with_port
623     parser = HttpParser.new
624     req = parser.env
625     parser.buf << "GET / HTTP/1.1\r\n" \
626                   "Host: [::1]:666\r\n\r\n"
627     assert_equal req, parser.parse
628     assert_equal "[::1]", req['SERVER_NAME']
629     assert_equal '666', req['SERVER_PORT']
630     assert_equal "[::1]:666", req['HTTP_HOST']
631     assert_equal "", parser.buf
632     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
633   end
635   def test_ipv6_host_header_with_empty_port
636     parser = HttpParser.new
637     req = parser.env
638     parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n"
639     assert_equal req, parser.parse
640     assert_equal "[::1]", req['SERVER_NAME']
641     assert_equal '80', req['SERVER_PORT']
642     assert_equal "[::1]:", req['HTTP_HOST']
643     assert_equal "", parser.buf
644     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
645   end
647   # XXX Highly unlikely..., just make sure we don't segfault or assert on it
648   def test_broken_ipv6_host_header
649     parser = HttpParser.new
650     req = parser.env
651     parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n"
652     assert_equal req, parser.parse
653     assert_equal "[", req['SERVER_NAME']
654     assert_equal ':1:', req['SERVER_PORT']
655     assert_equal "[::1:", req['HTTP_HOST']
656     assert_equal "", parser.buf
657   end
659   def test_put_body_oneshot
660     parser = HttpParser.new
661     req = parser.env
662     parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
663     assert_equal req, parser.parse
664     assert_equal '/', req['REQUEST_PATH']
665     assert_equal '/', req['REQUEST_URI']
666     assert_equal 'PUT', req['REQUEST_METHOD']
667     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
668     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
669     assert_equal "abcde", parser.buf
670     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
671   end
673   def test_put_body_later
674     parser = HttpParser.new
675     req = parser.env
676     parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
677     assert_equal req, parser.parse
678     assert_equal '/l', req['REQUEST_PATH']
679     assert_equal '/l', req['REQUEST_URI']
680     assert_equal 'PUT', req['REQUEST_METHOD']
681     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
682     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
683     assert_equal "", parser.buf
684     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
685   end
687   def test_unknown_methods
688     %w(GETT HEADR XGET XHEAD).each { |m|
689       parser = HttpParser.new
690       req = parser.env
691       s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
692       ok = false
693       assert_nothing_raised do
694         ok = parser.headers(req, s)
695       end
696       assert ok
697       assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
698       assert_equal 'posts-17408', req['FRAGMENT']
699       assert_equal 'page=1', req['QUERY_STRING']
700       assert_equal "", s
701       assert_equal m, req['REQUEST_METHOD']
702       assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
703     }
704   end
706   def test_fragment_in_uri
707     parser = HttpParser.new
708     req = parser.env
709     get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
710     parser.buf << get
711     ok = false
712     assert_nothing_raised do
713       ok = parser.parse
714     end
715     assert ok
716     assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
717     assert_equal 'posts-17408', req['FRAGMENT']
718     assert_equal 'page=1', req['QUERY_STRING']
719     assert_equal '', parser.buf
720     assert parser.keepalive?
721   end
723   # lame random garbage maker
724   def rand_data(min, max, readable=true)
725     count = min + ((rand(max)+1) *10).to_i
726     res = count.to_s + "/"
727     
728     if readable
729       res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
730     else
731       res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
732     end
734     return res
735   end
736   
738   def test_horrible_queries
739     parser = HttpParser.new
741     # then that large header names are caught
742     10.times do |c|
743       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
744       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
745         parser.buf << get
746         parser.parse
747         parser.clear
748       end
749     end
751     # then that large mangled field values are caught
752     10.times do |c|
753       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
754       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
755         parser.buf << get
756         parser.parse
757         parser.clear
758       end
759     end
761     # then large headers are rejected too
762     get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
763     get << "X-Test: test\r\n" * (80 * 1024)
764     parser.buf << get
765     assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
766       parser.parse
767     end
768     parser.clear
770     # finally just that random garbage gets blocked all the time
771     10.times do |c|
772       get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
773       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
774         parser.buf << get
775         parser.parse
776         parser.clear
777       end
778     end
780   end
782   def test_leading_tab
783     parser = HttpParser.new
784     get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
785     assert parser.add_parse(get)
786     assert_equal 'example.com', parser.env['HTTP_HOST']
787   end
789   def test_trailing_whitespace
790     parser = HttpParser.new
791     get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
792     assert parser.add_parse(get)
793     assert_equal 'example.com', parser.env['HTTP_HOST']
794   end
796   def test_trailing_tab
797     parser = HttpParser.new
798     get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
799     assert parser.add_parse(get)
800     assert_equal 'example.com', parser.env['HTTP_HOST']
801   end
803   def test_trailing_multiple_linear_whitespace
804     parser = HttpParser.new
805     get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
806     assert parser.add_parse(get)
807     assert_equal 'example.com', parser.env['HTTP_HOST']
808   end
810   def test_embedded_linear_whitespace_ok
811     parser = HttpParser.new
812     get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
813     assert parser.add_parse(get)
814     assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
815   end
817   def test_null_byte_header
818     parser = HttpParser.new
819     get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
820     assert_raises(HttpParserError) { parser.add_parse(get) }
821   end
823   def test_null_byte_in_middle
824     parser = HttpParser.new
825     get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
826     assert_raises(HttpParserError) { parser.add_parse(get) }
827   end
829   def test_null_byte_at_end
830     parser = HttpParser.new
831     get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
832     assert_raises(HttpParserError) { parser.add_parse(get) }
833   end
835   def test_empty_header
836     parser = HttpParser.new
837     get = "GET / HTTP/1.1\r\nHost:  \r\n\r\n"
838     assert parser.add_parse(get)
839     assert_equal '', parser.env['HTTP_HOST']
840   end
842   # so we don't  care about the portability of this test
843   # if it doesn't leak on Linux, it won't leak anywhere else
844   # unless your C compiler or platform is otherwise broken
845   LINUX_PROC_PID_STATUS = "/proc/self/status"
846   def test_memory_leak
847     match_rss = /^VmRSS:\s+(\d+)/
848     if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
849       before = $1.to_i
850       1000000.times { Unicorn::HttpParser.new }
851       File.read(LINUX_PROC_PID_STATUS) =~ match_rss
852       after = $1.to_i
853       diff = after - before
854       assert(diff < 10000, "memory grew more than 10M: #{diff}")
855     end
856   end if RUBY_PLATFORM =~ /linux/ &&
857          File.readable?(LINUX_PROC_PID_STATUS) &&
858          !defined?(RUBY_ENGINE)