rest-client 1.6.14 → 1.7.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rest-client might be problematic. Click here for more details.

Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -3
  3. data/.travis.yml +12 -1
  4. data/AUTHORS +10 -4
  5. data/Gemfile +5 -1
  6. data/LICENSE +21 -0
  7. data/README.rdoc +27 -2
  8. data/Rakefile +78 -10
  9. data/bin/restclient +1 -1
  10. data/history.md +22 -16
  11. data/lib/restclient.rb +2 -8
  12. data/lib/restclient/exceptions.rb +7 -2
  13. data/lib/restclient/platform.rb +2 -1
  14. data/lib/restclient/request.rb +270 -48
  15. data/lib/restclient/response.rb +0 -2
  16. data/lib/restclient/version.rb +1 -1
  17. data/lib/restclient/windows.rb +8 -0
  18. data/lib/restclient/windows/root_certs.rb +105 -0
  19. data/rest-client.gemspec +14 -10
  20. data/rest-client.windows.gemspec +19 -0
  21. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  22. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  23. data/spec/integration/capath_verisign/README +8 -0
  24. data/spec/integration/capath_verisign/verisign.crt +14 -0
  25. data/spec/{integration_spec.rb → integration/integration_spec.rb} +2 -5
  26. data/spec/integration/request_spec.rb +46 -17
  27. data/spec/{base.rb → spec_helper.rb} +2 -3
  28. data/spec/{abstract_response_spec.rb → unit/abstract_response_spec.rb} +1 -1
  29. data/spec/{exceptions_spec.rb → unit/exceptions_spec.rb} +1 -4
  30. data/spec/{master_shake.jpg → unit/master_shake.jpg} +0 -0
  31. data/spec/{payload_spec.rb → unit/payload_spec.rb} +2 -1
  32. data/spec/{raw_response_spec.rb → unit/raw_response_spec.rb} +1 -1
  33. data/spec/{request2_spec.rb → unit/request2_spec.rb} +1 -4
  34. data/spec/{request_spec.rb → unit/request_spec.rb} +386 -9
  35. data/spec/{resource_spec.rb → unit/resource_spec.rb} +1 -4
  36. data/spec/{response_spec.rb → unit/response_spec.rb} +1 -4
  37. data/spec/{restclient_spec.rb → unit/restclient_spec.rb} +7 -1
  38. data/spec/unit/windows/root_certs_spec.rb +22 -0
  39. metadata +93 -58
  40. data/lib/restclient/net_http_ext.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 317f1bdcf77b2dae0fe9a08a165a56c71fb9c3d79b71c8d6c5a12cb1e4868a4e
4
- data.tar.gz: d2a662189817597ff1fca0e6685cdd5d06bdac1ca72bf9a932f1d7def456b26b
2
+ SHA1:
3
+ metadata.gz: bf3dedbad07361a8b9256ccfe76b123db48c00b3
4
+ data.tar.gz: 74cc0618213110d4aababe7911c367e84e2f813b
5
5
  SHA512:
6
- metadata.gz: f789e7291285e26aa719119331d0d1a6322ec618a25eb2626a1a8486e3584fb07e52d25dfb7a56972cb495e5e2cad6c67eae5f3da98d66974d51f3c4fc2566f1
7
- data.tar.gz: cf43508dbbc1f6977b3e8ad047651591760b8f386e3daa472888fc81f262b3d2669798f847b88fdd7b6f6f77b2a44990688c88efc53e02072bb9f284459c6b41
6
+ metadata.gz: 1230bc04a0a791ff416e9592dbd9ecf96bc315f39ddcebdd41266f4e84dbfd5ded56a79796a13e4b4598f2a0443c09dc8e925a73351e4d77bebd62d43338dc3f
7
+ data.tar.gz: 3a90bb3dc2297d6a6206780c4b088633ec098c535195592b86e481902cd794974ce0552b668ab75f1992eda0615c6ffc1282e76f835de72fdd5d8836ff89b491
data/.gitignore CHANGED
@@ -1,8 +1,9 @@
1
+ *.gem
1
2
  .idea
