unicorn-maintained 6.2.0

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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +149 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +317 -0
  20. data/HACKING +112 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LATEST +1 -0
  24. data/LICENSE +67 -0
  25. data/Links +58 -0
  26. data/NEWS +1 -0
  27. data/PHILOSOPHY +139 -0
  28. data/README +156 -0
  29. data/Rakefile +16 -0
  30. data/SIGNALS +123 -0
  31. data/Sandbox +104 -0
  32. data/TODO +3 -0
  33. data/TUNING +119 -0
  34. data/archive/.gitignore +3 -0
  35. data/archive/slrnpull.conf +4 -0
  36. data/bin/unicorn +128 -0
  37. data/bin/unicorn_rails +209 -0
  38. data/examples/big_app_gc.rb +2 -0
  39. data/examples/echo.ru +26 -0
  40. data/examples/init.sh +102 -0
  41. data/examples/logger_mp_safe.rb +25 -0
  42. data/examples/logrotate.conf +44 -0
  43. data/examples/nginx.conf +156 -0
  44. data/examples/unicorn.conf.minimal.rb +13 -0
  45. data/examples/unicorn.conf.rb +110 -0
  46. data/examples/unicorn.socket +11 -0
  47. data/examples/unicorn@.service +40 -0
  48. data/ext/unicorn_http/CFLAGS +13 -0
  49. data/ext/unicorn_http/c_util.h +116 -0
  50. data/ext/unicorn_http/common_field_optimization.h +128 -0
  51. data/ext/unicorn_http/epollexclusive.h +128 -0
  52. data/ext/unicorn_http/ext_help.h +38 -0
  53. data/ext/unicorn_http/extconf.rb +39 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +91 -0
  56. data/ext/unicorn_http/unicorn_http.c +4334 -0
  57. data/ext/unicorn_http/unicorn_http.rl +1040 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +748 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +201 -0
  65. data/lib/unicorn/http_response.rb +93 -0
  66. data/lib/unicorn/http_server.rb +859 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +81 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/select_waiter.rb +6 -0
  71. data/lib/unicorn/socket_helper.rb +185 -0
  72. data/lib/unicorn/stream_input.rb +151 -0
  73. data/lib/unicorn/tee_input.rb +131 -0
  74. data/lib/unicorn/tmpio.rb +33 -0
  75. data/lib/unicorn/util.rb +90 -0
  76. data/lib/unicorn/version.rb +1 -0
  77. data/lib/unicorn/worker.rb +165 -0
  78. data/lib/unicorn.rb +136 -0
  79. data/man/man1/unicorn.1 +222 -0
  80. data/man/man1/unicorn_rails.1 +207 -0
  81. data/setup.rb +1586 -0
  82. data/t/.gitignore +4 -0
  83. data/t/GNUmakefile +5 -0
  84. data/t/README +49 -0
  85. data/t/active-unix-socket.t +117 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/client_body_buffer_size.ru +14 -0
  89. data/t/client_body_buffer_size.t +80 -0
  90. data/t/detach.ru +11 -0
  91. data/t/env.ru +3 -0
  92. data/t/fails-rack-lint.ru +5 -0
  93. data/t/heartbeat-timeout.ru +12 -0
  94. data/t/heartbeat-timeout.t +62 -0
  95. data/t/integration.ru +115 -0
  96. data/t/integration.t +356 -0
  97. data/t/lib.perl +258 -0
  98. data/t/listener_names.ru +4 -0
  99. data/t/my-tap-lib.sh +201 -0
  100. data/t/oob_gc.ru +17 -0
  101. data/t/oob_gc_path.ru +17 -0
  102. data/t/pid.ru +3 -0
  103. data/t/preread_input.ru +22 -0
  104. data/t/reload-bad-config.t +54 -0
  105. data/t/reopen-logs.ru +13 -0
  106. data/t/reopen-logs.t +39 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0010-reap-logging.sh +55 -0
  110. data/t/t0012-reload-empty-config.sh +86 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0020-at_exit-handler.sh +49 -0
  117. data/t/t0021-process_detach.sh +29 -0
  118. data/t/t0022-listener_names-preload_app.sh +32 -0
  119. data/t/t0300-no-default-middleware.sh +20 -0
  120. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  121. data/t/t0301.ru +13 -0
  122. data/t/t9001-oob_gc.sh +47 -0
  123. data/t/t9002-oob_gc-path.sh +75 -0
  124. data/t/test-lib.sh +125 -0
  125. data/t/winch_ttin.t +67 -0
  126. data/t/working_directory.t +94 -0
  127. data/test/aggregate.rb +15 -0
  128. data/test/benchmark/README +60 -0
  129. data/test/benchmark/dd.ru +18 -0
  130. data/test/benchmark/ddstream.ru +50 -0
  131. data/test/benchmark/readinput.ru +40 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/benchmark/uconnect.perl +66 -0
  134. data/test/exec/README +5 -0
  135. data/test/exec/test_exec.rb +1029 -0
  136. data/test/test_helper.rb +306 -0
  137. data/test/unit/test_ccc.rb +91 -0
  138. data/test/unit/test_configurator.rb +175 -0
  139. data/test/unit/test_droplet.rb +28 -0
  140. data/test/unit/test_http_parser.rb +884 -0
  141. data/test/unit/test_http_parser_ng.rb +714 -0
  142. data/test/unit/test_request.rb +169 -0
  143. data/test/unit/test_server.rb +244 -0
  144. data/test/unit/test_signals.rb +188 -0
  145. data/test/unit/test_socket_helper.rb +159 -0
  146. data/test/unit/test_stream_input.rb +210 -0
  147. data/test/unit/test_tee_input.rb +303 -0
  148. data/test/unit/test_util.rb +131 -0
  149. data/test/unit/test_waiter.rb +34 -0
  150. data/unicorn.gemspec +48 -0
  151. metadata +275 -0
