rest-client 1.5.0 → 1.5.1

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.

@@ -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