rest-client 1.6.7 → 1.8.0
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -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 +63 -24
- data/Rakefile +85 -35
- data/bin/restclient +9 -8
- data/history.md +63 -1
- data/lib/restclient/abstract_response.rb +44 -15
- data/lib/restclient/exceptions.rb +20 -10
- data/lib/restclient/payload.rb +21 -18
- data/lib/restclient/platform.rb +30 -0
- data/lib/restclient/raw_response.rb +3 -2
- data/lib/restclient/request.rb +368 -63
- data/lib/restclient/resource.rb +3 -4
- data/lib/restclient/response.rb +2 -5
- data/lib/restclient/version.rb +7 -0
- data/lib/restclient/windows/root_certs.rb +105 -0
- data/lib/restclient/windows.rb +8 -0
- data/lib/restclient.rb +6 -15
- 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_spec.rb → integration/integration_spec.rb} +10 -13
- data/spec/integration/request_spec.rb +86 -7
- data/spec/spec_helper.rb +2 -0
- data/spec/{abstract_response_spec.rb → unit/abstract_response_spec.rb} +18 -15
- data/spec/{exceptions_spec.rb → unit/exceptions_spec.rb} +17 -20
- data/spec/unit/master_shake.jpg +0 -0
- data/spec/{payload_spec.rb → unit/payload_spec.rb} +42 -31
- data/spec/unit/raw_response_spec.rb +18 -0
- data/spec/{request2_spec.rb → unit/request2_spec.rb} +6 -14
- data/spec/unit/request_spec.rb +917 -0
- data/spec/{resource_spec.rb → unit/resource_spec.rb} +27 -31
- data/spec/{response_spec.rb → unit/response_spec.rb} +63 -57
- data/spec/{restclient_spec.rb → unit/restclient_spec.rb} +8 -2
- data/spec/unit/windows/root_certs_spec.rb +22 -0
- metadata +210 -112
- data/VERSION +0 -1
- data/lib/restclient/net_http_ext.rb +0 -55
- data/spec/base.rb +0 -16
- data/spec/integration/certs/equifax.crt +0 -19
- data/spec/master_shake.jpg +0 -0
- data/spec/raw_response_spec.rb +0 -17
- data/spec/request_spec.rb +0 -529
data/lib/restclient/request.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'tempfile'
|
2
2
|
require 'mime/types'
|
3
3
|
require 'cgi'
|
4
|
+
require 'netrc'
|
5
|
+
require 'set'
|
4
6
|
|
5
7
|
module RestClient
|
6
8
|
# This class is used internally by RestClient to send the request, but you can also
|
@@ -19,20 +21,87 @@ module RestClient
|
|
19
21
|
# * :block_response call the provided block with the HTTPResponse as parameter
|
20
22
|
# * :raw_response return a low-level RawResponse instead of a Response
|
21
23
|
# * :max_redirects maximum number of redirections (default to 10)
|
22
|
-
# * :verify_ssl enable ssl verification, possible values are constants from
|
23
|
-
#
|
24
|
-
# * :
|
24
|
+
# * :verify_ssl enable ssl verification, possible values are constants from
|
25
|
+
# OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER
|
26
|
+
# * :timeout and :open_timeout are how long to wait for a response and to
|
27
|
+
# open a connection, in seconds. Pass nil to disable the timeout.
|
28
|
+
# * :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path,
|
29
|
+
# :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings
|
30
|
+
# * :ssl_version specifies the SSL version for the underlying Net::HTTP connection
|
31
|
+
# * :ssl_ciphers sets SSL ciphers for the connection. See
|
32
|
+
# OpenSSL::SSL::SSLContext#ciphers=
|
25
33
|
class Request
|
26
34
|
|
27
35
|
attr_reader :method, :url, :headers, :cookies,
|
28
36
|
:payload, :user, :password, :timeout, :max_redirects,
|
29
|
-
:open_timeout, :raw_response, :
|
30
|
-
:
|
37
|
+
:open_timeout, :raw_response, :processed_headers, :args,
|
38
|
+
:ssl_opts
|
31
39
|
|
32
40
|
def self.execute(args, & block)
|
33
41
|
new(args).execute(& block)
|
34
42
|
end
|
35
43
|
|
44
|
+
# This is similar to the list now in ruby core, but adds HIGH and RC4-MD5
|
45
|
+
# for better compatibility (similar to Firefox) and moves AES-GCM cipher
|
46
|
+
# suites above DHE/ECDHE CBC suites (similar to Chromium).
|
47
|
+
# https://github.com/ruby/ruby/commit/699b209cf8cf11809620e12985ad33ae33b119ee
|
48
|
+
#
|
49
|
+
# This list will be used by default if the Ruby global OpenSSL default
|
50
|
+
# ciphers appear to be a weak list.
|
51
|
+
DefaultCiphers = %w{
|
52
|
+
!aNULL
|
53
|
+
!eNULL
|
54
|
+
!EXPORT
|
55
|
+
!SSLV2
|
56
|
+
!LOW
|
57
|
+
|
58
|
+
ECDHE-ECDSA-AES128-GCM-SHA256
|
59
|
+
ECDHE-RSA-AES128-GCM-SHA256
|
60
|
+
ECDHE-ECDSA-AES256-GCM-SHA384
|
61
|
+
ECDHE-RSA-AES256-GCM-SHA384
|
62
|
+
DHE-RSA-AES128-GCM-SHA256
|
63
|
+
DHE-DSS-AES128-GCM-SHA256
|
64
|
+
DHE-RSA-AES256-GCM-SHA384
|
65
|
+
DHE-DSS-AES256-GCM-SHA384
|
66
|
+
AES128-GCM-SHA256
|
67
|
+
AES256-GCM-SHA384
|
68
|
+
ECDHE-ECDSA-AES128-SHA256
|
69
|
+
ECDHE-RSA-AES128-SHA256
|
70
|
+
ECDHE-ECDSA-AES128-SHA
|
71
|
+
ECDHE-RSA-AES128-SHA
|
72
|
+
ECDHE-ECDSA-AES256-SHA384
|
73
|
+
ECDHE-RSA-AES256-SHA384
|
74
|
+
ECDHE-ECDSA-AES256-SHA
|
75
|
+
ECDHE-RSA-AES256-SHA
|
76
|
+
DHE-RSA-AES128-SHA256
|
77
|
+
DHE-RSA-AES256-SHA256
|
78
|
+
DHE-RSA-AES128-SHA
|
79
|
+
DHE-RSA-AES256-SHA
|
80
|
+
DHE-DSS-AES128-SHA256
|
81
|
+
DHE-DSS-AES256-SHA256
|
82
|
+
DHE-DSS-AES128-SHA
|
83
|
+
DHE-DSS-AES256-SHA
|
84
|
+
AES128-SHA256
|
85
|
+
AES256-SHA256
|
86
|
+
AES128-SHA
|
87
|
+
AES256-SHA
|
88
|
+
ECDHE-ECDSA-RC4-SHA
|
89
|
+
ECDHE-RSA-RC4-SHA
|
90
|
+
RC4-SHA
|
91
|
+
|
92
|
+
HIGH
|
93
|
+
+RC4
|
94
|
+
RC4-MD5
|
95
|
+
}.join(":")
|
96
|
+
|
97
|
+
# A set of weak default ciphers that we will override by default.
|
98
|
+
WeakDefaultCiphers = Set.new([
|
99
|
+
"ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW",
|
100
|
+
])
|
101
|
+
|
102
|
+
SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store
|
103
|
+
version ciphers verify_callback verify_callback_warnings}
|
104
|
+
|
36
105
|
def initialize args
|
37
106
|
@method = args[:method] or raise ArgumentError, "must pass :method"
|
38
107
|
@headers = args[:headers] || {}
|
@@ -45,14 +114,57 @@ module RestClient
|
|
45
114
|
@payload = Payload.generate(args[:payload])
|
46
115
|
@user = args[:user]
|
47
116
|
@password = args[:password]
|
48
|
-
|
49
|
-
|
117
|
+
if args.include?(:timeout)
|
118
|
+
@timeout = args[:timeout]
|
119
|
+
end
|
120
|
+
if args.include?(:open_timeout)
|
121
|
+
@open_timeout = args[:open_timeout]
|
122
|
+
end
|
50
123
|
@block_response = args[:block_response]
|
51
124
|
@raw_response = args[:raw_response] || false
|
52
|
-
|
53
|
-
@
|
54
|
-
|
55
|
-
|
125
|
+
|
126
|
+
@ssl_opts = {}
|
127
|
+
|
128
|
+
if args.include?(:verify_ssl)
|
129
|
+
v_ssl = args.fetch(:verify_ssl)
|
130
|
+
if v_ssl
|
131
|
+
if v_ssl == true
|
132
|
+
# interpret :verify_ssl => true as VERIFY_PEER
|
133
|
+
@ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
|
134
|
+
else
|
135
|
+
# otherwise pass through any truthy values
|
136
|
+
@ssl_opts[:verify_ssl] = v_ssl
|
137
|
+
end
|
138
|
+
else
|
139
|
+
# interpret all falsy :verify_ssl values as VERIFY_NONE
|
140
|
+
@ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
|
141
|
+
end
|
142
|
+
else
|
143
|
+
# if :verify_ssl was not passed, default to VERIFY_PEER
|
144
|
+
@ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
|
145
|
+
end
|
146
|
+
|
147
|
+
SSLOptionList.each do |key|
|
148
|
+
source_key = ('ssl_' + key).to_sym
|
149
|
+
if args.has_key?(source_key)
|
150
|
+
@ssl_opts[key.to_sym] = args.fetch(source_key)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# If there's no CA file, CA path, or cert store provided, use default
|
155
|
+
if !ssl_ca_file && !ssl_ca_path && !@ssl_opts.include?(:cert_store)
|
156
|
+
@ssl_opts[:cert_store] = self.class.default_ssl_cert_store
|
157
|
+
end
|
158
|
+
|
159
|
+
unless @ssl_opts.include?(:ciphers)
|
160
|
+
# If we're on a Ruby version that has insecure default ciphers,
|
161
|
+
# override it with our default list.
|
162
|
+
if WeakDefaultCiphers.include?(
|
163
|
+
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.fetch(:ciphers))
|
164
|
+
@ssl_opts[:ciphers] = DefaultCiphers
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
56
168
|
@tf = nil # If you are a raw request, this is your tempfile
|
57
169
|
@max_redirects = args[:max_redirects] || 10
|
58
170
|
@processed_headers = make_headers headers
|
@@ -66,6 +178,16 @@ module RestClient
|
|
66
178
|
payload.close if payload
|
67
179
|
end
|
68
180
|
|
181
|
+
# SSL-related options
|
182
|
+
def verify_ssl
|
183
|
+
@ssl_opts.fetch(:verify_ssl)
|
184
|
+
end
|
185
|
+
SSLOptionList.each do |key|
|
186
|
+
define_method('ssl_' + key) do
|
187
|
+
@ssl_opts[key.to_sym]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
69
191
|
# Extract the query parameters and append them to the url
|
70
192
|
def process_url_params url, headers
|
71
193
|
url_params = {}
|
@@ -87,13 +209,46 @@ module RestClient
|
|
87
209
|
|
88
210
|
def make_headers user_headers
|
89
211
|
unless @cookies.empty?
|
90
|
-
|
212
|
+
|
213
|
+
# Validate that the cookie names and values look sane. If you really
|
214
|
+
# want to pass scary characters, just set the Cookie header directly.
|
215
|
+
# RFC6265 is actually much more restrictive than we are.
|
216
|
+
@cookies.each do |key, val|
|
217
|
+
unless valid_cookie_key?(key)
|
218
|
+
raise ArgumentError.new("Invalid cookie name: #{key.inspect}")
|
219
|
+
end
|
220
|
+
unless valid_cookie_value?(val)
|
221
|
+
raise ArgumentError.new("Invalid cookie value: #{val.inspect}")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
user_headers[:cookie] = @cookies.map { |key, val| "#{key}=#{val}" }.sort.join('; ')
|
91
226
|
end
|
92
227
|
headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
|
93
228
|
headers.merge!(@payload.headers) if @payload
|
94
229
|
headers
|
95
230
|
end
|
96
231
|
|
232
|
+
# Do some sanity checks on cookie keys.
|
233
|
+
#
|
234
|
+
# Properly it should be a valid TOKEN per RFC 2616, but lots of servers are
|
235
|
+
# more liberal.
|
236
|
+
#
|
237
|
+
# Disallow the empty string as well as keys containing control characters,
|
238
|
+
# equals sign, semicolon, comma, or space.
|
239
|
+
#
|
240
|
+
def valid_cookie_key?(string)
|
241
|
+
return false if string.empty?
|
242
|
+
|
243
|
+
! Regexp.new('[\x0-\x1f\x7f=;, ]').match(string)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Validate cookie values. Rather than following RFC 6265, allow anything
|
247
|
+
# but control characters, comma, and semicolon.
|
248
|
+
def valid_cookie_value?(value)
|
249
|
+
! Regexp.new('[\x0-\x1f\x7f,;]').match(value)
|
250
|
+
end
|
251
|
+
|
97
252
|
def net_http_class
|
98
253
|
if RestClient.proxy
|
99
254
|
proxy_uri = URI.parse(RestClient.proxy)
|
@@ -107,6 +262,15 @@ module RestClient
|
|
107
262
|
Net::HTTP.const_get(method.to_s.capitalize)
|
108
263
|
end
|
109
264
|
|
265
|
+
def net_http_do_request(http, req, body=nil, &block)
|
266
|
+
if body != nil && body.respond_to?(:read)
|
267
|
+
req.body_stream = body
|
268
|
+
return http.request(req, nil, &block)
|
269
|
+
else
|
270
|
+
return http.request(req, body, &block)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
110
274
|
def parse_url(url)
|
111
275
|
url = "http://#{url}" unless url.match(/^http/)
|
112
276
|
URI.parse(url)
|
@@ -116,6 +280,9 @@ module RestClient
|
|
116
280
|
uri = parse_url(url)
|
117
281
|
@user = CGI.unescape(uri.user) if uri.user
|
118
282
|
@password = CGI.unescape(uri.password) if uri.password
|
283
|
+
if !@user && !@password
|
284
|
+
@user, @password = Netrc.read[uri.host]
|
285
|
+
end
|
119
286
|
uri
|
120
287
|
end
|
121
288
|
|
@@ -129,39 +296,112 @@ module RestClient
|
|
129
296
|
if p[k].is_a? Hash
|
130
297
|
process_payload(p[k], key)
|
131
298
|
else
|
132
|
-
value =
|
299
|
+
value = parser.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
133
300
|
"#{key}=#{value}"
|
134
301
|
end
|
135
302
|
end.join("&")
|
136
303
|
end
|
137
304
|
end
|
138
305
|
|
306
|
+
# Return a certificate store that can be used to validate certificates with
|
307
|
+
# the system certificate authorities. This will probably not do anything on
|
308
|
+
# OS X, which monkey patches OpenSSL in terrible ways to insert its own
|
309
|
+
# validation. On most *nix platforms, this will add the system certifcates
|
310
|
+
# using OpenSSL::X509::Store#set_default_paths. On Windows, this will use
|
311
|
+
# RestClient::Windows::RootCerts to look up the CAs trusted by the system.
|
312
|
+
#
|
313
|
+
# @return [OpenSSL::X509::Store]
|
314
|
+
#
|
315
|
+
def self.default_ssl_cert_store
|
316
|
+
cert_store = OpenSSL::X509::Store.new
|
317
|
+
cert_store.set_default_paths
|
318
|
+
|
319
|
+
# set_default_paths() doesn't do anything on Windows, so look up
|
320
|
+
# certificates using the win32 API.
|
321
|
+
if RestClient::Platform.windows?
|
322
|
+
RestClient::Windows::RootCerts.instance.to_a.uniq.each do |cert|
|
323
|
+
begin
|
324
|
+
cert_store.add_cert(cert)
|
325
|
+
rescue OpenSSL::X509::StoreError => err
|
326
|
+
# ignore duplicate certs
|
327
|
+
raise unless err.message == 'cert already in hash table'
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
cert_store
|
333
|
+
end
|
334
|
+
|
335
|
+
def print_verify_callback_warnings
|
336
|
+
warned = false
|
337
|
+
if RestClient::Platform.mac_mri?
|
338
|
+
warn('warning: ssl_verify_callback return code is ignored on OS X')
|
339
|
+
warned = true
|
340
|
+
end
|
341
|
+
if RestClient::Platform.jruby?
|
342
|
+
warn('warning: SSL verify_callback may not work correctly in jruby')
|
343
|
+
warn('see https://github.com/jruby/jruby/issues/597')
|
344
|
+
warned = true
|
345
|
+
end
|
346
|
+
warned
|
347
|
+
end
|
348
|
+
|
139
349
|
def transmit uri, req, payload, & block
|
140
350
|
setup_credentials req
|
141
351
|
|
142
352
|
net = net_http_class.new(uri.host, uri.port)
|
143
353
|
net.use_ssl = uri.is_a?(URI::HTTPS)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
354
|
+
net.ssl_version = ssl_version if ssl_version
|
355
|
+
net.ciphers = ssl_ciphers if ssl_ciphers
|
356
|
+
|
357
|
+
net.verify_mode = verify_ssl
|
358
|
+
|
359
|
+
net.cert = ssl_client_cert if ssl_client_cert
|
360
|
+
net.key = ssl_client_key if ssl_client_key
|
361
|
+
net.ca_file = ssl_ca_file if ssl_ca_file
|
362
|
+
net.ca_path = ssl_ca_path if ssl_ca_path
|
363
|
+
net.cert_store = ssl_cert_store if ssl_cert_store
|
364
|
+
|
365
|
+
# We no longer rely on net.verify_callback for the main SSL verification
|
366
|
+
# because it's not well supported on all platforms (see comments below).
|
367
|
+
# But do allow users to set one if they want.
|
368
|
+
if ssl_verify_callback
|
369
|
+
net.verify_callback = ssl_verify_callback
|
370
|
+
|
371
|
+
# Hilariously, jruby only calls the callback when cert_store is set to
|
372
|
+
# something, so make sure to set one.
|
373
|
+
# https://github.com/jruby/jruby/issues/597
|
374
|
+
if RestClient::Platform.jruby?
|
375
|
+
net.cert_store ||= OpenSSL::X509::Store.new
|
376
|
+
end
|
377
|
+
|
378
|
+
if ssl_verify_callback_warnings != false
|
379
|
+
if print_verify_callback_warnings
|
380
|
+
warn('pass :ssl_verify_callback_warnings => false to silence this')
|
152
381
|
end
|
153
|
-
true
|
154
382
|
end
|
155
383
|
end
|
156
|
-
net.cert = @ssl_client_cert if @ssl_client_cert
|
157
|
-
net.key = @ssl_client_key if @ssl_client_key
|
158
|
-
net.ca_file = @ssl_ca_file if @ssl_ca_file
|
159
|
-
net.read_timeout = @timeout if @timeout
|
160
|
-
net.open_timeout = @open_timeout if @open_timeout
|
161
384
|
|
162
|
-
|
163
|
-
|
164
|
-
|
385
|
+
if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
|
386
|
+
warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE')
|
387
|
+
warn('This dangerous monkey patch leaves you open to MITM attacks!')
|
388
|
+
warn('Try passing :verify_ssl => false instead.')
|
389
|
+
end
|
390
|
+
|
391
|
+
if defined? @timeout
|
392
|
+
if @timeout == -1
|
393
|
+
warn 'To disable read timeouts, please set timeout to nil instead of -1'
|
394
|
+
@timeout = nil
|
395
|
+
end
|
396
|
+
net.read_timeout = @timeout
|
397
|
+
end
|
398
|
+
if defined? @open_timeout
|
399
|
+
if @open_timeout == -1
|
400
|
+
warn 'To disable open timeouts, please set open_timeout to nil instead of -1'
|
401
|
+
@open_timeout = nil
|
402
|
+
end
|
403
|
+
net.open_timeout = @open_timeout
|
404
|
+
end
|
165
405
|
|
166
406
|
RestClient.before_execution_procs.each do |before_proc|
|
167
407
|
before_proc.call(req, args)
|
@@ -169,19 +409,43 @@ module RestClient
|
|
169
409
|
|
170
410
|
log_request
|
171
411
|
|
412
|
+
|
172
413
|
net.start do |http|
|
173
414
|
if @block_response
|
174
|
-
http
|
415
|
+
net_http_do_request(http, req, payload ? payload.to_s : nil,
|
416
|
+
&@block_response)
|
175
417
|
else
|
176
|
-
res = http
|
418
|
+
res = net_http_do_request(http, req, payload ? payload.to_s : nil) \
|
419
|
+
{ |http_response| fetch_body(http_response) }
|
177
420
|
log_response res
|
178
421
|
process_result res, & block
|
179
422
|
end
|
180
423
|
end
|
181
424
|
rescue EOFError
|
182
425
|
raise RestClient::ServerBrokeConnection
|
183
|
-
rescue Timeout::Error
|
426
|
+
rescue Timeout::Error, Errno::ETIMEDOUT
|
184
427
|
raise RestClient::RequestTimeout
|
428
|
+
|
429
|
+
rescue OpenSSL::SSL::SSLError => error
|
430
|
+
# TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
|
431
|
+
# pass through OpenSSL::SSL::SSLError directly.
|
432
|
+
#
|
433
|
+
# Exceptions in verify_callback are ignored [1], and jruby doesn't support
|
434
|
+
# it at all [2]. RestClient has to catch OpenSSL::SSL::SSLError and either
|
435
|
+
# re-throw it as is, or throw SSLCertificateNotVerified based on the
|
436
|
+
# contents of the message field of the original exception.
|
437
|
+
#
|
438
|
+
# The client has to handle OpenSSL::SSL::SSLError exceptions anyway, so
|
439
|
+
# we shouldn't make them handle both OpenSSL and RestClient exceptions.
|
440
|
+
#
|
441
|
+
# [1] https://github.com/ruby/ruby/blob/89e70fe8e7/ext/openssl/ossl.c#L238
|
442
|
+
# [2] https://github.com/jruby/jruby/issues/597
|
443
|
+
|
444
|
+
if error.message.include?("certificate verify failed")
|
445
|
+
raise SSLCertificateNotVerified.new(error.message)
|
446
|
+
else
|
447
|
+
raise error
|
448
|
+
end
|
185
449
|
end
|
186
450
|
|
187
451
|
def setup_credentials(req)
|
@@ -194,17 +458,18 @@ module RestClient
|
|
194
458
|
# Stolen from http://www.ruby-forum.com/topic/166423
|
195
459
|
# Kudos to _why!
|
196
460
|
@tf = Tempfile.new("rest-client")
|
461
|
+
@tf.binmode
|
197
462
|
size, total = 0, http_response.header['Content-Length'].to_i
|
198
463
|
http_response.read_body do |chunk|
|
199
464
|
@tf.write chunk
|
200
465
|
size += chunk.size
|
201
466
|
if RestClient.log
|
202
467
|
if size == 0
|
203
|
-
RestClient.log << "
|
468
|
+
RestClient.log << "%s %s done (0 length file)\n" % [@method, @url]
|
204
469
|
elsif total == 0
|
205
|
-
RestClient.log << "
|
470
|
+
RestClient.log << "%s %s (zero content length)\n" % [@method, @url]
|
206
471
|
else
|
207
|
-
RestClient.log << "
|
472
|
+
RestClient.log << "%s %s %d%% done (%d of %d)\n" % [@method, @url, (size * 100) / total, size, total]
|
208
473
|
end
|
209
474
|
end
|
210
475
|
end
|
@@ -219,9 +484,9 @@ module RestClient
|
|
219
484
|
def process_result res, & block
|
220
485
|
if @raw_response
|
221
486
|
# We don't decode raw requests
|
222
|
-
response = RawResponse.new(@tf, res, args)
|
487
|
+
response = RawResponse.new(@tf, res, args, self)
|
223
488
|
else
|
224
|
-
response = Response.create(Request.decode(res['content-encoding'], res.body), res, args)
|
489
|
+
response = Response.create(Request.decode(res['content-encoding'], res.body), res, args, self)
|
225
490
|
end
|
226
491
|
|
227
492
|
if block_given?
|
@@ -251,20 +516,36 @@ module RestClient
|
|
251
516
|
end
|
252
517
|
|
253
518
|
def log_request
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
519
|
+
return unless RestClient.log
|
520
|
+
|
521
|
+
out = []
|
522
|
+
sanitized_url = begin
|
523
|
+
uri = URI.parse(url)
|
524
|
+
uri.password = "REDACTED" if uri.password
|
525
|
+
uri.to_s
|
526
|
+
rescue URI::InvalidURIError
|
527
|
+
# An attacker may be able to manipulate the URL to be
|
528
|
+
# invalid, which could force discloure of a password if
|
529
|
+
# we show any of the un-parsed URL here.
|
530
|
+
"[invalid uri]"
|
260
531
|
end
|
532
|
+
|
533
|
+
out << "RestClient.#{method} #{sanitized_url.inspect}"
|
534
|
+
out << payload.short_inspect if payload
|
535
|
+
out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
|
536
|
+
RestClient.log << out.join(', ') + "\n"
|
261
537
|
end
|
262
538
|
|
263
539
|
def log_response res
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
540
|
+
return unless RestClient.log
|
541
|
+
|
542
|
+
size = if @raw_response
|
543
|
+
File.size(@tf.path)
|
544
|
+
else
|
545
|
+
res.body.nil? ? 0 : res.body.size
|
546
|
+
end
|
547
|
+
|
548
|
+
RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
268
549
|
end
|
269
550
|
|
270
551
|
# Return a hash of headers whose keys are capitalized strings
|
@@ -274,8 +555,7 @@ module RestClient
|
|
274
555
|
key = key.to_s.split(/_/).map { |w| w.capitalize }.join('-')
|
275
556
|
end
|
276
557
|
if 'CONTENT-TYPE' == key.upcase
|
277
|
-
|
278
|
-
result[key] = MIME::Types.type_for_extension target_value
|
558
|
+
result[key] = maybe_convert_extension(value.to_s)
|
279
559
|
elsif 'ACCEPT' == key.upcase
|
280
560
|
# Accept can be composed of several comma-separated values
|
281
561
|
if value.is_a? Array
|
@@ -283,7 +563,9 @@ module RestClient
|
|
283
563
|
else
|
284
564
|
target_values = value.to_s.split ','
|
285
565
|
end
|
286
|
-
result[key] = target_values.map { |ext|
|
566
|
+
result[key] = target_values.map { |ext|
|
567
|
+
maybe_convert_extension(ext.to_s.strip)
|
568
|
+
}.join(', ')
|
287
569
|
else
|
288
570
|
result[key] = value.to_s
|
289
571
|
end
|
@@ -295,21 +577,44 @@ module RestClient
|
|
295
577
|
{:accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate'}
|
296
578
|
end
|
297
579
|
|
298
|
-
|
299
|
-
end
|
300
|
-
|
301
|
-
module MIME
|
302
|
-
class Types
|
580
|
+
private
|
303
581
|
|
304
|
-
|
305
|
-
|
306
|
-
candidates = @extension_index[ext]
|
307
|
-
candidates.empty? ? ext : candidates[0].content_type
|
582
|
+
def parser
|
583
|
+
URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
308
584
|
end
|
309
585
|
|
310
|
-
|
311
|
-
|
312
|
-
|
586
|
+
# Given a MIME type or file extension, return either a MIME type or, if
|
587
|
+
# none is found, the input unchanged.
|
588
|
+
#
|
589
|
+
# >> maybe_convert_extension('json')
|
590
|
+
# => 'application/json'
|
591
|
+
#
|
592
|
+
# >> maybe_convert_extension('unknown')
|
593
|
+
# => 'unknown'
|
594
|
+
#
|
595
|
+
# >> maybe_convert_extension('application/xml')
|
596
|
+
# => 'application/xml'
|
597
|
+
#
|
598
|
+
# @param ext [String]
|
599
|
+
#
|
600
|
+
# @return [String]
|
601
|
+
#
|
602
|
+
def maybe_convert_extension(ext)
|
603
|
+
unless ext =~ /\A[a-zA-Z0-9_@-]+\z/
|
604
|
+
# Don't look up strings unless they look like they could be a file
|
605
|
+
# extension known to mime-types.
|
606
|
+
#
|
607
|
+
# There currently isn't any API public way to look up extensions
|
608
|
+
# directly out of MIME::Types, but the type_for() method only strips
|
609
|
+
# off after a period anyway.
|
610
|
+
return ext
|
611
|
+
end
|
612
|
+
|
613
|
+
types = MIME::Types.type_for(ext)
|
614
|
+
if types.empty?
|
615
|
+
ext
|
616
|
+
else
|
617
|
+
types.first.content_type
|
313
618
|
end
|
314
619
|
end
|
315
620
|
end
|
data/lib/restclient/resource.rb
CHANGED
@@ -149,10 +149,9 @@ module RestClient
|
|
149
149
|
#
|
150
150
|
def [](suburl, &new_block)
|
151
151
|
case
|
152
|
-
|
153
|
-
|
154
|
-
else
|
155
|
-
self.class.new(concat_urls(url, suburl), options)
|
152
|
+
when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
|
153
|
+
when block then self.class.new(concat_urls(url, suburl), options, &block)
|
154
|
+
else self.class.new(concat_urls(url, suburl), options)
|
156
155
|
end
|
157
156
|
end
|
158
157
|
|
data/lib/restclient/response.rb
CHANGED
@@ -6,17 +6,14 @@ module RestClient
|
|
6
6
|
|
7
7
|
include AbstractResponse
|
8
8
|
|
9
|
-
attr_accessor :args, :body, :net_http_res
|
10
|
-
|
11
9
|
def body
|
12
10
|
self
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
13
|
+
def self.create body, net_http_res, args, request
|
16
14
|
result = body || ''
|
17
15
|
result.extend Response
|
18
|
-
result.net_http_res
|
19
|
-
result.args = args
|
16
|
+
result.response_set_vars(net_http_res, args, request)
|
20
17
|
result
|
21
18
|
end
|
22
19
|
|