rest-client 1.6.14 → 1.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

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