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/lib/rack/handler.rb
CHANGED
@@ -12,14 +12,18 @@ module Rack
|
|
12
12
|
return unless server
|
13
13
|
server = server.to_s
|
14
14
|
|
15
|
+
unless @handlers.include? server
|
16
|
+
load_error = try_require('rack/handler', server)
|
17
|
+
end
|
18
|
+
|
15
19
|
if klass = @handlers[server]
|
16
|
-
|
17
|
-
klass.split("::").each { |x| obj = obj.const_get(x) }
|
18
|
-
obj
|
20
|
+
klass.split("::").inject(Object) { |o, x| o.const_get(x) }
|
19
21
|
else
|
20
|
-
try_require('rack/handler', server)
|
21
22
|
const_get(server)
|
22
23
|
end
|
24
|
+
|
25
|
+
rescue NameError => name_error
|
26
|
+
raise load_error || name_error
|
23
27
|
end
|
24
28
|
|
25
29
|
def self.default(options = {})
|
@@ -35,7 +39,7 @@ module Rack
|
|
35
39
|
else
|
36
40
|
begin
|
37
41
|
Rack::Handler::Mongrel
|
38
|
-
rescue LoadError
|
42
|
+
rescue LoadError
|
39
43
|
Rack::Handler::WEBrick
|
40
44
|
end
|
41
45
|
end
|
@@ -57,12 +61,14 @@ module Rack
|
|
57
61
|
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
|
58
62
|
|
59
63
|
require(::File.join(prefix, file))
|
60
|
-
|
64
|
+
nil
|
65
|
+
rescue LoadError => error
|
66
|
+
error
|
61
67
|
end
|
62
68
|
|
63
69
|
def self.register(server, klass)
|
64
70
|
@handlers ||= {}
|
65
|
-
@handlers[server] = klass
|
71
|
+
@handlers[server.to_s] = klass.to_s
|
66
72
|
end
|
67
73
|
|
68
74
|
autoload :CGI, "rack/handler/cgi"
|
data/lib/rack/handler/cgi.rb
CHANGED
data/lib/rack/handler/fastcgi.rb
CHANGED
@@ -19,16 +19,25 @@ module Rack
|
|
19
19
|
module Handler
|
20
20
|
class FastCGI
|
21
21
|
def self.run(app, options={})
|
22
|
-
|
23
|
-
|
22
|
+
if options[:File]
|
23
|
+
STDIN.reopen(UNIXServer.new(options[:File]))
|
24
|
+
elsif options[:Port]
|
25
|
+
STDIN.reopen(TCPServer.new(options[:Host], options[:Port]))
|
26
|
+
end
|
24
27
|
FCGI.each { |request|
|
25
28
|
serve request, app
|
26
29
|
}
|
27
30
|
end
|
31
|
+
|
32
|
+
def self.valid_options
|
33
|
+
{
|
34
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
35
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
36
|
+
"File=PATH" => "Creates a Domain socket at PATH instead of a TCP socket. Ignores Host and Port if set.",
|
37
|
+
}
|
38
|
+
end
|
28
39
|
|
29
40
|
def self.serve(request, app)
|
30
|
-
app = Rack::ContentLength.new(app)
|
31
|
-
|
32
41
|
env = request.env
|
33
42
|
env.delete "HTTP_CONTENT_LENGTH"
|
34
43
|
|
data/lib/rack/handler/lsws.rb
CHANGED
data/lib/rack/handler/mongrel.rb
CHANGED
@@ -38,8 +38,18 @@ module Rack
|
|
38
38
|
server.run.join
|
39
39
|
end
|
40
40
|
|
41
|
+
def self.valid_options
|
42
|
+
{
|
43
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
44
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
45
|
+
"Processors=N" => "Number of concurrent processors to accept (default: 950)",
|
46
|
+
"Timeout=N" => "Time before a request is dropped for inactivity (default: 60)",
|
47
|
+
"Throttle=N" => "Throttle time between socket.accept calls in hundredths of a second (default: 0)",
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
41
51
|
def initialize(app)
|
42
|
-
@app =
|
52
|
+
@app = app
|
43
53
|
end
|
44
54
|
|
45
55
|
def process(request, response)
|
@@ -60,7 +70,7 @@ module Rack
|
|
60
70
|
"rack.multiprocess" => false, # ???
|
61
71
|
"rack.run_once" => false,
|
62
72
|
|
63
|
-
"rack.url_scheme" => "http"
|
73
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
64
74
|
})
|
65
75
|
env["QUERY_STRING"] ||= ""
|
66
76
|
|
data/lib/rack/handler/scgi.rb
CHANGED
@@ -9,14 +9,22 @@ module Rack
|
|
9
9
|
attr_accessor :app
|
10
10
|
|
11
11
|
def self.run(app, options=nil)
|
12
|
+
options[:Socket] = UNIXServer.new(options[:File]) if options[:File]
|
12
13
|
new(options.merge(:app=>app,
|
13
14
|
:host=>options[:Host],
|
14
15
|
:port=>options[:Port],
|
15
16
|
:socket=>options[:Socket])).listen
|
16
17
|
end
|
18
|
+
|
19
|
+
def self.valid_options
|
20
|
+
{
|
21
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
22
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
23
|
+
}
|
24
|
+
end
|
17
25
|
|
18
26
|
def initialize(settings = {})
|
19
|
-
@app =
|
27
|
+
@app = settings[:app]
|
20
28
|
super(settings)
|
21
29
|
end
|
22
30
|
|
data/lib/rack/handler/thin.rb
CHANGED
@@ -6,13 +6,19 @@ module Rack
|
|
6
6
|
module Handler
|
7
7
|
class Thin
|
8
8
|
def self.run(app, options={})
|
9
|
-
app = Rack::Chunked.new(Rack::ContentLength.new(app))
|
10
9
|
server = ::Thin::Server.new(options[:Host] || '0.0.0.0',
|
11
10
|
options[:Port] || 8080,
|
12
11
|
app)
|
13
12
|
yield server if block_given?
|
14
13
|
server.start
|
15
14
|
end
|
15
|
+
|
16
|
+
def self.valid_options
|
17
|
+
{
|
18
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
19
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
20
|
+
}
|
21
|
+
end
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
data/lib/rack/handler/webrick.rb
CHANGED
@@ -12,6 +12,13 @@ module Rack
|
|
12
12
|
yield @server if block_given?
|
13
13
|
@server.start
|
14
14
|
end
|
15
|
+
|
16
|
+
def self.valid_options
|
17
|
+
{
|
18
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
19
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
20
|
+
}
|
21
|
+
end
|
15
22
|
|
16
23
|
def self.shutdown
|
17
24
|
@server.shutdown
|
@@ -20,7 +27,7 @@ module Rack
|
|
20
27
|
|
21
28
|
def initialize(server, app)
|
22
29
|
super server
|
23
|
-
@app =
|
30
|
+
@app = app
|
24
31
|
end
|
25
32
|
|
26
33
|
def service(req, res)
|
@@ -43,11 +50,11 @@ module Rack
|
|
43
50
|
|
44
51
|
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
45
52
|
env["QUERY_STRING"] ||= ""
|
46
|
-
env["REQUEST_PATH"] ||= "/"
|
47
53
|
unless env["PATH_INFO"] == ""
|
48
54
|
path, n = req.request_uri.path, env["SCRIPT_NAME"].length
|
49
55
|
env["PATH_INFO"] = path[n, path.length-n]
|
50
56
|
end
|
57
|
+
env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env["PATH_INFO"]].join
|
51
58
|
|
52
59
|
status, headers, body = @app.call(env)
|
53
60
|
begin
|
@@ -56,9 +63,9 @@ module Rack
|
|
56
63
|
if k.downcase == "set-cookie"
|
57
64
|
res.cookies.concat vs.split("\n")
|
58
65
|
else
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
# Since WEBrick won't accept repeated headers,
|
67
|
+
# merge the values per RFC 1945 section 4.2.
|
68
|
+
res[k] = vs.split("\n").join(", ")
|
62
69
|
end
|
63
70
|
}
|
64
71
|
body.each { |part|
|
data/lib/rack/lint.rb
CHANGED
@@ -30,7 +30,7 @@ module Rack
|
|
30
30
|
|
31
31
|
## = Rack applications
|
32
32
|
|
33
|
-
## A Rack application is
|
33
|
+
## A Rack application is an Ruby object (not a class) that
|
34
34
|
## responds to +call+.
|
35
35
|
def call(env=nil)
|
36
36
|
dup._call(env)
|
@@ -302,7 +302,7 @@ module Rack
|
|
302
302
|
end
|
303
303
|
|
304
304
|
## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
|
305
|
-
## If given, +length+ must be
|
305
|
+
## If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must
|
306
306
|
## be a String and may not be nil. If +length+ is given and not nil, then this method
|
307
307
|
## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
|
308
308
|
## then this method reads all data until EOF.
|
data/lib/rack/lock.rb
CHANGED
@@ -2,15 +2,41 @@ require 'thread'
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
class Lock
|
5
|
+
class Proxy < Struct.new(:target, :mutex) # :nodoc:
|
6
|
+
def each
|
7
|
+
target.each { |x| yield x }
|
8
|
+
end
|
9
|
+
|
10
|
+
def close
|
11
|
+
target.close if target.respond_to?(:close)
|
12
|
+
ensure
|
13
|
+
mutex.unlock
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_path
|
17
|
+
target.to_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to?(sym)
|
21
|
+
sym.to_sym == :close || target.respond_to?(sym)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
5
25
|
FLAG = 'rack.multithread'.freeze
|
6
26
|
|
7
|
-
def initialize(app,
|
8
|
-
@app, @
|
27
|
+
def initialize(app, mutex = Mutex.new)
|
28
|
+
@app, @mutex = app, mutex
|
9
29
|
end
|
10
30
|
|
11
31
|
def call(env)
|
12
32
|
old, env[FLAG] = env[FLAG], false
|
13
|
-
@lock
|
33
|
+
@mutex.lock
|
34
|
+
response = @app.call(env)
|
35
|
+
response[2] = Proxy.new(response[2], @mutex)
|
36
|
+
response
|
37
|
+
rescue Exception
|
38
|
+
@mutex.unlock
|
39
|
+
raise
|
14
40
|
ensure
|
15
41
|
env[FLAG] = old
|
16
42
|
end
|
data/lib/rack/methodoverride.rb
CHANGED
data/lib/rack/mime.rb
CHANGED
@@ -70,7 +70,6 @@ module Rack
|
|
70
70
|
".dll" => "application/x-msdownload",
|
71
71
|
".dmg" => "application/octet-stream",
|
72
72
|
".doc" => "application/msword",
|
73
|
-
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
74
73
|
".dot" => "application/msword",
|
75
74
|
".dtd" => "application/xml-dtd",
|
76
75
|
".dvi" => "application/x-dvi",
|
@@ -182,6 +181,7 @@ module Rack
|
|
182
181
|
".tiff" => "image/tiff",
|
183
182
|
".torrent" => "application/x-bittorrent",
|
184
183
|
".tr" => "text/troff",
|
184
|
+
".ttf" => "application/octet-stream",
|
185
185
|
".txt" => "text/plain",
|
186
186
|
".vcf" => "text/x-vcard",
|
187
187
|
".vcs" => "text/x-vcalendar",
|
@@ -192,12 +192,12 @@ module Rack
|
|
192
192
|
".wma" => "audio/x-ms-wma",
|
193
193
|
".wmv" => "video/x-ms-wmv",
|
194
194
|
".wmx" => "video/x-ms-wmx",
|
195
|
+
".woff" => "application/octet-stream",
|
195
196
|
".wrl" => "model/vrml",
|
196
197
|
".wsdl" => "application/wsdl+xml",
|
197
198
|
".xbm" => "image/x-xbitmap",
|
198
199
|
".xhtml" => "application/xhtml+xml",
|
199
200
|
".xls" => "application/vnd.ms-excel",
|
200
|
-
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
201
201
|
".xml" => "application/xml",
|
202
202
|
".xpm" => "image/x-xpixmap",
|
203
203
|
".xsl" => "application/xml",
|
data/lib/rack/mock.rb
CHANGED
@@ -141,50 +141,45 @@ module Rack
|
|
141
141
|
# Usually, you don't create the MockResponse on your own, but use
|
142
142
|
# MockRequest.
|
143
143
|
|
144
|
-
class MockResponse
|
145
|
-
def initialize(status, headers, body, errors=StringIO.new(""))
|
146
|
-
@status = status.to_i
|
147
|
-
|
148
|
-
@original_headers = headers
|
149
|
-
@headers = Rack::Utils::HeaderHash.new
|
150
|
-
headers.each { |field, values|
|
151
|
-
@headers[field] = values
|
152
|
-
@headers[field] = "" if values.empty?
|
153
|
-
}
|
154
|
-
|
155
|
-
@body = ""
|
156
|
-
body.each { |part| @body << part }
|
157
|
-
|
158
|
-
@errors = errors.string if errors.respond_to?(:string)
|
159
|
-
end
|
160
|
-
|
161
|
-
# Status
|
162
|
-
attr_reader :status
|
163
|
-
|
144
|
+
class MockResponse < Rack::Response
|
164
145
|
# Headers
|
165
|
-
attr_reader :
|
146
|
+
attr_reader :original_headers
|
166
147
|
|
167
|
-
|
168
|
-
|
169
|
-
end
|
148
|
+
# Errors
|
149
|
+
attr_accessor :errors
|
170
150
|
|
151
|
+
def initialize(status, headers, body, errors=StringIO.new(""))
|
152
|
+
@original_headers = headers
|
153
|
+
@errors = errors.string if errors.respond_to?(:string)
|
154
|
+
@body_string = nil
|
171
155
|
|
172
|
-
|
173
|
-
|
156
|
+
super(body, status, headers)
|
157
|
+
end
|
174
158
|
|
175
159
|
def =~(other)
|
176
|
-
|
160
|
+
body =~ other
|
177
161
|
end
|
178
162
|
|
179
163
|
def match(other)
|
180
|
-
|
164
|
+
body.match other
|
181
165
|
end
|
182
166
|
|
167
|
+
def body
|
168
|
+
# FIXME: apparently users of MockResponse expect the return value of
|
169
|
+
# MockResponse#body to be a string. However, the real response object
|
170
|
+
# returns the body as a list.
|
171
|
+
#
|
172
|
+
# See spec_showstatus.rb:
|
173
|
+
#
|
174
|
+
# should "not replace existing messages" do
|
175
|
+
# ...
|
176
|
+
# res.body.should == "foo!"
|
177
|
+
# end
|
178
|
+
super.join
|
179
|
+
end
|
183
180
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
include Response::Helpers
|
181
|
+
def empty?
|
182
|
+
[201, 204, 304].include? status
|
183
|
+
end
|
189
184
|
end
|
190
185
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rack
|
2
|
+
# A multipart form data parser, adapted from IOWA.
|
3
|
+
#
|
4
|
+
# Usually, Rack::Request#POST takes care of calling this.
|
5
|
+
module Multipart
|
6
|
+
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
7
|
+
autoload :Parser, 'rack/multipart/parser'
|
8
|
+
autoload :Generator, 'rack/multipart/generator'
|
9
|
+
|
10
|
+
EOL = "\r\n"
|
11
|
+
MULTIPART_BOUNDARY = "AaB03x"
|
12
|
+
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
13
|
+
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
14
|
+
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
15
|
+
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})*/
|
16
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
17
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
18
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
19
|
+
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni
|
21
|
+
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def parse_multipart(env)
|
25
|
+
Parser.new(env).parse
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_multipart(params, first = true)
|
29
|
+
Generator.new(params, first).dump
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Rack
|
2
|
+
module Multipart
|
3
|
+
class Generator
|
4
|
+
def initialize(params, first = true)
|
5
|
+
@params, @first = params, first
|
6
|
+
|
7
|
+
if @first && !@params.is_a?(Hash)
|
8
|
+
raise ArgumentError, "value must be a Hash"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def dump
|
13
|
+
return nil if @first && !multipart?
|
14
|
+
return flattened_params if !@first
|
15
|
+
|
16
|
+
flattened_params.map do |name, file|
|
17
|
+
if file.respond_to?(:original_filename)
|
18
|
+
::File.open(file.path, "rb") do |f|
|
19
|
+
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
20
|
+
content_for_tempfile(f, file, name)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
content_for_other(file, name)
|
24
|
+
end
|
25
|
+
end.join + "--#{MULTIPART_BOUNDARY}--\r"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def multipart?
|
30
|
+
multipart = false
|
31
|
+
|
32
|
+
query = lambda { |value|
|
33
|
+
case value
|
34
|
+
when Array
|
35
|
+
value.each(&query)
|
36
|
+
when Hash
|
37
|
+
value.values.each(&query)
|
38
|
+
when Rack::Multipart::UploadedFile
|
39
|
+
multipart = true
|
40
|
+
end
|
41
|
+
}
|
42
|
+
@params.values.each(&query)
|
43
|
+
|
44
|
+
multipart
|
45
|
+
end
|
46
|
+
|
47
|
+
def flattened_params
|
48
|
+
@flattened_params ||= begin
|
49
|
+
h = Hash.new
|
50
|
+
@params.each do |key, value|
|
51
|
+
k = @first ? key.to_s : "[#{key}]"
|
52
|
+
|
53
|
+
case value
|
54
|
+
when Array
|
55
|
+
value.map { |v|
|
56
|
+
Multipart.build_multipart(v, false).each { |subkey, subvalue|
|
57
|
+
h["#{k}[]#{subkey}"] = subvalue
|
58
|
+
}
|
59
|
+
}
|
60
|
+
when Hash
|
61
|
+
Multipart.build_multipart(value, false).each { |subkey, subvalue|
|
62
|
+
h[k + subkey] = subvalue
|
63
|
+
}
|
64
|
+
else
|
65
|
+
h[k] = value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
h
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def content_for_tempfile(io, file, name)
|
73
|
+
<<-EOF
|
74
|
+
--#{MULTIPART_BOUNDARY}\r
|
75
|
+
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
|
76
|
+
Content-Type: #{file.content_type}\r
|
77
|
+
Content-Length: #{::File.stat(file.path).size}\r
|
78
|
+
\r
|
79
|
+
#{io.read}\r
|
80
|
+
EOF
|
81
|
+
end
|
82
|
+
|
83
|
+
def content_for_other(file, name)
|
84
|
+
<<-EOF
|
85
|
+
--#{MULTIPART_BOUNDARY}\r
|
86
|
+
Content-Disposition: form-data; name="#{name}"\r
|
87
|
+
\r
|
88
|
+
#{file}\r
|
89
|
+
EOF
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|