rest-client 1.6.7 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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