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'
13 class HttpParserTest < Test::Unit::TestCase
16 parser = HttpParser.new
19 http << "GET / HTTP/1.1\r\n\r\n"
20 assert_equal req, parser.parse
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?
36 assert_nil parser.parse
37 assert_equal "G", http
40 # try parsing again to ensure we were reset correctly
41 http << "ET /hello-world HTTP/1.1\r\n\r\n"
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']
52 assert parser.keepalive?
56 parser = HttpParser.new
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']
63 def test_connection_close_no_ka
64 parser = HttpParser.new
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?
72 def test_connection_keep_alive_ka
73 parser = HttpParser.new
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?
80 def test_connection_keep_alive_no_body
81 parser = HttpParser.new
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?
88 def test_connection_keep_alive_no_body_empty
89 parser = HttpParser.new
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?
98 def test_connection_keep_alive_ka_bad_version
99 parser = HttpParser.new
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?
106 def test_parse_server_host_default_port
107 parser = HttpParser.new
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?
117 def test_parse_server_host_alt_port
118 parser = HttpParser.new
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?
128 def test_parse_server_host_empty_port
129 parser = HttpParser.new
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?
139 def test_parse_server_host_xfp_https
140 parser = HttpParser.new
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?
151 def test_parse_xfp_https_chained
152 parser = HttpParser.new
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
162 def test_parse_xfp_https_chained_backwards
163 parser = HttpParser.new
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
173 def test_parse_xfp_gopher_is_ignored
174 parser = HttpParser.new
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
184 def test_parse_x_forwarded_ssl_on
185 parser = HttpParser.new
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
195 def test_parse_x_forwarded_ssl_off
196 parser = HttpParser.new
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
205 def test_parse_strange_headers
206 parser = HttpParser.new
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?
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"
222 parser.buf << nasty_pound_header.dup
224 assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
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']
232 def test_continuation_eats_leading_spaces
233 parser = HttpParser.new
234 header = "GET / HTTP/1.1\r\n" \
241 assert_equal req, parser.parse
242 assert_equal '', parser.buf
243 assert_equal 'ASDF', req['HTTP_X_ASDF']
246 def test_continuation_eats_scattered_leading_spaces
247 parser = HttpParser.new
248 header = "GET / HTTP/1.1\r\n" \
256 assert_equal req, parser.parse
257 assert_equal '', parser.buf
258 assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
261 def test_continuation_eats_trailing_spaces
262 parser = HttpParser.new
263 header = "GET / HTTP/1.1\r\n" \
270 assert_equal req, parser.parse
271 assert_equal '', parser.buf
272 assert_equal 'b ASDF', req['HTTP_X_ASDF']
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" \
283 assert_equal req, parser.parse
284 assert_equal 'example.com', req['HTTP_HOST']
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
292 header = "GET / HTTP/1.1\r\n" \
297 parser = HttpParser.new
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 ]
305 tmp.each_with_index { |(parser, asdf), i|
306 parser.buf << " .\r\n\r\n"
308 assert_equal "hello #{i} .", asdf
312 def test_invalid_continuation
313 parser = HttpParser.new
314 header = "GET / HTTP/1.1\r\n" \
319 assert_raises(HttpParserError) { parser.parse }
322 def test_parse_ie6_urls
323 %w(/some/random/path"
326 /we/love/you/ie6?q=<"">
330 parser = HttpParser.new
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?
341 parser = HttpParser.new
343 bad_http = "GET / SsUTF/1.1"
345 assert_raises(HttpParserError) { parser.headers(req, bad_http) }
347 # make sure we can recover
350 assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
351 assert ! parser.keepalive?
355 parser = HttpParser.new
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?
375 # not common, but underscores do appear in practice
376 def test_absolute_uri_underscores
377 parser = HttpParser.new
379 http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
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?
394 # some dumb clients add users because they're stupid
395 def test_absolute_uri_w_user
396 parser = HttpParser.new
398 http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
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?
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
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?
434 def test_absolute_uri
435 parser = HttpParser.new
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?
451 # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
452 def test_absolute_uri_https
453 parser = HttpParser.new
455 http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
456 "X-Forwarded-Proto: http\r\n\r\n"
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?
471 # Host: header should be ignored for absolute URIs
472 def test_absolute_uri_with_port
473 parser = HttpParser.new
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
490 def test_absolute_uri_with_empty_port
491 parser = HttpParser.new
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
508 def test_absolute_ipv6_uri
509 parser = HttpParser.new
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']
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
530 def test_absolute_ipv6_uri_alpha
531 parser = HttpParser.new
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']
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']
547 def test_absolute_ipv6_uri_alpha_2
548 parser = HttpParser.new
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']
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']
564 def test_absolute_ipv6_uri_with_empty_port
565 parser = HttpParser.new
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']
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
586 def test_absolute_ipv6_uri_with_port
587 parser = HttpParser.new
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']
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
608 def test_ipv6_host_header
609 parser = HttpParser.new
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
621 def test_ipv6_host_header_with_port
622 parser = HttpParser.new
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
634 def test_ipv6_host_header_with_empty_port
635 parser = HttpParser.new
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
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
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
658 def test_put_body_oneshot
659 parser = HttpParser.new
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
672 def test_put_body_later
673 parser = HttpParser.new
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
686 def test_unknown_methods
687 %w(GETT HEADR XGET XHEAD).each { |m|
688 parser = HttpParser.new
690 s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
692 assert_nothing_raised do
693 ok = parser.headers(req, s)
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']
700 assert_equal m, req['REQUEST_METHOD']
701 assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
705 def test_fragment_in_uri
706 parser = HttpParser.new
708 get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
711 assert_nothing_raised do
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?
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 + "/"
728 res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
730 res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
737 def test_horrible_queries
738 parser = HttpParser.new
740 # then that large header names are caught
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
750 # then that large mangled field values are caught
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
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)
764 assert_raises Unicorn::HttpParserError do
769 # finally just that random garbage gets blocked all the time
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
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']
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']
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']
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']
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"]
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']
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"
828 match_rss = /^VmRSS:\s+(\d+)/
829 if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
831 1000000.times { Unicorn::HttpParser.new }
832 File.read(LINUX_PROC_PID_STATUS) =~ match_rss
834 diff = after - before
835 assert(diff < 10000, "memory grew more than 10M: #{diff}")
837 end if RUBY_PLATFORM =~ /linux/ &&
838 File.readable?(LINUX_PROC_PID_STATUS) &&
839 !defined?(RUBY_ENGINE)