rack 1.6.13 → 2.0.9
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 +4 -4
- data/COPYING +1 -1
- data/HISTORY.md +138 -8
- data/README.rdoc +18 -28
- data/Rakefile +6 -14
- data/SPEC +3 -3
- 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/content_length.rb +2 -2
- data/lib/rack/deflater.rb +4 -39
- data/lib/rack/directory.rb +66 -54
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +64 -40
- 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 +24 -26
- data/lib/rack/handler.rb +3 -25
- data/lib/rack/head.rb +15 -17
- data/lib/rack/lint.rb +40 -40
- data/lib/rack/lobster.rb +1 -1
- data/lib/rack/lock.rb +15 -10
- data/lib/rack/logger.rb +2 -2
- data/lib/rack/media_type.rb +38 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +6 -6
- data/lib/rack/mime.rb +18 -5
- data/lib/rack/mock.rb +36 -54
- data/lib/rack/multipart/generator.rb +5 -5
- data/lib/rack/multipart/parser.rb +270 -157
- 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 +394 -305
- data/lib/rack/response.rb +130 -57
- 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 +30 -23
- data/lib/rack/session/abstract/id.rb +110 -75
- data/lib/rack/session/cookie.rb +24 -17
- data/lib/rack/session/memcache.rb +9 -9
- data/lib/rack/session/pool.rb +8 -8
- 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 +138 -211
- data/lib/rack.rb +70 -21
- data/rack.gemspec +10 -9
- 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_single_quote +7 -0
- 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 +37 -35
- 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 +85 -49
- 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 +120 -77
- data/test/spec_handler.rb +19 -34
- data/test/spec_head.rb +15 -14
- data/test/spec_lint.rb +164 -199
- data/test/spec_lobster.rb +24 -23
- data/test/spec_lock.rb +79 -39
- data/test/spec_logger.rb +4 -3
- data/test/spec_media_type.rb +42 -0
- data/test/{spec_methodoverride.rb → spec_method_override.rb} +34 -35
- data/test/spec_mime.rb +19 -19
- data/test/spec_mock.rb +206 -144
- data/test/spec_multipart.rb +322 -200
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +780 -605
- data/test/spec_response.rb +233 -112
- 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 +45 -0
- data/test/spec_session_cookie.rb +99 -67
- data/test/spec_session_memcache.rb +67 -68
- data/test/spec_session_pool.rb +52 -51
- data/test/{spec_showexceptions.rb → spec_show_exceptions.rb} +23 -28
- 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 +93 -71
- 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 +57 -36
- 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_mongrel.rb +0 -182
- /data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
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,63 +74,98 @@ 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
|
+
# automatic gzip detection (streamable)
|
85
|
+
def auto_inflater
|
86
|
+
Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
87
|
+
end
|
88
|
+
|
89
|
+
def deflate_or_gzip
|
90
|
+
{'deflate, gzip' => 'gzip'}
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'be able to deflate bodies that respond to each' do
|
84
94
|
app_body = Object.new
|
85
95
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
86
96
|
|
87
|
-
verify(200, 'foobar',
|
88
|
-
headers.
|
89
|
-
'Content-Encoding' => '
|
97
|
+
verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body|
|
98
|
+
headers.must_equal({
|
99
|
+
'Content-Encoding' => 'gzip',
|
90
100
|
'Vary' => 'Accept-Encoding',
|
91
101
|
'Content-Type' => 'text/plain'
|
92
102
|
})
|
93
103
|
end
|
94
104
|
end
|
95
105
|
|
96
|
-
|
106
|
+
it 'flush deflated chunks to the client as they become ready' do
|
97
107
|
app_body = Object.new
|
98
108
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
99
109
|
|
100
|
-
verify(200, app_body,
|
101
|
-
headers.
|
102
|
-
'Content-Encoding' => '
|
110
|
+
verify(200, app_body, deflate_or_gzip, { 'skip_body_verify' => true }) do |status, headers, body|
|
111
|
+
headers.must_equal({
|
112
|
+
'Content-Encoding' => 'gzip',
|
103
113
|
'Vary' => 'Accept-Encoding',
|
104
114
|
'Content-Type' => 'text/plain'
|
105
115
|
})
|
106
116
|
|
107
117
|
buf = []
|
108
|
-
inflater =
|
118
|
+
inflater = auto_inflater
|
109
119
|
body.each { |part| buf << inflater.inflate(part) }
|
110
120
|
buf << inflater.finish
|
111
121
|
|
112
|
-
buf.delete_if { |part| part.empty? }.join.
|
122
|
+
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'does not raise when a client aborts reading' do
|
127
|
+
app_body = Object.new
|
128
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
129
|
+
opts = { 'skip_body_verify' => true }
|
130
|
+
verify(200, app_body, 'gzip', opts) do |status, headers, body|
|
131
|
+
headers.must_equal({
|
132
|
+
'Content-Encoding' => 'gzip',
|
133
|
+
'Vary' => 'Accept-Encoding',
|
134
|
+
'Content-Type' => 'text/plain'
|
135
|
+
})
|
136
|
+
|
137
|
+
buf = []
|
138
|
+
inflater = auto_inflater
|
139
|
+
FakeDisconnect = Class.new(RuntimeError)
|
140
|
+
assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do
|
141
|
+
body.each do |part|
|
142
|
+
tmp = inflater.inflate(part)
|
143
|
+
buf << tmp if tmp.bytesize > 0
|
144
|
+
raise FakeDisconnect
|
145
|
+
end
|
146
|
+
end
|
147
|
+
inflater.finish
|
148
|
+
buf.must_equal(%w(foo))
|
113
149
|
end
|
114
150
|
end
|
115
151
|
|
116
152
|
# TODO: This is really just a special case of the above...
|
117
|
-
|
118
|
-
verify(200, 'Hello world!',
|
119
|
-
headers.
|
120
|
-
'Content-Encoding' => '
|
153
|
+
it 'be able to deflate String bodies' do
|
154
|
+
verify(200, 'Hello world!', deflate_or_gzip) do |status, headers, body|
|
155
|
+
headers.must_equal({
|
156
|
+
'Content-Encoding' => 'gzip',
|
121
157
|
'Vary' => 'Accept-Encoding',
|
122
158
|
'Content-Type' => 'text/plain'
|
123
159
|
})
|
124
160
|
end
|
125
161
|
end
|
126
162
|
|
127
|
-
|
163
|
+
it 'be able to gzip bodies that respond to each' do
|
128
164
|
app_body = Object.new
|
129
165
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
130
166
|
|
131
167
|
verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body|
|
132
|
-
headers.
|
168
|
+
headers.must_equal({
|
133
169
|
'Content-Encoding' => 'gzip',
|
134
170
|
'Vary' => 'Accept-Encoding',
|
135
171
|
'Content-Type' => 'text/plain'
|
@@ -137,12 +173,12 @@ describe Rack::Deflater do
|
|
137
173
|
end
|
138
174
|
end
|
139
175
|
|
140
|
-
|
176
|
+
it 'flush gzipped chunks to the client as they become ready' do
|
141
177
|
app_body = Object.new
|
142
178
|
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
143
179
|
|
144
180
|
verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body|
|
145
|
-
headers.
|
181
|
+
headers.must_equal({
|
146
182
|
'Content-Encoding' => 'gzip',
|
147
183
|
'Vary' => 'Accept-Encoding',
|
148
184
|
'Content-Type' => 'text/plain'
|
@@ -153,26 +189,26 @@ describe Rack::Deflater do
|
|
153
189
|
body.each { |part| buf << inflater.inflate(part) }
|
154
190
|
buf << inflater.finish
|
155
191
|
|
156
|
-
buf.delete_if { |part| part.empty? }.join.
|
192
|
+
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
157
193
|
end
|
158
194
|
end
|
159
195
|
|
160
|
-
|
196
|
+
it 'be able to fallback to no deflation' do
|
161
197
|
verify(200, 'Hello world!', 'superzip') do |status, headers, body|
|
162
|
-
headers.
|
198
|
+
headers.must_equal({
|
163
199
|
'Vary' => 'Accept-Encoding',
|
164
200
|
'Content-Type' => 'text/plain'
|
165
201
|
})
|
166
202
|
end
|
167
203
|
end
|
168
204
|
|
169
|
-
|
205
|
+
it 'be able to skip when there is no response entity body' do
|
170
206
|
verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body|
|
171
|
-
headers.
|
207
|
+
headers.must_equal({})
|
172
208
|
end
|
173
209
|
end
|
174
210
|
|
175
|
-
|
211
|
+
it 'handle the lack of an acceptable encoding' do
|
176
212
|
app_body = 'Hello world!'
|
177
213
|
not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.'
|
178
214
|
not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.'
|
@@ -192,21 +228,21 @@ describe Rack::Deflater do
|
|
192
228
|
}
|
193
229
|
|
194
230
|
verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body|
|
195
|
-
headers.
|
231
|
+
headers.must_equal({
|
196
232
|
'Content-Type' => 'text/plain',
|
197
233
|
'Content-Length' => not_found_body1.length.to_s
|
198
234
|
})
|
199
235
|
end
|
200
236
|
|
201
237
|
verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body|
|
202
|
-
headers.
|
238
|
+
headers.must_equal({
|
203
239
|
'Content-Type' => 'text/plain',
|
204
240
|
'Content-Length' => not_found_body2.length.to_s
|
205
241
|
})
|
206
242
|
end
|
207
243
|
end
|
208
244
|
|
209
|
-
|
245
|
+
it 'handle gzip response with Last-Modified header' do
|
210
246
|
last_modified = Time.now.httpdate
|
211
247
|
options = {
|
212
248
|
'response_headers' => {
|
@@ -216,7 +252,7 @@ describe Rack::Deflater do
|
|
216
252
|
}
|
217
253
|
|
218
254
|
verify(200, 'Hello World!', 'gzip', options) do |status, headers, body|
|
219
|
-
headers.
|
255
|
+
headers.must_equal({
|
220
256
|
'Content-Encoding' => 'gzip',
|
221
257
|
'Vary' => 'Accept-Encoding',
|
222
258
|
'Last-Modified' => last_modified,
|
@@ -225,7 +261,7 @@ describe Rack::Deflater do
|
|
225
261
|
end
|
226
262
|
end
|
227
263
|
|
228
|
-
|
264
|
+
it 'do nothing when no-transform Cache-Control directive present' do
|
229
265
|
options = {
|
230
266
|
'response_headers' => {
|
231
267
|
'Content-Type' => 'text/plain',
|
@@ -233,11 +269,11 @@ describe Rack::Deflater do
|
|
233
269
|
}
|
234
270
|
}
|
235
271
|
verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body|
|
236
|
-
headers.
|
272
|
+
headers.wont_include 'Content-Encoding'
|
237
273
|
end
|
238
274
|
end
|
239
275
|
|
240
|
-
|
276
|
+
it 'do nothing when Content-Encoding already present' do
|
241
277
|
options = {
|
242
278
|
'response_headers' => {
|
243
279
|
'Content-Type' => 'text/plain',
|
@@ -247,17 +283,17 @@ describe Rack::Deflater do
|
|
247
283
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
248
284
|
end
|
249
285
|
|
250
|
-
|
286
|
+
it 'deflate when Content-Encoding is identity' do
|
251
287
|
options = {
|
252
288
|
'response_headers' => {
|
253
289
|
'Content-Type' => 'text/plain',
|
254
290
|
'Content-Encoding' => 'identity'
|
255
291
|
}
|
256
292
|
}
|
257
|
-
verify(200, 'Hello World!',
|
293
|
+
verify(200, 'Hello World!', deflate_or_gzip, options)
|
258
294
|
end
|
259
295
|
|
260
|
-
|
296
|
+
it "deflate if content-type matches :include" do
|
261
297
|
options = {
|
262
298
|
'response_headers' => {
|
263
299
|
'Content-Type' => 'text/plain'
|
@@ -269,7 +305,7 @@ describe Rack::Deflater do
|
|
269
305
|
verify(200, 'Hello World!', 'gzip', options)
|
270
306
|
end
|
271
307
|
|
272
|
-
|
308
|
+
it "deflate if content-type is included it :include" do
|
273
309
|
options = {
|
274
310
|
'response_headers' => {
|
275
311
|
'Content-Type' => 'text/plain; charset=us-ascii'
|
@@ -281,7 +317,7 @@ describe Rack::Deflater do
|
|
281
317
|
verify(200, 'Hello World!', 'gzip', options)
|
282
318
|
end
|
283
319
|
|
284
|
-
|
320
|
+
it "not deflate if content-type is not set but given in :include" do
|
285
321
|
options = {
|
286
322
|
'deflater_options' => {
|
287
323
|
:include => %w(text/plain)
|
@@ -290,7 +326,7 @@ describe Rack::Deflater do
|
|
290
326
|
verify(304, 'Hello World!', { 'gzip' => nil }, options)
|
291
327
|
end
|
292
328
|
|
293
|
-
|
329
|
+
it "not deflate if content-type do not match :include" do
|
294
330
|
options = {
|
295
331
|
'response_headers' => {
|
296
332
|
'Content-Type' => 'text/plain'
|
@@ -302,16 +338,16 @@ describe Rack::Deflater do
|
|
302
338
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
303
339
|
end
|
304
340
|
|
305
|
-
|
341
|
+
it "deflate response if :if lambda evaluates to true" do
|
306
342
|
options = {
|
307
343
|
'deflater_options' => {
|
308
344
|
:if => lambda { |env, status, headers, body| true }
|
309
345
|
}
|
310
346
|
}
|
311
|
-
verify(200, 'Hello World!',
|
347
|
+
verify(200, 'Hello World!', deflate_or_gzip, options)
|
312
348
|
end
|
313
349
|
|
314
|
-
|
350
|
+
it "not deflate if :if lambda evaluates to false" do
|
315
351
|
options = {
|
316
352
|
'deflater_options' => {
|
317
353
|
:if => lambda { |env, status, headers, body| false }
|
@@ -320,20 +356,20 @@ describe Rack::Deflater do
|
|
320
356
|
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
321
357
|
end
|
322
358
|
|
323
|
-
|
324
|
-
|
325
|
-
|
359
|
+
it "check for Content-Length via :if" do
|
360
|
+
response = 'Hello World!'
|
361
|
+
response_len = response.length
|
326
362
|
options = {
|
327
363
|
'response_headers' => {
|
328
|
-
'Content-Length' =>
|
364
|
+
'Content-Length' => response_len.to_s
|
329
365
|
},
|
330
366
|
'deflater_options' => {
|
331
367
|
:if => lambda { |env, status, headers, body|
|
332
|
-
headers['Content-Length'].to_i >=
|
368
|
+
headers['Content-Length'].to_i >= response_len
|
333
369
|
}
|
334
370
|
}
|
335
371
|
}
|
336
372
|
|
337
|
-
verify(200,
|
373
|
+
verify(200, response, 'gzip', options)
|
338
374
|
end
|
339
375
|
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_be_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
|