edgar-rack 1.2.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.
- data/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +401 -0
- data/Rakefile +101 -0
- data/SPEC +171 -0
- data/bin/rackup +4 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +81 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +43 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +53 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/builder.rb +80 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +52 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +63 -0
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +157 -0
- data/lib/rack/etag.rb +59 -0
- data/lib/rack/file.rb +118 -0
- data/lib/rack/handler.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +90 -0
- data/lib/rack/handler/lsws.rb +61 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +59 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +17 -0
- data/lib/rack/handler/webrick.rb +73 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +567 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +44 -0
- data/lib/rack/logger.rb +18 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +210 -0
- data/lib/rack/mock.rb +185 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +61 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +307 -0
- data/lib/rack/response.rb +151 -0
- data/lib/rack/rewindable_input.rb +104 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +139 -0
- data/lib/rack/server.rb +289 -0
- data/lib/rack/session/abstract/id.rb +348 -0
- data/lib/rack/session/cookie.rb +152 -0
- data/lib/rack/session/memcache.rb +93 -0
- data/lib/rack/session/pool.rb +79 -0
- data/lib/rack/showexceptions.rb +378 -0
- data/lib/rack/showstatus.rb +113 -0
- data/lib/rack/static.rb +53 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +698 -0
- data/rack.gemspec +39 -0
- data/test/cgi/lighttpd.conf +25 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +6 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/rackup/config.ru +31 -0
- data/test/spec_auth_basic.rb +70 -0
- data/test/spec_auth_digest.rb +241 -0
- data/test/spec_builder.rb +123 -0
- data/test/spec_cascade.rb +45 -0
- data/test/spec_cgi.rb +102 -0
- data/test/spec_chunked.rb +60 -0
- data/test/spec_commonlogger.rb +56 -0
- data/test/spec_conditionalget.rb +86 -0
- data/test/spec_config.rb +23 -0
- data/test/spec_content_length.rb +36 -0
- data/test/spec_content_type.rb +29 -0
- data/test/spec_deflater.rb +125 -0
- data/test/spec_directory.rb +57 -0
- data/test/spec_etag.rb +75 -0
- data/test/spec_fastcgi.rb +107 -0
- data/test/spec_file.rb +92 -0
- data/test/spec_handler.rb +49 -0
- data/test/spec_head.rb +30 -0
- data/test/spec_lint.rb +515 -0
- data/test/spec_lobster.rb +43 -0
- data/test/spec_lock.rb +142 -0
- data/test/spec_logger.rb +28 -0
- data/test/spec_methodoverride.rb +58 -0
- data/test/spec_mock.rb +241 -0
- data/test/spec_mongrel.rb +182 -0
- data/test/spec_nulllogger.rb +12 -0
- data/test/spec_recursive.rb +69 -0
- data/test/spec_request.rb +774 -0
- data/test/spec_response.rb +245 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +39 -0
- data/test/spec_sendfile.rb +83 -0
- data/test/spec_server.rb +8 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +171 -0
- data/test/spec_session_memcache.rb +289 -0
- data/test/spec_session_pool.rb +200 -0
- data/test/spec_showexceptions.rb +87 -0
- data/test/spec_showstatus.rb +79 -0
- data/test/spec_static.rb +48 -0
- data/test/spec_thin.rb +86 -0
- data/test/spec_urlmap.rb +213 -0
- data/test/spec_utils.rb +678 -0
- data/test/spec_webrick.rb +141 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +329 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'rack/directory'
|
|
2
|
+
require 'rack/mock'
|
|
3
|
+
|
|
4
|
+
describe Rack::Directory do
|
|
5
|
+
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
|
6
|
+
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
|
|
7
|
+
app = Rack::Directory.new DOCROOT, FILE_CATCH
|
|
8
|
+
|
|
9
|
+
should "serve directory indices" do
|
|
10
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
11
|
+
get("/cgi/")
|
|
12
|
+
|
|
13
|
+
res.should.be.ok
|
|
14
|
+
res.should =~ /<html><head>/
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
should "pass to app if file found" do
|
|
18
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
19
|
+
get("/cgi/test")
|
|
20
|
+
|
|
21
|
+
res.should.be.ok
|
|
22
|
+
res.should =~ /passed!/
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
should "serve uri with URL encoded filenames" do
|
|
26
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
27
|
+
get("/%63%67%69/") # "/cgi/test"
|
|
28
|
+
|
|
29
|
+
res.should.be.ok
|
|
30
|
+
res.should =~ /<html><head>/
|
|
31
|
+
|
|
32
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
33
|
+
get("/cgi/%74%65%73%74") # "/cgi/test"
|
|
34
|
+
|
|
35
|
+
res.should.be.ok
|
|
36
|
+
res.should =~ /passed!/
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
should "not allow directory traversal" do
|
|
40
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
41
|
+
get("/cgi/../test")
|
|
42
|
+
|
|
43
|
+
res.should.be.forbidden
|
|
44
|
+
|
|
45
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
46
|
+
get("/cgi/%2E%2E/test")
|
|
47
|
+
|
|
48
|
+
res.should.be.forbidden
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
should "404 if it can't find the file" do
|
|
52
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
|
53
|
+
get("/cgi/blubb")
|
|
54
|
+
|
|
55
|
+
res.should.be.not_found
|
|
56
|
+
end
|
|
57
|
+
end
|
data/test/spec_etag.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'rack/etag'
|
|
2
|
+
|
|
3
|
+
describe Rack::ETag do
|
|
4
|
+
def sendfile_body
|
|
5
|
+
res = ['Hello World']
|
|
6
|
+
def res.to_path ; "/tmp/hello.txt" ; end
|
|
7
|
+
res
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
should "set ETag if none is set if status is 200" do
|
|
11
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
|
12
|
+
response = Rack::ETag.new(app).call({})
|
|
13
|
+
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
should "set ETag if none is set if status is 201" do
|
|
17
|
+
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
|
18
|
+
response = Rack::ETag.new(app).call({})
|
|
19
|
+
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
|
|
23
|
+
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
|
24
|
+
response = Rack::ETag.new(app).call({})
|
|
25
|
+
response[1]['Cache-Control'].should.equal 'max-age=0, private, must-revalidate'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
should "set Cache-Control to chosen one if none is set" do
|
|
29
|
+
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
|
30
|
+
response = Rack::ETag.new(app, nil, 'public').call({})
|
|
31
|
+
response[1]['Cache-Control'].should.equal 'public'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
should "set a given Cache-Control even if digest could not be calculated" do
|
|
35
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, []] }
|
|
36
|
+
response = Rack::ETag.new(app, 'no-cache').call({})
|
|
37
|
+
response[1]['Cache-Control'].should.equal 'no-cache'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
should "not set Cache-Control if it is already set" do
|
|
41
|
+
app = lambda { |env| [201, {'Content-Type' => 'text/plain', 'Cache-Control' => 'public'}, ["Hello, World!"]] }
|
|
42
|
+
response = Rack::ETag.new(app).call({})
|
|
43
|
+
response[1]['Cache-Control'].should.equal 'public'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
should "not change ETag if it is already set" do
|
|
47
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
|
|
48
|
+
response = Rack::ETag.new(app).call({})
|
|
49
|
+
response[1]['ETag'].should.equal "\"abc\""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
should "not set ETag if body is empty" do
|
|
53
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, []] }
|
|
54
|
+
response = Rack::ETag.new(app).call({})
|
|
55
|
+
response[1]['ETag'].should.be.nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
should "not set ETag if Last-Modified is set" do
|
|
59
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, ["Hello, World!"]] }
|
|
60
|
+
response = Rack::ETag.new(app).call({})
|
|
61
|
+
response[1]['ETag'].should.be.nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
should "not set ETag if a sendfile_body is given" do
|
|
65
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, sendfile_body] }
|
|
66
|
+
response = Rack::ETag.new(app).call({})
|
|
67
|
+
response[1]['ETag'].should.be.nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
should "not set ETag if a status is not 200 or 201" do
|
|
71
|
+
app = lambda { |env| [401, {'Content-Type' => 'text/plain'}, ['Access denied.']] }
|
|
72
|
+
response = Rack::ETag.new(app).call({})
|
|
73
|
+
response[1]['ETag'].should.be.nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require File.expand_path('../testrequest', __FILE__)
|
|
3
|
+
require 'rack/handler/fastcgi'
|
|
4
|
+
|
|
5
|
+
describe Rack::Handler::FastCGI do
|
|
6
|
+
extend TestRequest::Helpers
|
|
7
|
+
|
|
8
|
+
@host = '0.0.0.0'
|
|
9
|
+
@port = 9203
|
|
10
|
+
|
|
11
|
+
if `which lighttpd` && !$?.success?
|
|
12
|
+
raise "lighttpd not found"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Keep this first.
|
|
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
|
|
29
|
+
sleep 1
|
|
30
|
+
GET("/test")
|
|
31
|
+
response.should.not.be.nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
should "respond via rackup server" do
|
|
35
|
+
GET("/sample_rackup.ru")
|
|
36
|
+
status.should.equal 200
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
should "be a lighttpd" do
|
|
40
|
+
GET("/test.fcgi")
|
|
41
|
+
status.should.equal 200
|
|
42
|
+
response["SERVER_SOFTWARE"].should =~ /lighttpd/
|
|
43
|
+
response["HTTP_VERSION"].should.equal "HTTP/1.1"
|
|
44
|
+
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
|
|
45
|
+
response["SERVER_PORT"].should.equal @port.to_s
|
|
46
|
+
response["SERVER_NAME"].should.equal @host
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
should "have rack headers" do
|
|
50
|
+
GET("/test.fcgi")
|
|
51
|
+
response["rack.version"].should.equal [1,1]
|
|
52
|
+
response["rack.multithread"].should.be.false
|
|
53
|
+
response["rack.multiprocess"].should.be.true
|
|
54
|
+
response["rack.run_once"].should.be.false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
should "have CGI headers on GET" do
|
|
58
|
+
GET("/test.fcgi")
|
|
59
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
|
60
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
|
61
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
62
|
+
response["PATH_INFO"].should.equal ""
|
|
63
|
+
response["QUERY_STRING"].should.equal ""
|
|
64
|
+
response["test.postdata"].should.equal ""
|
|
65
|
+
|
|
66
|
+
GET("/test.fcgi/foo?quux=1")
|
|
67
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
|
68
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
|
69
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
70
|
+
response["PATH_INFO"].should.equal "/foo"
|
|
71
|
+
response["QUERY_STRING"].should.equal "quux=1"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
should "have CGI headers on POST" do
|
|
75
|
+
POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
|
|
76
|
+
status.should.equal 200
|
|
77
|
+
response["REQUEST_METHOD"].should.equal "POST"
|
|
78
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
|
79
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
80
|
+
response["QUERY_STRING"].should.equal ""
|
|
81
|
+
response["HTTP_X_TEST_HEADER"].should.equal "42"
|
|
82
|
+
response["test.postdata"].should.equal "rack-form-data=23"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
should "support HTTP auth" do
|
|
86
|
+
GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
|
|
87
|
+
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
should "set status" do
|
|
91
|
+
GET("/test.fcgi?secret")
|
|
92
|
+
status.should.equal 403
|
|
93
|
+
response["rack.url_scheme"].should.equal "http"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Keep this last.
|
|
97
|
+
should "shutdown" do
|
|
98
|
+
Process.kill 15, $pid
|
|
99
|
+
Process.wait($pid).should.equal $pid
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
rescue RuntimeError
|
|
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
|
data/test/spec_file.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'rack/file'
|
|
2
|
+
require 'rack/mock'
|
|
3
|
+
|
|
4
|
+
describe Rack::File do
|
|
5
|
+
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
|
6
|
+
|
|
7
|
+
should "serve files" do
|
|
8
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
9
|
+
get("/cgi/test")
|
|
10
|
+
|
|
11
|
+
res.should.be.ok
|
|
12
|
+
res.should =~ /ruby/
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
should "set Last-Modified header" do
|
|
16
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
17
|
+
get("/cgi/test")
|
|
18
|
+
|
|
19
|
+
path = File.join(DOCROOT, "/cgi/test")
|
|
20
|
+
|
|
21
|
+
res.should.be.ok
|
|
22
|
+
res["Last-Modified"].should.equal File.mtime(path).httpdate
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
should "serve files with URL encoded filenames" do
|
|
26
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
27
|
+
get("/cgi/%74%65%73%74") # "/cgi/test"
|
|
28
|
+
|
|
29
|
+
res.should.be.ok
|
|
30
|
+
res.should =~ /ruby/
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
should "not allow directory traversal" do
|
|
34
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
35
|
+
get("/cgi/../test")
|
|
36
|
+
|
|
37
|
+
res.should.be.forbidden
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
should "not allow directory traversal with encoded periods" do
|
|
41
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
42
|
+
get("/%2E%2E/README")
|
|
43
|
+
|
|
44
|
+
res.should.be.forbidden
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should "404 if it can't find the file" do
|
|
48
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
49
|
+
get("/cgi/blubb")
|
|
50
|
+
|
|
51
|
+
res.should.be.not_found
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
should "detect SystemCallErrors" do
|
|
55
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
|
56
|
+
get("/cgi")
|
|
57
|
+
|
|
58
|
+
res.should.be.not_found
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
should "return bodies that respond to #to_path" do
|
|
62
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
|
63
|
+
status, _, body = Rack::File.new(DOCROOT).call(env)
|
|
64
|
+
|
|
65
|
+
path = File.join(DOCROOT, "/cgi/test")
|
|
66
|
+
|
|
67
|
+
status.should.equal 200
|
|
68
|
+
body.should.respond_to :to_path
|
|
69
|
+
body.to_path.should.equal path
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
should "return correct byte range in body" do
|
|
73
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
|
74
|
+
env["HTTP_RANGE"] = "bytes=22-33"
|
|
75
|
+
res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
|
|
76
|
+
|
|
77
|
+
res.status.should.equal 206
|
|
78
|
+
res["Content-Length"].should.equal "12"
|
|
79
|
+
res["Content-Range"].should.equal "bytes 22-33/193"
|
|
80
|
+
res.body.should.equal "-*- ruby -*-"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
should "return error for unsatisfiable byte range" do
|
|
84
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
|
85
|
+
env["HTTP_RANGE"] = "bytes=1234-5678"
|
|
86
|
+
res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
|
|
87
|
+
|
|
88
|
+
res.status.should.equal 416
|
|
89
|
+
res["Content-Range"].should.equal "bytes */193"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'rack/handler'
|
|
2
|
+
|
|
3
|
+
class Rack::Handler::Lobster; end
|
|
4
|
+
class RockLobster; end
|
|
5
|
+
|
|
6
|
+
describe Rack::Handler do
|
|
7
|
+
it "has registered default handlers" do
|
|
8
|
+
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
|
|
9
|
+
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
|
|
13
|
+
rescue LoadError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
|
|
18
|
+
rescue LoadError
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
should "raise NameError if handler doesn't exist" do
|
|
23
|
+
lambda {
|
|
24
|
+
Rack::Handler.get('boom')
|
|
25
|
+
}.should.raise(NameError)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
should "get unregistered, but already required, handler by name" do
|
|
29
|
+
Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
should "register custom handler" do
|
|
33
|
+
Rack::Handler.register('rock_lobster', 'RockLobster')
|
|
34
|
+
Rack::Handler.get('rock_lobster').should.equal RockLobster
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
should "not need registration for properly coded handlers even if not already required" do
|
|
38
|
+
begin
|
|
39
|
+
$LOAD_PATH.push File.expand_path('../unregistered_handler', __FILE__)
|
|
40
|
+
Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
|
|
41
|
+
lambda {
|
|
42
|
+
Rack::Handler.get('UnRegistered')
|
|
43
|
+
}.should.raise(NameError)
|
|
44
|
+
Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
|
|
45
|
+
ensure
|
|
46
|
+
$LOAD_PATH.delete File.expand_path('../unregistered_handler', __FILE__)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/test/spec_head.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'rack/head'
|
|
2
|
+
require 'rack/mock'
|
|
3
|
+
|
|
4
|
+
describe Rack::Head do
|
|
5
|
+
def test_response(headers = {})
|
|
6
|
+
app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
|
|
7
|
+
request = Rack::MockRequest.env_for("/", headers)
|
|
8
|
+
response = Rack::Head.new(app).call(request)
|
|
9
|
+
|
|
10
|
+
return response
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
should "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
|
|
14
|
+
%w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
|
|
15
|
+
resp = test_response("REQUEST_METHOD" => type)
|
|
16
|
+
|
|
17
|
+
resp[0].should.equal(200)
|
|
18
|
+
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
|
19
|
+
resp[2].should.equal(["foo"])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
should "remove body from HEAD requests" do
|
|
24
|
+
resp = test_response("REQUEST_METHOD" => "HEAD")
|
|
25
|
+
|
|
26
|
+
resp[0].should.equal(200)
|
|
27
|
+
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
|
28
|
+
resp[2].should.equal([])
|
|
29
|
+
end
|
|
30
|
+
end
|
data/test/spec_lint.rb
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
require 'rack/lint'
|
|
3
|
+
require 'rack/mock'
|
|
4
|
+
|
|
5
|
+
describe Rack::Lint do
|
|
6
|
+
def env(*args)
|
|
7
|
+
Rack::MockRequest.env_for("/", *args)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
should "pass valid request" do
|
|
11
|
+
lambda {
|
|
12
|
+
Rack::Lint.new(lambda { |env|
|
|
13
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
|
14
|
+
}).call(env({}))
|
|
15
|
+
}.should.not.raise
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
should "notice fatal errors" do
|
|
19
|
+
lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
|
|
20
|
+
message.should.match(/No env given/)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
should "notice environment errors" do
|
|
24
|
+
lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
|
|
25
|
+
message.should.match(/not a Hash/)
|
|
26
|
+
|
|
27
|
+
lambda {
|
|
28
|
+
e = env
|
|
29
|
+
e.delete("REQUEST_METHOD")
|
|
30
|
+
Rack::Lint.new(nil).call(e)
|
|
31
|
+
}.should.raise(Rack::Lint::LintError).
|
|
32
|
+
message.should.match(/missing required key REQUEST_METHOD/)
|
|
33
|
+
|
|
34
|
+
lambda {
|
|
35
|
+
e = env
|
|
36
|
+
e.delete("SERVER_NAME")
|
|
37
|
+
Rack::Lint.new(nil).call(e)
|
|
38
|
+
}.should.raise(Rack::Lint::LintError).
|
|
39
|
+
message.should.match(/missing required key SERVER_NAME/)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
lambda {
|
|
43
|
+
Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
|
|
44
|
+
}.should.raise(Rack::Lint::LintError).
|
|
45
|
+
message.should.match(/contains HTTP_CONTENT_TYPE/)
|
|
46
|
+
|
|
47
|
+
lambda {
|
|
48
|
+
Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
|
|
49
|
+
}.should.raise(Rack::Lint::LintError).
|
|
50
|
+
message.should.match(/contains HTTP_CONTENT_LENGTH/)
|
|
51
|
+
|
|
52
|
+
lambda {
|
|
53
|
+
Rack::Lint.new(nil).call(env("FOO" => Object.new))
|
|
54
|
+
}.should.raise(Rack::Lint::LintError).
|
|
55
|
+
message.should.match(/non-string value/)
|
|
56
|
+
|
|
57
|
+
lambda {
|
|
58
|
+
Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
|
|
59
|
+
}.should.raise(Rack::Lint::LintError).
|
|
60
|
+
message.should.match(/must be an Array/)
|
|
61
|
+
|
|
62
|
+
lambda {
|
|
63
|
+
Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
|
|
64
|
+
}.should.raise(Rack::Lint::LintError).
|
|
65
|
+
message.should.match(/url_scheme unknown/)
|
|
66
|
+
|
|
67
|
+
lambda {
|
|
68
|
+
Rack::Lint.new(nil).call(env("rack.session" => []))
|
|
69
|
+
}.should.raise(Rack::Lint::LintError).
|
|
70
|
+
message.should.equal("session [] must respond to store and []=")
|
|
71
|
+
|
|
72
|
+
lambda {
|
|
73
|
+
Rack::Lint.new(nil).call(env("rack.logger" => []))
|
|
74
|
+
}.should.raise(Rack::Lint::LintError).
|
|
75
|
+
message.should.equal("logger [] must respond to info")
|
|
76
|
+
|
|
77
|
+
lambda {
|
|
78
|
+
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
|
|
79
|
+
}.should.raise(Rack::Lint::LintError).
|
|
80
|
+
message.should.match(/REQUEST_METHOD/)
|
|
81
|
+
|
|
82
|
+
lambda {
|
|
83
|
+
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
|
|
84
|
+
}.should.raise(Rack::Lint::LintError).
|
|
85
|
+
message.should.match(/must start with/)
|
|
86
|
+
|
|
87
|
+
lambda {
|
|
88
|
+
Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
|
|
89
|
+
}.should.raise(Rack::Lint::LintError).
|
|
90
|
+
message.should.match(/must start with/)
|
|
91
|
+
|
|
92
|
+
lambda {
|
|
93
|
+
Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
|
|
94
|
+
}.should.raise(Rack::Lint::LintError).
|
|
95
|
+
message.should.match(/Invalid CONTENT_LENGTH/)
|
|
96
|
+
|
|
97
|
+
lambda {
|
|
98
|
+
e = env
|
|
99
|
+
e.delete("PATH_INFO")
|
|
100
|
+
e.delete("SCRIPT_NAME")
|
|
101
|
+
Rack::Lint.new(nil).call(e)
|
|
102
|
+
}.should.raise(Rack::Lint::LintError).
|
|
103
|
+
message.should.match(/One of .* must be set/)
|
|
104
|
+
|
|
105
|
+
lambda {
|
|
106
|
+
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
|
|
107
|
+
}.should.raise(Rack::Lint::LintError).
|
|
108
|
+
message.should.match(/cannot be .* make it ''/)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
should "notice input errors" do
|
|
112
|
+
lambda {
|
|
113
|
+
Rack::Lint.new(nil).call(env("rack.input" => ""))
|
|
114
|
+
}.should.raise(Rack::Lint::LintError).
|
|
115
|
+
message.should.match(/does not respond to #gets/)
|
|
116
|
+
|
|
117
|
+
lambda {
|
|
118
|
+
input = Object.new
|
|
119
|
+
def input.binmode?
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
Rack::Lint.new(nil).call(env("rack.input" => input))
|
|
123
|
+
}.should.raise(Rack::Lint::LintError).
|
|
124
|
+
message.should.match(/is not opened in binary mode/)
|
|
125
|
+
|
|
126
|
+
lambda {
|
|
127
|
+
input = Object.new
|
|
128
|
+
def input.external_encoding
|
|
129
|
+
result = Object.new
|
|
130
|
+
def result.name
|
|
131
|
+
"US-ASCII"
|
|
132
|
+
end
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
Rack::Lint.new(nil).call(env("rack.input" => input))
|
|
136
|
+
}.should.raise(Rack::Lint::LintError).
|
|
137
|
+
message.should.match(/does not have ASCII-8BIT as its external encoding/)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
should "notice error errors" do
|
|
141
|
+
lambda {
|
|
142
|
+
Rack::Lint.new(nil).call(env("rack.errors" => ""))
|
|
143
|
+
}.should.raise(Rack::Lint::LintError).
|
|
144
|
+
message.should.match(/does not respond to #puts/)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
should "notice status errors" do
|
|
148
|
+
lambda {
|
|
149
|
+
Rack::Lint.new(lambda { |env|
|
|
150
|
+
["cc", {}, ""]
|
|
151
|
+
}).call(env({}))
|
|
152
|
+
}.should.raise(Rack::Lint::LintError).
|
|
153
|
+
message.should.match(/must be >=100 seen as integer/)
|
|
154
|
+
|
|
155
|
+
lambda {
|
|
156
|
+
Rack::Lint.new(lambda { |env|
|
|
157
|
+
[42, {}, ""]
|
|
158
|
+
}).call(env({}))
|
|
159
|
+
}.should.raise(Rack::Lint::LintError).
|
|
160
|
+
message.should.match(/must be >=100 seen as integer/)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
should "notice header errors" do
|
|
164
|
+
lambda {
|
|
165
|
+
Rack::Lint.new(lambda { |env|
|
|
166
|
+
[200, Object.new, []]
|
|
167
|
+
}).call(env({}))
|
|
168
|
+
}.should.raise(Rack::Lint::LintError).
|
|
169
|
+
message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
|
|
170
|
+
|
|
171
|
+
lambda {
|
|
172
|
+
Rack::Lint.new(lambda { |env|
|
|
173
|
+
[200, {true=>false}, []]
|
|
174
|
+
}).call(env({}))
|
|
175
|
+
}.should.raise(Rack::Lint::LintError).
|
|
176
|
+
message.should.equal("header key must be a string, was TrueClass")
|
|
177
|
+
|
|
178
|
+
lambda {
|
|
179
|
+
Rack::Lint.new(lambda { |env|
|
|
180
|
+
[200, {"Status" => "404"}, []]
|
|
181
|
+
}).call(env({}))
|
|
182
|
+
}.should.raise(Rack::Lint::LintError).
|
|
183
|
+
message.should.match(/must not contain Status/)
|
|
184
|
+
|
|
185
|
+
lambda {
|
|
186
|
+
Rack::Lint.new(lambda { |env|
|
|
187
|
+
[200, {"Content-Type:" => "text/plain"}, []]
|
|
188
|
+
}).call(env({}))
|
|
189
|
+
}.should.raise(Rack::Lint::LintError).
|
|
190
|
+
message.should.match(/must not contain :/)
|
|
191
|
+
|
|
192
|
+
lambda {
|
|
193
|
+
Rack::Lint.new(lambda { |env|
|
|
194
|
+
[200, {"Content-" => "text/plain"}, []]
|
|
195
|
+
}).call(env({}))
|
|
196
|
+
}.should.raise(Rack::Lint::LintError).
|
|
197
|
+
message.should.match(/must not end/)
|
|
198
|
+
|
|
199
|
+
lambda {
|
|
200
|
+
Rack::Lint.new(lambda { |env|
|
|
201
|
+
[200, {"..%%quark%%.." => "text/plain"}, []]
|
|
202
|
+
}).call(env({}))
|
|
203
|
+
}.should.raise(Rack::Lint::LintError).
|
|
204
|
+
message.should.equal("invalid header name: ..%%quark%%..")
|
|
205
|
+
|
|
206
|
+
lambda {
|
|
207
|
+
Rack::Lint.new(lambda { |env|
|
|
208
|
+
[200, {"Foo" => Object.new}, []]
|
|
209
|
+
}).call(env({}))
|
|
210
|
+
}.should.raise(Rack::Lint::LintError).
|
|
211
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
|
|
212
|
+
|
|
213
|
+
lambda {
|
|
214
|
+
Rack::Lint.new(lambda { |env|
|
|
215
|
+
[200, {"Foo" => [1, 2, 3]}, []]
|
|
216
|
+
}).call(env({}))
|
|
217
|
+
}.should.raise(Rack::Lint::LintError).
|
|
218
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
lambda {
|
|
222
|
+
Rack::Lint.new(lambda { |env|
|
|
223
|
+
[200, {"Foo-Bar" => "text\000plain"}, []]
|
|
224
|
+
}).call(env({}))
|
|
225
|
+
}.should.raise(Rack::Lint::LintError).
|
|
226
|
+
message.should.match(/invalid header/)
|
|
227
|
+
|
|
228
|
+
# line ends (010) should be allowed in header values.
|
|
229
|
+
lambda {
|
|
230
|
+
Rack::Lint.new(lambda { |env|
|
|
231
|
+
[200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
|
|
232
|
+
}).call(env({}))
|
|
233
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
should "notice content-type errors" do
|
|
237
|
+
lambda {
|
|
238
|
+
Rack::Lint.new(lambda { |env|
|
|
239
|
+
[200, {"Content-length" => "0"}, []]
|
|
240
|
+
}).call(env({}))
|
|
241
|
+
}.should.raise(Rack::Lint::LintError).
|
|
242
|
+
message.should.match(/No Content-Type/)
|
|
243
|
+
|
|
244
|
+
[100, 101, 204, 304].each do |status|
|
|
245
|
+
lambda {
|
|
246
|
+
Rack::Lint.new(lambda { |env|
|
|
247
|
+
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
248
|
+
}).call(env({}))
|
|
249
|
+
}.should.raise(Rack::Lint::LintError).
|
|
250
|
+
message.should.match(/Content-Type header found/)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
should "notice content-length errors" do
|
|
255
|
+
[100, 101, 204, 304].each do |status|
|
|
256
|
+
lambda {
|
|
257
|
+
Rack::Lint.new(lambda { |env|
|
|
258
|
+
[status, {"Content-length" => "0"}, []]
|
|
259
|
+
}).call(env({}))
|
|
260
|
+
}.should.raise(Rack::Lint::LintError).
|
|
261
|
+
message.should.match(/Content-Length header found/)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
lambda {
|
|
265
|
+
Rack::Lint.new(lambda { |env|
|
|
266
|
+
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
|
|
267
|
+
}).call(env({}))[2].each { }
|
|
268
|
+
}.should.raise(Rack::Lint::LintError).
|
|
269
|
+
message.should.match(/Content-Length header was 1, but should be 0/)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
should "notice body errors" do
|
|
273
|
+
lambda {
|
|
274
|
+
body = Rack::Lint.new(lambda { |env|
|
|
275
|
+
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
|
|
276
|
+
}).call(env({}))[2]
|
|
277
|
+
body.each { |part| }
|
|
278
|
+
}.should.raise(Rack::Lint::LintError).
|
|
279
|
+
message.should.match(/yielded non-string/)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
should "notice input handling errors" do
|
|
283
|
+
lambda {
|
|
284
|
+
Rack::Lint.new(lambda { |env|
|
|
285
|
+
env["rack.input"].gets("\r\n")
|
|
286
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
287
|
+
}).call(env({}))
|
|
288
|
+
}.should.raise(Rack::Lint::LintError).
|
|
289
|
+
message.should.match(/gets called with arguments/)
|
|
290
|
+
|
|
291
|
+
lambda {
|
|
292
|
+
Rack::Lint.new(lambda { |env|
|
|
293
|
+
env["rack.input"].read(1, 2, 3)
|
|
294
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
295
|
+
}).call(env({}))
|
|
296
|
+
}.should.raise(Rack::Lint::LintError).
|
|
297
|
+
message.should.match(/read called with too many arguments/)
|
|
298
|
+
|
|
299
|
+
lambda {
|
|
300
|
+
Rack::Lint.new(lambda { |env|
|
|
301
|
+
env["rack.input"].read("foo")
|
|
302
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
303
|
+
}).call(env({}))
|
|
304
|
+
}.should.raise(Rack::Lint::LintError).
|
|
305
|
+
message.should.match(/read called with non-integer and non-nil length/)
|
|
306
|
+
|
|
307
|
+
lambda {
|
|
308
|
+
Rack::Lint.new(lambda { |env|
|
|
309
|
+
env["rack.input"].read(-1)
|
|
310
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
311
|
+
}).call(env({}))
|
|
312
|
+
}.should.raise(Rack::Lint::LintError).
|
|
313
|
+
message.should.match(/read called with a negative length/)
|
|
314
|
+
|
|
315
|
+
lambda {
|
|
316
|
+
Rack::Lint.new(lambda { |env|
|
|
317
|
+
env["rack.input"].read(nil, nil)
|
|
318
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
319
|
+
}).call(env({}))
|
|
320
|
+
}.should.raise(Rack::Lint::LintError).
|
|
321
|
+
message.should.match(/read called with non-String buffer/)
|
|
322
|
+
|
|
323
|
+
lambda {
|
|
324
|
+
Rack::Lint.new(lambda { |env|
|
|
325
|
+
env["rack.input"].read(nil, 1)
|
|
326
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
327
|
+
}).call(env({}))
|
|
328
|
+
}.should.raise(Rack::Lint::LintError).
|
|
329
|
+
message.should.match(/read called with non-String buffer/)
|
|
330
|
+
|
|
331
|
+
lambda {
|
|
332
|
+
Rack::Lint.new(lambda { |env|
|
|
333
|
+
env["rack.input"].rewind(0)
|
|
334
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
335
|
+
}).call(env({}))
|
|
336
|
+
}.should.raise(Rack::Lint::LintError).
|
|
337
|
+
message.should.match(/rewind called with arguments/)
|
|
338
|
+
|
|
339
|
+
weirdio = Object.new
|
|
340
|
+
class << weirdio
|
|
341
|
+
def gets
|
|
342
|
+
42
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def read
|
|
346
|
+
23
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def each
|
|
350
|
+
yield 23
|
|
351
|
+
yield 42
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def rewind
|
|
355
|
+
raise Errno::ESPIPE, "Errno::ESPIPE"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
eof_weirdio = Object.new
|
|
360
|
+
class << eof_weirdio
|
|
361
|
+
def gets
|
|
362
|
+
nil
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def read(*args)
|
|
366
|
+
nil
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def each
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def rewind
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
lambda {
|
|
377
|
+
Rack::Lint.new(lambda { |env|
|
|
378
|
+
env["rack.input"].gets
|
|
379
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
380
|
+
}).call(env("rack.input" => weirdio))
|
|
381
|
+
}.should.raise(Rack::Lint::LintError).
|
|
382
|
+
message.should.match(/gets didn't return a String/)
|
|
383
|
+
|
|
384
|
+
lambda {
|
|
385
|
+
Rack::Lint.new(lambda { |env|
|
|
386
|
+
env["rack.input"].each { |x| }
|
|
387
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
388
|
+
}).call(env("rack.input" => weirdio))
|
|
389
|
+
}.should.raise(Rack::Lint::LintError).
|
|
390
|
+
message.should.match(/each didn't yield a String/)
|
|
391
|
+
|
|
392
|
+
lambda {
|
|
393
|
+
Rack::Lint.new(lambda { |env|
|
|
394
|
+
env["rack.input"].read
|
|
395
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
396
|
+
}).call(env("rack.input" => weirdio))
|
|
397
|
+
}.should.raise(Rack::Lint::LintError).
|
|
398
|
+
message.should.match(/read didn't return nil or a String/)
|
|
399
|
+
|
|
400
|
+
lambda {
|
|
401
|
+
Rack::Lint.new(lambda { |env|
|
|
402
|
+
env["rack.input"].read
|
|
403
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
404
|
+
}).call(env("rack.input" => eof_weirdio))
|
|
405
|
+
}.should.raise(Rack::Lint::LintError).
|
|
406
|
+
message.should.match(/read\(nil\) returned nil on EOF/)
|
|
407
|
+
|
|
408
|
+
lambda {
|
|
409
|
+
Rack::Lint.new(lambda { |env|
|
|
410
|
+
env["rack.input"].rewind
|
|
411
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
412
|
+
}).call(env("rack.input" => weirdio))
|
|
413
|
+
}.should.raise(Rack::Lint::LintError).
|
|
414
|
+
message.should.match(/rewind raised Errno::ESPIPE/)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
lambda {
|
|
418
|
+
Rack::Lint.new(lambda { |env|
|
|
419
|
+
env["rack.input"].close
|
|
420
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
421
|
+
}).call(env({}))
|
|
422
|
+
}.should.raise(Rack::Lint::LintError).
|
|
423
|
+
message.should.match(/close must not be called/)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
should "notice error handling errors" do
|
|
427
|
+
lambda {
|
|
428
|
+
Rack::Lint.new(lambda { |env|
|
|
429
|
+
env["rack.errors"].write(42)
|
|
430
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
431
|
+
}).call(env({}))
|
|
432
|
+
}.should.raise(Rack::Lint::LintError).
|
|
433
|
+
message.should.match(/write not called with a String/)
|
|
434
|
+
|
|
435
|
+
lambda {
|
|
436
|
+
Rack::Lint.new(lambda { |env|
|
|
437
|
+
env["rack.errors"].close
|
|
438
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
439
|
+
}).call(env({}))
|
|
440
|
+
}.should.raise(Rack::Lint::LintError).
|
|
441
|
+
message.should.match(/close must not be called/)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
should "notice HEAD errors" do
|
|
445
|
+
lambda {
|
|
446
|
+
Rack::Lint.new(lambda { |env|
|
|
447
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
|
|
448
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))
|
|
449
|
+
}.should.not.raise
|
|
450
|
+
|
|
451
|
+
lambda {
|
|
452
|
+
Rack::Lint.new(lambda { |env|
|
|
453
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
|
454
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
|
|
455
|
+
}.should.raise(Rack::Lint::LintError).
|
|
456
|
+
message.should.match(/body was given for HEAD/)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
should "pass valid read calls" do
|
|
460
|
+
hello_str = "hello world"
|
|
461
|
+
hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
|
|
462
|
+
lambda {
|
|
463
|
+
Rack::Lint.new(lambda { |env|
|
|
464
|
+
env["rack.input"].read
|
|
465
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
466
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
467
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
468
|
+
|
|
469
|
+
lambda {
|
|
470
|
+
Rack::Lint.new(lambda { |env|
|
|
471
|
+
env["rack.input"].read(0)
|
|
472
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
473
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
474
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
475
|
+
|
|
476
|
+
lambda {
|
|
477
|
+
Rack::Lint.new(lambda { |env|
|
|
478
|
+
env["rack.input"].read(1)
|
|
479
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
480
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
481
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
482
|
+
|
|
483
|
+
lambda {
|
|
484
|
+
Rack::Lint.new(lambda { |env|
|
|
485
|
+
env["rack.input"].read(nil)
|
|
486
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
487
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
488
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
489
|
+
|
|
490
|
+
lambda {
|
|
491
|
+
Rack::Lint.new(lambda { |env|
|
|
492
|
+
env["rack.input"].read(nil, '')
|
|
493
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
494
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
495
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
496
|
+
|
|
497
|
+
lambda {
|
|
498
|
+
Rack::Lint.new(lambda { |env|
|
|
499
|
+
env["rack.input"].read(1, '')
|
|
500
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
|
501
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
|
502
|
+
}.should.not.raise(Rack::Lint::LintError)
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
describe "Rack::Lint::InputWrapper" do
|
|
507
|
+
should "delegate :rewind to underlying IO object" do
|
|
508
|
+
io = StringIO.new("123")
|
|
509
|
+
wrapper = Rack::Lint::InputWrapper.new(io)
|
|
510
|
+
wrapper.read.should.equal "123"
|
|
511
|
+
wrapper.read.should.equal ""
|
|
512
|
+
wrapper.rewind
|
|
513
|
+
wrapper.read.should.equal "123"
|
|
514
|
+
end
|
|
515
|
+
end
|