2
3
  .rvmrc
4
+ Gemfile.lock
3
5
  doc
4
- rdoc
5
6
  pkg
6
- *.gem
7
+ rdoc
7
8
  scratchpad.rb
8
- /Gemfile.lock
9
+ /.bundle/
data/.travis.yml CHANGED
@@ -1,3 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.8.7"
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ # Forgo 2.1.0 until Travis has a satisfactory fix for
7
+ # https://github.com/travis-ci/travis-ci/issues/2220
8
+ - "2.1" # always the latest 2.1.x
9
+ - "jruby-19mode"
10
+ script:
11
+ bundle exec rake test
12
+ branches:
13
+ except:
14
+ - "readme-edits"
data/AUTHORS CHANGED
@@ -11,12 +11,14 @@ Braintree
11
11
  Brian Donovan
12
12
  Caleb Land
13
13
  Chris Dinn
14
+ Chris Frohoff
14
15
  Chris Green
15
16
  Coda Hale
16
17
  Crawford
17
18
  Cyril Rohr
18
19
  Dan Mayer
19
20
  David Backeus
21
+ David Perkowski
20
22
  Dmitri Dolguikh
21
23
  Dusty Doris
22
24
  Dylan Egan
@@ -32,16 +34,19 @@ Hiro Asari
32
34
  Hugh McGowan
33
35
  Ian Warshak
34
36
  Ivan Makfinsky
37
+ JH. Chabran
35
38
  James Edward Gray II
36
39
  Jari Bakken
37
40
  Jeff Remer
38
- JH. Chabran
41
+ Jeffrey Hardy
42
+ Jeremy Kemper
39
43
  John Barnette
40
44
  Jon Rowe
41
45
  Jordi Massaguer Pla
42
46
  Juan Alvarez
43
47
  Julien Kirch
44
48
  Justin Coyne
49
+ Justin Lambert
45
50
  Keith Rarick
46
51
  Kenichi Kamiya
47
52
  Kevin Read
@@ -51,7 +56,7 @@ Lars Gierth
51
56
  Lawrence Leonard Gilbert
52
57
  Lee Jarvis
53
58
  Lennon Day-Reynolds
54
- macournoyer
59
+ Marc-André Cournoyer
55
60
  Matthew Manning
56
61
  Michael Klett
57
62
  Mike Fletcher
@@ -62,14 +67,15 @@ Oscar Del Ben
62
67
  Pablo Astigarraga
63
68
  Paul Dlug
64
69
  Pedro Belo
65
- rafael.ssouza
66
70
  Philip Corliss
71
+ Pierre-Louis Gottfrois
72
+ Rafael Ssouza
67
73
  Rick "technoweenie"
68
74
  Robert Eanes
69
75
  Rodrigo Panachi
70
76
  Syl Turner
71
77
  T. Watanabe
72
78
  Tekin
73
- tpresa
74
79
  W. Andrew Loe III
75
80
  Waynn Lue
81
+ tpresa
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec
3
+ if !!File::ALT_SEPARATOR
4
+ gemspec :name => 'rest-client.windows'
5
+ else
6
+ gemspec :name => 'rest-client'
7
+ end
4
8
 
5
9
  group :test do
6
10
  gem 'rake'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2008-2014 Rest Client Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.rdoc CHANGED
@@ -1,6 +1,7 @@
1
1
  = REST Client -- simple DSL for accessing HTTP and REST resources
2
2
 
