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,714 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require './test/test_helper'
4
+ require 'digest/md5'
5
+
6
+ include Unicorn
7
+
8
+ class HttpParserNgTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @parser = HttpParser.new
12
+ end
13
+
14
+ # RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
15
+ # but "chunked" must be last if used
16
+ def test_is_chunked
17
+ [ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
18
+ assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
19
+ end
20
+ [ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
21
+ assert HttpParser.is_chunked?(x)
22
+ end
23
+ [ 'gzip', 'xhunked', 'xchunked' ].each do |x|
24
+ assert !HttpParser.is_chunked?(x)
25
+ end
26
+ end
27
+
28
+ def test_parser_max_len
29
+ assert_raises(RangeError) do
30
+ HttpParser.max_header_len = 0xffffffff + 1
31
+ end
32
+ end
33
+
34
+ def test_next_clear
35
+ r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
36
+ @parser.buf << r
37
+ @parser.parse
38
+ @parser.response_start_sent = true
39
+ assert @parser.keepalive?
40
+ assert @parser.next?
41
+ assert @parser.response_start_sent
42
+
43
+ # persistent client makes another request:
44
+ @parser.buf << r
45
+ @parser.parse
46
+ assert @parser.keepalive?
47
+ assert @parser.next?
48
+ assert_equal false, @parser.response_start_sent
49
+ end
50
+
51
+ def test_response_start_sent
52
+ assert_equal false, @parser.response_start_sent, "default is false"
53
+ @parser.response_start_sent = true
54
+ assert_equal true, @parser.response_start_sent
55
+ @parser.response_start_sent = false
56
+ assert_equal false, @parser.response_start_sent
57
+ @parser.response_start_sent = true
58
+ @parser.clear
59
+ assert_equal false, @parser.response_start_sent
60
+ end
61
+
62
+ def test_connection_TE
63
+ @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
64
+ @parser.buf << "TE: trailers\r\n\r\n"
65
+ @parser.parse
66
+ assert @parser.keepalive?
67
+ assert @parser.next?
68
+ end
69
+
70
+ def test_keepalive_requests_with_next?
71
+ req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
72
+ expect = {
73
+ "SERVER_NAME" => "example.com",
74
+ "HTTP_HOST" => "example.com",
75
+ "rack.url_scheme" => "http",
76
+ "REQUEST_PATH" => "/",
77
+ "SERVER_PROTOCOL" => "HTTP/1.1",
78
+ "PATH_INFO" => "/",
79
+ "HTTP_VERSION" => "HTTP/1.1",
80
+ "REQUEST_URI" => "/",
81
+ "SERVER_PORT" => "80",
82
+ "REQUEST_METHOD" => "GET",
83
+ "QUERY_STRING" => ""
84
+ }.freeze
85
+ 100.times do |nr|
86
+ @parser.buf << req
87
+ assert_equal expect, @parser.parse
88
+ assert @parser.next?
89
+ end
90
+ end
91
+
92
+ def test_default_keepalive_is_off
93
+ assert ! @parser.keepalive?
94
+ assert ! @parser.next?
95
+ @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
96
+ @parser.parse
97
+ assert @parser.keepalive?
98
+ @parser.clear
99
+ assert ! @parser.keepalive?
100
+ assert ! @parser.next?
101
+ end
102
+
103
+ def test_identity_byte_headers
104
+ req = @parser.env
105
+ str = "PUT / HTTP/1.1\r\n"
106
+ str << "Content-Length: 123\r\n"
107
+ str << "\r"
108
+ hdr = @parser.buf
109
+ str.each_byte { |byte|
110
+ hdr << byte.chr
111
+ assert_nil @parser.parse
112
+ }
113
+ hdr << "\n"
114
+ assert_equal req.object_id, @parser.parse.object_id
115
+ assert_equal '123', req['CONTENT_LENGTH']
116
+ assert_equal 0, hdr.size
117
+ assert ! @parser.keepalive?
118
+ assert @parser.headers?
119
+ assert_equal 123, @parser.content_length
120
+ dst = ""
121
+ buf = '.' * 123
122
+ @parser.filter_body(dst, buf)
123
+ assert_equal '.' * 123, dst
124
+ assert_equal "", buf
125
+ assert @parser.keepalive?
126
+ end
127
+
128
+ def test_identity_step_headers
129
+ req = @parser.env
130
+ str = @parser.buf
131
+ str << "PUT / HTTP/1.1\r\n"
132
+ assert ! @parser.parse
133
+ str << "Content-Length: 123\r\n"
134
+ assert ! @parser.parse
135
+ str << "\r\n"
136
+ assert_equal req.object_id, @parser.parse.object_id
137
+ assert_equal '123', req['CONTENT_LENGTH']
138
+ assert_equal 0, str.size
139
+ assert ! @parser.keepalive?
140
+ assert @parser.headers?
141
+ dst = ""
142
+ buf = '.' * 123
143
+ @parser.filter_body(dst, buf)
144
+ assert_equal '.' * 123, dst
145
+ assert_equal "", buf
146
+ assert @parser.keepalive?
147
+ end
148
+
149
+ def test_identity_oneshot_header
150
+ req = @parser.env
151
+ str = @parser.buf
152
+ str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
153
+ assert_equal req.object_id, @parser.parse.object_id
154
+ assert_equal '123', req['CONTENT_LENGTH']
155
+ assert_equal 0, str.size
156
+ assert ! @parser.keepalive?
157
+ assert @parser.headers?
158
+ dst = ""
159
+ buf = '.' * 123
160
+ @parser.filter_body(dst, buf)
161
+ assert_equal '.' * 123, dst
162
+ assert_equal "", buf
163
+ end
164
+
165
+ def test_identity_oneshot_header_with_body
166
+ body = ('a' * 123).freeze
167
+ req = @parser.env
168
+ str = @parser.buf
169
+ str << "PUT / HTTP/1.1\r\n" \
170
+ "Content-Length: #{body.length}\r\n" \
171
+ "\r\n#{body}"
172
+ assert_equal req.object_id, @parser.parse.object_id
173
+ assert_equal '123', req['CONTENT_LENGTH']
174
+ assert_equal 123, str.size
175
+ assert_equal body, str
176
+ tmp = ''
177
+ assert_nil @parser.filter_body(tmp, str)
178
+ assert_equal 0, str.size
179
+ assert_equal tmp, body
180
+ assert_equal "", @parser.filter_body(tmp, str)
181
+ assert @parser.keepalive?
182
+ end
183
+
184
+ def test_identity_oneshot_header_with_body_partial
185
+ str = @parser.buf
186
+ str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
187
+ assert_equal Hash, @parser.parse.class
188
+ assert_equal 1, str.size
189
+ assert_equal 'a', str
190
+ tmp = ''
191
+ assert_nil @parser.filter_body(tmp, str)
192
+ assert_equal "", str
193
+ assert_equal "a", tmp
194
+ str << ' ' * 122
195
+ rv = @parser.filter_body(tmp, str)
196
+ assert_equal 122, tmp.size
197
+ assert_nil rv
198
+ assert_equal "", str
199
+ assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
200
+ assert @parser.keepalive?
201
+ end
202
+
203
+ def test_identity_oneshot_header_with_body_slop
204
+ str = @parser.buf
205
+ str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
206
+ assert_equal Hash, @parser.parse.class
207
+ assert_equal 2, str.size
208
+ assert_equal 'aG', str
209
+ tmp = ''
210
+ assert_nil @parser.filter_body(tmp, str)
211
+ assert_equal "G", str
212
+ assert_equal "G", @parser.filter_body(tmp, str)
213
+ assert_equal 1, tmp.size
214
+ assert_equal "a", tmp
215
+ assert @parser.keepalive?
216
+ end
217
+
218
+ def test_chunked
219
+ str = @parser.buf
220
+ req = @parser.env
221
+ str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
222
+ assert_equal req, @parser.parse, "msg=#{str}"
223
+ assert_equal 0, str.size
224
+ tmp = ""
225
+ assert_nil @parser.filter_body(tmp, str << "6")
226
+ assert_equal 0, tmp.size
227
+ assert_nil @parser.filter_body(tmp, str << "\r\n")
228
+ assert_equal 0, str.size
229
+ assert_equal 0, tmp.size
230
+ tmp = ""
231
+ assert_nil @parser.filter_body(tmp, str << "..")
232
+ assert_equal "..", tmp
233
+ assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
234
+ assert_equal "abcd", tmp
235
+ assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
236
+ assert_equal "PUT", str
237
+ assert ! @parser.keepalive?
238
+ str << "TY: FOO\r\n\r\n"
239
+ assert_equal req, @parser.parse
240
+ assert_equal "FOO", req["HTTP_PUTTY"]
241
+ assert @parser.keepalive?
242
+ end
243
+
244
+ def test_chunked_empty
245
+ str = @parser.buf
246
+ req = @parser.env
247
+ str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
248
+ assert_equal req, @parser.parse, "msg=#{str}"
249
+ assert_equal 0, str.size
250
+ tmp = ""
251
+ assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
252
+ assert_equal "", tmp
253
+ end
254
+
255
+ def test_two_chunks
256
+ str = @parser.buf
257
+ str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
258
+ req = @parser.env
259
+ assert_equal req, @parser.parse
260
+ assert_equal 0, str.size
261
+ tmp = ""
262
+ assert_nil @parser.filter_body(tmp, str << "6")
263
+ assert_equal 0, tmp.size
264
+ assert_nil @parser.filter_body(tmp, str << "\r\n")
265
+ assert_equal "", str
266
+ assert_equal 0, tmp.size
267
+ tmp = ""
268
+ assert_nil @parser.filter_body(tmp, str << "..")
269
+ assert_equal 2, tmp.size
270
+ assert_equal "..", tmp
271
+ assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
272
+ assert_equal "abcd", tmp
273
+ assert_nil @parser.filter_body(tmp, str << "\r")
274
+ assert_equal "", tmp
275
+ assert_nil @parser.filter_body(tmp, str << "\n")
276
+ assert_equal "", tmp
277
+ assert_nil @parser.filter_body(tmp, str << "z")
278
+ assert_equal "z", tmp
279
+ assert_nil @parser.filter_body(tmp, str << "\r\n")
280
+ assert_nil @parser.filter_body(tmp, str << "0")
281
+ assert_nil @parser.filter_body(tmp, str << "\r")
282
+ rv = @parser.filter_body(tmp, str << "\nGET")
283
+ assert_equal "GET", rv
284
+ assert_equal str.object_id, rv.object_id
285
+ assert ! @parser.keepalive?
286
+ end
287
+
288
+ def test_big_chunk
289
+ str = @parser.buf
290
+ str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
291
+ "4000\r\nabcd"
292
+ req = @parser.env
293
+ assert_equal req, @parser.parse
294
+ tmp = ''
295
+ assert_nil @parser.filter_body(tmp, str)
296
+ assert_equal '', str
297
+ str << ' ' * 16300
298
+ assert_nil @parser.filter_body(tmp, str)
299
+ assert_equal '', str
300
+ str << ' ' * 80
301
+ assert_nil @parser.filter_body(tmp, str)
302
+ assert_equal '', str
303
+ assert ! @parser.body_eof?
304
+ assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
305
+ assert_equal "", tmp
306
+ assert @parser.body_eof?
307
+ str << "\r\n"
308
+ assert_equal req, @parser.parse
309
+ assert_equal "", str
310
+ assert @parser.body_eof?
311
+ assert @parser.keepalive?
312
+ end
313
+
314
+ def test_two_chunks_oneshot
315
+ str = @parser.buf
316
+ req = @parser.env
317
+ str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
318
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
319
+ assert_equal req, @parser.parse
320
+ tmp = ''
321
+ assert_nil @parser.filter_body(tmp, str)
322
+ assert_equal 'a..', tmp
323
+ rv = @parser.filter_body(tmp, str)
324
+ assert_equal rv.object_id, str.object_id
325
+ assert ! @parser.keepalive?
326
+ end
327
+
328
+ def test_chunks_bytewise
329
+ chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
330
+ str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
331
+ buf = @parser.buf
332
+ buf << str
333
+ req = @parser.env
334
+ assert_equal req, @parser.parse
335
+ assert_equal "", buf
336
+ tmp = ''
337
+ body = ''
338
+ str = chunked[0..-2]
339
+ str.each_byte { |byte|
340
+ assert_nil @parser.filter_body(tmp, buf << byte.chr)
341
+ body << tmp
342
+ }
343
+ assert_equal 'abcdefghijklmnop0123456789abcdefg', body
344
+ rv = @parser.filter_body(tmp, buf<< "\n")
345
+ assert_equal rv.object_id, buf.object_id
346
+ assert ! @parser.keepalive?
347
+ end
348
+
349
+ def test_trailers
350
+ req = @parser.env
351
+ str = @parser.buf
352
+ str << "PUT / HTTP/1.1\r\n" \
353
+ "Trailer: Content-MD5\r\n" \
354
+ "transfer-Encoding: chunked\r\n\r\n" \
355
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
356
+ assert_equal req, @parser.parse
357
+ assert_equal 'Content-MD5', req['HTTP_TRAILER']
358
+ assert_nil req['HTTP_CONTENT_MD5']
359
+ tmp = ''
360
+ assert_nil @parser.filter_body(tmp, str)
361
+ assert_equal 'a..', tmp
362
+ md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
363
+ rv = @parser.filter_body(tmp, str)
364
+ assert_equal rv.object_id, str.object_id
365
+ assert_equal '', str
366
+ md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
367
+ str << md5_hdr
368
+ assert_nil @parser.trailers(req, str)
369
+ assert_equal md5_b64, req['HTTP_CONTENT_MD5']
370
+ assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
371
+ str << "\r"
372
+ assert_nil @parser.parse
373
+ str << "\nGET / "
374
+ assert_equal req, @parser.parse
375
+ assert_equal "GET / ", str
376
+ assert @parser.keepalive?
377
+ end
378
+
379
+ def test_trailers_slowly
380
+ str = @parser.buf
381
+ str << "PUT / HTTP/1.1\r\n" \
382
+ "Trailer: Content-MD5\r\n" \
383
+ "transfer-Encoding: chunked\r\n\r\n" \
384
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
385
+ req = @parser.env
386
+ assert_equal req, @parser.parse
387
+ assert_equal 'Content-MD5', req['HTTP_TRAILER']
388
+ assert_nil req['HTTP_CONTENT_MD5']
389
+ tmp = ''
390
+ assert_nil @parser.filter_body(tmp, str)
391
+ assert_equal 'a..', tmp
392
+ md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
393
+ rv = @parser.filter_body(tmp, str)
394
+ assert_equal rv.object_id, str.object_id
395
+ assert_equal '', str
396
+ assert_nil @parser.trailers(req, str)
397
+ md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
398
+ md5_hdr.each_byte { |byte|
399
+ str << byte.chr
400
+ assert_nil @parser.trailers(req, str)
401
+ }
402
+ assert_equal md5_b64, req['HTTP_CONTENT_MD5']
403
+ assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
404
+ str << "\r"
405
+ assert_nil @parser.parse
406
+ str << "\n"
407
+ assert_equal req, @parser.parse
408
+ end
409
+
410
+ def test_max_chunk
411
+ str = @parser.buf
412
+ str << "PUT / HTTP/1.1\r\n" \
413
+ "transfer-Encoding: chunked\r\n\r\n" \
414
+ "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
415
+ req = @parser.env
416
+ assert_equal req, @parser.parse
417
+ assert_nil @parser.content_length
418
+ @parser.filter_body('', str)
419
+ assert ! @parser.keepalive?
420
+ end
421
+
422
+ def test_max_body
423
+ n = HttpParser::LENGTH_MAX
424
+ @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
425
+ req = @parser.env
426
+ @parser.headers(req, @parser.buf)
427
+ assert_equal n, req['CONTENT_LENGTH'].to_i
428
+ assert ! @parser.keepalive?
429
+ end
430
+
431
+ def test_overflow_chunk
432
+ n = HttpParser::CHUNK_MAX + 1
433
+ str = @parser.buf
434
+ req = @parser.env
435
+ str << "PUT / HTTP/1.1\r\n" \
436
+ "transfer-Encoding: chunked\r\n\r\n" \
437
+ "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
438
+ assert_equal req, @parser.parse
439
+ assert_nil @parser.content_length
440
+ assert_raise(HttpParserError) { @parser.filter_body('', str) }
441
+ end
442
+
443
+ def test_overflow_content_length
444
+ n = HttpParser::LENGTH_MAX + 1
445
+ @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
446
+ assert_raise(HttpParserError) { @parser.parse }
447
+ end
448
+
449
+ def test_bad_chunk
450
+ @parser.buf << "PUT / HTTP/1.1\r\n" \
451
+ "transfer-Encoding: chunked\r\n\r\n" \
452
+ "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
453
+ req = @parser.env
454
+ assert_equal req, @parser.parse
455
+ assert_nil @parser.content_length
456
+ assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
457
+ end
458
+
459
+ def test_bad_content_length
460
+ @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
461
+ assert_raise(HttpParserError) { @parser.parse }
462
+ end
463
+
464
+ def test_bad_trailers
465
+ str = @parser.buf
466
+ req = @parser.env
467
+ str << "PUT / HTTP/1.1\r\n" \
468
+ "Trailer: Transfer-Encoding\r\n" \
469
+ "transfer-Encoding: chunked\r\n\r\n" \
470
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
471
+ assert_equal req, @parser.parse
472
+ assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
473
+ tmp = ''
474
+ assert_nil @parser.filter_body(tmp, str)
475
+ assert_equal 'a..', tmp
476
+ assert_equal '', str
477
+ str << "Transfer-Encoding: identity\r\n\r\n"
478
+ assert_raise(HttpParserError) { @parser.parse }
479
+ end
480
+
481
+ def test_repeat_headers
482
+ str = "PUT / HTTP/1.1\r\n" \
483
+ "Trailer: Content-MD5\r\n" \
484
+ "Trailer: Content-SHA1\r\n" \
485
+ "transfer-Encoding: chunked\r\n\r\n" \
486
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
487
+ req = @parser.env
488
+ @parser.buf << str
489
+ assert_equal req, @parser.parse
490
+ assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
491
+ assert ! @parser.keepalive?
492
+ end
493
+
494
+ def test_parse_simple_request
495
+ parser = HttpParser.new
496
+ req = parser.env
497
+ parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
498
+ assert_equal req, parser.parse
499
+ assert_equal '', parser.buf
500
+ expect = {
501
+ "SERVER_NAME"=>"localhost",
502
+ "rack.url_scheme"=>"http",
503
+ "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
504
+ "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
505
+ "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
506
+ "SERVER_PORT"=>"80",
507
+ "SERVER_PROTOCOL"=>"HTTP/0.9",
508
+ "REQUEST_METHOD"=>"GET",
509
+ "QUERY_STRING"=>""
510
+ }
511
+ assert_equal expect, req
512
+ assert ! parser.headers?
513
+ end
514
+
515
+ def test_path_info_semicolon
516
+ qs = "QUERY_STRING"
517
+ pi = "PATH_INFO"
518
+ req = {}
519
+ str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
520
+ {
521
+ "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
522
+ "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
523
+ "/1;a=b" => { qs => "", pi => "/1;a=b" },
524
+ "/1;a=b?" => { qs => "", pi => "/1;a=b" },
525
+ "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
526
+ "*" => { qs => "", pi => "" },
527
+ }.each do |uri,expect|
528
+ assert_equal req, @parser.headers(req.clear, str % [ uri ])
529
+ req = req.dup
530
+ @parser.clear
531
+ assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
532
+ assert_equal expect[qs], req[qs], "#{qs} mismatch"
533
+ assert_equal expect[pi], req[pi], "#{pi} mismatch"
534
+ next if uri == "*"
535
+ uri = URI.parse("http://example.com#{uri}")
536
+ assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
537
+ assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
538
+ end
539
+ end
540
+
541
+ def test_path_info_semicolon_absolute
542
+ qs = "QUERY_STRING"
543
+ pi = "PATH_INFO"
544
+ req = {}
545
+ str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
546
+ {
547
+ "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
548
+ "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
549
+ "/1;a=b" => { qs => "", pi => "/1;a=b" },
550
+ "/1;a=b?" => { qs => "", pi => "/1;a=b" },
551
+ "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
552
+ }.each do |uri,expect|
553
+ assert_equal req, @parser.headers(req.clear, str % [ uri ])
554
+ req = req.dup
555
+ @parser.clear
556
+ assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
557
+ assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
558
+ assert_equal expect[qs], req[qs], "#{qs} mismatch"
559
+ assert_equal expect[pi], req[pi], "#{pi} mismatch"
560
+ end
561
+ end
562
+
563
+ def test_negative_content_length
564
+ req = {}
565
+ str = "PUT / HTTP/1.1\r\n" \
566
+ "Content-Length: -1\r\n" \
567
+ "\r\n"
568
+ assert_raises(HttpParserError) do
569
+ @parser.headers(req, str)
570
+ end
571
+ end
572
+
573
+ def test_invalid_content_length
574
+ req = {}
575
+ str = "PUT / HTTP/1.1\r\n" \
576
+ "Content-Length: zzzzz\r\n" \
577
+ "\r\n"
578
+ assert_raises(HttpParserError) do
579
+ @parser.headers(req, str)
580
+ end
581
+ end
582
+
583
+ def test_duplicate_content_length
584
+ str = "PUT / HTTP/1.1\r\n" \
585
+ "Content-Length: 1\r\n" \
586
+ "Content-Length: 9\r\n" \
587
+ "\r\n"
588
+ assert_raises(HttpParserError) { @parser.headers({}, str) }
589
+ end
590
+
591
+ def test_chunked_overrides_content_length
592
+ order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
593
+ %w(a b).each do |x|
594
+ str = "PUT /#{x} HTTP/1.1\r\n" \
595
+ "#{order.join("\r\n")}" \
596
+ "\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
597
+ order.reverse!
598
+ env = @parser.headers({}, str)
599
+ assert_nil @parser.content_length
600
+ assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
601
+ assert_equal '666', env['CONTENT_LENGTH'],
602
+ 'Content-Length logged so the app can log a possible client bug/attack'
603
+ @parser.filter_body(dst = '', str)
604
+ assert_equal 'helloworld', dst
605
+ @parser.parse # handle the non-existent trailer
606
+ assert @parser.next?
607
+ end
608
+ end
609
+
610
+ def test_chunked_order_good
611
+ str = "PUT /x HTTP/1.1\r\n" \
612
+ "Transfer-Encoding: gzip\r\n" \
613
+ "Transfer-Encoding: chunked\r\n" \
614
+ "\r\n"
615
+ env = @parser.headers({}, str)
616
+ assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
617
+ assert_nil @parser.content_length
618
+
619
+ @parser.clear
620
+ str = "PUT /x HTTP/1.1\r\n" \
621
+ "Transfer-Encoding: gzip, chunked\r\n" \
622
+ "\r\n"
623
+ env = @parser.headers({}, str)
624
+ assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
625
+ assert_nil @parser.content_length
626
+ end
627
+
628
+ def test_chunked_order_bad
629
+ str = "PUT /x HTTP/1.1\r\n" \
630
+ "Transfer-Encoding: chunked\r\n" \
631
+ "Transfer-Encoding: gzip\r\n" \
632
+ "\r\n"
633
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
634
+ end
635
+
636
+ def test_double_chunked
637
+ str = "PUT /x HTTP/1.1\r\n" \
638
+ "Transfer-Encoding: chunked\r\n" \
639
+ "Transfer-Encoding: chunked\r\n" \
640
+ "\r\n"
641
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
642
+
643
+ @parser.clear
644
+ str = "PUT /x HTTP/1.1\r\n" \
645
+ "Transfer-Encoding: chunked,chunked\r\n" \
646
+ "\r\n"
647
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
648
+ end
649
+
650
+ def test_backtrace_is_empty
651
+ begin
652
+ @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
653
+ assert false, "should never get here line:#{__LINE__}"
654
+ rescue HttpParserError => e
655
+ assert_equal [], e.backtrace
656
+ return
657
+ end
658
+ assert false, "should never get here line:#{__LINE__}"
659
+ end
660
+
661
+ def test_ignore_version_header
662
+ @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
663
+ req = @parser.env
664
+ assert_equal req, @parser.parse
665
+ assert_equal '', @parser.buf
666
+ expect = {
667
+ "SERVER_NAME" => "localhost",
668
+ "rack.url_scheme" => "http",
669
+ "REQUEST_PATH" => "/",
670
+ "SERVER_PROTOCOL" => "HTTP/1.1",
671
+ "PATH_INFO" => "/",
672
+ "HTTP_VERSION" => "HTTP/1.1",
673
+ "REQUEST_URI" => "/",
674
+ "SERVER_PORT" => "80",
675
+ "REQUEST_METHOD" => "GET",
676
+ "QUERY_STRING" => ""
677
+ }
678
+ assert_equal expect, req
679
+ end
680
+
681
+ def test_pipelined_requests
682
+ host = "example.com"
683
+ expect = {
684
+ "HTTP_HOST" => host,
685
+ "SERVER_NAME" => host,
686
+ "REQUEST_PATH" => "/",
687
+ "rack.url_scheme" => "http",
688
+ "SERVER_PROTOCOL" => "HTTP/1.1",
689
+ "PATH_INFO" => "/",
690
+ "HTTP_VERSION" => "HTTP/1.1",
691
+ "REQUEST_URI" => "/",
692
+ "SERVER_PORT" => "80",
693
+ "REQUEST_METHOD" => "GET",
694
+ "QUERY_STRING" => ""
695
+ }
696
+ req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
697
+ req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
698
+ @parser.buf << (req1 + req2)
699
+ env1 = @parser.parse.dup
700
+ assert_equal expect, env1
701
+ assert_equal req2, @parser.buf
702
+ assert ! @parser.env.empty?
703
+ assert @parser.next?
704
+ assert @parser.keepalive?
705
+ assert @parser.headers?
706
+ assert_equal expect, @parser.env
707
+ env2 = @parser.parse.dup
708
+ host.replace "www.example.com"
709
+ assert_equal "www.example.com", expect["HTTP_HOST"]
710
+ assert_equal "www.example.com", expect["SERVER_NAME"]
711
+ assert_equal expect, env2
712
+ assert_equal "", @parser.buf
713
+ end
714
+ end