rack 1.0.1 → 2.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/mock.rb CHANGED
@@ -1,19 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'stringio'
3
- require 'rack/lint'
4
- require 'rack/utils'
5
- require 'rack/response'
5
+ require_relative '../rack'
6
6
 
7
7
  module Rack
8
8
  # Rack::MockRequest helps testing your Rack application without
9
9
  # actually using HTTP.
10
10
  #
11
- # After performing a request on a URL with get/post/put/delete, it
11
+ # After performing a request on a URL with get/post/put/patch/delete, it
12
12
  # returns a MockResponse with useful helper methods for effective
13
13
  # testing.
14
14
  #
15
15
  # You can pass a hash with additional configuration to the
16
- # get/post/put/delete.
16
+ # get/post/put/patch/delete.
17
17
  # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
18
18
  # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
19
19
  # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
@@ -40,25 +40,38 @@ module Rack
40
40
  end
41
41
 
42
42
  DEFAULT_ENV = {
43
- "rack.version" => [1,0],
44
- "rack.input" => StringIO.new,
45
- "rack.errors" => StringIO.new,
46
- "rack.multithread" => true,
47
- "rack.multiprocess" => true,
48
- "rack.run_once" => false,
49
- }
43
+ RACK_VERSION => Rack::VERSION,
44
+ RACK_INPUT => StringIO.new,
45
+ RACK_ERRORS => StringIO.new,
46
+ RACK_MULTITHREAD => true,
47
+ RACK_MULTIPROCESS => true,
48
+ RACK_RUNONCE => false,
49
+ }.freeze
50
50
 
51
51
  def initialize(app)
52
52
  @app = app
53
53
  end
54
54
 
55
- def get(uri, opts={}) request("GET", uri, opts) end
56
- def post(uri, opts={}) request("POST", uri, opts) end
57
- def put(uri, opts={}) request("PUT", uri, opts) end
58
- def delete(uri, opts={}) request("DELETE", uri, opts) end
59
-
60
- def request(method="GET", uri="", opts={})
61
- env = self.class.env_for(uri, opts.merge(:method => method))
55
+ # Make a GET request and return a MockResponse. See #request.
56
+ def get(uri, opts = {}) request(GET, uri, opts) end
57
+ # Make a POST request and return a MockResponse. See #request.
58
+ def post(uri, opts = {}) request(POST, uri, opts) end
59
+ # Make a PUT request and return a MockResponse. See #request.
60
+ def put(uri, opts = {}) request(PUT, uri, opts) end
61
+ # Make a PATCH request and return a MockResponse. See #request.
62
+ def patch(uri, opts = {}) request(PATCH, uri, opts) end
63
+ # Make a DELETE request and return a MockResponse. See #request.
64
+ def delete(uri, opts = {}) request(DELETE, uri, opts) end
65
+ # Make a HEAD request and return a MockResponse. See #request.
66
+ def head(uri, opts = {}) request(HEAD, uri, opts) end
67
+ # Make an OPTIONS request and return a MockResponse. See #request.
68
+ def options(uri, opts = {}) request(OPTIONS, uri, opts) end
69
+
70
+ # Make a request using the given request method for the given
71
+ # uri to the rack application and return a MockResponse.
72
+ # Options given are passed to MockRequest.env_for.
73
+ def request(method = GET, uri = "", opts = {})
74
+ env = self.class.env_for(uri, opts.merge(method: method))
62
75
 
63
76
  if opts[:lint]
64
77
  app = Rack::Lint.new(@app)
@@ -66,41 +79,83 @@ module Rack
66
79
  app = @app
67
80
  end
68
81
 
