giraffesoft-unicorn 0.93.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,150 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'test/unit'
4
+ require 'tempfile'
5
+ require 'unicorn'
6
+
7
+ TestStruct = Struct.new(
8
+ *(Unicorn::Configurator::DEFAULTS.keys + %w(listener_opts listeners)))
9
+ class TestConfigurator < Test::Unit::TestCase
10
+
11
+ def test_config_init
12
+ assert_nothing_raised { Unicorn::Configurator.new {} }
13
+ end
14
+
15
+ def test_expand_addr
16
+ meth = Unicorn::Configurator.new.method(:expand_addr)
17
+
18
+ assert_equal "/var/run/unicorn.sock", meth.call("/var/run/unicorn.sock")
19
+ assert_equal "#{Dir.pwd}/foo/bar.sock", meth.call("unix:foo/bar.sock")
20
+
21
+ path = meth.call("~/foo/bar.sock")
22
+ assert_equal "/", path[0..0]
23
+ assert_match %r{/foo/bar\.sock\z}, path
24
+
25
+ path = meth.call("~root/foo/bar.sock")
26
+ assert_equal "/", path[0..0]
27
+ assert_match %r{/foo/bar\.sock\z}, path
28
+
29
+ assert_equal "1.2.3.4:2007", meth.call('1.2.3.4:2007')
30
+ assert_equal "0.0.0.0:2007", meth.call('0.0.0.0:2007')
31
+ assert_equal "0.0.0.0:2007", meth.call(':2007')
32
+ assert_equal "0.0.0.0:2007", meth.call('*:2007')
33
+ assert_equal "0.0.0.0:2007", meth.call('2007')
34
+ assert_equal "0.0.0.0:2007", meth.call(2007)
35
+
36
+ # the next two aren't portable, consider them unsupported for now
37
+ # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('1:2007')
38
+ # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('2:2007')
39
+ end
40
+
41
+ def test_config_invalid
42
+ tmp = Tempfile.new('unicorn_config')
43
+ tmp.syswrite(%q(asdfasdf "hello-world"))
44
+ assert_raises(NoMethodError) do
45
+ Unicorn::Configurator.new(:config_file => tmp.path)
46
+ end
47
+ end
48
+
49
+ def test_config_non_existent
50
+ tmp = Tempfile.new('unicorn_config')
51
+ path = tmp.path
52
+ tmp.close!
53
+ assert_raises(Errno::ENOENT) do
54
+ Unicorn::Configurator.new(:config_file => path)
55
+ end
56
+ end
57
+
58
+ def test_config_defaults
59
+ cfg = Unicorn::Configurator.new(:use_defaults => true)
60
+ test_struct = TestStruct.new
61
+ assert_nothing_raised { cfg.commit!(test_struct) }
62
+ Unicorn::Configurator::DEFAULTS.each do |key,value|
63
+ assert_equal value, test_struct.__send__(key)
64
+ end
65
+ end
66
+
67
+ def test_config_defaults_skip
68
+ cfg = Unicorn::Configurator.new(:use_defaults => true)
69
+ skip = [ :logger ]
70
+ test_struct = TestStruct.new
71
+ assert_nothing_raised { cfg.commit!(test_struct, :skip => skip) }
72
+ Unicorn::Configurator::DEFAULTS.each do |key,value|
73
+ next if skip.include?(key)
74
+ assert_equal value, test_struct.__send__(key)
75
+ end
76
+ assert_nil test_struct.logger
77
+ end
78
+
79
+ def test_listen_options
80
+ tmp = Tempfile.new('unicorn_config')
81
+ expect = { :sndbuf => 1, :rcvbuf => 2, :backlog => 10 }.freeze
82
+ listener = "127.0.0.1:12345"
83
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
84
+ cfg = nil
85
+ assert_nothing_raised do
86
+ cfg = Unicorn::Configurator.new(:config_file => tmp.path)
87
+ end
88
+ test_struct = TestStruct.new
89
+ assert_nothing_raised { cfg.commit!(test_struct) }
90
+ assert(listener_opts = test_struct.listener_opts)
91
+ assert_equal expect, listener_opts[listener]
92
+ end
93
+
94
+ def test_listen_option_bad
95
+ tmp = Tempfile.new('unicorn_config')
96
+ expect = { :sndbuf => "five" }
97
+ listener = "127.0.0.1:12345"
98
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
99
+ assert_raises(ArgumentError) do
100
+ Unicorn::Configurator.new(:config_file => tmp.path)
101
+ end
102
+ end
103
+
104
+ def test_listen_option_bad_delay
105
+ tmp = Tempfile.new('unicorn_config')
106
+ expect = { :delay => "five" }
107
+ listener = "127.0.0.1:12345"
108
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
109
+ assert_raises(ArgumentError) do
110
+ Unicorn::Configurator.new(:config_file => tmp.path)
111
+ end
112
+ end
113
+
114
+ def test_listen_option_float_delay
115
+ tmp = Tempfile.new('unicorn_config')
116
+ expect = { :delay => 0.5 }
117
+ listener = "127.0.0.1:12345"
118
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
119
+ assert_nothing_raised do
120
+ Unicorn::Configurator.new(:config_file => tmp.path)
121
+ end
122
+ end
123
+
124
+ def test_listen_option_int_delay
125
+ tmp = Tempfile.new('unicorn_config')
126
+ expect = { :delay => 5 }
127
+ listener = "127.0.0.1:12345"
128
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
129
+ assert_nothing_raised do
130
+ Unicorn::Configurator.new(:config_file => tmp.path)
131
+ end
132
+ end
133
+
134
+ def test_after_fork_proc
135
+ test_struct = TestStruct.new
136
+ [ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
137
+ Unicorn::Configurator.new(:after_fork => my_proc).commit!(test_struct)
138
+ assert_equal my_proc, test_struct.after_fork
139
+ end
140
+ end
141
+
142
+ def test_after_fork_wrong_arity
143
+ [ proc { |a| }, Proc.new { }, lambda { |a,b,c| } ].each do |my_proc|
144
+ assert_raises(ArgumentError) do
145
+ Unicorn::Configurator.new(:after_fork => my_proc)
146
+ end
147
+ end
148
+ end
149
+
150
+ end
@@ -0,0 +1,492 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ require 'test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class HttpParserTest < Test::Unit::TestCase
14
+
15
+ def test_parse_simple
16
+ parser = HttpParser.new
17
+ req = {}
18
+ http = "GET / HTTP/1.1\r\n\r\n"
19
+ assert_equal req, parser.headers(req, http)
20
+ assert_equal '', http
21
+
22
+ assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
23
+ assert_equal '/', req['REQUEST_PATH']
24
+ assert_equal 'HTTP/1.1', req['HTTP_VERSION']
25
+ assert_equal '/', req['REQUEST_URI']
26
+ assert_equal 'GET', req['REQUEST_METHOD']
27
+ assert_nil req['FRAGMENT']
28
+ assert_equal '', req['QUERY_STRING']
29
+
30
+ assert parser.keepalive?
31
+ parser.reset
32
+ req.clear
33
+
34
+ http = "G"
35
+ assert_nil parser.headers(req, http)
36
+ assert_equal "G", http
37
+ assert req.empty?
38
+
39
+ # try parsing again to ensure we were reset correctly
40
+ http = "GET /hello-world HTTP/1.1\r\n\r\n"
41
+ assert parser.headers(req, http)
42
+
43
+ assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
44
+ assert_equal '/hello-world', req['REQUEST_PATH']
45
+ assert_equal 'HTTP/1.1', req['HTTP_VERSION']
46
+ assert_equal '/hello-world', req['REQUEST_URI']
47
+ assert_equal 'GET', req['REQUEST_METHOD']
48
+ assert_nil req['FRAGMENT']
49
+ assert_equal '', req['QUERY_STRING']
50
+ assert_equal '', http
51
+ assert parser.keepalive?
52
+ end
53
+
54
+ def test_connection_close_no_ka
55
+ parser = HttpParser.new
56
+ req = {}
57
+ tmp = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
58
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
59
+ assert_equal "GET", req['REQUEST_METHOD']
60
+ assert ! parser.keepalive?
61
+ end
62
+
63
+ def test_connection_keep_alive_ka
64
+ parser = HttpParser.new
65
+ req = {}
66
+ tmp = "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
67
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
68
+ assert parser.keepalive?
69
+ end
70
+
71
+ def test_connection_keep_alive_ka_bad_method
72
+ parser = HttpParser.new
73
+ req = {}
74
+ tmp = "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
75
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
76
+ assert ! parser.keepalive?
77
+ end
78
+
79
+ def test_connection_keep_alive_ka_bad_version
80
+ parser = HttpParser.new
81
+ req = {}
82
+ tmp = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
83
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
84
+ assert parser.keepalive?
85
+ end
86
+
87
+ def test_parse_server_host_default_port
88
+ parser = HttpParser.new
89
+ req = {}
90
+ tmp = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
91
+ assert_equal req, parser.headers(req, tmp)
92
+ assert_equal 'foo', req['SERVER_NAME']
93
+ assert_equal '80', req['SERVER_PORT']
94
+ assert_equal '', tmp
95
+ assert parser.keepalive?
96
+ end
97
+
98
+ def test_parse_server_host_alt_port
99
+ parser = HttpParser.new
100
+ req = {}
101
+ tmp = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
102
+ assert_equal req, parser.headers(req, tmp)
103
+ assert_equal 'foo', req['SERVER_NAME']
104
+ assert_equal '999', req['SERVER_PORT']
105
+ assert_equal '', tmp
106
+ assert parser.keepalive?
107
+ end
108
+
109
+ def test_parse_server_host_empty_port
110
+ parser = HttpParser.new
111
+ req = {}
112
+ tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
113
+ assert_equal req, parser.headers(req, tmp)
114
+ assert_equal 'foo', req['SERVER_NAME']
115
+ assert_equal '80', req['SERVER_PORT']
116
+ assert_equal '', tmp
117
+ assert parser.keepalive?
118
+ end
119
+
120
+ def test_parse_server_host_xfp_https
121
+ parser = HttpParser.new
122
+ req = {}
123
+ tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n" \
124
+ "X-Forwarded-Proto: https\r\n\r\n"
125
+ assert_equal req, parser.headers(req, tmp)
126
+ assert_equal 'foo', req['SERVER_NAME']
127
+ assert_equal '443', req['SERVER_PORT']
128
+ assert_equal '', tmp
129
+ assert parser.keepalive?
130
+ end
131
+
132
+ def test_parse_strange_headers
133
+ parser = HttpParser.new
134
+ req = {}
135
+ should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
136
+ assert_equal req, parser.headers(req, should_be_good)
137
+ assert_equal '', should_be_good
138
+ assert parser.keepalive?
139
+ end
140
+
141
+ # legacy test case from Mongrel that we never supported before...
142
+ # I still consider Pound irrelevant, unfortunately stupid clients that
143
+ # send extremely big headers do exist and they've managed to find Unicorn...
144
+ def test_nasty_pound_header
145
+ parser = HttpParser.new
146
+ 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"
147
+ req = {}
148
+ buf = nasty_pound_header.dup
149
+
150
+ assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
151
+ expect = $1.dup
152
+ expect.gsub!(/\r\n\t/, ' ')
153
+ assert_equal req, parser.headers(req, buf)
154
+ assert_equal '', buf
155
+ assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
156
+ end
157
+
158
+ def test_continuation_eats_leading_spaces
159
+ parser = HttpParser.new
160
+ header = "GET / HTTP/1.1\r\n" \
161
+ "X-ASDF: \r\n" \
162
+ "\t\r\n" \
163
+ " \r\n" \
164
+ " ASDF\r\n\r\n"
165
+ req = {}
166
+ assert_equal req, parser.headers(req, header)
167
+ assert_equal '', header
168
+ assert_equal 'ASDF', req['HTTP_X_ASDF']
169
+ end
170
+
171
+ def test_continuation_eats_scattered_leading_spaces
172
+ parser = HttpParser.new
173
+ header = "GET / HTTP/1.1\r\n" \
174
+ "X-ASDF: hi\r\n" \
175
+ " y\r\n" \
176
+ "\t\r\n" \
177
+ " x\r\n" \
178
+ " ASDF\r\n\r\n"
179
+ req = {}
180
+ assert_equal req, parser.headers(req, header)
181
+ assert_equal '', header
182
+ assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
183
+ end
184
+
185
+ def test_continuation_with_absolute_uri_and_ignored_host_header
186
+ parser = HttpParser.new
187
+ header = "GET http://example.com/ HTTP/1.1\r\n" \
188
+ "Host: \r\n" \
189
+ " YHBT.net\r\n" \
190
+ "\r\n"
191
+ req = {}
192
+ assert_equal req, parser.headers(req, header)
193
+ assert_equal 'example.com', req['HTTP_HOST']
194
+ end
195
+
196
+ # this may seem to be testing more of an implementation detail, but
197
+ # it also helps ensure we're safe in the presence of multiple parsers
198
+ # in case we ever go multithreaded/evented...
199
+ def test_resumable_continuations
200
+ nr = 1000
201
+ req = {}
202
+ header = "GET / HTTP/1.1\r\n" \
203
+ "X-ASDF: \r\n" \
204
+ " hello\r\n"
205
+ tmp = []
206
+ nr.times { |i|
207
+ parser = HttpParser.new
208
+ assert parser.headers(req, "#{header} #{i}\r\n").nil?
209
+ asdf = req['HTTP_X_ASDF']
210
+ assert_equal "hello #{i}", asdf
211
+ tmp << [ parser, asdf ]
212
+ req.clear
213
+ }
214
+ tmp.each_with_index { |(parser, asdf), i|
215
+ assert_equal req, parser.headers(req, "#{header} #{i}\r\n .\r\n\r\n")
216
+ assert_equal "hello #{i} .", asdf
217
+ }
218
+ end
219
+
220
+ def test_invalid_continuation
221
+ parser = HttpParser.new
222
+ header = "GET / HTTP/1.1\r\n" \
223
+ " y\r\n" \
224
+ "Host: hello\r\n" \
225
+ "\r\n"
226
+ req = {}
227
+ assert_raises(HttpParserError) { parser.headers(req, header) }
228
+ end
229
+
230
+ def test_parse_ie6_urls
231
+ %w(/some/random/path"
232
+ /some/random/path>
233
+ /some/random/path<
234
+ /we/love/you/ie6?q=<"">
235
+ /url?<="&>="
236
+ /mal"formed"?
237
+ ).each do |path|
238
+ parser = HttpParser.new
239
+ req = {}
240
+ sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
241
+ assert_equal req, parser.headers(req, sorta_safe)
242
+ assert_equal path, req['REQUEST_URI']
243
+ assert_equal '', sorta_safe
244
+ assert parser.keepalive?
245
+ end
246
+ end
247
+
248
+ def test_parse_error
249
+ parser = HttpParser.new
250
+ req = {}
251
+ bad_http = "GET / SsUTF/1.1"
252
+
253
+ assert_raises(HttpParserError) { parser.headers(req, bad_http) }
254
+
255
+ # make sure we can recover
256
+ parser.reset
257
+ req.clear
258
+ assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
259
+ assert ! parser.keepalive?
260
+ end
261
+
262
+ def test_piecemeal
263
+ parser = HttpParser.new
264
+ req = {}
265
+ http = "GET"
266
+ assert_nil parser.headers(req, http)
267
+ assert_nil parser.headers(req, http)
268
+ assert_nil parser.headers(req, http << " / HTTP/1.0")
269
+ assert_equal '/', req['REQUEST_PATH']
270
+ assert_equal '/', req['REQUEST_URI']
271
+ assert_equal 'GET', req['REQUEST_METHOD']
272
+ assert_nil parser.headers(req, http << "\r\n")
273
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
274
+ assert_nil parser.headers(req, http << "\r")
275
+ assert_equal req, parser.headers(req, http << "\n")
276
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
277
+ assert_nil req['FRAGMENT']
278
+ assert_equal '', req['QUERY_STRING']
279
+ assert_equal "", http
280
+ assert ! parser.keepalive?
281
+ end
282
+
283
+ # not common, but underscores do appear in practice
284
+ def test_absolute_uri_underscores
285
+ parser = HttpParser.new
286
+ req = {}
287
+ http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
288
+ assert_equal req, parser.headers(req, http)
289
+ assert_equal 'http', req['rack.url_scheme']
290
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
291
+ assert_equal '/foo', req['REQUEST_PATH']
292
+ assert_equal 'q=bar', req['QUERY_STRING']
293
+
294
+ assert_equal 'under_score.example.com', req['HTTP_HOST']
295
+ assert_equal 'under_score.example.com', req['SERVER_NAME']
296
+ assert_equal '80', req['SERVER_PORT']
297
+ assert_equal "", http
298
+ assert ! parser.keepalive?
299
+ end
300
+
301
+ def test_absolute_uri
302
+ parser = HttpParser.new
303
+ req = {}
304
+ http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
305
+ assert_equal req, parser.headers(req, http)
306
+ assert_equal 'http', req['rack.url_scheme']
307
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
308
+ assert_equal '/foo', req['REQUEST_PATH']
309
+ assert_equal 'q=bar', req['QUERY_STRING']
310
+
311
+ assert_equal 'example.com', req['HTTP_HOST']
312
+ assert_equal 'example.com', req['SERVER_NAME']
313
+ assert_equal '80', req['SERVER_PORT']
314
+ assert_equal "", http
315
+ assert ! parser.keepalive?
316
+ end
317
+
318
+ # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
319
+ def test_absolute_uri_https
320
+ parser = HttpParser.new
321
+ req = {}
322
+ http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
323
+ "X-Forwarded-Proto: http\r\n\r\n"
324
+ assert_equal req, parser.headers(req, http)
325
+ assert_equal 'https', req['rack.url_scheme']
326
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
327
+ assert_equal '/foo', req['REQUEST_PATH']
328
+ assert_equal 'q=bar', req['QUERY_STRING']
329
+
330
+ assert_equal 'example.com', req['HTTP_HOST']
331
+ assert_equal 'example.com', req['SERVER_NAME']
332
+ assert_equal '443', req['SERVER_PORT']
333
+ assert_equal "", http
334
+ assert parser.keepalive?
335
+ end
336
+
337
+ # Host: header should be ignored for absolute URIs
338
+ def test_absolute_uri_with_port
339
+ parser = HttpParser.new
340
+ req = {}
341
+ http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
342
+ "Host: bad.example.com\r\n\r\n"
343
+ assert_equal req, parser.headers(req, http)
344
+ assert_equal 'http', req['rack.url_scheme']
345
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
346
+ assert_equal '/foo', req['REQUEST_PATH']
347
+ assert_equal 'q=bar', req['QUERY_STRING']
348
+
349
+ assert_equal 'example.com:8080', req['HTTP_HOST']
350
+ assert_equal 'example.com', req['SERVER_NAME']
351
+ assert_equal '8080', req['SERVER_PORT']
352
+ assert_equal "", http
353
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
354
+ end
355
+
356
+ def test_absolute_uri_with_empty_port
357
+ parser = HttpParser.new
358
+ req = {}
359
+ http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
360
+ "Host: bad.example.com\r\n\r\n"
361
+ assert_equal req, parser.headers(req, http)
362
+ assert_equal 'https', req['rack.url_scheme']
363
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
364
+ assert_equal '/foo', req['REQUEST_PATH']
365
+ assert_equal 'q=bar', req['QUERY_STRING']
366
+
367
+ assert_equal 'example.com:', req['HTTP_HOST']
368
+ assert_equal 'example.com', req['SERVER_NAME']
369
+ assert_equal '443', req['SERVER_PORT']
370
+ assert_equal "", http
371
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
372
+ end
373
+
374
+ def test_put_body_oneshot
375
+ parser = HttpParser.new
376
+ req = {}
377
+ http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
378
+ assert_equal req, parser.headers(req, http)
379
+ assert_equal '/', req['REQUEST_PATH']
380
+ assert_equal '/', req['REQUEST_URI']
381
+ assert_equal 'PUT', req['REQUEST_METHOD']
382
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
383
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
384
+ assert_equal "abcde", http
385
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
386
+ end
387
+
388
+ def test_put_body_later
389
+ parser = HttpParser.new
390
+ req = {}
391
+ http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
392
+ assert_equal req, parser.headers(req, http)
393
+ assert_equal '/l', req['REQUEST_PATH']
394
+ assert_equal '/l', req['REQUEST_URI']
395
+ assert_equal 'PUT', req['REQUEST_METHOD']
396
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
397
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
398
+ assert_equal "", http
399
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
400
+ end
401
+
402
+ def test_unknown_methods
403
+ %w(GETT HEADR XGET XHEAD).each { |m|
404
+ parser = HttpParser.new
405
+ req = {}
406
+ s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
407
+ ok = false
408
+ assert_nothing_raised do
409
+ ok = parser.headers(req, s)
410
+ end
411
+ assert ok
412
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
413
+ assert_equal 'posts-17408', req['FRAGMENT']
414
+ assert_equal 'page=1', req['QUERY_STRING']
415
+ assert_equal "", s
416
+ assert_equal m, req['REQUEST_METHOD']
417
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
418
+ }
419
+ end
420
+
421
+ def test_fragment_in_uri
422
+ parser = HttpParser.new
423
+ req = {}
424
+ get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
425
+ ok = false
426
+ assert_nothing_raised do
427
+ ok = parser.headers(req, get)
428
+ end
429
+ assert ok
430
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
431
+ assert_equal 'posts-17408', req['FRAGMENT']
432
+ assert_equal 'page=1', req['QUERY_STRING']
433
+ assert_equal '', get
434
+ assert parser.keepalive?
435
+ end
436
+
437
+ # lame random garbage maker
438
+ def rand_data(min, max, readable=true)
439
+ count = min + ((rand(max)+1) *10).to_i
440
+ res = count.to_s + "/"
441
+
442
+ if readable
443
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
444
+ else
445
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
446
+ end
447
+
448
+ return res
449
+ end
450
+
451
+
452
+ def test_horrible_queries
453
+ parser = HttpParser.new
454
+
455
+ # then that large header names are caught
456
+ 10.times do |c|
457
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
458
+ assert_raises Unicorn::HttpParserError do
459
+ parser.headers({}, get)
460
+ parser.reset
461
+ end
462
+ end
463
+
464
+ # then that large mangled field values are caught
465
+ 10.times do |c|
466
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
467
+ assert_raises Unicorn::HttpParserError do
468
+ parser.headers({}, get)
469
+ parser.reset
470
+ end
471
+ end
472
+
473
+ # then large headers are rejected too
474
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
475
+ get << "X-Test: test\r\n" * (80 * 1024)
476
+ assert_raises Unicorn::HttpParserError do
477
+ parser.headers({}, get)
478
+ parser.reset
479
+ end
480
+
481
+ # finally just that random garbage gets blocked all the time
482
+ 10.times do |c|
483
+ get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
484
+ assert_raises Unicorn::HttpParserError do
485
+ parser.headers({}, get)
486
+ parser.reset
487
+ end
488
+ end
489
+
490
+ end
491
+ end
492
+