faye-websocket 0.10.6 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -63,7 +63,7 @@ module Faye
63
63
  end
64
64
 
65
65
  private
66
-
66
+
67
67
  def open
68
68
  return unless @ready_state == WebSocket::API::CONNECTING
69
69
 
@@ -83,9 +83,9 @@ module Faye
83
83
  gsub(/(\r\n|\r|\n)/, '\1data: ')
84
84
 
85
85
  frame = ""
86
- frame << "event: #{options[:event]}\r\n" if options[:event]
87
- frame << "id: #{options[:id]}\r\n" if options[:id]
88
- frame << "data: #{message}\r\n\r\n"
86
+ frame << "event: #{ options[:event] }\r\n" if options[:event]
87
+ frame << "id: #{ options[:id] }\r\n" if options[:id]
88
+ frame << "data: #{ message }\r\n\r\n"
89
89
 
90
90
  @stream.write(frame)
91
91
  true
@@ -79,6 +79,7 @@ module Faye
79
79
  end
80
80
 
81
81
  def write(data)
82
+ return @rack_hijack_io_reader.send_data(data) if @rack_hijack_io_reader
82
83
  return @rack_hijack_io.write(data) if @rack_hijack_io
83
84
  return @stream_send.call(data) if @stream_send
84
85
  rescue => e
@@ -17,9 +17,10 @@ module Faye
17
17
  class WebSocket
18
18
  root = File.expand_path('../websocket', __FILE__)
19
19
 
20
- autoload :Adapter, root + '/adapter'
21
- autoload :API, root + '/api'
22
- autoload :Client, root + '/client'
20
+ autoload :Adapter, root + '/adapter'
21
+ autoload :API, root + '/api'
22
+ autoload :Client, root + '/client'
23
+ autoload :SslVerifier, root + '/ssl_verifier'
23
24
 
