excon 0.20.1 → 0.21.0

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

Potentially problematic release.


This version of excon might be problematic. Click here for more details.

@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'excon'
16
- s.version = '0.20.1'
17
- s.date = '2013-03-19'
16
+ s.version = '0.21.0'
17
+ s.date = '2013-05-04'
18
18
  s.rubyforge_project = 'excon'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -80,7 +80,7 @@ Gem::Specification.new do |s|
80
80
  benchmarks/excon_vs.rb
81
81
  benchmarks/for_vs_array_each.rb
82
82
  benchmarks/for_vs_hash_each.rb
83
- benchmarks/has_key-vs-hash[key].rb
83
+ benchmarks/has_key-vs-lookup.rb
84
84
  benchmarks/headers_case_sensitivity.rb
85
85
  benchmarks/headers_split_vs_match.rb
86
86
  benchmarks/implicit_block-vs-explicit_block.rb
@@ -138,7 +138,8 @@ module Excon
138
138
  )
139
139
  if uri.user || uri.password
140
140
  request_params[:headers] ||= {}
141
- request_params[:headers]['Authorization'] ||= 'Basic ' << ['' << uri.user.to_s << ':' << uri.password.to_s].pack('m').delete(Excon::CR_NL)
141
+ user, pass = URI.decode_www_form_component(uri.user.to_s), URI.decode_www_form_component(uri.password.to_s)
142
+ request_params[:headers]['Authorization'] ||= 'Basic ' << ['' << user << ':' << pass].pack('m').delete(Excon::CR_NL)
142
143
  end
143
144
  end
144
145
  if block_given?
@@ -156,6 +157,40 @@ module Excon
156
157
  stub
157
158
  end
158
159
 
160
+ # get a stub matching params or nil
161
+ def stub_for(request_params={})
162
+ Excon.stubs.each do |stub, response|
163
+ captures = { :headers => {} }
164
+ headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
165
+ case value = stub[:headers][key]
166
+ when Regexp
167
+ if match = value.match(request_params[:headers][key])
168
+ captures[:headers][key] = match.captures
169
+ end
170
+ match
171
+ else
172
+ value == request_params[:headers][key]
173
+ end
174
+ end
175
+ non_headers_match = (stub.keys - [:headers]).all? do |key|
176
+ case value = stub[key]
177
+ when Regexp
178
+ if match = value.match(request_params[key])
179
+ captures[key] = match.captures
180
+ end
181
+ match
182
+ else
183
+ value == request_params[key]
184
+ end
185
+ end
186
+ if headers_match && non_headers_match
187
+ request_params[:captures] = captures
188
+ return response
189
+ end
190
+ end
191
+ nil
192
+ end
193
+
159
194
  # get a list of defined stubs
160
195
  def stubs
161
196
  @stubs ||= []
@@ -3,6 +3,15 @@ module Excon
3
3
 
4
4
  attr_reader :data
5
5
 
6
+ def connection
7
+ $stderr.puts("Excon::Connection#connection is deprecated use Excon::Connection#data instead (#{caller.first})")
8
+ @data
9
+ end
10
+ def connection=(new_params)
11
+ $stderr.puts("Excon::Connection#connection= is deprecated use Excon::Connection#data= instead (#{caller.first})")
12
+ @data = new_params
13
+ end
14
+
6
15
  def params
7
16
  $stderr.puts("Excon::Connection#params is deprecated use Excon::Connection#data instead (#{caller.first})")
8
17
  @data
@@ -45,19 +54,24 @@ module Excon
45
54
 
46
55
  @data.merge!(params)
47
56
 
48
- if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
49
- @data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY'])
50
- elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
51
- @data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY'])
52
- elsif @data.has_key?(:proxy)
53
- @data[:proxy] = setup_proxy(@data[:proxy])
57
+ no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"] || ""
58
+ no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] }
59
+ unless no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) }
60
+ if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
61
+ @data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY'])
62
+ elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
63
+ @data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY'])
64
+ elsif @data.has_key?(:proxy)
65
+ @data[:proxy] = setup_proxy(@data[:proxy])
66
+ end
54
67
  end
55
68
 
56
69
  if @data[:proxy]
57
70
  @data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
58
71
  # https credentials happen in handshake
