unicorn-simon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) 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 +156 -0
  8. data/.olddoc.yml +18 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +35 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +95 -0
  13. data/Documentation/.gitignore +5 -0
  14. data/Documentation/GNUmakefile +30 -0
  15. data/Documentation/unicorn.1.txt +187 -0
  16. data/Documentation/unicorn_rails.1.txt +175 -0
  17. data/FAQ +70 -0
  18. data/GIT-VERSION-FILE +1 -0
  19. data/GIT-VERSION-GEN +39 -0
  20. data/GNUmakefile +253 -0
  21. data/HACKING +120 -0
  22. data/ISSUES +90 -0
  23. data/KNOWN_ISSUES +79 -0
  24. data/LATEST +30 -0
  25. data/LICENSE +67 -0
  26. data/Links +56 -0
  27. data/NEWS +2465 -0
  28. data/PHILOSOPHY +139 -0
  29. data/README +138 -0
  30. data/Rakefile +16 -0
  31. data/SIGNALS +123 -0
  32. data/Sandbox +104 -0
  33. data/TODO +3 -0
  34. data/TUNING +119 -0
  35. data/archive/.gitignore +3 -0
  36. data/archive/slrnpull.conf +4 -0
  37. data/bin/unicorn +126 -0
  38. data/bin/unicorn_rails +209 -0
  39. data/examples/big_app_gc.rb +2 -0
  40. data/examples/echo.ru +27 -0
  41. data/examples/init.sh +102 -0
  42. data/examples/logger_mp_safe.rb +25 -0
  43. data/examples/logrotate.conf +44 -0
  44. data/examples/nginx.conf +155 -0
  45. data/examples/unicorn.conf.minimal.rb +13 -0
  46. data/examples/unicorn.conf.rb +110 -0
  47. data/examples/unicorn.socket +11 -0
  48. data/examples/unicorn@.service +33 -0
  49. data/ext/unicorn_http/CFLAGS +13 -0
  50. data/ext/unicorn_http/c_util.h +124 -0
  51. data/ext/unicorn_http/common_field_optimization.h +111 -0
  52. data/ext/unicorn_http/ext_help.h +62 -0
  53. data/ext/unicorn_http/extconf.rb +11 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +78 -0
  56. data/ext/unicorn_http/unicorn_http.c +4274 -0
  57. data/ext/unicorn_http/unicorn_http.rl +980 -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 +664 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +122 -0
  65. data/lib/unicorn/http_response.rb +60 -0
  66. data/lib/unicorn/http_server.rb +824 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +82 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/socket_helper.rb +195 -0
  71. data/lib/unicorn/stream_input.rb +146 -0
  72. data/lib/unicorn/tee_input.rb +133 -0
  73. data/lib/unicorn/tmpio.rb +27 -0
  74. data/lib/unicorn/util.rb +90 -0
  75. data/lib/unicorn/version.rb +1 -0
  76. data/lib/unicorn/worker.rb +140 -0
  77. data/lib/unicorn.rb +123 -0
  78. data/man/man1/unicorn.1 +221 -0
  79. data/man/man1/unicorn_rails.1 +212 -0
  80. data/setup.rb +1586 -0
  81. data/t/.gitignore +4 -0
  82. data/t/GNUmakefile +74 -0
  83. data/t/README +42 -0
  84. data/t/bin/content-md5-put +36 -0
  85. data/t/bin/sha1sum.rb +17 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/detach.ru +11 -0
  89. data/t/env.ru +3 -0
  90. data/t/fails-rack-lint.ru +5 -0
  91. data/t/heartbeat-timeout.ru +12 -0
  92. data/t/hijack.ru +43 -0
  93. data/t/listener_names.ru +4 -0
  94. data/t/my-tap-lib.sh +201 -0
  95. data/t/oob_gc.ru +20 -0
  96. data/t/oob_gc_path.ru +20 -0
  97. data/t/pid.ru +3 -0
  98. data/t/preread_input.ru +17 -0
  99. data/t/rack-input-tests.ru +21 -0
  100. data/t/t0000-http-basic.sh +50 -0
  101. data/t/t0001-reload-bad-config.sh +53 -0
  102. data/t/t0002-config-conflict.sh +49 -0
  103. data/t/t0002-parser-error.sh +94 -0
  104. data/t/t0003-working_directory.sh +51 -0
  105. data/t/t0004-heartbeat-timeout.sh +69 -0
  106. data/t/t0004-working_directory_broken.sh +24 -0
  107. data/t/t0005-working_directory_app.rb.sh +40 -0
  108. data/t/t0006-reopen-logs.sh +83 -0
  109. data/t/t0006.ru +13 -0
  110. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  111. data/t/t0008-back_out_of_upgrade.sh +110 -0
  112. data/t/t0009-broken-app.sh +56 -0
  113. data/t/t0009-winch_ttin.sh +59 -0
  114. data/t/t0010-reap-logging.sh +55 -0
  115. data/t/t0011-active-unix-socket.sh +79 -0
  116. data/t/t0012-reload-empty-config.sh +85 -0
  117. data/t/t0013-rewindable-input-false.sh +24 -0
  118. data/t/t0013.ru +12 -0
  119. data/t/t0014-rewindable-input-true.sh +24 -0
  120. data/t/t0014.ru +12 -0
  121. data/t/t0015-configurator-internals.sh +25 -0
  122. data/t/t0018-write-on-close.sh +23 -0
  123. data/t/t0019-max_header_len.sh +49 -0
  124. data/t/t0020-at_exit-handler.sh +49 -0
  125. data/t/t0021-process_detach.sh +29 -0
  126. data/t/t0022-listener_names-preload_app.sh +32 -0
  127. data/t/t0100-rack-input-tests.sh +124 -0
  128. data/t/t0116-client_body_buffer_size.sh +80 -0
  129. data/t/t0116.ru +16 -0
  130. data/t/t0200-rack-hijack.sh +30 -0
  131. data/t/t0300-no-default-middleware.sh +20 -0
  132. data/t/t9000-preread-input.sh +48 -0
  133. data/t/t9001-oob_gc.sh +47 -0
  134. data/t/t9002-oob_gc-path.sh +75 -0
  135. data/t/test-lib.sh +128 -0
  136. data/t/write-on-close.ru +11 -0
  137. data/test/aggregate.rb +15 -0
  138. data/test/benchmark/README +50 -0
  139. data/test/benchmark/dd.ru +18 -0
  140. data/test/benchmark/stack.ru +8 -0
  141. data/test/exec/README +5 -0
  142. data/test/exec/test_exec.rb +1099 -0
  143. data/test/test_helper.rb +298 -0
  144. data/test/unit/test_configurator.rb +175 -0
  145. data/test/unit/test_droplet.rb +28 -0
  146. data/test/unit/test_http_parser.rb +886 -0
  147. data/test/unit/test_http_parser_ng.rb +633 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +111 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_socket_helper.rb +197 -0
  153. data/test/unit/test_stream_input.rb +203 -0
  154. data/test/unit/test_tee_input.rb +304 -0
  155. data/test/unit/test_upload.rb +306 -0
  156. data/test/unit/test_util.rb +105 -0
  157. data/unicorn.gemspec +50 -0
  158. metadata +310 -0
