rest-client 1.7.0.rc1-x64-mingw32
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.
Potentially problematic release.
This version of rest-client might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +14 -0
- data/AUTHORS +81 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.rdoc +325 -0
- data/Rakefile +117 -0
- data/bin/restclient +93 -0
- data/history.md +166 -0
- data/lib/rest-client.rb +2 -0
- data/lib/rest_client.rb +2 -0
- data/lib/restclient.rb +164 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +203 -0
- data/lib/restclient/payload.rb +240 -0
- data/lib/restclient/platform.rb +30 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +582 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient/version.rb +7 -0
- data/lib/restclient/windows.rb +8 -0
- data/lib/restclient/windows/root_certs.rb +105 -0
- data/rest-client.gemspec +30 -0
- data/rest-client.windows.gemspec +19 -0
- data/spec/integration/capath_digicert/244b5494.0 +19 -0
- data/spec/integration/capath_digicert/81b9768f.0 +19 -0
- data/spec/integration/capath_digicert/README +8 -0
- data/spec/integration/capath_digicert/digicert.crt +19 -0
- data/spec/integration/capath_verisign/415660c1.0 +14 -0
- data/spec/integration/capath_verisign/7651b327.0 +14 -0
- data/spec/integration/capath_verisign/README +8 -0
- data/spec/integration/capath_verisign/verisign.crt +14 -0
- data/spec/integration/certs/digicert.crt +19 -0
- data/spec/integration/certs/verisign.crt +14 -0
- data/spec/integration/integration_spec.rb +35 -0
- data/spec/integration/request_spec.rb +104 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/unit/abstract_response_spec.rb +85 -0
- data/spec/unit/exceptions_spec.rb +95 -0
- data/spec/unit/master_shake.jpg +0 -0
- data/spec/unit/payload_spec.rb +245 -0
- data/spec/unit/raw_response_spec.rb +17 -0
- data/spec/unit/request2_spec.rb +32 -0
- data/spec/unit/request_spec.rb +905 -0
- data/spec/unit/resource_spec.rb +133 -0
- data/spec/unit/response_spec.rb +166 -0
- data/spec/unit/restclient_spec.rb +79 -0
- data/spec/unit/windows/root_certs_spec.rb +22 -0
- metadata +241 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module RestClient
|
4
|
+
|
5
|
+
module AbstractResponse
|
6
|
+
|
7
|
+
attr_reader :net_http_res, :args
|
8
|
+
|
9
|
+
# HTTP status code
|
10
|
+
def code
|
11
|
+
@code ||= @net_http_res.code.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
# A hash of the headers, beautified with symbols and underscores.
|
15
|
+
# e.g. "Content-type" will become :content_type.
|
16
|
+
def headers
|
17
|
+
@headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
# The raw headers.
|
21
|
+
def raw_headers
|
22
|
+
@raw_headers ||= @net_http_res.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# Hash of cookies extracted from response headers
|
26
|
+
def cookies
|
27
|
+
@cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
|
28
|
+
out.merge parse_cookie(cookie_content)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the default behavior corresponding to the response code:
|
33
|
+
# the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
|
34
|
+
def return! request = nil, result = nil, & block
|
35
|
+
if (200..207).include? code
|
36
|
+
self
|
37
|
+
elsif [301, 302, 307].include? code
|
38
|
+
unless [:get, :head].include? args[:method]
|
39
|
+
raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
|
40
|
+
else
|
41
|
+
follow_redirection(request, result, & block)
|
42
|
+
end
|
43
|
+
elsif code == 303
|
44
|
+
args[:method] = :get
|
45
|
+
args.delete :payload
|
46
|
+
follow_redirection(request, result, & block)
|
47
|
+
elsif Exceptions::EXCEPTIONS_MAP[code]
|
48
|
+
raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
|
49
|
+
else
|
50
|
+
raise RequestFailed.new(self, code)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_i
|
55
|
+
code
|
56
|
+
end
|
57
|
+
|
58
|
+
def description
|
59
|
+
"#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Follow a redirection
|
63
|
+
def follow_redirection request = nil, result = nil, & block
|
64
|
+
url = headers[:location]
|
65
|
+
if url !~ /^http/
|
66
|
+
url = URI.parse(args[:url]).merge(url).to_s
|
67
|
+
end
|
68
|
+
args[:url] = url
|
69
|
+
if request
|
70
|
+
if request.max_redirects == 0
|
71
|
+
raise MaxRedirectsReached
|
72
|
+
end
|
73
|
+
args[:password] = request.password
|
74
|
+
args[:user] = request.user
|
75
|
+
args[:headers] = request.headers
|
76
|
+
args[:max_redirects] = request.max_redirects - 1
|
77
|
+
# pass any cookie set in the result
|
78
|
+
if result && result['set-cookie']
|
79
|
+
args[:headers][:cookies] = (args[:headers][:cookies] || {}).merge(parse_cookie(result['set-cookie']))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
Request.execute args, &block
|
83
|
+
end
|
84
|
+
|
85
|
+
def AbstractResponse.beautify_headers(headers)
|
86
|
+
headers.inject({}) do |out, (key, value)|
|
87
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
|
88
|
+
out
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Parse a cookie value and return its content in an Hash
|
95
|
+
def parse_cookie cookie_content
|
96
|
+
out = {}
|
97
|
+
CGI::Cookie::parse(cookie_content).each do |key, cookie|
|
98
|
+
unless ['expires', 'path'].include? key
|
99
|
+
out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
|
100
|
+
end
|
101
|
+
end
|
102
|
+
out
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module RestClient
|
2
|
+
|
3
|
+
STATUSES = {100 => 'Continue',
|
4
|
+
101 => 'Switching Protocols',
|
5
|
+
102 => 'Processing', #WebDAV
|
6
|
+
|
7
|
+
200 => 'OK',
|
8
|
+
201 => 'Created',
|
9
|
+
202 => 'Accepted',
|
10
|
+
203 => 'Non-Authoritative Information', # http/1.1
|
11
|
+
204 => 'No Content',
|
12
|
+
205 => 'Reset Content',
|
13
|
+
206 => 'Partial Content',
|
14
|
+
207 => 'Multi-Status', #WebDAV
|
15
|
+
|
16
|
+
300 => 'Multiple Choices',
|
17
|
+
301 => 'Moved Permanently',
|
18
|
+
302 => 'Found',
|
19
|
+
303 => 'See Other', # http/1.1
|
20
|
+
304 => 'Not Modified',
|
21
|
+
305 => 'Use Proxy', # http/1.1
|
22
|
+
306 => 'Switch Proxy', # no longer used
|
23
|
+
307 => 'Temporary Redirect', # http/1.1
|
24
|
+
|
25
|
+
400 => 'Bad Request',
|
26
|
+
401 => 'Unauthorized',
|
27
|
+
402 => 'Payment Required',
|
28
|
+
403 => 'Forbidden',
|
29
|
+
404 => 'Resource Not Found',
|
30
|
+
405 => 'Method Not Allowed',
|
31
|
+
406 => 'Not Acceptable',
|
32
|
+
407 => 'Proxy Authentication Required',
|
33
|
+
408 => 'Request Timeout',
|
34
|
+
409 => 'Conflict',
|
35
|
+
410 => 'Gone',
|
36
|
+
411 => 'Length Required',
|
37
|
+
412 => 'Precondition Failed',
|
38
|
+
413 => 'Request Entity Too Large',
|
39
|
+
414 => 'Request-URI Too Long',
|
40
|
+
415 => 'Unsupported Media Type',
|
41
|
+
416 => 'Requested Range Not Satisfiable',
|
42
|
+
417 => 'Expectation Failed',
|
43
|
+
418 => 'I\'m A Teapot', #RFC2324
|
44
|
+
421 => 'Too Many Connections From This IP',
|
45
|
+
422 => 'Unprocessable Entity', #WebDAV
|
46
|
+
423 => 'Locked', #WebDAV
|
47
|
+
424 => 'Failed Dependency', #WebDAV
|
48
|
+
425 => 'Unordered Collection', #WebDAV
|
49
|
+
426 => 'Upgrade Required',
|
50
|
+
428 => 'Precondition Required', #RFC6585
|
51
|
+
429 => 'Too Many Requests', #RFC6585
|
52
|
+
431 => 'Request Header Fields Too Large', #RFC6585
|
53
|
+
449 => 'Retry With', #Microsoft
|
54
|
+
450 => 'Blocked By Windows Parental Controls', #Microsoft
|
55
|
+
|
56
|
+
500 => 'Internal Server Error',
|
57
|
+
501 => 'Not Implemented',
|
58
|
+
502 => 'Bad Gateway',
|
59
|
+
503 => 'Service Unavailable',
|
60
|
+
504 => 'Gateway Timeout',
|
61
|
+
505 => 'HTTP Version Not Supported',
|
62
|
+
506 => 'Variant Also Negotiates',
|
63
|
+
507 => 'Insufficient Storage', #WebDAV
|
64
|
+
509 => 'Bandwidth Limit Exceeded', #Apache
|
65
|
+
510 => 'Not Extended',
|
66
|
+
511 => 'Network Authentication Required', # RFC6585
|
67
|
+
}
|
68
|
+
|
69
|
+
# Compatibility : make the Response act like a Net::HTTPResponse when needed
|
70
|
+
module ResponseForException
|
71
|
+
def method_missing symbol, *args
|
72
|
+
if net_http_res.respond_to? symbol
|
73
|
+
warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
|
74
|
+
net_http_res.send symbol, *args
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# This is the base RestClient exception class. Rescue it if you want to
|
82
|
+
# catch any exception that your request might raise
|
83
|
+
# You can get the status code by e.http_code, or see anything about the
|
84
|
+
# response via e.response.
|
85
|
+
# For example, the entire result body (which is
|
86
|
+
# probably an HTML error page) is e.response.
|
87
|
+
class Exception < RuntimeError
|
88
|
+
attr_accessor :response
|
89
|
+
attr_writer :message
|
90
|
+
|
91
|
+
def initialize response = nil, initial_response_code = nil
|
92
|
+
@response = response
|
93
|
+
@message = nil
|
94
|
+
@initial_response_code = initial_response_code
|
95
|
+
|
96
|
+
# compatibility: this make the exception behave like a Net::HTTPResponse
|
97
|
+
response.extend ResponseForException if response
|
98
|
+
end
|
99
|
+
|
100
|
+
def http_code
|
101
|
+
# return integer for compatibility
|
102
|
+
if @response
|
103
|
+
@response.code.to_i
|
104
|
+
else
|
105
|
+
@initial_response_code
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def http_body
|
110
|
+
@response.body if @response
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"#{message}: #{http_body}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s
|
118
|
+
inspect
|
119
|
+
end
|
120
|
+
|
121
|
+
def message
|
122
|
+
@message || self.class.name
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# Compatibility
|
128
|
+
class ExceptionWithResponse < Exception
|
129
|
+
end
|
130
|
+
|
131
|
+
# The request failed with an error code not managed by the code
|
132
|
+
class RequestFailed < ExceptionWithResponse
|
133
|
+
|
134
|
+
def message
|
135
|
+
"HTTP status code #{http_code}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
message
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
144
|
+
module Exceptions
|
145
|
+
# Map http status codes to the corresponding exception class
|
146
|
+
EXCEPTIONS_MAP = {}
|
147
|
+
end
|
148
|
+
|
149
|
+
STATUSES.each_pair do |code, message|
|
150
|
+
|
151
|
+
# Compatibility
|
152
|
+
superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
|
153
|
+
klass = Class.new(superclass) do
|
154
|
+
send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
|
155
|
+
end
|
156
|
+
klass_constant = const_set message.delete(' \-\''), klass
|
157
|
+
Exceptions::EXCEPTIONS_MAP[code] = klass_constant
|
158
|
+
end
|
159
|
+
|
160
|
+
# A redirect was encountered; caught by execute to retry with the new url.
|
161
|
+
class Redirect < Exception
|
162
|
+
|
163
|
+
def message
|
164
|
+
'Redirect'
|
165
|
+
end
|
166
|
+
|
167
|
+
attr_accessor :url
|
168
|
+
|
169
|
+
def initialize(url)
|
170
|
+
@url = url
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class MaxRedirectsReached < Exception
|
175
|
+
def message
|
176
|
+
'Maximum number of redirect reached'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# The server broke the connection prior to the request completing. Usually
|
181
|
+
# this means it crashed, or sometimes that your network connection was
|
182
|
+
# severed before it could complete.
|
183
|
+
class ServerBrokeConnection < Exception
|
184
|
+
def initialize(message = 'Server broke connection')
|
185
|
+
super nil, nil
|
186
|
+
self.message = message
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class SSLCertificateNotVerified < Exception
|
191
|
+
def initialize(message)
|
192
|
+
super nil, nil
|
193
|
+
self.message = message
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# backwards compatibility
|
199
|
+
class RestClient::Request
|
200
|
+
Redirect = RestClient::Redirect
|
201
|
+
Unauthorized = RestClient::Unauthorized
|
202
|
+
RequestFailed = RestClient::RequestFailed
|
203
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'stringio'
|
3
|
+
require 'mime/types'
|
4
|
+
|
5
|
+
module RestClient
|
6
|
+
module Payload
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def generate(params)
|
10
|
+
if params.is_a?(String)
|
11
|
+
Base.new(params)
|
12
|
+
elsif params.is_a?(Hash)
|
13
|
+
if params.delete(:multipart) == true || has_file?(params)
|
14
|
+
Multipart.new(params)
|
15
|
+
else
|
16
|
+
UrlEncoded.new(params)
|
17
|
+
end
|
18
|
+
elsif params.respond_to?(:read)
|
19
|
+
Streamed.new(params)
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_file?(params)
|
26
|
+
params.any? do |_, v|
|
27
|
+
case v
|
28
|
+
when Hash
|
29
|
+
has_file?(v)
|
30
|
+
when Array
|
31
|
+
has_file_array?(v)
|
32
|
+
else
|
33
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_file_array?(params)
|
39
|
+
params.any? do |v|
|
40
|
+
case v
|
41
|
+
when Hash
|
42
|
+
has_file?(v)
|
43
|
+
when Array
|
44
|
+
has_file_array?(v)
|
45
|
+
else
|
46
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Base
|
52
|
+
def initialize(params)
|
53
|
+
build_stream(params)
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_stream(params)
|
57
|
+
@stream = StringIO.new(params)
|
58
|
+
@stream.seek(0)
|
59
|
+
end
|
60
|
+
|
61
|
+
def read(bytes=nil)
|
62
|
+
@stream.read(bytes)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias :to_s :read
|
66
|
+
|
67
|
+
# Flatten parameters by converting hashes of hashes to flat hashes
|
68
|
+
# {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value]
|
69
|
+
def flatten_params(params, parent_key = nil)
|
70
|
+
result = []
|
71
|
+
params.each do |key, value|
|
72
|
+
calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key)
|
73
|
+
if value.is_a? Hash
|
74
|
+
result += flatten_params(value, calculated_key)
|
75
|
+
elsif value.is_a? Array
|
76
|
+
result += flatten_params_array(value, calculated_key)
|
77
|
+
else
|
78
|
+
result << [calculated_key, value]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def flatten_params_array value, calculated_key
|
85
|
+
result = []
|
86
|
+
value.each do |elem|
|
87
|
+
if elem.is_a? Hash
|
88
|
+
result += flatten_params(elem, calculated_key)
|
89
|
+
elsif elem.is_a? Array
|
90
|
+
result += flatten_params_array(elem, calculated_key)
|
91
|
+
else
|
92
|
+
result << ["#{calculated_key}[]", elem]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def headers
|
99
|
+
{'Content-Length' => size.to_s}
|
100
|
+
end
|
101
|
+
|
102
|
+
def size
|
103
|
+
@stream.size
|
104
|
+
end
|
105
|
+
|
106
|
+
alias :length :size
|
107
|
+
|
108
|
+
def close
|
109
|
+
@stream.close unless @stream.closed?
|
110
|
+
end
|
111
|
+
|
112
|
+
def inspect
|
113
|
+
result = to_s.inspect
|
114
|
+
@stream.seek(0)
|
115
|
+
result
|
116
|
+
end
|
117
|
+
|
118
|
+
def short_inspect
|
119
|
+
(size > 500 ? "#{size} byte(s) length" : inspect)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Streamed < Base
|
125
|
+
def build_stream(params = nil)
|
126
|
+
@stream = params
|
127
|
+
end
|
128
|
+
|
129
|
+
def size
|
130
|
+
if @stream.respond_to?(:size)
|
131
|
+
@stream.size
|
132
|
+
elsif @stream.is_a?(IO)
|
133
|
+
@stream.stat.size
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
alias :length :size
|
138
|
+
end
|
139
|
+
|
140
|
+
class UrlEncoded < Base
|
141
|
+
def build_stream(params = nil)
|
142
|
+
@stream = StringIO.new(flatten_params(params).collect do |entry|
|
143
|
+
"#{entry[0]}=#{handle_key(entry[1])}"
|
144
|
+
end.join("&"))
|
145
|
+
@stream.seek(0)
|
146
|
+
end
|
147
|
+
|
148
|
+
# for UrlEncoded escape the keys
|
149
|
+
def handle_key key
|
150
|
+
parser.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
151
|
+
end
|
152
|
+
|
153
|
+
def headers
|
154
|
+
super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def parser
|
159
|
+
URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Multipart < Base
|
164
|
+
EOL = "\r\n"
|
165
|
+
|
166
|
+
def build_stream(params)
|
167
|
+
b = "--#{boundary}"
|
168
|
+
|
169
|
+
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
170
|
+
@stream.binmode
|
171
|
+
@stream.write(b + EOL)
|
172
|
+
|
173
|
+
if params.is_a? Hash
|
174
|
+
x = flatten_params(params)
|
175
|
+
else
|
176
|
+
x = params
|
177
|
+
end
|
178
|
+
|
179
|
+
last_index = x.length - 1
|
180
|
+
x.each_with_index do |a, index|
|
181
|
+
k, v = * a
|
182
|
+
if v.respond_to?(:read) && v.respond_to?(:path)
|
183
|
+
create_file_field(@stream, k, v)
|
184
|
+
else
|
185
|
+
create_regular_field(@stream, k, v)
|
186
|
+
end
|
187
|
+
@stream.write(EOL + b)
|
188
|
+
@stream.write(EOL) unless last_index == index
|
189
|
+
end
|
190
|
+
@stream.write('--')
|
191
|
+
@stream.write(EOL)
|
192
|
+
@stream.seek(0)
|
193
|
+
end
|
194
|
+
|
195
|
+
def create_regular_field(s, k, v)
|
196
|
+
s.write("Content-Disposition: form-data; name=\"#{k}\"")
|
197
|
+
s.write(EOL)
|
198
|
+
s.write(EOL)
|
199
|
+
s.write(v)
|
200
|
+
end
|
201
|
+
|
202
|
+
def create_file_field(s, k, v)
|
203
|
+
begin
|
204
|
+
s.write("Content-Disposition: form-data;")
|
205
|
+
s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
|
206
|
+
s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
|
207
|
+
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
|
208
|
+
s.write(EOL)
|
209
|
+
while data = v.read(8124)
|
210
|
+
s.write(data)
|
211
|
+
end
|
212
|
+
ensure
|
213
|
+
v.close if v.respond_to?(:close)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def mime_for(path)
|
218
|
+
mime = MIME::Types.type_for path
|
219
|
+
mime.empty? ? 'text/plain' : mime[0].content_type
|
220
|
+
end
|
221
|
+
|
222
|
+
def boundary
|
223
|
+
@boundary ||= rand(1_000_000).to_s
|
224
|
+
end
|
225
|
+
|
226
|
+
# for Multipart do not escape the keys
|
227
|
+
def handle_key key
|
228
|
+
key
|
229
|
+
end
|
230
|
+
|
231
|
+
def headers
|
232
|
+
super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
|
233
|
+
end
|
234
|
+
|
235
|
+
def close
|
236
|
+
@stream.close!
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|