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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +14 -0
  5. data/AUTHORS +81 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.rdoc +63 -24
  9. data/Rakefile +85 -35
  10. data/bin/restclient +9 -8
  11. data/history.md +63 -1
  12. data/lib/restclient/abstract_response.rb +44 -15
  13. data/lib/restclient/exceptions.rb +20 -10
  14. data/lib/restclient/payload.rb +21 -18
  15. data/lib/restclient/platform.rb +30 -0
  16. data/lib/restclient/raw_response.rb +3 -2
  17. data/lib/restclient/request.rb +368 -63
  18. data/lib/restclient/resource.rb +3 -4
  19. data/lib/restclient/response.rb +2 -5
  20. data/lib/restclient/version.rb +7 -0
  21. data/lib/restclient/windows/root_certs.rb +105 -0
  22. data/lib/restclient/windows.rb +8 -0
  23. data/lib/restclient.rb +6 -15
  24. data/rest-client.gemspec +30 -0
  25. data/rest-client.windows.gemspec +19 -0
  26. data/spec/integration/capath_digicert/244b5494.0 +19 -0
  27. data/spec/integration/capath_digicert/81b9768f.0 +19 -0
  28. data/spec/integration/capath_digicert/README +8 -0
  29. data/spec/integration/capath_digicert/digicert.crt +19 -0
  30. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  31. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  32. data/spec/integration/capath_verisign/README +8 -0
  33. data/spec/integration/capath_verisign/verisign.crt +14 -0
  34. data/spec/integration/certs/digicert.crt +19 -0
  35. data/spec/{integration_spec.rb → integration/integration_spec.rb} +10 -13
  36. data/spec/integration/request_spec.rb +86 -7
  37. data/spec/spec_helper.rb +2 -0
  38. data/spec/{abstract_response_spec.rb → unit/abstract_response_spec.rb} +18 -15
  39. data/spec/{exceptions_spec.rb → unit/exceptions_spec.rb} +17 -20
  40. data/spec/unit/master_shake.jpg +0 -0
  41. data/spec/{payload_spec.rb → unit/payload_spec.rb} +42 -31
  42. data/spec/unit/raw_response_spec.rb +18 -0
  43. data/spec/{request2_spec.rb → unit/request2_spec.rb} +6 -14
  44. data/spec/unit/request_spec.rb +917 -0
  45. data/spec/{resource_spec.rb → unit/resource_spec.rb} +27 -31
  46. data/spec/{response_spec.rb → unit/response_spec.rb} +63 -57
  47. data/spec/{restclient_spec.rb → unit/restclient_spec.rb} +8 -2
  48. data/spec/unit/windows/root_certs_spec.rb +22 -0
  49. metadata +210 -112
  50. data/VERSION +0 -1
  51. data/lib/restclient/net_http_ext.rb +0 -55
  52. data/spec/base.rb +0 -16
  53. data/spec/integration/certs/equifax.crt +0 -19
  54. data/spec/master_shake.jpg +0 -0
  55. data/spec/raw_response_spec.rb +0 -17
  56. data/spec/request_spec.rb +0 -529
@@ -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 OpenSSL::SSL
23
- # * :timeout and :open_timeout passing in -1 will disable the timeout by setting the corresponding net timeout values to nil
24
- # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file
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, :verify_ssl, :ssl_client_cert,
30
- :ssl_client_key, :ssl_ca_file, :processed_headers, :args
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
- @timeout = args[:timeout]
49
- @open_timeout = args[:open_timeout]
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
- @verify_ssl = args[:verify_ssl] || false
53
- @ssl_client_cert = args[:ssl_client_cert] || nil
54
- @ssl_client_key = args[:ssl_client_key] || nil
55
- @ssl_ca_file = args[:ssl_ca_file] || nil
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
- user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{CGI::unescape(val)}" }.sort.join('; ')
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 = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
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
- if (@verify_ssl == false) || (@verify_ssl == OpenSSL::SSL::VERIFY_NONE)
145
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE
146
- elsif @verify_ssl.is_a? Integer
147
- net.verify_mode = @verify_ssl
148
- net.verify_callback = lambda do |preverify_ok, ssl_context|
149
- if (!preverify_ok) || ssl_context.error != 0
150
- err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
151
- raise SSLCertificateNotVerified.new(err_msg)
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
- # disable the timeout if the timeout value is -1
163
- net.read_timeout = nil if @timeout == -1
164
- net.out_timeout = nil if @open_timeout == -1
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.request(req, payload ? payload.to_s : nil, & @block_response)
415
+ net_http_do_request(http, req, payload ? payload.to_s : nil,
416
+ &@block_response)
175
417
  else
176
- res = http.request(req, payload ? payload.to_s : nil) { |http_response| fetch_body(http_response) }
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 << "#{@method} #{@url} done (0 length file\n)"
468
+ RestClient.log << "%s %s done (0 length file)\n" % [@method, @url]
204
469
  elsif total == 0
205
- RestClient.log << "#{@method} #{@url} (zero content length)\n"
470
+ RestClient.log << "%s %s (zero content length)\n" % [@method, @url]
206
471
  else
207
- RestClient.log << "#{@method} #{@url} %d%% done (%d of %d)\n" % [(size * 100) / total, size, total]
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
- if RestClient.log
255
- out = []
256
- out << "RestClient.#{method} #{url.inspect}"
257
- out << payload.short_inspect if payload
258
- out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
259
- RestClient.log << out.join(', ') + "\n"
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
- if RestClient.log
265
- size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
266
- RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
267
- end
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
- target_value = value.to_s
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| MIME::Types.type_for_extension(ext.to_s.strip) }.join(', ')
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
- end
299
- end
300
-
301
- module MIME
302
- class Types
580
+ private
303
581
 
304
- # Return the first found content-type for a value considered as an extension or the value itself
305
- def type_for_extension ext
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
- class << self
311
- def type_for_extension ext
312
- @__types__.type_for_extension ext
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
@@ -149,10 +149,9 @@ module RestClient
149
149
  #
150
150
  def [](suburl, &new_block)
151
151
  case
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
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
 
@@ -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 Response.create body, net_http_res, args
13
+ def self.create body, net_http_res, args, request
16
14
  result = body || ''
17
15
  result.extend Response
18
- result.net_http_res = 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
 
@@ -0,0 +1,7 @@
1
+ module RestClient
2
+ VERSION = '1.8.0' unless defined?(self::VERSION)
3
+
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end