giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
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
+