59
72
  if @data[:scheme] == 'http' && (@data[:proxy][:user] || @data[:proxy][:password])
60
- auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL)
73
+ user, pass = URI.decode_www_form_component(@data[:proxy][:user].to_s), URI.decode_www_form_component(@data[:proxy][:password].to_s)
74
+ auth = ['' << user.to_s << ':' << pass.to_s].pack('m').delete(Excon::CR_NL)
61
75
  @data[:headers]['Proxy-Authorization'] = 'Basic ' << auth
62
76
  end
63
77
  end
@@ -68,10 +82,11 @@ module Excon
68
82
 
69
83
  # Use Basic Auth if url contains a login
70
84
  if @data[:user] || @data[:password]
71
- @data[:headers]['Authorization'] ||= 'Basic ' << ['' << @data[:user].to_s << ':' << @data[:password].to_s].pack('m').delete(Excon::CR_NL)
85
+ user, pass = URI.decode_www_form_component(@data[:user].to_s), URI.decode_www_form_component(@data[:password].to_s)
86
+ @data[:headers]['Authorization'] ||= 'Basic ' << ['' << user.to_s << ':' << pass.to_s].pack('m').delete(Excon::CR_NL)
72
87
  end
73
88
 
74
- @socket_key = '' << @data[:host] << ':' << @data[:port].to_s
89
+ @socket_key = '' << @data[:scheme] << '://' << @data[:host] << ':' << @data[:port].to_s
75
90
  reset
76
91
  end
77
92
 
@@ -71,7 +71,7 @@ module Excon
71
71
  :write_timeout
72
72
  ]
73
73
 
74
- VERSION = '0.20.1'
74
+ VERSION = '0.21.0'
75
75
 
76
76
  unless ::IO.const_defined?(:WaitReadable)
77
77
  class ::IO
@@ -14,70 +14,45 @@ module Excon
14
14
  datum[:body] = datum[:body].read
15
15
  end
16
16
 
17
- datum[:captures] = {:headers => {}} # setup data to hold captures
18
- Excon.stubs.each do |stub, response|
19
- headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
20
- case value = stub[:headers][key]
21
- when Regexp
22
- if match = value.match(datum[:headers][key])
23
- datum[:captures][:headers][key] = match.captures
24
- end
25
- match
26
- else
27
- value == datum[:headers][key]
28
- end
29
- end
30
- non_headers_match = (stub.keys - [:headers]).all? do |key|
31
- case value = stub[key]
32
- when Regexp
33
- if match = value.match(datum[key])
34
- datum[:captures][key] = match.captures
35
- end
36
- match
37
- else
38
- value == datum[key]
39
- end
40
- end
41
- if headers_match && non_headers_match
42
- datum[:response] = {
43
- :body => '',
44
- :headers => {},
45
- :status => 200,
46
- :remote_ip => '127.0.0.1'
47
- }
17
+ if response = Excon.stub_for(datum)
18
+ datum[:response] = {
19
+ :body => '',
20
+ :headers => {},
21
+ :status => 200,
22
+ :remote_ip => '127.0.0.1'
23
+ }
48
24
 
49
- stub_datum = case response
50
- when Proc
51
- response.call(datum)
52
- else
53
- response
54
- end
25
+ stub_datum = case response
26
+ when Proc
27
+ response.call(datum)
28
+ else
29
+ response
30
+ end
55
31
 
56
- datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
57
- if stub_datum.has_key?(:headers)
58
- datum[:response][:headers].merge!(stub_datum[:headers])
59
- end
32
+ datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
33
+ if stub_datum.has_key?(:headers)
34
+ datum[:response][:headers].merge!(stub_datum[:headers])
35
+ end
60
36
 
