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'
14 class HttpParserTest < Test::Unit::TestCase
17 parser = HttpParser.new
20 http << "GET / HTTP/1.1\r\n\r\n"
21 assert_equal req, parser.parse
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?
37 assert_nil parser.parse
38 assert_equal "G", http
41 # try parsing again to ensure we were reset correctly
42 http << "ET /hello-world HTTP/1.1\r\n\r\n"
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']
53 assert parser.keepalive?
57 parser = HttpParser.new
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']
64 def test_connection_close_no_ka
65 parser = HttpParser.new
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?
73 def test_connection_keep_alive_ka
74 parser = HttpParser.new
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?
81 def test_connection_keep_alive_no_body
82 parser = HttpParser.new
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?
89 def test_connection_keep_alive_no_body_empty
90 parser = HttpParser.new
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?
99 def test_connection_keep_alive_ka_bad_version
100 parser = HttpParser.new
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?
107 def test_parse_server_host_default_port
108 parser = HttpParser.new
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?
118 def test_parse_server_host_alt_port
119 parser = HttpParser.new
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?
129 def test_parse_server_host_empty_port
130 parser = HttpParser.new
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?
140 def test_parse_server_host_xfp_https
141 parser = HttpParser.new
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?
152 def test_parse_xfp_https_chained
153 parser = HttpParser.new
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
163 def test_parse_xfp_https_chained_backwards
164 parser = HttpParser.new
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
174 def test_parse_xfp_gopher_is_ignored
175 parser = HttpParser.new
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
185 def test_parse_x_forwarded_ssl_on
186 parser = HttpParser.new
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
196 def test_parse_x_forwarded_ssl_off
197 parser = HttpParser.new
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
206 def test_parse_strange_headers
207 parser = HttpParser.new
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?
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"
223 parser.buf << nasty_pound_header.dup
225 assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
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']
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"
238 assert_equal req, parser.parse
239 assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
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"
247 assert_equal req, parser.parse
248 assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
251 def test_continuation_eats_leading_spaces
252 parser = HttpParser.new
253 header = "GET / HTTP/1.1\r\n" \
260 assert_equal req, parser.parse
261 assert_equal '', parser.buf
262 assert_equal 'ASDF', req['HTTP_X_ASDF']
265 def test_continuation_eats_scattered_leading_spaces
266 parser = HttpParser.new
267 header = "GET / HTTP/1.1\r\n" \
275 assert_equal req, parser.parse
276 assert_equal '', parser.buf
277 assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
280 def test_continuation_eats_trailing_spaces
281 parser = HttpParser.new
282 header = "GET / HTTP/1.1\r\n" \
289 assert_equal req, parser.parse
290 assert_equal '', parser.buf
291 assert_equal 'b ASDF', req['HTTP_X_ASDF']
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" \
302 assert_equal req, parser.parse
303 assert_equal 'example.com', req['HTTP_HOST']
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
311 header = "GET / HTTP/1.1\r\n" \
316 parser = HttpParser.new
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 ]
324 tmp.each_with_index { |(parser, asdf), i|
325 parser.buf << " .\r\n\r\n"
327 assert_equal "hello #{i} .", asdf
331 def test_invalid_continuation
332 parser = HttpParser.new
333 header = "GET / HTTP/1.1\r\n" \
338 assert_raises(HttpParserError) { parser.parse }
341 def test_parse_ie6_urls
342 %w(/some/random/path"
345 /we/love/you/ie6?q=<"">
349 parser = HttpParser.new
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?
360 parser = HttpParser.new
362 bad_http = "GET / SsUTF/1.1"
364 assert_raises(HttpParserError) { parser.headers(req, bad_http) }
366 # make sure we can recover
369 assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
370 assert ! parser.keepalive?
374 parser = HttpParser.new
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?
394 # not common, but underscores do appear in practice
395 def test_absolute_uri_underscores
396 parser = HttpParser.new
398 http = "GET http://under_score.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 '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?
413 # some dumb clients add users because they're stupid
414 def test_absolute_uri_w_user
415 parser = HttpParser.new
417 http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
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?
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
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?
453 def test_absolute_uri
454 parser = HttpParser.new
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?
470 # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
471 def test_absolute_uri_https
472 parser = HttpParser.new
474 http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
475 "X-Forwarded-Proto: http\r\n\r\n"
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?
490 # Host: header should be ignored for absolute URIs
491 def test_absolute_uri_with_port
492 parser = HttpParser.new
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
509 def test_absolute_uri_with_empty_port
510 parser = HttpParser.new
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
527 def test_absolute_ipv6_uri
528 parser = HttpParser.new
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']
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
549 def test_absolute_ipv6_uri_alpha
550 parser = HttpParser.new
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']
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']
566 def test_absolute_ipv6_uri_alpha_2
567 parser = HttpParser.new
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']
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']
583 def test_absolute_ipv6_uri_with_empty_port
584 parser = HttpParser.new
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']
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
605 def test_absolute_ipv6_uri_with_port
606 parser = HttpParser.new
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']
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
627 def test_ipv6_host_header
628 parser = HttpParser.new
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
640 def test_ipv6_host_header_with_port
641 parser = HttpParser.new
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
653 def test_ipv6_host_header_with_empty_port
654 parser = HttpParser.new
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
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
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
677 def test_put_body_oneshot
678 parser = HttpParser.new
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
691 def test_put_body_later
692 parser = HttpParser.new
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
705 def test_unknown_methods
706 %w(GETT HEADR XGET XHEAD).each { |m|
707 parser = HttpParser.new
709 s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
710 ok = parser.headers(req, s)
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']
716 assert_equal m, req['REQUEST_METHOD']
717 assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
721 def test_fragment_in_uri
722 parser = HttpParser.new
724 get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
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?
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 + "/"
741 res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
743 res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
750 def test_horrible_queries
751 parser = HttpParser.new
753 # then that large header names are caught
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
763 # then that large mangled field values are caught
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
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)
777 assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
782 # finally just that random garbage gets blocked all the time
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
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']
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']
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']
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']
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"]
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) }
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) }
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) }
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']
856 if ObjectSpace.respond_to?(:memsize_of)
857 n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
858 assert_kind_of Integer, n
859 # need to update this when 128-bit machines come out
860 # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
861 # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
862 assert_operator n, :<=, 96
863 assert_operator n, :>, 0
866 # not all Ruby implementations have objspace
870 parser = HttpParser.new
871 # n.b. String#freeze optimization doesn't work under modern test-unit
873 get = "GET / HTTP/1.1\r\nHost: example.com\r\nHavpbea-fhpxf: true\r\n\r\n"
874 assert parser.add_parse(get)
875 key = parser.env.keys.detect { |k| k == exp }
878 if RUBY_VERSION.to_r >= 2.6 # 2.6.0-rc1+
879 exp = -'HTTP_HAVPBEA_FHPXF'
880 key = parser.env.keys.detect { |k| k == exp }
883 end if RUBY_VERSION.to_r >= 2.5 && RUBY_ENGINE == 'ruby'