rack 1.6.13 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/COPYING +1 -1
- data/HISTORY.md +138 -8
- data/README.rdoc +17 -25
- data/Rakefile +6 -14
- data/SPEC +8 -9
- data/contrib/rack_logo.svg +164 -111
- data/example/protectedlobster.rb +1 -1
- data/example/protectedlobster.ru +1 -1
- data/lib/rack/auth/abstract/request.rb +5 -1
- data/lib/rack/auth/digest/params.rb +2 -3
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/body_proxy.rb +14 -9
- data/lib/rack/builder.rb +3 -3
- data/lib/rack/chunked.rb +5 -5
- data/lib/rack/{commonlogger.rb → common_logger.rb} +3 -3
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
- data/lib/rack/content_length.rb +2 -2
- data/lib/rack/deflater.rb +4 -4
- data/lib/rack/directory.rb +66 -54
- data/lib/rack/etag.rb +4 -3
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +63 -39
- data/lib/rack/handler/cgi.rb +15 -16
- data/lib/rack/handler/fastcgi.rb +13 -14
- data/lib/rack/handler/lsws.rb +11 -11
- data/lib/rack/handler/scgi.rb +15 -15
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +22 -24
- data/lib/rack/handler.rb +3 -25
- data/lib/rack/head.rb +15 -17
- data/lib/rack/lint.rb +38 -38
- data/lib/rack/lobster.rb +1 -1
- data/lib/rack/lock.rb +6 -10
- data/lib/rack/logger.rb +2 -2
- data/lib/rack/media_type.rb +38 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +4 -11
- data/lib/rack/mime.rb +18 -5
- data/lib/rack/mock.rb +35 -53
- data/lib/rack/multipart/generator.rb +5 -5
- data/lib/rack/multipart/parser.rb +272 -158
- data/lib/rack/multipart/uploaded_file.rb +1 -2
- data/lib/rack/multipart.rb +35 -6
- data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
- data/lib/rack/query_parser.rb +192 -0
- data/lib/rack/recursive.rb +8 -8
- data/lib/rack/request.rb +383 -307
- data/lib/rack/response.rb +129 -56
- data/lib/rack/rewindable_input.rb +1 -12
- data/lib/rack/runtime.rb +10 -18
- data/lib/rack/sendfile.rb +5 -7
- data/lib/rack/server.rb +31 -25
- data/lib/rack/session/abstract/id.rb +95 -135
- data/lib/rack/session/cookie.rb +26 -28
- data/lib/rack/session/memcache.rb +8 -14
- data/lib/rack/session/pool.rb +14 -21
- data/lib/rack/show_exceptions.rb +386 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
- data/lib/rack/static.rb +30 -5
- data/lib/rack/tempfile_reaper.rb +2 -2
- data/lib/rack/urlmap.rb +15 -14
- data/lib/rack/utils.rb +135 -210
- data/lib/rack.rb +70 -21
- data/rack.gemspec +7 -5
- data/test/builder/an_underscore_app.rb +5 -0
- data/test/builder/options.ru +1 -1
- data/test/cgi/test.fcgi +1 -0
- data/test/cgi/test.gz +0 -0
- data/test/helper.rb +34 -0
- data/test/multipart/filename_with_encoded_words +7 -0
- data/test/multipart/{filename_with_null_byte → filename_with_single_quote} +1 -1
- data/test/multipart/quoted +15 -0
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/unity3d_wwwform +11 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
- data/test/spec_auth_basic.rb +27 -19
- data/test/spec_auth_digest.rb +47 -46
- data/test/spec_body_proxy.rb +27 -27
- data/test/spec_builder.rb +51 -41
- data/test/spec_cascade.rb +24 -22
- data/test/spec_cgi.rb +49 -67
- data/test/spec_chunked.rb +36 -34
- data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
- data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
- data/test/spec_config.rb +3 -2
- data/test/spec_content_length.rb +18 -17
- data/test/spec_content_type.rb +13 -12
- data/test/spec_deflater.rb +66 -40
- data/test/spec_directory.rb +87 -27
- data/test/spec_etag.rb +32 -31
- data/test/spec_events.rb +133 -0
- data/test/spec_fastcgi.rb +50 -72
- data/test/spec_file.rb +107 -77
- data/test/spec_handler.rb +19 -34
- data/test/spec_head.rb +15 -14
- data/test/spec_lint.rb +162 -197
- data/test/spec_lobster.rb +24 -23
- data/test/spec_lock.rb +69 -39
- data/test/spec_logger.rb +4 -3
- data/test/spec_media_type.rb +42 -0
- data/test/spec_method_override.rb +83 -0
- data/test/spec_mime.rb +19 -19
- data/test/spec_mock.rb +196 -151
- data/test/spec_multipart.rb +317 -201
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +768 -607
- data/test/spec_response.rb +214 -111
- data/test/spec_rewindable_input.rb +50 -40
- data/test/spec_runtime.rb +11 -10
- data/test/spec_sendfile.rb +30 -35
- data/test/spec_server.rb +78 -52
- data/test/spec_session_abstract_id.rb +11 -33
- data/test/spec_session_abstract_session_hash.rb +28 -0
- data/test/spec_session_cookie.rb +97 -65
- data/test/spec_session_memcache.rb +63 -101
- data/test/spec_session_pool.rb +48 -84
- data/test/spec_show_exceptions.rb +80 -0
- data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
- data/test/spec_static.rb +71 -32
- data/test/spec_tempfile_reaper.rb +11 -10
- data/test/spec_thin.rb +55 -50
- data/test/spec_urlmap.rb +79 -78
- data/test/spec_utils.rb +441 -346
- data/test/spec_version.rb +2 -8
- data/test/spec_webrick.rb +91 -67
- data/test/static/foo.html +1 -0
- data/test/testrequest.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
- metadata +103 -69
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_showexceptions.rb +0 -98
data/test/spec_deflater.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'minitest/autorun'
|
1
2
|
require 'stringio'
|
2
3
|
require 'time' # for Time#httpdate
|
3
4
|
require 'rack/deflater'
|
@@ -32,7 +33,7 @@ describe Rack::Deflater do
|
|
32
33
|
# [options] hash of request options, i.e.
|
33
34
|
# 'app_status' - what status dummy app should return (may be changed by deflater at some point)
|
34
35
|
# 'app_body' - what body dummy app should return (may be changed by deflater at some point)
|
35
|
-
# 'request_headers' - extra
|
36
|
+
# 'request_headers' - extra request headers to be sent
|
36
37
|
# 'response_headers' - extra response headers to be returned
|
37
38
|
# 'deflater_options' - options passed to deflater middleware
|
38
39
|
# [block] useful for doing some extra verification
|
@@ -52,7 +53,7 @@ describe Rack::Deflater do
|
|
52
53
|
)
|
53
54
|
|
54
55
|
# verify status
|
55
|
-
status.
|
56
|
+
status.must_equal expected_status
|
56
57
|
|
57
58
|
# verify body
|
58
59
|
unless options['skip_body_verify']
|
@@ -73,19 +74,19 @@ describe Rack::Deflater do
|
|
73
74
|
body_text
|
74
75
|
end
|
75
76
|
|
76
|
-
deflated_body.
|
77
|
+
deflated_body.must_equal expected_body
|
77
78
|
end
|
78
79
|
|
79
80
|
# yield full response verification
|
80
81
|
yield(status, headers, body) if block_given?
|
81
82
|
end
|
82
83
|
|
83
|
-
|
84
|
+
it 'be able to deflate bodies that respond to each' do
|
84
85
|
app_body = Object.new
|
85
86
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
86
87
|
|
87
88
|
verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body|
|
88
|
-
headers.
|
89
|
+
headers.must_equal({
|
89
90
|
'Content-Encoding' => 'deflate',
|
90
91
|
'Vary' => 'Accept-Encoding',
|
91
92
|
'Content-Type' => 'text/plain'
|
@@ -93,12 +94,12 @@ describe Rack::Deflater do
|
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
96
|
-
|
97
|
+
it 'flush deflated chunks to the client as they become ready' do
|
97
98
|
app_body = Object.new
|
98
99
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
99
100
|
|
100
101
|
verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body|
|
101
|
-
headers.
|
102
|
+
headers.must_equal({
|
102
103
|
'Content-Encoding' => 'deflate',
|
103
104
|
'Vary' => 'Accept-Encoding',
|
104
105
|
'Content-Type' => 'text/plain'
|
@@ -109,14 +110,39 @@ describe Rack::Deflater do
|
|
109
110
|
body.each { |part| buf << inflater.inflate(part) }
|
110
111
|
buf << inflater.finish
|
111
112
|
|
112
|
-
buf.delete_if { |part| part.empty? }.join.
|
113
|
+
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'does not raise when a client aborts reading' do
|
118
|
+
app_body = Object.new
|
119
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
120
|
+
opts = { 'skip_body_verify' => true }
|
121
|
+
verify(200, app_body, 'deflate', opts) do |status, headers, body|
|
122
|
+
headers.must_equal({
|
123
|
+
'Content-Encoding' => 'deflate',
|
124
|
+
'Vary' => 'Accept-Encoding',
|
125
|
+
'Content-Type' => 'text/plain'
|
126
|
+
})
|
127
|
+
|
128
|
+
buf = []
|
129
|
+
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
130
|
+
FakeDisconnect = Class.new(RuntimeError)
|
131
|
+
assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do
|
132
|
+
body.each do |part|
|
133
|
+
buf << inflater.inflate(part)
|
134
|
+
raise FakeDisconnect
|
135
|
+
end
|
136
|
+
end
|
137
|
+
assert_raises(Zlib::BufError) { inflater.finish }
|
138
|
+
buf.must_equal(%w(foo))
|
113
139
|
end
|
114
140
|
end
|
115
141
|
|
116
142
|
# TODO: This is really just a special case of the above...
|
117
|
-
|
143
|
+
it 'be able to deflate String bodies' do
|
118
144
|
verify(200, 'Hello world!', 'deflate') do |status, headers, body|
|
119
|
-
headers.
|
145
|
+
headers.must_equal({
|
120
146
|
'Content-Encoding' => 'deflate',
|
121
147
|
'Vary' => 'Accept-Encoding',
|
122
148
|
'Content-Type' => 'text/plain'
|
@@ -124,12 +150,12 @@ describe Rack::Deflater do
|
|
124
150
|
end
|
125
151
|
end
|
126
152
|
|
127
|
-
|
153
|
+
it 'be able to gzip bodies that respond to each' do
|
128
154
|
app_body = Object.new
|
129
155
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
130
156
|
|
131
157
|
verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body|
|
132
|
-
headers.
|
158
|
+
headers.must_equal({
|
133
159
|
'Content-Encoding' => 'gzip',
|
134
160
|
'Vary' => 'Accept-Encoding',
|
135
161
|
'Content-Type' => 'text/plain'
|
@@ -137,12 +163,12 @@ describe Rack::Deflater do
|
|
137
163
|
end
|
138
164
|
end
|
139
165
|
|
140
|
-
|
166
|
+
it 'flush gzipped chunks to the client as they become ready' do
|
141
167
|
app_body = Object.new
|
142
168
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
143
169
|
|
144
170
|
verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body|
|
145
|
-
headers.
|
171
|
+
headers.must_equal({
|
146
172
|
'Content-Encoding' => 'gzip',
|
147
173
|
'Vary' => 'Accept-Encoding',
|
148
174
|
'Content-Type' => 'text/plain'
|
@@ -153,26 +179,26 @@ describe Rack::Deflater do
|
|
153
179
|
body.each { |part| buf << inflater.inflate(part) }
|
154
180
|
buf << inflater.finish
|
155
181
|
|
156
|
-
buf.delete_if { |part| part.empty? }.join.
|
182
|
+
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
157
183
|
end
|
158
184
|
end
|
159
185
|
|
160
|
-
|
186
|
+
it 'be able to fallback to no deflation' do
|
161
187
|
verify(200, 'Hello world!', 'superzip') do |status, headers, body|
|
162
|
-
headers.
|
188
|
+
headers.must_equal({
|
163
189
|
'Vary' => 'Accept-Encoding',
|
164
190
|
'Content-Type' => 'text/plain'
|
165
191
|
})
|
166
192
|
end
|
167
193
|
end
|
168
194
|
|
169
|
-
|
195
|
+
it 'be able to skip when there is no response entity body' do
|
170
196
|
verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body|
|
171
|
-
headers.
|
197
|
+
headers.must_equal({})
|
172
198
|
end
|
173
199
|
end
|
174
200
|
|
175
|
-
|
201
|
+
it 'handle the lack of an acceptable encoding' do
|
176
202
|
app_body = 'Hello world!'
|
177
203
|
not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.'
|
178
204
|
not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.'
|
@@ -192,21 +218,21 @@ describe Rack::Deflater do
|
|
192
218
|
}
|
193
219
|
|
194
220
|
verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body|
|
195
|
-
headers.
|
221
|
+
headers.must_equal({
|
196
222
|
'Content-Type' => 'text/plain',
|
197
223
|
'Content-Length' => not_found_body1.length.to_s
|
198
224
|
})
|
199
225
|
end
|
200
226
|
|
201
227
|
verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body|
|
202
|
-
headers.
|
228
|
+
headers.must_equal({
|
203
229
|
'Content-Type' => 'text/plain',
|
204
230
|
'Content-Length' => not_found_body2.length.to_s
|
205
231
|
})
|
206
232
|
end
|
207
233
|
end
|
208
234
|
|
209
|
-
|
235
|
+
it 'handle gzip response with Last-Modified header' do
|
210
236
|
last_modified = Time.now.httpdate
|
211
237
|
options = {
|
212
238
|
'response_headers' => {
|
@@ -216,7 +242,7 @@ describe Rack::Deflater do
|
|
216
242
|
}
|
217
243
|
|
218
244
|
verify(200, 'Hello World!', 'gzip', options) do |status, headers, body|
|
219
|
-
headers.
|
245
|
+
headers.must_equal({
|
220
246
|
'Content-Encoding' => 'gzip',
|
221
247
|
'Vary' => 'Accept-Encoding',
|
222
248
|
'Last-Modified' => last_modified,
|
@@ -225,7 +251,7 @@ describe Rack::Deflater do
|
|
225
251
|
end
|
226
252
|
end
|
227
253
|
|
228
|
-
|
254
|
+
it 'do nothing when no-transform Cache-Control directive present' do
|
229
255
|
options = {
|
230
256
|
'response_headers' => {
|
231
257
|
'Content-Type' => 'text/plain',
|
@@ -233,11 +259,11 @@ describe Rack::Deflater do
|
|
233
259
|
}
|
234
260
|
}
|
235
261
|
verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body|
|
236
|
-
headers.
|
262
|
+
headers.wont_include 'Content-Encoding'
|
237
263
|
end
|
238
264
|
end
|
239
265
|
|
240
|
-
|
266
|
+
it 'do nothing when Content-Encoding already present' do
|
241
267
|
options = {
|
242
268
|
'response_headers' => {
|
243
269
|
'Content-Type' => 'text/plain',
|
@@ -247,7 +273,7 @@ describe Rack::Deflater do
|
|
247
273
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
248
274
|
end
|
249
275
|
|
250
|
-
|
276
|
+
it 'deflate when Content-Encoding is identity' do
|
251
277
|
options = {
|
252
278
|
'response_headers' => {
|
253
279
|
'Content-Type' => 'text/plain',
|
@@ -257,7 +283,7 @@ describe Rack::Deflater do
|
|
257
283
|
verify(200, 'Hello World!', 'deflate', options)
|
258
284
|
end
|
259
285
|
|
260
|
-
|
286
|
+
it "deflate if content-type matches :include" do
|
261
287
|
options = {
|
262
288
|
'response_headers' => {
|
263
289
|
'Content-Type' => 'text/plain'
|
@@ -269,7 +295,7 @@ describe Rack::Deflater do
|
|
269
295
|
verify(200, 'Hello World!', 'gzip', options)
|
270
296
|
end
|
271
297
|
|
272
|
-
|
298
|
+
it "deflate if content-type is included it :include" do
|
273
299
|
options = {
|
274
300
|
'response_headers' => {
|
275
301
|
'Content-Type' => 'text/plain; charset=us-ascii'
|
@@ -281,7 +307,7 @@ describe Rack::Deflater do
|
|
281
307
|
verify(200, 'Hello World!', 'gzip', options)
|
282
308
|
end
|
283
309
|
|
284
|
-
|
310
|
+
it "not deflate if content-type is not set but given in :include" do
|
285
311
|
options = {
|
286
312
|
'deflater_options' => {
|
287
313
|
:include => %w(text/plain)
|
@@ -290,7 +316,7 @@ describe Rack::Deflater do
|
|
290
316
|
verify(304, 'Hello World!', { 'gzip' => nil }, options)
|
291
317
|
end
|
292
318
|
|
293
|
-
|
319
|
+
it "not deflate if content-type do not match :include" do
|
294
320
|
options = {
|
295
321
|
'response_headers' => {
|
296
322
|
'Content-Type' => 'text/plain'
|
@@ -302,7 +328,7 @@ describe Rack::Deflater do
|
|
302
328
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
303
329
|
end
|
304
330
|
|
305
|
-
|
331
|
+
it "deflate response if :if lambda evaluates to true" do
|
306
332
|
options = {
|
307
333
|
'deflater_options' => {
|
308
334
|
:if => lambda { |env, status, headers, body| true }
|
@@ -311,7 +337,7 @@ describe Rack::Deflater do
|
|
311
337
|
verify(200, 'Hello World!', 'deflate', options)
|
312
338
|
end
|
313
339
|
|
314
|
-
|
340
|
+
it "not deflate if :if lambda evaluates to false" do
|
315
341
|
options = {
|
316
342
|
'deflater_options' => {
|
317
343
|
:if => lambda { |env, status, headers, body| false }
|
@@ -320,20 +346,20 @@ describe Rack::Deflater do
|
|
320
346
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
321
347
|
end
|
322
348
|
|
323
|
-
|
324
|
-
|
325
|
-
|
349
|
+
it "check for Content-Length via :if" do
|
350
|
+
response = 'Hello World!'
|
351
|
+
response_len = response.length
|
326
352
|
options = {
|
327
353
|
'response_headers' => {
|
328
|
-
'Content-Length' =>
|
354
|
+
'Content-Length' => response_len.to_s
|
329
355
|
},
|
330
356
|
'deflater_options' => {
|
331
357
|
:if => lambda { |env, status, headers, body|
|
332
|
-
headers['Content-Length'].to_i >=
|
358
|
+
headers['Content-Length'].to_i >= response_len
|
333
359
|
}
|
334
360
|
}
|
335
361
|
}
|
336
362
|
|
337
|
-
verify(200,
|
363
|
+
verify(200, response, 'gzip', options)
|
338
364
|
end
|
339
365
|
end
|
data/test/spec_directory.rb
CHANGED
@@ -1,77 +1,129 @@
|
|
1
|
+
require 'minitest/autorun'
|
1
2
|
require 'rack/directory'
|
2
3
|
require 'rack/lint'
|
3
4
|
require 'rack/mock'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'fileutils'
|
4
7
|
|
5
8
|
describe Rack::Directory do
|
6
9
|
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
7
10
|
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
|
8
|
-
app = Rack::Lint.new(Rack::Directory.new(DOCROOT, FILE_CATCH))
|
9
11
|
|
10
|
-
|
12
|
+
attr_reader :app
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@app = Rack::Lint.new(Rack::Directory.new(DOCROOT, FILE_CATCH))
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'serves directories with + in the name' do
|
19
|
+
Dir.mktmpdir do |dir|
|
20
|
+
plus_dir = "foo+bar"
|
21
|
+
full_dir = File.join(dir, plus_dir)
|
22
|
+
FileUtils.mkdir full_dir
|
23
|
+
FileUtils.touch File.join(full_dir, "omg.txt")
|
24
|
+
app = Rack::Directory.new(dir, FILE_CATCH)
|
25
|
+
env = Rack::MockRequest.env_for("/#{plus_dir}/")
|
26
|
+
status,_,body = app.call env
|
27
|
+
|
28
|
+
assert_equal 200, status
|
29
|
+
|
30
|
+
str = ''
|
31
|
+
body.each { |x| str << x }
|
32
|
+
assert_match "foo+bar", str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "serve directory indices" do
|
11
37
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
12
38
|
get("/cgi/")
|
13
39
|
|
14
|
-
res.
|
15
|
-
res
|
40
|
+
res.must_be :ok?
|
41
|
+
assert_match(res, /<html><head>/)
|
16
42
|
end
|
17
43
|
|
18
|
-
|
44
|
+
it "pass to app if file found" do
|
19
45
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
20
46
|
get("/cgi/test")
|
21
47
|
|
22
|
-
res.
|
23
|
-
res
|
48
|
+
res.must_be :ok?
|
49
|
+
assert_match(res, /passed!/)
|
24
50
|
end
|
25
51
|
|
26
|
-
|
52
|
+
it "serve uri with URL encoded filenames" do
|
27
53
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
28
54
|
get("/%63%67%69/") # "/cgi/test"
|
29
55
|
|
30
|
-
res.
|
31
|
-
res
|
56
|
+
res.must_be :ok?
|
57
|
+
assert_match(res, /<html><head>/)
|
32
58
|
|
33
59
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
34
60
|
get("/cgi/%74%65%73%74") # "/cgi/test"
|
35
61
|
|
36
|
-
res.
|
37
|
-
res
|
62
|
+
res.must_be :ok?
|
63
|
+
assert_match(res, /passed!/)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "serve uri with URL encoded null byte (%00) in filenames" do
|
67
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app))
|
68
|
+
.get("/cgi/test%00")
|
69
|
+
|
70
|
+
res.must_be :bad_request?
|
38
71
|
end
|
39
72
|
|
40
|
-
|
73
|
+
it "not allow directory traversal" do
|
41
74
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
42
75
|
get("/cgi/../test")
|
43
76
|
|
44
|
-
res.
|
77
|
+
res.must_be :forbidden?
|
45
78
|
|
46
79
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
47
80
|
get("/cgi/%2E%2E/test")
|
48
81
|
|
49
|
-
res.
|
82
|
+
res.must_be :forbidden?
|
50
83
|
end
|
51
84
|
|
52
|
-
|
85
|
+
it "404 if it can't find the file" do
|
53
86
|
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
54
87
|
get("/cgi/blubb")
|
55
88
|
|
56
|
-
res.
|
89
|
+
res.must_be :not_found?
|
57
90
|
end
|
58
91
|
|
59
|
-
|
92
|
+
it "uri escape path parts" do # #265, properly escape file names
|
60
93
|
mr = Rack::MockRequest.new(Rack::Lint.new(app))
|
61
94
|
|
62
95
|
res = mr.get("/cgi/test%2bdirectory")
|
63
96
|
|
64
|
-
res.
|
65
|
-
res.body.
|
97
|
+
res.must_be :ok?
|
98
|
+
res.body.must_match(%r[/cgi/test\+directory/test\+file])
|
66
99
|
|
67
100
|
res = mr.get("/cgi/test%2bdirectory/test%2bfile")
|
68
|
-
res.
|
101
|
+
res.must_be :ok?
|
102
|
+
end
|
103
|
+
|
104
|
+
it "correctly escape script name with spaces" do
|
105
|
+
Dir.mktmpdir do |dir|
|
106
|
+
space_dir = "foo bar"
|
107
|
+
full_dir = File.join(dir, space_dir)
|
108
|
+
FileUtils.mkdir full_dir
|
109
|
+
FileUtils.touch File.join(full_dir, "omg omg.txt")
|
110
|
+
app = Rack::Directory.new(dir, FILE_CATCH)
|
111
|
+
env = Rack::MockRequest.env_for(Rack::Utils.escape_path("/#{space_dir}/"))
|
112
|
+
status,_,body = app.call env
|
113
|
+
|
114
|
+
assert_equal 200, status
|
115
|
+
|
116
|
+
str = ''
|
117
|
+
body.each { |x| str << x }
|
118
|
+
assert_match "/foo%20bar/omg%20omg.txt", str
|
119
|
+
end
|
69
120
|
end
|
70
121
|
|
71
|
-
|
122
|
+
it "correctly escape script name" do
|
123
|
+
_app = app
|
72
124
|
app2 = Rack::Builder.new do
|
73
125
|
map '/script-path' do
|
74
|
-
run
|
126
|
+
run _app
|
75
127
|
end
|
76
128
|
end
|
77
129
|
|
@@ -79,10 +131,18 @@ describe Rack::Directory do
|
|
79
131
|
|
80
132
|
res = mr.get("/script-path/cgi/test%2bdirectory")
|
81
133
|
|
82
|
-
res.
|
83
|
-
res.body.
|
134
|
+
res.must_be :ok?
|
135
|
+
res.body.must_match(%r[/script-path/cgi/test\+directory/test\+file])
|
136
|
+
|
137
|
+
res = mr.get("/script-path/cgi/test+directory/test+file")
|
138
|
+
res.must_be :ok?
|
139
|
+
end
|
140
|
+
|
141
|
+
it "return error when file not found for head request" do
|
142
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
143
|
+
head("/cgi/missing")
|
84
144
|
|
85
|
-
res
|
86
|
-
res.
|
145
|
+
res.must_be :not_found?
|
146
|
+
res.body.must_be :empty?
|
87
147
|
end
|
88
148
|
end
|
data/test/spec_etag.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'minitest/autorun'
|
1
2
|
require 'rack/etag'
|
2
3
|
require 'rack/lint'
|
3
4
|
require 'rack/mock'
|
@@ -7,101 +8,101 @@ describe Rack::ETag do
|
|
7
8
|
def etag(app, *args)
|
8
9
|
Rack::Lint.new Rack::ETag.new(app, *args)
|
9
10
|
end
|
10
|
-
|
11
|
+
|
11
12
|
def request
|
12
13
|
Rack::MockRequest.env_for
|
13
14
|
end
|
14
|
-
|
15
|
+
|
15
16
|
def sendfile_body
|
16
17
|
res = ['Hello World']
|
17
18
|
def res.to_path ; "/tmp/hello.txt" ; end
|
18
19
|
res
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
it "set ETag if none is set if status is 200" do
|
22
23
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
23
24
|
response = etag(app).call(request)
|
24
|
-
response[1]['ETag'].
|
25
|
+
response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
+
it "set ETag if none is set if status is 201" do
|
28
29
|
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
29
30
|
response = etag(app).call(request)
|
30
|
-
response[1]['ETag'].
|
31
|
+
response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
+
it "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
|
34
35
|
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
35
36
|
response = etag(app).call(request)
|
36
|
-
response[1]['Cache-Control'].
|
37
|
+
response[1]['Cache-Control'].must_equal 'max-age=0, private, must-revalidate'
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
+
it "set Cache-Control to chosen one if none is set" do
|
40
41
|
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
41
42
|
response = etag(app, nil, 'public').call(request)
|
42
|
-
response[1]['Cache-Control'].
|
43
|
+
response[1]['Cache-Control'].must_equal 'public'
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
+
it "set a given Cache-Control even if digest could not be calculated" do
|
46
47
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, []] }
|
47
48
|
response = etag(app, 'no-cache').call(request)
|
48
|
-
response[1]['Cache-Control'].
|
49
|
+
response[1]['Cache-Control'].must_equal 'no-cache'
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
+
it "not set Cache-Control if it is already set" do
|
52
53
|
app = lambda { |env| [201, {'Content-Type' => 'text/plain', 'Cache-Control' => 'public'}, ["Hello, World!"]] }
|
53
54
|
response = etag(app).call(request)
|
54
|
-
response[1]['Cache-Control'].
|
55
|
+
response[1]['Cache-Control'].must_equal 'public'
|
55
56
|
end
|
56
57
|
|
57
|
-
|
58
|
+
it "not set Cache-Control if directive isn't present" do
|
58
59
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
59
60
|
response = etag(app, nil, nil).call(request)
|
60
|
-
response[1]['Cache-Control'].
|
61
|
+
response[1]['Cache-Control'].must_equal nil
|
61
62
|
end
|
62
63
|
|
63
|
-
|
64
|
+
it "not change ETag if it is already set" do
|
64
65
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
|
65
66
|
response = etag(app).call(request)
|
66
|
-
response[1]['ETag'].
|
67
|
+
response[1]['ETag'].must_equal "\"abc\""
|
67
68
|
end
|
68
69
|
|
69
|
-
|
70
|
+
it "not set ETag if body is empty" do
|
70
71
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, []] }
|
71
72
|
response = etag(app).call(request)
|
72
|
-
response[1]['ETag'].
|
73
|
+
response[1]['ETag'].must_be_nil
|
73
74
|
end
|
74
75
|
|
75
|
-
|
76
|
+
it "not set ETag if Last-Modified is set" do
|
76
77
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, ["Hello, World!"]] }
|
77
78
|
response = etag(app).call(request)
|
78
|
-
response[1]['ETag'].
|
79
|
+
response[1]['ETag'].must_be_nil
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
+
it "not set ETag if a sendfile_body is given" do
|
82
83
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, sendfile_body] }
|
83
84
|
response = etag(app).call(request)
|
84
|
-
response[1]['ETag'].
|
85
|
+
response[1]['ETag'].must_be_nil
|
85
86
|
end
|
86
87
|
|
87
|
-
|
88
|
+
it "not set ETag if a status is not 200 or 201" do
|
88
89
|
app = lambda { |env| [401, {'Content-Type' => 'text/plain'}, ['Access denied.']] }
|
89
90
|
response = etag(app).call(request)
|
90
|
-
response[1]['ETag'].
|
91
|
+
response[1]['ETag'].must_be_nil
|
91
92
|
end
|
92
93
|
|
93
|
-
|
94
|
+
it "not set ETag if no-cache is given" do
|
94
95
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache, must-revalidate'}, ['Hello, World!']] }
|
95
96
|
response = etag(app).call(request)
|
96
|
-
response[1]['ETag'].
|
97
|
+
response[1]['ETag'].must_be_nil
|
97
98
|
end
|
98
99
|
|
99
|
-
|
100
|
+
it "close the original body" do
|
100
101
|
body = StringIO.new
|
101
102
|
app = lambda { |env| [200, {}, body] }
|
102
103
|
response = etag(app).call(request)
|
103
|
-
body.
|
104
|
+
body.wont_be :closed?
|
104
105
|
response[2].close
|
105
|
-
body.
|
106
|
+
body.must_be :closed?
|
106
107
|
end
|
107
108
|
end
|