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