@@ -0,0 +1,884 @@
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 GPLv2+ (GPLv3+ preferred)
6
+ #
7
+ # Additional work donated by contributors. See git history
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_multiline_header_0d0a
234
+ parser = HttpParser.new
235
+ parser.buf << "GET / HTTP/1.0\r\n" \
236
+ "X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n"
237
+ req = parser.env
238
+ assert_equal req, parser.parse
239
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
240
+ end
241
+
242
+ def test_multiline_header_0a
243
+ parser = HttpParser.new
244
+ parser.buf << "GET / HTTP/1.0\n" \
245
+ "X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n"
246
+ req = parser.env
247
+ assert_equal req, parser.parse
248
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
249
+ end
250
+
251
+ def test_continuation_eats_leading_spaces
252
+ parser = HttpParser.new
253
+ header = "GET / HTTP/1.1\r\n" \
254
+ "X-ASDF: \r\n" \
255
+ "\t\r\n" \
256
+ " \r\n" \
257
+ " ASDF\r\n\r\n"
258
+ parser.buf << header
259
+ req = parser.env
260
+ assert_equal req, parser.parse
261
+ assert_equal '', parser.buf
262
+ assert_equal 'ASDF', req['HTTP_X_ASDF']
263
+ end
264
+
265
+ def test_continuation_eats_scattered_leading_spaces
266
+ parser = HttpParser.new
267
+ header = "GET / HTTP/1.1\r\n" \
268
+ "X-ASDF: hi\r\n" \
269
+ " y\r\n" \
270
+ "\t\r\n" \
271
+ " x\r\n" \
272
+ " ASDF\r\n\r\n"
273
+ req = parser.env
274
+ parser.buf << header
275
+ assert_equal req, parser.parse
276
+ assert_equal '', parser.buf
277
+ assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
278
+ end
279
+
280
+ def test_continuation_eats_trailing_spaces
281
+ parser = HttpParser.new
282
+ header = "GET / HTTP/1.1\r\n" \
283
+ "X-ASDF: \r\n" \
284
+ "\t\r\n" \
285
+ " b \r\n" \
286
+ " ASDF\r\n\r\n"
287
+ parser.buf << header
288
+ req = parser.env
289
+ assert_equal req, parser.parse
290
+ assert_equal '', parser.buf
291
+ assert_equal 'b ASDF', req['HTTP_X_ASDF']
292
+ end
293
+
294
+ def test_continuation_with_absolute_uri_and_ignored_host_header
295
+ parser = HttpParser.new
296
+ header = "GET http://example.com/ HTTP/1.1\r\n" \
297
+ "Host: \r\n" \
298
+ " YHBT.net\r\n" \
299
+ "\r\n"
300
+ parser.buf << header
301
+ req = parser.env
302
+ assert_equal req, parser.parse
303
+ assert_equal 'example.com', req['HTTP_HOST']
304
+ end
305
+
306
+ # this may seem to be testing more of an implementation detail, but
307
+ # it also helps ensure we're safe in the presence of multiple parsers
308
+ # in case we ever go multithreaded/evented...
309
+ def test_resumable_continuations
310
+ nr = 1000
311
+ header = "GET / HTTP/1.1\r\n" \
312
+ "X-ASDF: \r\n" \
313
+ " hello\r\n"
314
+ tmp = []
315
+ nr.times { |i|
316
+ parser = HttpParser.new
317
+ req = parser.env
318
+ parser.buf << "#{header} #{i}\r\n"
319
+ assert parser.parse.nil?
320
+ asdf = req['HTTP_X_ASDF']
321
+ assert_equal "hello #{i}", asdf
322
+ tmp << [ parser, asdf ]
323
+ }
324
+ tmp.each_with_index { |(parser, asdf), i|
325
+ parser.buf << " .\r\n\r\n"
326
+ assert parser.parse
327
+ assert_equal "hello #{i} .", asdf
328
+ }
329
+ end
330
+
331
+ def test_invalid_continuation
332
+ parser = HttpParser.new
333
+ header = "GET / HTTP/1.1\r\n" \
334
+ " y\r\n" \
335
+ "Host: hello\r\n" \
336
+ "\r\n"
337
+ parser.buf << header
338
+ assert_raises(HttpParserError) { parser.parse }
339
+ end
340
+
341
+ def test_parse_ie6_urls
342
+ %w(/some/random/path"
343
+ /some/random/path>
344
+ /some/random/path<
345
+ /we/love/you/ie6?q=<"">
346
+ /url?<="&>="
347
+ /mal"formed"?
348
+ ).each do |path|
349
+ parser = HttpParser.new
350
+ req = parser.env
351
+ sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
352
+ assert_equal req, parser.headers(req, sorta_safe)
353
+ assert_equal path, req['REQUEST_URI']
354
+ assert_equal '', sorta_safe
355
+ assert parser.keepalive?
356
+ end
357
+ end
358
+
359
+ def test_parse_error
360
+ parser = HttpParser.new
361
+ req = parser.env
362
+ bad_http = "GET / SsUTF/1.1"
363
+
364
+ assert_raises(HttpParserError) { parser.headers(req, bad_http) }
365
+
366
+ # make sure we can recover
367
+ parser.clear
368
+ req.clear
369
+ assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
370
+ assert ! parser.keepalive?
371
+ end
372
+
373
+ def test_piecemeal
374
+ parser = HttpParser.new
375
+ req = parser.env
376
+ http = "GET"
377
+ assert_nil parser.headers(req, http)
378
+ assert_nil parser.headers(req, http)
379
+ assert_nil parser.headers(req, http << " / HTTP/1.0")
380
+ assert_equal '/', req['REQUEST_PATH']
381
+ assert_equal '/', req['REQUEST_URI']
382
+ assert_equal 'GET', req['REQUEST_METHOD']
383
+ assert_nil parser.headers(req, http << "\r\n")
384
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
385
+ assert_nil parser.headers(req, http << "\r")
386
+ assert_equal req, parser.headers(req, http << "\n")
387
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
388
+ assert_nil req['FRAGMENT']
389
+ assert_equal '', req['QUERY_STRING']
390
+ assert_equal "", http
391
+ assert ! parser.keepalive?
392
+ end
393
+
394
+ # not common, but underscores do appear in practice
395
+ def test_absolute_uri_underscores
396
+ parser = HttpParser.new
397
+ req = parser.env
398
+ http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
399
+ parser.buf << http
400
+ assert_equal req, parser.parse
401
+ assert_equal 'http', req['rack.url_scheme']
402
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
403
+ assert_equal '/foo', req['REQUEST_PATH']
404
+ assert_equal 'q=bar', req['QUERY_STRING']
405
+
406
+ assert_equal 'under_score.example.com', req['HTTP_HOST']
407
+ assert_equal 'under_score.example.com', req['SERVER_NAME']
408
+ assert_equal '80', req['SERVER_PORT']
409
+ assert_equal "", parser.buf
410
+ assert ! parser.keepalive?
411
+ end
412
+
413
+ # some dumb clients add users because they're stupid
414
+ def test_absolute_uri_w_user
415
+ parser = HttpParser.new
416
+ req = parser.env
417
+ http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
418
+ parser.buf << http
419
+ assert_equal req, parser.parse
420
+ assert_equal 'http', req['rack.url_scheme']
421
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
422
+ assert_equal '/foo', req['REQUEST_PATH']
423
+ assert_equal 'q=bar', req['QUERY_STRING']
424
+
425
+ assert_equal 'example.com', req['HTTP_HOST']
426
+ assert_equal 'example.com', req['SERVER_NAME']
427
+ assert_equal '80', req['SERVER_PORT']
428
+ assert_equal "", parser.buf
429
+ assert ! parser.keepalive?
430
+ end
431
+
432
+ # since Mongrel supported anything URI.parse supported, we're stuck
433
+ # supporting everything URI.parse supports
434
+ def test_absolute_uri_uri_parse
435
+ "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
436
+ parser = HttpParser.new
437
+ req = parser.env
438
+ http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
439
+ assert_equal req, parser.headers(req, http)
440
+ assert_equal 'http', req['rack.url_scheme']
441
+ assert_equal '/', req['REQUEST_URI']
442
+ assert_equal '/', req['REQUEST_PATH']
443
+ assert_equal '', req['QUERY_STRING']
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 "", http
449
+ assert ! parser.keepalive?
450
+ end
451
+ end
452
+
453
+ def test_absolute_uri
454
+ parser = HttpParser.new
455
+ req = parser.env
456
+ parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
457
+ assert_equal req, parser.parse
458
+ assert_equal 'http', req['rack.url_scheme']
459
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
460
+ assert_equal '/foo', req['REQUEST_PATH']
461
+ assert_equal 'q=bar', req['QUERY_STRING']
462
+
463
+ assert_equal 'example.com', req['HTTP_HOST']
464
+ assert_equal 'example.com', req['SERVER_NAME']
465
+ assert_equal '80', req['SERVER_PORT']
466
+ assert_equal "", parser.buf
467
+ assert ! parser.keepalive?
468
+ end
469
+
470
+ # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
471
+ def test_absolute_uri_https
472
+ parser = HttpParser.new
473
+ req = parser.env
474
+ http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
475
+ "X-Forwarded-Proto: http\r\n\r\n"
476
+ parser.buf << http
477
+ assert_equal req, parser.parse
478
+ assert_equal 'https', req['rack.url_scheme']
479
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
480
+ assert_equal '/foo', req['REQUEST_PATH']
481
+ assert_equal 'q=bar', req['QUERY_STRING']
482
+
483
+ assert_equal 'example.com', req['HTTP_HOST']
484
+ assert_equal 'example.com', req['SERVER_NAME']
485
+ assert_equal '443', req['SERVER_PORT']
486
+ assert_equal "", parser.buf
487
+ assert parser.keepalive?
488
+ end
489
+
490
+ # Host: header should be ignored for absolute URIs
491
+ def test_absolute_uri_with_port
492
+ parser = HttpParser.new
493
+ req = parser.env
494
+ parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
495
+ "Host: bad.example.com\r\n\r\n"
496
+ assert_equal req, parser.parse
497
+ assert_equal 'http', req['rack.url_scheme']
498
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
499
+ assert_equal '/foo', req['REQUEST_PATH']
500
+ assert_equal 'q=bar', req['QUERY_STRING']
501
+
502
+ assert_equal 'example.com:8080', req['HTTP_HOST']
503
+ assert_equal 'example.com', req['SERVER_NAME']
504
+ assert_equal '8080', req['SERVER_PORT']
505
+ assert_equal "", parser.buf
506
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
507
+ end
508
+
509
+ def test_absolute_uri_with_empty_port
510
+ parser = HttpParser.new
511
+ req = parser.env
512
+ parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
513
+ "Host: bad.example.com\r\n\r\n"
514
+ assert_equal req, parser.parse
515
+ assert_equal 'https', req['rack.url_scheme']
516
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
517
+ assert_equal '/foo', req['REQUEST_PATH']
518
+ assert_equal 'q=bar', req['QUERY_STRING']
519
+
520
+ assert_equal 'example.com:', req['HTTP_HOST']
521
+ assert_equal 'example.com', req['SERVER_NAME']
522
+ assert_equal '443', req['SERVER_PORT']
523
+ assert_equal "", parser.buf
524
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
525
+ end
526
+
527
+ def test_absolute_ipv6_uri
528
+ parser = HttpParser.new
529
+ req = parser.env
530
+ url = "http://[::1]/foo?q=bar"
531
+ http = "GET #{url} HTTP/1.1\r\n" \
532
+ "Host: bad.example.com\r\n\r\n"
533
+ assert_equal req, parser.headers(req, http)
534
+ assert_equal 'http', req['rack.url_scheme']
535
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
536
+ assert_equal '/foo', req['REQUEST_PATH']
537
+ assert_equal 'q=bar', req['QUERY_STRING']
538
+
539
+ uri = URI.parse(url)
540
+ assert_equal "[::1]", uri.host,
541
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
542
+ assert_equal "[::1]", req['HTTP_HOST']
543
+ assert_equal "[::1]", req['SERVER_NAME']
544
+ assert_equal '80', req['SERVER_PORT']
545
+ assert_equal "", http
546
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
547
+ end
548
+
549
+ def test_absolute_ipv6_uri_alpha
550
+ parser = HttpParser.new
551
+ req = parser.env
552
+ url = "http://[::a]/"
553
+ http = "GET #{url} HTTP/1.1\r\n" \
554
+ "Host: bad.example.com\r\n\r\n"
555
+ assert_equal req, parser.headers(req, http)
556
+ assert_equal 'http', req['rack.url_scheme']
557
+
558
+ uri = URI.parse(url)
559
+ assert_equal "[::a]", uri.host,
560
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
561
+ assert_equal "[::a]", req['HTTP_HOST']
562
+ assert_equal "[::a]", req['SERVER_NAME']
563
+ assert_equal '80', req['SERVER_PORT']
564
+ end
565
+
566
+ def test_absolute_ipv6_uri_alpha_2
567
+ parser = HttpParser.new
568
+ req = parser.env
569
+ url = "http://[::B]/"
570
+ http = "GET #{url} HTTP/1.1\r\n" \
571
+ "Host: bad.example.com\r\n\r\n"
572
+ assert_equal req, parser.headers(req, http)
573
+ assert_equal 'http', req['rack.url_scheme']
574
+
575
+ uri = URI.parse(url)
576
+ assert_equal "[::B]", uri.host,
577
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
578
+ assert_equal "[::B]", req['HTTP_HOST']
579
+ assert_equal "[::B]", req['SERVER_NAME']
580
+ assert_equal '80', req['SERVER_PORT']
581
+ end
582
+
583
+ def test_absolute_ipv6_uri_with_empty_port
584
+ parser = HttpParser.new
585
+ req = parser.env
586
+ url = "https://[::1]:/foo?q=bar"
587
+ http = "GET #{url} HTTP/1.1\r\n" \
588
+ "Host: bad.example.com\r\n\r\n"
589
+ assert_equal req, parser.headers(req, http)
590
+ assert_equal 'https', req['rack.url_scheme']
591
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
592
+ assert_equal '/foo', req['REQUEST_PATH']
593
+ assert_equal 'q=bar', req['QUERY_STRING']
594
+
595
+ uri = URI.parse(url)
596
+ assert_equal "[::1]", uri.host,
597
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
598
+ assert_equal "[::1]:", req['HTTP_HOST']
599
+ assert_equal "[::1]", req['SERVER_NAME']
600
+ assert_equal '443', req['SERVER_PORT']
601
+ assert_equal "", http
602
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
603
+ end
604
+
605
+ def test_absolute_ipv6_uri_with_port
606
+ parser = HttpParser.new
607
+ req = parser.env
608
+ url = "https://[::1]:666/foo?q=bar"
609
+ http = "GET #{url} HTTP/1.1\r\n" \
610
+ "Host: bad.example.com\r\n\r\n"
611
+ assert_equal req, parser.headers(req, http)
612
+ assert_equal 'https', req['rack.url_scheme']
613
+ assert_equal '/foo?q=bar', req['REQUEST_URI']
614
+ assert_equal '/foo', req['REQUEST_PATH']
615
+ assert_equal 'q=bar', req['QUERY_STRING']
616
+
617
+ uri = URI.parse(url)
618
+ assert_equal "[::1]", uri.host,
619
+ "URI.parse changed upstream for #{url}? host=#{uri.host}"
620
+ assert_equal "[::1]:666", req['HTTP_HOST']
621
+ assert_equal "[::1]", req['SERVER_NAME']
622
+ assert_equal '666', req['SERVER_PORT']
623
+ assert_equal "", http
624
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
625
+ end
626
+
627
+ def test_ipv6_host_header
628
+ parser = HttpParser.new
629
+ req = parser.env
630
+ parser.buf << "GET / HTTP/1.1\r\n" \
631
+ "Host: [::1]\r\n\r\n"
632
+ assert_equal req, parser.parse
633
+ assert_equal "[::1]", req['HTTP_HOST']
634
+ assert_equal "[::1]", req['SERVER_NAME']
635
+ assert_equal '80', req['SERVER_PORT']
636
+ assert_equal "", parser.buf
637
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
638
+ end
639
+
640
+ def test_ipv6_host_header_with_port
641
+ parser = HttpParser.new
642
+ req = parser.env
643
+ parser.buf << "GET / HTTP/1.1\r\n" \
644
+ "Host: [::1]:666\r\n\r\n"
645
+ assert_equal req, parser.parse
646
+ assert_equal "[::1]", req['SERVER_NAME']
647
+ assert_equal '666', req['SERVER_PORT']
648
+ assert_equal "[::1]:666", req['HTTP_HOST']
649
+ assert_equal "", parser.buf
650
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
651
+ end
652
+
653
+ def test_ipv6_host_header_with_empty_port
654
+ parser = HttpParser.new
655
+ req = parser.env
656
+ parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n"
657
+ assert_equal req, parser.parse
658
+ assert_equal "[::1]", req['SERVER_NAME']
659
+ assert_equal '80', req['SERVER_PORT']
660
+ assert_equal "[::1]:", req['HTTP_HOST']
661
+ assert_equal "", parser.buf
662
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
663
+ end
664
+
665
+ # XXX Highly unlikely..., just make sure we don't segfault or assert on it
666
+ def test_broken_ipv6_host_header
667
+ parser = HttpParser.new
668
+ req = parser.env
669
+ parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n"
670
+ assert_equal req, parser.parse
671
+ assert_equal "[", req['SERVER_NAME']
672
+ assert_equal ':1:', req['SERVER_PORT']
673
+ assert_equal "[::1:", req['HTTP_HOST']
674
+ assert_equal "", parser.buf
675
+ end
676
+
677
+ def test_put_body_oneshot
678
+ parser = HttpParser.new
679
+ req = parser.env
680
+ parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
681
+ assert_equal req, parser.parse
682
+ assert_equal '/', req['REQUEST_PATH']
683
+ assert_equal '/', req['REQUEST_URI']
684
+ assert_equal 'PUT', req['REQUEST_METHOD']
685
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
686
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
687
+ assert_equal "abcde", parser.buf
688
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
689
+ end
690
+
691
+ def test_put_body_later
692
+ parser = HttpParser.new
693
+ req = parser.env
694
+ parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
695
+ assert_equal req, parser.parse
696
+ assert_equal '/l', req['REQUEST_PATH']
697
+ assert_equal '/l', req['REQUEST_URI']
698
+ assert_equal 'PUT', req['REQUEST_METHOD']
699
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
700
+ assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
701
+ assert_equal "", parser.buf
702
+ assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
703
+ end
704
+
705
+ def test_unknown_methods
706
+ %w(GETT HEADR XGET XHEAD).each { |m|
707
+ parser = HttpParser.new
708
+ req = parser.env
709
+ s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
710
+ ok = parser.headers(req, s)
711
+ assert ok
712
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
713
+ assert_equal 'posts-17408', req['FRAGMENT']
714
+ assert_equal 'page=1', req['QUERY_STRING']
715
+ assert_equal "", s
716
+ assert_equal m, req['REQUEST_METHOD']
717
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
718
+ }
719
+ end
720
+
721
+ def test_fragment_in_uri
722
+ parser = HttpParser.new
723
+ req = parser.env
724
+ get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
725
+ parser.buf << get
726
+ ok = parser.parse
727
+ assert ok
728
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
729
+ assert_equal 'posts-17408', req['FRAGMENT']
730
+ assert_equal 'page=1', req['QUERY_STRING']
731
+ assert_equal '', parser.buf
732
+ assert parser.keepalive?
733
+ end
734
+
735
+ # lame random garbage maker
736
+ def rand_data(min, max, readable=true)
737
+ count = min + ((rand(max)+1) *10).to_i
738
+ res = count.to_s + "/"
739
+
740
+ if readable
741
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
742
+ else
743
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
744
+ end
745
+
746
+ return res
747
+ end
748
+
749
+
750
+ def test_horrible_queries
751
+ parser = HttpParser.new
752
+
753
+ # then that large header names are caught
754
+ 10.times do |c|
755
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
756
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
757
+ parser.buf << get
758
+ parser.parse
759
+ parser.clear
760
+ end
761
+ end
762
+
763
+ # then that large mangled field values are caught
764
+ 10.times do |c|
765
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
766
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
767
+ parser.buf << get
768
+ parser.parse
769
+ parser.clear
770
+ end
771
+ end
772
+
773
+ # then large headers are rejected too
774
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
775
+ get << "X-Test: test\r\n" * (80 * 1024)
776
+ parser.buf << get
777
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
778
+ parser.parse
779
+ end
780
+ parser.clear
781
+
782
+ # finally just that random garbage gets blocked all the time
783
+ 10.times do |c|
784
+ get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
785
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
786
+ parser.buf << get
787
+ parser.parse
788
+ parser.clear
789
+ end
790
+ end
791
+
792
+ end
793
+
794
+ def test_leading_tab
795
+ parser = HttpParser.new
796
+ get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
797
+ assert parser.add_parse(get)
798
+ assert_equal 'example.com', parser.env['HTTP_HOST']
799
+ end
800
+
801
+ def test_trailing_whitespace
802
+ parser = HttpParser.new
803
+ get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
804
+ assert parser.add_parse(get)
805
+ assert_equal 'example.com', parser.env['HTTP_HOST']
806
+ end
807
+
808
+ def test_trailing_tab
809
+ parser = HttpParser.new
810
+ get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
811
+ assert parser.add_parse(get)
812
+ assert_equal 'example.com', parser.env['HTTP_HOST']
813
+ end
814
+
815
+ def test_trailing_multiple_linear_whitespace
816
+ parser = HttpParser.new
817
+ get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
818
+ assert parser.add_parse(get)
819
+ assert_equal 'example.com', parser.env['HTTP_HOST']
820
+ end
821
+
822
+ def test_embedded_linear_whitespace_ok
823
+ parser = HttpParser.new
824
+ get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
825
+ assert parser.add_parse(get)
826
+ assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
827
+ end
828
+
829
+ def test_null_byte_header
830
+ parser = HttpParser.new
831
+ get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
832
+ assert_raises(HttpParserError) { parser.add_parse(get) }
833
+ end
834
+
835
+ def test_null_byte_in_middle
836
+ parser = HttpParser.new
837
+ get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
838
+ assert_raises(HttpParserError) { parser.add_parse(get) }
839
+ end
840
+
841
+ def test_null_byte_at_end
842
+ parser = HttpParser.new
843
+ get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
844
+ assert_raises(HttpParserError) { parser.add_parse(get) }
845
+ end
846
+
847
+ def test_empty_header
848
+ parser = HttpParser.new
849
+ get = "GET / HTTP/1.1\r\nHost: \r\n\r\n"
850
+ assert parser.add_parse(get)
851
+ assert_equal '', parser.env['HTTP_HOST']
852
+ end
853
+
854
+ def test_memsize
855
+ require 'objspace'
856
+ if ObjectSpace.respond_to?(:memsize_of)
857
+ n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
858
+ assert_kind_of Integer, n
859
+ # need to update this when 128-bit machines come out
860
+ # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
861
+ # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
862
+ assert_operator n, :<=, 96
863
+ assert_operator n, :>, 0
864
+ end
865
+ rescue LoadError
866
+ # not all Ruby implementations have objspace
867
+ end
868
+
869
+ def test_dedupe
870
+ parser = HttpParser.new
871
+ # n.b. String#freeze optimization doesn't work under modern test-unit
872
+ exp = -'HTTP_HOST'
873
+ get = "GET / HTTP/1.1\r\nHost: example.com\r\nHavpbea-fhpxf: true\r\n\r\n"
874
+ assert parser.add_parse(get)
875
+ key = parser.env.keys.detect { |k| k == exp }
876
+ assert_same exp, key
877
+
878
+ if RUBY_VERSION.to_r >= 2.6 # 2.6.0-rc1+
879
+ exp = -'HTTP_HAVPBEA_FHPXF'
880
+ key = parser.env.keys.detect { |k| k == exp }
881
+ assert_same exp, key
882
+ end
883
+ end if RUBY_VERSION.to_r >= 2.5 && RUBY_ENGINE == 'ruby'
884
+ end