69
- errors = env["rack.errors"]
70
- MockResponse.new(*(app.call(env) + [errors]))
82
+ errors = env[RACK_ERRORS]
83
+ status, headers, body = app.call(env)
84
+ MockResponse.new(status, headers, body, errors)
85
+ ensure
86
+ body.close if body.respond_to?(:close)
87
+ end
88
+
89
+ # For historical reasons, we're pinning to RFC 2396.
90
+ # URI::Parser = URI::RFC2396_Parser
91
+ def self.parse_uri_rfc2396(uri)
92
+ @parser ||= URI::Parser.new
93
+ @parser.parse(uri)
71
94
  end
72
95
 
73
96
  # Return the Rack environment used for a request to +uri+.
74
- def self.env_for(uri="", opts={})
75
- uri = URI(uri)
97
+ # All options that are strings are added to the returned environment.
98
+ # Options:
99
+ # :fatal :: Whether to raise an exception if request outputs to rack.errors
100
+ # :input :: The rack.input to set
101
+ # :method :: The HTTP request method to use
102
+ # :params :: The params to use
103
+ # :script_name :: The SCRIPT_NAME to set
104
+ def self.env_for(uri = "", opts = {})
105
+ uri = parse_uri_rfc2396(uri)
106
+ uri.path = "/#{uri.path}" unless uri.path[0] == ?/
107
+
76
108
  env = DEFAULT_ENV.dup
77
109
 
78
- env["REQUEST_METHOD"] = opts[:method] || "GET"
79
- env["SERVER_NAME"] = uri.host || "example.org"
80
- env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
81
- env["QUERY_STRING"] = uri.query.to_s
82
- env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
83
- env["rack.url_scheme"] = uri.scheme || "http"
110
+ env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
111
+ env[SERVER_NAME] = (uri.host || "example.org").b
112
+ env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
113
+ env[QUERY_STRING] = (uri.query.to_s).b
114
+ env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
115
+ env[RACK_URL_SCHEME] = (uri.scheme || "http").b
116
+ env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
84
117
 
85
- env["SCRIPT_NAME"] = opts[:script_name] || ""
118
+ env[SCRIPT_NAME] = opts[:script_name] || ""
86
119
 
87
120
  if opts[:fatal]
88
- env["rack.errors"] = FatalWarner.new
121
+ env[RACK_ERRORS] = FatalWarner.new
89
122
  else
90
- env["rack.errors"] = StringIO.new
123
+ env[RACK_ERRORS] = StringIO.new
91
124
  end
92
125
 
93
- opts[:input] ||= ""
126
+ if params = opts[:params]
127
+ if env[REQUEST_METHOD] == GET
128
+ params = Utils.parse_nested_query(params) if params.is_a?(String)
129
+ params.update(Utils.parse_nested_query(env[QUERY_STRING]))
130
+ env[QUERY_STRING] = Utils.build_nested_query(params)
131
+ elsif !opts.has_key?(:input)
132
+ opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
133
+ if params.is_a?(Hash)
134
+ if data = Rack::Multipart.build_multipart(params)
135
+ opts[:input] = data
136
+ opts["CONTENT_LENGTH"] ||= data.length.to_s
137
+ opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
138
+ else
139
+ opts[:input] = Utils.build_nested_query(params)
140
+ end
141
+ else
142
+ opts[:input] = params
143
+ end
144
+ end
145
+ end
146
+
147
+ empty_str = String.new
148
+ opts[:input] ||= empty_str
94
149
  if String === opts[:input]
95
150
  rack_input = StringIO.new(opts[:input])
96
151
  else
97
152
  rack_input = opts[:input]
98
153
  end
99
154
 
100
- rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
101
- env['rack.input'] = rack_input
155
+ rack_input.set_encoding(Encoding::BINARY)
156
+ env[RACK_INPUT] = rack_input
102
157
 
103
- env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
158
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
104
159
 
