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.

Files changed (89) hide show
  1. data/README +9 -177
  2. data/Rakefile +2 -1
  3. data/SPEC +2 -2
  4. data/lib/rack.rb +2 -13
  5. data/lib/rack/auth/abstract/request.rb +7 -5
  6. data/lib/rack/auth/digest/md5.rb +6 -2
  7. data/lib/rack/auth/digest/params.rb +5 -7
  8. data/lib/rack/auth/digest/request.rb +1 -1
  9. data/lib/rack/backports/uri/common.rb +64 -0
  10. data/lib/rack/builder.rb +60 -3
  11. data/lib/rack/chunked.rb +29 -22
  12. data/lib/rack/conditionalget.rb +35 -16
  13. data/lib/rack/content_length.rb +3 -3
  14. data/lib/rack/deflater.rb +5 -2
  15. data/lib/rack/etag.rb +38 -10
  16. data/lib/rack/file.rb +76 -43
  17. data/lib/rack/handler.rb +13 -7
  18. data/lib/rack/handler/cgi.rb +0 -2
  19. data/lib/rack/handler/fastcgi.rb +13 -4
  20. data/lib/rack/handler/lsws.rb +0 -2
  21. data/lib/rack/handler/mongrel.rb +12 -2
  22. data/lib/rack/handler/scgi.rb +9 -1
  23. data/lib/rack/handler/thin.rb +7 -1
  24. data/lib/rack/handler/webrick.rb +12 -5
  25. data/lib/rack/lint.rb +2 -2
  26. data/lib/rack/lock.rb +29 -3
  27. data/lib/rack/methodoverride.rb +1 -1
  28. data/lib/rack/mime.rb +2 -2
  29. data/lib/rack/mock.rb +28 -33
  30. data/lib/rack/multipart.rb +34 -0
  31. data/lib/rack/multipart/generator.rb +93 -0
  32. data/lib/rack/multipart/parser.rb +164 -0
  33. data/lib/rack/multipart/uploaded_file.rb +30 -0
  34. data/lib/rack/request.rb +55 -19
  35. data/lib/rack/response.rb +10 -8
  36. data/lib/rack/sendfile.rb +14 -18
  37. data/lib/rack/server.rb +55 -8
  38. data/lib/rack/session/abstract/id.rb +233 -22
  39. data/lib/rack/session/cookie.rb +99 -46
  40. data/lib/rack/session/memcache.rb +30 -56
  41. data/lib/rack/session/pool.rb +22 -43
  42. data/lib/rack/showexceptions.rb +40 -11
  43. data/lib/rack/showstatus.rb +9 -2
  44. data/lib/rack/static.rb +29 -9
  45. data/lib/rack/urlmap.rb +6 -1
  46. data/lib/rack/utils.rb +67 -326
  47. data/rack.gemspec +2 -3
  48. data/test/builder/anything.rb +5 -0
  49. data/test/builder/comment.ru +4 -0
  50. data/test/builder/end.ru +3 -0
  51. data/test/builder/options.ru +2 -0
  52. data/test/cgi/lighttpd.conf +1 -1
  53. data/test/cgi/lighttpd.errors +412 -0
  54. data/test/multipart/content_type_and_no_filename +6 -0
  55. data/test/multipart/text +5 -0
  56. data/test/multipart/webkit +32 -0
  57. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  58. data/test/spec_auth_digest.rb +20 -5
  59. data/test/spec_builder.rb +29 -0
  60. data/test/spec_cgi.rb +11 -0
  61. data/test/spec_chunked.rb +1 -1
  62. data/test/spec_commonlogger.rb +1 -1
  63. data/test/spec_conditionalget.rb +47 -0
  64. data/test/spec_content_length.rb +0 -6
  65. data/test/spec_content_type.rb +5 -5
  66. data/test/spec_deflater.rb +46 -2
  67. data/test/spec_etag.rb +68 -1
  68. data/test/spec_fastcgi.rb +11 -0
  69. data/test/spec_file.rb +54 -3
  70. data/test/spec_handler.rb +23 -5
  71. data/test/spec_lint.rb +2 -2
  72. data/test/spec_lock.rb +111 -5
  73. data/test/spec_methodoverride.rb +2 -2
  74. data/test/spec_mock.rb +3 -3
  75. data/test/spec_mongrel.rb +1 -2
  76. data/test/spec_multipart.rb +279 -0
  77. data/test/spec_request.rb +222 -38
  78. data/test/spec_response.rb +9 -3
  79. data/test/spec_server.rb +74 -0
  80. data/test/spec_session_abstract_id.rb +43 -0
  81. data/test/spec_session_cookie.rb +97 -15
  82. data/test/spec_session_memcache.rb +60 -50
  83. data/test/spec_session_pool.rb +63 -40
  84. data/test/spec_showexceptions.rb +64 -0
  85. data/test/spec_static.rb +23 -0
  86. data/test/spec_utils.rb +65 -351
  87. data/test/spec_webrick.rb +23 -4
  88. metadata +35 -15
  89. data/test/spec_auth.rb +0 -57
@@ -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
@@ -31,12 +31,32 @@ describe Rack::File do
31
31
  end
32
32
 
33
33
  should "not allow directory traversal" do
34
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
35
- get("/cgi/../test")
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, headers, body = Rack::File.new(DOCROOT).call(env)
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
@@ -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 NameError if handler doesn't exist" do
22
+ should "raise LoadError if handler doesn't exist" do
15
23
  lambda {
16
24
  Rack::Handler.get('boom')
17
- }.should.raise(NameError)
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(NameError)
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
@@ -271,9 +271,9 @@ describe Rack::Lint do
271
271
 
272
272
  should "notice body errors" do
273
273
  lambda {
274
- status, header, body = Rack::Lint.new(lambda { |env|
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/)
@@ -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| }, lock)
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| env['rack.multithread'] })
29
- app.call(Rack::MockRequest.env_for("/")).should.equal false
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.new(lambda { |env| env })
34
- app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true
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
@@ -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" => "PUT"
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 "PUT"
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
@@ -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::Utils::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
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
- res = Rack::MockRequest.new(app).
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.be > 0
217
+ res.content_length.should.not.equal 0
218
218
  res.location.should.be.nil
219
219
  end
220
220
 
@@ -177,7 +177,6 @@ describe Rack::Handler::Mongrel do
177
177
  @acc.raise Mongrel::StopServer
178
178
  end
179
179
 
180
- rescue LoadError => ex
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