rack 1.6.13 → 2.0.0.alpha
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/HISTORY.md +139 -18
- data/README.rdoc +17 -25
- data/Rakefile +6 -14
- data/SPEC +8 -9
- data/contrib/rack_logo.svg +164 -111
- data/lib/rack.rb +70 -21
- 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} +2 -2
- 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 +49 -55
- data/lib/rack/etag.rb +2 -1
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +55 -40
- data/lib/rack/handler.rb +2 -24
- 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/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 -52
- data/lib/rack/multipart.rb +35 -6
- data/lib/rack/multipart/generator.rb +4 -4
- data/lib/rack/multipart/parser.rb +273 -158
- data/lib/rack/multipart/uploaded_file.rb +1 -2
- data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
- data/lib/rack/query_parser.rb +174 -0
- data/lib/rack/recursive.rb +8 -8
- data/lib/rack/reloader.rb +1 -2
- data/lib/rack/request.rb +370 -304
- 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 +93 -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 +13 -14
- data/lib/rack/utils.rb +128 -221
- data/rack.gemspec +9 -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 +31 -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/registering_handler/rack/handler/registering_myself.rb +1 -1
- data/test/spec_auth_basic.rb +20 -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 +72 -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 +96 -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 +310 -202
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +763 -607
- data/test/spec_response.rb +209 -156
- 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_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 +417 -345
- data/test/spec_version.rb +2 -8
- data/test/spec_webrick.rb +77 -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 +116 -71
- 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_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/\"65a8e27d8879283831b664bd8b7f0ad4\""
|
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/\"65a8e27d8879283831b664bd8b7f0ad4\""
|
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
|
data/test/spec_events.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'rack/events'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class TestEvents < Rack::TestCase
|
6
|
+
class EventMiddleware
|
7
|
+
attr_reader :events
|
8
|
+
|
9
|
+
def initialize events
|
10
|
+
@events = events
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_start req, res
|
14
|
+
events << [self, __method__]
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_commit req, res
|
18
|
+
events << [self, __method__]
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_send req, res
|
22
|
+
events << [self, __method__]
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_finish req, res
|
26
|
+
events << [self, __method__]
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_error req, res, e
|
30
|
+
events << [self, __method__]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_events_fire
|
35
|
+
events = []
|
36
|
+
ret = [200, {}, []]
|
37
|
+
app = lambda { |env| events << [app, :call]; ret }
|
38
|
+
se = EventMiddleware.new events
|
39
|
+
e = Events.new app, [se]
|
40
|
+
triple = e.call({})
|
41
|
+
response_body = []
|
42
|
+
triple[2].each { |x| response_body << x }
|
43
|
+
triple[2].close
|
44
|
+
triple[2] = response_body
|
45
|
+
assert_equal ret, triple
|
46
|
+
assert_equal [[se, :on_start],
|
47
|
+
[app, :call],
|
48
|
+
[se, :on_commit],
|
49
|
+
[se, :on_send],
|
50
|
+
[se, :on_finish],
|
51
|
+
], events
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_send_and_finish_are_not_run_until_body_is_sent
|
55
|
+
events = []
|
56
|
+
ret = [200, {}, []]
|
57
|
+
app = lambda { |env| events << [app, :call]; ret }
|
58
|
+
se = EventMiddleware.new events
|
59
|
+
e = Events.new app, [se]
|
60
|
+
triple = e.call({})
|
61
|
+
assert_equal [[se, :on_start],
|
62
|
+
[app, :call],
|
63
|
+
[se, :on_commit],
|
64
|
+
], events
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_send_is_called_on_each
|
68
|
+
events = []
|
69
|
+
ret = [200, {}, []]
|
70
|
+
app = lambda { |env| events << [app, :call]; ret }
|
71
|
+
se = EventMiddleware.new events
|
72
|
+
e = Events.new app, [se]
|
73
|
+
triple = e.call({})
|
74
|
+
triple[2].each { |x| }
|
75
|
+
assert_equal [[se, :on_start],
|
76
|
+
[app, :call],
|
77
|
+
[se, :on_commit],
|
78
|
+
[se, :on_send],
|
79
|
+
], events
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_finish_is_called_on_close
|
83
|
+
events = []
|
84
|
+
ret = [200, {}, []]
|
85
|
+
app = lambda { |env| events << [app, :call]; ret }
|
86
|
+
se = EventMiddleware.new events
|
87
|
+
e = Events.new app, [se]
|
88
|
+
triple = e.call({})
|
89
|
+
triple[2].each { |x| }
|
90
|
+
triple[2].close
|
91
|
+
assert_equal [[se, :on_start],
|
92
|
+
[app, :call],
|
93
|
+
[se, :on_commit],
|
94
|
+
[se, :on_send],
|
95
|
+
[se, :on_finish],
|
96
|
+
], events
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_finish_is_called_in_reverse_order
|
100
|
+
events = []
|
101
|
+
ret = [200, {}, []]
|
102
|
+
app = lambda { |env| events << [app, :call]; ret }
|
103
|
+
se1 = EventMiddleware.new events
|
104
|
+
se2 = EventMiddleware.new events
|
105
|
+
se3 = EventMiddleware.new events
|
106
|
+
|
107
|
+
e = Events.new app, [se1, se2, se3]
|
108
|
+
triple = e.call({})
|
109
|
+
triple[2].each { |x| }
|
110
|
+
triple[2].close
|
111
|
+
|
112
|
+
groups = events.group_by { |x| x.last }
|
113
|
+
assert_equal groups[:on_start].map(&:first), groups[:on_finish].map(&:first).reverse
|
114
|
+
assert_equal groups[:on_commit].map(&:first), groups[:on_finish].map(&:first)
|
115
|
+
assert_equal groups[:on_send].map(&:first), groups[:on_finish].map(&:first)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_finish_is_called_if_there_is_an_exception
|
119
|
+
events = []
|
120
|
+
ret = [200, {}, []]
|
121
|
+
app = lambda { |env| raise }
|
122
|
+
se = EventMiddleware.new events
|
123
|
+
e = Events.new app, [se]
|
124
|
+
assert_raises(RuntimeError) do
|
125
|
+
e.call({})
|
126
|
+
end
|
127
|
+
assert_equal [[se, :on_start],
|
128
|
+
[se, :on_error],
|
129
|
+
[se, :on_finish],
|
130
|
+
], events
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/test/spec_fastcgi.rb
CHANGED
@@ -1,107 +1,85 @@
|
|
1
|
-
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
if defined? LIGHTTPD_PID
|
4
|
+
|
2
5
|
require File.expand_path('../testrequest', __FILE__)
|
3
6
|
require 'rack/handler/fastcgi'
|
4
7
|
|
5
8
|
describe Rack::Handler::FastCGI do
|
6
|
-
|
9
|
+
include TestRequest::Helpers
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if `which lighttpd` && !$?.success?
|
12
|
-
raise "lighttpd not found"
|
11
|
+
before do
|
12
|
+
@host = '127.0.0.1'
|
13
|
+
@port = 9203
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
$pid = fork {
|
17
|
-
ENV['RACK_ENV'] = 'deployment'
|
18
|
-
ENV['RUBYLIB'] = [
|
19
|
-
File.expand_path('../../lib', __FILE__),
|
20
|
-
ENV['RUBYLIB'],
|
21
|
-
].compact.join(':')
|
22
|
-
|
23
|
-
Dir.chdir(File.expand_path("../cgi", __FILE__)) do
|
24
|
-
exec "lighttpd -D -f lighttpd.conf"
|
25
|
-
end
|
26
|
-
}
|
27
|
-
|
28
|
-
should "respond" do
|
16
|
+
it "respond" do
|
29
17
|
sleep 1
|
30
18
|
GET("/test")
|
31
|
-
response.
|
19
|
+
response.wont_be :nil?
|
32
20
|
end
|
33
21
|
|
34
|
-
|
22
|
+
it "respond via rackup server" do
|
35
23
|
GET("/sample_rackup.ru")
|
36
|
-
status.
|
24
|
+
status.must_equal 200
|
37
25
|
end
|
38
26
|
|
39
|
-
|
27
|
+
it "be a lighttpd" do
|
40
28
|
GET("/test.fcgi")
|
41
|
-
status.
|
42
|
-
response["SERVER_SOFTWARE"].
|
43
|
-
response["HTTP_VERSION"].
|
44
|
-
response["SERVER_PROTOCOL"].
|
45
|
-
response["SERVER_PORT"].
|
46
|
-
response["SERVER_NAME"].
|
29
|
+
status.must_equal 200
|
30
|
+
response["SERVER_SOFTWARE"].must_match(/lighttpd/)
|
31
|
+
response["HTTP_VERSION"].must_equal "HTTP/1.1"
|
32
|
+
response["SERVER_PROTOCOL"].must_equal "HTTP/1.1"
|
33
|
+
response["SERVER_PORT"].must_equal @port.to_s
|
34
|
+
response["SERVER_NAME"].must_equal @host
|
47
35
|
end
|
48
36
|
|
49
|
-
|
37
|
+
it "have rack headers" do
|
50
38
|
GET("/test.fcgi")
|
51
|
-
response["rack.version"].
|
52
|
-
response["rack.multithread"]
|
53
|
-
response["rack.multiprocess"]
|
54
|
-
response["rack.run_once"]
|
39
|
+
response["rack.version"].must_equal [1,3]
|
40
|
+
assert_equal false, response["rack.multithread"]
|
41
|
+
assert_equal true, response["rack.multiprocess"]
|
42
|
+
assert_equal false, response["rack.run_once"]
|
55
43
|
end
|
56
44
|
|
57
|
-
|
45
|
+
it "have CGI headers on GET" do
|
58
46
|
GET("/test.fcgi")
|
59
|
-
response["REQUEST_METHOD"].
|
60
|
-
response["SCRIPT_NAME"].
|
61
|
-
response["REQUEST_PATH"].
|
62
|
-
response["PATH_INFO"].
|
63
|
-
response["QUERY_STRING"].
|
64
|
-
response["test.postdata"].
|
47
|
+
response["REQUEST_METHOD"].must_equal "GET"
|
48
|
+
response["SCRIPT_NAME"].must_equal "/test.fcgi"
|
49
|
+
response["REQUEST_PATH"].must_equal "/"
|
50
|
+
response["PATH_INFO"].must_equal ""
|
51
|
+
response["QUERY_STRING"].must_equal ""
|
52
|
+
response["test.postdata"].must_equal ""
|
65
53
|
|
66
54
|
GET("/test.fcgi/foo?quux=1")
|
67
|
-
response["REQUEST_METHOD"].
|
68
|
-
response["SCRIPT_NAME"].
|
69
|
-
response["REQUEST_PATH"].
|
70
|
-
response["PATH_INFO"].
|
71
|
-
response["QUERY_STRING"].
|
55
|
+
response["REQUEST_METHOD"].must_equal "GET"
|
56
|
+
response["SCRIPT_NAME"].must_equal "/test.fcgi"
|
57
|
+
response["REQUEST_PATH"].must_equal "/"
|
58
|
+
response["PATH_INFO"].must_equal "/foo"
|
59
|
+
response["QUERY_STRING"].must_equal "quux=1"
|
72
60
|
end
|
73
61
|
|
74
|
-
|
62
|
+
it "have CGI headers on POST" do
|
75
63
|
POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
|
76
|
-
status.
|
77
|
-
response["REQUEST_METHOD"].
|
78
|
-
response["SCRIPT_NAME"].
|
79
|
-
response["REQUEST_PATH"].
|
80
|
-
response["QUERY_STRING"].
|
81
|
-
response["HTTP_X_TEST_HEADER"].
|
82
|
-
response["test.postdata"].
|
64
|
+
status.must_equal 200
|
65
|
+
response["REQUEST_METHOD"].must_equal "POST"
|
66
|
+
response["SCRIPT_NAME"].must_equal "/test.fcgi"
|
67
|
+
response["REQUEST_PATH"].must_equal "/"
|
68
|
+
response["QUERY_STRING"].must_equal ""
|
69
|
+
response["HTTP_X_TEST_HEADER"].must_equal "42"
|
70
|
+
response["test.postdata"].must_equal "rack-form-data=23"
|
83
71
|
end
|
84
72
|
|
85
|
-
|
73
|
+
it "support HTTP auth" do
|
86
74
|
GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
|
87
|
-
response["HTTP_AUTHORIZATION"].
|
75
|
+
response["HTTP_AUTHORIZATION"].must_equal "Basic cnV0aDpzZWNyZXQ="
|
88
76
|
end
|
89
77
|
|
90
|
-
|
78
|
+
it "set status" do
|
91
79
|
GET("/test.fcgi?secret")
|
92
|
-
status.
|
93
|
-
response["rack.url_scheme"].
|
94
|
-
end
|
95
|
-
|
96
|
-
# Keep this last.
|
97
|
-
should "shutdown" do
|
98
|
-
Process.kill 15, $pid
|
99
|
-
Process.wait($pid).should.equal $pid
|
80
|
+
status.must_equal 403
|
81
|
+
response["rack.url_scheme"].must_equal "http"
|
100
82
|
end
|
101
83
|
end
|
102
84
|
|
103
|
-
|
104
|
-
$stderr.puts "Skipping Rack::Handler::FastCGI tests (lighttpd is required). Install lighttpd and try again."
|
105
|
-
rescue LoadError
|
106
|
-
$stderr.puts "Skipping Rack::Handler::FastCGI tests (FCGI is required). `gem install fcgi` and try again."
|
107
|
-
end
|
85
|
+
end # if defined? LIGHTTPD_PID
|
data/test/spec_file.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'minitest/autorun'
|
1
2
|
require 'rack/file'
|
2
3
|
require 'rack/lint'
|
3
4
|
require 'rack/mock'
|
@@ -9,213 +10,231 @@ describe Rack::File do
|
|
9
10
|
Rack::Lint.new Rack::File.new(*args)
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
|
+
it 'serves files with + in the file name' do
|
14
|
+
Dir.mktmpdir do |dir|
|
15
|
+
File.write File.join(dir, "you+me.txt"), "hello world"
|
16
|
+
app = file(dir)
|
17
|
+
env = Rack::MockRequest.env_for("/you+me.txt")
|
18
|
+
status,_,body = app.call env
|
19
|
+
|
20
|
+
assert_equal 200, status
|
21
|
+
|
22
|
+
str = ''
|
23
|
+
body.each { |x| str << x }
|
24
|
+
assert_match "hello world", str
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "serve files" do
|
13
29
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test")
|
14
30
|
|
15
|
-
res.
|
16
|
-
res
|
31
|
+
res.must_be :ok?
|
32
|
+
assert_match(res, /ruby/)
|
17
33
|
end
|
18
34
|
|
19
|
-
|
35
|
+
it "set Last-Modified header" do
|
20
36
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test")
|
21
37
|
|
22
38
|
path = File.join(DOCROOT, "/cgi/test")
|
23
39
|
|
24
|
-
res.
|
25
|
-
res["Last-Modified"].
|
40
|
+
res.must_be :ok?
|
41
|
+
res["Last-Modified"].must_equal File.mtime(path).httpdate
|
26
42
|
end
|
27
43
|
|
28
|
-
|
44
|
+
it "return 304 if file isn't modified since last serve" do
|
29
45
|
path = File.join(DOCROOT, "/cgi/test")
|
30
46
|
res = Rack::MockRequest.new(file(DOCROOT)).
|
31
47
|
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
|
32
48
|
|
33
|
-
res.status.
|
34
|
-
res.body.
|
49
|
+
res.status.must_equal 304
|
50
|
+
res.body.must_be :empty?
|
35
51
|
end
|
36
52
|
|
37
|
-
|
53
|
+
it "return the file if it's modified since last serve" do
|
38
54
|
path = File.join(DOCROOT, "/cgi/test")
|
39
55
|
res = Rack::MockRequest.new(file(DOCROOT)).
|
40
56
|
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
|
41
57
|
|
42
|
-
res.
|
58
|
+
res.must_be :ok?
|
43
59
|
end
|
44
60
|
|
45
|
-
|
61
|
+
it "serve files with URL encoded filenames" do
|
46
62
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%74%65%73%74") # "/cgi/test"
|
47
63
|
|
48
|
-
res.
|
49
|
-
res.
|
64
|
+
res.must_be :ok?
|
65
|
+
# res.must_match(/ruby/) # nope
|
66
|
+
# (/ruby/).must_match res # This is wierd, but an oddity of minitest
|
67
|
+
# assert_match(/ruby/, res) # nope
|
68
|
+
assert_match(res, /ruby/)
|
50
69
|
end
|
51
70
|
|
52
|
-
|
71
|
+
it "allow safe directory traversal" do
|
53
72
|
req = Rack::MockRequest.new(file(DOCROOT))
|
54
73
|
|
55
74
|
res = req.get('/cgi/../cgi/test')
|
56
|
-
res.
|
75
|
+
res.must_be :successful?
|
57
76
|
|
58
77
|
res = req.get('.')
|
59
|
-
res.
|
78
|
+
res.must_be :not_found?
|
60
79
|
|
61
80
|
res = req.get("test/..")
|
62
|
-
res.
|
81
|
+
res.must_be :not_found?
|
63
82
|
end
|
64
83
|
|
65
|
-
|
84
|
+
it "not allow unsafe directory traversal" do
|
66
85
|
req = Rack::MockRequest.new(file(DOCROOT))
|
67
86
|
|
68
87
|
res = req.get("/../README.rdoc")
|
69
|
-
res.
|
88
|
+
res.must_be :client_error?
|
70
89
|
|
71
90
|
res = req.get("../test/spec_file.rb")
|
72
|
-
res.
|
91
|
+
res.must_be :client_error?
|
73
92
|
|
74
93
|
res = req.get("../README.rdoc")
|
75
|
-
res.
|
94
|
+
res.must_be :client_error?
|
76
95
|
|
77
|
-
res.
|
96
|
+
res.must_be :not_found?
|
78
97
|
end
|
79
98
|
|
80
|
-
|
99
|
+
it "allow files with .. in their name" do
|
81
100
|
req = Rack::MockRequest.new(file(DOCROOT))
|
82
101
|
res = req.get("/cgi/..test")
|
83
|
-
res.
|
102
|
+
res.must_be :not_found?
|
84
103
|
|
85
104
|
res = req.get("/cgi/test..")
|
86
|
-
res.
|
105
|
+
res.must_be :not_found?
|
87
106
|
|
88
107
|
res = req.get("/cgi../test..")
|
89
|
-
res.
|
108
|
+
res.must_be :not_found?
|
90
109
|
end
|
91
110
|
|
92
|
-
|
111
|
+
it "not allow unsafe directory traversal with encoded periods" do
|
93
112
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/%2E%2E/README")
|
94
113
|
|
95
|
-
res.
|
96
|
-
res.
|
114
|
+
res.must_be :client_error?
|
115
|
+
res.must_be :not_found?
|
97
116
|
end
|
98
117
|
|
99
|
-
|
118
|
+
it "allow safe directory traversal with encoded periods" do
|
100
119
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%2E%2E/cgi/test")
|
101
120
|
|
102
|
-
res.
|
121
|
+
res.must_be :successful?
|
103
122
|
end
|
104
123
|
|
105
|
-
|
124
|
+
it "404 if it can't find the file" do
|
106
125
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/blubb")
|
107
126
|
|
108
|
-
res.
|
127
|
+
res.must_be :not_found?
|
109
128
|
end
|
110
129
|
|
111
|
-
|
130
|
+
it "detect SystemCallErrors" do
|
112
131
|
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi")
|
113
132
|
|
114
|
-
res.
|
133
|
+
res.must_be :not_found?
|
115
134
|
end
|
116
135
|
|
117
|
-
|
136
|
+
it "return bodies that respond to #to_path" do
|
118
137
|
env = Rack::MockRequest.env_for("/cgi/test")
|
119
138
|
status, _, body = Rack::File.new(DOCROOT).call(env)
|
120
139
|
|
121
140
|
path = File.join(DOCROOT, "/cgi/test")
|
122
141
|
|
123
|
-
status.
|
124
|
-
body.
|
125
|
-
body.to_path.
|
142
|
+
status.must_equal 200
|
143
|
+
body.must_respond_to :to_path
|
144
|
+
body.to_path.must_equal path
|
126
145
|
end
|
127
146
|
|
128
|
-
|
147
|
+
it "return correct byte range in body" do
|
129
148
|
env = Rack::MockRequest.env_for("/cgi/test")
|
130
149
|
env["HTTP_RANGE"] = "bytes=22-33"
|
131
150
|
res = Rack::MockResponse.new(*file(DOCROOT).call(env))
|
132
151
|
|
133
|
-
res.status.
|
134
|
-
res["Content-Length"].
|
135
|
-
res["Content-Range"].
|
136
|
-
res.body.
|
152
|
+
res.status.must_equal 206
|
153
|
+
res["Content-Length"].must_equal "12"
|
154
|
+
res["Content-Range"].must_equal "bytes 22-33/193"
|
155
|
+
res.body.must_equal "-*- ruby -*-"
|
137
156
|
end
|
138
157
|
|
139
|
-
|
158
|
+
it "return error for unsatisfiable byte range" do
|
140
159
|
env = Rack::MockRequest.env_for("/cgi/test")
|
141
160
|
env["HTTP_RANGE"] = "bytes=1234-5678"
|
142
161
|
res = Rack::MockResponse.new(*file(DOCROOT).call(env))
|
143
162
|
|
144
|
-
res.status.
|
145
|
-
res["Content-Range"].
|
163
|
+
res.status.must_equal 416
|
164
|
+
res["Content-Range"].must_equal "bytes */193"
|
146
165
|
end
|
147
166
|
|
148
|
-
|
167
|
+
it "support custom http headers" do
|
149
168
|
env = Rack::MockRequest.env_for("/cgi/test")
|
150
169
|
status, heads, _ = file(DOCROOT, 'Cache-Control' => 'public, max-age=38',
|
151
170
|
'Access-Control-Allow-Origin' => '*').call(env)
|
152
171
|
|
153
|
-
status.
|
154
|
-
heads['Cache-Control'].
|
155
|
-
heads['Access-Control-Allow-Origin'].
|
172
|
+
status.must_equal 200
|
173
|
+
heads['Cache-Control'].must_equal 'public, max-age=38'
|
174
|
+
heads['Access-Control-Allow-Origin'].must_equal '*'
|
156
175
|
end
|
157
176
|
|
158
|
-
|
177
|
+
it "support not add custom http headers if none are supplied" do
|
159
178
|
env = Rack::MockRequest.env_for("/cgi/test")
|
160
179
|
status, heads, _ = file(DOCROOT).call(env)
|
161
180
|
|
162
|
-
status.
|
163
|
-
heads['Cache-Control'].
|
164
|
-
heads['Access-Control-Allow-Origin'].
|
181
|
+
status.must_equal 200
|
182
|
+
heads['Cache-Control'].must_equal nil
|
183
|
+
heads['Access-Control-Allow-Origin'].must_equal nil
|
165
184
|
end
|
166
185
|
|
167
|
-
|
186
|
+
it "only support GET, HEAD, and OPTIONS requests" do
|
168
187
|
req = Rack::MockRequest.new(file(DOCROOT))
|
169
188
|
|
170
189
|
forbidden = %w[post put patch delete]
|
171
190
|
forbidden.each do |method|
|
172
191
|
res = req.send(method, "/cgi/test")
|
173
|
-
res.
|
174
|
-
res.
|
175
|
-
res.headers['Allow'].split(/, */).sort.
|
192
|
+
res.must_be :client_error?
|
193
|
+
res.must_be :method_not_allowed?
|
194
|
+
res.headers['Allow'].split(/, */).sort.must_equal %w(GET HEAD OPTIONS)
|
176
195
|
end
|
177
196
|
|
178
197
|
allowed = %w[get head options]
|
179
198
|
allowed.each do |method|
|
180
199
|
res = req.send(method, "/cgi/test")
|
181
|
-
res.
|
200
|
+
res.must_be :successful?
|
182
201
|
end
|
183
202
|
end
|
184
203
|
|
185
|
-
|
204
|
+
it "set Allow correctly for OPTIONS requests" do
|
186
205
|
req = Rack::MockRequest.new(file(DOCROOT))
|
187
206
|
res = req.options('/cgi/test')
|
188
|
-
res.
|
189
|
-
res.headers['Allow'].
|
190
|
-
res.headers['Allow'].split(/, */).sort.
|
207
|
+
res.must_be :successful?
|
208
|
+
res.headers['Allow'].wont_equal nil
|
209
|
+
res.headers['Allow'].split(/, */).sort.must_equal %w(GET HEAD OPTIONS)
|
191
210
|
end
|
192
211
|
|
193
|
-
|
212
|
+
it "set Content-Length correctly for HEAD requests" do
|
194
213
|
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
195
214
|
res = req.head "/cgi/test"
|
196
|
-
res.
|
197
|
-
res['Content-Length'].
|
215
|
+
res.must_be :successful?
|
216
|
+
res['Content-Length'].must_equal "193"
|
198
217
|
end
|
199
218
|
|
200
|
-
|
219
|
+
it "default to a mime type of text/plain" do
|
201
220
|
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
202
221
|
res = req.get "/cgi/test"
|
203
|
-
res.
|
204
|
-
res['Content-Type'].
|
222
|
+
res.must_be :successful?
|
223
|
+
res['Content-Type'].must_equal "text/plain"
|
205
224
|
end
|
206
225
|
|
207
|
-
|
226
|
+
it "allow the default mime type to be set" do
|
208
227
|
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, 'application/octet-stream')))
|
209
228
|
res = req.get "/cgi/test"
|
210
|
-
res.
|
211
|
-
res['Content-Type'].
|
229
|
+
res.must_be :successful?
|
230
|
+
res['Content-Type'].must_equal "application/octet-stream"
|
212
231
|
end
|
213
232
|
|
214
|
-
|
233
|
+
it "not set Content-Type if the mime type is not set" do
|
215
234
|
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, nil)))
|
216
235
|
res = req.get "/cgi/test"
|
217
|
-
res.
|
218
|
-
res['Content-Type'].
|
236
|
+
res.must_be :successful?
|
237
|
+
res['Content-Type'].must_equal nil
|
219
238
|
end
|
220
239
|
|
221
240
|
end
|