3
- Build status: {<img src="https://travis-ci.org/rest-client/rest-client.svg?branch=1.6-legacy" alt="Build Status" />}[https://travis-ci.org/rest-client/rest-client]
3
+ Build status: {<img src="https://travis-ci.org/rest-client/rest-client.png" />}[https://travis-ci.org/rest-client/rest-client]
4
+
4
5
 
5
6
  A simple HTTP and REST client for Ruby, inspired by the Sinatra's microframework style
6
7
  of specifying actions: get, put, post, delete.
@@ -8,6 +9,27 @@ of specifying actions: get, put, post, delete.
8
9
  * Main page: https://github.com/rest-client/rest-client
9
10
  * Mailing list: rest.client@librelist.com (send a mail to subscribe).
10
11
 
12
+ == Requirements
13
+
14
+ MRI Ruby 1.9.2 and newer are supported. Alternative interpreters compatible with
15
+ 1.9.1+ should work as well.
16
+
17
+ Ruby 1.8.7 is no longer supported. That's because the Ruby 1.8.7 interpreter
18
+ itself no longer has official support, _not_ _even_ _security_ _patches!_ If you
19
+ have been putting off upgrading your servers, now is the time.
20
+ ({More info is on the Ruby developers'
21
+ blog.}[http://www.ruby-lang.org/en/news/2013/06/30/we-retire-1-8-7/])
22
+
23
+ The rest-client gem depends on these other gems for installation and usage:
24
+
25
+ * {mime-types}[http://rubygems.org/gems/mime-types]
26
+ * {netrc}[http://rubygems.org/gems/netrc]
27
+ * {rdoc}[http://rubygems.org/gems/rdoc]
28
+
29
+ If you want to hack on the code, you should also have {the Bundler
30
+ gem}[http://bundler.io/] installed so it can manage all necessary development
31
+ dependencies for you.
32
+
11
33
  == Usage: Raw URL
12
34
 
13
35
  require 'rest_client'
@@ -59,7 +81,7 @@ This does two things for you:
59
81
 
60
82
  If you are sending params that do not contain a File object but the payload needs to be multipart then:
61
83
 
62
- RestClient.post '/data', :foo => 'bar', :multipart => true
84
+ RestClient.post '/data', {:foo => 'bar', :multipart => true}
63
85
 
64
86
  == Usage: ActiveResource-Style
65
87
 
@@ -298,3 +320,6 @@ Released under the MIT License: http://www.opensource.org/licenses/mit-license.p
298
320
  "Master Shake" photo (http://www.flickr.com/photos/solgrundy/924205581/) by
299
321
  "SolGrundy"; used under terms of the Creative Commons Attribution-ShareAlike 2.0
300
322
  Generic license (http://creativecommons.org/licenses/by-sa/2.0/)
323
+
324
+ Code for reading Windows root certificate store derived from work by Puppet;
325
+ used under terms of the Apache License, Version 2.0.
data/Rakefile CHANGED
@@ -1,38 +1,106 @@
1
- begin
2
- # optionally load `rake build/install/release tasks'
3
- require 'bundler/gem_tasks'
4
- rescue LoadError
1
+ # load `rake build/install/release tasks'
2
+ require 'bundler/setup'
3
+ require_relative './lib/restclient/version'
4
+
5
+ namespace :ruby do
6
+ Bundler::GemHelper.install_tasks(:name => 'rest-client')
5
7
  end
6
8
 
7
9
  require "rspec/core/rake_task"
8
10
 
9
11
  desc "Run all specs"
10
- task :spec => ["spec:unit", "spec:integration"]
12
+ RSpec::Core::RakeTask.new('spec')
11
13
 
12
14
  desc "Run unit specs"
13
15
  RSpec::Core::RakeTask.new('spec:unit') do |t|
14
- t.pattern = ['spec/*_spec.rb']
16
+ t.pattern = 'spec/unit/*_spec.rb'
15
17
  end
16
18
 
17
19
  desc "Run integration specs"
18
20
  RSpec::Core::RakeTask.new('spec:integration') do |t|
19
- t.pattern = ['spec/integration/*_spec.rb']
21
+ t.pattern = 'spec/integration/*_spec.rb'
20
22
  end
21
23
 
22
24
  desc "Print specdocs"
23
25
  RSpec::Core::RakeTask.new(:doc) do |t|
24
26
  t.rspec_opts = ["--format", "specdoc", "--dry-run"]
25
- t.pattern = ['spec/*_spec.rb']
27
+ t.pattern = 'spec/**/*_spec.rb'
26
28
  end
27
29
 
28
30
  desc "Run all examples with RCov"
29
31
  RSpec::Core::RakeTask.new('rcov') do |t|
30
- t.pattern = ['spec/*_spec.rb']
32
+ t.pattern = 'spec/*_spec.rb'
31
33
  t.rcov = true
32
34
  t.rcov_opts = ['--exclude', 'examples']
33
35
  end
34
36
 
35
- task :default => :spec
37
+ task :default do
38
+ sh 'rake -T'
39
+ end
40
+
41
+ def alias_task(alias_task, original)
42
+ desc "Alias for rake #{original}"
43
+ task alias_task, Rake.application[original].arg_names => original
44
+ end
45
+ alias_task(:test, :spec)
46
+
47
+ ############################
48
+
49
+ WindowsPlatforms = %w{x86-mingw32 x64-mingw32 x86-mswin32}
50
+
51
+ namespace :all do
52
+
53
+ desc "Build rest-client #{RestClient::VERSION} for all platforms"
54
+ task :build => ['ruby:build'] + \
55
+ WindowsPlatforms.map {|p| "windows:#{p}:build"}
56
+
57
+ desc "Create tag v#{RestClient::VERSION} and for all platforms build and push " \
58
+ "rest-client #{RestClient::VERSION} to Rubygems"
59
+ task :release => ['build', 'ruby:release'] + \
60
+ WindowsPlatforms.map {|p| "windows:#{p}:push"}
61
+
62
+ end
63
+
64
+ namespace :windows do
65
+ spec_path = File.join(File.dirname(__FILE__), 'rest-client.windows.gemspec')
66
+
67
+ WindowsPlatforms.each do |platform|
68
+ namespace platform do
69
+ gem_filename = "rest-client-#{RestClient::VERSION}-#{platform}.gem"
70
+ base = File.dirname(__FILE__)
71
+ pkg_dir = File.join(base, 'pkg')
72
+ gem_file_path = File.join(pkg_dir, gem_filename)
73
+
74
+ desc "Build #{gem_filename} into the pkg directory"
75
+ task 'build' do
76
+ orig_platform = ENV['BUILD_PLATFORM']
77
+ begin
78
+ ENV['BUILD_PLATFORM'] = platform
79
+
80
+ sh("gem build -V #{spec_path}") do |ok, res|
81
+ if ok
82
+ FileUtils.mkdir_p(pkg_dir)
83
+ FileUtils.mv(File.join(base, gem_filename), pkg_dir)
84
+ Bundler.ui.confirm("rest-client #{RestClient::VERSION} " +
85
+ "built to pkg/#{gem_filename}")
86
+ else
87
+ abort "Command `gem build` failed: #{res}"
88
+ end
89
+ end
90
+
91
+ ensure
92
+ ENV['BUILD_PLATFORM'] = orig_platform
93
+ end
94
+ end
95
+
96
+ desc "Push #{gem_filename} to Rubygems"
97
+ task 'push' do
98
+ sh("gem push #{gem_file_path}")
99
+ end
100
+ end
101
+ end
102
+
103
+ end
36
104
 
37
105
  ############################
38
106
 
data/bin/restclient CHANGED
@@ -50,7 +50,7 @@ if @verb
50
50
  end
51
51
  exit 0
52
52
  rescue RestClient::Exception => e
53
- puts e.response.body if e.respond_to? :response
53
+ puts e.response.body if e.respond_to?(:response) && e.response
54
54
  raise
55
55
  end
56
56
  end
data/history.md CHANGED
@@ -1,19 +1,25 @@
1
- # 1.6.14
2
-
3
- - This release is unchanged from 1.6.9. It was published in order to supersede
4
- the malicious 1.6.10-13 versions, even for users who are still pinning to the
5
- legacy 1.6.x series. All users are encouraged to upgrade to rest-client 2.x.
6
-
7
- # 1.6.10, 1.6.11, 1.6.12, 1.6.13 (CVE-2019-15224)
8
-
9
- - These versions were pushed by a malicious actor and included a backdoor permitting
10
- remote code execution in Rails environments.
11
- - They were live for about five days before being yanked.
12
- https://github.com/rest-client/rest-client/issues/713
13
-
14
- # 1.6.9
15
-
16
- - Move rdoc to a development dependency
1
+ # 1.7.0
2
+
3
+ - This release drops support for Ruby 1.8.7 and breaks compatibility in a few
4
+ other relatively minor ways
5
+ - Upgrade to mime-types ~> 2.0
6
+ - Don't CGI.unescape cookie values sent to the server (issue #89)
7
+ - Add support for reading credentials from netrc
8
+ - Lots of SSL changes and enhancements: (#268)
9
+ - Enable peer verification by default (setting `VERIFY_PEER` with OpenSSL)
10
+ - By default, use the system default certificate store for SSL verification,
11
+ even on Windows (this uses a separate Windows build that pulls in ffi)
12
+ - Add support for SSL `ca_path`
13
+ - Add support for SSL `cert_store`
14
+ - Add support for SSL `verify_callback` (with some caveats for jruby, OS X, #277)
15
+ - Add support for SSL ciphers, and choose secure ones by default
16
+ - Run tests under travis
17
+ - Several other bugfixes and test improvements
18
+ - Convert Errno::ETIMEDOUT to RestClient::RequestTimeout
19
+ - Handle more HTTP response codes from recent standards
20
+ - Save raw responses to binary mode tempfile (#110)
21
+ - Disable timeouts with :timeout => nil rather than :timeout => -1
22
+ - Drop all Net::HTTP monkey patches
17
23
 
18
24
  # 1.6.8
19
25
 
data/lib/restclient.rb CHANGED
@@ -1,13 +1,7 @@
1
1
  require 'uri'
2
2
  require 'zlib'
3
3
  require 'stringio'
4
-
5
- begin
6
- require 'net/https'
7
- rescue LoadError => e
8
- raise e unless RUBY_PLATFORM =~ /linux/
9
- raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
10
- end
4
+ require 'net/https'
11
5
 
12
6
  require File.dirname(__FILE__) + '/restclient/version'
13
7
  require File.dirname(__FILE__) + '/restclient/platform'
@@ -18,7 +12,7 @@ require File.dirname(__FILE__) + '/restclient/response'
18
12
  require File.dirname(__FILE__) + '/restclient/raw_response'
19
13
  require File.dirname(__FILE__) + '/restclient/resource'
20
14
  require File.dirname(__FILE__) + '/restclient/payload'
21
- require File.dirname(__FILE__) + '/restclient/net_http_ext'
15
+ require File.dirname(__FILE__) + '/restclient/windows'
22
16
 
23
17
  # This module's static methods are the entry point for using the REST client.
24
18
  #
@@ -40,13 +40,16 @@ module RestClient
40
40
  415 => 'Unsupported Media Type',
41
41
  416 => 'Requested Range Not Satisfiable',
42
42
  417 => 'Expectation Failed',
43
- 418 => 'I\'m A Teapot',
43
+ 418 => 'I\'m A Teapot', #RFC2324
44
44
  421 => 'Too Many Connections From This IP',
45
45
  422 => 'Unprocessable Entity', #WebDAV
46
46
  423 => 'Locked', #WebDAV
47
47
  424 => 'Failed Dependency', #WebDAV
48
48
  425 => 'Unordered Collection', #WebDAV
49
49
  426 => 'Upgrade Required',
50
+ 428 => 'Precondition Required', #RFC6585
51
+ 429 => 'Too Many Requests', #RFC6585
52
+ 431 => 'Request Header Fields Too Large', #RFC6585
50
53
  449 => 'Retry With', #Microsoft
51
54
  450 => 'Blocked By Windows Parental Controls', #Microsoft
52
55
 
@@ -59,7 +62,9 @@ module RestClient
59
62
  506 => 'Variant Also Negotiates',
60
63
  507 => 'Insufficient Storage', #WebDAV
61
64
  509 => 'Bandwidth Limit Exceeded', #Apache
62
- 510 => 'Not Extended'}
65
+ 510 => 'Not Extended',
66
+ 511 => 'Network Authentication Required', # RFC6585
67
+ }
63
68
 
64
69
  # Compatibility : make the Response act like a Net::HTTPResponse when needed
65
70
  module ResponseForException
@@ -23,7 +23,8 @@ module RestClient
23
23
  # @return [Boolean]
24
24
  #
25
25
  def self.jruby?
26
- RUBY_PLATFORM == 'java'
26
+ # defined on mri >= 1.9
27
+ RUBY_ENGINE == 'jruby'
27
28
  end
28
29
  end
29
30
  end
@@ -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,22 +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
25
- # * :ssl_verify_callback, :ssl_verify_callback_warnings
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=
26
33
  class Request
27
34
 
28
35
  attr_reader :method, :url, :headers, :cookies,
29
36
  :payload, :user, :password, :timeout, :max_redirects,
30
- :open_timeout, :raw_response, :verify_ssl, :ssl_client_cert,
31
- :ssl_client_key, :ssl_ca_file, :processed_headers, :args,
32
- :ssl_verify_callback, :ssl_verify_callback_warnings
37
+ :open_timeout, :raw_response, :processed_headers, :args,
38
+ :ssl_opts
33
39
 
34
40
  def self.execute(args, & block)
35
41
  new(args).execute(& block)
36
42
  end
37
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
+
38
105
  def initialize args
39
106
  @method = args[:method] or raise ArgumentError, "must pass :method"
40
107
  @headers = args[:headers] || {}
@@ -47,16 +114,57 @@ module RestClient
47
114
  @payload = Payload.generate(args[:payload])
48
115
  @user = args[:user]
49
116
  @password = args[:password]
50
- @timeout = args[:timeout]
51
- @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
52
123
  @block_response = args[:block_response]
53
124
  @raw_response = args[:raw_response] || false
54
- @verify_ssl = args[:verify_ssl] || false
55
- @ssl_client_cert = args[:ssl_client_cert] || nil
56
- @ssl_client_key = args[:ssl_client_key] || nil
57
- @ssl_ca_file = args[:ssl_ca_file] || nil
58
- @ssl_verify_callback = args[:ssl_verify_callback] || nil
59
- @ssl_verify_callback_warnings = args.fetch(:ssl_verify_callback, true)
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
+
60
168
  @tf = nil # If you are a raw request, this is your tempfile
61
169
  @max_redirects = args[:max_redirects] || 10
62
170
  @processed_headers = make_headers headers
@@ -70,6 +178,16 @@ module RestClient
70
178
  payload.close if payload
71
179
  end
72
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
+
73
191
  # Extract the query parameters and append them to the url
74
192
  def process_url_params url, headers
75
193
  url_params = {}
@@ -91,13 +209,46 @@ module RestClient
91
209
 
92
210
  def make_headers user_headers
93
211
  unless @cookies.empty?
94
- 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('; ')
95
226
  end
96
227
  headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
97
228
  headers.merge!(@payload.headers) if @payload
98
229
  headers
99
230
  end
100
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
+
101
252
  def net_http_class
102
253
  if RestClient.proxy
103
254
  proxy_uri = URI.parse(RestClient.proxy)
@@ -111,6 +262,15 @@ module RestClient
111
262
  Net::HTTP.const_get(method.to_s.capitalize)
112
263
  end
113
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
+
114
274
  def parse_url(url)
115
275
  url = "http://#{url}" unless url.match(/^http/)
116
276
  URI.parse(url)
@@ -120,6 +280,9 @@ module RestClient
120
280
  uri = parse_url(url)
121
281
  @user = CGI.unescape(uri.user) if uri.user
122
282
  @password = CGI.unescape(uri.password) if uri.password
283
+ if !@user && !@password
284
+ @user, @password = Netrc.read[uri.host]
285
+ end
123
286
  uri
124
287
  end
125
288
 
@@ -140,6 +303,30 @@ module RestClient
140
303
  end
141
304
  end
142
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
+ cert_store.add_cert(cert)
324
+ end
325
+ end
326
+
327
+ cert_store
328
+ end
329
+
143
330
  def print_verify_callback_warnings
144
331
  warned = false
145
332
  if RestClient::Platform.mac?
@@ -159,27 +346,20 @@ module RestClient
159
346
 
160
347
  net = net_http_class.new(uri.host, uri.port)
161
348
  net.use_ssl = uri.is_a?(URI::HTTPS)
162
- if @verify_ssl
163
- if @verify_ssl.is_a? Integer
164
- net.verify_mode = @verify_ssl
165
- else
166
- net.verify_mode = OpenSSL::SSL::VERIFY_PEER
167
- end
168
- else
169
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE
170
- end
171
- net.cert = @ssl_client_cert if @ssl_client_cert
172
- net.key = @ssl_client_key if @ssl_client_key
173
- net.ca_file = @ssl_ca_file if @ssl_ca_file
174
- net.read_timeout = @timeout if @timeout
175
- net.open_timeout = @open_timeout if @open_timeout
349
+ net.ssl_version = ssl_version if ssl_version
350
+ net.ciphers = ssl_ciphers if ssl_ciphers
176
351
 
177
- # disable the timeout if the timeout value is -1
178
- net.read_timeout = nil if @timeout == -1
179
- net.open_timeout = nil if @open_timeout == -1
352
+ net.verify_mode = verify_ssl
180
353
 
181
- # verify_callback isn't well supported on all platforms, but do allow
182
- # users to set one if they want.
354
+ net.cert = ssl_client_cert if ssl_client_cert
355
+ net.key = ssl_client_key if ssl_client_key
356
+ net.ca_file = ssl_ca_file if ssl_ca_file
357
+ net.ca_path = ssl_ca_path if ssl_ca_path
358
+ net.cert_store = ssl_cert_store if ssl_cert_store
359
+
360
+ # We no longer rely on net.verify_callback for the main SSL verification
361
+ # because it's not well supported on all platforms (see comments below).
362
+ # But do allow users to set one if they want.
183
363
  if ssl_verify_callback
184
364
  net.verify_callback = ssl_verify_callback
185
365
 
@@ -197,30 +377,70 @@ module RestClient
197
377
  end
198
378
  end
199
379
 
380
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
381
+ warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE')
382
+ warn('This dangerous monkey patch leaves you open to MITM attacks!')
383
+ warn('Try passing :verify_ssl => false instead.')
384
+ end
385
+
386
+ if defined? @timeout
387
+ if @timeout == -1
388
+ warn 'To disable read timeouts, please set timeout to nil instead of -1'
389
+ @timeout = nil
390
+ end
391
+ net.read_timeout = @timeout
392
+ end
393
+ if defined? @open_timeout
394
+ if @open_timeout == -1
395
+ warn 'To disable open timeouts, please set open_timeout to nil instead of -1'
396
+ @open_timeout = nil
397
+ end
398
+ net.open_timeout = @open_timeout
399
+ end
400
+
200
401
  RestClient.before_execution_procs.each do |before_proc|
201
402
  before_proc.call(req, args)
202
403
  end
203
404
 
204
405
  log_request
205
406
 
407
+
206
408
  net.start do |http|
207
409
  if @block_response
208
- http.request(req, payload ? payload.to_s : nil, & @block_response)
410
+ net_http_do_request(http, req, payload ? payload.to_s : nil,
411
+ & @block_response)
209
412
  else
210
- res = http.request(req, payload ? payload.to_s : nil) { |http_response| fetch_body(http_response) }
413
+ res = net_http_do_request(http, req, payload ? payload.to_s : nil) \
414
+ { |http_response| fetch_body(http_response) }
211
415
  log_response res
212
416
  process_result res, & block
213
417
  end
214
418
  end
215
419
  rescue EOFError
216
420
  raise RestClient::ServerBrokeConnection
217
- rescue Timeout::Error
421
+ rescue Timeout::Error, Errno::ETIMEDOUT
218
422
  raise RestClient::RequestTimeout
423
+
219
424
  rescue OpenSSL::SSL::SSLError => error
220
- # UGH. Not sure if this is needed at all. SSLCertificateNotVerified is not being used internally.
221
- # I think it would be better to leave SSLError processing to the client (they'd have to do that anyway...)
222
- raise SSLCertificateNotVerified.new(error.message) if error.message.include?("certificate verify failed")
223
- raise error
425
+ # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
426
+ # pass through OpenSSL::SSL::SSLError directly.
427
+ #
428
+ # Exceptions in verify_callback are ignored [1], and jruby doesn't support
429
+ # it at all [2]. RestClient has to catch OpenSSL::SSL::SSLError and either
430
+ # re-throw it as is, or throw SSLCertificateNotVerified based on the
431
+ # contents of the message field of the original exception.
432
+ #
433
+ # The client has to handle OpenSSL::SSL::SSLError exceptions anyway, so
434
+ # we shouldn't make them handle both OpenSSL and RestClient exceptions.
435
+ #
436
+ # [1] https://github.com/ruby/ruby/blob/89e70fe8e7/ext/openssl/ossl.c#L238
437
+ # [2] https://github.com/jruby/jruby/issues/597
438
+
439
+ if error.message.include?("certificate verify failed")
440
+ raise SSLCertificateNotVerified.new(error.message)
441
+ else
442
+ raise error
443
+ end
224
444
  end
225
445
 
226
446
  def setup_credentials(req)
@@ -233,17 +453,18 @@ module RestClient
233
453
  # Stolen from http://www.ruby-forum.com/topic/166423
234
454
  # Kudos to _why!
235
455
  @tf = Tempfile.new("rest-client")
456
+ @tf.binmode
236
457
  size, total = 0, http_response.header['Content-Length'].to_i
237
458
  http_response.read_body do |chunk|
238
459
  @tf.write chunk
239
460
  size += chunk.size
240
461
  if RestClient.log
241
462
  if size == 0
242
- RestClient.log << "#{@method} #{@url} done (0 length file\n)"
463
+ RestClient.log << "%s %s done (0 length file\n)" % [@method, @url]
243
464
  elsif total == 0
244
- RestClient.log << "#{@method} #{@url} (zero content length)\n"
465
+ RestClient.log << "%s %s (zero content length)\n" % [@method, @url]
245
466
  else
246
- RestClient.log << "#{@method} #{@url} %d%% done (%d of %d)\n" % [(size * 100) / total, size, total]
467
+ RestClient.log << "%s %s %d%% done (%d of %d)\n" % [@method, @url, (size * 100) / total, size, total]
247
468
  end
248
469
  end
249
470
  end
@@ -335,9 +556,10 @@ module RestClient
335
556
  end
336
557
 
337
558
  private
338
- def parser
339
- URI.const_defined?(:Parser) ? URI::Parser.new : URI
340
- end
559
+
560
+ def parser
561
+ URI.const_defined?(:Parser) ? URI::Parser.new : URI
562
+ end
341
563
 
342
564
  end
343
565
  end