eac-rack 1.1.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 +399 -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 +92 -0
- data/lib/rack/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +37 -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 +55 -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 +49 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +47 -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 +23 -0
- data/lib/rack/file.rb +90 -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 +89 -0
- data/lib/rack/handler/lsws.rb +63 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +62 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +18 -0
- data/lib/rack/handler/webrick.rb +69 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +575 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +206 -0
- data/lib/rack/mock.rb +189 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +271 -0
- data/lib/rack/response.rb +149 -0
- data/lib/rack/rewindable_input.rb +100 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +142 -0
- data/lib/rack/server.rb +212 -0
- data/lib/rack/session/abstract/id.rb +140 -0
- data/lib/rack/session/cookie.rb +90 -0
- data/lib/rack/session/memcache.rb +119 -0
- data/lib/rack/session/pool.rb +100 -0
- data/lib/rack/showexceptions.rb +349 -0
- data/lib/rack/showstatus.rb +106 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +56 -0
- data/lib/rack/utils.rb +614 -0
- data/rack.gemspec +38 -0
- data/test/spec_rack_auth_basic.rb +73 -0
- data/test/spec_rack_auth_digest.rb +226 -0
- data/test/spec_rack_builder.rb +84 -0
- data/test/spec_rack_camping.rb +51 -0
- data/test/spec_rack_cascade.rb +48 -0
- data/test/spec_rack_cgi.rb +89 -0
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_commonlogger.rb +61 -0
- data/test/spec_rack_conditionalget.rb +41 -0
- data/test/spec_rack_config.rb +24 -0
- data/test/spec_rack_content_length.rb +43 -0
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +127 -0
- data/test/spec_rack_directory.rb +61 -0
- data/test/spec_rack_etag.rb +17 -0
- data/test/spec_rack_fastcgi.rb +89 -0
- data/test/spec_rack_file.rb +75 -0
- data/test/spec_rack_handler.rb +43 -0
- data/test/spec_rack_head.rb +30 -0
- data/test/spec_rack_lint.rb +528 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_logger.rb +21 -0
- data/test/spec_rack_methodoverride.rb +60 -0
- data/test/spec_rack_mock.rb +243 -0
- data/test/spec_rack_mongrel.rb +189 -0
- data/test/spec_rack_nulllogger.rb +13 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +545 -0
- data/test/spec_rack_response.rb +221 -0
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_runtime.rb +35 -0
- data/test/spec_rack_sendfile.rb +86 -0
- data/test/spec_rack_session_cookie.rb +73 -0
- data/test/spec_rack_session_memcache.rb +273 -0
- data/test/spec_rack_session_pool.rb +172 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_showstatus.rb +72 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_thin.rb +91 -0
- data/test/spec_rack_urlmap.rb +215 -0
- data/test/spec_rack_utils.rb +554 -0
- data/test/spec_rack_webrick.rb +130 -0
- data/test/spec_rackup.rb +154 -0
- metadata +311 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
|
3
|
+
require 'rack/directory'
|
4
|
+
require 'rack/lint'
|
5
|
+
|
6
|
+
require 'rack/mock'
|
7
|
+
|
8
|
+
context "Rack::Directory" do
|
9
|
+
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
10
|
+
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
|
11
|
+
app = Rack::Directory.new DOCROOT, FILE_CATCH
|
12
|
+
|
13
|
+
specify "serves directory indices" do
|
14
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
15
|
+
get("/cgi/")
|
16
|
+
|
17
|
+
res.should.be.ok
|
18
|
+
res.should =~ /<html><head>/
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "passes to app if file found" do
|
22
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
23
|
+
get("/cgi/test")
|
24
|
+
|
25
|
+
res.should.be.ok
|
26
|
+
res.should =~ /passed!/
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "serves uri with URL encoded filenames" do
|
30
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
31
|
+
get("/%63%67%69/") # "/cgi/test"
|
32
|
+
|
33
|
+
res.should.be.ok
|
34
|
+
res.should =~ /<html><head>/
|
35
|
+
|
36
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
37
|
+
get("/cgi/%74%65%73%74") # "/cgi/test"
|
38
|
+
|
39
|
+
res.should.be.ok
|
40
|
+
res.should =~ /passed!/
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "does not allow directory traversal" do
|
44
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
45
|
+
get("/cgi/../test")
|
46
|
+
|
47
|
+
res.should.be.forbidden
|
48
|
+
|
49
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
50
|
+
get("/cgi/%2E%2E/test")
|
51
|
+
|
52
|
+
res.should.be.forbidden
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "404s if it can't find the file" do
|
56
|
+
res = Rack::MockRequest.new(Rack::Lint.new(app)).
|
57
|
+
get("/cgi/blubb")
|
58
|
+
|
59
|
+
res.should.be.not_found
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/etag'
|
4
|
+
|
5
|
+
context "Rack::ETag" do
|
6
|
+
specify "sets ETag if none is set" do
|
7
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
8
|
+
response = Rack::ETag.new(app).call({})
|
9
|
+
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "does not change ETag if it is already set" do
|
13
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
|
14
|
+
response = Rack::ETag.new(app).call({})
|
15
|
+
response[1]['ETag'].should.equal "\"abc\""
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'testrequest'
|
3
|
+
|
4
|
+
context "Rack::Handler::FastCGI" do
|
5
|
+
include TestRequest::Helpers
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@host = '0.0.0.0'
|
9
|
+
@port = 9203
|
10
|
+
end
|
11
|
+
|
12
|
+
# Keep this first.
|
13
|
+
specify "startup" do
|
14
|
+
$pid = fork {
|
15
|
+
Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
|
16
|
+
exec "lighttpd -D -f lighttpd.conf"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "should respond" do
|
21
|
+
sleep 1
|
22
|
+
lambda {
|
23
|
+
GET("/test.fcgi")
|
24
|
+
}.should.not.raise
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should be a lighttpd" do
|
28
|
+
GET("/test.fcgi")
|
29
|
+
status.should.be 200
|
30
|
+
response["SERVER_SOFTWARE"].should =~ /lighttpd/
|
31
|
+
response["HTTP_VERSION"].should.equal "HTTP/1.1"
|
32
|
+
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
|
33
|
+
response["SERVER_PORT"].should.equal @port.to_s
|
34
|
+
response["SERVER_NAME"].should =~ @host
|
35
|
+
end
|
36
|
+
|
37
|
+
specify "should have rack headers" do
|
38
|
+
GET("/test.fcgi")
|
39
|
+
response["rack.version"].should.equal [1,1]
|
40
|
+
response["rack.multithread"].should.be false
|
41
|
+
response["rack.multiprocess"].should.be true
|
42
|
+
response["rack.run_once"].should.be false
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "should have CGI headers on GET" do
|
46
|
+
GET("/test.fcgi")
|
47
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
48
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
49
|
+
response["REQUEST_PATH"].should.equal "/"
|
50
|
+
response["PATH_INFO"].should.equal ""
|
51
|
+
response["QUERY_STRING"].should.equal ""
|
52
|
+
response["test.postdata"].should.equal ""
|
53
|
+
|
54
|
+
GET("/test.fcgi/foo?quux=1")
|
55
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
56
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
57
|
+
response["REQUEST_PATH"].should.equal "/"
|
58
|
+
response["PATH_INFO"].should.equal "/foo"
|
59
|
+
response["QUERY_STRING"].should.equal "quux=1"
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "should have CGI headers on POST" do
|
63
|
+
POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
|
64
|
+
status.should.equal 200
|
65
|
+
response["REQUEST_METHOD"].should.equal "POST"
|
66
|
+
response["SCRIPT_NAME"].should.equal "/test.fcgi"
|
67
|
+
response["REQUEST_PATH"].should.equal "/"
|
68
|
+
response["QUERY_STRING"].should.equal ""
|
69
|
+
response["HTTP_X_TEST_HEADER"].should.equal "42"
|
70
|
+
response["test.postdata"].should.equal "rack-form-data=23"
|
71
|
+
end
|
72
|
+
|
73
|
+
specify "should support HTTP auth" do
|
74
|
+
GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
|
75
|
+
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
|
76
|
+
end
|
77
|
+
|
78
|
+
specify "should set status" do
|
79
|
+
GET("/test.fcgi?secret")
|
80
|
+
status.should.equal 403
|
81
|
+
response["rack.url_scheme"].should.equal "http"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Keep this last.
|
85
|
+
specify "shutdown" do
|
86
|
+
Process.kill 15, $pid
|
87
|
+
Process.wait($pid).should.equal $pid
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
|
3
|
+
require 'rack/file'
|
4
|
+
require 'rack/lint'
|
5
|
+
|
6
|
+
require 'rack/mock'
|
7
|
+
|
8
|
+
context "Rack::File" do
|
9
|
+
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
10
|
+
|
11
|
+
specify "serves files" do
|
12
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
13
|
+
get("/cgi/test")
|
14
|
+
|
15
|
+
res.should.be.ok
|
16
|
+
res.should =~ /ruby/
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "sets Last-Modified header" do
|
20
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
21
|
+
get("/cgi/test")
|
22
|
+
|
23
|
+
path = File.join(DOCROOT, "/cgi/test")
|
24
|
+
|
25
|
+
res.should.be.ok
|
26
|
+
res["Last-Modified"].should.equal File.mtime(path).httpdate
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "serves files with URL encoded filenames" do
|
30
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
31
|
+
get("/cgi/%74%65%73%74") # "/cgi/test"
|
32
|
+
|
33
|
+
res.should.be.ok
|
34
|
+
res.should =~ /ruby/
|
35
|
+
end
|
36
|
+
|
37
|
+
specify "does not allow directory traversal" do
|
38
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
39
|
+
get("/cgi/../test")
|
40
|
+
|
41
|
+
res.should.be.forbidden
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "does not allow directory traversal with encoded periods" do
|
45
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
46
|
+
get("/%2E%2E/README")
|
47
|
+
|
48
|
+
res.should.be.forbidden
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "404s if it can't find the file" do
|
52
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
53
|
+
get("/cgi/blubb")
|
54
|
+
|
55
|
+
res.should.be.not_found
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "detects SystemCallErrors" do
|
59
|
+
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
60
|
+
get("/cgi")
|
61
|
+
|
62
|
+
res.should.be.not_found
|
63
|
+
end
|
64
|
+
|
65
|
+
specify "returns bodies that respond to #to_path" do
|
66
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
67
|
+
status, headers, body = Rack::File.new(DOCROOT).call(env)
|
68
|
+
|
69
|
+
path = File.join(DOCROOT, "/cgi/test")
|
70
|
+
|
71
|
+
status.should.equal 200
|
72
|
+
body.should.respond_to :to_path
|
73
|
+
body.to_path.should.equal path
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
|
3
|
+
require 'rack/handler'
|
4
|
+
|
5
|
+
class Rack::Handler::Lobster; end
|
6
|
+
class RockLobster; end
|
7
|
+
|
8
|
+
context "Rack::Handler" do
|
9
|
+
specify "has registered default handlers" do
|
10
|
+
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
|
11
|
+
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
|
12
|
+
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
|
13
|
+
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "handler that doesn't exist should raise a NameError" do
|
17
|
+
lambda {
|
18
|
+
Rack::Handler.get('boom')
|
19
|
+
}.should.raise(NameError)
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should get unregistered, but already required, handler by name" do
|
23
|
+
Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "should register custom handler" do
|
27
|
+
Rack::Handler.register('rock_lobster', 'RockLobster')
|
28
|
+
Rack::Handler.get('rock_lobster').should.equal RockLobster
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "should not need registration for properly coded handlers even if not already required" do
|
32
|
+
begin
|
33
|
+
$:.push "test/unregistered_handler"
|
34
|
+
Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
|
35
|
+
lambda {
|
36
|
+
Rack::Handler.get('UnRegistered')
|
37
|
+
}.should.raise(NameError)
|
38
|
+
Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
|
39
|
+
ensure
|
40
|
+
$:.delete "test/unregistered_handler"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/head'
|
2
|
+
require 'rack/mock'
|
3
|
+
|
4
|
+
context "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
|
+
specify "passes 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
|
+
specify "removes 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
|
@@ -0,0 +1,528 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'rack/lint'
|
5
|
+
require 'rack/mock'
|
6
|
+
|
7
|
+
context "Rack::Lint" do
|
8
|
+
def env(*args)
|
9
|
+
Rack::MockRequest.env_for("/", *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "passes valid request" do
|
13
|
+
lambda {
|
14
|
+
Rack::Lint.new(lambda { |env|
|
15
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
16
|
+
}).call(env({}))
|
17
|
+
}.should.not.raise
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "notices fatal errors" do
|
21
|
+
lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
|
22
|
+
message.should.match(/No env given/)
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "notices environment errors" do
|
26
|
+
lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
|
27
|
+
message.should.match(/not a Hash/)
|
28
|
+
|
29
|
+
lambda {
|
30
|
+
e = env
|
31
|
+
e.delete("REQUEST_METHOD")
|
32
|
+
Rack::Lint.new(nil).call(e)
|
33
|
+
}.should.raise(Rack::Lint::LintError).
|
34
|
+
message.should.match(/missing required key REQUEST_METHOD/)
|
35
|
+
|
36
|
+
lambda {
|
37
|
+
e = env
|
38
|
+
e.delete("SERVER_NAME")
|
39
|
+
Rack::Lint.new(nil).call(e)
|
40
|
+
}.should.raise(Rack::Lint::LintError).
|
41
|
+
message.should.match(/missing required key SERVER_NAME/)
|
42
|
+
|
43
|
+
|
44
|
+
lambda {
|
45
|
+
Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
|
46
|
+
}.should.raise(Rack::Lint::LintError).
|
47
|
+
message.should.match(/contains HTTP_CONTENT_TYPE/)
|
48
|
+
|
49
|
+
lambda {
|
50
|
+
Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
|
51
|
+
}.should.raise(Rack::Lint::LintError).
|
52
|
+
message.should.match(/contains HTTP_CONTENT_LENGTH/)
|
53
|
+
|
54
|
+
lambda {
|
55
|
+
Rack::Lint.new(nil).call(env("FOO" => Object.new))
|
56
|
+
}.should.raise(Rack::Lint::LintError).
|
57
|
+
message.should.match(/non-string value/)
|
58
|
+
|
59
|
+
lambda {
|
60
|
+
Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
|
61
|
+
}.should.raise(Rack::Lint::LintError).
|
62
|
+
message.should.match(/must be an Array/)
|
63
|
+
|
64
|
+
lambda {
|
65
|
+
Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
|
66
|
+
}.should.raise(Rack::Lint::LintError).
|
67
|
+
message.should.match(/url_scheme unknown/)
|
68
|
+
|
69
|
+
lambda {
|
70
|
+
Rack::Lint.new(nil).call(env("rack.session" => []))
|
71
|
+
}.should.raise(Rack::Lint::LintError).
|
72
|
+
message.should.equal("session [] must respond to store and []=")
|
73
|
+
|
74
|
+
lambda {
|
75
|
+
Rack::Lint.new(nil).call(env("rack.logger" => []))
|
76
|
+
}.should.raise(Rack::Lint::LintError).
|
77
|
+
message.should.equal("logger [] must respond to info")
|
78
|
+
|
79
|
+
lambda {
|
80
|
+
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
|
81
|
+
}.should.raise(Rack::Lint::LintError).
|
82
|
+
message.should.match(/REQUEST_METHOD/)
|
83
|
+
|
84
|
+
lambda {
|
85
|
+
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
|
86
|
+
}.should.raise(Rack::Lint::LintError).
|
87
|
+
message.should.match(/must start with/)
|
88
|
+
|
89
|
+
lambda {
|
90
|
+
Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
|
91
|
+
}.should.raise(Rack::Lint::LintError).
|
92
|
+
message.should.match(/must start with/)
|
93
|
+
|
94
|
+
lambda {
|
95
|
+
Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
|
96
|
+
}.should.raise(Rack::Lint::LintError).
|
97
|
+
message.should.match(/Invalid CONTENT_LENGTH/)
|
98
|
+
|
99
|
+
lambda {
|
100
|
+
e = env
|
101
|
+
e.delete("PATH_INFO")
|
102
|
+
e.delete("SCRIPT_NAME")
|
103
|
+
Rack::Lint.new(nil).call(e)
|
104
|
+
}.should.raise(Rack::Lint::LintError).
|
105
|
+
message.should.match(/One of .* must be set/)
|
106
|
+
|
107
|
+
lambda {
|
108
|
+
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
|
109
|
+
}.should.raise(Rack::Lint::LintError).
|
110
|
+
message.should.match(/cannot be .* make it ''/)
|
111
|
+
end
|
112
|
+
|
113
|
+
specify "notices input errors" do
|
114
|
+
lambda {
|
115
|
+
Rack::Lint.new(nil).call(env("rack.input" => ""))
|
116
|
+
}.should.raise(Rack::Lint::LintError).
|
117
|
+
message.should.match(/does not respond to #gets/)
|
118
|
+
|
119
|
+
lambda {
|
120
|
+
input = Object.new
|
121
|
+
def input.binmode?
|
122
|
+
false
|
123
|
+
end
|
124
|
+
Rack::Lint.new(nil).call(env("rack.input" => input))
|
125
|
+
}.should.raise(Rack::Lint::LintError).
|
126
|
+
message.should.match(/is not opened in binary mode/)
|
127
|
+
|
128
|
+
lambda {
|
129
|
+
input = Object.new
|
130
|
+
def input.external_encoding
|
131
|
+
result = Object.new
|
132
|
+
def result.name
|
133
|
+
"US-ASCII"
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
Rack::Lint.new(nil).call(env("rack.input" => input))
|
138
|
+
}.should.raise(Rack::Lint::LintError).
|
139
|
+
message.should.match(/does not have ASCII-8BIT as its external encoding/)
|
140
|
+
end
|
141
|
+
|
142
|
+
specify "notices error errors" do
|
143
|
+
lambda {
|
144
|
+
Rack::Lint.new(nil).call(env("rack.errors" => ""))
|
145
|
+
}.should.raise(Rack::Lint::LintError).
|
146
|
+
message.should.match(/does not respond to #puts/)
|
147
|
+
end
|
148
|
+
|
149
|
+
specify "notices status errors" do
|
150
|
+
lambda {
|
151
|
+
Rack::Lint.new(lambda { |env|
|
152
|
+
["cc", {}, ""]
|
153
|
+
}).call(env({}))
|
154
|
+
}.should.raise(Rack::Lint::LintError).
|
155
|
+
message.should.match(/must be >=100 seen as integer/)
|
156
|
+
|
157
|
+
lambda {
|
158
|
+
Rack::Lint.new(lambda { |env|
|
159
|
+
[42, {}, ""]
|
160
|
+
}).call(env({}))
|
161
|
+
}.should.raise(Rack::Lint::LintError).
|
162
|
+
message.should.match(/must be >=100 seen as integer/)
|
163
|
+
end
|
164
|
+
|
165
|
+
specify "notices header errors" do
|
166
|
+
lambda {
|
167
|
+
Rack::Lint.new(lambda { |env|
|
168
|
+
[200, Object.new, []]
|
169
|
+
}).call(env({}))
|
170
|
+
}.should.raise(Rack::Lint::LintError).
|
171
|
+
message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
|
172
|
+
|
173
|
+
lambda {
|
174
|
+
Rack::Lint.new(lambda { |env|
|
175
|
+
[200, {true=>false}, []]
|
176
|
+
}).call(env({}))
|
177
|
+
}.should.raise(Rack::Lint::LintError).
|
178
|
+
message.should.equal("header key must be a string, was TrueClass")
|
179
|
+
|
180
|
+
lambda {
|
181
|
+
Rack::Lint.new(lambda { |env|
|
182
|
+
[200, {"Status" => "404"}, []]
|
183
|
+
}).call(env({}))
|
184
|
+
}.should.raise(Rack::Lint::LintError).
|
185
|
+
message.should.match(/must not contain Status/)
|
186
|
+
|
187
|
+
lambda {
|
188
|
+
Rack::Lint.new(lambda { |env|
|
189
|
+
[200, {"Content-Type:" => "text/plain"}, []]
|
190
|
+
}).call(env({}))
|
191
|
+
}.should.raise(Rack::Lint::LintError).
|
192
|
+
message.should.match(/must not contain :/)
|
193
|
+
|
194
|
+
lambda {
|
195
|
+
Rack::Lint.new(lambda { |env|
|
196
|
+
[200, {"Content-" => "text/plain"}, []]
|
197
|
+
}).call(env({}))
|
198
|
+
}.should.raise(Rack::Lint::LintError).
|
199
|
+
message.should.match(/must not end/)
|
200
|
+
|
201
|
+
lambda {
|
202
|
+
Rack::Lint.new(lambda { |env|
|
203
|
+
[200, {"..%%quark%%.." => "text/plain"}, []]
|
204
|
+
}).call(env({}))
|
205
|
+
}.should.raise(Rack::Lint::LintError).
|
206
|
+
message.should.equal("invalid header name: ..%%quark%%..")
|
207
|
+
|
208
|
+
lambda {
|
209
|
+
Rack::Lint.new(lambda { |env|
|
210
|
+
[200, {"Foo" => Object.new}, []]
|
211
|
+
}).call(env({}))
|
212
|
+
}.should.raise(Rack::Lint::LintError).
|
213
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
|
214
|
+
|
215
|
+
lambda {
|
216
|
+
Rack::Lint.new(lambda { |env|
|
217
|
+
[200, {"Foo" => [1, 2, 3]}, []]
|
218
|
+
}).call(env({}))
|
219
|
+
}.should.raise(Rack::Lint::LintError).
|
220
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
|
221
|
+
|
222
|
+
|
223
|
+
lambda {
|
224
|
+
Rack::Lint.new(lambda { |env|
|
225
|
+
[200, {"Foo-Bar" => "text\000plain"}, []]
|
226
|
+
}).call(env({}))
|
227
|
+
}.should.raise(Rack::Lint::LintError).
|
228
|
+
message.should.match(/invalid header/)
|
229
|
+
|
230
|
+
# line ends (010) should be allowed in header values.
|
231
|
+
lambda {
|
232
|
+
Rack::Lint.new(lambda { |env|
|
233
|
+
[200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
|
234
|
+
}).call(env({}))
|
235
|
+
}.should.not.raise(Rack::Lint::LintError)
|
236
|
+
end
|
237
|
+
|
238
|
+
specify "notices content-type errors" do
|
239
|
+
lambda {
|
240
|
+
Rack::Lint.new(lambda { |env|
|
241
|
+
[200, {"Content-length" => "0"}, []]
|
242
|
+
}).call(env({}))
|
243
|
+
}.should.raise(Rack::Lint::LintError).
|
244
|
+
message.should.match(/No Content-Type/)
|
245
|
+
|
246
|
+
[100, 101, 204, 304].each do |status|
|
247
|
+
lambda {
|
248
|
+
Rack::Lint.new(lambda { |env|
|
249
|
+
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
250
|
+
}).call(env({}))
|
251
|
+
}.should.raise(Rack::Lint::LintError).
|
252
|
+
message.should.match(/Content-Type header found/)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
specify "notices content-length errors" do
|
257
|
+
[100, 101, 204, 304].each do |status|
|
258
|
+
lambda {
|
259
|
+
Rack::Lint.new(lambda { |env|
|
260
|
+
[status, {"Content-length" => "0"}, []]
|
261
|
+
}).call(env({}))
|
262
|
+
}.should.raise(Rack::Lint::LintError).
|
263
|
+
message.should.match(/Content-Length header found/)
|
264
|
+
end
|
265
|
+
|
266
|
+
lambda {
|
267
|
+
Rack::Lint.new(lambda { |env|
|
268
|
+
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
|
269
|
+
}).call(env({}))
|
270
|
+
}.should.raise(Rack::Lint::LintError).
|
271
|
+
message.should.match(/Content-Length header was 1, but should be 0/)
|
272
|
+
end
|
273
|
+
|
274
|
+
specify "notices body errors" do
|
275
|
+
lambda {
|
276
|
+
status, header, body = Rack::Lint.new(lambda { |env|
|
277
|
+
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
|
278
|
+
}).call(env({}))
|
279
|
+
body.each { |part| }
|
280
|
+
}.should.raise(Rack::Lint::LintError).
|
281
|
+
message.should.match(/yielded non-string/)
|
282
|
+
end
|
283
|
+
|
284
|
+
specify "notices input handling errors" do
|
285
|
+
lambda {
|
286
|
+
Rack::Lint.new(lambda { |env|
|
287
|
+
env["rack.input"].gets("\r\n")
|
288
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
289
|
+
}).call(env({}))
|
290
|
+
}.should.raise(Rack::Lint::LintError).
|
291
|
+
message.should.match(/gets called with arguments/)
|
292
|
+
|
293
|
+
lambda {
|
294
|
+
Rack::Lint.new(lambda { |env|
|
295
|
+
env["rack.input"].read(1, 2, 3)
|
296
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
297
|
+
}).call(env({}))
|
298
|
+
}.should.raise(Rack::Lint::LintError).
|
299
|
+
message.should.match(/read called with too many arguments/)
|
300
|
+
|
301
|
+
lambda {
|
302
|
+
Rack::Lint.new(lambda { |env|
|
303
|
+
env["rack.input"].read("foo")
|
304
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
305
|
+
}).call(env({}))
|
306
|
+
}.should.raise(Rack::Lint::LintError).
|
307
|
+
message.should.match(/read called with non-integer and non-nil length/)
|
308
|
+
|
309
|
+
lambda {
|
310
|
+
Rack::Lint.new(lambda { |env|
|
311
|
+
env["rack.input"].read(-1)
|
312
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
313
|
+
}).call(env({}))
|
314
|
+
}.should.raise(Rack::Lint::LintError).
|
315
|
+
message.should.match(/read called with a negative length/)
|
316
|
+
|
317
|
+
lambda {
|
318
|
+
Rack::Lint.new(lambda { |env|
|
319
|
+
env["rack.input"].read(nil, nil)
|
320
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
321
|
+
}).call(env({}))
|
322
|
+
}.should.raise(Rack::Lint::LintError).
|
323
|
+
message.should.match(/read called with non-String buffer/)
|
324
|
+
|
325
|
+
lambda {
|
326
|
+
Rack::Lint.new(lambda { |env|
|
327
|
+
env["rack.input"].read(nil, 1)
|
328
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
329
|
+
}).call(env({}))
|
330
|
+
}.should.raise(Rack::Lint::LintError).
|
331
|
+
message.should.match(/read called with non-String buffer/)
|
332
|
+
|
333
|
+
lambda {
|
334
|
+
Rack::Lint.new(lambda { |env|
|
335
|
+
env["rack.input"].rewind(0)
|
336
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
337
|
+
}).call(env({}))
|
338
|
+
}.should.raise(Rack::Lint::LintError).
|
339
|
+
message.should.match(/rewind called with arguments/)
|
340
|
+
|
341
|
+
weirdio = Object.new
|
342
|
+
class << weirdio
|
343
|
+
def gets
|
344
|
+
42
|
345
|
+
end
|
346
|
+
|
347
|
+
def read
|
348
|
+
23
|
349
|
+
end
|
350
|
+
|
351
|
+
def each
|
352
|
+
yield 23
|
353
|
+
yield 42
|
354
|
+
end
|
355
|
+
|
356
|
+
def rewind
|
357
|
+
raise Errno::ESPIPE, "Errno::ESPIPE"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
eof_weirdio = Object.new
|
362
|
+
class << eof_weirdio
|
363
|
+
def gets
|
364
|
+
nil
|
365
|
+
end
|
366
|
+
|
367
|
+
def read(*args)
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
|
371
|
+
def each
|
372
|
+
end
|
373
|
+
|
374
|
+
def rewind
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
lambda {
|
379
|
+
Rack::Lint.new(lambda { |env|
|
380
|
+
env["rack.input"].gets
|
381
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
382
|
+
}).call(env("rack.input" => weirdio))
|
383
|
+
}.should.raise(Rack::Lint::LintError).
|
384
|
+
message.should.match(/gets didn't return a String/)
|
385
|
+
|
386
|
+
lambda {
|
387
|
+
Rack::Lint.new(lambda { |env|
|
388
|
+
env["rack.input"].each { |x| }
|
389
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
390
|
+
}).call(env("rack.input" => weirdio))
|
391
|
+
}.should.raise(Rack::Lint::LintError).
|
392
|
+
message.should.match(/each didn't yield a String/)
|
393
|
+
|
394
|
+
lambda {
|
395
|
+
Rack::Lint.new(lambda { |env|
|
396
|
+
env["rack.input"].read
|
397
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
398
|
+
}).call(env("rack.input" => weirdio))
|
399
|
+
}.should.raise(Rack::Lint::LintError).
|
400
|
+
message.should.match(/read didn't return nil or a String/)
|
401
|
+
|
402
|
+
lambda {
|
403
|
+
Rack::Lint.new(lambda { |env|
|
404
|
+
env["rack.input"].read
|
405
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
406
|
+
}).call(env("rack.input" => eof_weirdio))
|
407
|
+
}.should.raise(Rack::Lint::LintError).
|
408
|
+
message.should.match(/read\(nil\) returned nil on EOF/)
|
409
|
+
|
410
|
+
lambda {
|
411
|
+
Rack::Lint.new(lambda { |env|
|
412
|
+
env["rack.input"].rewind
|
413
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
414
|
+
}).call(env("rack.input" => weirdio))
|
415
|
+
}.should.raise(Rack::Lint::LintError).
|
416
|
+
message.should.match(/rewind raised Errno::ESPIPE/)
|
417
|
+
|
418
|
+
|
419
|
+
lambda {
|
420
|
+
Rack::Lint.new(lambda { |env|
|
421
|
+
env["rack.input"].close
|
422
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
423
|
+
}).call(env({}))
|
424
|
+
}.should.raise(Rack::Lint::LintError).
|
425
|
+
message.should.match(/close must not be called/)
|
426
|
+
end
|
427
|
+
|
428
|
+
specify "notices error handling errors" do
|
429
|
+
lambda {
|
430
|
+
Rack::Lint.new(lambda { |env|
|
431
|
+
env["rack.errors"].write(42)
|
432
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
433
|
+
}).call(env({}))
|
434
|
+
}.should.raise(Rack::Lint::LintError).
|
435
|
+
message.should.match(/write not called with a String/)
|
436
|
+
|
437
|
+
lambda {
|
438
|
+
Rack::Lint.new(lambda { |env|
|
439
|
+
env["rack.errors"].close
|
440
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
441
|
+
}).call(env({}))
|
442
|
+
}.should.raise(Rack::Lint::LintError).
|
443
|
+
message.should.match(/close must not be called/)
|
444
|
+
end
|
445
|
+
|
446
|
+
specify "notices HEAD errors" do
|
447
|
+
lambda {
|
448
|
+
Rack::Lint.new(lambda { |env|
|
449
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
|
450
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))
|
451
|
+
}.should.not.raise
|
452
|
+
|
453
|
+
lambda {
|
454
|
+
Rack::Lint.new(lambda { |env|
|
455
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
456
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))
|
457
|
+
}.should.raise(Rack::Lint::LintError).
|
458
|
+
message.should.match(/body was given for HEAD/)
|
459
|
+
end
|
460
|
+
|
461
|
+
specify "passes valid read calls" do
|
462
|
+
hello_str = "hello world"
|
463
|
+
hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
|
464
|
+
lambda {
|
465
|
+
Rack::Lint.new(lambda { |env|
|
466
|
+
env["rack.input"].read
|
467
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
468
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
469
|
+
}.should.not.raise(Rack::Lint::LintError)
|
470
|
+
|
471
|
+
lambda {
|
472
|
+
Rack::Lint.new(lambda { |env|
|
473
|
+
env["rack.input"].read(0)
|
474
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
475
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
476
|
+
}.should.not.raise(Rack::Lint::LintError)
|
477
|
+
|
478
|
+
lambda {
|
479
|
+
Rack::Lint.new(lambda { |env|
|
480
|
+
env["rack.input"].read(1)
|
481
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
482
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
483
|
+
}.should.not.raise(Rack::Lint::LintError)
|
484
|
+
|
485
|
+
lambda {
|
486
|
+
Rack::Lint.new(lambda { |env|
|
487
|
+
env["rack.input"].read(nil)
|
488
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
489
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
490
|
+
}.should.not.raise(Rack::Lint::LintError)
|
491
|
+
|
492
|
+
lambda {
|
493
|
+
Rack::Lint.new(lambda { |env|
|
494
|
+
env["rack.input"].read(nil, '')
|
495
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
496
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
497
|
+
}.should.not.raise(Rack::Lint::LintError)
|
498
|
+
|
499
|
+
lambda {
|
500
|
+
Rack::Lint.new(lambda { |env|
|
501
|
+
env["rack.input"].read(1, '')
|
502
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
503
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
504
|
+
}.should.not.raise(Rack::Lint::LintError)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context "Rack::Lint::InputWrapper" do
|
509
|
+
specify "delegates :size to underlying IO object" do
|
510
|
+
class IOMock
|
511
|
+
def size
|
512
|
+
101
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
|
517
|
+
wrapper.size.should == 101
|
518
|
+
end
|
519
|
+
|
520
|
+
specify "delegates :rewind to underlying IO object" do
|
521
|
+
io = StringIO.new("123")
|
522
|
+
wrapper = Rack::Lint::InputWrapper.new(io)
|
523
|
+
wrapper.read.should.equal "123"
|
524
|
+
wrapper.read.should.equal ""
|
525
|
+
wrapper.rewind
|
526
|
+
wrapper.read.should.equal "123"
|
527
|
+
end
|
528
|
+
end
|