unicorn-fotopedia 0.99.1

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 (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -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 +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. metadata +283 -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,555 @@
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_tab_lws
55
+ parser = HttpParser.new
56
+ req = {}
57
+ tmp = "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n"
58
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
59
+ assert_equal "foo.bar", req['HTTP_HOST']
60
+ end
61
+
62
+ def test_connection_close_no_ka
63
+ parser = HttpParser.new
64
+ req = {}
65
+ tmp = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
66
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
67
+ assert_equal "GET", req['REQUEST_METHOD']
68
+ assert ! parser.keepalive?
69
+ end
70
+
71
+ def test_connection_keep_alive_ka
72
+ parser = HttpParser.new
73
+ req = {}
74
+ tmp = "HEAD / 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_method
80
+ parser = HttpParser.new
81
+ req = {}
82
+ tmp = "POST / HTTP/1.1\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_connection_keep_alive_ka_bad_version
88
+ parser = HttpParser.new
89
+ req = {}
90
+ tmp = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
91
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
92
+ assert parser.keepalive?
93
+ end
94
+
95
+ def test_parse_server_host_default_port
96
+ parser = HttpParser.new
97
+ req = {}
98
+ tmp = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
99
+ assert_equal req, parser.headers(req, tmp)
100
+ assert_equal 'foo', req['SERVER_NAME']
101
+ assert_equal '80', req['SERVER_PORT']
102
+ assert_equal '', tmp
103
+ assert parser.keepalive?
104
+ end
105
+
106
+ def test_parse_server_host_alt_port
107
+ parser = HttpParser.new
108
+ req = {}
109
+ tmp = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
110
+ assert_equal req, parser.headers(req, tmp)
111
+ assert_equal 'foo', req['SERVER_NAME']
112
+ assert_equal '999', req['SERVER_PORT']
113
+ assert_equal '', tmp
114
+ assert parser.keepalive?
115
+ end
116
+
117
+ def test_parse_server_host_empty_port
118
+ parser = HttpParser.new
119
+ req = {}
120
+ tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
121
+ assert_equal req, parser.headers(req, tmp)
122
+ assert_equal 'foo', req['SERVER_NAME']
123
+ assert_equal '80', req['SERVER_PORT']
124
+ assert_equal '', tmp
125
+ assert parser.keepalive?
126
+ end
127
+
128
+ def test_parse_server_host_xfp_https
129
+ parser = HttpParser.new
130
+ req = {}
131
+ tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n" \
132
+ "X-Forwarded-Proto: https\r\n\r\n"
133
+ assert_equal req, parser.headers(req, tmp)
134
+ assert_equal 'foo', req['SERVER_NAME']
135
+ assert_equal '443', req['SERVER_PORT']
136
+ assert_equal '', tmp
137
+ assert parser.keepalive?
138
+ end
139
+
140
+ def test_parse_strange_headers
141
+ parser = HttpParser.new
142
+ req = {}
143
+ should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
144
+ assert_equal req, parser.headers(req, should_be_good)
145
+ assert_equal '', should_be_good
146
+ assert parser.keepalive?
147
+ end
148
+
149
+ # legacy test case from Mongrel that we never supported before...
150
+ # I still consider Pound irrelevant, unfortunately stupid clients that
151
+ # send extremely big headers do exist and they've managed to find Unicorn...
152
+ def test_nasty_pound_header
153
+ parser = HttpParser.new
154
+ 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"
155
+ req = {}
156
+ buf = nasty_pound_header.dup
157
+
158
+ assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
159
+ expect = $1.dup
160
+ expect.gsub!(/\r\n\t/, ' ')
161
+ assert_equal req, parser.headers(req, buf)
162
+ assert_equal '', buf
163
+ assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
164
+ end
165
+
166
+ def test_continuation_eats_leading_spaces
167
+ parser = HttpParser.new
168
+ header = "GET / HTTP/1.1\r\n" \
169
+ "X-ASDF: \r\n" \
170
+ "\t\r\n" \
171
+ " \r\n" \
172
+ " ASDF\r\n\r\n"
173
+ req = {}
174
+ assert_equal req, parser.headers(req, header)
175
+ assert_equal '', header
176
+ assert_equal 'ASDF', req['HTTP_X_ASDF']
177
+ end
178
+
179
+ def test_continuation_eats_scattered_leading_spaces
180
+ parser = HttpParser.new
181
+ header = "GET / HTTP/1.1\r\n" \
182
+ "X-ASDF: hi\r\n" \
183
+ " y\r\n" \
184
+ "\t\r\n" \
185
+ " x\r\n" \
186
+ " ASDF\r\n\r\n"
187
+ req = {}
188
+ assert_equal req, parser.headers(req, header)
189
+ assert_equal '', header
190
+ assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
191
+ end
192
+
193
+ def test_continuation_with_absolute_uri_and_ignored_host_header
194
+ parser = HttpParser.new
195
+ header = "GET http://example.com/ HTTP/1.1\r\n" \
196
+ "Host: \r\n" \
197
+ " YHBT.net\r\n" \
198
+ "\r\n"
199
+ req = {}
200
+ assert_equal req, parser.headers(req, header)
201
+ assert_equal 'example.com', req['HTTP_HOST']
202
+ end
203
+
204
+ # this may seem to be testing more of an implementation detail, but
205
+ # it also helps ensure we're safe in the presence of multiple parsers
206
+ # in case we ever go multithreaded/evented...
207
+ def test_resumable_continuations
208
+ nr = 1000
209
+ req = {}
210
+ header = "GET / HTTP/1.1\r\n" \
211
+ "X-ASDF: \r\n" \
212
+ " hello\r\n"
213
+ tmp = []
214
+ nr.times { |i|
215
+ parser = HttpParser.new
216
+ assert parser.headers(req, "#{header} #{i}\r\n").nil?
217
+ asdf = req['HTTP_X_ASDF']
218
+ assert_equal "hello #{i}", asdf
219
+ tmp << [ parser, asdf ]
220
+ req.clear
221
+ }
222
+ tmp.each_with_index { |(parser, asdf), i|
223
+ assert_equal req, parser.headers(req, "#{header} #{i}\r\n .\r\n\r\n")
224
+ assert_equal "hello #{i} .", asdf
225
+ }
226
+ end
227
+
228
+ def test_invalid_continuation
229
+ parser = HttpParser.new
230
+ header = "GET / HTTP/1.1\r\n" \
231
+ " y\r\n" \
232
+ "Host: hello\r\n" \
233
+ "\r\n"
234
+ req = {}
235
+ assert_raises(HttpParserError) { parser.headers(req, header) }
236
+ end
237
+
238
+ def test_parse_ie6_urls
239
+ %w(/some/random/path"
240
+ /some/random/path>
241
+ /some/random/path<
242
+ /we/love/you/ie6?q=<"">
243
+ /url?<="&>="
244
+ /mal"formed"?
245
+ ).each do |path|
246
+ parser = HttpParser.new
247
+ req = {}
248
+ sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
249
+ assert_equal req, parser.headers(req, sorta_safe)
250
+ assert_equal path, req['REQUEST_URI']
251
+ assert_equal '', sorta_safe
252
+ assert parser.keepalive?
253
+ end
254
+ end
255
+
256
+ def test_parse_error
257
+ parser = HttpParser.new
258
+ req = {}
259
+ bad_http = "GET / SsUTF/1.1"
260
+
261
+ assert_raises(HttpParserError) { parser.headers(req, bad_http) }
262
+
263
+ # make sure we can recover
264
+ parser.reset
265
+ req.clear
266
+ assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
267
+ assert ! parser.keepalive?
268
+ end
269
+
270
+ def test_piecemeal
271
+ parser = HttpParser.new
272
+ req = {}
273
+ http = "GET"
274
+ assert_nil parser.headers(req, http)
275
+ assert_nil parser.headers(req, http)
276
+ assert_nil parser.headers(req, http << " / HTTP/1.0")
277
+ assert_equal '/', req['REQUEST_PATH']
278
+ assert_equal '/', req['REQUEST_URI']
279
+ assert_equal 'GET', req['REQUEST_METHOD']
280
+ assert_nil parser.headers(req, http << "\r\n")
281
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
282
+ assert_nil parser.headers(req, http << "\r")
283
+ assert_equal req, parser.headers(req, http << "\n")
284
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
285
+ assert_nil req['FRAGMENT']
286
+ assert_equal '', req['QUERY_STRING']
287
+ assert_equal "", http
288
+ assert ! parser.keepalive?
289
+ end
290
+
291
+ # not common, but underscores do appear in practice
292
+ def test_absolute_uri_underscores
293
+ parser = HttpParser.new
294
+ req = {}
295
+ http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
296
+ assert_equal req, parser.headers(req, http)
297
+ assert_equal 'http', req['rack.url_scheme']
298
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
299
+ assert_equal '/foo', req['REQUEST_PATH']
300
+ assert_equal 'q=bar', req['QUERY_STRING']
301
+
302
+ assert_equal 'under_score.example.com', req['HTTP_HOST']
303
+ assert_equal 'under_score.example.com', req['SERVER_NAME']
304
+ assert_equal '80', req['SERVER_PORT']
305
+ assert_equal "", http
306
+ assert ! parser.keepalive?
307
+ end
308
+
309
+ # some dumb clients add users because they're stupid
310
+ def test_absolute_uri_w_user
311
+ parser = HttpParser.new
312
+ req = {}
313
+ http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
314
+ assert_equal req, parser.headers(req, http)
315
+ assert_equal 'http', req['rack.url_scheme']
316
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
317
+ assert_equal '/foo', req['REQUEST_PATH']
318
+ assert_equal 'q=bar', req['QUERY_STRING']
319
+
320
+ assert_equal 'example.com', req['HTTP_HOST']
321
+ assert_equal 'example.com', req['SERVER_NAME']
322
+ assert_equal '80', req['SERVER_PORT']
323
+ assert_equal "", http
324
+ assert ! parser.keepalive?
325
+ end
326
+
327
+ # since Mongrel supported anything URI.parse supported, we're stuck
328
+ # supporting everything URI.parse supports
329
+ def test_absolute_uri_uri_parse
330
+ "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
331
+ parser = HttpParser.new
332
+ req = {}
333
+ http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
334
+ assert_equal req, parser.headers(req, http)
335
+ assert_equal 'http', req['rack.url_scheme']
336
+ assert_equal '/', req['REQUEST_URI']
337
+ assert_equal '/', req['REQUEST_PATH']
338
+ assert_equal '', req['QUERY_STRING']
339
+
340
+ assert_equal 'example.com', req['HTTP_HOST']
341
+ assert_equal 'example.com', req['SERVER_NAME']
342
+ assert_equal '80', req['SERVER_PORT']
343
+ assert_equal "", http
344
+ assert ! parser.keepalive?
345
+ end
346
+ end
347
+
348
+ def test_absolute_uri
349
+ parser = HttpParser.new
350
+ req = {}
351
+ http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
352
+ assert_equal req, parser.headers(req, http)
353
+ assert_equal 'http', req['rack.url_scheme']
354
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
355
+ assert_equal '/foo', req['REQUEST_PATH']
356
+ assert_equal 'q=bar', req['QUERY_STRING']
357
+
358
+ assert_equal 'example.com', req['HTTP_HOST']
359
+ assert_equal 'example.com', req['SERVER_NAME']
360
+ assert_equal '80', req['SERVER_PORT']
361
+ assert_equal "", http
362
+ assert ! parser.keepalive?
363
+ end
364
+
365
+ # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
366
+ def test_absolute_uri_https
367
+ parser = HttpParser.new
368
+ req = {}
369
+ http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
370
+ "X-Forwarded-Proto: http\r\n\r\n"
371
+ assert_equal req, parser.headers(req, http)
372
+ assert_equal 'https', req['rack.url_scheme']
373
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
374
+ assert_equal '/foo', req['REQUEST_PATH']
375
+ assert_equal 'q=bar', req['QUERY_STRING']
376
+
377
+ assert_equal 'example.com', req['HTTP_HOST']
378
+ assert_equal 'example.com', req['SERVER_NAME']
379
+ assert_equal '443', req['SERVER_PORT']
380
+ assert_equal "", http
381
+ assert parser.keepalive?
382
+ end
383
+
384
+ # Host: header should be ignored for absolute URIs
385
+ def test_absolute_uri_with_port
386
+ parser = HttpParser.new
387
+ req = {}
388
+ http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
389
+ "Host: bad.example.com\r\n\r\n"
390
+ assert_equal req, parser.headers(req, http)
391
+ assert_equal 'http', req['rack.url_scheme']
392
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
393
+ assert_equal '/foo', req['REQUEST_PATH']
394
+ assert_equal 'q=bar', req['QUERY_STRING']
395
+
396
+ assert_equal 'example.com:8080', req['HTTP_HOST']
397
+ assert_equal 'example.com', req['SERVER_NAME']
398
+ assert_equal '8080', req['SERVER_PORT']
399
+ assert_equal "", http
400
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
401
+ end
402
+
403
+ def test_absolute_uri_with_empty_port
404
+ parser = HttpParser.new
405
+ req = {}
406
+ http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
407
+ "Host: bad.example.com\r\n\r\n"
408
+ assert_equal req, parser.headers(req, http)
409
+ assert_equal 'https', req['rack.url_scheme']
410
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
411
+ assert_equal '/foo', req['REQUEST_PATH']
412
+ assert_equal 'q=bar', req['QUERY_STRING']
413
+
414
+ assert_equal 'example.com:', req['HTTP_HOST']
415
+ assert_equal 'example.com', req['SERVER_NAME']
416
+ assert_equal '443', req['SERVER_PORT']
417
+ assert_equal "", http
418
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
419
+ end
420
+
421
+ def test_put_body_oneshot
422
+ parser = HttpParser.new
423
+ req = {}
424
+ http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
425
+ assert_equal req, parser.headers(req, http)
426
+ assert_equal '/', req['REQUEST_PATH']
427
+ assert_equal '/', req['REQUEST_URI']
428
+ assert_equal 'PUT', req['REQUEST_METHOD']
429
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
430
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
431
+ assert_equal "abcde", http
432
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
433
+ end
434
+
435
+ def test_put_body_later
436
+ parser = HttpParser.new
437
+ req = {}
438
+ http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
439
+ assert_equal req, parser.headers(req, http)
440
+ assert_equal '/l', req['REQUEST_PATH']
441
+ assert_equal '/l', req['REQUEST_URI']
442
+ assert_equal 'PUT', req['REQUEST_METHOD']
443
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
444
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
445
+ assert_equal "", http
446
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
447
+ end
448
+
449
+ def test_unknown_methods
450
+ %w(GETT HEADR XGET XHEAD).each { |m|
451
+ parser = HttpParser.new
452
+ req = {}
453
+ s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
454
+ ok = false
455
+ assert_nothing_raised do
456
+ ok = parser.headers(req, s)
457
+ end
458
+ assert ok
459
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
460
+ assert_equal 'posts-17408', req['FRAGMENT']
461
+ assert_equal 'page=1', req['QUERY_STRING']
462
+ assert_equal "", s
463
+ assert_equal m, req['REQUEST_METHOD']
464
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
465
+ }
466
+ end
467
+
468
+ def test_fragment_in_uri
469
+ parser = HttpParser.new
470
+ req = {}
471
+ get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
472
+ ok = false
473
+ assert_nothing_raised do
474
+ ok = parser.headers(req, get)
475
+ end
476
+ assert ok
477
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
478
+ assert_equal 'posts-17408', req['FRAGMENT']
479
+ assert_equal 'page=1', req['QUERY_STRING']
480
+ assert_equal '', get
481
+ assert parser.keepalive?
482
+ end
483
+
484
+ # lame random garbage maker
485
+ def rand_data(min, max, readable=true)
486
+ count = min + ((rand(max)+1) *10).to_i
487
+ res = count.to_s + "/"
488
+
489
+ if readable
490
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
491
+ else
492
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
493
+ end
494
+
495
+ return res
496
+ end
497
+
498
+
499
+ def test_horrible_queries
500
+ parser = HttpParser.new
501
+
502
+ # then that large header names are caught
503
+ 10.times do |c|
504
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
505
+ assert_raises Unicorn::HttpParserError do
506
+ parser.headers({}, get)
507
+ parser.reset
508
+ end
509
+ end
510
+
511
+ # then that large mangled field values are caught
512
+ 10.times do |c|
513
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
514
+ assert_raises Unicorn::HttpParserError do
515
+ parser.headers({}, get)
516
+ parser.reset
517
+ end
518
+ end
519
+
520
+ # then large headers are rejected too
521
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
522
+ get << "X-Test: test\r\n" * (80 * 1024)
523
+ assert_raises Unicorn::HttpParserError do
524
+ parser.headers({}, get)
525
+ parser.reset
526
+ end
527
+
528
+ # finally just that random garbage gets blocked all the time
529
+ 10.times do |c|
530
+ get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
531
+ assert_raises Unicorn::HttpParserError do
532
+ parser.headers({}, get)
533
+ parser.reset
534
+ end
535
+ end
536
+
537
+ end
538
+
539
+ # so we don't care about the portability of this test
540
+ # if it doesn't leak on Linux, it won't leak anywhere else
541
+ # unless your C compiler or platform is otherwise broken
542
+ LINUX_PROC_PID_STATUS = "/proc/self/status"
543
+ def test_memory_leak
544
+ match_rss = /^VmRSS:\s+(\d+)/
545
+ if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
546
+ before = $1.to_i
547
+ 1000000.times { Unicorn::HttpParser.new }
548
+ File.read(LINUX_PROC_PID_STATUS) =~ match_rss
549
+ after = $1.to_i
550
+ diff = after - before
551
+ assert(diff < 10000, "memory grew more than 10M: #{diff}")
552
+ end
553
+ end if RUBY_PLATFORM =~ /linux/ && test(?r, LINUX_PROC_PID_STATUS)
554
+
555
+ end