rack 1.2.8 → 1.3.0.beta
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/README +9 -177
- data/Rakefile +2 -1
- data/SPEC +2 -2
- data/lib/rack.rb +2 -13
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/digest/md5.rb +6 -2
- data/lib/rack/auth/digest/params.rb +5 -7
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/backports/uri/common.rb +64 -0
- data/lib/rack/builder.rb +60 -3
- data/lib/rack/chunked.rb +29 -22
- data/lib/rack/conditionalget.rb +35 -16
- data/lib/rack/content_length.rb +3 -3
- data/lib/rack/deflater.rb +5 -2
- data/lib/rack/etag.rb +38 -10
- data/lib/rack/file.rb +76 -43
- data/lib/rack/handler.rb +13 -7
- data/lib/rack/handler/cgi.rb +0 -2
- data/lib/rack/handler/fastcgi.rb +13 -4
- data/lib/rack/handler/lsws.rb +0 -2
- data/lib/rack/handler/mongrel.rb +12 -2
- data/lib/rack/handler/scgi.rb +9 -1
- data/lib/rack/handler/thin.rb +7 -1
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/lint.rb +2 -2
- data/lib/rack/lock.rb +29 -3
- data/lib/rack/methodoverride.rb +1 -1
- data/lib/rack/mime.rb +2 -2
- data/lib/rack/mock.rb +28 -33
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +164 -0
- data/lib/rack/multipart/uploaded_file.rb +30 -0
- data/lib/rack/request.rb +55 -19
- data/lib/rack/response.rb +10 -8
- data/lib/rack/sendfile.rb +14 -18
- data/lib/rack/server.rb +55 -8
- data/lib/rack/session/abstract/id.rb +233 -22
- data/lib/rack/session/cookie.rb +99 -46
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +22 -43
- data/lib/rack/showexceptions.rb +40 -11
- data/lib/rack/showstatus.rb +9 -2
- data/lib/rack/static.rb +29 -9
- data/lib/rack/urlmap.rb +6 -1
- data/lib/rack/utils.rb +67 -326
- data/rack.gemspec +2 -3
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +3 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/lighttpd.conf +1 -1
- data/test/cgi/lighttpd.errors +412 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/text +5 -0
- data/test/multipart/webkit +32 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_auth_digest.rb +20 -5
- data/test/spec_builder.rb +29 -0
- data/test/spec_cgi.rb +11 -0
- data/test/spec_chunked.rb +1 -1
- data/test/spec_commonlogger.rb +1 -1
- data/test/spec_conditionalget.rb +47 -0
- data/test/spec_content_length.rb +0 -6
- data/test/spec_content_type.rb +5 -5
- data/test/spec_deflater.rb +46 -2
- data/test/spec_etag.rb +68 -1
- data/test/spec_fastcgi.rb +11 -0
- data/test/spec_file.rb +54 -3
- data/test/spec_handler.rb +23 -5
- data/test/spec_lint.rb +2 -2
- data/test/spec_lock.rb +111 -5
- data/test/spec_methodoverride.rb +2 -2
- data/test/spec_mock.rb +3 -3
- data/test/spec_mongrel.rb +1 -2
- data/test/spec_multipart.rb +279 -0
- data/test/spec_request.rb +222 -38
- data/test/spec_response.rb +9 -3
- data/test/spec_server.rb +74 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +97 -15
- data/test/spec_session_memcache.rb +60 -50
- data/test/spec_session_pool.rb +63 -40
- data/test/spec_showexceptions.rb +64 -0
- data/test/spec_static.rb +23 -0
- data/test/spec_utils.rb +65 -351
- data/test/spec_webrick.rb +23 -4
- metadata +35 -15
- data/test/spec_auth.rb +0 -57
data/test/spec_fastcgi.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
begin
|
1
2
|
require File.expand_path('../testrequest', __FILE__)
|
2
3
|
require 'rack/handler/fastcgi'
|
3
4
|
|
@@ -7,6 +8,10 @@ describe Rack::Handler::FastCGI do
|
|
7
8
|
@host = '0.0.0.0'
|
8
9
|
@port = 9203
|
9
10
|
|
11
|
+
if `which lighttpd` && !$?.success?
|
12
|
+
raise "lighttpd not found"
|
13
|
+
end
|
14
|
+
|
10
15
|
# Keep this first.
|
11
16
|
$pid = fork {
|
12
17
|
ENV['RACK_ENV'] = 'deployment'
|
@@ -94,3 +99,9 @@ describe Rack::Handler::FastCGI do
|
|
94
99
|
Process.wait($pid).should.equal $pid
|
95
100
|
end
|
96
101
|
end
|
102
|
+
|
103
|
+
rescue RuntimeError
|
104
|
+
$stderr.puts "Skipping Rack::Handler::FastCGI tests (lighttpd is required). Install lighttpd and try again."
|
105
|
+
rescue LoadError
|
106
|
+
$stderr.puts "Skipping Rack::Handler::FastCGI tests (FCGI is required). `gem install fcgi` and try again."
|
107
|
+
end
|
data/test/spec_file.rb
CHANGED
@@ -31,12 +31,32 @@ describe Rack::File do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
should "not allow directory traversal" do
|
34
|
-
|
35
|
-
|
34
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
35
|
+
res = req.get("/cgi/../test")
|
36
|
+
res.should.be.forbidden
|
37
|
+
|
38
|
+
res = req.get("../test")
|
39
|
+
res.should.be.forbidden
|
36
40
|
|
41
|
+
res = req.get("..")
|
42
|
+
res.should.be.forbidden
|
43
|
+
|
44
|
+
res = req.get("test/..")
|
37
45
|
res.should.be.forbidden
|
38
46
|
end
|
39
47
|
|
48
|
+
should "allow files with .. in their name" do
|
49
|
+
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
50
|
+
res = req.get("/cgi/..test")
|
51
|
+
res.should.be.not_found
|
52
|
+
|
53
|
+
res = req.get("/cgi/test..")
|
54
|
+
res.should.be.not_found
|
55
|
+
|
56
|
+
res = req.get("/cgi../test..")
|
57
|
+
res.should.be.not_found
|
58
|
+
end
|
59
|
+
|
40
60
|
should "not allow directory traversal with encoded periods" do
|
41
61
|
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
|
42
62
|
get("/%2E%2E/README")
|
@@ -60,7 +80,7 @@ describe Rack::File do
|
|
60
80
|
|
61
81
|
should "return bodies that respond to #to_path" do
|
62
82
|
env = Rack::MockRequest.env_for("/cgi/test")
|
63
|
-
status,
|
83
|
+
status, _, body = Rack::File.new(DOCROOT).call(env)
|
64
84
|
|
65
85
|
path = File.join(DOCROOT, "/cgi/test")
|
66
86
|
|
@@ -68,4 +88,35 @@ describe Rack::File do
|
|
68
88
|
body.should.respond_to :to_path
|
69
89
|
body.to_path.should.equal path
|
70
90
|
end
|
91
|
+
|
92
|
+
should "return correct byte range in body" do
|
93
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
94
|
+
env["HTTP_RANGE"] = "bytes=22-33"
|
95
|
+
res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
|
96
|
+
|
97
|
+
res.status.should.equal 206
|
98
|
+
res["Content-Length"].should.equal "12"
|
99
|
+
res["Content-Range"].should.equal "bytes 22-33/193"
|
100
|
+
res.body.should.equal "-*- ruby -*-"
|
101
|
+
end
|
102
|
+
|
103
|
+
should "return error for unsatisfiable byte range" do
|
104
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
105
|
+
env["HTTP_RANGE"] = "bytes=1234-5678"
|
106
|
+
res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
|
107
|
+
|
108
|
+
res.status.should.equal 416
|
109
|
+
res["Content-Range"].should.equal "bytes */193"
|
110
|
+
end
|
111
|
+
|
112
|
+
should "support cache control options" do
|
113
|
+
env = Rack::MockRequest.env_for("/cgi/test")
|
114
|
+
status, heads, _ = Rack::File.new(DOCROOT, 'public, max-age=38').call(env)
|
115
|
+
|
116
|
+
path = File.join(DOCROOT, "/cgi/test")
|
117
|
+
|
118
|
+
status.should.equal 200
|
119
|
+
heads['Cache-Control'].should.equal 'public, max-age=38'
|
120
|
+
end
|
121
|
+
|
71
122
|
end
|
data/test/spec_handler.rb
CHANGED
@@ -6,15 +6,23 @@ class RockLobster; end
|
|
6
6
|
describe Rack::Handler do
|
7
7
|
it "has registered default handlers" do
|
8
8
|
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
|
9
|
-
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
|
10
|
-
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
|
11
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
|
12
20
|
end
|
13
21
|
|
14
|
-
should "raise
|
22
|
+
should "raise LoadError if handler doesn't exist" do
|
15
23
|
lambda {
|
16
24
|
Rack::Handler.get('boom')
|
17
|
-
}.should.raise(
|
25
|
+
}.should.raise(LoadError)
|
18
26
|
end
|
19
27
|
|
20
28
|
should "get unregistered, but already required, handler by name" do
|
@@ -32,10 +40,20 @@ describe Rack::Handler do
|
|
32
40
|
Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
|
33
41
|
lambda {
|
34
42
|
Rack::Handler.get('UnRegistered')
|
35
|
-
}.should.raise
|
43
|
+
}.should.raise LoadError
|
36
44
|
Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
|
37
45
|
ensure
|
38
46
|
$LOAD_PATH.delete File.expand_path('../unregistered_handler', __FILE__)
|
39
47
|
end
|
40
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
|
41
59
|
end
|
data/test/spec_lint.rb
CHANGED
@@ -271,9 +271,9 @@ describe Rack::Lint do
|
|
271
271
|
|
272
272
|
should "notice body errors" do
|
273
273
|
lambda {
|
274
|
-
|
274
|
+
body = Rack::Lint.new(lambda { |env|
|
275
275
|
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
|
276
|
-
}).call(env({}))
|
276
|
+
}).call(env({}))[2]
|
277
277
|
body.each { |part| }
|
278
278
|
}.should.raise(Rack::Lint::LintError).
|
279
279
|
message.should.match(/yielded non-string/)
|
data/test/spec_lock.rb
CHANGED
@@ -12,25 +12,131 @@ class Lock
|
|
12
12
|
@synchronized = true
|
13
13
|
yield
|
14
14
|
end
|
15
|
+
|
16
|
+
def lock
|
17
|
+
@synchronized = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock
|
21
|
+
@synchronized = false
|
22
|
+
end
|
15
23
|
end
|
16
24
|
|
17
25
|
describe Rack::Lock do
|
26
|
+
describe 'Proxy' do
|
27
|
+
should 'delegate each' do
|
28
|
+
lock = Lock.new
|
29
|
+
env = Rack::MockRequest.env_for("/")
|
30
|
+
response = Class.new {
|
31
|
+
attr_accessor :close_called
|
32
|
+
def initialize; @close_called = false; end
|
33
|
+
def each; %w{ hi mom }.each { |x| yield x }; end
|
34
|
+
}.new
|
35
|
+
|
36
|
+
app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
|
37
|
+
response = app.call(env)[2]
|
38
|
+
list = []
|
39
|
+
response.each { |x| list << x }
|
40
|
+
list.should.equal %w{ hi mom }
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'delegate to_path' do
|
44
|
+
lock = Lock.new
|
45
|
+
env = Rack::MockRequest.env_for("/")
|
46
|
+
|
47
|
+
res = ['Hello World']
|
48
|
+
def res.to_path ; "/tmp/hello.txt" ; end
|
49
|
+
|
50
|
+
app = Rack::Lock.new(lambda { |inner_env| [200, {}, res] }, lock)
|
51
|
+
body = app.call(env)[2]
|
52
|
+
|
53
|
+
body.should.respond_to :to_path
|
54
|
+
body.to_path.should.equal "/tmp/hello.txt"
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'not delegate to_path if body does not implement it' do
|
58
|
+
lock = Lock.new
|
59
|
+
env = Rack::MockRequest.env_for("/")
|
60
|
+
|
61
|
+
res = ['Hello World']
|
62
|
+
|
63
|
+
app = Rack::Lock.new(lambda { |inner_env| [200, {}, res] }, lock)
|
64
|
+
body = app.call(env)[2]
|
65
|
+
|
66
|
+
body.should.not.respond_to :to_path
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
should 'call super on close' do
|
71
|
+
lock = Lock.new
|
72
|
+
env = Rack::MockRequest.env_for("/")
|
73
|
+
response = Class.new {
|
74
|
+
attr_accessor :close_called
|
75
|
+
def initialize; @close_called = false; end
|
76
|
+
def close; @close_called = true; end
|
77
|
+
}.new
|
78
|
+
|
79
|
+
app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
|
80
|
+
app.call(env)
|
81
|
+
response.close_called.should.equal false
|
82
|
+
response.close
|
83
|
+
response.close_called.should.equal true
|
84
|
+
end
|
85
|
+
|
86
|
+
should "not unlock until body is closed" do
|
87
|
+
lock = Lock.new
|
88
|
+
env = Rack::MockRequest.env_for("/")
|
89
|
+
response = Object.new
|
90
|
+
app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
|
91
|
+
lock.synchronized.should.equal false
|
92
|
+
response = app.call(env)[2]
|
93
|
+
lock.synchronized.should.equal true
|
94
|
+
response.close
|
95
|
+
lock.synchronized.should.equal false
|
96
|
+
end
|
97
|
+
|
98
|
+
should "return value from app" do
|
99
|
+
lock = Lock.new
|
100
|
+
env = Rack::MockRequest.env_for("/")
|
101
|
+
body = [200, {}, %w{ hi mom }]
|
102
|
+
app = Rack::Lock.new(lambda { |inner_env| body }, lock)
|
103
|
+
app.call(env).should.equal body
|
104
|
+
end
|
105
|
+
|
18
106
|
should "call synchronize on lock" do
|
19
107
|
lock = Lock.new
|
20
108
|
env = Rack::MockRequest.env_for("/")
|
21
|
-
app = Rack::Lock.new(lambda { |inner_env|
|
109
|
+
app = Rack::Lock.new(lambda { |inner_env|
|
110
|
+
[200, {}, %w{ a b c }]
|
111
|
+
}, lock)
|
22
112
|
lock.synchronized.should.equal false
|
23
113
|
app.call(env)
|
24
114
|
lock.synchronized.should.equal true
|
25
115
|
end
|
26
116
|
|
117
|
+
should "unlock if the app raises" do
|
118
|
+
lock = Lock.new
|
119
|
+
env = Rack::MockRequest.env_for("/")
|
120
|
+
app = Rack::Lock.new(lambda { raise Exception }, lock)
|
121
|
+
lambda { app.call(env) }.should.raise(Exception)
|
122
|
+
lock.synchronized.should.equal false
|
123
|
+
end
|
124
|
+
|
27
125
|
should "set multithread flag to false" do
|
28
|
-
app = Rack::Lock.new(lambda { |env|
|
29
|
-
|
126
|
+
app = Rack::Lock.new(lambda { |env|
|
127
|
+
env['rack.multithread'].should.equal false
|
128
|
+
[200, {}, %w{ a b c }]
|
129
|
+
})
|
130
|
+
app.call(Rack::MockRequest.env_for("/"))
|
30
131
|
end
|
31
132
|
|
32
133
|
should "reset original multithread flag when exiting lock" do
|
33
|
-
app = Rack::Lock
|
34
|
-
|
134
|
+
app = Class.new(Rack::Lock) {
|
135
|
+
def call(env)
|
136
|
+
env['rack.multithread'].should.equal true
|
137
|
+
super
|
138
|
+
end
|
139
|
+
}.new(lambda { |env| [200, {}, %w{ a b c }] })
|
140
|
+
app.call(Rack::MockRequest.env_for("/"))
|
35
141
|
end
|
36
142
|
end
|
data/test/spec_methodoverride.rb
CHANGED
@@ -22,12 +22,12 @@ describe Rack::MethodOverride do
|
|
22
22
|
should "modify REQUEST_METHOD for POST requests when X-HTTP-Method-Override is set" do
|
23
23
|
env = Rack::MockRequest.env_for("/",
|
24
24
|
:method => "POST",
|
25
|
-
"HTTP_X_HTTP_METHOD_OVERRIDE" => "
|
25
|
+
"HTTP_X_HTTP_METHOD_OVERRIDE" => "PATCH"
|
26
26
|
)
|
27
27
|
app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
|
28
28
|
req = app.call(env)
|
29
29
|
|
30
|
-
req.env["REQUEST_METHOD"].should.equal "
|
30
|
+
req.env["REQUEST_METHOD"].should.equal "PATCH"
|
31
31
|
end
|
32
32
|
|
33
33
|
should "not modify REQUEST_METHOD if the method is unknown" do
|
data/test/spec_mock.rb
CHANGED
@@ -167,7 +167,7 @@ describe Rack::MockRequest do
|
|
167
167
|
end
|
168
168
|
|
169
169
|
should "accept params and build multipart encoded params for POST requests" do
|
170
|
-
files = Rack::
|
170
|
+
files = Rack::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
|
171
171
|
res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files })
|
172
172
|
env = YAML.load(res.body)
|
173
173
|
env["REQUEST_METHOD"].should.equal "POST"
|
@@ -179,7 +179,7 @@ describe Rack::MockRequest do
|
|
179
179
|
|
180
180
|
should "behave valid according to the Rack spec" do
|
181
181
|
lambda {
|
182
|
-
|
182
|
+
Rack::MockRequest.new(app).
|
183
183
|
get("https://bla.example.org:9292/meh/foo?bar", :lint => true)
|
184
184
|
}.should.not.raise(Rack::Lint::LintError)
|
185
185
|
end
|
@@ -214,7 +214,7 @@ describe Rack::MockResponse do
|
|
214
214
|
res.original_headers["Content-Type"].should.equal "text/yaml"
|
215
215
|
res["Content-Type"].should.equal "text/yaml"
|
216
216
|
res.content_type.should.equal "text/yaml"
|
217
|
-
res.content_length.should.
|
217
|
+
res.content_length.should.not.equal 0
|
218
218
|
res.location.should.be.nil
|
219
219
|
end
|
220
220
|
|
data/test/spec_mongrel.rb
CHANGED
@@ -177,7 +177,6 @@ describe Rack::Handler::Mongrel do
|
|
177
177
|
@acc.raise Mongrel::StopServer
|
178
178
|
end
|
179
179
|
|
180
|
-
rescue LoadError
|
181
|
-
warn ex
|
180
|
+
rescue LoadError
|
182
181
|
warn "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again."
|
183
182
|
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'rack/mock'
|
3
|
+
|
4
|
+
describe Rack::Multipart do
|
5
|
+
def multipart_fixture(name)
|
6
|
+
file = multipart_file(name)
|
7
|
+
data = File.open(file, 'rb') { |io| io.read }
|
8
|
+
|
9
|
+
type = "multipart/form-data; boundary=AaB03x"
|
10
|
+
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
|
11
|
+
|
12
|
+
{ "CONTENT_TYPE" => type,
|
13
|
+
"CONTENT_LENGTH" => length.to_s,
|
14
|
+
:input => StringIO.new(data) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def multipart_file(name)
|
18
|
+
File.join(File.dirname(__FILE__), "multipart", name.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "return nil if content type is not multipart" do
|
22
|
+
env = Rack::MockRequest.env_for("/",
|
23
|
+
"CONTENT_TYPE" => 'application/x-www-form-urlencoded')
|
24
|
+
Rack::Multipart.parse_multipart(env).should.equal nil
|
25
|
+
end
|
26
|
+
|
27
|
+
should "parse multipart content when content type present but filename is not" do
|
28
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
29
|
+
params = Rack::Multipart.parse_multipart(env)
|
30
|
+
params["text"].should.equal "contents"
|
31
|
+
end
|
32
|
+
|
33
|
+
should "parse multipart form webkit style" do
|
34
|
+
env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
|
35
|
+
env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
|
36
|
+
params = Rack::Multipart.parse_multipart(env)
|
37
|
+
params['profile']['bio'].should.include 'hello'
|
38
|
+
end
|
39
|
+
|
40
|
+
should "parse multipart upload with text file" do
|
41
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
|
42
|
+
params = Rack::Multipart.parse_multipart(env)
|
43
|
+
params["submit-name"].should.equal "Larry"
|
44
|
+
params["submit-name-with-content"].should.equal "Berry"
|
45
|
+
params["files"][:type].should.equal "text/plain"
|
46
|
+
params["files"][:filename].should.equal "file1.txt"
|
47
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
48
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
49
|
+
"Content-Type: text/plain\r\n"
|
50
|
+
params["files"][:name].should.equal "files"
|
51
|
+
params["files"][:tempfile].read.should.equal "contents"
|
52
|
+
end
|
53
|
+
|
54
|
+
should "parse multipart upload with nested parameters" do
|
55
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
|
56
|
+
params = Rack::Multipart.parse_multipart(env)
|
57
|
+
params["foo"]["submit-name"].should.equal "Larry"
|
58
|
+
params["foo"]["files"][:type].should.equal "text/plain"
|
59
|
+
params["foo"]["files"][:filename].should.equal "file1.txt"
|
60
|
+
params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
|
61
|
+
"name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
|
62
|
+
"Content-Type: text/plain\r\n"
|
63
|
+
params["foo"]["files"][:name].should.equal "foo[files]"
|
64
|
+
params["foo"]["files"][:tempfile].read.should.equal "contents"
|
65
|
+
end
|
66
|
+
|
67
|
+
should "parse multipart upload with binary file" do
|
68
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
|
69
|
+
params = Rack::Multipart.parse_multipart(env)
|
70
|
+
params["submit-name"].should.equal "Larry"
|
71
|
+
params["files"][:type].should.equal "image/png"
|
72
|
+
params["files"][:filename].should.equal "rack-logo.png"
|
73
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
74
|
+
"name=\"files\"; filename=\"rack-logo.png\"\r\n" +
|
75
|
+
"Content-Type: image/png\r\n"
|
76
|
+
params["files"][:name].should.equal "files"
|
77
|
+
params["files"][:tempfile].read.length.should.equal 26473
|
78
|
+
end
|
79
|
+
|
80
|
+
should "parse multipart upload with empty file" do
|
81
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
|
82
|
+
params = Rack::Multipart.parse_multipart(env)
|
83
|
+
params["submit-name"].should.equal "Larry"
|
84
|
+
params["files"][:type].should.equal "text/plain"
|
85
|
+
params["files"][:filename].should.equal "file1.txt"
|
86
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
87
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
88
|
+
"Content-Type: text/plain\r\n"
|
89
|
+
params["files"][:name].should.equal "files"
|
90
|
+
params["files"][:tempfile].read.should.equal ""
|
91
|
+
end
|
92
|
+
|
93
|
+
should "parse multipart upload with filename with semicolons" do
|
94
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
|
95
|
+
params = Rack::Multipart.parse_multipart(env)
|
96
|
+
params["files"][:type].should.equal "text/plain"
|
97
|
+
params["files"][:filename].should.equal "fi;le1.txt"
|
98
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
99
|
+
"name=\"files\"; filename=\"fi;le1.txt\"\r\n" +
|
100
|
+
"Content-Type: text/plain\r\n"
|
101
|
+
params["files"][:name].should.equal "files"
|
102
|
+
params["files"][:tempfile].read.should.equal "contents"
|
103
|
+
end
|
104
|
+
|
105
|
+
should "not include file params if no file was selected" do
|
106
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
107
|
+
params = Rack::Multipart.parse_multipart(env)
|
108
|
+
params["submit-name"].should.equal "Larry"
|
109
|
+
params["files"].should.equal nil
|
110
|
+
params.keys.should.not.include "files"
|
111
|
+
end
|
112
|
+
|
113
|
+
should "parse IE multipart upload and clean up filename" do
|
114
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
|
115
|
+
params = Rack::Multipart.parse_multipart(env)
|
116
|
+
params["files"][:type].should.equal "text/plain"
|
117
|
+
params["files"][:filename].should.equal "file1.txt"
|
118
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
119
|
+
"name=\"files\"; " +
|
120
|
+
'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
|
121
|
+
"\r\nContent-Type: text/plain\r\n"
|
122
|
+
params["files"][:name].should.equal "files"
|
123
|
+
params["files"][:tempfile].read.should.equal "contents"
|
124
|
+
end
|
125
|
+
|
126
|
+
should "parse filename and modification param" do
|
127
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param))
|
128
|
+
params = Rack::Multipart.parse_multipart(env)
|
129
|
+
params["files"][:type].should.equal "image/jpeg"
|
130
|
+
params["files"][:filename].should.equal "genome.jpeg"
|
131
|
+
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
|
132
|
+
"Content-Disposition: attachment; " +
|
133
|
+
"name=\"files\"; " +
|
134
|
+
"filename=genome.jpeg; " +
|
135
|
+
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
|
136
|
+
"Content-Description: a complete map of the human genome\r\n"
|
137
|
+
params["files"][:name].should.equal "files"
|
138
|
+
params["files"][:tempfile].read.should.equal "contents"
|
139
|
+
end
|
140
|
+
|
141
|
+
should "parse filename with escaped quotes" do
|
142
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes))
|
143
|
+
params = Rack::Multipart.parse_multipart(env)
|
144
|
+
params["files"][:type].should.equal "application/octet-stream"
|
145
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
146
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
147
|
+
"name=\"files\"; " +
|
148
|
+
"filename=\"escape \\\"quotes\"\r\n" +
|
149
|
+
"Content-Type: application/octet-stream\r\n"
|
150
|
+
params["files"][:name].should.equal "files"
|
151
|
+
params["files"][:tempfile].read.should.equal "contents"
|
152
|
+
end
|
153
|
+
|
154
|
+
should "parse filename with percent escaped quotes" do
|
155
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
|
156
|
+
params = Rack::Multipart.parse_multipart(env)
|
157
|
+
params["files"][:type].should.equal "application/octet-stream"
|
158
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
159
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
160
|
+
"name=\"files\"; " +
|
161
|
+
"filename=\"escape %22quotes\"\r\n" +
|
162
|
+
"Content-Type: application/octet-stream\r\n"
|
163
|
+
params["files"][:name].should.equal "files"
|
164
|
+
params["files"][:tempfile].read.should.equal "contents"
|
165
|
+
end
|
166
|
+
|
167
|
+
should "parse filename with unescaped quotes" do
|
168
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
|
169
|
+
params = Rack::Multipart.parse_multipart(env)
|
170
|
+
params["files"][:type].should.equal "application/octet-stream"
|
171
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
172
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
173
|
+
"name=\"files\"; " +
|
174
|
+
"filename=\"escape \"quotes\"\r\n" +
|
175
|
+
"Content-Type: application/octet-stream\r\n"
|
176
|
+
params["files"][:name].should.equal "files"
|
177
|
+
params["files"][:tempfile].read.should.equal "contents"
|
178
|
+
end
|
179
|
+
|
180
|
+
should "parse filename with escaped quotes and modification param" do
|
181
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
|
182
|
+
params = Rack::Multipart.parse_multipart(env)
|
183
|
+
params["files"][:type].should.equal "image/jpeg"
|
184
|
+
params["files"][:filename].should.equal "\"human\" genome.jpeg"
|
185
|
+
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
|
186
|
+
"Content-Disposition: attachment; " +
|
187
|
+
"name=\"files\"; " +
|
188
|
+
"filename=\"\"human\" genome.jpeg\"; " +
|
189
|
+
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
|
190
|
+
"Content-Description: a complete map of the human genome\r\n"
|
191
|
+
params["files"][:name].should.equal "files"
|
192
|
+
params["files"][:tempfile].read.should.equal "contents"
|
193
|
+
end
|
194
|
+
|
195
|
+
it "rewinds input after parsing upload" do
|
196
|
+
options = multipart_fixture(:text)
|
197
|
+
input = options[:input]
|
198
|
+
env = Rack::MockRequest.env_for("/", options)
|
199
|
+
params = Rack::Multipart.parse_multipart(env)
|
200
|
+
params["submit-name"].should.equal "Larry"
|
201
|
+
params["files"][:filename].should.equal "file1.txt"
|
202
|
+
input.read.length.should.equal 307
|
203
|
+
end
|
204
|
+
|
205
|
+
it "builds multipart body" do
|
206
|
+
files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
|
207
|
+
data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
|
208
|
+
|
209
|
+
options = {
|
210
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
211
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
212
|
+
:input => StringIO.new(data)
|
213
|
+
}
|
214
|
+
env = Rack::MockRequest.env_for("/", options)
|
215
|
+
params = Rack::Multipart.parse_multipart(env)
|
216
|
+
params["submit-name"].should.equal "Larry"
|
217
|
+
params["files"][:filename].should.equal "file1.txt"
|
218
|
+
params["files"][:tempfile].read.should.equal "contents"
|
219
|
+
end
|
220
|
+
|
221
|
+
it "builds nested multipart body" do
|
222
|
+
files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
|
223
|
+
data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
|
224
|
+
|
225
|
+
options = {
|
226
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
227
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
228
|
+
:input => StringIO.new(data)
|
229
|
+
}
|
230
|
+
env = Rack::MockRequest.env_for("/", options)
|
231
|
+
params = Rack::Multipart.parse_multipart(env)
|
232
|
+
params["people"][0]["submit-name"].should.equal "Larry"
|
233
|
+
params["people"][0]["files"][:filename].should.equal "file1.txt"
|
234
|
+
params["people"][0]["files"][:tempfile].read.should.equal "contents"
|
235
|
+
end
|
236
|
+
|
237
|
+
it "can parse fields that end at the end of the buffer" do
|
238
|
+
input = File.read(multipart_file("bad_robots"))
|
239
|
+
|
240
|
+
req = Rack::Request.new Rack::MockRequest.env_for("/",
|
241
|
+
"CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
|
242
|
+
"CONTENT_LENGTH" => input.size,
|
243
|
+
:input => input)
|
244
|
+
|
245
|
+
req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
|
246
|
+
req.POST['addresses'].should.not.equal nil
|
247
|
+
end
|
248
|
+
|
249
|
+
it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
|
250
|
+
data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
|
251
|
+
options = {
|
252
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
|
253
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
254
|
+
:input => StringIO.new(data)
|
255
|
+
}
|
256
|
+
env = Rack::MockRequest.env_for("/", options)
|
257
|
+
params = Rack::Multipart.parse_multipart(env)
|
258
|
+
|
259
|
+
params.should.not.equal nil
|
260
|
+
params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
|
261
|
+
params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
|
262
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
|
263
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
|
264
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
|
265
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
|
266
|
+
end
|
267
|
+
|
268
|
+
should "return nil if no UploadedFiles were used" do
|
269
|
+
data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
|
270
|
+
data.should.equal nil
|
271
|
+
end
|
272
|
+
|
273
|
+
should "raise ArgumentError if params is not a Hash" do
|
274
|
+
lambda { Rack::Multipart.build_multipart("foo=bar") }.
|
275
|
+
should.raise(ArgumentError).
|
276
|
+
message.should.equal "value must be a Hash"
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|