unicorn-maintained 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
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