rack 0.9.1 → 1.0.0
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.
- data/COPYING +1 -1
- data/RDOX +115 -16
- data/README +54 -7
- data/Rakefile +61 -85
- data/SPEC +50 -17
- data/bin/rackup +9 -5
- data/example/protectedlobster.ru +1 -1
- data/lib/rack.rb +7 -3
- data/lib/rack/auth/abstract/handler.rb +13 -4
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/auth/digest/request.rb +2 -2
- data/lib/rack/auth/openid.rb +344 -302
- data/lib/rack/builder.rb +1 -5
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/conditionalget.rb +4 -0
- data/lib/rack/content_length.rb +7 -3
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +83 -74
- data/lib/rack/directory.rb +5 -2
- data/lib/rack/file.rb +4 -1
- data/lib/rack/handler.rb +22 -1
- data/lib/rack/handler/cgi.rb +7 -3
- data/lib/rack/handler/fastcgi.rb +26 -24
- data/lib/rack/handler/lsws.rb +7 -4
- data/lib/rack/handler/mongrel.rb +5 -3
- data/lib/rack/handler/scgi.rb +5 -3
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +11 -5
- data/lib/rack/lint.rb +138 -66
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/mime.rb +4 -4
- data/lib/rack/mock.rb +3 -3
- data/lib/rack/reloader.rb +88 -46
- data/lib/rack/request.rb +46 -10
- data/lib/rack/response.rb +15 -3
- data/lib/rack/rewindable_input.rb +98 -0
- data/lib/rack/session/abstract/id.rb +71 -82
- data/lib/rack/session/cookie.rb +2 -0
- data/lib/rack/session/memcache.rb +59 -47
- data/lib/rack/session/pool.rb +56 -29
- data/lib/rack/showexceptions.rb +2 -1
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/urlmap.rb +12 -5
- data/lib/rack/utils.rb +115 -65
- data/rack.gemspec +54 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +5 -1
- data/test/spec_rack_auth_digest.rb +93 -36
- data/test/spec_rack_auth_openid.rb +47 -100
- data/test/spec_rack_builder.rb +2 -2
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_conditionalget.rb +7 -7
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +36 -14
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_file.rb +11 -0
- data/test/spec_rack_handler.rb +21 -2
- data/test/spec_rack_lint.rb +163 -44
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_mock.rb +6 -1
- data/test/spec_rack_request.rb +81 -12
- data/test/spec_rack_response.rb +46 -2
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_memcache.rb +170 -62
- data/test/spec_rack_session_pool.rb +129 -41
- data/test/spec_rack_static.rb +2 -2
- data/test/spec_rack_thin.rb +3 -2
- data/test/spec_rack_urlmap.rb +10 -0
- data/test/spec_rack_utils.rb +214 -49
- data/test/spec_rack_webrick.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +95 -6
- data/AUTHORS +0 -8
@@ -8,34 +8,34 @@ context "Rack::ConditionalGet" do
|
|
8
8
|
specify "should set a 304 status and truncate body when If-Modified-Since hits" do
|
9
9
|
timestamp = Time.now.httpdate
|
10
10
|
app = Rack::ConditionalGet.new(lambda { |env|
|
11
|
-
[200, {'Last-Modified'=>timestamp}, 'TEST'] })
|
11
|
+
[200, {'Last-Modified'=>timestamp}, ['TEST']] })
|
12
12
|
|
13
13
|
response = Rack::MockRequest.new(app).
|
14
14
|
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
|
15
15
|
|
16
|
-
response.status.should.
|
16
|
+
response.status.should.equal 304
|
17
17
|
response.body.should.be.empty
|
18
18
|
end
|
19
19
|
|
20
20
|
specify "should set a 304 status and truncate body when If-None-Match hits" do
|
21
21
|
app = Rack::ConditionalGet.new(lambda { |env|
|
22
|
-
[200, {'Etag'=>'1234'}, 'TEST'] })
|
22
|
+
[200, {'Etag'=>'1234'}, ['TEST']] })
|
23
23
|
|
24
24
|
response = Rack::MockRequest.new(app).
|
25
25
|
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
26
26
|
|
27
|
-
response.status.should.
|
27
|
+
response.status.should.equal 304
|
28
28
|
response.body.should.be.empty
|
29
29
|
end
|
30
30
|
|
31
31
|
specify "should not affect non-GET/HEAD requests" do
|
32
32
|
app = Rack::ConditionalGet.new(lambda { |env|
|
33
|
-
[200, {'Etag'=>'1234'}, 'TEST'] })
|
33
|
+
[200, {'Etag'=>'1234'}, ['TEST']] })
|
34
34
|
|
35
35
|
response = Rack::MockRequest.new(app).
|
36
36
|
post("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
37
37
|
|
38
|
-
response.status.should.
|
39
|
-
response.body.should.
|
38
|
+
response.status.should.equal 200
|
39
|
+
response.body.should.equal 'TEST'
|
40
40
|
end
|
41
41
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
require 'rack/content_type'
|
3
|
+
|
4
|
+
context "Rack::ContentType" do
|
5
|
+
specify "sets Content-Type to default text/html if none is set" do
|
6
|
+
app = lambda { |env| [200, {}, "Hello, World!"] }
|
7
|
+
status, headers, body = Rack::ContentType.new(app).call({})
|
8
|
+
headers['Content-Type'].should.equal 'text/html'
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "sets Content-Type to chosen default if none is set" do
|
12
|
+
app = lambda { |env| [200, {}, "Hello, World!"] }
|
13
|
+
status, headers, body =
|
14
|
+
Rack::ContentType.new(app, 'application/octet-stream').call({})
|
15
|
+
headers['Content-Type'].should.equal 'application/octet-stream'
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "does not change Content-Type if it is already set" do
|
19
|
+
app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
|
20
|
+
status, headers, body = Rack::ContentType.new(app).call({})
|
21
|
+
headers['Content-Type'].should.equal 'foo/bar'
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "case insensitive detection of Content-Type" do
|
25
|
+
app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
|
26
|
+
status, headers, body = Rack::ContentType.new(app).call({})
|
27
|
+
headers.to_a.select { |k,v| k.downcase == "content-type" }.
|
28
|
+
should.equal [["CONTENT-Type","foo/bar"]]
|
29
|
+
end
|
30
|
+
end
|
data/test/spec_rack_deflater.rb
CHANGED
@@ -7,6 +7,7 @@ require 'time' # for Time#httpdate
|
|
7
7
|
|
8
8
|
context "Rack::Deflater" do
|
9
9
|
def build_response(status, body, accept_encoding, headers = {})
|
10
|
+
body = [body] if body.respond_to? :to_str
|
10
11
|
app = lambda { |env| [status, {}, body] }
|
11
12
|
request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
|
12
13
|
response = Rack::Deflater.new(app).call(request)
|
@@ -21,8 +22,13 @@ context "Rack::Deflater" do
|
|
21
22
|
response = build_response(200, body, "deflate")
|
22
23
|
|
23
24
|
response[0].should.equal(200)
|
24
|
-
response[1].should.equal({
|
25
|
-
|
25
|
+
response[1].should.equal({
|
26
|
+
"Content-Encoding" => "deflate",
|
27
|
+
"Vary" => "Accept-Encoding"
|
28
|
+
})
|
29
|
+
buf = ''
|
30
|
+
response[2].each { |part| buf << part }
|
31
|
+
buf.should.equal("K\313\317OJ,\002\000")
|
26
32
|
end
|
27
33
|
|
28
34
|
# TODO: This is really just a special case of the above...
|
@@ -30,8 +36,13 @@ context "Rack::Deflater" do
|
|
30
36
|
response = build_response(200, "Hello world!", "deflate")
|
31
37
|
|
32
38
|
response[0].should.equal(200)
|
33
|
-
response[1].should.equal({
|
34
|
-
|
39
|
+
response[1].should.equal({
|
40
|
+
"Content-Encoding" => "deflate",
|
41
|
+
"Vary" => "Accept-Encoding"
|
42
|
+
})
|
43
|
+
buf = ''
|
44
|
+
response[2].each { |part| buf << part }
|
45
|
+
buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
|
35
46
|
end
|
36
47
|
|
37
48
|
specify "should be able to gzip bodies that respond to each" do
|
@@ -41,9 +52,14 @@ context "Rack::Deflater" do
|
|
41
52
|
response = build_response(200, body, "gzip")
|
42
53
|
|
43
54
|
response[0].should.equal(200)
|
44
|
-
response[1].should.equal({
|
45
|
-
|
46
|
-
|
55
|
+
response[1].should.equal({
|
56
|
+
"Content-Encoding" => "gzip",
|
57
|
+
"Vary" => "Accept-Encoding",
|
58
|
+
})
|
59
|
+
|
60
|
+
buf = ''
|
61
|
+
response[2].each { |part| buf << part }
|
62
|
+
io = StringIO.new(buf)
|
47
63
|
gz = Zlib::GzipReader.new(io)
|
48
64
|
gz.read.should.equal("foobar")
|
49
65
|
gz.close
|
@@ -54,7 +70,7 @@ context "Rack::Deflater" do
|
|
54
70
|
|
55
71
|
response[0].should.equal(200)
|
56
72
|
response[1].should.equal({ "Vary" => "Accept-Encoding" })
|
57
|
-
response[2].should.equal("Hello world!")
|
73
|
+
response[2].should.equal(["Hello world!"])
|
58
74
|
end
|
59
75
|
|
60
76
|
specify "should be able to skip when there is no response entity body" do
|
@@ -68,26 +84,32 @@ context "Rack::Deflater" do
|
|
68
84
|
specify "should handle the lack of an acceptable encoding" do
|
69
85
|
response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
|
70
86
|
response1[0].should.equal(406)
|
71
|
-
response1[1].should.equal({"Content-Type" => "text/plain"})
|
87
|
+
response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
|
72
88
|
response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
|
73
89
|
|
74
90
|
response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
|
75
91
|
response2[0].should.equal(406)
|
76
|
-
response2[1].should.equal({"Content-Type" => "text/plain"})
|
92
|
+
response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
|
77
93
|
response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
|
78
94
|
end
|
79
95
|
|
80
96
|
specify "should handle gzip response with Last-Modified header" do
|
81
97
|
last_modified = Time.now.httpdate
|
82
98
|
|
83
|
-
app = lambda { |env| [200, { "Last-Modified" => last_modified }, "Hello World!"] }
|
99
|
+
app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] }
|
84
100
|
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
|
85
101
|
response = Rack::Deflater.new(app).call(request)
|
86
102
|
|
87
103
|
response[0].should.equal(200)
|
88
|
-
response[1].should.equal({
|
89
|
-
|
90
|
-
|
104
|
+
response[1].should.equal({
|
105
|
+
"Content-Encoding" => "gzip",
|
106
|
+
"Vary" => "Accept-Encoding",
|
107
|
+
"Last-Modified" => last_modified
|
108
|
+
})
|
109
|
+
|
110
|
+
buf = ''
|
111
|
+
response[2].each { |part| buf << part }
|
112
|
+
io = StringIO.new(buf)
|
91
113
|
gz = Zlib::GzipReader.new(io)
|
92
114
|
gz.read.should.equal("Hello World!")
|
93
115
|
gz.close
|
data/test/spec_rack_directory.rb
CHANGED
@@ -7,7 +7,7 @@ require 'rack/mock'
|
|
7
7
|
|
8
8
|
context "Rack::Directory" do
|
9
9
|
DOCROOT = File.expand_path(File.dirname(__FILE__))
|
10
|
-
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, 'passed!'] }
|
10
|
+
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
|
11
11
|
app = Rack::Directory.new DOCROOT, FILE_CATCH
|
12
12
|
|
13
13
|
specify "serves directory indices" do
|
data/test/spec_rack_file.rb
CHANGED
@@ -61,4 +61,15 @@ context "Rack::File" do
|
|
61
61
|
|
62
62
|
res.should.be.not_found
|
63
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
|
64
75
|
end
|
data/test/spec_rack_handler.rb
CHANGED
@@ -12,13 +12,32 @@ context "Rack::Handler" do
|
|
12
12
|
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
|
13
13
|
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
|
14
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
|
15
21
|
|
16
|
-
specify "should get unregistered handler by name" do
|
17
|
-
Rack::Handler.get('
|
22
|
+
specify "should get unregistered, but already required, handler by name" do
|
23
|
+
Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
|
18
24
|
end
|
19
25
|
|
20
26
|
specify "should register custom handler" do
|
21
27
|
Rack::Handler.register('rock_lobster', 'RockLobster')
|
22
28
|
Rack::Handler.get('rock_lobster').should.equal RockLobster
|
23
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
|
24
43
|
end
|
data/test/spec_rack_lint.rb
CHANGED
@@ -12,7 +12,7 @@ context "Rack::Lint" do
|
|
12
12
|
specify "passes valid request" do
|
13
13
|
lambda {
|
14
14
|
Rack::Lint.new(lambda { |env|
|
15
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
|
15
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
16
16
|
}).call(env({}))
|
17
17
|
}.should.not.raise
|
18
18
|
end
|
@@ -66,6 +66,11 @@ context "Rack::Lint" do
|
|
66
66
|
}.should.raise(Rack::Lint::LintError).
|
67
67
|
message.should.match(/url_scheme unknown/)
|
68
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
|
+
|
69
74
|
lambda {
|
70
75
|
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
|
71
76
|
}.should.raise(Rack::Lint::LintError).
|
@@ -133,73 +138,80 @@ context "Rack::Lint" do
|
|
133
138
|
specify "notices header errors" do
|
134
139
|
lambda {
|
135
140
|
Rack::Lint.new(lambda { |env|
|
136
|
-
[200, Object.new,
|
141
|
+
[200, Object.new, []]
|
137
142
|
}).call(env({}))
|
138
143
|
}.should.raise(Rack::Lint::LintError).
|
139
144
|
message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
|
140
145
|
|
141
146
|
lambda {
|
142
147
|
Rack::Lint.new(lambda { |env|
|
143
|
-
[200, {true=>false},
|
148
|
+
[200, {true=>false}, []]
|
144
149
|
}).call(env({}))
|
145
150
|
}.should.raise(Rack::Lint::LintError).
|
146
151
|
message.should.equal("header key must be a string, was TrueClass")
|
147
152
|
|
148
153
|
lambda {
|
149
154
|
Rack::Lint.new(lambda { |env|
|
150
|
-
[200, {"Status" => "404"},
|
155
|
+
[200, {"Status" => "404"}, []]
|
151
156
|
}).call(env({}))
|
152
157
|
}.should.raise(Rack::Lint::LintError).
|
153
158
|
message.should.match(/must not contain Status/)
|
154
159
|
|
155
160
|
lambda {
|
156
161
|
Rack::Lint.new(lambda { |env|
|
157
|
-
[200, {"Content-Type:" => "text/plain"},
|
162
|
+
[200, {"Content-Type:" => "text/plain"}, []]
|
158
163
|
}).call(env({}))
|
159
164
|
}.should.raise(Rack::Lint::LintError).
|
160
165
|
message.should.match(/must not contain :/)
|
161
166
|
|
162
167
|
lambda {
|
163
168
|
Rack::Lint.new(lambda { |env|
|
164
|
-
[200, {"Content-" => "text/plain"},
|
169
|
+
[200, {"Content-" => "text/plain"}, []]
|
165
170
|
}).call(env({}))
|
166
171
|
}.should.raise(Rack::Lint::LintError).
|
167
172
|
message.should.match(/must not end/)
|
168
173
|
|
169
174
|
lambda {
|
170
175
|
Rack::Lint.new(lambda { |env|
|
171
|
-
[200, {"..%%quark%%.." => "text/plain"},
|
176
|
+
[200, {"..%%quark%%.." => "text/plain"}, []]
|
172
177
|
}).call(env({}))
|
173
178
|
}.should.raise(Rack::Lint::LintError).
|
174
179
|
message.should.equal("invalid header name: ..%%quark%%..")
|
175
180
|
|
176
181
|
lambda {
|
177
182
|
Rack::Lint.new(lambda { |env|
|
178
|
-
[200, {"Foo" => Object.new},
|
183
|
+
[200, {"Foo" => Object.new}, []]
|
179
184
|
}).call(env({}))
|
180
185
|
}.should.raise(Rack::Lint::LintError).
|
181
|
-
message.should.equal("header
|
186
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
|
182
187
|
|
183
188
|
lambda {
|
184
189
|
Rack::Lint.new(lambda { |env|
|
185
|
-
[200, {"Foo" => [1,2,3]},
|
190
|
+
[200, {"Foo" => [1, 2, 3]}, []]
|
186
191
|
}).call(env({}))
|
187
192
|
}.should.raise(Rack::Lint::LintError).
|
188
|
-
message.should.equal("header
|
193
|
+
message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
|
189
194
|
|
190
195
|
|
191
196
|
lambda {
|
192
197
|
Rack::Lint.new(lambda { |env|
|
193
|
-
[200, {"Foo-Bar" => "text\000plain"},
|
198
|
+
[200, {"Foo-Bar" => "text\000plain"}, []]
|
194
199
|
}).call(env({}))
|
195
200
|
}.should.raise(Rack::Lint::LintError).
|
196
201
|
message.should.match(/invalid header/)
|
202
|
+
|
203
|
+
# line ends (010) should be allowed in header values.
|
204
|
+
lambda {
|
205
|
+
Rack::Lint.new(lambda { |env|
|
206
|
+
[200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
|
207
|
+
}).call(env({}))
|
208
|
+
}.should.not.raise(Rack::Lint::LintError)
|
197
209
|
end
|
198
210
|
|
199
211
|
specify "notices content-type errors" do
|
200
212
|
lambda {
|
201
213
|
Rack::Lint.new(lambda { |env|
|
202
|
-
[200, {"Content-length" => "0"},
|
214
|
+
[200, {"Content-length" => "0"}, []]
|
203
215
|
}).call(env({}))
|
204
216
|
}.should.raise(Rack::Lint::LintError).
|
205
217
|
message.should.match(/No Content-Type/)
|
@@ -207,7 +219,7 @@ context "Rack::Lint" do
|
|
207
219
|
[100, 101, 204, 304].each do |status|
|
208
220
|
lambda {
|
209
221
|
Rack::Lint.new(lambda { |env|
|
210
|
-
[status, {"Content-type" => "text/plain", "Content-length" => "0"},
|
222
|
+
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
211
223
|
}).call(env({}))
|
212
224
|
}.should.raise(Rack::Lint::LintError).
|
213
225
|
message.should.match(/Content-Type header found/)
|
@@ -215,17 +227,10 @@ context "Rack::Lint" do
|
|
215
227
|
end
|
216
228
|
|
217
229
|
specify "notices content-length errors" do
|
218
|
-
lambda {
|
219
|
-
Rack::Lint.new(lambda { |env|
|
220
|
-
[200, {"Content-type" => "text/plain"}, ""]
|
221
|
-
}).call(env({}))
|
222
|
-
}.should.raise(Rack::Lint::LintError).
|
223
|
-
message.should.match(/No Content-Length/)
|
224
|
-
|
225
230
|
[100, 101, 204, 304].each do |status|
|
226
231
|
lambda {
|
227
232
|
Rack::Lint.new(lambda { |env|
|
228
|
-
[status, {"Content-length" => "0"},
|
233
|
+
[status, {"Content-length" => "0"}, []]
|
229
234
|
}).call(env({}))
|
230
235
|
}.should.raise(Rack::Lint::LintError).
|
231
236
|
message.should.match(/Content-Length header found/)
|
@@ -233,14 +238,7 @@ context "Rack::Lint" do
|
|
233
238
|
|
234
239
|
lambda {
|
235
240
|
Rack::Lint.new(lambda { |env|
|
236
|
-
[200, {"Content-type" => "text/plain", "Content-Length" => "
|
237
|
-
}).call(env({}))
|
238
|
-
}.should.raise(Rack::Lint::LintError).
|
239
|
-
message.should.match(/Content-Length header should not be used/)
|
240
|
-
|
241
|
-
lambda {
|
242
|
-
Rack::Lint.new(lambda { |env|
|
243
|
-
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, ""]
|
241
|
+
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
|
244
242
|
}).call(env({}))
|
245
243
|
}.should.raise(Rack::Lint::LintError).
|
246
244
|
message.should.match(/Content-Length header was 1, but should be 0/)
|
@@ -260,18 +258,58 @@ context "Rack::Lint" do
|
|
260
258
|
lambda {
|
261
259
|
Rack::Lint.new(lambda { |env|
|
262
260
|
env["rack.input"].gets("\r\n")
|
263
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
261
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
264
262
|
}).call(env({}))
|
265
263
|
}.should.raise(Rack::Lint::LintError).
|
266
264
|
message.should.match(/gets called with arguments/)
|
267
265
|
|
266
|
+
lambda {
|
267
|
+
Rack::Lint.new(lambda { |env|
|
268
|
+
env["rack.input"].read(1, 2, 3)
|
269
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
270
|
+
}).call(env({}))
|
271
|
+
}.should.raise(Rack::Lint::LintError).
|
272
|
+
message.should.match(/read called with too many arguments/)
|
273
|
+
|
268
274
|
lambda {
|
269
275
|
Rack::Lint.new(lambda { |env|
|
270
276
|
env["rack.input"].read("foo")
|
271
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
277
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
278
|
+
}).call(env({}))
|
279
|
+
}.should.raise(Rack::Lint::LintError).
|
280
|
+
message.should.match(/read called with non-integer and non-nil length/)
|
281
|
+
|
282
|
+
lambda {
|
283
|
+
Rack::Lint.new(lambda { |env|
|
284
|
+
env["rack.input"].read(-1)
|
285
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
286
|
+
}).call(env({}))
|
287
|
+
}.should.raise(Rack::Lint::LintError).
|
288
|
+
message.should.match(/read called with a negative length/)
|
289
|
+
|
290
|
+
lambda {
|
291
|
+
Rack::Lint.new(lambda { |env|
|
292
|
+
env["rack.input"].read(nil, nil)
|
293
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
294
|
+
}).call(env({}))
|
295
|
+
}.should.raise(Rack::Lint::LintError).
|
296
|
+
message.should.match(/read called with non-String buffer/)
|
297
|
+
|
298
|
+
lambda {
|
299
|
+
Rack::Lint.new(lambda { |env|
|
300
|
+
env["rack.input"].read(nil, 1)
|
301
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
272
302
|
}).call(env({}))
|
273
303
|
}.should.raise(Rack::Lint::LintError).
|
274
|
-
message.should.match(/read called with non-
|
304
|
+
message.should.match(/read called with non-String buffer/)
|
305
|
+
|
306
|
+
lambda {
|
307
|
+
Rack::Lint.new(lambda { |env|
|
308
|
+
env["rack.input"].rewind(0)
|
309
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
310
|
+
}).call(env({}))
|
311
|
+
}.should.raise(Rack::Lint::LintError).
|
312
|
+
message.should.match(/rewind called with arguments/)
|
275
313
|
|
276
314
|
weirdio = Object.new
|
277
315
|
class << weirdio
|
@@ -287,12 +325,33 @@ context "Rack::Lint" do
|
|
287
325
|
yield 23
|
288
326
|
yield 42
|
289
327
|
end
|
328
|
+
|
329
|
+
def rewind
|
330
|
+
raise Errno::ESPIPE, "Errno::ESPIPE"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
eof_weirdio = Object.new
|
335
|
+
class << eof_weirdio
|
336
|
+
def gets
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
|
340
|
+
def read(*args)
|
341
|
+
nil
|
342
|
+
end
|
343
|
+
|
344
|
+
def each
|
345
|
+
end
|
346
|
+
|
347
|
+
def rewind
|
348
|
+
end
|
290
349
|
end
|
291
350
|
|
292
351
|
lambda {
|
293
352
|
Rack::Lint.new(lambda { |env|
|
294
353
|
env["rack.input"].gets
|
295
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
354
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
296
355
|
}).call(env("rack.input" => weirdio))
|
297
356
|
}.should.raise(Rack::Lint::LintError).
|
298
357
|
message.should.match(/gets didn't return a String/)
|
@@ -300,7 +359,7 @@ context "Rack::Lint" do
|
|
300
359
|
lambda {
|
301
360
|
Rack::Lint.new(lambda { |env|
|
302
361
|
env["rack.input"].each { |x| }
|
303
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
362
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
304
363
|
}).call(env("rack.input" => weirdio))
|
305
364
|
}.should.raise(Rack::Lint::LintError).
|
306
365
|
message.should.match(/each didn't yield a String/)
|
@@ -308,16 +367,32 @@ context "Rack::Lint" do
|
|
308
367
|
lambda {
|
309
368
|
Rack::Lint.new(lambda { |env|
|
310
369
|
env["rack.input"].read
|
311
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
370
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
312
371
|
}).call(env("rack.input" => weirdio))
|
313
372
|
}.should.raise(Rack::Lint::LintError).
|
314
|
-
message.should.match(/read didn't return a String/)
|
373
|
+
message.should.match(/read didn't return nil or a String/)
|
374
|
+
|
375
|
+
lambda {
|
376
|
+
Rack::Lint.new(lambda { |env|
|
377
|
+
env["rack.input"].read
|
378
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
379
|
+
}).call(env("rack.input" => eof_weirdio))
|
380
|
+
}.should.raise(Rack::Lint::LintError).
|
381
|
+
message.should.match(/read\(nil\) returned nil on EOF/)
|
382
|
+
|
383
|
+
lambda {
|
384
|
+
Rack::Lint.new(lambda { |env|
|
385
|
+
env["rack.input"].rewind
|
386
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
387
|
+
}).call(env("rack.input" => weirdio))
|
388
|
+
}.should.raise(Rack::Lint::LintError).
|
389
|
+
message.should.match(/rewind raised Errno::ESPIPE/)
|
315
390
|
|
316
391
|
|
317
392
|
lambda {
|
318
393
|
Rack::Lint.new(lambda { |env|
|
319
394
|
env["rack.input"].close
|
320
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
395
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
321
396
|
}).call(env({}))
|
322
397
|
}.should.raise(Rack::Lint::LintError).
|
323
398
|
message.should.match(/close must not be called/)
|
@@ -327,7 +402,7 @@ context "Rack::Lint" do
|
|
327
402
|
lambda {
|
328
403
|
Rack::Lint.new(lambda { |env|
|
329
404
|
env["rack.errors"].write(42)
|
330
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
405
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
331
406
|
}).call(env({}))
|
332
407
|
}.should.raise(Rack::Lint::LintError).
|
333
408
|
message.should.match(/write not called with a String/)
|
@@ -335,7 +410,7 @@ context "Rack::Lint" do
|
|
335
410
|
lambda {
|
336
411
|
Rack::Lint.new(lambda { |env|
|
337
412
|
env["rack.errors"].close
|
338
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"},
|
413
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
339
414
|
}).call(env({}))
|
340
415
|
}.should.raise(Rack::Lint::LintError).
|
341
416
|
message.should.match(/close must not be called/)
|
@@ -350,11 +425,55 @@ context "Rack::Lint" do
|
|
350
425
|
|
351
426
|
lambda {
|
352
427
|
Rack::Lint.new(lambda { |env|
|
353
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
|
428
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
354
429
|
}).call(env({"REQUEST_METHOD" => "HEAD"}))
|
355
430
|
}.should.raise(Rack::Lint::LintError).
|
356
431
|
message.should.match(/body was given for HEAD/)
|
357
432
|
end
|
433
|
+
|
434
|
+
specify "passes valid read calls" do
|
435
|
+
lambda {
|
436
|
+
Rack::Lint.new(lambda { |env|
|
437
|
+
env["rack.input"].read
|
438
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
439
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
440
|
+
}.should.not.raise(Rack::Lint::LintError)
|
441
|
+
|
442
|
+
lambda {
|
443
|
+
Rack::Lint.new(lambda { |env|
|
444
|
+
env["rack.input"].read(0)
|
445
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
446
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
447
|
+
}.should.not.raise(Rack::Lint::LintError)
|
448
|
+
|
449
|
+
lambda {
|
450
|
+
Rack::Lint.new(lambda { |env|
|
451
|
+
env["rack.input"].read(1)
|
452
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
453
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
454
|
+
}.should.not.raise(Rack::Lint::LintError)
|
455
|
+
|
456
|
+
lambda {
|
457
|
+
Rack::Lint.new(lambda { |env|
|
458
|
+
env["rack.input"].read(nil)
|
459
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
460
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
461
|
+
}.should.not.raise(Rack::Lint::LintError)
|
462
|
+
|
463
|
+
lambda {
|
464
|
+
Rack::Lint.new(lambda { |env|
|
465
|
+
env["rack.input"].read(nil, '')
|
466
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
467
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
468
|
+
}.should.not.raise(Rack::Lint::LintError)
|
469
|
+
|
470
|
+
lambda {
|
471
|
+
Rack::Lint.new(lambda { |env|
|
472
|
+
env["rack.input"].read(1, '')
|
473
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
474
|
+
}).call(env({"rack.input" => StringIO.new("hello world")}))
|
475
|
+
}.should.not.raise(Rack::Lint::LintError)
|
476
|
+
end
|
358
477
|
end
|
359
478
|
|
360
479
|
context "Rack::Lint::InputWrapper" do
|
@@ -372,9 +491,9 @@ context "Rack::Lint::InputWrapper" do
|
|
372
491
|
specify "delegates :rewind to underlying IO object" do
|
373
492
|
io = StringIO.new("123")
|
374
493
|
wrapper = Rack::Lint::InputWrapper.new(io)
|
375
|
-
wrapper.read.should
|
376
|
-
wrapper.read.should
|
494
|
+
wrapper.read.should.equal "123"
|
495
|
+
wrapper.read.should.equal ""
|
377
496
|
wrapper.rewind
|
378
|
-
wrapper.read.should
|
497
|
+
wrapper.read.should.equal "123"
|
379
498
|
end
|
380
499
|
end
|