rest-client 1.8.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.mailmap +10 -0
- data/.rspec +2 -1
- data/.rubocop +2 -0
- data/.rubocop-disables.yml +386 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +56 -8
- data/AUTHORS +26 -1
- data/README.md +901 -0
- data/Rakefile +27 -3
- data/bin/restclient +3 -5
- data/history.md +181 -0
- data/lib/restclient/abstract_response.rb +172 -55
- data/lib/restclient/exceptions.rb +96 -55
- data/lib/restclient/params_array.rb +72 -0
- data/lib/restclient/payload.rb +70 -74
- data/lib/restclient/platform.rb +19 -0
- data/lib/restclient/raw_response.rb +21 -7
- data/lib/restclient/request.rb +540 -281
- data/lib/restclient/resource.rb +19 -9
- data/lib/restclient/response.rb +75 -6
- data/lib/restclient/utils.rb +274 -0
- data/lib/restclient/version.rb +2 -1
- data/lib/restclient.rb +21 -3
- data/rest-client.gemspec +12 -10
- data/spec/ISS.jpg +0 -0
- data/spec/helpers.rb +54 -0
- data/spec/integration/_lib.rb +1 -0
- data/spec/integration/capath_digicert/3513523f.0 +22 -0
- data/spec/integration/capath_digicert/399e7759.0 +22 -0
- data/spec/integration/capath_digicert/digicert.crt +20 -17
- data/spec/integration/certs/digicert.crt +20 -17
- data/spec/integration/httpbin_spec.rb +128 -0
- data/spec/integration/integration_spec.rb +97 -14
- data/spec/integration/request_spec.rb +25 -2
- data/spec/spec_helper.rb +28 -1
- data/spec/unit/_lib.rb +1 -0
- data/spec/unit/abstract_response_spec.rb +95 -38
- data/spec/unit/exceptions_spec.rb +41 -28
- data/spec/unit/params_array_spec.rb +36 -0
- data/spec/unit/payload_spec.rb +118 -68
- data/spec/unit/raw_response_spec.rb +10 -6
- data/spec/unit/request2_spec.rb +34 -12
- data/spec/unit/request_spec.rb +745 -424
- data/spec/unit/resource_spec.rb +31 -27
- data/spec/unit/response_spec.rb +134 -57
- data/spec/unit/restclient_spec.rb +16 -15
- data/spec/unit/utils_spec.rb +147 -0
- data/spec/unit/windows/root_certs_spec.rb +3 -3
- metadata +79 -29
- data/README.rdoc +0 -324
- data/spec/integration/capath_digicert/244b5494.0 +0 -19
- data/spec/integration/capath_digicert/81b9768f.0 +0 -19
- data/spec/unit/master_shake.jpg +0 -0
@@ -1,5 +1,19 @@
|
|
1
1
|
module RestClient
|
2
2
|
|
3
|
+
# Hash of HTTP status code => message.
|
4
|
+
#
|
5
|
+
# 1xx: Informational - Request received, continuing process
|
6
|
+
# 2xx: Success - The action was successfully received, understood, and
|
7
|
+
# accepted
|
8
|
+
# 3xx: Redirection - Further action must be taken in order to complete the
|
9
|
+
# request
|
10
|
+
# 4xx: Client Error - The request contains bad syntax or cannot be fulfilled
|
11
|
+
# 5xx: Server Error - The server failed to fulfill an apparently valid
|
12
|
+
# request
|
13
|
+
#
|
14
|
+
# @see
|
15
|
+
# http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
16
|
+
#
|
3
17
|
STATUSES = {100 => 'Continue',
|
4
18
|
101 => 'Switching Protocols',
|
5
19
|
102 => 'Processing', #WebDAV
|
@@ -12,6 +26,8 @@ module RestClient
|
|
12
26
|
205 => 'Reset Content',
|
13
27
|
206 => 'Partial Content',
|
14
28
|
207 => 'Multi-Status', #WebDAV
|
29
|
+
208 => 'Already Reported', # RFC5842
|
30
|
+
226 => 'IM Used', # RFC3229
|
15
31
|
|
16
32
|
300 => 'Multiple Choices',
|
17
33
|
301 => 'Moved Permanently',
|
@@ -21,12 +37,13 @@ module RestClient
|
|
21
37
|
305 => 'Use Proxy', # http/1.1
|
22
38
|
306 => 'Switch Proxy', # no longer used
|
23
39
|
307 => 'Temporary Redirect', # http/1.1
|
40
|
+
308 => 'Permanent Redirect', # RFC7538
|
24
41
|
|
25
42
|
400 => 'Bad Request',
|
26
43
|
401 => 'Unauthorized',
|
27
44
|
402 => 'Payment Required',
|
28
45
|
403 => 'Forbidden',
|
29
|
-
404 => '
|
46
|
+
404 => 'Not Found',
|
30
47
|
405 => 'Method Not Allowed',
|
31
48
|
406 => 'Not Acceptable',
|
32
49
|
407 => 'Proxy Authentication Required',
|
@@ -35,10 +52,10 @@ module RestClient
|
|
35
52
|
410 => 'Gone',
|
36
53
|
411 => 'Length Required',
|
37
54
|
412 => 'Precondition Failed',
|
38
|
-
413 => '
|
39
|
-
414 => '
|
55
|
+
413 => 'Payload Too Large', # RFC7231 (renamed, see below)
|
56
|
+
414 => 'URI Too Long', # RFC7231 (renamed, see below)
|
40
57
|
415 => 'Unsupported Media Type',
|
41
|
-
416 => '
|
58
|
+
416 => 'Range Not Satisfiable', # RFC7233 (renamed, see below)
|
42
59
|
417 => 'Expectation Failed',
|
43
60
|
418 => 'I\'m A Teapot', #RFC2324
|
44
61
|
421 => 'Too Many Connections From This IP',
|
@@ -61,22 +78,27 @@ module RestClient
|
|
61
78
|
505 => 'HTTP Version Not Supported',
|
62
79
|
506 => 'Variant Also Negotiates',
|
63
80
|
507 => 'Insufficient Storage', #WebDAV
|
81
|
+
508 => 'Loop Detected', # RFC5842
|
64
82
|
509 => 'Bandwidth Limit Exceeded', #Apache
|
65
83
|
510 => 'Not Extended',
|
66
84
|
511 => 'Network Authentication Required', # RFC6585
|
67
85
|
}
|
68
86
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
STATUSES_COMPATIBILITY = {
|
88
|
+
# The RFCs all specify "Not Found", but "Resource Not Found" was used in
|
89
|
+
# earlier RestClient releases.
|
90
|
+
404 => ['ResourceNotFound'],
|
91
|
+
|
92
|
+
# HTTP 413 was renamed to "Payload Too Large" in RFC7231.
|
93
|
+
413 => ['RequestEntityTooLarge'],
|
94
|
+
|
95
|
+
# HTTP 414 was renamed to "URI Too Long" in RFC7231.
|
96
|
+
414 => ['RequestURITooLong'],
|
97
|
+
|
98
|
+
# HTTP 416 was renamed to "Range Not Satisfiable" in RFC7233.
|
99
|
+
416 => ['RequestedRangeNotSatisfiable'],
|
100
|
+
}
|
101
|
+
|
80
102
|
|
81
103
|
# This is the base RestClient exception class. Rescue it if you want to
|
82
104
|
# catch any exception that your request might raise
|
@@ -86,15 +108,13 @@ module RestClient
|
|
86
108
|
# probably an HTML error page) is e.response.
|
87
109
|
class Exception < RuntimeError
|
88
110
|
attr_accessor :response
|
111
|
+
attr_accessor :original_exception
|
89
112
|
attr_writer :message
|
90
113
|
|
91
114
|
def initialize response = nil, initial_response_code = nil
|
92
115
|
@response = response
|
93
116
|
@message = nil
|
94
117
|
@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
118
|
end
|
99
119
|
|
100
120
|
def http_code
|
@@ -106,32 +126,35 @@ module RestClient
|
|
106
126
|
end
|
107
127
|
end
|
108
128
|
|
109
|
-
def
|
110
|
-
@response.
|
129
|
+
def http_headers
|
130
|
+
@response.headers if @response
|
111
131
|
end
|
112
132
|
|
113
|
-
def
|
114
|
-
|
133
|
+
def http_body
|
134
|
+
@response.body if @response
|
115
135
|
end
|
116
136
|
|
117
137
|
def to_s
|
118
|
-
|
138
|
+
message
|
119
139
|
end
|
120
140
|
|
121
141
|
def message
|
122
|
-
@message ||
|
142
|
+
@message || default_message
|
123
143
|
end
|
124
144
|
|
145
|
+
def default_message
|
146
|
+
self.class.name
|
147
|
+
end
|
125
148
|
end
|
126
149
|
|
127
150
|
# Compatibility
|
128
|
-
class ExceptionWithResponse < Exception
|
151
|
+
class ExceptionWithResponse < RestClient::Exception
|
129
152
|
end
|
130
153
|
|
131
154
|
# The request failed with an error code not managed by the code
|
132
155
|
class RequestFailed < ExceptionWithResponse
|
133
156
|
|
134
|
-
def
|
157
|
+
def default_message
|
135
158
|
"HTTP status code #{http_code}"
|
136
159
|
end
|
137
160
|
|
@@ -140,64 +163,82 @@ module RestClient
|
|
140
163
|
end
|
141
164
|
end
|
142
165
|
|
143
|
-
#
|
166
|
+
# RestClient exception classes. TODO: move all exceptions into this module.
|
167
|
+
#
|
168
|
+
# We will a create an exception for each status code, see
|
169
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
170
|
+
#
|
144
171
|
module Exceptions
|
145
172
|
# Map http status codes to the corresponding exception class
|
146
173
|
EXCEPTIONS_MAP = {}
|
147
174
|
end
|
148
175
|
|
176
|
+
# Create HTTP status exception classes
|
149
177
|
STATUSES.each_pair do |code, message|
|
150
|
-
|
151
|
-
|
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}"}
|
178
|
+
klass = Class.new(RequestFailed) do
|
179
|
+
send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
|
155
180
|
end
|
156
|
-
klass_constant = const_set
|
181
|
+
klass_constant = const_set(message.delete(' \-\''), klass)
|
157
182
|
Exceptions::EXCEPTIONS_MAP[code] = klass_constant
|
158
183
|
end
|
159
184
|
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
185
|
+
# Create HTTP status exception classes used for backwards compatibility
|
186
|
+
STATUSES_COMPATIBILITY.each_pair do |code, compat_list|
|
187
|
+
klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
|
188
|
+
compat_list.each do |old_name|
|
189
|
+
const_set(old_name, klass)
|
165
190
|
end
|
191
|
+
end
|
166
192
|
|
167
|
-
|
193
|
+
module Exceptions
|
194
|
+
# We have to split the Exceptions module like we do here because the
|
195
|
+
# EXCEPTIONS_MAP is under Exceptions, but we depend on
|
196
|
+
# RestClient::RequestTimeout below.
|
197
|
+
|
198
|
+
# Base class for request timeouts.
|
199
|
+
#
|
200
|
+
# NB: Previous releases of rest-client would raise RequestTimeout both for
|
201
|
+
# HTTP 408 responses and for actual connection timeouts.
|
202
|
+
class Timeout < RestClient::RequestTimeout
|
203
|
+
def initialize(message=nil, original_exception=nil)
|
204
|
+
super(nil, nil)
|
205
|
+
self.message = message if message
|
206
|
+
self.original_exception = original_exception if original_exception
|
207
|
+
end
|
208
|
+
end
|
168
209
|
|
169
|
-
|
170
|
-
|
210
|
+
# Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in
|
211
|
+
# ruby 2.0 or greater).
|
212
|
+
class OpenTimeout < Timeout
|
213
|
+
def default_message
|
214
|
+
'Timed out connecting to server'
|
215
|
+
end
|
171
216
|
end
|
172
|
-
end
|
173
217
|
|
174
|
-
|
175
|
-
|
176
|
-
|
218
|
+
# Timeout when reading from a server. Typically wraps Net::ReadTimeout (in
|
219
|
+
# ruby 2.0 or greater).
|
220
|
+
class ReadTimeout < Timeout
|
221
|
+
def default_message
|
222
|
+
'Timed out reading data from server'
|
223
|
+
end
|
177
224
|
end
|
178
225
|
end
|
179
226
|
|
227
|
+
|
180
228
|
# The server broke the connection prior to the request completing. Usually
|
181
229
|
# this means it crashed, or sometimes that your network connection was
|
182
230
|
# severed before it could complete.
|
183
|
-
class ServerBrokeConnection < Exception
|
231
|
+
class ServerBrokeConnection < RestClient::Exception
|
184
232
|
def initialize(message = 'Server broke connection')
|
185
233
|
super nil, nil
|
186
234
|
self.message = message
|
187
235
|
end
|
188
236
|
end
|
189
237
|
|
190
|
-
class SSLCertificateNotVerified < Exception
|
191
|
-
def initialize(message)
|
238
|
+
class SSLCertificateNotVerified < RestClient::Exception
|
239
|
+
def initialize(message = 'SSL certificate not verified')
|
192
240
|
super nil, nil
|
193
241
|
self.message = message
|
194
242
|
end
|
195
243
|
end
|
196
244
|
end
|
197
|
-
|
198
|
-
class RestClient::Request
|
199
|
-
# backwards compatibility
|
200
|
-
Redirect = RestClient::Redirect
|
201
|
-
Unauthorized = RestClient::Unauthorized
|
202
|
-
RequestFailed = RestClient::RequestFailed
|
203
|
-
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module RestClient
|
2
|
+
|
3
|
+
# The ParamsArray class is used to represent an ordered list of [key, value]
|
4
|
+
# pairs. Use this when you need to include a key multiple times or want
|
5
|
+
# explicit control over parameter ordering.
|
6
|
+
#
|
7
|
+
# Most of the request payload & parameter functions normally accept a Hash of
|
8
|
+
# keys => values, which does not allow for duplicated keys.
|
9
|
+
#
|
10
|
+
# @see RestClient::Utils.encode_query_string
|
11
|
+
# @see RestClient::Utils.flatten_params
|
12
|
+
#
|
13
|
+
class ParamsArray
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
# @param array [Array<Array>] An array of parameter key,value pairs. These
|
17
|
+
# pairs may be 2 element arrays [key, value] or single element hashes
|
18
|
+
# {key => value}. They may also be single element arrays to represent a
|
19
|
+
# key with no value.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# >> ParamsArray.new([[:foo, 123], [:foo, 456], [:bar, 789]])
|
23
|
+
# This will be encoded as "foo=123&foo=456&bar=789"
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# >> ParamsArray.new({foo: 123, bar: 456})
|
27
|
+
# This is valid, but there's no reason not to just use the Hash directly
|
28
|
+
# instead of a ParamsArray.
|
29
|
+
#
|
30
|
+
#
|
31
|
+
def initialize(array)
|
32
|
+
@array = process_input(array)
|
33
|
+
end
|
34
|
+
|
35
|
+
def each(*args, &blk)
|
36
|
+
@array.each(*args, &blk)
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty?
|
40
|
+
@array.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def process_input(array)
|
46
|
+
array.map {|v| process_pair(v) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# A pair may be:
|
50
|
+
# - A single element hash, e.g. {foo: 'bar'}
|
51
|
+
# - A two element array, e.g. ['foo', 'bar']
|
52
|
+
# - A one element array, e.g. ['foo']
|
53
|
+
#
|
54
|
+
def process_pair(pair)
|
55
|
+
case pair
|
56
|
+
when Hash
|
57
|
+
if pair.length != 1
|
58
|
+
raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
|
59
|
+
end
|
60
|
+
pair.to_a.fetch(0)
|
61
|
+
when Array
|
62
|
+
if pair.length > 2
|
63
|
+
raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
|
64
|
+
end
|
65
|
+
[pair.fetch(0), pair[1]]
|
66
|
+
else
|
67
|
+
# recurse, converting any non-array to an array
|
68
|
+
process_pair(pair.to_a)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/restclient/payload.rb
CHANGED
@@ -1,13 +1,23 @@
|
|
1
1
|
require 'tempfile'
|
2
|
+
require 'securerandom'
|
2
3
|
require 'stringio'
|
3
|
-
|
4
|
+
|
5
|
+
begin
|
6
|
+
# Use mime/types/columnar if available, for reduced memory usage
|
7
|
+
require 'mime/types/columnar'
|
8
|
+
rescue LoadError
|
9
|
+
require 'mime/types'
|
10
|
+
end
|
4
11
|
|
5
12
|
module RestClient
|
6
13
|
module Payload
|
7
14
|
extend self
|
8
15
|
|
9
16
|
def generate(params)
|
10
|
-
if params.is_a?(
|
17
|
+
if params.is_a?(RestClient::Payload::Base)
|
18
|
+
# pass through Payload objects unchanged
|
19
|
+
params
|
20
|
+
elsif params.is_a?(String)
|
11
21
|
Base.new(params)
|
12
22
|
elsif params.is_a?(Hash)
|
13
23
|
if params.delete(:multipart) == true || has_file?(params)
|
@@ -15,6 +25,12 @@ module RestClient
|
|
15
25
|
else
|
16
26
|
UrlEncoded.new(params)
|
17
27
|
end
|
28
|
+
elsif params.is_a?(ParamsArray)
|
29
|
+
if _has_file?(params)
|
30
|
+
Multipart.new(params)
|
31
|
+
else
|
32
|
+
UrlEncoded.new(params)
|
33
|
+
end
|
18
34
|
elsif params.respond_to?(:read)
|
19
35
|
Streamed.new(params)
|
20
36
|
else
|
@@ -23,28 +39,20 @@ module RestClient
|
|
23
39
|
end
|
24
40
|
|
25
41
|
def has_file?(params)
|
26
|
-
params.
|
27
|
-
|
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
|
42
|
+
unless params.is_a?(Hash)
|
43
|
+
raise ArgumentError.new("Must pass Hash, not #{params.inspect}")
|
35
44
|
end
|
45
|
+
_has_file?(params)
|
36
46
|
end
|
37
47
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
v.respond_to?(:path) && v.respond_to?(:read)
|
47
|
-
end
|
48
|
+
def _has_file?(obj)
|
49
|
+
case obj
|
50
|
+
when Hash, ParamsArray
|
51
|
+
obj.any? {|_, v| _has_file?(v) }
|
52
|
+
when Array
|
53
|
+
obj.any? {|v| _has_file?(v) }
|
54
|
+
else
|
55
|
+
obj.respond_to?(:path) && obj.respond_to?(:read)
|
48
56
|
end
|
49
57
|
end
|
50
58
|
|
@@ -58,40 +66,13 @@ module RestClient
|
|
58
66
|
@stream.seek(0)
|
59
67
|
end
|
60
68
|
|
61
|
-
def read(
|
62
|
-
@stream.read(
|
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
|
69
|
+
def read(*args)
|
70
|
+
@stream.read(*args)
|
82
71
|
end
|
83
72
|
|
84
|
-
def
|
85
|
-
result =
|
86
|
-
|
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
|
73
|
+
def to_s
|
74
|
+
result = read
|
75
|
+
@stream.seek(0)
|
95
76
|
result
|
96
77
|
end
|
97
78
|
|
@@ -109,14 +90,20 @@ module RestClient
|
|
109
90
|
@stream.close unless @stream.closed?
|
110
91
|
end
|
111
92
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
93
|
+
def closed?
|
94
|
+
@stream.closed?
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s_inspect
|
98
|
+
to_s.inspect
|
116
99
|
end
|
117
100
|
|
118
101
|
def short_inspect
|
119
|
-
|
102
|
+
if size && size > 500
|
103
|
+
"#{size} byte(s) length"
|
104
|
+
else
|
105
|
+
to_s_inspect
|
106
|
+
end
|
120
107
|
end
|
121
108
|
|
122
109
|
end
|
@@ -134,42 +121,36 @@ module RestClient
|
|
134
121
|
end
|
135
122
|
end
|
136
123
|
|
124
|
+
# TODO (breaks compatibility): ought to use mime_for() to autodetect the
|
125
|
+
# Content-Type for stream objects that have a filename.
|
126
|
+
|
137
127
|
alias :length :size
|
138
128
|
end
|
139
129
|
|
140
130
|
class UrlEncoded < Base
|
141
131
|
def build_stream(params = nil)
|
142
|
-
@stream = StringIO.new(
|
143
|
-
"#{entry[0]}=#{handle_key(entry[1])}"
|
144
|
-
end.join("&"))
|
132
|
+
@stream = StringIO.new(Utils.encode_query_string(params))
|
145
133
|
@stream.seek(0)
|
146
134
|
end
|
147
135
|
|
148
|
-
# for UrlEncoded escape the keys
|
149
|
-
def handle_key key
|
150
|
-
Parser.escape(key.to_s, Escape)
|
151
|
-
end
|
152
|
-
|
153
136
|
def headers
|
154
137
|
super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
|
155
138
|
end
|
156
|
-
|
157
|
-
Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
158
|
-
Escape = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
159
139
|
end
|
160
140
|
|
161
141
|
class Multipart < Base
|
162
142
|
EOL = "\r\n"
|
163
143
|
|
164
144
|
def build_stream(params)
|
165
|
-
b =
|
145
|
+
b = '--' + boundary
|
166
146
|
|
167
|
-
@stream = Tempfile.new(
|
147
|
+
@stream = Tempfile.new('rest-client.multipart.')
|
168
148
|
@stream.binmode
|
169
149
|
@stream.write(b + EOL)
|
170
150
|
|
171
|
-
|
172
|
-
|
151
|
+
case params
|
152
|
+
when Hash, ParamsArray
|
153
|
+
x = Utils.flatten_params(params)
|
173
154
|
else
|
174
155
|
x = params
|
175
156
|
end
|
@@ -218,10 +199,25 @@ module RestClient
|
|
218
199
|
end
|
219
200
|
|
220
201
|
def boundary
|
221
|
-
@boundary
|
202
|
+
return @boundary if defined?(@boundary) && @boundary
|
203
|
+
|
204
|
+
# Use the same algorithm used by WebKit: generate 16 random
|
205
|
+
# alphanumeric characters, replacing `+` `/` with `A` `B` (included in
|
206
|
+
# the list twice) to round out the set of 64.
|
207
|
+
s = SecureRandom.base64(12)
|
208
|
+
s.tr!('+/', 'AB')
|
209
|
+
|
210
|
+
@boundary = '----RubyFormBoundary' + s
|
222
211
|
end
|
223
212
|
|
224
213
|
# for Multipart do not escape the keys
|
214
|
+
#
|
215
|
+
# Ostensibly multipart keys MAY be percent encoded per RFC 7578, but in
|
216
|
+
# practice no major browser that I'm aware of uses percent encoding.
|
217
|
+
#
|
218
|
+
# Further discussion of multipart encoding:
|
219
|
+
# https://github.com/rest-client/rest-client/pull/403#issuecomment-156976930
|
220
|
+
#
|
225
221
|
def handle_key key
|
226
222
|
key
|
227
223
|
end
|
data/lib/restclient/platform.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
module RestClient
|
2
4
|
module Platform
|
3
5
|
# Return true if we are running on a darwin-based Ruby platform. This will
|
@@ -26,5 +28,22 @@ module RestClient
|
|
26
28
|
# defined on mri >= 1.9
|
27
29
|
RUBY_ENGINE == 'jruby'
|
28
30
|
end
|
31
|
+
|
32
|
+
def self.architecture
|
33
|
+
"#{RbConfig::CONFIG['host_os']} #{RbConfig::CONFIG['host_cpu']}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.ruby_agent_version
|
37
|
+
case RUBY_ENGINE
|
38
|
+
when 'jruby'
|
39
|
+
"jruby/#{JRUBY_VERSION} (#{RUBY_VERSION}p#{RUBY_PATCHLEVEL})"
|
40
|
+
else
|
41
|
+
"#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.default_user_agent
|
46
|
+
"rest-client/#{VERSION} (#{architecture}) #{ruby_agent_version}"
|
47
|
+
end
|
29
48
|
end
|
30
49
|
end
|
@@ -13,22 +13,36 @@ module RestClient
|
|
13
13
|
|
14
14
|
include AbstractResponse
|
15
15
|
|
16
|
-
attr_reader :file, :request
|
16
|
+
attr_reader :file, :request, :start_time, :end_time
|
17
17
|
|
18
|
-
def
|
19
|
-
@
|
20
|
-
|
18
|
+
def inspect
|
19
|
+
"<RestClient::RawResponse @code=#{code.inspect}, @file=#{file.inspect}, @request=#{request.inspect}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Tempfile] tempfile The temporary file containing the body
|
23
|
+
# @param [Net::HTTPResponse] net_http_res
|
24
|
+
# @param [RestClient::Request] request
|
25
|
+
# @param [Time] start_time
|
26
|
+
def initialize(tempfile, net_http_res, request, start_time=nil)
|
21
27
|
@file = tempfile
|
22
|
-
|
28
|
+
|
29
|
+
# reopen the tempfile so we can read it
|
30
|
+
@file.open
|
31
|
+
|
32
|
+
response_set_vars(net_http_res, request, start_time)
|
23
33
|
end
|
24
34
|
|
25
35
|
def to_s
|
26
|
-
|
36
|
+
body
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
@file.rewind
|
27
41
|
@file.read
|
28
42
|
end
|
29
43
|
|
30
44
|
def size
|
31
|
-
|
45
|
+
file.size
|
32
46
|
end
|
33
47
|
|
34
48
|
end
|