rest-client 1.5.0 → 1.5.1

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.

@@ -39,7 +39,7 @@ of specifying actions: get, put, post, delete.
39
39
  :group => 'those_guys'
40
40
  },
41
41
  :upload => {
42
- :file => File.new(path)
42
+ :file => File.new(path, 'rb')
43
43
  }
44
44
  })
45
45
 
@@ -47,7 +47,7 @@ of specifying actions: get, put, post, delete.
47
47
 
48
48
  Yeah, that's right! This does multipart sends for you!
49
49
 
50
- RestClient.post '/data', :myfile => File.new("/path/to/image.jpg")
50
+ RestClient.post '/data', :myfile => File.new("/path/to/image.jpg", 'rb')
51
51
 
52
52
  This does two things for you:
53
53
 
data/Rakefile CHANGED
@@ -25,11 +25,20 @@ Jeweler::RubyforgeTasks.new
25
25
  require 'spec/rake/spectask'
26
26
 
27
27
  desc "Run all specs"
28
- Spec::Rake::SpecTask.new('spec') do |t|
28
+ task :spec => ["spec:unit", "spec:integration"]
29
+
30
+ desc "Run unit specs"
31
+ Spec::Rake::SpecTask.new('spec:unit') do |t|
29
32
  t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
30
33
  t.spec_files = FileList['spec/*_spec.rb']
31
34
  end
32
35
 
36
+ desc "Run integration specs"
37
+ Spec::Rake::SpecTask.new('spec:integration') do |t|
38
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
39
+ t.spec_files = FileList['spec/integration/*_spec.rb']
40
+ end
41
+
33
42
  desc "Print specdocs"
34
43
  Spec::Rake::SpecTask.new(:doc) do |t|
