test_http_parser: fix for URI too long errors
[unicorn.git] / test / unit / test_http_parser.rb
blobe6a990d85ad0890462fb0469e4296626e1f8a9b4
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.
6 # Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
7 # for more information.
9 require 'test/test_helper'
11 include Unicorn
13 class HttpParserTest < Test::Unit::TestCase
15   def test_parse_simple
16     parser = HttpParser.new
17     req = parser.env
18     http = parser.buf
19     http << "GET / HTTP/1.1\r\n\r\n"
20     assert_equal req, parser.parse
21     assert_equal '', http
23     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
24     assert_equal '/', req['REQUEST_PATH']
25     assert_equal 'HTTP/1.1', req['HTTP_VERSION']
26     assert_equal '/', req['REQUEST_URI']
27     assert_equal 'GET', req['REQUEST_METHOD']
28     assert_nil req['FRAGMENT']
29     assert_equal '', req['QUERY_STRING']
31     assert parser.keepalive?
32     parser.clear
33     req.clear
35     http << "G"
36     assert_nil parser.parse
37     assert_equal "G", http
38     assert req.empty?
40     # try parsing again to ensure we were reset correctly
41     http << "ET /hello-world HTTP/1.1\r\n\r\n"
42     assert parser.parse
44     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
45     assert_equal '/hello-world', req['REQUEST_PATH']
46     assert_equal 'HTTP/1.1', req['HTTP_VERSION']
47     assert_equal '/hello-world', req['REQUEST_URI']
48     assert_equal 'GET', req['REQUEST_METHOD']
49     assert_nil req['FRAGMENT']
50     assert_equal '', req['QUERY_STRING']
51     assert_equal '', http
52     assert parser.keepalive?
53   end
55   def test_tab_lws
56     parser = HttpParser.new
57     req = parser.env
58     parser.buf << "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n"
59     assert_equal req.object_id, parser.parse.object_id
60     assert_equal "foo.bar", req['HTTP_HOST']
61   end
63   def test_connection_close_no_ka
64     parser = HttpParser.new
65     req = parser.env
66     parser.buf << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
67     assert_equal req.object_id, parser.parse.object_id
68     assert_equal "GET", req['REQUEST_METHOD']
69     assert ! parser.keepalive?
70   end
72   def test_connection_keep_alive_ka
73     parser = HttpParser.new
74     req = parser.env
75     parser.buf << "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
76     assert_equal req.object_id, parser.parse.object_id
77     assert parser.keepalive?
78   end
80   def test_connection_keep_alive_no_body
81     parser = HttpParser.new
82     req = parser.env
83     parser.buf << "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
84     assert_equal req.object_id, parser.parse.object_id
85     assert parser.keepalive?
86   end
88   def test_connection_keep_alive_no_body_empty
89     parser = HttpParser.new
90     req = parser.env
91     parser.buf << "POST / HTTP/1.1\r\n" \
92                   "Content-Length: 0\r\n" \
93                   "Connection: keep-alive\r\n\r\n"
94     assert_equal req.object_id, parser.parse.object_id
95     assert parser.keepalive?
96   end
98   def test_connection_keep_alive_ka_bad_version
99     parser = HttpParser.new
100     req = parser.env
101     parser.buf << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
102     assert_equal req.object_id, parser.parse.object_id
103     assert parser.keepalive?
104   end
106   def test_parse_server_host_default_port
107     parser = HttpParser.new
108     req = parser.env
109     parser.buf << "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
110     assert_equal req, parser.parse
111     assert_equal 'foo', req['SERVER_NAME']
112     assert_equal '80', req['SERVER_PORT']
113     assert_equal '', parser.buf
114     assert parser.keepalive?
115   end
117   def test_parse_server_host_alt_port
118     parser = HttpParser.new
119     req = parser.env
120     parser.buf << "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
121     assert_equal req, parser.parse
122     assert_equal 'foo', req['SERVER_NAME']
123     assert_equal '999', req['SERVER_PORT']
124     assert_equal '', parser.buf
125     assert parser.keepalive?
126   end
128   def test_parse_server_host_empty_port
129     parser = HttpParser.new
130     req = parser.env
131     parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
132     assert_equal req, parser.parse
133     assert_equal 'foo', req['SERVER_NAME']
134     assert_equal '80', req['SERVER_PORT']
135     assert_equal '', parser.buf
136     assert parser.keepalive?
137   end
139   def test_parse_server_host_xfp_https
140     parser = HttpParser.new
141     req = parser.env
142     parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \
143                   "X-Forwarded-Proto: https\r\n\r\n"
144     assert_equal req, parser.parse
145     assert_equal 'foo', req['SERVER_NAME']
146     assert_equal '443', req['SERVER_PORT']
147     assert_equal '', parser.buf
148     assert parser.keepalive?
149   end
151   def test_parse_xfp_https_chained
152     parser = HttpParser.new
153     req = parser.env
154     parser.buf << "GET / HTTP/1.0\r\n" \
155                   "X-Forwarded-Proto: https,http\r\n\r\n"
156     assert_equal req, parser.parse
157     assert_equal '443', req['SERVER_PORT'], req.inspect
158     assert_equal 'https', req['rack.url_scheme'], req.inspect
159     assert_equal '', parser.buf
160   end
162   def test_parse_xfp_https_chained_backwards
163     parser = HttpParser.new
164     req = parser.env
165     parser.buf << "GET / HTTP/1.0\r\n" \
166           "X-Forwarded-Proto: http,https\r\n\r\n"
167     assert_equal req, parser.parse
168     assert_equal '80', req['SERVER_PORT'], req.inspect
169     assert_equal 'http', req['rack.url_scheme'], req.inspect
170     assert_equal '', parser.buf
171   end
173   def test_parse_xfp_gopher_is_ignored
174     parser = HttpParser.new
175     req = parser.env
176     parser.buf << "GET / HTTP/1.0\r\n" \
177                   "X-Forwarded-Proto: gopher\r\n\r\n"
178     assert_equal req, parser.parse
179     assert_equal '80', req['SERVER_PORT'], req.inspect
180     assert_equal 'http', req['rack.url_scheme'], req.inspect
181     assert_equal '', parser.buf
182   end
184   def test_parse_x_forwarded_ssl_on
185     parser = HttpParser.new
186     req = parser.env
187     parser.buf << "GET / HTTP/1.0\r\n" \
188                   "X-Forwarded-Ssl: on\r\n\r\n"
189     assert_equal req, parser.parse
190     assert_equal '443', req['SERVER_PORT'], req.inspect
191     assert_equal 'https', req['rack.url_scheme'], req.inspect
192     assert_equal '', parser.buf
193   end
195   def test_parse_x_forwarded_ssl_off
196     parser = HttpParser.new
197     req = parser.env
198     parser.buf << "GET / HTTP/1.0\r\nX-Forwarded-Ssl: off\r\n\r\n"
199     assert_equal req, parser.parse
200     assert_equal '80', req['SERVER_PORT'], req.inspect
201     assert_equal 'http', req['rack.url_scheme'], req.inspect
202     assert_equal '', parser.buf
203   end
205   def test_parse_strange_headers
206     parser = HttpParser.new
207     req = parser.env
208     should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
209     parser.buf << should_be_good
210     assert_equal req, parser.parse
211     assert_equal '', parser.buf
212     assert parser.keepalive?
213   end
215   # legacy test case from Mongrel that we never supported before...
216   # I still consider Pound irrelevant, unfortunately stupid clients that
217   # send extremely big headers do exist and they've managed to find Unicorn...
218   def test_nasty_pound_header
219     parser = HttpParser.new
220     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"
221     req = parser.env
222     parser.buf << nasty_pound_header.dup
224     assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
225     expect = $1.dup
226     expect.gsub!(/\r\n\t/, ' ')
227     assert_equal req, parser.parse
228     assert_equal '', parser.buf
229     assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
230   end
232   def test_continuation_eats_leading_spaces
233     parser = HttpParser.new
234     header = "GET / HTTP/1.1\r\n" \
235              "X-ASDF:      \r\n" \
236              "\t\r\n" \
237              "    \r\n" \
238              "  ASDF\r\n\r\n"
239     parser.buf << header
240     req = parser.env
241     assert_equal req, parser.parse
242     assert_equal '', parser.buf
243     assert_equal 'ASDF', req['HTTP_X_ASDF']
244   end
246   def test_continuation_eats_scattered_leading_spaces
247     parser = HttpParser.new
248     header = "GET / HTTP/1.1\r\n" \
249              "X-ASDF:   hi\r\n" \
250              "    y\r\n" \
251              "\t\r\n" \
252              "       x\r\n" \
253              "  ASDF\r\n\r\n"
254     req = parser.env
255     parser.buf << header
256     assert_equal req, parser.parse
257     assert_equal '', parser.buf
258     assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
259   end
261   def test_continuation_eats_trailing_spaces
262     parser = HttpParser.new
263     header = "GET / HTTP/1.1\r\n" \
264              "X-ASDF:      \r\n" \
265              "\t\r\n" \
266              "  b  \r\n" \
267              "  ASDF\r\n\r\n"
268     parser.buf << header
269     req = parser.env
270     assert_equal req, parser.parse
271     assert_equal '', parser.buf
272     assert_equal 'b ASDF', req['HTTP_X_ASDF']
273   end
275   def test_continuation_with_absolute_uri_and_ignored_host_header
276     parser = HttpParser.new
277     header = "GET http://example.com/ HTTP/1.1\r\n" \
278              "Host: \r\n" \
279              "    YHBT.net\r\n" \
280              "\r\n"
281     parser.buf << header
282     req = parser.env
283     assert_equal req, parser.parse
284     assert_equal 'example.com', req['HTTP_HOST']
285   end
287   # this may seem to be testing more of an implementation detail, but
288   # it also helps ensure we're safe in the presence of multiple parsers
289   # in case we ever go multithreaded/evented...
290   def test_resumable_continuations
291     nr = 1000
292     header = "GET / HTTP/1.1\r\n" \
293              "X-ASDF:      \r\n" \
294              "  hello\r\n"
295     tmp = []
296     nr.times { |i|
297       parser = HttpParser.new
298       req = parser.env
299       parser.buf << "#{header} #{i}\r\n"
300       assert parser.parse.nil?
301       asdf = req['HTTP_X_ASDF']
302       assert_equal "hello #{i}", asdf
303       tmp << [ parser, asdf ]
304     }
305     tmp.each_with_index { |(parser, asdf), i|
306       parser.buf << " .\r\n\r\n"
307       assert parser.parse
308       assert_equal "hello #{i} .", asdf
309     }
310   end
312   def test_invalid_continuation
313     parser = HttpParser.new
314     header = "GET / HTTP/1.1\r\n" \
315              "    y\r\n" \
316              "Host: hello\r\n" \
317              "\r\n"
318     parser.buf << header
319     assert_raises(HttpParserError) { parser.parse }
320   end
322   def test_parse_ie6_urls
323     %w(/some/random/path"
324        /some/random/path>
325        /some/random/path<
326        /we/love/you/ie6?q=<"">
327        /url?<="&>="
328        /mal"formed"?
329     ).each do |path|
330       parser = HttpParser.new
331       req = parser.env
332       sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
333       assert_equal req, parser.headers(req, sorta_safe)
334       assert_equal path, req['REQUEST_URI']
335       assert_equal '', sorta_safe
336       assert parser.keepalive?
337     end
338   end
339   
340   def test_parse_error
341     parser = HttpParser.new
342     req = parser.env
343     bad_http = "GET / SsUTF/1.1"
345     assert_raises(HttpParserError) { parser.headers(req, bad_http) }
347     # make sure we can recover
348     parser.clear
349     req.clear
350     assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
351     assert ! parser.keepalive?
352   end
354   def test_piecemeal
355     parser = HttpParser.new
356     req = parser.env
357     http = "GET"
358     assert_nil parser.headers(req, http)
359     assert_nil parser.headers(req, http)
360     assert_nil parser.headers(req, http << " / HTTP/1.0")
361     assert_equal '/', req['REQUEST_PATH']
362     assert_equal '/', req['REQUEST_URI']
363     assert_equal 'GET', req['REQUEST_METHOD']
364     assert_nil parser.headers(req, http << "\r\n")
365     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
366     assert_nil parser.headers(req, http << "\r")
367     assert_equal req, parser.headers(req, http << "\n")
368     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
369     assert_nil req['FRAGMENT']
370     assert_equal '', req['QUERY_STRING']
371     assert_equal "", http
372     assert ! parser.keepalive?
373   end
375   # not common, but underscores do appear in practice
376   def test_absolute_uri_underscores
377     parser = HttpParser.new
378     req = parser.env
379     http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
380     parser.buf << http
381     assert_equal req, parser.parse
382     assert_equal 'http', req['rack.url_scheme']
383     assert_equal '/foo?q=bar', req['REQUEST_URI']
384     assert_equal '/foo', req['REQUEST_PATH']
385     assert_equal 'q=bar', req['QUERY_STRING']
387     assert_equal 'under_score.example.com', req['HTTP_HOST']
388     assert_equal 'under_score.example.com', req['SERVER_NAME']
389     assert_equal '80', req['SERVER_PORT']
390     assert_equal "", parser.buf
391     assert ! parser.keepalive?
392   end
394   # some dumb clients add users because they're stupid
395   def test_absolute_uri_w_user
396     parser = HttpParser.new
397     req = parser.env
398     http = "GET http://user%20space@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 'example.com', req['HTTP_HOST']
407     assert_equal 'example.com', req['SERVER_NAME']
408     assert_equal '80', req['SERVER_PORT']
409     assert_equal "", parser.buf
410     assert ! parser.keepalive?
411   end
413   # since Mongrel supported anything URI.parse supported, we're stuck
414   # supporting everything URI.parse supports
415   def test_absolute_uri_uri_parse
416     "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
417       parser = HttpParser.new
418       req = parser.env
419       http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
420       assert_equal req, parser.headers(req, http)
421       assert_equal 'http', req['rack.url_scheme']
422       assert_equal '/', req['REQUEST_URI']
423       assert_equal '/', req['REQUEST_PATH']
424       assert_equal '', req['QUERY_STRING']
426       assert_equal 'example.com', req['HTTP_HOST']
427       assert_equal 'example.com', req['SERVER_NAME']
428       assert_equal '80', req['SERVER_PORT']
429       assert_equal "", http
430       assert ! parser.keepalive?
431     end
432   end
434   def test_absolute_uri
435     parser = HttpParser.new
436     req = parser.env
437     parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
438     assert_equal req, parser.parse
439     assert_equal 'http', req['rack.url_scheme']
440     assert_equal '/foo?q=bar', req['REQUEST_URI']
441     assert_equal '/foo', req['REQUEST_PATH']
442     assert_equal 'q=bar', req['QUERY_STRING']
444     assert_equal 'example.com', req['HTTP_HOST']
445     assert_equal 'example.com', req['SERVER_NAME']
446     assert_equal '80', req['SERVER_PORT']
447     assert_equal "", parser.buf
448     assert ! parser.keepalive?
449   end
451   # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
452   def test_absolute_uri_https
453     parser = HttpParser.new
454     req = parser.env
455     http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
456            "X-Forwarded-Proto: http\r\n\r\n"
457     parser.buf << http
458     assert_equal req, parser.parse
459     assert_equal 'https', req['rack.url_scheme']
460     assert_equal '/foo?q=bar', req['REQUEST_URI']
461     assert_equal '/foo', req['REQUEST_PATH']
462     assert_equal 'q=bar', req['QUERY_STRING']
464     assert_equal 'example.com', req['HTTP_HOST']
465     assert_equal 'example.com', req['SERVER_NAME']
466     assert_equal '443', req['SERVER_PORT']
467     assert_equal "", parser.buf
468     assert parser.keepalive?
469   end
471   # Host: header should be ignored for absolute URIs
472   def test_absolute_uri_with_port
473     parser = HttpParser.new
474     req = parser.env
475     parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
476            "Host: bad.example.com\r\n\r\n"
477     assert_equal req, parser.parse
478     assert_equal 'http', 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:8080', req['HTTP_HOST']
484     assert_equal 'example.com', req['SERVER_NAME']
485     assert_equal '8080', req['SERVER_PORT']
486     assert_equal "", parser.buf
487     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
488   end
490   def test_absolute_uri_with_empty_port
491     parser = HttpParser.new
492     req = parser.env
493     parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
494            "Host: bad.example.com\r\n\r\n"
495     assert_equal req, parser.parse
496     assert_equal 'https', req['rack.url_scheme']
497     assert_equal '/foo?q=bar', req['REQUEST_URI']
498     assert_equal '/foo', req['REQUEST_PATH']
499     assert_equal 'q=bar', req['QUERY_STRING']
501     assert_equal 'example.com:', req['HTTP_HOST']
502     assert_equal 'example.com', req['SERVER_NAME']
503     assert_equal '443', req['SERVER_PORT']
504     assert_equal "", parser.buf
505     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
506   end
508   def test_absolute_ipv6_uri
509     parser = HttpParser.new
510     req = parser.env
511     url = "http://[::1]/foo?q=bar"
512     http = "GET #{url} HTTP/1.1\r\n" \
513            "Host: bad.example.com\r\n\r\n"
514     assert_equal req, parser.headers(req, http)
515     assert_equal 'http', 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     uri = URI.parse(url)
521     assert_equal "[::1]", uri.host,
522                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
523     assert_equal "[::1]", req['HTTP_HOST']
524     assert_equal "[::1]", req['SERVER_NAME']
525     assert_equal '80', req['SERVER_PORT']
526     assert_equal "", http
527     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
528   end
530   def test_absolute_ipv6_uri_alpha
531     parser = HttpParser.new
532     req = parser.env
533     url = "http://[::a]/"
534     http = "GET #{url} HTTP/1.1\r\n" \
535            "Host: bad.example.com\r\n\r\n"
536     assert_equal req, parser.headers(req, http)
537     assert_equal 'http', req['rack.url_scheme']
539     uri = URI.parse(url)
540     assert_equal "[::a]", uri.host,
541                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
542     assert_equal "[::a]", req['HTTP_HOST']
543     assert_equal "[::a]", req['SERVER_NAME']
544     assert_equal '80', req['SERVER_PORT']
545   end
547   def test_absolute_ipv6_uri_alpha_2
548     parser = HttpParser.new
549     req = parser.env
550     url = "http://[::B]/"
551     http = "GET #{url} HTTP/1.1\r\n" \
552            "Host: bad.example.com\r\n\r\n"
553     assert_equal req, parser.headers(req, http)
554     assert_equal 'http', req['rack.url_scheme']
556     uri = URI.parse(url)
557     assert_equal "[::B]", uri.host,
558                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
559     assert_equal "[::B]", req['HTTP_HOST']
560     assert_equal "[::B]", req['SERVER_NAME']
561     assert_equal '80', req['SERVER_PORT']
562   end
564   def test_absolute_ipv6_uri_with_empty_port
565     parser = HttpParser.new
566     req = parser.env
567     url = "https://[::1]:/foo?q=bar"
568     http = "GET #{url} HTTP/1.1\r\n" \
569            "Host: bad.example.com\r\n\r\n"
570     assert_equal req, parser.headers(req, http)
571     assert_equal 'https', req['rack.url_scheme']
572     assert_equal '/foo?q=bar', req['REQUEST_URI']
573     assert_equal '/foo', req['REQUEST_PATH']
574     assert_equal 'q=bar', req['QUERY_STRING']
576     uri = URI.parse(url)
577     assert_equal "[::1]", uri.host,
578                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
579     assert_equal "[::1]:", req['HTTP_HOST']
580     assert_equal "[::1]", req['SERVER_NAME']
581     assert_equal '443', req['SERVER_PORT']
582     assert_equal "", http
583     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
584   end
586   def test_absolute_ipv6_uri_with_port
587     parser = HttpParser.new
588     req = parser.env
589     url = "https://[::1]:666/foo?q=bar"
590     http = "GET #{url} HTTP/1.1\r\n" \
591            "Host: bad.example.com\r\n\r\n"
592     assert_equal req, parser.headers(req, http)
593     assert_equal 'https', req['rack.url_scheme']
594     assert_equal '/foo?q=bar', req['REQUEST_URI']
595     assert_equal '/foo', req['REQUEST_PATH']
596     assert_equal 'q=bar', req['QUERY_STRING']
598     uri = URI.parse(url)
599     assert_equal "[::1]", uri.host,
600                  "URI.parse changed upstream for #{url}? host=#{uri.host}"
601     assert_equal "[::1]:666", req['HTTP_HOST']
602     assert_equal "[::1]", req['SERVER_NAME']
603     assert_equal '666', req['SERVER_PORT']
604     assert_equal "", http
605     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
606   end
608   def test_ipv6_host_header
609     parser = HttpParser.new
610     req = parser.env
611     parser.buf << "GET / HTTP/1.1\r\n" \
612                   "Host: [::1]\r\n\r\n"
613     assert_equal req, parser.parse
614     assert_equal "[::1]", req['HTTP_HOST']
615     assert_equal "[::1]", req['SERVER_NAME']
616     assert_equal '80', req['SERVER_PORT']
617     assert_equal "", parser.buf
618     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
619   end
621   def test_ipv6_host_header_with_port
622     parser = HttpParser.new
623     req = parser.env
624     parser.buf << "GET / HTTP/1.1\r\n" \
625                   "Host: [::1]:666\r\n\r\n"
626     assert_equal req, parser.parse
627     assert_equal "[::1]", req['SERVER_NAME']
628     assert_equal '666', req['SERVER_PORT']
629     assert_equal "[::1]:666", req['HTTP_HOST']
630     assert_equal "", parser.buf
631     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
632   end
634   def test_ipv6_host_header_with_empty_port
635     parser = HttpParser.new
636     req = parser.env
637     parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n"
638     assert_equal req, parser.parse
639     assert_equal "[::1]", req['SERVER_NAME']
640     assert_equal '80', req['SERVER_PORT']
641     assert_equal "[::1]:", req['HTTP_HOST']
642     assert_equal "", parser.buf
643     assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
644   end
646   # XXX Highly unlikely..., just make sure we don't segfault or assert on it
647   def test_broken_ipv6_host_header
648     parser = HttpParser.new
649     req = parser.env
650     parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n"
651     assert_equal req, parser.parse
652     assert_equal "[", req['SERVER_NAME']
653     assert_equal ':1:', req['SERVER_PORT']
654     assert_equal "[::1:", req['HTTP_HOST']
655     assert_equal "", parser.buf
656   end
658   def test_put_body_oneshot
659     parser = HttpParser.new
660     req = parser.env
661     parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
662     assert_equal req, parser.parse
663     assert_equal '/', req['REQUEST_PATH']
664     assert_equal '/', req['REQUEST_URI']
665     assert_equal 'PUT', req['REQUEST_METHOD']
666     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
667     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
668     assert_equal "abcde", parser.buf
669     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
670   end
672   def test_put_body_later
673     parser = HttpParser.new
674     req = parser.env
675     parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
676     assert_equal req, parser.parse
677     assert_equal '/l', req['REQUEST_PATH']
678     assert_equal '/l', req['REQUEST_URI']
679     assert_equal 'PUT', req['REQUEST_METHOD']
680     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
681     assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
682     assert_equal "", parser.buf
683     assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
684   end
686   def test_unknown_methods
687     %w(GETT HEADR XGET XHEAD).each { |m|
688       parser = HttpParser.new
689       req = parser.env
690       s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
691       ok = false
692       assert_nothing_raised do
693         ok = parser.headers(req, s)
694       end
695       assert ok
696       assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
697       assert_equal 'posts-17408', req['FRAGMENT']
698       assert_equal 'page=1', req['QUERY_STRING']
699       assert_equal "", s
700       assert_equal m, req['REQUEST_METHOD']
701       assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
702     }
703   end
705   def test_fragment_in_uri
706     parser = HttpParser.new
707     req = parser.env
708     get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
709     parser.buf << get
710     ok = false
711     assert_nothing_raised do
712       ok = parser.parse
713     end
714     assert ok
715     assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
716     assert_equal 'posts-17408', req['FRAGMENT']
717     assert_equal 'page=1', req['QUERY_STRING']
718     assert_equal '', parser.buf
719     assert parser.keepalive?
720   end
722   # lame random garbage maker
723   def rand_data(min, max, readable=true)
724     count = min + ((rand(max)+1) *10).to_i
725     res = count.to_s + "/"
726     
727     if readable
728       res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
729     else
730       res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
731     end
733     return res
734   end
735   
737   def test_horrible_queries
738     parser = HttpParser.new
740     # then that large header names are caught
741     10.times do |c|
742       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
743       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
744         parser.buf << get
745         parser.parse
746         parser.clear
747       end
748     end
750     # then that large mangled field values are caught
751     10.times do |c|
752       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
753       assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
754         parser.buf << get
755         parser.parse
756         parser.clear
757       end
758     end
760     # then large headers are rejected too
761     get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
762     get << "X-Test: test\r\n" * (80 * 1024)
763     parser.buf << get
764     assert_raises Unicorn::HttpParserError do
765       parser.parse
766     end
767     parser.clear
769     # finally just that random garbage gets blocked all the time
770     10.times do |c|
771       get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
772       assert_raises Unicorn::HttpParserError do
773         parser.buf << get
774         parser.parse
775         parser.clear
776       end
777     end
779   end
781   def test_leading_tab
782     parser = HttpParser.new
783     get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
784     assert parser.add_parse(get)
785     assert_equal 'example.com', parser.env['HTTP_HOST']
786   end
788   def test_trailing_whitespace
789     parser = HttpParser.new
790     get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
791     assert parser.add_parse(get)
792     assert_equal 'example.com', parser.env['HTTP_HOST']
793   end
795   def test_trailing_tab
796     parser = HttpParser.new
797     get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
798     assert parser.add_parse(get)
799     assert_equal 'example.com', parser.env['HTTP_HOST']
800   end
802   def test_trailing_multiple_linear_whitespace
803     parser = HttpParser.new
804     get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
805     assert parser.add_parse(get)
806     assert_equal 'example.com', parser.env['HTTP_HOST']
807   end
809   def test_embedded_linear_whitespace_ok
810     parser = HttpParser.new
811     get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
812     assert parser.add_parse(get)
813     assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
814   end
816   def test_empty_header
817     parser = HttpParser.new
818     get = "GET / HTTP/1.1\r\nHost:  \r\n\r\n"
819     assert parser.add_parse(get)
820     assert_equal '', parser.env['HTTP_HOST']
821   end
823   # so we don't  care about the portability of this test
824   # if it doesn't leak on Linux, it won't leak anywhere else
825   # unless your C compiler or platform is otherwise broken
826   LINUX_PROC_PID_STATUS = "/proc/self/status"
827   def test_memory_leak
828     match_rss = /^VmRSS:\s+(\d+)/
829     if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
830       before = $1.to_i
831       1000000.times { Unicorn::HttpParser.new }
832       File.read(LINUX_PROC_PID_STATUS) =~ match_rss
833       after = $1.to_i
834       diff = after - before
835       assert(diff < 10000, "memory grew more than 10M: #{diff}")
836     end
837   end if RUBY_PLATFORM =~ /linux/ &&
838          File.readable?(LINUX_PROC_PID_STATUS) &&
839          !defined?(RUBY_ENGINE)