61
- if datum[:expects] && ![*datum[:expects]].include?(datum[:response][:status])
62
- # don't pass stuff into a block if there was an error
63
- elsif datum.has_key?(:response_block) && datum[:response].has_key?(:body)
64
- body = datum[:response].delete(:body)
65
- content_length = remaining = body.bytesize
66
- i = 0
67
- while i < body.length
68
- datum[:response_block].call(body[i, datum[:chunk_size]], [remaining - datum[:chunk_size], 0].max, content_length)
69
- remaining -= datum[:chunk_size]
70
- i += datum[:chunk_size]
71
- end
37
+ if datum[:expects] && ![*datum[:expects]].include?(datum[:response][:status])
38
+ # don't pass stuff into a block if there was an error
39
+ elsif datum.has_key?(:response_block) && datum[:response].has_key?(:body)
40
+ body = datum[:response].delete(:body)
41
+ content_length = remaining = body.bytesize
42
+ i = 0
43
+ while i < body.length
44
+ datum[:response_block].call(body[i, datum[:chunk_size]], [remaining - datum[:chunk_size], 0].max, content_length)
45
+ remaining -= datum[:chunk_size]
46
+ i += datum[:chunk_size]
72
47
  end
73
- return @stack.request_call(datum)
74
48
  end
49
+ else
50
+ # if we reach here no stubs matched
51
+ raise(Excon::Errors::StubNotFound.new('no stubs matched ' << datum.inspect))
75
52
  end
76
- # if we reach here no stubs matched
77
- raise(Excon::Errors::StubNotFound.new('no stubs matched ' << datum.inspect))
78
- else
79
- @stack.request_call(datum)
80
53
  end
54
+
55
+ @stack.request_call(datum)
81
56
  end
82
57
  end
83
58
  end
@@ -32,67 +32,6 @@ module Excon
32
32
  connect
33
33
  end
34
34
 
35
- def connect
36
- @socket = nil
37
- exception = nil
38
-
39
- addrinfo = if @data[:proxy]
40
- ::Socket.getaddrinfo(@data[:proxy][:host], @data[:proxy][:port], @data[:proxy][:family], ::Socket::Constants::SOCK_STREAM)
41
- else
42
- ::Socket.getaddrinfo(@data[:host], @data[:port], @data[:family], ::Socket::Constants::SOCK_STREAM)
43
- end
44
-
45
- addrinfo.each do |_, port, _, ip, a_family, s_type|
46
- @remote_ip = ip
47
-
48
- # nonblocking connect
49
- begin
50
- sockaddr = ::Socket.sockaddr_in(port, ip)
51
-
52
- socket = ::Socket.new(a_family, s_type, 0)
53
-
54
- if @data[:nonblock]
55
- socket.connect_nonblock(sockaddr)
56
- else
57
- begin
58
- Timeout.timeout(@data[:connect_timeout]) do
59
- socket.connect(sockaddr)
60
- end
61
- rescue Timeout::Error
62
- raise Excon::Errors::Timeout.new('connect timeout reached')
63
- end
64
- end
65
-
66
- @socket = socket
67
- break
68
- rescue Errno::EINPROGRESS
69
- unless IO.select(nil, [socket], nil, @data[:connect_timeout])
70
- raise(Excon::Errors::Timeout.new("connect timeout reached"))
71
- end
72
- begin
73
- socket.connect_nonblock(sockaddr)
74
-
75
- @socket = socket
76
- break
77
- rescue Errno::EISCONN
78
- @socket = socket
79
- break
80
- rescue SystemCallError => exception
81
- socket.close
82
- next
83
- end
84
- rescue SystemCallError => exception
85
- socket.close
86
- next
87
- end
88
- end
89
-
90
- unless @socket
91
- # this will be our last encountered exception
92
- raise exception
93
- end
94
- end
95
-
96
35
  def read(max_length=nil)
97
36
  if @eof
98
37
  return nil
@@ -198,5 +137,68 @@ module Excon
198
137
  end
199
138
  end
200
139
 