35
44
  t.spec_opts = ["--format", "specdoc", "--dry-run"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.0
1
+ 1.5.1
@@ -77,7 +77,7 @@ if File.exists? ".irbrc"
77
77
  ENV['IRBRC'] = ".irbrc"
78
78
  end
79
79
 
80
- if File.exists?(rcfile = "~/.restclientrc")
80
+ if File.exists?( File.expand_path(rcfile = "~/.restclientrc") )
81
81
  load(rcfile)
82
82
  end
83
83
 
data/history.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 1.5.1
2
+
3
+ - only converts headers keys which are Symbols
4
+ - use CGI for cookie parsing instead of custom code
5
+ - unescape user and password before using them (patch provided by Lars Gierth)
6
+ - expand ~ in ~/.restclientrc (patch provided by Mike Fletcher)
7
+ - ssl verification raise an exception when the ca certificate is incorrect (patch provided by Braintree)
8
+
1
9
  # 1.5.0
2
10
 
3
11
  - the response is now a String with the Response module a.k.a. the change in 1.4.0 was a mistake (Response.body is returning self for compatability)
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  module RestClient
2
4
 
3
5
  module AbstractResponse
@@ -22,15 +24,11 @@ module RestClient
22
24
 
23
25
  # Hash of cookies extracted from response headers
24
26
  def cookies
25
- @cookies ||= (self.headers[:set_cookie] || []).inject({}) do |out, cookie_content|
26
- # correctly parse comma-separated cookies containing HTTP dates (which also contain a comma)
27
- cookie_content.split(/,\s*/).inject([""]) { |array, blob|
28
- blob =~ /expires=.+?$/ ? array.push(blob) : array.last.concat(blob)
29
- array
30
- }.each do |cookie|
31
- next if cookie.empty?
32
- key, *val = cookie.split(";").first.split("=")
33
- out[key] = val.join("=")
27
+ @cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
28
+ CGI::Cookie::parse(cookie_content).each do |key, cookie|
29
+ unless ['expires', 'path'].include? key
30
+ out[key] = cookie.value[0] || ''
31
+ end
34
32
  end
35
33
  out
36
34
  end
@@ -45,12 +43,12 @@ module RestClient
45
43
  unless [:get, :head].include? args[:method]
46
44
  raise Exceptions::EXCEPTIONS_MAP[code], self
47
45
  else
48
- follow_redirection &block
46
+ follow_redirection(&block)
49
47
  end
50
48
  elsif code == 303
51
49
  args[:method] = :get
52
50
  args.delete :payload
53
- follow_redirection &block
51
+ follow_redirection(&block)
54
52
  elsif Exceptions::EXCEPTIONS_MAP[code]
55
53
  raise Exceptions::EXCEPTIONS_MAP[code], self
56
54
  else
@@ -134,8 +134,12 @@ module RestClient
134
134
  message = 'Server broke connection'
135
135
  end
136
136
 
137
-
138
-
137
+ class SSLCertificateNotVerified < Exception
138
+ def initialize(message)
139
+ super(nil)
140
+ self.message = message
141
+ end
142
+ end
139
143
  end
140
144
 
141
145
  # backwards compatibility
@@ -1,5 +1,6 @@
1
1
  require 'tempfile'
2
2
  require 'mime/types'
3
+ require 'cgi'
3
4
 
4
5
  module RestClient
5
6
  # This class is used internally by RestClient to send the request, but you can also
@@ -27,7 +28,7 @@ module RestClient
27
28
  :ssl_client_key, :ssl_ca_file, :processed_headers, :args
28
29
 
29
30
  def self.execute(args, &block)
30
- new(args).execute &block
31
+ new(args).execute(&block)
31
32
  end
32
33
 
33
34
  def initialize args
@@ -84,8 +85,8 @@ module RestClient
84
85
 
85
86
  def parse_url_with_auth(url)
86
87
  uri = parse_url(url)
87
- @user = uri.user if uri.user
88
- @password = uri.password if uri.password
88
+ @user = CGI.unescape(uri.user) if uri.user
89
+ @password = CGI.unescape(uri.password) if uri.password
89
90
  uri
90
91
  end
91
92
 
@@ -115,6 +116,13 @@ module RestClient
115
116
  net.verify_mode = OpenSSL::SSL::VERIFY_NONE
116
117
  elsif @verify_ssl.is_a? Integer
117
118
  net.verify_mode = @verify_ssl
119
+ net.verify_callback = lambda do |preverify_ok, ssl_context|
120
+ if (!preverify_ok) || ssl_context.error != 0
121
+ err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
122
+ raise SSLCertificateNotVerified.new(err_msg)
123
+ end
124
+ true
125
+ end
118
126
  end
119
127
  net.cert = @ssl_client_cert if @ssl_client_cert
120
128
  net.key = @ssl_client_key if @ssl_client_key
@@ -182,7 +190,7 @@ module RestClient
182
190
  if block_given?
183
191
  block.call response, &block
184
192
  else
185
- response.return! &block
193
+ response.return!(&block)
186
194
  end
187
195
 
188
196
  end
@@ -219,20 +227,22 @@ module RestClient
219
227
  # Return a hash of headers whose keys are capitalized strings
220
228
  def stringify_headers headers
221
229
  headers.inject({}) do |result, (key, value)|
222
- target_key = key.to_s.split(/_/).map{|w| w.capitalize}.join('-')
223
- if 'CONTENT-TYPE' == target_key.upcase
230
+ if key.is_a? Symbol
231
+ key = key.to_s.split(/_/).map{|w| w.capitalize}.join('-')
232
+ end
233
+ if 'CONTENT-TYPE' == key.upcase
224
234
  target_value = value.to_s
225
- result[target_key] = MIME::Types.type_for_extension target_value
226
- elsif 'ACCEPT' == target_key.upcase
235
+ result[key] = MIME::Types.type_for_extension target_value
236
+ elsif 'ACCEPT' == key.upcase
227
237
  # Accept can be composed of several comma-separated values
228
238
  if value.is_a? Array
229
239
  target_values = value
230
240
  else
231
241
  target_values = value.to_s.split ','
232
242
  end
233
- result[target_key] = target_values.map{ |ext| MIME::Types.type_for_extension(ext.to_s.strip)}.join(', ')
243
+ result[key] = target_values.map{ |ext| MIME::Types.type_for_extension(ext.to_s.strip)}.join(', ')
234
244
  else
235
- result[target_key] = value.to_s
245
+ result[key] = value.to_s
236
246
  end
237
247
  result
238
248
  end
@@ -6,7 +6,7 @@ module RestClient
6
6
 
7
7
  include AbstractResponse
8
8
 
9
- attr_accessor :body, :net_http_res, :args
9
+ attr_accessor :args, :body, :net_http_res
10
10
 
11
11
  def body
12
12
  self
@@ -51,6 +51,11 @@ describe RestClient::AbstractResponse do
51
51
  @response.cookies.should == { 'session_id' => '1' }
52
52
  end
53
53
 
54
+ it "extract strange cookies" do
55
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/'])
56
+ @response.cookies.should == { 'session_id' => 'ZJ/HQVH6YE rVkTpn0zvTQ==' }
57
+ end
58
+
54
59
  it "can access the net http result directly" do
55
60
  @response.net_http_res.should == @net_http_res
56
61
  end
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
3
+ UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
4
+ dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
5
+ MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
6
+ dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
7
+ AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
8
+ BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
9
+ cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
10
+ AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
11
+ MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
12
+ aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
13
+ ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
14
+ IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
15
+ MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
16
+ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
17
+ 7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
18
+ 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
3
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
4
+ cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
5
+ MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
6
+ BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
7
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
8
+ ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
9
+ BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10
+ I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11
+ CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12
+ lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13
+ AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14
+ -----END CERTIFICATE-----
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ describe RestClient::Request do
4
+ describe "ssl verification" do
5
+ it "is successful with the correct ca_file" do
6
+ request = RestClient::Request.new(
7
+ :method => :get,
8
+ :url => 'https://www.google.com',
9
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
10
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt")
11
+ )
12
+ expect { request.execute }.to_not raise_error
13
+ end
14
+
15
+ it "is unsuccessful with an incorrect ca_file" do
16
+ request = RestClient::Request.new(
17
+ :method => :get,
18
+ :url => 'https://www.google.com',
19
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
20
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "equifax.crt")
21
+ )
22
+ expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
23
+ end
24
+ end
25
+ end
@@ -86,6 +86,13 @@ describe RestClient::Request do
86
86
  @request.password.should == 'pass1'
