em-http-request 1.1.5 → 1.1.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 028b29e92d03433bc4550ef3b8a5372de4f02221
4
- data.tar.gz: 65ebbad4d0b0dcde83a9147b0f2682341fb36e3f
2
+ SHA256:
3
+ metadata.gz: c7562e4d20c35c54a9319250e665a883c810546cb657d8f897591e113999ed3a
4
+ data.tar.gz: 643bf26ea7bfa2a85d6e4257a475295c16eca044ff0d04341537793f07d5bd04
5
5
  SHA512:
6
- metadata.gz: 33d8a20eff08d9a80173de52e46df0cb6507b6f9a275fd5bfe276a6ea58630b0f47591cb56fdd19967c3081cf65908fcf33b26bdc5e230b62c98cfdbb5f61675
7
- data.tar.gz: 2ec7763bcc9ef1e9594838f7a24657c7c6ffe933e898654fc4da6b72e12a9c1b78058d21a11dc8462a002f8649ca7dbdc732295681f041721ede2312547cfac7
6
+ metadata.gz: 9d9d1f441081a034f29447cb99b26c73ef7767a989c31df007e1ecfc6ea8db1f4cbe00fb26c0f81b59108f53b54dfbccd51ea9140fd5286efaca6095b45943ad
7
+ data.tar.gz: 690dc944373085313c41c484edd25366036a3da46855b6a153aab65c19aed961d94d202de07cb3f5f8406585bb5c3a66998913debcba4d02796fb1c8f04be7c1
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EM-HTTP-Request
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/em-http-request.png)](http://rubygems.org/gems/em-http-request) [![Build Status](https://travis-ci.org/igrigorik/em-http-request.svg)](https://travis-ci.org/igrigorik/em-http-request)
3
+ [![Gem Version](https://badge.fury.io/rb/em-http-request.svg)](http://rubygems.org/gems/em-http-request) [![Build Status](https://travis-ci.org/igrigorik/em-http-request.svg)](https://travis-ci.org/igrigorik/em-http-request)
4
4
 
5
5
  Async (EventMachine) HTTP client, with support for:
6
6
 
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_development_dependency 'mongrel', '~> 1.2.0.pre2'
25
25
  s.add_development_dependency 'multi_json'
26
- s.add_development_dependency 'rack'
26
+ s.add_development_dependency 'rack', '< 2.0'
27
27
  s.add_development_dependency 'rake'
28
28
  s.add_development_dependency 'rspec'
29
29
 
@@ -0,0 +1,49 @@
1
+ require 'em/streamer'
2
+
3
+ # similar to EventMachine::FileStreamer, but for any IO object
4
+ module EventMachine
5
+ class IOStreamer
6
+ include Deferrable
7
+ CHUNK_SIZE = 16384
8
+
9
+ # @param [EventMachine::Connection] connection
10
+ # @param [IO] io Data source
11
+ # @param [Integer] Data size
12
+ #
13
+ # @option opts [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics.
14
+ def initialize(connection, io, opts = {})
15
+ @connection = connection
16
+ @io = io
17
+ @http_chunks = opts[:http_chunks]
18
+
19
+ @buff = String.new
20
+ @io.binmode if @io.respond_to?(:binmode)
21
+ stream_one_chunk
22
+ end
23
+
24
+ private
25
+
26
+ # Used internally to stream one chunk at a time over multiple reactor ticks
27
+ # @private
28
+ def stream_one_chunk
29
+ loop do
30
+ if @io.eof?
31
+ @connection.send_data "0\r\n\r\n" if @http_chunks
32
+ succeed
33
+ break
34
+ end
35
+
36
+ if @connection.respond_to?(:get_outbound_data_size) && (@connection.get_outbound_data_size > FileStreamer::BackpressureLevel)
37
+ EventMachine::next_tick { stream_one_chunk }
38
+ break
39
+ end
40
+
41
+ if @io.read(CHUNK_SIZE, @buff)
42
+ @connection.send_data("#{@buff.length.to_s(16)}\r\n") if @http_chunks
43
+ @connection.send_data(@buff)
44
+ @connection.send_data("\r\n") if @http_chunks
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -182,7 +182,7 @@ module EventMachine
182
182
  # Set the Content-Length if body is given,
183
183
  # or we're doing an empty post or put
184
184
  if body
185
- head['content-length'] = body.bytesize
185
+ head['content-length'] ||= body.respond_to?(:bytesize) ? body.bytesize : body.size
186
186
  elsif @req.method == 'POST' or @req.method == 'PUT'
187
187
  # wont happen if body is set and we already set content-length above
188
188
  head['content-length'] ||= 0
@@ -198,11 +198,8 @@ module EventMachine
198
198
  request_header << CRLF
199
199
  @conn.send_data request_header
200
200
 
201
- if body
202
- @conn.send_data body
203
- elsif @req.file
204
- @conn.stream_file_data @req.file, :http_chunks => false
205
- end
201
+ @req_body = body || (@req.file && Pathname.new(@req.file))
202
+ send_request_body unless @req.headers['expect'] == '100-continue'
206
203
  end
207
204
 
208
205
  def on_body_data(data)
@@ -226,6 +223,28 @@ module EventMachine
226
223
  end
227
224
  end
228
225
 
226
+ def request_body_pending?
227
+ !!@req_body
228
+ end
229
+
230
+ def send_request_body
231
+ return if @req_body.nil?
232
+
233
+ if @req_body.is_a?(String)
234
+ @conn.send_data @req_body
235
+
236
+ elsif @req_body.is_a?(Pathname)
237
+ @conn.stream_file_data @req_body.to_path, http_chunks: false
238
+
239
+ elsif @req_body.respond_to?(:read) && @req_body.respond_to?(:eof?) # IO or IO-like object
240
+ @conn.stream_data @req_body
241
+
242
+ else
243
+ raise "Don't know how to send request body: #{@req_body.inspect}"
244
+ end
245
+ @req_body = nil
246
+ end
247
+
229
248
  def parse_response_header(header, version, status)
230
249
  @response_header.raw = header
231
250
  header.each do |key, val|
@@ -34,7 +34,7 @@ class HttpClientOptions
34
34
 
35
35
  @uri = uri
36
36
  @path = uri.path
37
- @host = uri.host
37
+ @host = uri.hostname
38
38
  @port = uri.port
39
39
  @query = query
40
40
 
@@ -1,3 +1,5 @@
1
+ require 'em/io_streamer'
2
+
1
3
  module EventMachine
2
4
 
3
5
  module HTTPMethods
@@ -20,7 +22,11 @@ module EventMachine
20
22
  end
21
23
 
22
24
  def receive_data(data)
23
- @parent.receive_data data
25
+ begin
26
+ @parent.receive_data data
27
+ rescue EventMachine::Connectify::CONNECTError => e
28
+ @parent.close(e.message)
29
+ end
24
30
  end
25
31
 
26
32
  def connection_completed
@@ -30,6 +36,62 @@ module EventMachine
30
36
  def unbind(reason=nil)
31
37
  @parent.unbind(reason)
32
38
  end
39
+
40
+ # TLS verification support, original implementation by Mislav Marohnić
41
+ # https://github.com/lostisland/faraday/blob/63cf47c95b573539f047c729bd9ad67560bc83ff/lib/faraday/adapter/em_http_ssl_patch.rb
42
+ def ssl_verify_peer(cert_string)
43
+ cert = nil
44
+ begin
45
+ cert = OpenSSL::X509::Certificate.new(cert_string)
46
+ rescue OpenSSL::X509::CertificateError
47
+ return false
48
+ end
49
+
50
+ @last_seen_cert = cert
51
+
52
+ if certificate_store.verify(@last_seen_cert)
53
+ begin
54
+ certificate_store.add_cert(@last_seen_cert)
55
+ rescue OpenSSL::X509::StoreError => e
56
+ raise e unless e.message == 'cert already in hash table'
57
+ end
58
+ true
59
+ else
60
+ raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}"))
61
+ end
62
+ end
63
+
64
+ def ssl_handshake_completed
65
+ unless verify_peer?
66
+ warn "[WARNING; em-http-request] TLS hostname validation is disabled (use 'tls: {verify_peer: true}'), see" +
67
+ " CVE-2020-13482 and https://github.com/igrigorik/em-http-request/issues/339 for details" unless parent.connopts.tls.has_key?(:verify_peer)
68
+ return true
69
+ end
70
+
71
+ unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host)
72
+ raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate))
73
+ else
74
+ true
75
+ end
76
+ end
77
+
78
+ def verify_peer?
79
+ parent.connopts.tls[:verify_peer]
80
+ end
81
+
82
+ def host
83
+ parent.connopts.host
84
+ end
85
+
86
+ def certificate_store
87
+ @certificate_store ||= begin
88
+ store = OpenSSL::X509::Store.new
89
+ store.set_default_paths
90
+ ca_file = parent.connopts.tls[:cert_chain_file]
91
+ store.add_file(ca_file) if ca_file
92
+ store
93
+ end
94
+ end
33
95
  end
