excon 0.45.4 → 0.46.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.45.4'
17
- s.date = '2015-07-13'
16
+ s.version = '0.46.0'
17
+ s.date = '2016-02-26'
18
18
  s.rubyforge_project = 'excon'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -62,6 +62,7 @@ Gem::Specification.new do |s|
62
62
  s.add_development_dependency('rdoc')
63
63
  s.add_development_dependency('shindo')
64
64
  s.add_development_dependency('sinatra')
65
+ s.add_development_dependency('sinatra-contrib')
65
66
  s.add_development_dependency('json', '>= 1.8.2')
66
67
 
67
68
  ## Leave this section as-is. It will be automatically generated from the
@@ -104,6 +105,7 @@ Gem::Specification.new do |s|
104
105
  lib/excon/extensions/uri.rb
105
106
  lib/excon/headers.rb
106
107
  lib/excon/middlewares/base.rb
108
+ lib/excon/middlewares/capture_cookies.rb
107
109
  lib/excon/middlewares/decompress.rb
108
110
  lib/excon/middlewares/escape_path.rb
109
111
  lib/excon/middlewares/expects.rb
@@ -131,6 +133,7 @@ Gem::Specification.new do |s|
131
133
  tests/errors_tests.rb
132
134
  tests/header_tests.rb
133
135
  tests/middlewares/canned_response_tests.rb
136
+ tests/middlewares/capture_cookies_tests.rb
134
137
  tests/middlewares/decompress_tests.rb
135
138
  tests/middlewares/escape_path_tests.rb
136
139
  tests/middlewares/idempotent_tests.rb
@@ -147,6 +150,7 @@ Gem::Specification.new do |s|
147
150
  tests/rackups/proxy.ru
148
151
  tests/rackups/query_string.ru
149
152
  tests/rackups/redirecting.ru
153
+ tests/rackups/redirecting_with_cookie.ru
150
154
  tests/rackups/request_headers.ru
151
155
  tests/rackups/request_methods.ru
152
156
  tests/rackups/response_header.ru
@@ -30,6 +30,7 @@ require 'excon/response'
30
30
  require 'excon/middlewares/decompress'
31
31
  require 'excon/middlewares/escape_path'
32
32
  require 'excon/middlewares/redirect_follower'
33
+ require 'excon/middlewares/capture_cookies'
33
34
  require 'excon/pretty_printer'
34
35
  require 'excon/socket'
35
36
  require 'excon/ssl_socket'
@@ -207,7 +207,7 @@ module Excon
207
207
  datum[:headers] = @data[:headers].merge(datum[:headers] || {})
208
208
 
209
209
  if datum[:scheme] == UNIX
210
- datum[:headers]['Host'] ||= '' << datum[:socket]
210
+ datum[:headers]['Host'] = ''
211
211
  else
212
212
  datum[:headers]['Host'] ||= '' << datum[:host] << port_string(datum)
213
213
  end
@@ -1,6 +1,6 @@
1
1
  module Excon
2
2
 
3
- VERSION = '0.45.4'
3
+ VERSION = '0.46.0'
4
4
 
5
5
  CR_NL = "\r\n"
6
6
 
@@ -8,7 +8,7 @@ module Excon
8
8
  class SocketError < Error
9
9
  attr_reader :socket_error
10
10
 
11
- def initialize(socket_error=Excon::Error.new)
11
+ def initialize(socket_error=Excon::Errors::Error.new)
12
12
  if socket_error.message =~ /certificate verify failed/
13
13
  super("Unable to verify certificate, please set `Excon.defaults[:ssl_ca_path] = path_to_certs`, `ENV['SSL_CERT_DIR'] = path_to_certs`, `Excon.defaults[:ssl_ca_file] = path_to_file`, `ENV['SSL_CERT_FILE'] = path_to_file`, `Excon.defaults[:ssl_verify_callback] = callback` (see OpenSSL::SSL::SSLContext#verify_callback), or `Excon.defaults[:ssl_verify_peer] = false` (less secure).")
14
14
  else