87
87
  end
88
88
 
89
+ it "extracts with escaping the username and password when parsing http://user:password@example.com/" do
90
+ URI.stub!(:parse).and_return(mock('uri', :user => 'joe%20', :password => 'pass1'))
91
+ @request.parse_url_with_auth('http://joe%20:pass1@example.com/resource')
92
+ @request.user.should == 'joe '
93
+ @request.password.should == 'pass1'
94
+ end
95
+
89
96
  it "doesn't overwrite user and password (which may have already been set by the Resource constructor) if there is no user/password in the url" do
90
97
  URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
91
98
  @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :user => 'beth', :password => 'pass2')
@@ -98,8 +105,8 @@ describe RestClient::Request do
98
105
  it "correctly formats cookies provided to the constructor" do
99
106
  URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
100
107
  @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1', :user_id => "someone" })
101
- @request.should_receive(:default_headers).and_return({'foo' => 'bar'})
102
- headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1;user_id=someone'}
108
+ @request.should_receive(:default_headers).and_return({'Foo' => 'bar'})
109
+ @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1;user_id=someone'}
103
110
  end
104
111
 
105
112
  it "determines the Net::HTTP class to instantiate by the method name" do
@@ -152,6 +159,13 @@ describe RestClient::Request do
152
159
  headers['Accept'].should == 'application/json'
153
160
  end
154
161
 
162
+ it "only convert symbols in header" do
163
+ @request.should_receive(:default_headers).and_return({})
164
+ headers = @request.make_headers({:foo_bar => 'value', "bar_bar" => 'value'})
165
+ headers['Foo-Bar'].should == 'value'
166
+ headers['bar_bar'].should == 'value'
167
+ end
168
+
155
169
  it "converts header values to strings" do
156
170
  @request.make_headers('A' => 1)['A'].should == '1'
157
171
  end
@@ -398,6 +412,7 @@ describe RestClient::Request do
398
412
  :payload => 'payload',
399
413
  :verify_ssl => mode )
400
414
  @net.should_receive(:verify_mode=).with(mode)
415
+ @net.should_receive(:verify_callback=)
401
416
  @http.stub!(:request)
402
417
  @request.stub!(:process_result)
403
418
  @request.stub!(:response_log)
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-client
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 1
4
5
  prerelease: false
5
6
  segments:
6
7
  - 1
7
8
  - 5
8
- - 0
9
- version: 1.5.0
9
+ - 1
10
+ version: 1.5.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Adam Wiggins
@@ -22,9 +23,11 @@ dependencies:
22
23
  name: mime-types
23
24
  prerelease: false
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
25
27
  requirements:
26
28
  - - ">="
27
29
  - !ruby/object:Gem::Version
30
+ hash: 47
28
31
  segments:
29
32
  - 1
30
33
  - 16
@@ -66,6 +69,9 @@ files:
66
69
  - spec/resource_spec.rb
67
70
  - spec/response_spec.rb
68
71
  - spec/restclient_spec.rb
72
+ - spec/integration/certs/equifax.crt
73
+ - spec/integration/certs/verisign.crt
74
+ - spec/integration/request_spec.rb
69
75
  - history.md
70
76
  has_rdoc: true
71
77
  homepage: http://github.com/archiloque/rest-client
@@ -77,23 +83,27 @@ rdoc_options:
77
83
  require_paths:
78
84
  - lib
79
85
  required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
80
87
  requirements:
81
88
  - - ">="
82
89
  - !ruby/object:Gem::Version
90
+ hash: 3
83
91
  segments:
84
92
  - 0
85
93
  version: "0"
86
94
  required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
87
96
  requirements:
88
97
  - - ">="
89
98
  - !ruby/object:Gem::Version
99
+ hash: 3
90
100
  segments:
91
101
  - 0
92
102
  version: "0"
93
103
  requirements: []
94
104
 
95
105
  rubyforge_project: rest-client
96
- rubygems_version: 1.3.6
106
+ rubygems_version: 1.3.7
97
107
  signing_key:
98
108
  specification_version: 3
99
109
  summary: Simple REST client for Ruby, inspired by microframework syntax for specifying actions.
@@ -108,3 +118,4 @@ test_files:
108
118
  - spec/resource_spec.rb
109
119
  - spec/response_spec.rb
110
120
  - spec/restclient_spec.rb
121
+ - spec/integration/request_spec.rb