140
+ private
141
+
142
+ def connect
143
+ @socket = nil
144
+ exception = nil
145
+
146
+ addrinfo = if @data[:proxy]
147
+ ::Socket.getaddrinfo(@data[:proxy][:host], @data[:proxy][:port], @data[:proxy][:family], ::Socket::Constants::SOCK_STREAM)
148
+ else
149
+ ::Socket.getaddrinfo(@data[:host], @data[:port], @data[:family], ::Socket::Constants::SOCK_STREAM)
150
+ end
151
+
152
+ addrinfo.each do |_, port, _, ip, a_family, s_type|
153
+ @remote_ip = ip
154
+
155
+ # nonblocking connect
156
+ begin
157
+ sockaddr = ::Socket.sockaddr_in(port, ip)
158
+
159
+ socket = ::Socket.new(a_family, s_type, 0)
160
+
161
+ if @data[:nonblock]
162
+ socket.connect_nonblock(sockaddr)
163
+ else
164
+ begin
165
+ Timeout.timeout(@data[:connect_timeout]) do
166
+ socket.connect(sockaddr)
167
+ end
168
+ rescue Timeout::Error
169
+ raise Excon::Errors::Timeout.new('connect timeout reached')
170
+ end
171
+ end
172
+
173
+ @socket = socket
174
+ break
175
+ rescue Errno::EINPROGRESS
176
+ unless IO.select(nil, [socket], nil, @data[:connect_timeout])
177
+ raise(Excon::Errors::Timeout.new("connect timeout reached"))
178
+ end
179
+ begin
180
+ socket.connect_nonblock(sockaddr)
181
+
182
+ @socket = socket
183
+ break
184
+ rescue Errno::EISCONN
185
+ @socket = socket
186
+ break
187
+ rescue SystemCallError => exception
188
+ socket.close
189
+ next
190
+ end
191
+ rescue SystemCallError => exception
192
+ socket.close if socket
193
+ next
194
+ end
195
+ end
196
+
197
+ unless @socket
198
+ # this will be our last encountered exception
199
+ raise exception
200
+ end
201
+ end
202
+
201
203
  end
202
204
  end
@@ -67,11 +67,6 @@ module Excon
67
67
  @socket
68
68
  end
69
69
 
70
- def connect
71
- check_nonblock_support
72
- super
73
- end
74
-
75
70
  def read(max_length=nil)
76
71
  check_nonblock_support
77
72
  super
@@ -92,5 +87,10 @@ module Excon
92
87
  end
93
88
  end
94
89
 
90
+ def connect
91
+ check_nonblock_support
92
+ super
93
+ end
94
+
95
95
  end
96
96
  end
@@ -2,6 +2,7 @@ with_rackup('basic_auth.ru') do
2
2
  Shindo.tests('Excon basics (Authorization data redacted)') do
3
3
  cases = [
4
4
  ['user & pass', 'http://user1:pass1@foo.com/', 'Basic dXNlcjE6cGFzczE='],
5
+ ['email & pass', 'http://foo%40bar.com:pass1@foo.com/', 'Basic Zm9vQGJhci5jb206cGFzczE='],
5
6
  ['user no pass', 'http://three_user@foo.com/', 'Basic dGhyZWVfdXNlcjo='],
6
7
  ['pass no user', 'http://:derppass@foo.com/', 'Basic OmRlcnBwYXNz']
7
8
  ]
@@ -1,4 +1,5 @@
1
1
  Shindo.tests('Excon response header support') do
2
+ env_init
2
3
 
3
4
  with_rackup('response_header.ru') do
4
5
 
@@ -46,4 +47,5 @@ Shindo.tests('Excon response header support') do
46
47
 
47
48
  end
48
49
 
50
+ env_restore
49
51
  end
@@ -1,4 +1,5 @@
1
1
  Shindo.tests('Excon stubs') do
2
+ env_init
2
3
 
3
4
  tests("missing stub").raises(Excon::Errors::StubNotFound) do
4
5
  connection = Excon.new('http://127.0.0.1:9292', :mock => true)
@@ -198,4 +199,5 @@ Shindo.tests('Excon stubs') do
198
199
  end
199
200
  end
200
201
 
202
+ env_restore
201
203
  end
@@ -1,4 +1,5 @@
1
1
  Shindo.tests('Excon proxy support') do
2
+ env_init
2
3
 
3
4
  tests('proxy configuration') do
4
5
 
@@ -26,9 +27,8 @@ Shindo.tests('Excon proxy support') do
26
27
  end
27
28
  end
28
29
 
29
- tests('with lowercase proxy config from the environment') do
30
- ENV['http_proxy'] = 'http://myproxy:8080'
31
- ENV['https_proxy'] = 'http://mysecureproxy:8081'
30
+ def env_proxy_tests(env)
31
+ env_init(env)
32
32
 
33
33
  tests('an http connection') do
34
34
  connection = Excon.new('http://foo.com')