105
160
  opts.each { |field, value|
106
161
  env[field] = value if String === field
@@ -114,50 +169,134 @@ module Rack
114
169
  # Usually, you don't create the MockResponse on your own, but use
115
170
  # MockRequest.
116
171
 
117
- class MockResponse
118
- def initialize(status, headers, body, errors=StringIO.new(""))
119
- @status = status.to_i
120
-
121
- @original_headers = headers
122
- @headers = Rack::Utils::HeaderHash.new
123
- headers.each { |field, values|
124
- @headers[field] = values
125
- @headers[field] = "" if values.empty?
126
- }
127
-
128
- @body = ""
129
- body.each { |part| @body << part }
130
-
131
- @errors = errors.string
172
+ class MockResponse < Rack::Response
173
+ begin
174
+ # Recent versions of the CGI gem may not provide `CGI::Cookie`.
175
+ require 'cgi/cookie'
176
+ Cookie = CGI::Cookie
177
+ rescue LoadError
178
+ class Cookie
179
+ attr_reader :name, :value, :path, :domain, :expires, :secure
180
+
181
+ def initialize(args)
182
+ @name = args["name"]
183
+ @value = args["value"]
184
+ @path = args["path"]
185
+ @domain = args["domain"]
186
+ @expires = args["expires"]
187
+ @secure = args["secure"]
188
+ end
189
+
190
+ def method_missing(method_name, *args, &block)
191
+ @value.send(method_name, *args, &block)
192
+ end
193
+ # :nocov:
194
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
195
+ # :nocov:
196
+
197
+ def respond_to_missing?(method_name, include_all = false)
198
+ @value.respond_to?(method_name, include_all) || super
199
+ end
200
+ end
132
201
  end
133
202
 
134
- # Status
135
- attr_reader :status
203
+ class << self
204
+ alias [] new
205
+ end
136
206
 
137
207
  # Headers
138
- attr_reader :headers, :original_headers
208
+ attr_reader :original_headers, :cookies
139
209
 
140
- def [](field)
141
- headers[field]
142
- end
210
+ # Errors
211
+ attr_accessor :errors
143
212
 
213
+ def initialize(status, headers, body, errors = StringIO.new(""))
214
+ @original_headers = headers
215
+ @errors = errors.string if errors.respond_to?(:string)
216
+ @cookies = parse_cookies_from_header
217
+
218
+ super(body, status, headers)
144
219
 
145
- # Body
146
- attr_reader :body
220
+ buffered_body!
221
+ end
147
222
 
148
223
  def =~(other)
149
- @body =~ other
224
+ body =~ other
150
225
  end
151
226
 
152
227
  def match(other)
153
- @body.match other
228
+ body.match other
154
229
  end
155
230
 
231
+ def body
232
+ # FIXME: apparently users of MockResponse expect the return value of
233
+ # MockResponse#body to be a string. However, the real response object
234
+ # returns the body as a list.
235
+ #
236
+ # See spec_showstatus.rb:
237
+ #
238
+ # should "not replace existing messages" do
239
+ # ...
240
+ # res.body.should == "foo!"
241
+ # end
242
+ buffer = String.new
243
+
244
+ super.each do |chunk|
245
+ buffer << chunk
246
+ end
247
+
248
+ return buffer
249
+ end
156
250
 
157
- # Errors
158
- attr_accessor :errors
251
+ def empty?
252
+ [201, 204, 304].include? status
253
+ end
254
+
255
+ def cookie(name)
256
+ cookies.fetch(name, nil)
257
+ end
258
+
259
+ private
260
+
261
+ def parse_cookies_from_header
262
+ cookies = Hash.new
263
+ if original_headers.has_key? 'Set-Cookie'
264
+ set_cookie_header = original_headers.fetch('Set-Cookie')
265
+ set_cookie_header.split("\n").each do |cookie|
266
+ cookie_name, cookie_filling = cookie.split('=', 2)
267
+ cookie_attributes = identify_cookie_attributes cookie_filling
268
+ parsed_cookie = Cookie.new(
269
+ 'name' => cookie_name.strip,
270
+ 'value' => cookie_attributes.fetch('value'),
271
+ 'path' => cookie_attributes.fetch('path', nil),
272
+ 'domain' => cookie_attributes.fetch('domain', nil),
273
+ 'expires' => cookie_attributes.fetch('expires', nil),
274
+ 'secure' => cookie_attributes.fetch('secure', false)
275
+ )
276
+ cookies.store(cookie_name, parsed_cookie)
277
+ end
278
+ end
279
+ cookies
280
+ end
159
281
 
282
+ def identify_cookie_attributes(cookie_filling)
283
+ cookie_bits = cookie_filling.split(';')
284
+ cookie_attributes = Hash.new
285
+ cookie_attributes.store('value', Array(cookie_bits[0].strip))
286
+ cookie_bits.each do |bit|
287
+ if bit.include? '='
288
+ cookie_attribute, attribute_value = bit.split('=')
289
+ cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
290
+ if cookie_attribute.include? 'max-age'
291
+ cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
292
+ end
293
+ end
294
+ if bit.include? 'secure'
295
+ cookie_attributes.store('secure', true)
296
+ end
297
+ end
298
+ cookie_attributes
299
+ end
160
300
 
161
- include Response::Helpers
162
301
  end
163
302
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module Multipart
5
+ class Generator
6
+ def initialize(params, first = true)
7
+ @params, @first = params, first
8
+
9
+ if @first && !@params.is_a?(Hash)
10
+ raise ArgumentError, "value must be a Hash"
11
+ end
12
+ end
13
+
14
+ def dump
15
+ return nil if @first && !multipart?
16
+ return flattened_params unless @first
17
+
18
+ flattened_params.map do |name, file|
19
+ if file.respond_to?(:original_filename)
20
+ if file.path
21
+ ::File.open(file.path, 'rb') do |f|
22
+ f.set_encoding(Encoding::BINARY)
23
+ content_for_tempfile(f, file, name)
24
+ end
25
+ else
26
+ content_for_tempfile(file, file, name)
27
+ end
28
+ else
29
+ content_for_other(file, name)
30
+ end
31
+ end.join << "--#{MULTIPART_BOUNDARY}--\r"
32
+ end
33
+
34
+ private
35
+ def multipart?
36
+ query = lambda { |value|
37
+ case value
38
+ when Array
39
+ value.any?(&query)
40
+ when Hash
41
+ value.values.any?(&query)
42
+ when Rack::Multipart::UploadedFile
43
+ true
44
+ end
45
+ }
46
+
47
+ @params.values.any?(&query)
48
+ end
49
+
50
+ def flattened_params
51
+ @flattened_params ||= begin
52
+ h = Hash.new
53
+ @params.each do |key, value|
54
+ k = @first ? key.to_s : "[#{key}]"
55
+
56
+ case value
57
+ when Array
58
+ value.map { |v|
59
+ Multipart.build_multipart(v, false).each { |subkey, subvalue|
60
+ h["#{k}[]#{subkey}"] = subvalue
61
+ }
62
+ }
63
+ when Hash
64
+ Multipart.build_multipart(value, false).each { |subkey, subvalue|
65
+ h[k + subkey] = subvalue
66
+ }
67
+ else
68
+ h[k] = value
69
+ end
70
+ end
71
+ h
72
+ end
73
+ end
74
+
75
+ def content_for_tempfile(io, file, name)
76
+ length = ::File.stat(file.path).size if file.path
77
+ filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
78
+ <<-EOF
79
+ --#{MULTIPART_BOUNDARY}\r
80
+ Content-Disposition: form-data; name="#{name}"#{filename}\r
81
+ Content-Type: #{file.content_type}\r
82
+ #{"Content-Length: #{length}\r\n" if length}\r
83
+ #{io.read}\r
84
+ EOF
85
+ end
86
+
87
+ def content_for_other(file, name)
88
+ <<-EOF
89
+ --#{MULTIPART_BOUNDARY}\r
90
+ Content-Disposition: form-data; name="#{name}"\r
91
+ \r
92
+ #{file}\r
93
+ EOF
94
+ end
95
+ end
96
+ end
97
+ end