@@ -0,0 +1,31 @@
1
+ module Excon
2
+ module Middleware
3
+ class CaptureCookies < Excon::Middleware::Base
4
+
5
+ def extract_cookies_from_set_cookie(set_cookie)
6
+ set_cookie.split(',').map { |full| full.split(';').first.strip }.join('; ')
7
+ end
8
+
9
+ def get_header(datum, header)
10
+ _, header_value = datum[:response][:headers].detect do |key, value|
11
+ key.casecmp(header) == 0
12
+ end
13
+ header_value
14
+ end
15
+
16
+ def response_call(datum)
17
+ cookie = get_header(datum, 'Set-Cookie')
18
+ if cookie
19
+ cookie = extract_cookies_from_set_cookie(cookie)
20
+ unless datum[:headers].key?("Cookie")
21
+ datum[:headers]["Cookie"] = cookie
22
+ else
23
+ original_cookies = datum[:headers]["Cookie"]
24
+ datum[:headers]["Cookie"] = original_cookies.empty? ? cookie : [original_cookies,cookie].join('; ')
25
+ end
26
+ end
27
+ super(datum)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -12,16 +12,17 @@ module Excon
12
12
  end
13
13
 
14
14
  def response_call(datum)
15
- unless datum.has_key?(:response_block)
15
+ body = datum[:response][:body]
16
+ unless datum.has_key?(:response_block) || body.nil? || body.empty?
16
17
  if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
17
18
  encodings = Utils.split_header_value(datum[:response][:headers][key])
18
19
  if encoding = encodings.last
19
20
  if encoding.casecmp('deflate') == 0
20
21
  # assume inflate omits header
21
- datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(datum[:response][:body])
22
+ datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
22
23
  encodings.pop
23
24
  elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
24
- datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(datum[:response][:body])).read
25
+ datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
25
26
  encodings.pop
26
27
  end
27
28
  datum[:response][:headers][key] = encodings.join(', ')
@@ -1,14 +1,21 @@
1
1
  module Excon
2
2
  module Middleware
3
3
  class RedirectFollower < Excon::Middleware::Base
4
+
5
+ def get_header(datum, header)
6
+ _, header_value = datum[:response][:headers].detect do |key, value|
7
+ key.casecmp(header) == 0
8
+ end
9
+ header_value
10
+ end
11
+
4
12
  def response_call(datum)
5
13
  if datum.has_key?(:response)
6
14
  case datum[:response][:status]
7
15
  when 301, 302, 303, 307, 308
8
16
  uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser]
9
- _, location = datum[:response][:headers].detect do |key, value|
10
- key.casecmp('Location') == 0
11
- end
17
+
18
+ location = get_header(datum, 'Location')
12
19
  uri = uri_parser.parse(location)
13
20
 
14
21
  # delete old/redirect response
@@ -16,17 +16,20 @@ module Excon
16
16
  def headers
17
17
  @data[:headers]
18
18
  end
19
- def status=(new_status)
20
- @data[:status] = new_status
19
+ def host
20
+ @data[:host]
21
21
  end
22
- def status
23
- @data[:status]
22
+ def local_address
23
+ @data[:local_address]
24
24
  end
25
- def status_line
26
- @data[:status_line]
25
+ def local_port
26
+ @data[:local_port]
27
27
  end
28
- def status_line=(new_status_line)
29
- @data[:status_line] = new_status_line
28
+ def path
29
+ @data[:path]
30
+ end
31
+ def port
32
+ @data[:port]
30
33
  end
31
34
  def reason_phrase=(new_reason_phrase)
32
35
  @data[:reason_phrase] = new_reason_phrase
@@ -40,11 +43,17 @@ module Excon
40
43
  def remote_ip
41
44
  @data[:remote_ip]
42
45
  end
43
- def local_port
44
- @data[:local_port]
46
+ def status=(new_status)
47
+ @data[:status] = new_status
45
48
  end
46
- def local_address
47
- @data[:local_address]
49
+ def status
50
+ @data[:status]
51
+ end
52
+ def status_line
53
+ @data[:status_line]
54
+ end
55
+ def status_line=(new_status_line)
56
+ @data[:status_line] = new_status_line
48
57
  end
49
58
 
50
59
  def self.parse(socket, datum)
@@ -57,7 +66,10 @@ module Excon
57
66
 
