rack 2.0.9 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack.rb +63 -60
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +39 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +32 -17
- data/lib/rack/directory.rb +19 -16
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +51 -45
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +79 -26
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -18
- data/lib/rack/session/abstract/id.rb +30 -20
- data/lib/rack/session/cookie.rb +10 -9
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +4 -2
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +59 -72
- data/rack.gemspec +17 -7
- metadata +33 -175
- data/HISTORY.md +0 -505
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -528
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/test/spec_handler.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'rack/handler'
|
3
|
-
|
4
|
-
class Rack::Handler::Lobster; end
|
5
|
-
class RockLobster; end
|
6
|
-
|
7
|
-
describe Rack::Handler do
|
8
|
-
it "has registered default handlers" do
|
9
|
-
Rack::Handler.get('cgi').must_equal Rack::Handler::CGI
|
10
|
-
Rack::Handler.get('webrick').must_equal Rack::Handler::WEBrick
|
11
|
-
|
12
|
-
begin
|
13
|
-
Rack::Handler.get('fastcgi').must_equal Rack::Handler::FastCGI
|
14
|
-
rescue LoadError
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
it "raise LoadError if handler doesn't exist" do
|
19
|
-
lambda {
|
20
|
-
Rack::Handler.get('boom')
|
21
|
-
}.must_raise(LoadError)
|
22
|
-
|
23
|
-
lambda {
|
24
|
-
Rack::Handler.get('Object')
|
25
|
-
}.must_raise(LoadError)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "get unregistered, but already required, handler by name" do
|
29
|
-
Rack::Handler.get('Lobster').must_equal Rack::Handler::Lobster
|
30
|
-
end
|
31
|
-
|
32
|
-
it "register custom handler" do
|
33
|
-
Rack::Handler.register('rock_lobster', 'RockLobster')
|
34
|
-
Rack::Handler.get('rock_lobster').must_equal RockLobster
|
35
|
-
end
|
36
|
-
|
37
|
-
it "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').must_equal Rack::Handler::Unregistered
|
41
|
-
lambda { Rack::Handler.get('UnRegistered') }.must_raise LoadError
|
42
|
-
Rack::Handler.get('UnregisteredLongOne').must_equal Rack::Handler::UnregisteredLongOne
|
43
|
-
ensure
|
44
|
-
$LOAD_PATH.delete File.expand_path('../unregistered_handler', __FILE__)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
it "allow autoloaded handlers to be registered properly while being loaded" do
|
49
|
-
path = File.expand_path('../registering_handler', __FILE__)
|
50
|
-
begin
|
51
|
-
$LOAD_PATH.push path
|
52
|
-
Rack::Handler.get('registering_myself').must_equal Rack::Handler::RegisteringMyself
|
53
|
-
ensure
|
54
|
-
$LOAD_PATH.delete path
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
data/test/spec_head.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'rack/head'
|
3
|
-
require 'rack/lint'
|
4
|
-
require 'rack/mock'
|
5
|
-
|
6
|
-
describe Rack::Head do
|
7
|
-
|
8
|
-
def test_response(headers = {})
|
9
|
-
body = StringIO.new "foo"
|
10
|
-
app = lambda do |env|
|
11
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, body]
|
12
|
-
end
|
13
|
-
request = Rack::MockRequest.env_for("/", headers)
|
14
|
-
response = Rack::Lint.new(Rack::Head.new(app)).call(request)
|
15
|
-
|
16
|
-
return response, body
|
17
|
-
end
|
18
|
-
|
19
|
-
it "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
|
20
|
-
%w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
|
21
|
-
resp, _ = test_response("REQUEST_METHOD" => type)
|
22
|
-
|
23
|
-
resp[0].must_equal 200
|
24
|
-
resp[1].must_equal "Content-type" => "test/plain", "Content-length" => "3"
|
25
|
-
resp[2].to_enum.to_a.must_equal ["foo"]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it "remove body from HEAD requests" do
|
30
|
-
resp, _ = test_response("REQUEST_METHOD" => "HEAD")
|
31
|
-
|
32
|
-
resp[0].must_equal 200
|
33
|
-
resp[1].must_equal "Content-type" => "test/plain", "Content-length" => "3"
|
34
|
-
resp[2].to_enum.to_a.must_equal []
|
35
|
-
end
|
36
|
-
|
37
|
-
it "close the body when it is removed" do
|
38
|
-
resp, body = test_response("REQUEST_METHOD" => "HEAD")
|
39
|
-
resp[0].must_equal 200
|
40
|
-
resp[1].must_equal "Content-type" => "test/plain", "Content-length" => "3"
|
41
|
-
resp[2].to_enum.to_a.must_equal []
|
42
|
-
body.wont_be :closed?
|
43
|
-
resp[2].close
|
44
|
-
body.must_be :closed?
|
45
|
-
end
|
46
|
-
end
|
data/test/spec_lint.rb
DELETED
@@ -1,515 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'stringio'
|
3
|
-
require 'tempfile'
|
4
|
-
require 'rack/lint'
|
5
|
-
require 'rack/mock'
|
6
|
-
|
7
|
-
describe Rack::Lint do
|
8
|
-
def env(*args)
|
9
|
-
Rack::MockRequest.env_for("/", *args)
|
10
|
-
end
|
11
|
-
|
12
|
-
it "pass valid request" do
|
13
|
-
Rack::Lint.new(lambda { |env|
|
14
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
15
|
-
}).call(env({})).first.must_equal 200
|
16
|
-
end
|
17
|
-
|
18
|
-
it "notice fatal errors" do
|
19
|
-
lambda { Rack::Lint.new(nil).call }.must_raise(Rack::Lint::LintError).
|
20
|
-
message.must_match(/No env given/)
|
21
|
-
end
|
22
|
-
|
23
|
-
it "notice environment errors" do
|
24
|
-
lambda { Rack::Lint.new(nil).call 5 }.must_raise(Rack::Lint::LintError).
|
25
|
-
message.must_match(/not a Hash/)
|
26
|
-
|
27
|
-
lambda {
|
28
|
-
e = env
|
29
|
-
e.delete("REQUEST_METHOD")
|
30
|
-
Rack::Lint.new(nil).call(e)
|
31
|
-
}.must_raise(Rack::Lint::LintError).
|
32
|
-
message.must_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
|
-
}.must_raise(Rack::Lint::LintError).
|
39
|
-
message.must_match(/missing required key SERVER_NAME/)
|
40
|
-
|
41
|
-
|
42
|
-
lambda {
|
43
|
-
Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
|
44
|
-
}.must_raise(Rack::Lint::LintError).
|
45
|
-
message.must_match(/contains HTTP_CONTENT_TYPE/)
|
46
|
-
|
47
|
-
lambda {
|
48
|
-
Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
|
49
|
-
}.must_raise(Rack::Lint::LintError).
|
50
|
-
message.must_match(/contains HTTP_CONTENT_LENGTH/)
|
51
|
-
|
52
|
-
lambda {
|
53
|
-
Rack::Lint.new(nil).call(env("FOO" => Object.new))
|
54
|
-
}.must_raise(Rack::Lint::LintError).
|
55
|
-
message.must_match(/non-string value/)
|
56
|
-
|
57
|
-
lambda {
|
58
|
-
Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
|
59
|
-
}.must_raise(Rack::Lint::LintError).
|
60
|
-
message.must_match(/must be an Array/)
|
61
|
-
|
62
|
-
lambda {
|
63
|
-
Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
|
64
|
-
}.must_raise(Rack::Lint::LintError).
|
65
|
-
message.must_match(/url_scheme unknown/)
|
66
|
-
|
67
|
-
lambda {
|
68
|
-
Rack::Lint.new(nil).call(env("rack.session" => []))
|
69
|
-
}.must_raise(Rack::Lint::LintError).
|
70
|
-
message.must_equal "session [] must respond to store and []="
|
71
|
-
|
72
|
-
lambda {
|
73
|
-
Rack::Lint.new(nil).call(env("rack.logger" => []))
|
74
|
-
}.must_raise(Rack::Lint::LintError).
|
75
|
-
message.must_equal "logger [] must respond to info"
|
76
|
-
|
77
|
-
lambda {
|
78
|
-
Rack::Lint.new(nil).call(env("rack.multipart.buffer_size" => 0))
|
79
|
-
}.must_raise(Rack::Lint::LintError).
|
80
|
-
message.must_equal "rack.multipart.buffer_size must be an Integer > 0 if specified"
|
81
|
-
|
82
|
-
lambda {
|
83
|
-
Rack::Lint.new(nil).call(env("rack.multipart.tempfile_factory" => Tempfile))
|
84
|
-
}.must_raise(Rack::Lint::LintError).
|
85
|
-
message.must_equal "rack.multipart.tempfile_factory must respond to #call"
|
86
|
-
|
87
|
-
lambda {
|
88
|
-
Rack::Lint.new(lambda { |env|
|
89
|
-
env['rack.multipart.tempfile_factory'].call("testfile", "text/plain")
|
90
|
-
}).call(env("rack.multipart.tempfile_factory" => lambda { |filename, content_type| Object.new }))
|
91
|
-
}.must_raise(Rack::Lint::LintError).
|
92
|
-
message.must_equal "rack.multipart.tempfile_factory return value must respond to #<<"
|
93
|
-
|
94
|
-
lambda {
|
95
|
-
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
|
96
|
-
}.must_raise(Rack::Lint::LintError).
|
97
|
-
message.must_match(/REQUEST_METHOD/)
|
98
|
-
|
99
|
-
lambda {
|
100
|
-
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
|
101
|
-
}.must_raise(Rack::Lint::LintError).
|
102
|
-
message.must_match(/must start with/)
|
103
|
-
|
104
|
-
lambda {
|
105
|
-
Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
|
106
|
-
}.must_raise(Rack::Lint::LintError).
|
107
|
-
message.must_match(/must start with/)
|
108
|
-
|
109
|
-
lambda {
|
110
|
-
Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
|
111
|
-
}.must_raise(Rack::Lint::LintError).
|
112
|
-
message.must_match(/Invalid CONTENT_LENGTH/)
|
113
|
-
|
114
|
-
lambda {
|
115
|
-
e = env
|
116
|
-
e.delete("PATH_INFO")
|
117
|
-
e.delete("SCRIPT_NAME")
|
118
|
-
Rack::Lint.new(nil).call(e)
|
119
|
-
}.must_raise(Rack::Lint::LintError).
|
120
|
-
message.must_match(/One of .* must be set/)
|
121
|
-
|
122
|
-
lambda {
|
123
|
-
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
|
124
|
-
}.must_raise(Rack::Lint::LintError).
|
125
|
-
message.must_match(/cannot be .* make it ''/)
|
126
|
-
end
|
127
|
-
|
128
|
-
it "notice input errors" do
|
129
|
-
lambda {
|
130
|
-
Rack::Lint.new(nil).call(env("rack.input" => ""))
|
131
|
-
}.must_raise(Rack::Lint::LintError).
|
132
|
-
message.must_match(/does not respond to #gets/)
|
133
|
-
|
134
|
-
lambda {
|
135
|
-
input = Object.new
|
136
|
-
def input.binmode?
|
137
|
-
false
|
138
|
-
end
|
139
|
-
Rack::Lint.new(nil).call(env("rack.input" => input))
|
140
|
-
}.must_raise(Rack::Lint::LintError).
|
141
|
-
message.must_match(/is not opened in binary mode/)
|
142
|
-
|
143
|
-
lambda {
|
144
|
-
input = Object.new
|
145
|
-
def input.external_encoding
|
146
|
-
result = Object.new
|
147
|
-
def result.name
|
148
|
-
"US-ASCII"
|
149
|
-
end
|
150
|
-
result
|
151
|
-
end
|
152
|
-
Rack::Lint.new(nil).call(env("rack.input" => input))
|
153
|
-
}.must_raise(Rack::Lint::LintError).
|
154
|
-
message.must_match(/does not have ASCII-8BIT as its external encoding/)
|
155
|
-
end
|
156
|
-
|
157
|
-
it "notice error errors" do
|
158
|
-
lambda {
|
159
|
-
Rack::Lint.new(nil).call(env("rack.errors" => ""))
|
160
|
-
}.must_raise(Rack::Lint::LintError).
|
161
|
-
message.must_match(/does not respond to #puts/)
|
162
|
-
end
|
163
|
-
|
164
|
-
it "notice status errors" do
|
165
|
-
lambda {
|
166
|
-
Rack::Lint.new(lambda { |env|
|
167
|
-
["cc", {}, ""]
|
168
|
-
}).call(env({}))
|
169
|
-
}.must_raise(Rack::Lint::LintError).
|
170
|
-
message.must_match(/must be >=100 seen as integer/)
|
171
|
-
|
172
|
-
lambda {
|
173
|
-
Rack::Lint.new(lambda { |env|
|
174
|
-
[42, {}, ""]
|
175
|
-
}).call(env({}))
|
176
|
-
}.must_raise(Rack::Lint::LintError).
|
177
|
-
message.must_match(/must be >=100 seen as integer/)
|
178
|
-
end
|
179
|
-
|
180
|
-
it "notice header errors" do
|
181
|
-
lambda {
|
182
|
-
Rack::Lint.new(lambda { |env|
|
183
|
-
[200, Object.new, []]
|
184
|
-
}).call(env({}))
|
185
|
-
}.must_raise(Rack::Lint::LintError).
|
186
|
-
message.must_equal "headers object should respond to #each, but doesn't (got Object as headers)"
|
187
|
-
|
188
|
-
lambda {
|
189
|
-
Rack::Lint.new(lambda { |env|
|
190
|
-
[200, {true=>false}, []]
|
191
|
-
}).call(env({}))
|
192
|
-
}.must_raise(Rack::Lint::LintError).
|
193
|
-
message.must_equal "header key must be a string, was TrueClass"
|
194
|
-
|
195
|
-
lambda {
|
196
|
-
Rack::Lint.new(lambda { |env|
|
197
|
-
[200, {"Status" => "404"}, []]
|
198
|
-
}).call(env({}))
|
199
|
-
}.must_raise(Rack::Lint::LintError).
|
200
|
-
message.must_match(/must not contain Status/)
|
201
|
-
|
202
|
-
# From RFC 7230:<F24><F25>
|
203
|
-
# Most HTTP header field values are defined using common syntax
|
204
|
-
# components (token, quoted-string, and comment) separated by
|
205
|
-
# whitespace or specific delimiting characters. Delimiters are chosen
|
206
|
-
# from the set of US-ASCII visual characters not allowed in a token
|
207
|
-
# (DQUOTE and "(),/:;<=>?@[\]{}").
|
208
|
-
#
|
209
|
-
# token = 1*tchar
|
210
|
-
#
|
211
|
-
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
212
|
-
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
213
|
-
# / DIGIT / ALPHA
|
214
|
-
# ; any VCHAR, except delimiters
|
215
|
-
invalid_headers = 0.upto(31).map(&:chr) + %W<( ) , / : ; < = > ? @ [ \\ ] { } \x7F>
|
216
|
-
invalid_headers.each do |invalid_header|
|
217
|
-
lambda {
|
218
|
-
Rack::Lint.new(lambda { |env|
|
219
|
-
[200, {invalid_header => "text/plain"}, []]
|
220
|
-
}).call(env({}))
|
221
|
-
}.must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}").
|
222
|
-
message.must_equal("invalid header name: #{invalid_header}")
|
223
|
-
end
|
224
|
-
valid_headers = 0.upto(127).map(&:chr) - invalid_headers
|
225
|
-
valid_headers.each do |valid_header|
|
226
|
-
Rack::Lint.new(lambda { |env|
|
227
|
-
[200, {valid_header => "text/plain"}, []]
|
228
|
-
}).call(env({})).first.must_equal 200
|
229
|
-
end
|
230
|
-
|
231
|
-
lambda {
|
232
|
-
Rack::Lint.new(lambda { |env|
|
233
|
-
[200, {"Foo" => Object.new}, []]
|
234
|
-
}).call(env({}))
|
235
|
-
}.must_raise(Rack::Lint::LintError).
|
236
|
-
message.must_equal "a header value must be a String, but the value of 'Foo' is a Object"
|
237
|
-
|
238
|
-
lambda {
|
239
|
-
Rack::Lint.new(lambda { |env|
|
240
|
-
[200, {"Foo" => [1, 2, 3]}, []]
|
241
|
-
}).call(env({}))
|
242
|
-
}.must_raise(Rack::Lint::LintError).
|
243
|
-
message.must_equal "a header value must be a String, but the value of 'Foo' is a Array"
|
244
|
-
|
245
|
-
|
246
|
-
lambda {
|
247
|
-
Rack::Lint.new(lambda { |env|
|
248
|
-
[200, {"Foo-Bar" => "text\000plain"}, []]
|
249
|
-
}).call(env({}))
|
250
|
-
}.must_raise(Rack::Lint::LintError).
|
251
|
-
message.must_match(/invalid header/)
|
252
|
-
|
253
|
-
# line ends (010).must_be :allowed in header values.?
|
254
|
-
Rack::Lint.new(lambda { |env|
|
255
|
-
[200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
|
256
|
-
}).call(env({})).first.must_equal 200
|
257
|
-
|
258
|
-
# non-Hash header responses.must_be :allowed?
|
259
|
-
Rack::Lint.new(lambda { |env|
|
260
|
-
[200, [%w(Content-Type text/plain), %w(Content-Length 0)], []]
|
261
|
-
}).call(env({})).first.must_equal 200
|
262
|
-
end
|
263
|
-
|
264
|
-
it "notice content-type errors" do
|
265
|
-
# lambda {
|
266
|
-
# Rack::Lint.new(lambda { |env|
|
267
|
-
# [200, {"Content-length" => "0"}, []]
|
268
|
-
# }).call(env({}))
|
269
|
-
# }.must_raise(Rack::Lint::LintError).
|
270
|
-
# message.must_match(/No Content-Type/)
|
271
|
-
|
272
|
-
[100, 101, 204, 304].each do |status|
|
273
|
-
lambda {
|
274
|
-
Rack::Lint.new(lambda { |env|
|
275
|
-
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
276
|
-
}).call(env({}))
|
277
|
-
}.must_raise(Rack::Lint::LintError).
|
278
|
-
message.must_match(/Content-Type header found/)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
it "notice content-length errors" do
|
283
|
-
[100, 101, 204, 304].each do |status|
|
284
|
-
lambda {
|
285
|
-
Rack::Lint.new(lambda { |env|
|
286
|
-
[status, {"Content-length" => "0"}, []]
|
287
|
-
}).call(env({}))
|
288
|
-
}.must_raise(Rack::Lint::LintError).
|
289
|
-
message.must_match(/Content-Length header found/)
|
290
|
-
end
|
291
|
-
|
292
|
-
lambda {
|
293
|
-
Rack::Lint.new(lambda { |env|
|
294
|
-
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
|
295
|
-
}).call(env({}))[2].each { }
|
296
|
-
}.must_raise(Rack::Lint::LintError).
|
297
|
-
message.must_match(/Content-Length header was 1, but should be 0/)
|
298
|
-
end
|
299
|
-
|
300
|
-
it "notice body errors" do
|
301
|
-
lambda {
|
302
|
-
body = Rack::Lint.new(lambda { |env|
|
303
|
-
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
|
304
|
-
}).call(env({}))[2]
|
305
|
-
body.each { |part| }
|
306
|
-
}.must_raise(Rack::Lint::LintError).
|
307
|
-
message.must_match(/yielded non-string/)
|
308
|
-
end
|
309
|
-
|
310
|
-
it "notice input handling errors" do
|
311
|
-
lambda {
|
312
|
-
Rack::Lint.new(lambda { |env|
|
313
|
-
env["rack.input"].gets("\r\n")
|
314
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
315
|
-
}).call(env({}))
|
316
|
-
}.must_raise(Rack::Lint::LintError).
|
317
|
-
message.must_match(/gets called with arguments/)
|
318
|
-
|
319
|
-
lambda {
|
320
|
-
Rack::Lint.new(lambda { |env|
|
321
|
-
env["rack.input"].read(1, 2, 3)
|
322
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
323
|
-
}).call(env({}))
|
324
|
-
}.must_raise(Rack::Lint::LintError).
|
325
|
-
message.must_match(/read called with too many arguments/)
|
326
|
-
|
327
|
-
lambda {
|
328
|
-
Rack::Lint.new(lambda { |env|
|
329
|
-
env["rack.input"].read("foo")
|
330
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
331
|
-
}).call(env({}))
|
332
|
-
}.must_raise(Rack::Lint::LintError).
|
333
|
-
message.must_match(/read called with non-integer and non-nil length/)
|
334
|
-
|
335
|
-
lambda {
|
336
|
-
Rack::Lint.new(lambda { |env|
|
337
|
-
env["rack.input"].read(-1)
|
338
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
339
|
-
}).call(env({}))
|
340
|
-
}.must_raise(Rack::Lint::LintError).
|
341
|
-
message.must_match(/read called with a negative length/)
|
342
|
-
|
343
|
-
lambda {
|
344
|
-
Rack::Lint.new(lambda { |env|
|
345
|
-
env["rack.input"].read(nil, nil)
|
346
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
347
|
-
}).call(env({}))
|
348
|
-
}.must_raise(Rack::Lint::LintError).
|
349
|
-
message.must_match(/read called with non-String buffer/)
|
350
|
-
|
351
|
-
lambda {
|
352
|
-
Rack::Lint.new(lambda { |env|
|
353
|
-
env["rack.input"].read(nil, 1)
|
354
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
355
|
-
}).call(env({}))
|
356
|
-
}.must_raise(Rack::Lint::LintError).
|
357
|
-
message.must_match(/read called with non-String buffer/)
|
358
|
-
|
359
|
-
lambda {
|
360
|
-
Rack::Lint.new(lambda { |env|
|
361
|
-
env["rack.input"].rewind(0)
|
362
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
363
|
-
}).call(env({}))
|
364
|
-
}.must_raise(Rack::Lint::LintError).
|
365
|
-
message.must_match(/rewind called with arguments/)
|
366
|
-
|
367
|
-
weirdio = Object.new
|
368
|
-
class << weirdio
|
369
|
-
def gets
|
370
|
-
42
|
371
|
-
end
|
372
|
-
|
373
|
-
def read
|
374
|
-
23
|
375
|
-
end
|
376
|
-
|
377
|
-
def each
|
378
|
-
yield 23
|
379
|
-
yield 42
|
380
|
-
end
|
381
|
-
|
382
|
-
def rewind
|
383
|
-
raise Errno::ESPIPE, "Errno::ESPIPE"
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
|
-
eof_weirdio = Object.new
|
388
|
-
class << eof_weirdio
|
389
|
-
def gets
|
390
|
-
nil
|
391
|
-
end
|
392
|
-
|
393
|
-
def read(*args)
|
394
|
-
nil
|
395
|
-
end
|
396
|
-
|
397
|
-
def each
|
398
|
-
end
|
399
|
-
|
400
|
-
def rewind
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
lambda {
|
405
|
-
Rack::Lint.new(lambda { |env|
|
406
|
-
env["rack.input"].gets
|
407
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
408
|
-
}).call(env("rack.input" => weirdio))
|
409
|
-
}.must_raise(Rack::Lint::LintError).
|
410
|
-
message.must_match(/gets didn't return a String/)
|
411
|
-
|
412
|
-
lambda {
|
413
|
-
Rack::Lint.new(lambda { |env|
|
414
|
-
env["rack.input"].each { |x| }
|
415
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
416
|
-
}).call(env("rack.input" => weirdio))
|
417
|
-
}.must_raise(Rack::Lint::LintError).
|
418
|
-
message.must_match(/each didn't yield a String/)
|
419
|
-
|
420
|
-
lambda {
|
421
|
-
Rack::Lint.new(lambda { |env|
|
422
|
-
env["rack.input"].read
|
423
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
424
|
-
}).call(env("rack.input" => weirdio))
|
425
|
-
}.must_raise(Rack::Lint::LintError).
|
426
|
-
message.must_match(/read didn't return nil or a String/)
|
427
|
-
|
428
|
-
lambda {
|
429
|
-
Rack::Lint.new(lambda { |env|
|
430
|
-
env["rack.input"].read
|
431
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
432
|
-
}).call(env("rack.input" => eof_weirdio))
|
433
|
-
}.must_raise(Rack::Lint::LintError).
|
434
|
-
message.must_match(/read\(nil\) returned nil on EOF/)
|
435
|
-
|
436
|
-
lambda {
|
437
|
-
Rack::Lint.new(lambda { |env|
|
438
|
-
env["rack.input"].rewind
|
439
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
440
|
-
}).call(env("rack.input" => weirdio))
|
441
|
-
}.must_raise(Rack::Lint::LintError).
|
442
|
-
message.must_match(/rewind raised Errno::ESPIPE/)
|
443
|
-
|
444
|
-
|
445
|
-
lambda {
|
446
|
-
Rack::Lint.new(lambda { |env|
|
447
|
-
env["rack.input"].close
|
448
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
449
|
-
}).call(env({}))
|
450
|
-
}.must_raise(Rack::Lint::LintError).
|
451
|
-
message.must_match(/close must not be called/)
|
452
|
-
end
|
453
|
-
|
454
|
-
it "notice error handling errors" do
|
455
|
-
lambda {
|
456
|
-
Rack::Lint.new(lambda { |env|
|
457
|
-
env["rack.errors"].write(42)
|
458
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
459
|
-
}).call(env({}))
|
460
|
-
}.must_raise(Rack::Lint::LintError).
|
461
|
-
message.must_match(/write not called with a String/)
|
462
|
-
|
463
|
-
lambda {
|
464
|
-
Rack::Lint.new(lambda { |env|
|
465
|
-
env["rack.errors"].close
|
466
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
467
|
-
}).call(env({}))
|
468
|
-
}.must_raise(Rack::Lint::LintError).
|
469
|
-
message.must_match(/close must not be called/)
|
470
|
-
end
|
471
|
-
|
472
|
-
it "notice HEAD errors" do
|
473
|
-
Rack::Lint.new(lambda { |env|
|
474
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
|
475
|
-
}).call(env({"REQUEST_METHOD" => "HEAD"})).first.must_equal 200
|
476
|
-
|
477
|
-
lambda {
|
478
|
-
Rack::Lint.new(lambda { |env|
|
479
|
-
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
|
480
|
-
}).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
|
481
|
-
}.must_raise(Rack::Lint::LintError).
|
482
|
-
message.must_match(/body was given for HEAD/)
|
483
|
-
end
|
484
|
-
|
485
|
-
def assert_lint(*args)
|
486
|
-
hello_str = "hello world"
|
487
|
-
hello_str.force_encoding(Encoding::ASCII_8BIT)
|
488
|
-
|
489
|
-
Rack::Lint.new(lambda { |env|
|
490
|
-
env["rack.input"].send(:read, *args)
|
491
|
-
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
|
492
|
-
}).call(env({"rack.input" => StringIO.new(hello_str)})).
|
493
|
-
first.must_equal 201
|
494
|
-
end
|
495
|
-
|
496
|
-
it "pass valid read calls" do
|
497
|
-
assert_lint
|
498
|
-
assert_lint 0
|
499
|
-
assert_lint 1
|
500
|
-
assert_lint nil
|
501
|
-
assert_lint nil, ''
|
502
|
-
assert_lint 1, ''
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
describe "Rack::Lint::InputWrapper" do
|
507
|
-
it "delegate :rewind to underlying IO object" do
|
508
|
-
io = StringIO.new("123")
|
509
|
-
wrapper = Rack::Lint::InputWrapper.new(io)
|
510
|
-
wrapper.read.must_equal "123"
|
511
|
-
wrapper.read.must_equal ""
|
512
|
-
wrapper.rewind
|
513
|
-
wrapper.read.must_equal "123"
|
514
|
-
end
|
515
|
-
end
|