@@ -0,0 +1,886 @@
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
+ # so we don't care about the portability of this test
855
+ # if it doesn't leak on Linux, it won't leak anywhere else
856
+ # unless your C compiler or platform is otherwise broken
857
+ LINUX_PROC_PID_STATUS = "/proc/self/status"
858
+ def test_memory_leak
859
+ match_rss = /^VmRSS:\s+(\d+)/
860
+ if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
861
+ before = $1.to_i
862
+ 1000000.times { Unicorn::HttpParser.new }
863
+ File.read(LINUX_PROC_PID_STATUS) =~ match_rss
864
+ after = $1.to_i
865
+ diff = after - before
866
+ assert(diff < 10000, "memory grew more than 10M: #{diff}")
867
+ end
868
+ end if RUBY_PLATFORM =~ /linux/ &&
869
+ File.readable?(LINUX_PROC_PID_STATUS) &&
870
+ !defined?(RUBY_ENGINE)
871
+
872
+ def test_memsize
873
+ require 'objspace'
874
+ if ObjectSpace.respond_to?(:memsize_of)
875
+ n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
876
+ assert_kind_of Integer, n
877
+ # need to update this when 128-bit machines come out
878
+ # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
879
+ # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
880
+ assert_operator n, :<=, 96
881
+ assert_operator n, :>, 0
882
+ end
883
+ rescue LoadError
884
+ # not all Ruby implementations have objspace
885
+ end
886
+ end