unicorn-heroku 4.3.1.1.gc608.dirty

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +29 -0
  3. data/.gitignore +24 -0
  4. data/.mailmap +26 -0
  5. data/.wrongdoc.yml +10 -0
  6. data/Application_Timeouts +77 -0
  7. data/CONTRIBUTORS +35 -0
  8. data/COPYING +674 -0
  9. data/DESIGN +97 -0
  10. data/Documentation/.gitignore +5 -0
  11. data/Documentation/GNUmakefile +30 -0
  12. data/Documentation/unicorn.1.txt +174 -0
  13. data/Documentation/unicorn_rails.1.txt +175 -0
  14. data/FAQ +53 -0
  15. data/GIT-VERSION-GEN +40 -0
  16. data/GNUmakefile +294 -0
  17. data/HACKING +134 -0
  18. data/ISSUES +36 -0
  19. data/KNOWN_ISSUES +79 -0
  20. data/LICENSE +64 -0
  21. data/Links +56 -0
  22. data/PHILOSOPHY +145 -0
  23. data/README +154 -0
  24. data/Rakefile +97 -0
  25. data/SIGNALS +114 -0
  26. data/Sandbox +96 -0
  27. data/TODO +5 -0
  28. data/TUNING +98 -0
  29. data/bin/unicorn +121 -0
  30. data/bin/unicorn_rails +209 -0
  31. data/examples/big_app_gc.rb +2 -0
  32. data/examples/echo.ru +27 -0
  33. data/examples/git.ru +13 -0
  34. data/examples/init.sh +74 -0
  35. data/examples/logger_mp_safe.rb +25 -0
  36. data/examples/logrotate.conf +29 -0
  37. data/examples/nginx.conf +156 -0
  38. data/examples/unicorn.conf.minimal.rb +13 -0
  39. data/examples/unicorn.conf.rb +94 -0
  40. data/ext/unicorn_http/CFLAGS +13 -0
  41. data/ext/unicorn_http/c_util.h +124 -0
  42. data/ext/unicorn_http/common_field_optimization.h +111 -0
  43. data/ext/unicorn_http/ext_help.h +86 -0
  44. data/ext/unicorn_http/extconf.rb +10 -0
  45. data/ext/unicorn_http/global_variables.h +97 -0
  46. data/ext/unicorn_http/httpdate.c +82 -0
  47. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  48. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  49. data/lib/unicorn.rb +107 -0
  50. data/lib/unicorn/app/exec_cgi.rb +154 -0
  51. data/lib/unicorn/app/inetd.rb +109 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +630 -0
  56. data/lib/unicorn/const.rb +40 -0
  57. data/lib/unicorn/http_request.rb +77 -0
  58. data/lib/unicorn/http_response.rb +45 -0
  59. data/lib/unicorn/http_server.rb +744 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +71 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +208 -0
  64. data/lib/unicorn/ssl_client.rb +11 -0
  65. data/lib/unicorn/ssl_configurator.rb +104 -0
  66. data/lib/unicorn/ssl_server.rb +42 -0
  67. data/lib/unicorn/stream_input.rb +149 -0
  68. data/lib/unicorn/tee_input.rb +126 -0
  69. data/lib/unicorn/tmpio.rb +29 -0
  70. data/lib/unicorn/util.rb +68 -0
  71. data/lib/unicorn/worker.rb +88 -0
  72. data/local.mk.sample +59 -0
  73. data/script/isolate_for_tests +50 -0
  74. data/setup.rb +1586 -0
  75. data/t/.gitignore +5 -0
  76. data/t/GNUmakefile +82 -0
  77. data/t/README +42 -0
  78. data/t/bin/content-md5-put +36 -0
  79. data/t/bin/sha1sum.rb +17 -0
  80. data/t/bin/unused_listen +40 -0
  81. data/t/bin/utee +12 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/heartbeat-timeout.ru +12 -0
  86. data/t/my-tap-lib.sh +201 -0
  87. data/t/oob_gc.ru +21 -0
  88. data/t/oob_gc_path.ru +21 -0
  89. data/t/pid.ru +3 -0
  90. data/t/preread_input.ru +17 -0
  91. data/t/rack-input-tests.ru +21 -0
  92. data/t/rails3-app/.gitignore +4 -0
  93. data/t/rails3-app/Gemfile +26 -0
  94. data/t/rails3-app/Rakefile +10 -0
  95. data/t/rails3-app/app/controllers/application_controller.rb +4 -0
  96. data/t/rails3-app/app/helpers/application_helper.rb +2 -0
  97. data/t/rails3-app/app/views/layouts/application.html.erb +14 -0
  98. data/t/rails3-app/config.ru +4 -0
  99. data/t/rails3-app/config/application.rb +46 -0
  100. data/t/rails3-app/config/boot.rb +6 -0
  101. data/t/rails3-app/config/database.yml +22 -0
  102. data/t/rails3-app/config/environment.rb +5 -0
  103. data/t/rails3-app/config/environments/development.rb +19 -0
  104. data/t/rails3-app/config/environments/production.rb +42 -0
  105. data/t/rails3-app/config/environments/test.rb +32 -0
  106. data/t/rails3-app/config/initializers/backtrace_silencers.rb +7 -0
  107. data/t/rails3-app/config/initializers/inflections.rb +10 -0
  108. data/t/rails3-app/config/initializers/mime_types.rb +5 -0
  109. data/t/rails3-app/config/initializers/secret_token.rb +7 -0
  110. data/t/rails3-app/config/initializers/session_store.rb +8 -0
  111. data/t/rails3-app/config/locales/en.yml +5 -0
  112. data/t/rails3-app/config/routes.rb +58 -0
  113. data/t/rails3-app/db/seeds.rb +7 -0
  114. data/t/rails3-app/doc/README_FOR_APP +2 -0
  115. data/t/rails3-app/lib/tasks/.gitkeep +0 -0
  116. data/t/rails3-app/public/404.html +1 -0
  117. data/t/rails3-app/public/500.html +1 -0
  118. data/t/rails3-app/public/x.txt +1 -0
  119. data/t/rails3-app/script/rails +9 -0
  120. data/t/rails3-app/test/performance/browsing_test.rb +9 -0
  121. data/t/rails3-app/test/test_helper.rb +13 -0
  122. data/t/rails3-app/vendor/plugins/.gitkeep +0 -0
  123. data/t/sslgen.sh +71 -0
  124. data/t/t0000-http-basic.sh +50 -0
  125. data/t/t0001-reload-bad-config.sh +53 -0
  126. data/t/t0002-config-conflict.sh +49 -0
  127. data/t/t0002-parser-error.sh +94 -0
  128. data/t/t0003-working_directory.sh +51 -0
  129. data/t/t0004-heartbeat-timeout.sh +69 -0
  130. data/t/t0004-working_directory_broken.sh +24 -0
  131. data/t/t0005-working_directory_app.rb.sh +37 -0
  132. data/t/t0006-reopen-logs.sh +83 -0
  133. data/t/t0006.ru +13 -0
  134. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  135. data/t/t0008-back_out_of_upgrade.sh +110 -0
  136. data/t/t0009-broken-app.sh +56 -0
  137. data/t/t0009-winch_ttin.sh +59 -0
  138. data/t/t0010-reap-logging.sh +55 -0
  139. data/t/t0011-active-unix-socket.sh +79 -0
  140. data/t/t0012-reload-empty-config.sh +85 -0
  141. data/t/t0013-rewindable-input-false.sh +24 -0
  142. data/t/t0013.ru +12 -0
  143. data/t/t0014-rewindable-input-true.sh +24 -0
  144. data/t/t0014.ru +12 -0
  145. data/t/t0015-configurator-internals.sh +25 -0
  146. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  147. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  148. data/t/t0018-write-on-close.sh +23 -0
  149. data/t/t0019-max_header_len.sh +49 -0
  150. data/t/t0020-at_exit-handler.sh +49 -0
  151. data/t/t0021-process_detach.sh +29 -0
  152. data/t/t0100-rack-input-tests.sh +124 -0
  153. data/t/t0116-client_body_buffer_size.sh +80 -0
  154. data/t/t0116.ru +16 -0
  155. data/t/t0300-rails3-basic.sh +28 -0
  156. data/t/t0301-rails3-missing-config-ru.sh +33 -0
  157. data/t/t0302-rails3-alt-working_directory.sh +32 -0
  158. data/t/t0303-rails3-alt-working_directory_config.ru.sh +56 -0
  159. data/t/t0304-rails3-alt-working_directory_no_embed_cli.sh +52 -0
  160. data/t/t0600-https-server-basic.sh +48 -0
  161. data/t/t9000-preread-input.sh +48 -0
  162. data/t/t9001-oob_gc.sh +47 -0
  163. data/t/t9002-oob_gc-path.sh +75 -0
  164. data/t/test-lib.sh +113 -0
  165. data/t/test-rails3.sh +27 -0
  166. data/t/write-on-close.ru +11 -0
  167. data/test/aggregate.rb +15 -0
  168. data/test/benchmark/README +50 -0
  169. data/test/benchmark/dd.ru +18 -0
  170. data/test/benchmark/stack.ru +8 -0
  171. data/test/exec/README +5 -0
  172. data/test/exec/test_exec.rb +1055 -0
  173. data/test/rails/app-1.2.3/.gitignore +2 -0
  174. data/test/rails/app-1.2.3/Rakefile +7 -0
  175. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  176. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  177. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  178. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  179. data/test/rails/app-1.2.3/config/database.yml +12 -0
  180. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  181. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  182. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  183. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  184. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  185. data/test/rails/app-1.2.3/public/404.html +1 -0
  186. data/test/rails/app-1.2.3/public/500.html +1 -0
  187. data/test/rails/app-2.0.2/.gitignore +2 -0
  188. data/test/rails/app-2.0.2/Rakefile +7 -0
  189. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  190. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  191. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  192. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  193. data/test/rails/app-2.0.2/config/database.yml +12 -0
  194. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  195. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  196. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  197. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  198. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  199. data/test/rails/app-2.0.2/public/404.html +1 -0
  200. data/test/rails/app-2.0.2/public/500.html +1 -0
  201. data/test/rails/app-2.1.2/.gitignore +2 -0
  202. data/test/rails/app-2.1.2/Rakefile +7 -0
  203. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  204. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  205. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  206. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  207. data/test/rails/app-2.1.2/config/database.yml +12 -0
  208. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  209. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  210. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  211. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  212. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  213. data/test/rails/app-2.1.2/public/404.html +1 -0
  214. data/test/rails/app-2.1.2/public/500.html +1 -0
  215. data/test/rails/app-2.2.2/.gitignore +2 -0
  216. data/test/rails/app-2.2.2/Rakefile +7 -0
  217. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  218. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  219. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  220. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  221. data/test/rails/app-2.2.2/config/database.yml +12 -0
  222. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  223. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  224. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  225. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  226. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  227. data/test/rails/app-2.2.2/public/404.html +1 -0
  228. data/test/rails/app-2.2.2/public/500.html +1 -0
  229. data/test/rails/test_rails.rb +287 -0
  230. data/test/test_helper.rb +300 -0
  231. data/test/unit/test_configurator.rb +158 -0
  232. data/test/unit/test_droplet.rb +28 -0
  233. data/test/unit/test_http_parser.rb +860 -0
  234. data/test/unit/test_http_parser_ng.rb +716 -0
  235. data/test/unit/test_http_parser_xftrust.rb +38 -0
  236. data/test/unit/test_request.rb +197 -0
  237. data/test/unit/test_response.rb +99 -0
  238. data/test/unit/test_server.rb +289 -0
  239. data/test/unit/test_signals.rb +207 -0
  240. data/test/unit/test_sni_hostnames.rb +47 -0
  241. data/test/unit/test_socket_helper.rb +192 -0
  242. data/test/unit/test_stream_input.rb +204 -0
  243. data/test/unit/test_tee_input.rb +296 -0
  244. data/test/unit/test_upload.rb +306 -0
  245. data/test/unit/test_util.rb +100 -0
  246. data/unicorn-heroku.gemspec +44 -0
  247. metadata +426 -0