24
25
  ADAPTERS = {
25
26
  'goliath' => :Goliath,
@@ -44,7 +45,7 @@ module Faye
44
45
  def self.load_adapter(backend)
45
46
  const = Kernel.const_get(ADAPTERS[backend]) rescue nil
46
47
  require(backend) unless const
47
- path = File.expand_path("../adapters/#{backend}.rb", __FILE__)
48
+ path = File.expand_path("../adapters/#{ backend }.rb", __FILE__)
48
49
  require(path) if File.file?(path)
49
50
  end
50
51
 
@@ -43,7 +43,7 @@ module Faye
43
43
 
44
44
  @driver.on(:open) { |e| open }
45
45
  @driver.on(:message) { |e| receive_message(e.data) }
46
- @driver.on(:close) { |e| begin_close(e.reason, e.code) }
46
+ @driver.on(:close) { |e| begin_close(e.reason, e.code, :wait_for_write => true) }
47
47
 
48
48
  @driver.on(:error) do |error|
49
49
  emit_error(error.message)
@@ -83,13 +83,16 @@ module Faye
83
83
  unless code == 1000 or (code >= 3000 and code <= 4999)
84
84
  raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
85
85
  "The code must be either 1000, or between 3000 and 4999. " +
86
- "#{code} is neither."
86
+ "#{ code } is neither."
87
+ end
88
+
89
+ if @ready_state < CLOSING
90
+ @close_timer = EventMachine.add_timer(CLOSE_TIMEOUT) { begin_close('', 1006) }
87
91
  end
88
92
 
89
93
  @ready_state = CLOSING unless @ready_state == CLOSED
90
- @driver.close(reason, code)
91
94
 
92
- @close_timer = EventMachine.add_timer(CLOSE_TIMEOUT) { begin_close('', 1006) }
95
+ @driver.close(reason, code)
93
96
  end
94
97
 
95
98
  def protocol
@@ -121,13 +124,17 @@ module Faye
121
124
  dispatch_event(event)
122
125
  end
123
126
 
124
- def begin_close(reason, code)
127
+ def begin_close(reason, code, options = {})
125
128
  return if @ready_state == CLOSED
126
129
  @ready_state = CLOSING
127
130
  @close_params = [reason, code]
128
131
 
129
132
  if @stream
130
- @stream.close_connection_after_writing
133
+ if options[:wait_for_write]
134
+ @stream.close_connection_after_writing
135
+ else
136
+ @stream.close_connection
137
+ end
131
138
  else
132
139
  finalize_close
133
140
  end
@@ -10,7 +10,7 @@ module Faye::WebSocket::API
10
10
 
11
11
  def initialize(event_type, options)
12
12
  @type = event_type
13
- options.each { |key, value| instance_variable_set("@#{key}", value) }
13
+ options.each { |key, value| instance_variable_set("@#{ key }", value) }
14
14
  end
15
15
 
16
16
  def init_event(event_type, can_bubble, cancelable)
@@ -5,10 +5,10 @@ module Faye::WebSocket::API
5
5
  events = %w[open message error close]
6
6
 
7
7
  events.each do |event_type|
8
- define_method "on#{event_type}=" do |handler|
8
+ define_method "on#{ event_type }=" do |handler|
9
9
  EventMachine.next_tick do
10
10
  flush(event_type, handler)
11
- instance_variable_set("@on#{event_type}", handler)
11
+ instance_variable_set("@on#{ event_type }", handler)
12
12
  end
13
13
  end
14
14
  end
@@ -7,7 +7,7 @@ module Faye
7
7
  extend Forwardable
8
8
  include API
9
9
 
10
- DEFAULT_PORTS = {'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443}
10
+ DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443 }
11
11
  SECURE_PROTOCOLS = ['https', 'wss']
12
12
 
13
13
  def_delegators :@driver, :headers, :status
@@ -17,58 +17,72 @@ module Faye
17
17
  super(options) { ::WebSocket::Driver.client(self, :max_length => options[:max_length], :protocols => protocols) }
18
18
 
19
19
  proxy = options.fetch(:proxy, {})
20
- endpoint = URI.parse(proxy[:origin] || @url)
21
- port = endpoint.port || DEFAULT_PORTS[endpoint.scheme]
22
- @secure = SECURE_PROTOCOLS.include?(endpoint.scheme)
20
+ @endpoint = URI.parse(proxy[:origin] || @url)
21
+ port = @endpoint.port || DEFAULT_PORTS[@endpoint.scheme]
23
22
  @origin_tls = options.fetch(:tls, {})
24
23
  @socket_tls = proxy[:origin] ? proxy.fetch(:tls, {}) : @origin_tls
25
24
 
26
- if proxy[:origin]
27
- @proxy = @driver.proxy(proxy[:origin])
28
- if headers = proxy[:headers]
29
- headers.each { |name, value| @proxy.set_header(name, value) }
30
- end
25
+ configure_proxy(proxy)
31
26
 
32
- @proxy.on(:connect) do
33
- uri = URI.parse(@url)
34
- secure = SECURE_PROTOCOLS.include?(uri.scheme)
35
- @proxy = nil
27
+ EventMachine.connect(@endpoint.host, port, Connection) do |conn|
28
+ conn.parent = self
29
+ end
30
+ rescue => error
31
+ on_network_error(error)
32
+ end
36
33
 
37
- if secure
38
- origin_tls = {:sni_hostname => uri.host}.merge(@origin_tls)
39
- @stream.start_tls(origin_tls)
40
- end
34
+ private
35
+
36
+ def configure_proxy(proxy)
37
+ return unless proxy[:origin]
41
38
 
42
- @driver.start
43
- end
39
+ @proxy = @driver.proxy(proxy[:origin])
40
+ @proxy.on(:error) { |error| @driver.emit(:error, error) }
44
41
 
45
- @proxy.on(:error) do |error|
46
- @driver.emit(:error, error)
47
- end
42
+ if headers = proxy[:headers]
43
+ headers.each { |name, value| @proxy.set_header(name, value) }
48
44
  end
49
45
 
50
- EventMachine.connect(endpoint.host, port, Connection) do |conn|
51
- conn.parent = self
46
+ @proxy.on(:connect) do
47
+ @proxy = nil
48
+ start_tls(URI.parse(@url), @origin_tls)
49
+ @driver.start
52
50
  end
53
- rescue => error
54
- emit_error("Network error: #{url}: #{error.message}")
55
- finalize_close
56
51
  end
57
52
 
58
- private
53
+ def start_tls(uri, options)
54
+ return unless SECURE_PROTOCOLS.include?(uri.scheme)
55
+
56
+ tls_options = { :sni_hostname => uri.host, :verify_peer => true }.merge(options)
57
+ @ssl_verifier = SslVerifier.new(uri.host, tls_options)
58
+ @stream.start_tls(tls_options)
59
+ end
59
60
 
60
61
  def on_connect(stream)
61
62
  @stream = stream
62
-
63
- if @secure
64
- socket_tls = {:sni_hostname => URI.parse(@url).host}.merge(@socket_tls)
65
- @stream.start_tls(socket_tls)
66
- end
63
+ start_tls(@endpoint, @socket_tls)
67
64
 
68
65
  worker = @proxy || @driver
69
66
  worker.start
70
67
  end
71
68
 
69
+ def on_network_error(error)
70
+ emit_error("Network error: #{ @url }: #{ error.message }")
71
+ finalize_close
72
+ end
73
+
74
+ def ssl_verify_peer(cert)
75
+ @ssl_verifier.ssl_verify_peer(cert)
76
+ rescue => error
77
+ on_network_error(error)
78
+ end
79
+
80
+ def ssl_handshake_completed
81
+ @ssl_verifier.ssl_handshake_completed
82
+ rescue => error
83
+ on_network_error(error)
84
+ end
85
+
72
86
  module Connection
73
87
  attr_accessor :parent
74
88
 
@@ -76,11 +90,20 @@ module Faye
76
90
  parent.__send__(:on_connect, self)
77
91
  end
78
92
 
93
+ def ssl_verify_peer(cert)
94
+ parent.__send__(:ssl_verify_peer, cert)
95
+ end
96
+
97
+ def ssl_handshake_completed
98
+ parent.__send__(:ssl_handshake_completed)
99
+ end
100
+
79
101
  def receive_data(data)
80
102
  parent.__send__(:parse, data)
81
103
  end
82
104
 
83
- def unbind
105
+ def unbind(error = nil)
106
+ parent.__send__(:emit_error, error) if error
84
107
  parent.__send__(:finalize_close)
85
108
  end
86
109
 
@@ -0,0 +1,89 @@
1
+ # This code is based on the implementation in Faraday:
2
+ #
3
+ # https://github.com/lostisland/faraday/blob/v1.0.1/lib/faraday/adapter/em_http_ssl_patch.rb
4
+ #
5
+ # Faraday is published under the MIT license as detailed here:
6
+ #
7
+ # https://github.com/lostisland/faraday/blob/v1.0.1/LICENSE.md
8
+ #
9
+ # Copyright (c) 2009-2019 Rick Olson, Zack Hobson
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ # of this software and associated documentation files (the "Software"), to deal
13
+ # in the Software without restriction, including without limitation the rights
14
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ # copies of the Software, and to permit persons to whom the Software is
16
+ # furnished to do so, subject to the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be included in
19
+ # all copies or substantial portions of the Software.
20
+
21
+ require 'openssl'
22
+
23
+ module Faye
24
+ class WebSocket
25
+
26
+ SSLError = Class.new(OpenSSL::SSL::SSLError)
27
+
28
+ class SslVerifier
29
+ def initialize(hostname, ssl_opts)
30
+ @hostname = hostname
31
+ @ssl_opts = ssl_opts
32
+ @cert_store = OpenSSL::X509::Store.new
33
+
34
+ if root = @ssl_opts[:root_cert_file]
35
+ [root].flatten.each { |ca_path| @cert_store.add_file(ca_path) }
36
+ else
37
+ @cert_store.set_default_paths
38
+ end
39
+ end
40
+
41
+ def ssl_verify_peer(cert_text)
42
+ return true unless should_verify?
43
+
44
+ certificate = parse_cert(cert_text)
45
+ return false unless certificate
46
+
47
+ unless @cert_store.verify(certificate)
48
+ raise SSLError, "Unable to verify the server certificate for '#{ @hostname }'"
49
+ end
50
+
51
+ store_cert(certificate)
52
+ @last_cert = certificate
53
+
54
+ true
55
+ end
56
+
57
+ def ssl_handshake_completed
58
+ return unless should_verify?
59
+
60
+ unless identity_verified?
61
+ raise SSLError, "Host '#{ @hostname }' does not match the server certificate"
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def should_verify?
68
+ @ssl_opts[:verify_peer] != false
69
+ end
70
+
71
+ def parse_cert(cert_text)
72
+ OpenSSL::X509::Certificate.new(cert_text)
73
+ rescue OpenSSL::X509::CertificateError
74
+ nil
75
+ end
76
+
77
+ def store_cert(certificate)
78
+ @cert_store.add_cert(certificate)
79
+ rescue OpenSSL::X509::StoreError => error
80
+ raise error unless error.message == 'cert already in hash table'
81
+ end
82
+
83
+ def identity_verified?
84
+ @last_cert and OpenSSL::SSL.verify_certificate_identity(@last_cert, @hostname)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faye-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.6
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Coglan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-22 00:00:00.000000000 Z
11
+ date: 2021-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: public_suffix
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "<"
74
- - !ruby/object:Gem::Version
75
- version: 1.5.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "<"
81
- - !ruby/object:Gem::Version
82
- version: 1.5.0
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: puma
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -137,45 +123,51 @@ dependencies:
137
123
  - !ruby/object:Gem::Version
138
124
  version: 0.2.0
139
125
  - !ruby/object:Gem::Dependency
140
- name: rainbows
126
+ name: thin
141
127
  requirement: !ruby/object:Gem::Requirement
142
128
  requirements:
143
- - - "~>"
129
+ - - ">="
144
130
  - !ruby/object:Gem::Version
145
- version: 4.4.0
131
+ version: 1.2.0
132
+ - - ">"
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
146
135
  type: :development
147
136
  prerelease: false
148
137
  version_requirements: !ruby/object:Gem::Requirement
149
138
  requirements:
150
- - - "~>"
139
+ - - ">="
151
140
  - !ruby/object:Gem::Version
152
- version: 4.4.0
141
+ version: 1.2.0
142
+ - - ">"
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
153
145
  - !ruby/object:Gem::Dependency
154
- name: thin
146
+ name: rainbows
155
147
  requirement: !ruby/object:Gem::Requirement
156
148
  requirements:
157
- - - ">="
149
+ - - "~>"
158
150
  - !ruby/object:Gem::Version
159
- version: 1.2.0
151
+ version: 4.4.0
160
152
  type: :development
161
153
  prerelease: false
162
154
  version_requirements: !ruby/object:Gem::Requirement
163
155
  requirements:
164
- - - ">="
156
+ - - "~>"
165
157
  - !ruby/object:Gem::Version
166
- version: 1.2.0
158
+ version: 4.4.0
167
159
  - !ruby/object:Gem::Dependency
168
160
  name: goliath
169
161
  requirement: !ruby/object:Gem::Requirement
170
162
  requirements:
171
- - - ">="
163
+ - - ">"
172
164
  - !ruby/object:Gem::Version
173
165
  version: '0'
174
166
  type: :development
175
167
  prerelease: false
176
168
  version_requirements: !ruby/object:Gem::Requirement
177
169
  requirements:
178
- - - ">="
170
+ - - ">"
179
171
  - !ruby/object:Gem::Version
180
172
  version: '0'
181
173
  - !ruby/object:Gem::Dependency
@@ -192,7 +184,7 @@ dependencies:
192
184
  - - ">="
193
185
  - !ruby/object:Gem::Version
194
186
  version: 4.0.0
195
- description:
187
+ description:
196
188
  email: jcoglan@gmail.com
197
189
  executables: []
198
190
  extensions: []
@@ -200,17 +192,8 @@ extra_rdoc_files:
200
192
  - README.md
201
193
  files:
202
194
  - CHANGELOG.md
195
+ - LICENSE.md
203
196
  - README.md
204
- - examples/app.rb
205
- - examples/autobahn_client.rb
206
- - examples/client.rb
207
- - examples/config.ru
208
- - examples/haproxy.conf
209
- - examples/proxy_server.rb
210
- - examples/rainbows.conf
211
- - examples/server.rb
212
- - examples/sse.html
213
- - examples/ws.html
214
197
  - lib/faye/adapters/goliath.rb
215
198
  - lib/faye/adapters/rainbows.rb
216
199
  - lib/faye/adapters/rainbows_client.rb
@@ -223,11 +206,12 @@ files:
223
206
  - lib/faye/websocket/api/event.rb
224
207
  - lib/faye/websocket/api/event_target.rb
225
208
  - lib/faye/websocket/client.rb
209
+ - lib/faye/websocket/ssl_verifier.rb
226
210
  homepage: https://github.com/faye/faye-websocket-ruby
227
211
  licenses:
228
- - MIT
212
+ - Apache-2.0
229
213
  metadata: {}
230
- post_install_message:
214
+ post_install_message:
231
215
  rdoc_options:
232
216
  - "--main"
233
217
  - README.md
@@ -246,9 +230,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
230
  - !ruby/object:Gem::Version
247
231
  version: '0'
248
232
  requirements: []
249
- rubyforge_project:
250
- rubygems_version: 2.6.8
251
- signing_key:
233
+ rubygems_version: 3.1.6
234
+ signing_key:
252
235
  specification_version: 4
253
236
  summary: Standards-compliant WebSocket server and client
254
237
  test_files: []