58
67
  datum[:response] = {
59
68
  :body => '',
69
+ :host => datum[:host],
60
70
  :headers => Excon::Headers.new,
71
+ :path => datum[:path],
72
+ :port => datum[:port],
61
73
  :status => status,
62
74
  :status_line => line,
63
75
  :reason_phrase => reason_phrase
@@ -100,25 +112,27 @@ module Excon
100
112
  if response_block
101
113
  while (chunk_size = socket.readline.chomp!.to_i(16)) > 0
102
114
  while chunk_size > 0
103
- chunk = socket.read(chunk_size)
115
+ chunk = socket.read(chunk_size) || raise(EOFError)
104
116
  chunk_size -= chunk.bytesize
105
117
  response_block.call(chunk, nil, nil)
106
118
  end
107
119
  new_line_size = 2 # 2 == "\r\n".length
108
120
  while new_line_size > 0
109
- new_line_size -= socket.read(new_line_size).length
121
+ chunk = socket.read(new_line_size) || raise(EOFError)
122
+ new_line_size -= chunk.length
110
123
  end
111
124
  end
112
125
  else
113
126
  while (chunk_size = socket.readline.chomp!.to_i(16)) > 0
114
127
  while chunk_size > 0
115
- chunk = socket.read(chunk_size)
128
+ chunk = socket.read(chunk_size) || raise(EOFError)
116
129
  chunk_size -= chunk.bytesize
117
130
  datum[:response][:body] << chunk
118
131
  end
119
132
  new_line_size = 2 # 2 == "\r\n".length
120
133
  while new_line_size > 0
121
- new_line_size -= socket.read(new_line_size).length
134
+ chunk = socket.read(new_line_size) || raise(EOFError)
135
+ new_line_size -= chunk.length
122
136
  end
123
137
  end
124
138
  end
@@ -131,13 +145,13 @@ module Excon
131
145
  if remaining = content_length
132
146
  if response_block
133
147
  while remaining > 0
134
- chunk = socket.read([datum[:chunk_size], remaining].min)
148
+ chunk = socket.read([datum[:chunk_size], remaining].min) || raise(EOFError)
135
149
  response_block.call(chunk, [remaining - chunk.bytesize, 0].max, content_length)
136
150
  remaining -= chunk.bytesize
137
151
  end
138
152
  else
139
153
  while remaining > 0
140
- chunk = socket.read([datum[:chunk_size], remaining].min)
154
+ chunk = socket.read([datum[:chunk_size], remaining].min) || raise(EOFError)
141
155
  datum[:response][:body] << chunk
142
156
  remaining -= chunk.bytesize
143
157
  end
@@ -160,7 +174,7 @@ module Excon
160
174
 
161
175
  def self.parse_headers(socket, datum)
162
176
  last_key = nil
163
- until (data = socket.readline.chomp!).empty?
177
+ until (data = socket.readline.chomp).empty?
164
178
  if !data.lstrip!.nil?
165
179
  raise Excon::Errors::ResponseParseError, 'malformed header' unless last_key
166
180
  # append to last_key's last value
@@ -40,11 +40,12 @@ module Excon
40
40
  when Hash
41
41
  str << '?'
42
42
  datum[:query].sort_by {|k,_| k.to_s }.each do |key, values|
43
+ key = CGI.escape(key.to_s)
43
44
  if values.nil?
44
- str << key.to_s << '&'
45
+ str << key << '&'
45
46
  else
46
47
  [values].flatten.each do |value|
47
- str << key.to_s << '=' << CGI.escape(value.to_s) << '&'
48
+ str << key << '=' << CGI.escape(value.to_s) << '&'
48
49
  end
49
50
  end
50
51
  end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  Shindo.tests('Excon basics') do
2
4
  with_rackup('basic.ru') do
3
5
  basic_tests
@@ -246,6 +248,19 @@ Shindo.tests('Excon basics (Unix socket)') do
246
248
  response[:status]
247
249
  end
248
250
  end
251
+
252
+ tests('http Host header is empty') do
253
+ tests('GET /headers').returns("") do
254
+ connection = Excon::Connection.new({
255
+ :socket => file_name,
256
+ :nonblock => false,
257
+ :scheme => 'unix',
258
+ :ssl_verify_peer => false
259
+ })
260
+ response = connection.request(:method => :get, :path => '/headers')
261
+ JSON.parse(response.body)['HTTP_HOST']
262
+ end
263
+ end
249
264
  end
250
265
  end
251
266
 
@@ -0,0 +1,34 @@
1
+ Shindo.tests("Excon redirecting with cookie preserved") do
2
+ env_init
3
+
4
+ with_rackup('redirecting_with_cookie.ru') do
5
+ tests('second request will send cookies set by the first').returns('ok') do
6
+ Excon.get(
7
+ 'http://127.0.0.1:9292',
8
+ :path => '/sets_cookie',
9
+ :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower]
10
+ ).body
11
+ end
12
+
13
+ tests('second request will send multiple cookies set by the first').returns('ok') do
14
+ Excon.get(
15
+ 'http://127.0.0.1:9292',
16
+ :path => '/sets_multi_cookie',
17
+ :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower]
18
+ ).body
19
+ end
20
+ end
21
+
22
+ with_rackup('redirecting.ru') do
23
+ tests("runs normally when there are no cookies set").returns('ok') do
24
+ Excon.post(
25
+ 'http://127.0.0.1:9292',
26
+ :path => '/first',
27
+ :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower],
28
+ :body => "a=Some_content"
29
+ ).body
30
+ end
31
+ end
32
+
33
+ env_restore
34
+ end
@@ -29,6 +29,11 @@ Shindo.tests('Excon Decompress Middleware') do
29
29
  tests('removes processed encoding from header').returns('') do
