lack 2.0.0
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.
- checksums.yaml +7 -0
- data/bin/rackup +5 -0
- data/lib/rack.rb +26 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +166 -0
- data/lib/rack/handler.rb +63 -0
- data/lib/rack/handler/webrick.rb +120 -0
- data/lib/rack/mime.rb +661 -0
- data/lib/rack/mock.rb +198 -0
- data/lib/rack/multipart.rb +31 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +239 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/request.rb +394 -0
- data/lib/rack/response.rb +160 -0
- data/lib/rack/server.rb +258 -0
- data/lib/rack/server/options.rb +121 -0
- data/lib/rack/utils.rb +653 -0
- data/lib/rack/version.rb +3 -0
- data/spec/spec_helper.rb +1 -0
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -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_and_no_name +6 -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_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -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/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_body_proxy.rb +69 -0
- data/test/spec_builder.rb +223 -0
- data/test/spec_chunked.rb +101 -0
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +59 -0
- data/test/spec_head.rb +45 -0
- data/test/spec_lint.rb +522 -0
- data/test/spec_mime.rb +51 -0
- data/test/spec_mock.rb +277 -0
- data/test/spec_multipart.rb +547 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1199 -0
- data/test/spec_response.rb +343 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_utils.rb +635 -0
- data/test/spec_webrick.rb +184 -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 +240 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rack/chunked'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/mock'
|
4
|
+
|
5
|
+
describe Rack::Chunked do
|
6
|
+
def chunked(app)
|
7
|
+
proc do |env|
|
8
|
+
app = Rack::Chunked.new(app)
|
9
|
+
response = Rack::Lint.new(app).call(env)
|
10
|
+
# we want to use body like an array, but it only has #each
|
11
|
+
response[2] = response[2].to_enum.to_a
|
12
|
+
response
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
@env = Rack::MockRequest.
|
18
|
+
env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'chunk responses with no Content-Length' do
|
22
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
|
23
|
+
response = Rack::MockResponse.new(*chunked(app).call(@env))
|
24
|
+
response.headers.should.not.include 'Content-Length'
|
25
|
+
response.headers['Transfer-Encoding'].should.equal 'chunked'
|
26
|
+
response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'chunks empty bodies properly' do
|
30
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] }
|
31
|
+
response = Rack::MockResponse.new(*chunked(app).call(@env))
|
32
|
+
response.headers.should.not.include 'Content-Length'
|
33
|
+
response.headers['Transfer-Encoding'].should.equal 'chunked'
|
34
|
+
response.body.should.equal "0\r\n\r\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'chunks encoded bodies properly' do
|
38
|
+
body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
|
39
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] }
|
40
|
+
response = Rack::MockResponse.new(*chunked(app).call(@env))
|
41
|
+
response.headers.should.not.include 'Content-Length'
|
42
|
+
response.headers['Transfer-Encoding'].should.equal 'chunked'
|
43
|
+
response.body.encoding.to_s.should.equal "ASCII-8BIT"
|
44
|
+
response.body.should.equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".force_encoding("BINARY")
|
45
|
+
end if RUBY_VERSION >= "1.9"
|
46
|
+
|
47
|
+
should 'not modify response when Content-Length header present' do
|
48
|
+
app = lambda { |env|
|
49
|
+
[200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']]
|
50
|
+
}
|
51
|
+
status, headers, body = chunked(app).call(@env)
|
52
|
+
status.should.equal 200
|
53
|
+
headers.should.not.include 'Transfer-Encoding'
|
54
|
+
headers.should.include 'Content-Length'
|
55
|
+
body.join.should.equal 'Hello World!'
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'not modify response when client is HTTP/1.0' do
|
59
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
|
60
|
+
@env['HTTP_VERSION'] = 'HTTP/1.0'
|
61
|
+
status, headers, body = chunked(app).call(@env)
|
62
|
+
status.should.equal 200
|
63
|
+
headers.should.not.include 'Transfer-Encoding'
|
64
|
+
body.join.should.equal 'Hello World!'
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'not modify response when client is ancient, pre-HTTP/1.0' do
|
68
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
|
69
|
+
check = lambda do
|
70
|
+
status, headers, body = chunked(app).call(@env.dup)
|
71
|
+
status.should.equal 200
|
72
|
+
headers.should.not.include 'Transfer-Encoding'
|
73
|
+
body.join.should.equal 'Hello World!'
|
74
|
+
end
|
75
|
+
|
76
|
+
@env.delete('HTTP_VERSION') # unicorn will do this on pre-HTTP/1.0 requests
|
77
|
+
check.call
|
78
|
+
|
79
|
+
@env['HTTP_VERSION'] = 'HTTP/0.9' # not sure if this happens in practice
|
80
|
+
check.call
|
81
|
+
end
|
82
|
+
|
83
|
+
should 'not modify response when Transfer-Encoding header already present' do
|
84
|
+
app = lambda { |env|
|
85
|
+
[200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
|
86
|
+
}
|
87
|
+
status, headers, body = chunked(app).call(@env)
|
88
|
+
status.should.equal 200
|
89
|
+
headers['Transfer-Encoding'].should.equal 'identity'
|
90
|
+
body.join.should.equal 'Hello World!'
|
91
|
+
end
|
92
|
+
|
93
|
+
[100, 204, 205, 304].each do |status_code|
|
94
|
+
should "not modify response when status code is #{status_code}" do
|
95
|
+
app = lambda { |env| [status_code, {}, []] }
|
96
|
+
status, headers, _ = chunked(app).call(@env)
|
97
|
+
status.should.equal status_code
|
98
|
+
headers.should.not.include 'Transfer-Encoding'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/test/spec_file.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rack/file'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/mock'
|
4
|
+
|
5
|
+
describe Rack::File do
|
6
|
+
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
|
7
|
+
|
8
|
+
def file(*args)
|
9
|
+
Rack::Lint.new Rack::File.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "serve files" do
|
13
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test")
|
14
|
+
|
15
|
+
res.should.be.ok
|
16
|
+
res.should =~ /ruby/
|
17
|
+
end
|
18
|
+
|
19
|
+
should "set Last-Modified header" do
|
20
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test")
|
21
|
+
|
22
|
+
path = File.join(DOCROOT, "/cgi/test")
|
23
|
+
|
24
|
+
res.should.be.ok
|
25
|
+
res["Last-Modified"].should.equal File.mtime(path).httpdate
|
26
|
+
end
|
27
|
+
|
28
|
+
should "return 304 if file isn't modified since last serve" do
|
29
|
+
path = File.join(DOCROOT, "/cgi/test")
|
30
|
+
res = Rack::MockRequest.new(file(DOCROOT)).
|
31
|
+
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
|
32
|
+
|
33
|
+
res.status.should.equal 304
|
34
|
+
res.body.should.be.empty
|
35
|
+
end
|
36
|
+
|
37
|
+
should "return the file if it's modified since last serve" do
|
38
|
+
path = File.join(DOCROOT, "/cgi/test")
|
39
|
+
res = Rack::MockRequest.new(file(DOCROOT)).
|
40
|
+
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
|
41
|
+
|
42
|
+
res.should.be.ok
|
43
|
+
end
|
44
|
+
|
45
|
+
should "serve files with URL encoded filenames" do
|
46
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%74%65%73%74") # "/cgi/test"
|
47
|
+
|
48
|
+
res.should.be.ok
|
49
|
+
res.should =~ /ruby/
|
50
|
+
end
|
51
|
+
|
52
|
+
should "allow safe directory traversal" do
|
53
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
54
|
+
|
55
|
+
res = req.get('/cgi/../cgi/test')
|
56
|
+
res.should.be.successful
|
57
|
+
|
58
|
+
res = req.get('.')
|
59
|
+
res.should.be.not_found
|
60
|
+
|
61
|
+
res = req.get("test/..")
|
62
|
+
res.should.be.not_found
|
63
|
+
end
|
64
|
+
|
65
|
+
should "not allow unsafe directory traversal" do
|
66
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
67
|
+
|
68
|
+
res = req.get("/../README.rdoc")
|
69
|
+
res.should.be.client_error
|
70
|
+
|
71
|
+
res = req.get("../test/spec_file.rb")
|
72
|
+
res.should.be.client_error
|
73
|
+
|
74
|
+
res = req.get("../README.rdoc")
|
75
|
+
res.should.be.client_error
|
76
|
+
|
77
|
+
res.should.be.not_found
|
78
|
+
end
|
79
|
+
|
80
|
+
should "allow files with .. in their name" do
|
81
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
82
|
+
res = req.get("/cgi/..test")
|
83
|
+
res.should.be.not_found
|
84
|
+
|
85
|
+
res = req.get("/cgi/test..")
|
86
|
+
res.should.be.not_found
|
87
|
+
|
88
|
+
res = req.get("/cgi../test..")
|
89
|
+
res.should.be.not_found
|
90
|
+
end
|
91
|
+
|
92
|
+
should "not allow unsafe directory traversal with encoded periods" do
|
93
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/%2E%2E/README")
|
94
|
+
|
95
|
+
res.should.be.client_error?
|
96
|
+
res.should.be.not_found
|
97
|
+
end
|
98
|
+
|
99
|
+
should "allow safe directory traversal with encoded periods" do
|
100
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%2E%2E/cgi/test")
|
101
|
+
|
102
|
+
res.should.be.successful
|
103
|
+
end
|
104
|
+
|
105
|
+
should "404 if it can't find the file" do
|
106
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/blubb")
|
107
|
+
|
108
|
+
res.should.be.not_found
|
109
|
+
end
|
110
|
+
|
111
|
+
should "detect SystemCallErrors" do
|
112
|
+
res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi")
|
113
|
+
|
114
|
+
res.should.be.not_found
|
115
|
+
end
|
116
|
+
|
117
|
+
should "return bodies that respond to #to_path" do
|
118
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
119
|
+
status, _, body = Rack::File.new(DOCROOT).call(env)
|
120
|
+
|
121
|
+
path = File.join(DOCROOT, "/cgi/test")
|
122
|
+
|
123
|
+
status.should.equal 200
|
124
|
+
body.should.respond_to :to_path
|
125
|
+
body.to_path.should.equal path
|
126
|
+
end
|
127
|
+
|
128
|
+
should "return correct byte range in body" do
|
129
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
130
|
+
env["HTTP_RANGE"] = "bytes=22-33"
|
131
|
+
res = Rack::MockResponse.new(*file(DOCROOT).call(env))
|
132
|
+
|
133
|
+
res.status.should.equal 206
|
134
|
+
res["Content-Length"].should.equal "12"
|
135
|
+
res["Content-Range"].should.equal "bytes 22-33/193"
|
136
|
+
res.body.should.equal "-*- ruby -*-"
|
137
|
+
end
|
138
|
+
|
139
|
+
should "return error for unsatisfiable byte range" do
|
140
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
141
|
+
env["HTTP_RANGE"] = "bytes=1234-5678"
|
142
|
+
res = Rack::MockResponse.new(*file(DOCROOT).call(env))
|
143
|
+
|
144
|
+
res.status.should.equal 416
|
145
|
+
res["Content-Range"].should.equal "bytes */193"
|
146
|
+
end
|
147
|
+
|
148
|
+
should "support custom http headers" do
|
149
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
150
|
+
status, heads, _ = file(DOCROOT, 'Cache-Control' => 'public, max-age=38',
|
151
|
+
'Access-Control-Allow-Origin' => '*').call(env)
|
152
|
+
|
153
|
+
status.should.equal 200
|
154
|
+
heads['Cache-Control'].should.equal 'public, max-age=38'
|
155
|
+
heads['Access-Control-Allow-Origin'].should.equal '*'
|
156
|
+
end
|
157
|
+
|
158
|
+
should "support not add custom http headers if none are supplied" do
|
159
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
160
|
+
status, heads, _ = file(DOCROOT).call(env)
|
161
|
+
|
162
|
+
status.should.equal 200
|
163
|
+
heads['Cache-Control'].should.equal nil
|
164
|
+
heads['Access-Control-Allow-Origin'].should.equal nil
|
165
|
+
end
|
166
|
+
|
167
|
+
should "only support GET, HEAD, and OPTIONS requests" do
|
168
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
169
|
+
|
170
|
+
forbidden = %w[post put patch delete]
|
171
|
+
forbidden.each do |method|
|
172
|
+
res = req.send(method, "/cgi/test")
|
173
|
+
res.should.be.client_error
|
174
|
+
res.should.be.method_not_allowed
|
175
|
+
res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS)
|
176
|
+
end
|
177
|
+
|
178
|
+
allowed = %w[get head options]
|
179
|
+
allowed.each do |method|
|
180
|
+
res = req.send(method, "/cgi/test")
|
181
|
+
res.should.be.successful
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
should "set Allow correctly for OPTIONS requests" do
|
186
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
187
|
+
res = req.options('/cgi/test')
|
188
|
+
res.should.be.successful
|
189
|
+
res.headers['Allow'].should.not.equal nil
|
190
|
+
res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS)
|
191
|
+
end
|
192
|
+
|
193
|
+
should "set Content-Length correctly for HEAD requests" do
|
194
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
195
|
+
res = req.head "/cgi/test"
|
196
|
+
res.should.be.successful
|
197
|
+
res['Content-Length'].should.equal "193"
|
198
|
+
end
|
199
|
+
|
200
|
+
should "default to a mime type of text/plain" do
|
201
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
202
|
+
res = req.get "/cgi/test"
|
203
|
+
res.should.be.successful
|
204
|
+
res['Content-Type'].should.equal "text/plain"
|
205
|
+
end
|
206
|
+
|
207
|
+
should "allow the default mime type to be set" do
|
208
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, 'application/octet-stream')))
|
209
|
+
res = req.get "/cgi/test"
|
210
|
+
res.should.be.successful
|
211
|
+
res['Content-Type'].should.equal "application/octet-stream"
|
212
|
+
end
|
213
|
+
|
214
|
+
should "not set Content-Type if the mime type is not set" do
|
215
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, nil)))
|
216
|
+
res = req.get "/cgi/test"
|
217
|
+
res.should.be.successful
|
218
|
+
res['Content-Type'].should.equal nil
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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 LoadError if handler doesn't exist" do
|
23
|
+
lambda {
|
24
|
+
Rack::Handler.get('boom')
|
25
|
+
}.should.raise(LoadError)
|
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 LoadError
|
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
|
+
|
50
|
+
should "allow autoloaded handlers to be registered properly while being loaded" do
|
51
|
+
path = File.expand_path('../registering_handler', __FILE__)
|
52
|
+
begin
|
53
|
+
$LOAD_PATH.push path
|
54
|
+
Rack::Handler.get('registering_myself').should.equal Rack::Handler::RegisteringMyself
|
55
|
+
ensure
|
56
|
+
$LOAD_PATH.delete path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/test/spec_head.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rack/head'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/mock'
|
4
|
+
|
5
|
+
describe Rack::Head do
|
6
|
+
|
7
|
+
def test_response(headers = {})
|
8
|
+
body = StringIO.new "foo"
|
9
|
+
app = lambda do |env|
|
10
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, body]
|
11
|
+
end
|
12
|
+
request = Rack::MockRequest.env_for("/", headers)
|
13
|
+
response = Rack::Lint.new(Rack::Head.new(app)).call(request)
|
14
|
+
|
15
|
+
return response, body
|
16
|
+
end
|
17
|
+
|
18
|
+
should "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
|
19
|
+
%w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
|
20
|
+
resp, _ = test_response("REQUEST_METHOD" => type)
|
21
|
+
|
22
|
+
resp[0].should.equal(200)
|
23
|
+
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
24
|
+
resp[2].to_enum.to_a.should.equal(["foo"])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
should "remove body from HEAD requests" do
|
29
|
+
resp, _ = test_response("REQUEST_METHOD" => "HEAD")
|
30
|
+
|
31
|
+
resp[0].should.equal(200)
|
32
|
+
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
33
|
+
resp[2].to_enum.to_a.should.equal([])
|
34
|
+
end
|
35
|
+
|
36
|
+
should "close the body when it is removed" do
|
37
|
+
resp, body = test_response("REQUEST_METHOD" => "HEAD")
|
38
|
+
resp[0].should.equal(200)
|
39
|
+
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
40
|
+
resp[2].to_enum.to_a.should.equal([])
|
41
|
+
body.should.not.be.closed
|
42
|
+
resp[2].close
|
43
|
+
body.should.be.closed
|
44
|
+
end
|
45
|
+
end
|
data/test/spec_lint.rb
ADDED
@@ -0,0 +1,522 @@
|
|
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
|
+
|
235
|
+
# non-Hash header responses should be allowed
|
236
|
+
lambda {
|
237
|
+
Rack::Lint.new(lambda { |env|
|
238
|
+
[200, [%w(Content-Type text/plain), %w(Content-Length 0)], []]
|
239
|
+
}).call(env({}))
|
240
|
+
}.should.not.raise(TypeError)
|
241
|
+
end
|
242
|
+
|
243
|
+
should "notice content-type errors" do
|
244
|
+
# lambda {
|
245
|
+
# Rack::Lint.new(lambda { |env|
|
246
|
+
# [200, {"Content-length" => "0"}, []]
|
247
|
+
# }).call(env({}))
|
248
|
+
# }.should.raise(Rack::Lint::LintError).
|
249
|
+
# message.should.match(/No Content-Type/)
|
250
|
+
|
251
|
+
[100, 101, 204, 205, 304].each do |status|
|
252
|
+
lambda {
|
253
|
+
Rack::Lint.new(lambda { |env|
|
254
|
+
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
255
|
+
}).call(env({}))
|
256
|
+
}.should.raise(Rack::Lint::LintError).
|
257
|
+
message.should.match(/Content-Type header found/)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
should "notice content-length errors" do
|
262
|
+
[100, 101, 204, 205, 304].each do |status|
|
263
|
+
lambda {
|
264
|
+
Rack::Lint.new(lambda { |env|
|
265
|
+
[status, {"Content-length" => "0"}, []]
|
266
|
+
}).call(env({}))
|
267
|
+
}.should.raise(Rack::Lint::LintError).
|
268
|
+
message.should.match(/Content-Length header found/)
|
269
|
+
end
|
270
|
+
|
271
|
+
lambda {
|
272
|
+
Rack::Lint.new(lambda { |env|
|
273
|
+
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
|
274
|
+
}).call(env({}))[2].each { }
|
275
|
+
}.should.raise(Rack::Lint::LintError).
|
276
|
+
message.should.match(/Content-Length header was 1, but should be 0/)
|
277
|
+
end
|
278
|
+
|
279
|
+
should "notice body errors" do
|
280
|
+
lambda {
|
281
|
+
body = Rack::Lint.new(lambda { |env|
|
282
|
+
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
|
283
|
+
}).call(env({}))[2]
|
284
|
+
body.each { |part| }
|
285
|
+
}.should.raise(Rack::Lint::LintError).
|
286
|
+
message.should.match(/yielded non-string/)
|
287
|
+
end
|
288
|
+
|
289
|
+
should "notice input handling errors" do
|
290
|
+
lambda {
|
291
|
+
Rack::Lint.new(lambda { |env|
|
292
|
+
env["rack.input"].gets("\r\n")
|
293
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
294
|
+
}).call(env({}))
|
295
|
+
}.should.raise(Rack::Lint::LintError).
|
296
|
+
message.should.match(/gets called with arguments/)
|
297
|
+
|
298
|
+
lambda {
|
299
|
+
Rack::Lint.new(lambda { |env|
|
300
|
+
env["rack.input"].read(1, 2, 3)
|
301
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
302
|
+
}).call(env({}))
|
303
|
+
}.should.raise(Rack::Lint::LintError).
|
304
|
+
message.should.match(/read called with too many arguments/)
|
305
|
+
|
306
|
+
lambda {
|
307
|
+
Rack::Lint.new(lambda { |env|
|
308
|
+
env["rack.input"].read("foo")
|
309
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
310
|
+
}).call(env({}))
|
311
|
+
}.should.raise(Rack::Lint::LintError).
|
312
|
+
message.should.match(/read called with non-integer and non-nil length/)
|
313
|
+
|
314
|
+
lambda {
|
315
|
+
Rack::Lint.new(lambda { |env|
|
316
|
+
env["rack.input"].read(-1)
|
317
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
318
|
+
}).call(env({}))
|
319
|
+
}.should.raise(Rack::Lint::LintError).
|
320
|
+
message.should.match(/read called with a negative length/)
|
321
|
+
|
322
|
+
lambda {
|
323
|
+
Rack::Lint.new(lambda { |env|
|
324
|
+
env["rack.input"].read(nil, nil)
|
325
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
326
|
+
}).call(env({}))
|
327
|
+
}.should.raise(Rack::Lint::LintError).
|
328
|
+
message.should.match(/read called with non-String buffer/)
|
329
|
+
|
330
|
+
lambda {
|
331
|
+
Rack::Lint.new(lambda { |env|
|
332
|
+
env["rack.input"].read(nil, 1)
|
333
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
334
|
+
}).call(env({}))
|
335
|
+
}.should.raise(Rack::Lint::LintError).
|
336
|
+
message.should.match(/read called with non-String buffer/)
|
337
|
+
|
338
|
+
lambda {
|
339
|
+
Rack::Lint.new(lambda { |env|
|
340
|
+
env["rack.input"].rewind(0)
|
341
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
342
|
+
}).call(env({}))
|
343
|
+
}.should.raise(Rack::Lint::LintError).
|
344
|
+
message.should.match(/rewind called with arguments/)
|
345
|
+
|
346
|
+
weirdio = Object.new
|
347
|
+
class << weirdio
|
348
|
+
def gets
|
349
|
+
42
|
350
|
+
end
|
351
|
+
|
352
|
+
def read
|
353
|
+
23
|
354
|
+
end
|
355
|
+
|
356
|
+
def each
|
357
|
+
yield 23
|
358
|
+
yield 42
|
359
|
+
end
|
360
|
+
|
361
|
+
def rewind
|
362
|
+
raise Errno::ESPIPE, "Errno::ESPIPE"
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
eof_weirdio = Object.new
|
367
|
+
class << eof_weirdio
|
368
|
+
def gets
|
369
|
+
nil
|
370
|
+
end
|
371
|
+
|
372
|
+
def read(*args)
|
373
|
+
nil
|
374
|
+
end
|
375
|
+
|
376
|
+
def each
|
377
|
+
end
|
378
|
+
|
379
|
+
def rewind
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
lambda {
|
384
|
+
Rack::Lint.new(lambda { |env|
|
385
|
+
env["rack.input"].gets
|
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(/gets didn't return a String/)
|
390
|
+
|
391
|
+
lambda {
|
392
|
+
Rack::Lint.new(lambda { |env|
|
393
|
+
env["rack.input"].each { |x| }
|
394
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
395
|
+
}).call(env("rack.input" => weirdio))
|
396
|
+
}.should.raise(Rack::Lint::LintError).
|
397
|
+
message.should.match(/each didn't yield a String/)
|
398
|
+
|
399
|
+
lambda {
|
400
|
+
Rack::Lint.new(lambda { |env|
|
401
|
+
env["rack.input"].read
|
402
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
403
|
+
}).call(env("rack.input" => weirdio))
|
404
|
+
}.should.raise(Rack::Lint::LintError).
|
405
|
+
message.should.match(/read didn't return nil or a String/)
|
406
|
+
|
407
|
+
lambda {
|
408
|
+
Rack::Lint.new(lambda { |env|
|
409
|
+
env["rack.input"].read
|
410
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
411
|
+
}).call(env("rack.input" => eof_weirdio))
|
412
|
+
}.should.raise(Rack::Lint::LintError).
|
413
|
+
message.should.match(/read\(nil\) returned nil on EOF/)
|
414
|
+
|
415
|
+
lambda {
|
416
|
+
Rack::Lint.new(lambda { |env|
|
417
|
+
env["rack.input"].rewind
|
418
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
419
|
+
}).call(env("rack.input" => weirdio))
|
420
|
+
}.should.raise(Rack::Lint::LintError).
|
421
|
+
message.should.match(/rewind raised Errno::ESPIPE/)
|
422
|
+
|
423
|
+
|
424
|
+
lambda {
|
425
|
+
Rack::Lint.new(lambda { |env|
|
426
|
+
env["rack.input"].close
|
427
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
428
|
+
}).call(env({}))
|
429
|
+
}.should.raise(Rack::Lint::LintError).
|
430
|
+
message.should.match(/close must not be called/)
|
431
|
+
end
|
432
|
+
|
433
|
+
should "notice error handling errors" do
|
434
|
+
lambda {
|
435
|
+
Rack::Lint.new(lambda { |env|
|
436
|
+
env["rack.errors"].write(42)
|
437
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
438
|
+
}).call(env({}))
|
439
|
+
}.should.raise(Rack::Lint::LintError).
|
440
|
+
message.should.match(/write not called with a String/)
|
441
|
+
|
442
|
+
lambda {
|
443
|
+
Rack::Lint.new(lambda { |env|
|
444
|
+
env["rack.errors"].close
|
445
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
446
|
+
}).call(env({}))
|
447
|
+
}.should.raise(Rack::Lint::LintError).
|
448
|
+
message.should.match(/close must not be called/)
|
449
|
+
end
|
450
|
+
|
451
|
+
should "notice HEAD errors" do
|
452
|
+
lambda {
|
453
|
+
Rack::Lint.new(lambda { |env|
|
454
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
|
455
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))
|
456
|
+
}.should.not.raise
|
457
|
+
|
458
|
+
lambda {
|
459
|
+
Rack::Lint.new(lambda { |env|
|
460
|
+
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
461
|
+
}).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
|
462
|
+
}.should.raise(Rack::Lint::LintError).
|
463
|
+
message.should.match(/body was given for HEAD/)
|
464
|
+
end
|
465
|
+
|
466
|
+
should "pass valid read calls" do
|
467
|
+
hello_str = "hello world"
|
468
|
+
hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
|
469
|
+
lambda {
|
470
|
+
Rack::Lint.new(lambda { |env|
|
471
|
+
env["rack.input"].read
|
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(0)
|
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(1)
|
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(nil, '')
|
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
|
+
|
504
|
+
lambda {
|
505
|
+
Rack::Lint.new(lambda { |env|
|
506
|
+
env["rack.input"].read(1, '')
|
507
|
+
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
508
|
+
}).call(env({"rack.input" => StringIO.new(hello_str)}))
|
509
|
+
}.should.not.raise(Rack::Lint::LintError)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
describe "Rack::Lint::InputWrapper" do
|
514
|
+
should "delegate :rewind to underlying IO object" do
|
515
|
+
io = StringIO.new("123")
|
516
|
+
wrapper = Rack::Lint::InputWrapper.new(io)
|
517
|
+
wrapper.read.should.equal "123"
|
518
|
+
wrapper.read.should.equal ""
|
519
|
+
wrapper.rewind
|
520
|
+
wrapper.read.should.equal "123"
|
521
|
+
end
|
522
|
+
end
|