@@ -74,65 +74,49 @@ Shindo.tests('Excon proxy support') do
74
74
  end
75
75
  end
76
76
 
77
- ENV.delete('http_proxy')
78
- ENV.delete('https_proxy')
79
- end
80
-
81
- tests('with uppercase proxy config from the environment') do
82
- ENV['HTTP_PROXY'] = 'http://myproxy:8080'
83
- ENV['HTTPS_PROXY'] = 'http://mysecureproxy:8081'
84
-
85
- tests('an http connection') do
86
- connection = Excon.new('http://foo.com')
77
+ tests('an http connection in no_proxy') do
78
+ connection = Excon.new('http://somesubdomain.noproxy')
87
79
 
88
- tests('connection.data[:proxy][:host]').returns('myproxy') do
89
- connection.data[:proxy][:host]
90
- end
91
-
92
- tests('connection.data[:proxy][:port]').returns('8080') do
93
- connection.data[:proxy][:port]
94
- end
95
-
96
- tests('connection.data[:proxy][:scheme]').returns('http') do
97
- connection.data[:proxy][:scheme]
80
+ tests('connection.data[:proxy]').returns(nil) do
81
+ connection.data[:proxy]
98
82
  end
99
83
  end
100
84
 
101
- tests('an https connection') do
102
- connection = Excon.new('https://secret.com')
85
+ tests('an http connection not completely matching no_proxy') do
86
+ connection = Excon.new('http://noproxy2')
103
87
 
104
- tests('connection.data[:proxy][:host]').returns('mysecureproxy') do
88
+ tests('connection.data[:proxy][:host]').returns('myproxy') do
105
89
  connection.data[:proxy][:host]
106
90
  end
107
-
108
- tests('connection.data[:proxy][:port]').returns('8081') do
109
- connection.data[:proxy][:port]
110
- end
111
-
112
- tests('connection.data[:proxy][:scheme]').returns('http') do
113
- connection.data[:proxy][:scheme]
114
- end
115
91
  end
116
92
 
117
- tests('http proxy from the environment overrides config') do
118
- connection = Excon.new('http://foo.com', :proxy => 'http://hard.coded.proxy:6666')
119
-
120
- tests('connection.data[:proxy][:host]').returns('myproxy') do
121
- connection.data[:proxy][:host]
122
- end
93
+ tests('an http connection with subdomain in no_proxy') do
94
+ connection = Excon.new('http://a.subdomain.noproxy2')
123
95
 
124
- tests('connection.data[:proxy][:port]').returns('8080') do
125
- connection.data[:proxy][:port]
96
+ tests('connection.data[:proxy]').returns(nil) do
97
+ connection.data[:proxy]
126
98
  end
127
99
  end
128
100
 
129
- ENV.delete('HTTP_PROXY')
130
- ENV.delete('HTTPS_PROXY')
101
+ env_restore
102
+ end
103
+
104
+ tests('with complete proxy config from the environment') do
105
+ env = {
106
+ 'http_proxy' => 'http://myproxy:8080',
107
+ 'https_proxy' => 'http://mysecureproxy:8081',
108
+ 'no_proxy' => 'noproxy, subdomain.noproxy2'
109
+ }
110
+ tests('lowercase') { env_proxy_tests(env) }
111
+ upperenv = {}
112
+ env.each do |k, v|
113
+ upperenv[k.upcase] = v
114
+ end
115
+ tests('uppercase') { env_proxy_tests(upperenv) }
131
116
  end
132
117
 
133
118
  tests('with only http_proxy config from the environment') do
134
- ENV['http_proxy'] = 'http://myproxy:8080'
135
- ENV.delete('https_proxy')
119
+ env_init({'http_proxy' => 'http://myproxy:8080' })
136
120
 
137
121
  tests('an https connection') do
138
122
  connection = Excon.new('https://secret.com')
@@ -150,7 +134,7 @@ Shindo.tests('Excon proxy support') do
150
134
  end
151
135
  end
152
136
 
153
- ENV.delete('http_proxy')
137
+ env_restore
154
138
  end
155
139
 
156
140
  end
@@ -211,4 +195,5 @@ Shindo.tests('Excon proxy support') do
211
195
 
212
196
  end
213
197
 
198
+ env_restore
214
199
  end