30
30
  resp[:headers]['Content-Encoding']
31
31
  end
32
+
33
+ tests('empty response body').returns('') do
34
+ resp = @connection.request(:body => '')
35
+ resp[:body]
36
+ end
32
37
  end
33
38
 
34
39
  tests('deflate') do
@@ -52,12 +52,12 @@ Shindo.tests('Excon query string variants') do
52
52
  response = connection.request(:method => :get, :path => '/query', :query => {'foo[]' => ['bar', 'baz'], :me => 'too'})
53
53
  query_string = response.body[7..-1] # query string sent
54
54
 
55
- test("query string sent includes 'foo[]=bar'") do
56
- query_string.split('&').include?('foo[]=bar')
55
+ test("query string sent includes 'foo%5B%5D=bar'") do
56
+ query_string.split('&').include?('foo%5B%5D=bar')
57
57
  end
58
58
 
59
- test("query string sent includes 'foo[]=baz'") do
60
- query_string.split('&').include?('foo[]=baz')
59
+ test("query string sent includes 'foo%5B%5D=baz'") do
60
+ query_string.split('&').include?('foo%5B%5D=baz')
61
61
  end
62
62
 
63
63
  test("query string sent includes 'me=too'") do
@@ -65,5 +65,23 @@ Shindo.tests('Excon query string variants') do
65
65
  end
66
66
  end
67
67
 
68
+ tests(":query => {'foo%=#' => 'bar%=#'}") do
69
+ response = connection.request(:method => :get, :path => '/query', :query => {'foo%=#' => 'bar%=#'})
70
+ query_string = response.body[7..-1] # query string sent
71
+
72
+ tests("query string sent").returns('foo%25%3D%23=bar%25%3D%23') do
73
+ query_string
74
+ end
75
+ end
76
+
77
+ tests(":query => {'foo%=#' => nil}") do
78
+ response = connection.request(:method => :get, :path => '/query', :query => {'foo%=#' => nil})
79
+ query_string = response.body[7..-1] # query string sent
80
+
81
+ tests("query string sent").returns('foo%25%3D%23') do
82
+ query_string
83
+ end
84
+ end
85
+
68
86
  end
69
87
  end
@@ -1,4 +1,5 @@
1
1
  require 'sinatra'
2
+ require 'json'
2
3
  require File.join(File.dirname(__FILE__), 'webrick_patch')
3
4
 
4
5
  class Basic < Sinatra::Base
@@ -10,6 +11,11 @@ class Basic < Sinatra::Base
10
11
  'x' * value.to_i
11
12
  end
12
13
 
14
+ get('/headers') do
15
+ content_type :json
16
+ request.env.select{|key, _| key.start_with? 'HTTP_'}.to_json
17
+ end
18
+
13
19
  post('/body-sink') do
14
20
  request.body.read.size.to_s
15
21
  end
@@ -0,0 +1,40 @@
1
+ require 'sinatra'
2
+ require 'sinatra/cookies'
3
+ require 'json'
4
+ require File.join(File.dirname(__FILE__), 'webrick_patch')
5
+
6
+ class App < Sinatra::Base
7
+ helpers Sinatra::Cookies
8
+ set :environment, :production
9
+ enable :dump_errors
10
+
11
+ get('/sets_cookie') do
12
+ cookies[:chocolatechip] = "chunky"
13
+ redirect "/requires_cookie"
14
+ end
15
+
16
+ get('/requires_cookie') do
17
+ cookie = cookies[:chocolatechip]
18
+ unless cookie.nil? || cookie != "chunky"
19
+ "ok"
20
+ else
21
+ JSON.pretty_generate(headers)
22
+ end
23
+ end
24
+
25
+ get('/sets_multi_cookie') do
26
+ cookies[:chocolatechip] = "chunky"
27
+ cookies[:thinmints] = "minty"
28
+ redirect "/requires_cookie"
29
+ end
30
+
31
+ get('/requires_cookie') do
32
+ if cookies[:chocolatechip] == "chunky" && cookies[:thinmints] == "minty"
33
+ "ok"
34
+ else
35
+ JSON.pretty_generate(headers)
36
+ end
37
+ end
38
+ end
39
+
40
+ run App