34
96
 
35
97
  class HttpConnection
@@ -115,8 +177,13 @@ module EventMachine
115
177
  @p.header_value_type = :mixed
116
178
  @p.on_headers_complete = proc do |h|
117
179
  if client
118
- client.parse_response_header(h, @p.http_version, @p.status_code)
119
- :reset if client.req.no_body?
180
+ if @p.status_code == 100
181
+ client.send_request_body
182
+ @p.reset!
183
+ else
184
+ client.parse_response_header(h, @p.http_version, @p.status_code)
185
+ :reset if client.req.no_body?
186
+ end
120
187
  else
121
188
  # if we receive unexpected data without a pending client request
122
189
  # reset the parser to avoid firing any further callbacks and close
@@ -160,9 +227,9 @@ module EventMachine
160
227
  @peer = @conn.get_peername
161
228
 
162
229
  if @connopts.socks_proxy?
163
- socksify(client.req.uri.host, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
230
+ socksify(client.req.uri.hostname, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
164
231
  elsif @connopts.connect_proxy?
165
- connectify(client.req.uri.host, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
232
+ connectify(client.req.uri.hostname, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
166
233
  else
167
234
  start
168
235
  end
@@ -185,6 +252,7 @@ module EventMachine
185
252
  conn = HttpConnection.new
186
253
  client.conn = conn
187
254
  conn.connopts = @connopts
255
+ conn.connopts.https = new_location.scheme == "https"
188
256
  conn.uri = client.req.uri
189
257
  conn.activate_connection(client)
190
258
 
@@ -240,6 +308,10 @@ module EventMachine
240
308
  @conn.stream_file_data filename, args
241
309
  end
242
310
 
311
+ def stream_data(io, opts = {})
312
+ EventMachine::IOStreamer.new(self, io, opts)
313
+ end
314
+
243
315
  private
244
316
 
245
317
  def client
@@ -1,13 +1,13 @@
1
1
  class HttpConnectionOptions
2
2
  attr_reader :host, :port, :tls, :proxy, :bind, :bind_port
3
3
  attr_reader :connect_timeout, :inactivity_timeout
4
+ attr_writer :https
4
5
 
5
6
  def initialize(uri, options)
6
7
  @connect_timeout = options[:connect_timeout] || 5 # default connection setup timeout
7
8
  @inactivity_timeout = options[:inactivity_timeout] ||= 10 # default connection inactivity (post-setup) timeout
8
9
 
9
10
  @tls = options[:tls] || options[:ssl] || {}
10
- @proxy = options[:proxy]
11
11
 
12
12
  if bind = options[:bind]
13
13
  @bind = bind[:host] || '0.0.0.0'
@@ -20,13 +20,15 @@ class HttpConnectionOptions
20
20
  uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
21
21
  @https = uri.scheme == "https"
22
22
  uri.port ||= (@https ? 443 : 80)
23
- @tls[:sni_hostname] = uri.host
23
+ @tls[:sni_hostname] = uri.hostname
24
24
 
25
- if proxy = options[:proxy]
25
+ @proxy = options[:proxy] || proxy_from_env
26
+
27
+ if proxy
26
28
  @host = proxy[:host]
27
29
  @port = proxy[:port]
28
30
  else
29
- @host = uri.host
31
+ @host = uri.hostname
30
32
  @port = uri.port
31
33
  end
32
34
  end
@@ -42,4 +44,27 @@ class HttpConnectionOptions
42
44
  def socks_proxy?
43
45
  @proxy && (@proxy[:type] == :socks5)
44
46
  end
47
+
48
+ def proxy_from_env
49
+ # TODO: Add support for $http_no_proxy or $no_proxy ?
50
+ proxy_str = if @https
51
+ ENV['HTTPS_PROXY'] || ENV['https_proxy']
52
+ else
53
+ ENV['HTTP_PROXY'] || ENV['http_proxy']
54
+
55
+ # Fall-back to $ALL_PROXY if none of the above env-vars have values
56
+ end || ENV['ALL_PROXY']
57
+
58
+ # Addressable::URI::parse will return `nil` if given `nil` and an empty URL for an empty string
59
+ # so, let's short-circuit that:
60
+ return if !proxy_str || proxy_str.empty?
61
+
62
+ proxy_env_uri = Addressable::URI::parse(proxy_str)
63
+ { :host => proxy_env_uri.host, :port => proxy_env_uri.port, :type => :http }
64
+
65
+ rescue Addressable::URI::InvalidURIError
66
+ # An invalid env-var shouldn't crash the config step, IMHO.
67
+ # We should somehow log / warn about this, perhaps...
68
+ return
69
+ end
45
70
  end
@@ -1,5 +1,5 @@
1
1
  module EventMachine
2
2
  class HttpRequest
3
- VERSION = "1.1.5"
3
+ VERSION = "1.1.7"
4
4
  end
5
5
  end
data/lib/em-http.rb CHANGED
@@ -5,6 +5,7 @@ require 'http/parser'
5
5
 
6
6
  require 'base64'
7
7
  require 'socket'
8
+ require 'openssl'
8
9
 
9
10
  require 'em-http/core_ext/bytesize'
10
11
  require 'em-http/http_connection'
data/spec/client_spec.rb CHANGED
@@ -256,6 +256,19 @@ describe EventMachine::HttpRequest do
256
256
  }
257
257
  end
258
258
 
259
+ xit "should support expect-continue header" do
260
+ EventMachine.run {
261
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090').post :body => "data", :head => { 'expect' => '100-continue' }
262
+
263
+ http.errback { failed(http) }
264
+ http.callback {
265
+ http.response_header.status.should == 200
266
+ http.response.should == "data"
267
+ EventMachine.stop
268
+ }
269
+ }
270
+ end
271
+
259
272
  it "should perform successful GET with custom header" do
260
273
  EventMachine.run {
261
274
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get :head => {'if-none-match' => 'evar!'}
@@ -789,6 +802,28 @@ describe EventMachine::HttpRequest do
789
802
  }
790
803
  end
791
804
 
805
+ it "streams POST request from disk via Pathname" do
806
+ EventMachine.run {
807
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => Pathname.new('spec/fixtures/google.ca')
808
+ http.errback { failed(http) }
809
+ http.callback {
810
+ http.response.should match('google')
811
+ EventMachine.stop
812
+ }
813
+ }
814
+ end
815
+
816
+ it "streams POST request from IO object" do
817
+ EventMachine.run {
818
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => StringIO.new(File.read('spec/fixtures/google.ca'))
819
+ http.errback { failed(http) }
820
+ http.callback {
821
+ http.response.should match('google')
822
+ EventMachine.stop
823
+ }
824
+ }
825
+ end
826
+
792
827
  it "should reconnect if connection was closed between requests" do
793
828
  EventMachine.run {
794
829
  conn = EM::HttpRequest.new('http://127.0.0.1:8090/')
@@ -942,4 +977,24 @@ describe EventMachine::HttpRequest do
942
977
  }
943
978
  end
944
979
  end
980
+
981
+ context "IPv6" do
982
+ it "should perform successful GET" do
983
+ EventMachine.run {
984
+ @s = StubServer.new({
985
+ response: "HTTP/1.1 200 OK\r\n\r\nHello IPv6",
986
+ port: 8091,
987
+ host: '::1',
988
+ })
989
+ http = EventMachine::HttpRequest.new('http://[::1]:8091/').get
990
+
991
+ http.errback { failed(http) }
992
+ http.callback {
993
+ http.response_header.status.should == 200
994
+ http.response.should match(/Hello IPv6/)
995
+ EventMachine.stop
996
+ }
997
+ }
998
+ end
999
+ end
945
1000
  end
data/spec/dns_spec.rb CHANGED
@@ -7,7 +7,7 @@ describe EventMachine::HttpRequest do
7
7
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/redirect/badhost', :connect_timeout => 0.1).get :redirects => 1
8
8
  http.callback { failed(http) }
9
9
  http.errback {
10
- http.error.should match('unable to resolve server address')
10
+ http.error.should match(/unable to resolve (server |)address/)
11
11
  EventMachine.stop
12
12
  }
13
13
  }
@@ -31,7 +31,7 @@ describe EventMachine::HttpRequest do
31
31
  http = EventMachine::HttpRequest.new('http://somethinglocal/', :connect_timeout => 0.1).get
32
32
  http.callback { failed(http) }
33
33
  http.errback {
34
- http.error.should match(/unable to resolve server address/)
34
+ http.error.should match(/unable to resolve (server |)address/)
35
35
  http.response_header.status.should == 0
36
36
  EventMachine.stop
37
37
  }
@@ -1,107 +1,268 @@
1
1
  require 'helper'
2
2
 
3
+ shared_examples "*_PROXY var (through proxy)" do
4
+ it "should use HTTP proxy" do
5
+ EventMachine.run {
6
+ http = EventMachine::HttpRequest.new("#{proxy_test_scheme}://127.0.0.1:8090/?q=test").get
7
+
8
+ http.errback { failed(http) }
9
+ http.callback {
10
+ http.response_header.status.should == 200
11
+ http.response_header.should_not include("X_PROXY_AUTH")
12
+ http.response.should match('test')
13
+ EventMachine.stop
14
+ }
15
+ }
16
+ end
17
+ end
18
+
19
+ shared_examples "*_PROXY var (testing var)" do
20
+ subject { HttpConnectionOptions.new("#{proxy_test_scheme}://example.com", {}) }
21
+ it { expect(subject.proxy_from_env).to eq({ :host => "127.0.0.1", :port => 8083, :type => :http }) }
22
+ it { expect(subject.host).to eq "127.0.0.1" }
23
+ it { expect(subject.port).to be 8083 }
24
+ it do
25
+ case proxy_test_scheme.to_sym
26
+ when :http
27
+ expect(subject.http_proxy?).to be_truthy
28
+ when :https
29
+ expect(subject.connect_proxy?).to be_truthy
30
+ end
31
+ end
32
+ end
33
+
3
34
  describe EventMachine::HttpRequest do
4
35
 
5
36
  context "connections via" do
6
- let(:proxy) { {:proxy => { :host => '127.0.0.1', :port => 8083 }} }
7
- let(:authenticated_proxy) { {:proxy => { :host => '127.0.0.1', :port => 8083, :authorization => ["user", "name"] } } }
8
-
9
- it "should use HTTP proxy" do
10
- EventMachine.run {
11
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/?q=test', proxy).get
12
-
13
- http.errback { failed(http) }
14
- http.callback {
15
- http.response_header.status.should == 200
16
- http.response_header.should_not include("X_PROXY_AUTH")
17
- http.response.should match('test')
18
- EventMachine.stop
37
+ context "without *_PROXY env" do
38
+ let(:proxy) { {:proxy => { :host => '127.0.0.1', :port => 8083 }} }
39
+ let(:authenticated_proxy) { {:proxy => { :host => '127.0.0.1', :port => 8083, :authorization => ["user", "name"] } } }
40
+
41
+ it "should use HTTP proxy" do
42
+ EventMachine.run {
43
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/?q=test', proxy).get
44
+
45
+ http.errback { failed(http) }
46
+ http.callback {
47
+ http.response_header.status.should == 200
48
+ http.response_header.should_not include("X_PROXY_AUTH")
49
+ http.response.should match('test')
50
+ EventMachine.stop
51
+ }
19
52
  }
20
- }
21
- end
53
+ end
22
54
 
23
- it "should use HTTP proxy with authentication" do
24
- EventMachine.run {
25
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/proxyauth?q=test', authenticated_proxy).get
55
+ it "should use HTTP proxy with authentication" do
56
+ EventMachine.run {
57
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/proxyauth?q=test', authenticated_proxy).get
26
58
 
27
- http.errback { failed(http) }
28
- http.callback {
29
- http.response_header.status.should == 200
30
- http.response_header['X_PROXY_AUTH'].should == "Proxy-Authorization: Basic dXNlcjpuYW1l"
31
- http.response.should match('test')
32
- EventMachine.stop
59
+ http.errback { failed(http) }
60
+ http.callback {
61
+ http.response_header.status.should == 200
62
+ http.response_header['X_PROXY_AUTH'].should == "Proxy-Authorization: Basic dXNlcjpuYW1l"
63
+ http.response.should match('test')
64
+ EventMachine.stop
65
+ }
33
66
  }
34
- }
35
- end
67
+ end
36
68
 
37
- it "should send absolute URIs to the proxy server" do
38
- EventMachine.run {
69
+ it "should send absolute URIs to the proxy server" do
70
+ EventMachine.run {
39
71
 
40
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/?q=test', proxy).get
72
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/?q=test', proxy).get
41
73
 
42
- http.errback { failed(http) }
43
- http.callback {
44
- http.response_header.status.should == 200
74
+ http.errback { failed(http) }
75
+ http.callback {
76
+ http.response_header.status.should == 200
45
77
 
46
- # The test proxy server gives the requested uri back in this header
47
- http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/?q=test'
48
- http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test'
49
- http.response.should match('test')
50
- EventMachine.stop
78
+ # The test proxy server gives the requested uri back in this header
79
+ http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/?q=test'
80
+ http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test'
81
+ http.response.should match('test')
82
+ EventMachine.stop
83
+ }
51
84
  }
52
- }
53
- end
85
+ end
54
86
 
55
- it "should strip basic auth from before the host in URI sent to proxy" do
56
- EventMachine.run {
87
+ it "should strip basic auth from before the host in URI sent to proxy" do
88
+ EventMachine.run {
57
89
 
58
- http = EventMachine::HttpRequest.new('http://user:pass@127.0.0.1:8090/echo_authorization_header', proxy).get
90
+ http = EventMachine::HttpRequest.new('http://user:pass@127.0.0.1:8090/echo_authorization_header', proxy).get
59
91
 
60
- http.errback { failed(http) }
61
- http.callback {
62
- http.response_header.status.should == 200
63
- # The test proxy server gives the requested uri back in this header
64
- http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/echo_authorization_header'
65
- # Ensure the basic auth was converted to a header correctly
66
- http.response.should match('authorization:Basic dXNlcjpwYXNz')
67
- EventMachine.stop
92
+ http.errback { failed(http) }
93
+ http.callback {
94
+ http.response_header.status.should == 200
95
+ # The test proxy server gives the requested uri back in this header
96
+ http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/echo_authorization_header'
97
+ # Ensure the basic auth was converted to a header correctly
98
+ http.response.should match('authorization:Basic dXNlcjpwYXNz')
99
+ EventMachine.stop
100
+ }
68
101
  }
69
- }
70
- end
102
+ end
71
103
 
72
- it "should include query parameters specified in the options" do
73
- EventMachine.run {
74
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/', proxy).get :query => { 'q' => 'test' }
104
+ it "should include query parameters specified in the options" do
105
+ EventMachine.run {
106
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/', proxy).get :query => { 'q' => 'test' }
75
107
 
76
- http.errback { failed(http) }
77
- http.callback {
78
- http.response_header.status.should == 200
79
- http.response.should match('test')
80
- EventMachine.stop
108
+ http.errback { failed(http) }
109
+ http.callback {
110
+ http.response_header.status.should == 200
111
+ http.response.should match('test')
112
+ EventMachine.stop
113
+ }
81
114
  }
82
- }
83
- end
115
+ end
84
116
 
85
- it "should use HTTP proxy while redirecting" do
86
- EventMachine.run {
87
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/redirect', proxy).get :redirects => 1
117
+ it "should use HTTP proxy while redirecting" do
118
+ EventMachine.run {
119
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/redirect', proxy).get :redirects => 1
88
120
 
89
- http.errback { failed(http) }
90
- http.callback {
91
- http.response_header.status.should == 200
121
+ http.errback { failed(http) }
122
+ http.callback {
123
+ http.response_header.status.should == 200
92
124
 
93
- http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/gzip'
94
- http.response_header['X_THE_REQUESTED_URI'].should_not == '/redirect'
125
+ http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8090/gzip'
126
+ http.response_header['X_THE_REQUESTED_URI'].should_not == '/redirect'
95
127
 
96
- http.response_header["CONTENT_ENCODING"].should == "gzip"
97
- http.response.should == "compressed"
98
- http.last_effective_url.to_s.should == 'http://127.0.0.1:8090/gzip'
99
- http.redirects.should == 1
128
+ http.response_header["CONTENT_ENCODING"].should == "gzip"
129
+ http.response.should == "compressed"
130
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8090/gzip'
131
+ http.redirects.should == 1
100
132
 
101
- EventMachine.stop
133
+ EventMachine.stop
134
+ }
102
135
  }
103
- }
136
+ end
137
+ end
138
+
139
+ context "when parsing *_PROXY var (through proxy)s" do
140
+ context "with $HTTP_PROXY env" do
141
+ let(:proxy_test_scheme) { :http }
142
+
143
+ before(:all) do
144
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
145
+ ENV['HTTP_PROXY'] = 'http://127.0.0.1:8083'
146
+ end
147
+
148
+ include_examples "*_PROXY var (through proxy)"
149
+ end
150
+
151
+ context "with $http_proxy env" do
152
+ let(:proxy_test_scheme) { :http }
153
+
154
+ before(:all) do
155
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
156
+ ENV['http_proxy'] = 'http://127.0.0.1:8083'
157
+ end
158
+
159
+ include_examples "*_PROXY var (through proxy)"
160
+ end
161
+
162
+ ## TODO: Use a Mongrel HTTP server that can handle SSL:
163
+ context "with $HTTPS_PROXY env", skip: "Mongrel isn't configured to handle HTTPS, currently" do
164
+ let(:proxy_test_scheme) { :https }
165
+
166
+ before(:all) do
167
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
168
+ ENV['HTTPS_PROXY'] = 'http://127.0.0.1:8083'
169
+ end
170
+
171
+ include_examples "*_PROXY var (through proxy)"
172
+ end
173
+
174
+ ## TODO: Use a Mongrel HTTP server that can handle SSL:
175
+ context "with $https_proxy env", skip: "Mongrel isn't configured to handle HTTPS, currently" do
176
+ let(:proxy_test_scheme) { :https }
177
+
178
+ before(:all) do
179
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
180
+ ENV['https_proxy'] = 'http://127.0.0.1:8083'
181
+ end
182
+
183
+ include_examples "*_PROXY var (through proxy)"
184
+ end
185
+
186
+ context "with $ALL_PROXY env" do
187
+ let(:proxy_test_scheme) { :http }
188
+
189
+ before(:all) do
190
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
191
+ ENV['ALL_PROXY'] = 'http://127.0.0.1:8083'
192
+ end
193
+
194
+ include_examples "*_PROXY var (through proxy)"
195
+ end
104
196
  end
105
197
  end
106
198
 
199
+ context "when parsing *_PROXY vars" do
200
+ context "without a *_PROXY var" do
201
+ before(:all) do
202
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
203
+ end
204
+
205
+ subject { HttpConnectionOptions.new("http://example.com", {}) }
206
+ it { expect(subject.proxy_from_env).to be_nil }
207
+ it { expect(subject.host).to eq "example.com" }
208
+ it { expect(subject.port).to be 80 }
209
+ it { expect(subject.http_proxy?).to be_falsey }
210
+ it { expect(subject.connect_proxy?).to be_falsey }
211
+ end
212
+
213
+ context "with $HTTP_PROXY env" do
214
+ let(:proxy_test_scheme) { :http }
215
+
216
+ before(:each) do
217
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
218
+ ENV['HTTP_PROXY'] = 'http://127.0.0.1:8083'
219
+ end
220
+
221
+ include_examples "*_PROXY var (testing var)"
222
+ end
223
+
224
+ context "with $http_proxy env" do
225
+ let(:proxy_test_scheme) { :http }
226
+
227
+ before(:each) do
228
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
229
+ ENV['http_proxy'] = 'http://127.0.0.1:8083'
230
+ end
231
+
232
+ include_examples "*_PROXY var (testing var)"
233
+ end
234
+
235
+ context "with $HTTPS_PROXY env" do
236
+ let(:proxy_test_scheme) { :https }
237
+
238
+ before(:each) do
239
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
240
+ ENV['HTTPS_PROXY'] = 'http://127.0.0.1:8083'
241
+ end
242
+
243
+ include_examples "*_PROXY var (testing var)"
244
+ end
245
+
246
+ context "with $https_proxy env" do
247
+ let(:proxy_test_scheme) { :https }
248
+
249
+ before(:each) do
250
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
251
+ ENV['https_proxy'] = 'http://127.0.0.1:8083'
252
+ end
253
+
254
+ include_examples "*_PROXY var (testing var)"
255
+ end
256
+
257
+ context "with $ALL_PROXY env" do
258
+ let(:proxy_test_scheme) { :https }
259
+
260
+ before(:each) do
261
+ PROXY_ENV_VARS.each {|k| ENV.delete k }
262
+ ENV['ALL_PROXY'] = 'http://127.0.0.1:8083'
263
+ end
264
+
265
+ include_examples "*_PROXY var (testing var)"
266
+ end
267
+ end
107
268
  end
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,25 @@
1
+ PROXY_ENV_VARS = %w[HTTP_PROXY http_proxy HTTPS_PROXY https_proxy ALL_PROXY]
2
+
1
3
  RSpec.configure do |config|
4
+ proxy_envs = {}
5
+
2
6
  config.mock_with :rspec do |c|
3
7
  c.syntax = [:should, :expect]
4
8
  end
5
9
  config.expect_with :rspec do |c|
6
10
  c.syntax = [:should, :expect]
7
11
  end
8
- end
12
+
13
+ config.before :all do
14
+ # Back-up ENV *_PROXY vars
15
+ orig_proxy_envs = Hash[
16
+ PROXY_ENV_VARS.select {|k| ENV.key? k }.map {|k| [k, ENV.delete(k)] }
17
+ ]
18
+ proxy_envs.replace(orig_proxy_envs)
19
+ end
20
+
21
+ config.after :all do
22
+ # Restore ENV *_PROXY vars
23
+ ENV.update(proxy_envs)
24
+ end
25
+ end
data/spec/ssl_spec.rb CHANGED
@@ -3,7 +3,6 @@ require 'helper'
3
3
  requires_connection do
4
4
 
5
5
  describe EventMachine::HttpRequest do
6
-
7
6
  it "should initiate SSL/TLS on HTTPS connections" do
8
7
  EventMachine.run {
9
8
  http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail/').get
@@ -15,6 +14,58 @@ requires_connection do
15
14
  }
16
15
  }
17
16
  end
17
+
18
+ describe "TLS hostname verification" do
19
+ before do
20
+ @cve_warning = "[WARNING; em-http-request] TLS hostname validation is disabled (use 'tls: {verify_peer: true}'), see" +
21
+ " CVE-2020-13482 and https://github.com/igrigorik/em-http-request/issues/339 for details"
22
+ @orig_stderr = $stderr
23
+ $stderr = StringIO.new
24
+ end
25
+
26
+ after do
27
+ $stderr = @orig_stderr
28
+ end
29
+
30
+ it "should not warn if verify_peer is specified" do
31
+ EventMachine.run {
32
+ http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail', {tls: {verify_peer: false}}).get
33
+
34
+ http.callback {
35
+ $stderr.rewind
36
+ $stderr.string.chomp.should_not eq(@cve_warning)
37
+
38
+ EventMachine.stop
39
+ }
40
+ }
41
+ end
42
+
43
+ it "should not warn if verify_peer is true" do
44
+ EventMachine.run {
45
+ http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail', {tls: {verify_peer: true}}).get
46
+
47
+ http.callback {
48
+ $stderr.rewind
49
+ $stderr.string.chomp.should_not eq(@cve_warning)
50
+
51
+ EventMachine.stop
52
+ }
53
+ }
54
+ end
55
+
56
+ it "should warn if verify_peer is unspecified" do
57
+ EventMachine.run {
58
+ http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail').get
59
+
60
+ http.callback {
61
+ $stderr.rewind
62
+ $stderr.string.chomp.should eq(@cve_warning)
63
+
64
+ EventMachine.stop
65
+ }
66
+ }
67
+ end
68
+ end
18
69
  end
19
70
 
20
71
  end
data/spec/stallion.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # #--
2
2
  # Includes portion originally Copyright (C)2008 Michael Fellinger
3
- # license See file LICENSE for details
3
+ # MIT License
4
4
  # #--
5
5
 
6
6
  require 'rack'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-http-request
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Grigorik
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-01 00:00:00.000000000 Z
11
+ date: 2020-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -112,16 +112,16 @@ dependencies:
112
112
  name: rack
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "<"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: '2.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "<"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: '2.0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rake
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +197,7 @@ files:
197
197
  - lib/em-http/multi.rb
198
198
  - lib/em-http/request.rb
199
199
  - lib/em-http/version.rb
200
+ - lib/em/io_streamer.rb
200
201
  - spec/client_fiber_spec.rb
201
202
  - spec/client_spec.rb
202
203
  - spec/digest_auth_spec.rb
@@ -222,7 +223,7 @@ homepage: http://github.com/igrigorik/em-http-request
222
223
  licenses:
223
224
  - MIT
224
225
  metadata: {}
225
- post_install_message:
226
+ post_install_message:
226
227
  rdoc_options: []
227
228
  require_paths:
228
229
  - lib
@@ -237,9 +238,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
238
  - !ruby/object:Gem::Version
238
239
  version: '0'
239
240
  requirements: []
240
- rubyforge_project: em-http-request
241
- rubygems_version: 2.5.1
242
- signing_key:
241
+ rubygems_version: 3.0.3
242
+ signing_key:
243
243
  specification_version: 4
244
244
  summary: EventMachine based, async HTTP Request client
245
245
  test_files:
@@ -264,4 +264,3 @@ test_files:
264
264
  - spec/ssl_spec.rb
265
265
  - spec/stallion.rb
266
266
  - spec/stub_server.rb
267
- has_rdoc: