boourns-unicorn 4.4.1

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