@@ -0,0 +1,28 @@
1
+ require 'test/unit'
2
+ require 'unicorn'
3
+
4
+ class TestDroplet < Test::Unit::TestCase
5
+ def test_create_many_droplets
6
+ now = Time.now.to_i
7
+ tmp = (0..1024).map do |i|
8
+ droplet = Unicorn::Worker.new(i)
9
+ assert droplet.respond_to?(:tick)
10
+ assert_equal 0, droplet.tick
11
+ assert_equal(now, droplet.tick = now)
12
+ assert_equal now, droplet.tick
13
+ assert_equal(0, droplet.tick = 0)
14
+ assert_equal 0, droplet.tick
15
+ end
16
+ end
17
+
18
+ def test_shared_process
19
+ droplet = Unicorn::Worker.new(0)
20
+ _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
21
+ assert status.success?, status.inspect
22
+ assert_equal 1, droplet.tick
23
+
24
+ _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
25
+ assert status.success?, status.inspect
26
+ assert_equal 2, droplet.tick
27
+ end
28
+ end
@@ -0,0 +1,860 @@
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 1.8 or
5
+ # the GPLv3
6
+ #
7
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
8
+ # for more information.
9
+
10
+ require 'test/test_helper'
11
+
12
+ include Unicorn
13
+
14
+ class HttpParserTest < Test::Unit::TestCase
15
+
16
+ def test_parse_simple
17
+ parser = HttpParser.new
18
+ req = parser.env
19
+ http = parser.buf
20
+ http << "GET / HTTP/1.1\r\n\r\n"
21
+ assert_equal req, parser.parse
22
+ assert_equal '', http
23
+
24
+ assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
25
+ assert_equal '/', req['REQUEST_PATH']
26
+ assert_equal 'HTTP/1.1', req['HTTP_VERSION']
27
+ assert_equal '/', req['REQUEST_URI']
28
+ assert_equal 'GET', req['REQUEST_METHOD']
29
+ assert_nil req['FRAGMENT']
30
+ assert_equal '', req['QUERY_STRING']
31
+
32
+ assert parser.keepalive?
33
+ parser.clear
34
+ req.clear
35
+
36
+ http << "G"
37
+ assert_nil parser.parse
38
+ assert_equal "G", http
39
+ assert req.empty?
40
+
41
+ # try parsing again to ensure we were reset correctly
42
+ http << "ET /hello-world HTTP/1.1\r\n\r\n"
43
+ assert parser.parse
44
+
45
+ assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
46
+ assert_equal '/hello-world', req['REQUEST_PATH']
47
+ assert_equal 'HTTP/1.1', req['HTTP_VERSION']
48
+ assert_equal '/hello-world', req['REQUEST_URI']
49
+ assert_equal 'GET', req['REQUEST_METHOD']
50
+ assert_nil req['FRAGMENT']
51
+ assert_equal '', req['QUERY_STRING']
52
+ assert_equal '', http
53
+ assert parser.keepalive?
54
+ end
55
+
56
+ def test_tab_lws
57
+ parser = HttpParser.new
58
+ req = parser.env
59
+ parser.buf << "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n"
60
+ assert_equal req.object_id, parser.parse.object_id
61
+ assert_equal "foo.bar", req['HTTP_HOST']
62
+ end
63
+
64
+ def test_connection_close_no_ka
65
+ parser = HttpParser.new
66
+ req = parser.env
67
+ parser.buf << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
68
+ assert_equal req.object_id, parser.parse.object_id
69
+ assert_equal "GET", req['REQUEST_METHOD']
70
+ assert ! parser.keepalive?
71
+ end
72
+
73
+ def test_connection_keep_alive_ka
74
+ parser = HttpParser.new
75
+ req = parser.env
76
+ parser.buf << "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
77
+ assert_equal req.object_id, parser.parse.object_id
78
+ assert parser.keepalive?
79
+ end
80
+
81
+ def test_connection_keep_alive_no_body
82
+ parser = HttpParser.new
83
+ req = parser.env
84
+ parser.buf << "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
85
+ assert_equal req.object_id, parser.parse.object_id
86
+ assert parser.keepalive?
87
+ end
88
+
89
+ def test_connection_keep_alive_no_body_empty
90
+ parser = HttpParser.new
91
+ req = parser.env
92
+ parser.buf << "POST / HTTP/1.1\r\n" \
93
+ "Content-Length: 0\r\n" \
94
+ "Connection: keep-alive\r\n\r\n"
95
+ assert_equal req.object_id, parser.parse.object_id
96
+ assert parser.keepalive?
97
+ end
98
+
99
+ def test_connection_keep_alive_ka_bad_version
100
+ parser = HttpParser.new
101
+ req = parser.env
102
+ parser.buf << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
103
+ assert_equal req.object_id, parser.parse.object_id
104
+ assert parser.keepalive?
105
+ end
106
+
107
+ def test_parse_server_host_default_port
108
+ parser = HttpParser.new
109
+ req = parser.env
110
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
111
+ assert_equal req, parser.parse
112
+ assert_equal 'foo', req['SERVER_NAME']
113
+ assert_equal '80', req['SERVER_PORT']
114
+ assert_equal '', parser.buf
115
+ assert parser.keepalive?
116
+ end
117
+
118
+ def test_parse_server_host_alt_port
119
+ parser = HttpParser.new
120
+ req = parser.env
121
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
122
+ assert_equal req, parser.parse
123
+ assert_equal 'foo', req['SERVER_NAME']
124
+ assert_equal '999', req['SERVER_PORT']
125
+ assert_equal '', parser.buf
126
+ assert parser.keepalive?
127
+ end
128
+
129
+ def test_parse_server_host_empty_port
130
+ parser = HttpParser.new
131
+ req = parser.env
132
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
133
+ assert_equal req, parser.parse
134
+ assert_equal 'foo', req['SERVER_NAME']
135
+ assert_equal '80', req['SERVER_PORT']
136
+ assert_equal '', parser.buf
137
+ assert parser.keepalive?
138
+ end
139
+
140
+ def test_parse_server_host_xfp_https
141
+ parser = HttpParser.new
142
+ req = parser.env
143
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \
144
+ "X-Forwarded-Proto: https\r\n\r\n"
145
+ assert_equal req, parser.parse
146
+ assert_equal 'foo', req['SERVER_NAME']
147
+ assert_equal '443', req['SERVER_PORT']
148
+ assert_equal '', parser.buf
149
+ assert parser.keepalive?
150
+ end
151
+
152
+ def test_parse_xfp_https_chained
153
+ parser = HttpParser.new
154
+ req = parser.env
155
+ parser.buf << "GET / HTTP/1.0\r\n" \
156
+ "X-Forwarded-Proto: https,http\r\n\r\n"
157
+ assert_equal req, parser.parse
158
+ assert_equal '443', req['SERVER_PORT'], req.inspect
159
+ assert_equal 'https', req['rack.url_scheme'], req.inspect
160
+ assert_equal '', parser.buf
161
+ end
162
+
163
+ def test_parse_xfp_https_chained_backwards
164
+ parser = HttpParser.new
165
+ req = parser.env
166
+ parser.buf << "GET / HTTP/1.0\r\n" \
167
+ "X-Forwarded-Proto: http,https\r\n\r\n"
168
+ assert_equal req, parser.parse
169
+ assert_equal '80', req['SERVER_PORT'], req.inspect
170
+ assert_equal 'http', req['rack.url_scheme'], req.inspect
171
+ assert_equal '', parser.buf
172
+ end
173
+
174
+ def test_parse_xfp_gopher_is_ignored
175
+ parser = HttpParser.new
176
+ req = parser.env
177
+ parser.buf << "GET / HTTP/1.0\r\n" \
178
+ "X-Forwarded-Proto: gopher\r\n\r\n"
179
+ assert_equal req, parser.parse
180
+ assert_equal '80', req['SERVER_PORT'], req.inspect
181
+ assert_equal 'http', req['rack.url_scheme'], req.inspect
182
+ assert_equal '', parser.buf
183
+ end
184
+
185
+ def test_parse_x_forwarded_ssl_on
186
+ parser = HttpParser.new
187
+ req = parser.env
188
+ parser.buf << "GET / HTTP/1.0\r\n" \
189
+ "X-Forwarded-Ssl: on\r\n\r\n"
190
+ assert_equal req, parser.parse
191
+ assert_equal '443', req['SERVER_PORT'], req.inspect
192
+ assert_equal 'https', req['rack.url_scheme'], req.inspect
193
+ assert_equal '', parser.buf
194
+ end
195
+
196
+ def test_parse_x_forwarded_ssl_off
197
+ parser = HttpParser.new
198
+ req = parser.env
199
+ parser.buf << "GET / HTTP/1.0\r\nX-Forwarded-Ssl: off\r\n\r\n"
200
+ assert_equal req, parser.parse
201
+ assert_equal '80', req['SERVER_PORT'], req.inspect
202
+ assert_equal 'http', req['rack.url_scheme'], req.inspect
203
+ assert_equal '', parser.buf
204
+ end
205
+
206
+ def test_parse_strange_headers
207
+ parser = HttpParser.new
208
+ req = parser.env
209
+ should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
210
+ parser.buf << should_be_good
211
+ assert_equal req, parser.parse
212
+ assert_equal '', parser.buf
213
+ assert parser.keepalive?
214
+ end
215
+
216
+ # legacy test case from Mongrel that we never supported before...
217
+ # I still consider Pound irrelevant, unfortunately stupid clients that
218
+ # send extremely big headers do exist and they've managed to find Unicorn...
219
+ def test_nasty_pound_header
220
+ parser = HttpParser.new
221
+ nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
222
+ req = parser.env
223
+ parser.buf << nasty_pound_header.dup
224
+
225
+ assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
226
+ expect = $1.dup
227
+ expect.gsub!(/\r\n\t/, ' ')
228
+ assert_equal req, parser.parse
229
+ assert_equal '', parser.buf
230
+ assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
231
+ end
232
+
233
+ def test_continuation_eats_leading_spaces
234
+ parser = HttpParser.new
235
+ header = "GET / HTTP/1.1\r\n" \
236
+ "X-ASDF: \r\n" \
237
+ "\t\r\n" \
238
+ " \r\n" \
239
+ " ASDF\r\n\r\n"
240
+ parser.buf << header
241
+ req = parser.env
242
+ assert_equal req, parser.parse
243
+ assert_equal '', parser.buf
244
+ assert_equal 'ASDF', req['HTTP_X_ASDF']
245
+ end
246
+
247
+ def test_continuation_eats_scattered_leading_spaces
248
+ parser = HttpParser.new
249
+ header = "GET / HTTP/1.1\r\n" \
250
+ "X-ASDF: hi\r\n" \
251
+ " y\r\n" \
252
+ "\t\r\n" \
253
+ " x\r\n" \
254
+ " ASDF\r\n\r\n"
255
+ req = parser.env
256
+ parser.buf << header
257
+ assert_equal req, parser.parse
258
+ assert_equal '', parser.buf
259
+ assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
260
+ end
261
+
262
+ def test_continuation_eats_trailing_spaces
263
+ parser = HttpParser.new
264
+ header = "GET / HTTP/1.1\r\n" \
265
+ "X-ASDF: \r\n" \
266
+ "\t\r\n" \
267
+ " b \r\n" \
268
+ " ASDF\r\n\r\n"
269
+ parser.buf << header
270
+ req = parser.env
271
+ assert_equal req, parser.parse
272
+ assert_equal '', parser.buf
273
+ assert_equal 'b ASDF', req['HTTP_X_ASDF']
274
+ end
275
+
276
+ def test_continuation_with_absolute_uri_and_ignored_host_header
277
+ parser = HttpParser.new
278
+ header = "GET http://example.com/ HTTP/1.1\r\n" \
279
+ "Host: \r\n" \
280
+ " YHBT.net\r\n" \
281
+ "\r\n"
282
+ parser.buf << header
283
+ req = parser.env
284
+ assert_equal req, parser.parse
285
+ assert_equal 'example.com', req['HTTP_HOST']
286
+ end
287
+
288
+ # this may seem to be testing more of an implementation detail, but
289
+ # it also helps ensure we're safe in the presence of multiple parsers
290
+ # in case we ever go multithreaded/evented...
291
+ def test_resumable_continuations
292
+ nr = 1000
293
+ header = "GET / HTTP/1.1\r\n" \
294
+ "X-ASDF: \r\n" \
295
+ " hello\r\n"
296
+ tmp = []
297
+ nr.times { |i|
298
+ parser = HttpParser.new
299
+ req = parser.env
300
+ parser.buf << "#{header} #{i}\r\n"
301
+ assert parser.parse.nil?
302
+ asdf = req['HTTP_X_ASDF']
303
+ assert_equal "hello #{i}", asdf
304
+ tmp << [ parser, asdf ]
305
+ }
306
+ tmp.each_with_index { |(parser, asdf), i|
307
+ parser.buf << " .\r\n\r\n"
308
+ assert parser.parse
309
+ assert_equal "hello #{i} .", asdf
310
+ }
311
+ end
312
+
313
+ def test_invalid_continuation
314
+ parser = HttpParser.new
315
+ header = "GET / HTTP/1.1\r\n" \
316
+ " y\r\n" \
317
+ "Host: hello\r\n" \
318
+ "\r\n"
319
+ parser.buf << header
320
+ assert_raises(HttpParserError) { parser.parse }
321
+ end
322
+
323
+ def test_parse_ie6_urls
324
+ %w(/some/random/path"
325
+ /some/random/path>
326
+ /some/random/path<
327
+ /we/love/you/ie6?q=<"">
328
+ /url?<="&>="
329
+ /mal"formed"?
330
+ ).each do |path|
331
+ parser = HttpParser.new
332
+ req = parser.env
333
+ sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
334
+ assert_equal req, parser.headers(req, sorta_safe)
335
+ assert_equal path, req['REQUEST_URI']
336
+ assert_equal '', sorta_safe
337
+ assert parser.keepalive?
338
+ end
339
+ end
340
+
341
+ def test_parse_error
342
+ parser = HttpParser.new
343
+ req = parser.env
344
+ bad_http = "GET / SsUTF/1.1"
345
+
346
+ assert_raises(HttpParserError) { parser.headers(req, bad_http) }
347
+
348
+ # make sure we can recover
349
+ parser.clear
350
+ req.clear
351
+ assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
352
+ assert ! parser.keepalive?
353
+ end
354
+
355
+ def test_piecemeal
356
+ parser = HttpParser.new
357
+ req = parser.env
358
+ http = "GET"
359
+ assert_nil parser.headers(req, http)
360
+ assert_nil parser.headers(req, http)
361
+ assert_nil parser.headers(req, http << " / HTTP/1.0")
362
+ assert_equal '/', req['REQUEST_PATH']
363
+ assert_equal '/', req['REQUEST_URI']
364
+ assert_equal 'GET', req['REQUEST_METHOD']
365
+ assert_nil parser.headers(req, http << "\r\n")
366
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
367
+ assert_nil parser.headers(req, http << "\r")
368
+ assert_equal req, parser.headers(req, http << "\n")
369
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
370
+ assert_nil req['FRAGMENT']
371
+ assert_equal '', req['QUERY_STRING']
372
+ assert_equal "", http
373
+ assert ! parser.keepalive?
374
+ end
375
+
376
+ # not common, but underscores do appear in practice
377
+ def test_absolute_uri_underscores
378
+ parser = HttpParser.new
379
+ req = parser.env
380
+ http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
381
+ parser.buf << http
382
+ assert_equal req, parser.parse
383
+ assert_equal 'http', req['rack.url_scheme']
384
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
385
+ assert_equal '/foo', req['REQUEST_PATH']
386
+ assert_equal 'q=bar', req['QUERY_STRING']
387
+
388
+ assert_equal 'under_score.example.com', req['HTTP_HOST']
389
+ assert_equal 'under_score.example.com', req['SERVER_NAME']
390
+ assert_equal '80', req['SERVER_PORT']
391
+ assert_equal "", parser.buf
392
+ assert ! parser.keepalive?
393
+ end
394
+
395
+ # some dumb clients add users because they're stupid
396
+ def test_absolute_uri_w_user
397
+ parser = HttpParser.new
398
+ req = parser.env
399
+ http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
400
+ parser.buf << http
401
+ assert_equal req, parser.parse
402
+ assert_equal 'http', req['rack.url_scheme']
403
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
404
+ assert_equal '/foo', req['REQUEST_PATH']
405
+ assert_equal 'q=bar', req['QUERY_STRING']
406
+
407
+ assert_equal 'example.com', req['HTTP_HOST']
408
+ assert_equal 'example.com', req['SERVER_NAME']
409
+ assert_equal '80', req['SERVER_PORT']
410
+ assert_equal "", parser.buf
411
+ assert ! parser.keepalive?
412
+ end
413
+
414
+ # since Mongrel supported anything URI.parse supported, we're stuck
415
+ # supporting everything URI.parse supports
416
+ def test_absolute_uri_uri_parse
417
+ "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
418
+ parser = HttpParser.new
419
+ req = parser.env
420
+ http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
421
+ assert_equal req, parser.headers(req, http)
422
+ assert_equal 'http', req['rack.url_scheme']
423
+ assert_equal '/', req['REQUEST_URI']
424
+ assert_equal '/', req['REQUEST_PATH']
425
+ assert_equal '', req['QUERY_STRING']
426
+
427
+ assert_equal 'example.com', req['HTTP_HOST']
428
+ assert_equal 'example.com', req['SERVER_NAME']
429
+ assert_equal '80', req['SERVER_PORT']
430
+ assert_equal "", http
431
+ assert ! parser.keepalive?
432
+ end
433
+ end
434
+
435
+ def test_absolute_uri
436
+ parser = HttpParser.new
437
+ req = parser.env
438
+ parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
439
+ assert_equal req, parser.parse
440
+ assert_equal 'http', req['rack.url_scheme']
441
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
442
+ assert_equal '/foo', req['REQUEST_PATH']
443
+ assert_equal 'q=bar', req['QUERY_STRING']
444
+
445
+ assert_equal 'example.com', req['HTTP_HOST']
446
+ assert_equal 'example.com', req['SERVER_NAME']
447
+ assert_equal '80', req['SERVER_PORT']
448
+ assert_equal "", parser.buf
449
+ assert ! parser.keepalive?
450
+ end
451
+
452
+ # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
453
+ def test_absolute_uri_https
454
+ parser = HttpParser.new
455
+ req = parser.env
456
+ http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
457
+ "X-Forwarded-Proto: http\r\n\r\n"
458
+ parser.buf << http
459
+ assert_equal req, parser.parse
460
+ assert_equal 'https', req['rack.url_scheme']
461
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
462
+ assert_equal '/foo', req['REQUEST_PATH']
463
+ assert_equal 'q=bar', req['QUERY_STRING']
464
+
465
+ assert_equal 'example.com', req['HTTP_HOST']
466
+ assert_equal 'example.com', req['SERVER_NAME']
467
+ assert_equal '443', req['SERVER_PORT']
468
+ assert_equal "", parser.buf
469
+ assert parser.keepalive?
470
+ end
471
+
472
+ # Host: header should be ignored for absolute URIs
473
+ def test_absolute_uri_with_port
474
+ parser = HttpParser.new
475
+ req = parser.env
476
+ parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
477
+ "Host: bad.example.com\r\n\r\n"
478
+ assert_equal req, parser.parse
479
+ assert_equal 'http', req['rack.url_scheme']
480
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
481
+ assert_equal '/foo', req['REQUEST_PATH']
482
+ assert_equal 'q=bar', req['QUERY_STRING']
483
+
484
+ assert_equal 'example.com:8080', req['HTTP_HOST']
485
+ assert_equal 'example.com', req['SERVER_NAME']
486
+ assert_equal '8080', req['SERVER_PORT']
487
+ assert_equal "", parser.buf
488
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
489
+ end
490
+
491
+ def test_absolute_uri_with_empty_port
492
+ parser = HttpParser.new
493
+ req = parser.env
494
+ parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
495
+ "Host: bad.example.com\r\n\r\n"
496
+ assert_equal req, parser.parse
497
+ assert_equal 'https', req['rack.url_scheme']
498
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
499
+ assert_equal '/foo', req['REQUEST_PATH']
500
+ assert_equal 'q=bar', req['QUERY_STRING']
501
+
502
+ assert_equal 'example.com:', req['HTTP_HOST']
503
+ assert_equal 'example.com', req['SERVER_NAME']
504
+ assert_equal '443', req['SERVER_PORT']
505
+ assert_equal "", parser.buf
506
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
507
+ end
508
+
509
+ def test_absolute_ipv6_uri
510
+ parser = HttpParser.new
511
+ req = parser.env
512
+ url = "http://[::1]/foo?q=bar"
513
+ http = "GET #{url} HTTP/1.1\r\n" \
514
+ "Host: bad.example.com\r\n\r\n"
515
+ assert_equal req, parser.headers(req, http)
516
+ assert_equal 'http', req['rack.url_scheme']
517
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
518
+ assert_equal '/foo', req['REQUEST_PATH']
519
+ assert_equal 'q=bar', req['QUERY_STRING']
520
+
521
+ uri = URI.parse(url)
522
+ assert_equal "[::1]", uri.host,
523
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
524
+ assert_equal "[::1]", req['HTTP_HOST']
525
+ assert_equal "[::1]", req['SERVER_NAME']
526
+ assert_equal '80', req['SERVER_PORT']
527
+ assert_equal "", http
528
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
529
+ end
530
+
531
+ def test_absolute_ipv6_uri_alpha
532
+ parser = HttpParser.new
533
+ req = parser.env
534
+ url = "http://[::a]/"
535
+ http = "GET #{url} HTTP/1.1\r\n" \
536
+ "Host: bad.example.com\r\n\r\n"
537
+ assert_equal req, parser.headers(req, http)
538
+ assert_equal 'http', req['rack.url_scheme']
539
+
540
+ uri = URI.parse(url)
541
+ assert_equal "[::a]", uri.host,
542
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
543
+ assert_equal "[::a]", req['HTTP_HOST']
544
+ assert_equal "[::a]", req['SERVER_NAME']
545
+ assert_equal '80', req['SERVER_PORT']
546
+ end
547
+
548
+ def test_absolute_ipv6_uri_alpha_2
549
+ parser = HttpParser.new
550
+ req = parser.env
551
+ url = "http://[::B]/"
552
+ http = "GET #{url} HTTP/1.1\r\n" \
553
+ "Host: bad.example.com\r\n\r\n"
554
+ assert_equal req, parser.headers(req, http)
555
+ assert_equal 'http', req['rack.url_scheme']
556
+
557
+ uri = URI.parse(url)
558
+ assert_equal "[::B]", uri.host,
559
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
560
+ assert_equal "[::B]", req['HTTP_HOST']
561
+ assert_equal "[::B]", req['SERVER_NAME']
562
+ assert_equal '80', req['SERVER_PORT']
563
+ end
564
+
565
+ def test_absolute_ipv6_uri_with_empty_port
566
+ parser = HttpParser.new
567
+ req = parser.env
568
+ url = "https://[::1]:/foo?q=bar"
569
+ http = "GET #{url} HTTP/1.1\r\n" \
570
+ "Host: bad.example.com\r\n\r\n"
571
+ assert_equal req, parser.headers(req, http)
572
+ assert_equal 'https', req['rack.url_scheme']
573
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
574
+ assert_equal '/foo', req['REQUEST_PATH']
575
+ assert_equal 'q=bar', req['QUERY_STRING']
576
+
577
+ uri = URI.parse(url)
578
+ assert_equal "[::1]", uri.host,
579
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
580
+ assert_equal "[::1]:", req['HTTP_HOST']
581
+ assert_equal "[::1]", req['SERVER_NAME']
582
+ assert_equal '443', req['SERVER_PORT']
583
+ assert_equal "", http
584
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
585
+ end
586
+
587
+ def test_absolute_ipv6_uri_with_port
588
+ parser = HttpParser.new
589
+ req = parser.env
590
+ url = "https://[::1]:666/foo?q=bar"
591
+ http = "GET #{url} HTTP/1.1\r\n" \
592
+ "Host: bad.example.com\r\n\r\n"
593
+ assert_equal req, parser.headers(req, http)
594
+ assert_equal 'https', req['rack.url_scheme']
595
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
596
+ assert_equal '/foo', req['REQUEST_PATH']
597
+ assert_equal 'q=bar', req['QUERY_STRING']
598
+
599
+ uri = URI.parse(url)
600
+ assert_equal "[::1]", uri.host,
601
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
602
+ assert_equal "[::1]:666", req['HTTP_HOST']
603
+ assert_equal "[::1]", req['SERVER_NAME']
604
+ assert_equal '666', req['SERVER_PORT']
605
+ assert_equal "", http
606
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
607
+ end
608
+
609
+ def test_ipv6_host_header
610
+ parser = HttpParser.new
611
+ req = parser.env
612
+ parser.buf << "GET / HTTP/1.1\r\n" \
613
+ "Host: [::1]\r\n\r\n"
614
+ assert_equal req, parser.parse
615
+ assert_equal "[::1]", req['HTTP_HOST']
616
+ assert_equal "[::1]", req['SERVER_NAME']
617
+ assert_equal '80', req['SERVER_PORT']
618
+ assert_equal "", parser.buf
619
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
620
+ end
621
+
622
+ def test_ipv6_host_header_with_port
623
+ parser = HttpParser.new
624
+ req = parser.env
625
+ parser.buf << "GET / HTTP/1.1\r\n" \
626
+ "Host: [::1]:666\r\n\r\n"
627
+ assert_equal req, parser.parse
628
+ assert_equal "[::1]", req['SERVER_NAME']
629
+ assert_equal '666', req['SERVER_PORT']
630
+ assert_equal "[::1]:666", req['HTTP_HOST']
631
+ assert_equal "", parser.buf
632
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
633
+ end
634
+
635
+ def test_ipv6_host_header_with_empty_port
636
+ parser = HttpParser.new
637
+ req = parser.env
638
+ parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n"
639
+ assert_equal req, parser.parse
640
+ assert_equal "[::1]", req['SERVER_NAME']
641
+ assert_equal '80', req['SERVER_PORT']
642
+ assert_equal "[::1]:", req['HTTP_HOST']
643
+ assert_equal "", parser.buf
644
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
645
+ end
646
+
647
+ # XXX Highly unlikely..., just make sure we don't segfault or assert on it
648
+ def test_broken_ipv6_host_header
649
+ parser = HttpParser.new
650
+ req = parser.env
651
+ parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n"
652
+ assert_equal req, parser.parse
653
+ assert_equal "[", req['SERVER_NAME']
654
+ assert_equal ':1:', req['SERVER_PORT']
655
+ assert_equal "[::1:", req['HTTP_HOST']
656
+ assert_equal "", parser.buf
657
+ end
658
+
659
+ def test_put_body_oneshot
660
+ parser = HttpParser.new
661
+ req = parser.env
662
+ parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
663
+ assert_equal req, parser.parse
664
+ assert_equal '/', req['REQUEST_PATH']
665
+ assert_equal '/', req['REQUEST_URI']
666
+ assert_equal 'PUT', req['REQUEST_METHOD']
667
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
668
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
669
+ assert_equal "abcde", parser.buf
670
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
671
+ end
672
+
673
+ def test_put_body_later
674
+ parser = HttpParser.new
675
+ req = parser.env
676
+ parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
677
+ assert_equal req, parser.parse
678
+ assert_equal '/l', req['REQUEST_PATH']
679
+ assert_equal '/l', req['REQUEST_URI']
680
+ assert_equal 'PUT', req['REQUEST_METHOD']
681
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
682
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
683
+ assert_equal "", parser.buf
684
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
685
+ end
686
+
687
+ def test_unknown_methods
688
+ %w(GETT HEADR XGET XHEAD).each { |m|
689
+ parser = HttpParser.new
690
+ req = parser.env
691
+ s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
692
+ ok = false
693
+ assert_nothing_raised do
694
+ ok = parser.headers(req, s)
695
+ end
696
+ assert ok
697
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
698
+ assert_equal 'posts-17408', req['FRAGMENT']
699
+ assert_equal 'page=1', req['QUERY_STRING']
700
+ assert_equal "", s
701
+ assert_equal m, req['REQUEST_METHOD']
702
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
703
+ }
704
+ end
705
+
706
+ def test_fragment_in_uri
707
+ parser = HttpParser.new
708
+ req = parser.env
709
+ get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
710
+ parser.buf << get
711
+ ok = false
712
+ assert_nothing_raised do
713
+ ok = parser.parse
714
+ end
715
+ assert ok
716
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
717
+ assert_equal 'posts-17408', req['FRAGMENT']
718
+ assert_equal 'page=1', req['QUERY_STRING']
719
+ assert_equal '', parser.buf
720
+ assert parser.keepalive?
721
+ end
722
+
723
+ # lame random garbage maker
724
+ def rand_data(min, max, readable=true)
725
+ count = min + ((rand(max)+1) *10).to_i
726
+ res = count.to_s + "/"
727
+
728
+ if readable
729
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
730
+ else
731
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
732
+ end
733
+
734
+ return res
735
+ end
736
+
737
+
738
+ def test_horrible_queries
739
+ parser = HttpParser.new
740
+
741
+ # then that large header names are caught
742
+ 10.times do |c|
743
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
744
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
745
+ parser.buf << get
746
+ parser.parse
747
+ parser.clear
748
+ end
749
+ end
750
+
751
+ # then that large mangled field values are caught
752
+ 10.times do |c|
753
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
754
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
755
+ parser.buf << get
756
+ parser.parse
757
+ parser.clear
758
+ end
759
+ end
760
+
761
+ # then large headers are rejected too
762
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
763
+ get << "X-Test: test\r\n" * (80 * 1024)
764
+ parser.buf << get
765
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
766
+ parser.parse
767
+ end
768
+ parser.clear
769
+
770
+ # finally just that random garbage gets blocked all the time
771
+ 10.times do |c|
772
+ get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
773
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
774
+ parser.buf << get
775
+ parser.parse
776
+ parser.clear
777
+ end
778
+ end
779
+
780
+ end
781
+
782
+ def test_leading_tab
783
+ parser = HttpParser.new
784
+ get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
785
+ assert parser.add_parse(get)
786
+ assert_equal 'example.com', parser.env['HTTP_HOST']
787
+ end
788
+
789
+ def test_trailing_whitespace
790
+ parser = HttpParser.new
791
+ get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
792
+ assert parser.add_parse(get)
793
+ assert_equal 'example.com', parser.env['HTTP_HOST']
794
+ end
795
+
796
+ def test_trailing_tab
797
+ parser = HttpParser.new
798
+ get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
799
+ assert parser.add_parse(get)
800
+ assert_equal 'example.com', parser.env['HTTP_HOST']
801
+ end
802
+
803
+ def test_trailing_multiple_linear_whitespace
804
+ parser = HttpParser.new
805
+ get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
806
+ assert parser.add_parse(get)
807
+ assert_equal 'example.com', parser.env['HTTP_HOST']
808
+ end
809
+
810
+ def test_embedded_linear_whitespace_ok
811
+ parser = HttpParser.new
812
+ get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
813
+ assert parser.add_parse(get)
814
+ assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
815
+ end
816
+
817
+ def test_null_byte_header
818
+ parser = HttpParser.new
819
+ get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
820
+ assert_raises(HttpParserError) { parser.add_parse(get) }
821
+ end
822
+
823
+ def test_null_byte_in_middle
824
+ parser = HttpParser.new
825
+ get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
826
+ assert_raises(HttpParserError) { parser.add_parse(get) }
827
+ end
828
+
829
+ def test_null_byte_at_end
830
+ parser = HttpParser.new
831
+ get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
832
+ assert_raises(HttpParserError) { parser.add_parse(get) }
833
+ end
834
+
835
+ def test_empty_header
836
+ parser = HttpParser.new
837
+ get = "GET / HTTP/1.1\r\nHost: \r\n\r\n"
838
+ assert parser.add_parse(get)
839
+ assert_equal '', parser.env['HTTP_HOST']
840
+ end
841
+
842
+ # so we don't care about the portability of this test
843
+ # if it doesn't leak on Linux, it won't leak anywhere else
844
+ # unless your C compiler or platform is otherwise broken
845
+ LINUX_PROC_PID_STATUS = "/proc/self/status"
846
+ def test_memory_leak
847
+ match_rss = /^VmRSS:\s+(\d+)/
848
+ if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
849
+ before = $1.to_i
850
+ 1000000.times { Unicorn::HttpParser.new }
851
+ File.read(LINUX_PROC_PID_STATUS) =~ match_rss
852
+ after = $1.to_i
853
+ diff = after - before
854
+ assert(diff < 10000, "memory grew more than 10M: #{diff}")
855
+ end
856
+ end if RUBY_PLATFORM =~ /linux/ &&
857
+ File.readable?(LINUX_PROC_PID_STATUS) &&
858
+ !defined?(RUBY_ENGINE)
859
+
860
+ end