websocket-1.2.9/0000755000004100000410000000000014001403022013523 5ustar www-datawww-datawebsocket-1.2.9/.travis.yml0000644000004100000410000000045114001403022015634 0ustar www-datawww-datadist: trusty language: ruby script: "bundle exec rake" rvm: - 2.1 - 2.2 - 2.3 - 2.4 - 2.5 - ruby-head - jruby-9.1.9.0 # https://github.com/travis-ci/travis-ci/issues/8446 - jruby-head - rbx-3 matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 websocket-1.2.9/README.md0000644000004100000410000001254314001403022015007 0ustar www-datawww-data# WebSocket Ruby Universal Ruby library to handle WebSocket protocol. It focuses on providing abstraction layer over [WebSocket API](http://dev.w3.org/html5/websockets/) instead of providing server or client functionality. [![Gem Version](https://badge.fury.io/rb/websocket.svg)](http://badge.fury.io/rb/websocket) [![Gem Downloads](https://img.shields.io/gem/dt/websocket.svg?maxAge=2592000)](https://rubygems.org/gems/websocket) [![Travis CI](https://travis-ci.org/imanel/websocket-ruby.svg)](http://travis-ci.org/imanel/websocket-ruby) [![Code Climate](https://codeclimate.com/github/imanel/websocket-ruby.svg)](https://codeclimate.com/github/imanel/websocket-ruby) **Autobahn tests:** [server](http://imanel.github.com/websocket-ruby/autobahn/server/), [client](http://imanel.github.com/websocket-ruby/autobahn/client/) Currently WebSocket Ruby supports all existing drafts of WebSocket, which include: - [hixie-75](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75) - [hixie-76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76) - [all hybi drafts (00-13)](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17) - [RFC 6455](http://datatracker.ietf.org/doc/rfc6455/) ## Installation WebSocket Ruby has no external dependencies, so it can be installed from source or directly from rubygems: ``` gem install "websocket" ``` or via Gemfile: ``` gem "websocket" ``` ## Server handshake ``` ruby @handshake = WebSocket::Handshake::Server.new # Parse client request @handshake << < 'SESSIONID=1234' }) # Create request @handshake.to_s # GET /demo HTTP/1.1 # Upgrade: websocket # Connection: Upgrade # Host: example.com # Cookie: SESSIONID=1234 # Origin: http://example.com # Sec-WebSocket-Version: 13 # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # Parse server response @handshake << < 'bbb' } } expect(handshake.headers).to eql('aaa' => 'bbb') end it 'parses uri' do @request_params = { uri: 'ws://test.example.org:301/test_path?query=true' } expect(handshake.host).to eql('test.example.org') expect(handshake.port).to be(301) expect(handshake.path).to eql('/test_path') expect(handshake.query).to eql('query=true') end it 'parses url' do @request_params = { url: 'ws://test.example.org:301/test_path?query=true' } expect(handshake.host).to eql('test.example.org') expect(handshake.port).to be(301) expect(handshake.path).to eql('/test_path') expect(handshake.query).to eql('query=true') end it 'resolves correct path with root server provided' do @request_params = { url: 'ws://test.example.org' } expect(handshake.path).to eql('/') end it 'returns valid response' do validate_request end it 'allows custom path' do @request_params = { path: '/custom' } validate_request end it 'allows query in path' do @request_params = { query: 'test=true' } validate_request end it 'allows custom port' do @request_params = { port: 123 } validate_request end it 'allows custom headers' do @request_params = { headers: { 'aaa' => 'bbb' } } validate_request end it 'recognizes unfinished requests' do handshake << server_response[0..-20] expect(handshake).not_to be_finished expect(handshake).not_to be_valid end it 'disallows requests with invalid request method' do handshake << server_response.gsub('101', '404') expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_status_code) end end websocket-1.2.9/spec/support/overwrites.rb0000644000004100000410000000017714001403022020734 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake class Base attr_reader :handler end end end websocket-1.2.9/spec/support/all_server_drafts.rb0000644000004100000410000000575414001403022022232 0ustar www-datawww-data# frozen_string_literal: true require 'webrick' RSpec.shared_examples_for 'all server drafts' do def validate_request handshake << client_request expect(handshake.error).to be_nil expect(handshake).to be_finished expect(handshake).to be_valid expect(handshake.to_s).to eql(server_response) end it 'is valid' do handshake << client_request expect(handshake.error).to be_nil expect(handshake).to be_finished expect(handshake).to be_valid end it 'returns valid version' do handshake << client_request expect(handshake.version).to eql(version) end it 'returns valid host' do @request_params = { host: 'www.test.cc' } handshake << client_request expect(handshake.host).to eql('www.test.cc') end it 'returns valid path' do @request_params = { path: '/custom' } handshake << client_request expect(handshake.path).to eql('/custom') end it 'returns valid query' do @request_params = { path: '/custom?aaa=bbb' } handshake << client_request expect(handshake.query).to eql('aaa=bbb') end it 'returns valid port' do @request_params = { port: 123 } handshake << client_request expect(handshake.port).to eql('123') end it 'returns valid response' do validate_request end it 'allows custom path' do @request_params = { path: '/custom' } validate_request end it 'allows query in path' do @request_params = { path: '/custom?test=true' } validate_request end it 'allows custom port' do @request_params = { port: 123 } validate_request end it 'recognizes unfinished requests' do handshake << client_request[0..-10] expect(handshake).not_to be_finished expect(handshake).not_to be_valid end it 'disallows requests with invalid request method' do handshake << client_request.gsub('GET', 'POST') expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:get_request_required) end it 'parses a rack request' do request = WEBrick::HTTPRequest.new(ServerSoftware: 'rspec') expect(request.parse(StringIO.new(client_request))).to be true rest = client_request.slice((request.to_s.length..-1)) handshake.from_rack(request.meta_vars.merge( 'rack.input' => StringIO.new(rest), :random_key => :random_value )) validate_request end it 'parses a hash request' do request = WEBrick::HTTPRequest.new(ServerSoftware: 'rspec') expect(request.parse(StringIO.new(client_request))).to be true body = client_request.slice((request.to_s.length..-1)) path = request.path query = request.query_string headers = request.header.each_with_object({}) do |header, hash| hash[header[0]] = header[1].first if header[0] && header[1] end handshake.from_hash(headers: headers, path: path, query: query, body: body) validate_request end end websocket-1.2.9/spec/support/outgoing_frames.rb0000644000004100000410000000137514001403022021714 0ustar www-datawww-data# frozen_string_literal: true RSpec.shared_examples_for 'valid_outgoing_frame' do it 'is outgoing frame' do expect(subject.class).to be WebSocket::Frame::Outgoing end it 'is in proper verions' do expect(subject.version).to eql version end it 'has proper type set' do expect(subject.type).to eql frame_type end it 'contains decoded data' do expect(subject.data).to eql(decoded_text) end it 'returns encoded data as to_s' do expect(subject.to_s).to eql(encoded_text) end context 'after parsing' do before { subject.to_s } it 'has valid errors set' do expect(subject.error).to eql error end it 'requires sending' do expect(subject.require_sending?).to eql require_sending end end end websocket-1.2.9/spec/support/incoming_frames.rb0000644000004100000410000000447314001403022021666 0ustar www-datawww-data# frozen_string_literal: true RSpec.shared_examples_for 'valid_incoming_frame' do let(:decoded_text_array) { Array(decoded_text) } let(:frame_type_array) { Array(frame_type) } it 'is incoming frame' do expect(subject.class).to be WebSocket::Frame::Incoming end it 'is in proper verions' do expect(subject.version).to eql version end it 'does not have type set' do expect(subject.type).to be nil end it 'is not decoded' do expect(subject.decoded?).to be false end it 'contains encoded data' do expect(subject.data).to eql(encoded_text || '') end it 'returns data as to_s' do expect(subject.to_s).to eql(encoded_text || '') end it 'has specified number of returned frames' do decoded_text_array.each_with_index do |da, index| n = subject.next expect(n).not_to be_nil, "Should return frame for #{da}, #{frame_type_array[index]}" expect(n.class).to eql(WebSocket::Frame::Incoming), "Should be WebSocket::Frame::Incoming, #{n} received instead" end expect(subject.next).to be_nil if error.is_a?(Class) expect(subject.error).to eql(error.new.message) else expect(subject.error).to eql(error) end end it 'returns valid decoded frame for each specified decoded texts' do decoded_text_array.each_with_index do |da, index| f = subject.next expect(f.decoded?).to be true expect(f.type).to eql(frame_type_array[index]) expect(f.code).to eql(close_code) if defined?(close_code) expect(f.to_s).to eql(da) end end context 'with raising' do before { WebSocket.should_raise = true } after { WebSocket.should_raise = false } it 'has specified number of returned frames' do if error expect do decoded_text_array.each_with_index do |da, index| n = subject.next expect(n).not_to be_nil, "Should return frame for #{da}, #{frame_type_array[index]}" expect(n.class).to eql(WebSocket::Frame::Incoming), "Should be WebSocket::Frame::Incoming, #{n} received instead" end expect(subject.next).to be_nil if error.is_a?(Class) expect(subject.error).to eql(error.new.message) else expect(subject.error).to eql(error) end end.to raise_error(error) end end end end websocket-1.2.9/spec/support/handshake_requests.rb0000644000004100000410000000620514001403022022402 0ustar www-datawww-data# frozen_string_literal: true def client_handshake_75(args = {}) <<-REQUEST GET #{args[:path] || '/demo'}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r Upgrade: WebSocket\r Connection: Upgrade\r Host: #{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}\r Origin: http://example.com\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}\r REQUEST end def server_handshake_75(args = {}) <<-REQUEST HTTP/1.1 101 Web Socket Protocol Handshake\r Upgrade: WebSocket\r Connection: Upgrade\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}WebSocket-Origin: http://example.com\r WebSocket-Location: ws#{args[:secure] ? 's' : ''}://#{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}#{args[:path] || '/demo'}\r \r REQUEST end def client_handshake_76(args = {}) request = <<-REQUEST GET #{args[:path] || '/demo'}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r Upgrade: WebSocket\r Connection: Upgrade\r Host: #{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}\r Origin: http://example.com\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}Sec-WebSocket-Key1: #{args[:key1] || '4 @1 46546xW%0l 1 5'}\r Sec-WebSocket-Key2: #{args[:key2] || '12998 5 Y3 1 .P00'}\r \r #{args[:key3] || '^n:ds[4U'} REQUEST request[0..-2] end def server_handshake_76(args = {}) request = <<-REQUEST HTTP/1.1 101 WebSocket Protocol Handshake\r Upgrade: WebSocket\r Connection: Upgrade\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}Sec-WebSocket-Origin: http://example.com\r Sec-WebSocket-Location: ws#{args[:secure] ? 's' : ''}://#{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}#{args[:path] || '/demo'}\r \r #{args[:challenge] || "8jKS'y:G*Co,Wxa-"} REQUEST request[0..-2] end def client_handshake_04(args = {}) <<-REQUEST GET #{args[:path] || '/demo'}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r Upgrade: websocket\r Connection: Upgrade\r Host: #{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}Sec-WebSocket-Origin: http://example.com\r Sec-WebSocket-Version: #{args[:version] || '4'}\r Sec-WebSocket-Key: #{args[:key] || 'dGhlIHNhbXBsZSBub25jZQ=='}\r \r REQUEST end def server_handshake_04(args = {}) <<-REQUEST HTTP/1.1 101 Switching Protocols\r Upgrade: websocket\r Connection: Upgrade\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}Sec-WebSocket-Accept: #{args[:accept] || 's3pPLMBiTxaQ9kYGzzhZRbK+xOo='}\r \r REQUEST end def client_handshake_11(args = {}) <<-REQUEST GET #{args[:path] || '/demo'}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r Upgrade: websocket\r Connection: Upgrade\r Host: #{args[:host] || 'example.com'}#{":#{args[:port]}" if args[:port]}\r #{(args[:headers] || {}).map { |key, value| "#{key}: #{value}\r\n" }.join('')}Origin: http://example.com\r Sec-WebSocket-Version: #{args[:version] || '4'}\r Sec-WebSocket-Key: #{args[:key] || 'dGhlIHNhbXBsZSBub25jZQ=='}\r \r REQUEST end def server_handshake_11(args = {}) server_handshake_04(args) end websocket-1.2.9/spec/handshake/0000755000004100000410000000000014001403022016403 5ustar www-datawww-datawebsocket-1.2.9/spec/handshake/client_11_spec.rb0000644000004100000410000000154314001403022021524 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Client draft 11 handshake' do let(:handshake) { WebSocket::Handshake::Client.new({ uri: 'ws://example.com/demo', origin: 'http://example.com', version: version }.merge(@request_params || {})) } let(:version) { 11 } let(:client_request) { client_handshake_11({ key: handshake.handler.send(:key), version: version }.merge(@request_params || {})) } let(:server_response) { server_handshake_11({ accept: handshake.handler.send(:accept) }.merge(@request_params || {})) } it_behaves_like 'all client drafts' it 'disallows client with invalid challenge' do @request_params = { accept: 'invalid' } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end end websocket-1.2.9/spec/handshake/server_76_spec.rb0000644000004100000410000000432714001403022021572 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Server draft 76 handshake' do let(:handshake) { WebSocket::Handshake::Server.new } let(:version) { 76 } let(:client_request) { client_handshake_76(@request_params || {}) } let(:server_response) { server_handshake_76(@request_params || {}) } it_behaves_like 'all server drafts' it 'disallows request without spaces in key 1' do @request_params = { key1: '4@146546xW%0l15' } handshake << client_request expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end it 'disallows request without spaces in key 2' do @request_params = { key2: '129985Y31.P00' } handshake << client_request expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end it 'disallows request with invalid number of spaces or numbers in key 1' do @request_params = { key1: '4 @1 46546xW%0l 1 5' } handshake << client_request expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end it 'disallows request with invalid number of spaces or numbers in key 2' do @request_params = { key2: '12998 5 Y3 1 .P00' } handshake << client_request expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Server.new(protocols: %w[binary]) } context 'supported' do it 'returns with the same protocol' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'binary' } } handshake << client_request expect(handshake.to_s).to match('Sec-WebSocket-Protocol: binary') end end context 'unsupported' do it 'returns with an empty protocol header' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'xmpp' } } handshake << client_request expect(handshake.to_s).to match("Sec-WebSocket-Protocol: \r\n") end end end end websocket-1.2.9/spec/handshake/server_04_spec.rb0000644000004100000410000000323314001403022021554 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Server draft 04 handshake' do let(:handshake) { WebSocket::Handshake::Server.new } let(:version) { 4 } let(:client_request) { client_handshake_04(@request_params || {}) } let(:server_response) { server_handshake_04(@request_params || {}) } it_behaves_like 'all server drafts' it 'disallows request without Sec-WebSocket-Key' do handshake << client_request.gsub(/^Sec-WebSocket-Key:.*\n/, '') expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Server.new(protocols: %w[binary xmpp]) } context 'single protocol requested' do it 'returns with the same protocol' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'binary' } } handshake << client_request expect(handshake.to_s).to match('Sec-WebSocket-Protocol: binary') end end context 'multiple protocols requested' do it 'returns with the first supported protocol' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'xmpp, binary' } } handshake << client_request expect(handshake.to_s).to match('Sec-WebSocket-Protocol: xmpp') end end context 'unsupported protocol requested' do it 'reutrns with an empty protocol header' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'generic' } } handshake << client_request expect(handshake.to_s).to match("Sec-WebSocket-Protocol: \r\n") end end end end websocket-1.2.9/spec/handshake/client_04_spec.rb0000644000004100000410000000420714001403022021526 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Client draft 4 handshake' do let(:handshake) { WebSocket::Handshake::Client.new({ uri: 'ws://example.com/demo', origin: 'http://example.com', version: version }.merge(@request_params || {})) } let(:version) { 4 } let(:client_request) { client_handshake_04({ key: handshake.handler.send(:key), version: version }.merge(@request_params || {})) } let(:server_response) { server_handshake_04({ accept: handshake.handler.send(:accept) }.merge(@request_params || {})) } it_behaves_like 'all client drafts' it 'disallows client with invalid challenge' do @request_params = { accept: 'invalid' } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Client.new(uri: 'ws://example.com/demo', origin: 'http://example.com', version: version, protocols: protocols) } context 'single protocol requested' do let(:protocols) { %w[binary] } it 'returns a valid handshake' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'binary' } } handshake << server_response expect(handshake).to be_finished expect(handshake).to be_valid end end context 'multiple protocols requested' do let(:protocols) { %w[binary xmpp] } it 'returns with a valid handshake' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'xmpp' } } handshake << server_response expect(handshake).to be_finished expect(handshake).to be_valid end end context 'unsupported protocol requested' do let(:protocols) { %w[binary xmpp] } it 'fails with an unsupported protocol error' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'generic' } } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:unsupported_protocol) end end end end websocket-1.2.9/spec/handshake/client_75_spec.rb0000644000004100000410000000244614001403022021541 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Client draft 75 handshake' do let(:handshake) { WebSocket::Handshake::Client.new({ uri: 'ws://example.com/demo', origin: 'http://example.com', version: version }.merge(@request_params || {})) } let(:version) { 75 } let(:client_request) { client_handshake_75(@request_params || {}) } let(:server_response) { server_handshake_75(@request_params || {}) } it_behaves_like 'all client drafts' context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Client.new(uri: 'ws://example.com/demo', origin: 'http://example.com', version: version, protocols: %w[binary]) } context 'supported' do it 'returns a valid handshake' do @request_params = { headers: { 'WebSocket-Protocol' => 'binary' } } handshake << server_response expect(handshake).to be_finished expect(handshake).to be_valid end end context 'unsupported' do it 'fails with an unsupported protocol error' do @request_params = { headers: { 'WebSocket-Protocol' => 'xmpp' } } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:unsupported_protocol) end end end end websocket-1.2.9/spec/handshake/server_75_spec.rb0000644000004100000410000000202414001403022021561 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Server draft 75 handshake' do let(:handshake) { WebSocket::Handshake::Server.new } let(:version) { 75 } let(:client_request) { client_handshake_75(@request_params || {}) } let(:server_response) { server_handshake_75(@request_params || {}) } it_behaves_like 'all server drafts' context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Server.new(protocols: %w[binary]) } context 'supported' do it 'returns with the same protocol' do @request_params = { headers: { 'WebSocket-Protocol' => 'binary' } } handshake << client_request expect(handshake.to_s).to match('WebSocket-Protocol: binary') end end context 'unsupported' do it 'returns with an empty protocol header' do @request_params = { headers: { 'WebSocket-Protocol' => 'xmpp' } } handshake << client_request expect(handshake.to_s).to match("WebSocket-Protocol: \r\n") end end end end websocket-1.2.9/spec/handshake/client_76_spec.rb0000644000004100000410000000337214001403022021541 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' RSpec.describe 'Client draft 76 handshake' do let(:handshake) { WebSocket::Handshake::Client.new({ uri: 'ws://example.com/demo', origin: 'http://example.com', version: version }.merge(@request_params || {})) } let(:version) { 76 } let(:client_request) { client_handshake_76({ key1: handshake.handler.send(:key1), key2: handshake.handler.send(:key2), key3: handshake.handler.send(:key3) }.merge(@request_params || {})) } let(:server_response) { server_handshake_76({ challenge: handshake.handler.send(:challenge) }.merge(@request_params || {})) } it_behaves_like 'all client drafts' it 'disallows client with invalid challenge' do @request_params = { challenge: 'invalid' } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:invalid_handshake_authentication) end context 'protocol header specified' do let(:handshake) { WebSocket::Handshake::Client.new(uri: 'ws://example.com/demo', origin: 'http://example.com', version: version, protocols: %w[binary]) } context 'supported' do it 'returns a valid handshake' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'binary' } } handshake << server_response expect(handshake).to be_finished expect(handshake).to be_valid end end context 'unsupported' do it 'fails with an unsupported protocol error' do @request_params = { headers: { 'Sec-WebSocket-Protocol' => 'xmpp' } } handshake << server_response expect(handshake).to be_finished expect(handshake).not_to be_valid expect(handshake.error).to be(:unsupported_protocol) end end end end websocket-1.2.9/CHANGELOG.md0000644000004100000410000000426714001403022015345 0ustar www-datawww-data# Changelog ## 1.2.9 - avoid ruby -w warnings ## 1.2.8 - restore support for Ruby 2.0+ ## 1.2.7 - fix bug in previous version for Ruby 2.3 ## 1.2.6 - duplicate variables passed in initializers to avoid changing them ## 1.2.5 - make handshake server resilient to non-string Rack env keys ## 1.2.4 - add subprotocol handling for both server and client ## 1.2.3 - fix for draft 76 when challenge might sometimes fail - multiple small optimizations ## 1.2.2 - fix handshake for draft 11+ sending Sec-WebSocket-Origin instead of Origin ## 1.2.1 - fix error for draft 76 when leftovers are empty ## 1.2.0 - Remove support for Ruby 1.8 - Add support for sending custom headers for Client - Better detection and handling of draft 76 - Multiple small fixes and optimizations ## 1.1.4 - verify valid close codes according to spec - return error on invalid UTF-8 payload - expose error message ## 1.1.3 - fix close code support ## 1.1.2 - fix support for rack input that is blocking (i.e. Passenger) ## 1.1.1 - fix handling close code for frames version 5+ ## 1.1.0 - allow raising ruby errors instead of setting `error` flag - allow access to handshake headers - add from_rack method - add from_hash method - stop extending handlers - it should improve performance for opening connection ## 1.0.7 - fix requiring url under Ruby 1.9.1 - support for Ruby 2.0.0 ## 1.0.6 - support text frame types instead of only symbol ones - support for sending masked frames ## 1.0.5 - add support for close codes ## 1.0.4 - nicer inspect - handful during debugging ## 1.0.3 - improve pure ruby implementation performance by ~30% - add support for native extension ## 1.0.2 - allow configuration of max frame size via WebSocket.max_frame_size option - much better documentation - remove handler-specific methods from public list - refactor code for easier use - make parsers return more consistent values - fix server handshake #to_s when no version was found - add #uri to server handshake ## 1.0.1 - allow creating client with :uri and :url options - prevent strange results when header is mailformed - set client path to '/' when :uri option is provided but without trailing slash ## 1.0.0 - initial release websocket-1.2.9/.rubocop.yml0000644000004100000410000000211214001403022015771 0ustar www-datawww-datarequire: rubocop-rspec AllCops: DisplayCopNames: true TargetRubyVersion: 2.1 # New version of Rubocop does not support 2.0 Gemspec/RequiredRubyVersion: Enabled: false Layout/IndentHeredoc: Enabled: false # Target: 15 Metrics/AbcSize: Max: 24 Exclude: - lib/websocket/frame/handler/handler75.rb - spec/**/* Metrics/BlockLength: Exclude: - spec/**/* Metrics/ClassLength: Enabled: false # Target: 6 Metrics/CyclomaticComplexity: Max: 11 Exclude: - spec/support/handshake_requests.rb Metrics/LineLength: Enabled: false # Target: 10 Metrics/MethodLength: Max: 18 Exclude: - lib/websocket/frame/handler/handler75.rb # Target: 7 Metrics/PerceivedComplexity: Max: 8 Exclude: - lib/websocket/frame/handler/handler75.rb - spec/support/handshake_requests.rb RSpec/ContextWording: Enabled: false RSpec/DescribeClass: Enabled: false RSpec/ExampleLength: Enabled: false RSpec/InstanceVariable: Enabled: false RSpec/MultipleExpectations: Enabled: false RSpec/NamedSubject: Enabled: false Style/Documentation: Enabled: false websocket-1.2.9/.gitignore0000644000004100000410000000004014001403022015505 0ustar www-datawww-dataGemfile.lock autobahn pkg/*.gem websocket-1.2.9/.codeclimate.yml0000644000004100000410000000033714001403022016600 0ustar www-datawww-data--- engines: duplication: enabled: true config: languages: - ruby fixme: enabled: true rubocop: enabled: true channel: rubocop-0-52 ratings: paths: - "**.rb" exclude_paths: - spec/ websocket-1.2.9/Rakefile0000644000004100000410000000113314001403022015166 0ustar www-datawww-data# frozen_string_literal: true require 'bundler' require 'rspec/core/rake_task' require 'rubocop/rake_task' Bundler::GemHelper.install_tasks RSpec::Core::RakeTask.new do |t| t.rspec_opts = ['-c', '-f progress'] t.pattern = 'spec/**/*_spec.rb' end RuboCop::RakeTask.new task default: %i[spec rubocop] namespace :autobahn do desc 'Run autobahn tests for client' task :client do system('wstest --mode=fuzzingserver --spec=autobahn-client.json') end desc 'Run autobahn tests for server' task :server do system('wstest --mode=fuzzingclient --spec=autobahn-server.json') end end websocket-1.2.9/lib/0000755000004100000410000000000014001403022014271 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket.rb0000644000004100000410000000263014001403022016605 0ustar www-datawww-data# frozen_string_literal: true # WebSocket protocol implementation in Ruby # This module does not provide a WebSocket server or client, but is made for using # in http servers or clients to provide WebSocket support. # @author Bernard "Imanel" Potocki # @see http://github.com/imanel/websocket-ruby main repository module WebSocket # Default WebSocket version to use DEFAULT_VERSION = 13 ROOT = __dir__ autoload :Error, "#{ROOT}/websocket/error" autoload :ExceptionHandler, "#{ROOT}/websocket/exception_handler" autoload :Frame, "#{ROOT}/websocket/frame" autoload :Handshake, "#{ROOT}/websocket/handshake" autoload :NiceInspect, "#{ROOT}/websocket/nice_inspect" # Limit of frame size payload in bytes def self.max_frame_size @max_frame_size ||= 20 * 1024 * 1024 # 20MB end # Set limit of frame size payload in bytes def self.max_frame_size=(val) @max_frame_size = val end # If set to true error will be raised instead of setting `error` method. # All errors inherit from WebSocket::Error. def self.should_raise @should_raise ||= false end # Should protocol errors raise ruby errors? If false then `error` flag is set instead. def self.should_raise=(val) @should_raise = val end end # Try loading websocket-native if available begin require 'websocket-native' rescue LoadError => e raise unless e.message =~ /websocket-native/ end websocket-1.2.9/lib/websocket/0000755000004100000410000000000014001403022016257 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/frame/0000755000004100000410000000000014001403022017351 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/frame/incoming/0000755000004100000410000000000014001403022021154 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/frame/incoming/client.rb0000644000004100000410000000041514001403022022757 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame class Incoming class Client < Incoming def incoming_masking? false end def outgoing_masking? @handler.masking? end end end end end websocket-1.2.9/lib/websocket/frame/incoming/server.rb0000644000004100000410000000041514001403022023007 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame class Incoming class Server < Incoming def incoming_masking? @handler.masking? end def outgoing_masking? false end end end end end websocket-1.2.9/lib/websocket/frame/handler/0000755000004100000410000000000014001403022020766 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/frame/handler/handler05.rb0000644000004100000410000000042214001403022023073 0ustar www-datawww-data# encoding: binary # frozen_string_literal: true module WebSocket module Frame module Handler class Handler05 < Handler04 # Since handler 5 masking should be enabled by default def masking? true end end end end end websocket-1.2.9/lib/websocket/frame/handler/handler03.rb0000644000004100000410000001677114001403022023107 0ustar www-datawww-data# encoding: binary # frozen_string_literal: true require 'securerandom' module WebSocket module Frame module Handler class Handler03 < Base # Hash of frame names and it's opcodes FRAME_TYPES = { continuation: 0, close: 1, ping: 2, pong: 3, text: 4, binary: 5 }.freeze # Hash of frame opcodes and it's names FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze def initialize(frame) super @application_data_buffer = nil end # @see WebSocket::Frame::Base#supported_frames def supported_frames %i[text binary close ping pong] end # @see WebSocket::Frame::Handler::Base#encode_frame def encode_frame frame = if @frame.outgoing_masking? masking_key = SecureRandom.random_bytes(4) tmp_data = Data.new(masking_key + @frame.data) tmp_data.set_mask masking_key + tmp_data.getbytes(4, tmp_data.size) else @frame.data end encode_header + frame end # @see WebSocket::Frame::Handler::Base#decode_frame def decode_frame while @frame.data.size > 1 valid_header, more, frame_type, mask, payload_length = decode_header return unless valid_header application_data = decode_payload(payload_length, mask) if more decode_continuation_frame(application_data, frame_type) elsif frame_type == :continuation return decode_finish_continuation_frame(application_data) else raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && !application_data.valid_encoding? return @frame.class.new(version: @frame.version, type: frame_type, data: application_data, decoded: true) end end nil end # Allow turning on or off masking def masking? false end private # This allows flipping the more bit to fin for draft 04 def fin false end # Convert frame type name to opcode # @param [Symbol] frame_type Frame type name # @return [Integer] opcode or nil # @raise [WebSocket::Error] if frame opcode is not known def type_to_opcode(frame_type) FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType) end # Convert frame opcode to type name # @param [Integer] opcode Opcode # @return [Symbol] Frame type name or nil # @raise [WebSocket::Error] if frame type name is not known def opcode_to_type(opcode) FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode) end def encode_header mask = @frame.outgoing_masking? ? 0b10000000 : 0b00000000 output = String.new('') output << (type_to_opcode(@frame.type) | (fin ? 0b10000000 : 0b00000000)) # since more, rsv1-3 are 0 and 0x80 for Draft 4 output << encode_payload_length(@frame.data.size, mask) output end def encode_payload_length(length, mask) output = String.new('') if length <= 125 output << (length | mask) # since rsv4 is 0 elsif length < 65_536 # write 2 byte length output << (126 | mask) output << [length].pack('n') else # write 8 byte length output << (127 | mask) output << [length >> 32, length & 0xFFFFFFFF].pack('NN') end output end def decode_header more, frame_type = decode_first_byte header_length, payload_length, mask = decode_second_byte(frame_type) return unless header_length # Compute the expected frame length frame_length = header_length + payload_length frame_length += 4 if mask raise(WebSocket::Error::Frame::TooLong) if frame_length > WebSocket.max_frame_size # Check buffer size return unless buffer_exists?(frame_length) # Buffer incomplete # Remove frame header @frame.data.slice!(0...header_length) [true, more, frame_type, mask, payload_length] end def buffer_exists?(buffer_number) !@frame.data.getbyte(buffer_number - 1).nil? end def decode_first_byte first_byte = @frame.data.getbyte(0) raise(WebSocket::Error::Frame::ReservedBitUsed) if first_byte & 0b01110000 != 0b00000000 more = ((first_byte & 0b10000000) == 0b10000000) ^ fin frame_type = opcode_to_type first_byte & 0b00001111 raise(WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type) raise(WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil? [more, frame_type] end def decode_second_byte(frame_type) second_byte = @frame.data.getbyte(1) mask = @frame.incoming_masking? && (second_byte & 0b10000000) == 0b10000000 length = second_byte & 0b01111111 raise(WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type) header_length, payload_length = decode_payload_length(length) [header_length, payload_length, mask] end def decode_payload_length(length) case length when 127 # Length defined by 8 bytes # Check buffer size return unless buffer_exists?(10) # Buffer incomplete # Only using the last 4 bytes for now, till I work out how to # unpack 8 bytes. I'm sure 4GB frames will do for now :) [10, @frame.data.getbytes(6, 4).unpack('N').first] when 126 # Length defined by 2 bytes # Check buffer size return unless buffer_exists?(4) # Buffer incomplete [4, @frame.data.getbytes(2, 2).unpack('n').first] else [2, length] end end def decode_payload(payload_length, mask) pointer = 0 # Read application data (unmasked if required) @frame.data.set_mask if mask pointer += 4 if mask payload = @frame.data.getbytes(pointer, payload_length) payload.force_encoding('UTF-8') pointer += payload_length @frame.data.unset_mask if mask # Throw away data up to pointer @frame.data.slice!(0...pointer) payload end def decode_continuation_frame(application_data, frame_type) @application_data_buffer ||= String.new('') @application_data_buffer << application_data @frame_type ||= frame_type end def decode_finish_continuation_frame(application_data) raise(WebSocket::Error::Frame::UnexpectedContinuationFrame) unless @frame_type @application_data_buffer << application_data # Test valid UTF-8 encoding raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && !@application_data_buffer.valid_encoding? message = @frame.class.new(version: @frame.version, type: @frame_type, data: @application_data_buffer, decoded: true) @application_data_buffer = nil @frame_type = nil message end end end end end websocket-1.2.9/lib/websocket/frame/handler/base.rb0000644000004100000410000000214014001403022022222 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame module Handler class Base def initialize(frame) @frame = frame end # Convert data to raw frame ready to send to client # @return [String] Encoded frame def encode_frame raise NotImplementedError end # Convert raw data to decoded frame # @return [WebSocket::Frame::Incoming] Frame if found, nil otherwise def decode_frame raise NotImplementedError end private # Check if frame is one of control frames # @param [Symbol] frame_type Frame type # @return [Boolean] True if given frame type is control frame def control_frame?(frame_type) !%i[text binary continuation].include?(frame_type) end # Check if frame is one of data frames # @param [Symbol] frame_type Frame type # @return [Boolean] True if given frame type is data frame def data_frame?(frame_type) %i[text binary].include?(frame_type) end end end end end websocket-1.2.9/lib/websocket/frame/handler/handler04.rb0000644000004100000410000000055014001403022023074 0ustar www-datawww-data# encoding: binary # frozen_string_literal: true module WebSocket module Frame module Handler class Handler04 < Handler03 private # The only difference between draft 03 framing and draft 04 framing is # that the MORE bit has been changed to a FIN bit def fin true end end end end end websocket-1.2.9/lib/websocket/frame/handler/handler07.rb0000644000004100000410000000445714001403022023111 0ustar www-datawww-data# encoding: binary # frozen_string_literal: true module WebSocket module Frame module Handler class Handler07 < Handler05 # Hash of frame names and it's opcodes FRAME_TYPES = { continuation: 0, text: 1, binary: 2, close: 8, ping: 9, pong: 10 }.freeze # Hash of frame opcodes and it's names FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze def encode_frame if @frame.type == :close code = @frame.code || 1000 raise WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(code) @frame.data = Data.new([code].pack('n') + @frame.data.to_s) @frame.code = nil end super end def decode_frame result = super if close_code?(result) code = result.data.slice!(0..1) result.code = code.unpack('n').first raise WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(result.code) raise WebSocket::Error::Frame::InvalidPayloadEncoding unless valid_encoding?(result.data) end result end private def valid_code?(code) [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].include?(code) || (3000..4999).cover?(code) end def valid_encoding?(data) return true if data.nil? data.encode('UTF-8') true rescue StandardError false end def close_code?(frame) frame && frame.type == :close && !frame.data.empty? end # Convert frame type name to opcode # @param [Symbol] frame_type Frame type name # @return [Integer] opcode or nil # @raise [WebSocket::Error] if frame opcode is not known def type_to_opcode(frame_type) FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType) end # Convert frame opcode to type name # @param [Integer] opcode Opcode # @return [Symbol] Frame type name or nil # @raise [WebSocket::Error] if frame type name is not known def opcode_to_type(opcode) FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode) end end end end end websocket-1.2.9/lib/websocket/frame/handler/handler75.rb0000644000004100000410000000506314001403022023110 0ustar www-datawww-data# encoding: binary # frozen_string_literal: true module WebSocket module Frame module Handler class Handler75 < Base # @see WebSocket::Frame::Base#supported_frames def supported_frames %i[text close] end # @see WebSocket::Frame::Handler::Base#encode_frame def encode_frame case @frame.type when :close then "\xff\x00" when :text then ary = ["\x00", @frame.data, "\xff"] ary.map { |s| s.encode('UTF-8', 'UTF-8', invalid: :replace) } ary.join else raise WebSocket::Error::Frame::UnknownFrameType end end # @see WebSocket::Frame::Handler::Base#decode_frame def decode_frame return if @frame.data.size.zero? pointer = 0 frame_type = @frame.data.getbyte(pointer) pointer += 1 if (frame_type & 0x80) == 0x80 # If the high-order bit of the /frame type/ byte is set length = 0 loop do return unless @frame.data.getbyte(pointer) b = @frame.data.getbyte(pointer) pointer += 1 b_v = b & 0x7F length = length * 128 + b_v break unless (b & 0x80) == 0x80 end raise WebSocket::Error::Frame::TooLong if length > ::WebSocket.max_frame_size unless @frame.data.getbyte(pointer + length - 1).nil? # Straight from spec - I'm sure this isn't crazy... # 6. Read /length/ bytes. # 7. Discard the read bytes. @frame.instance_variable_set '@data', @frame.data[(pointer + length)..-1] # If the /frame type/ is 0xFF and the /length/ was 0, then close if length.zero? @frame.class.new(version: @frame.version, type: :close, decoded: true) end end else # If the high-order bit of the /frame type/ byte is _not_ set raise WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00 # Addition to the spec to protect against malicious requests raise WebSocket::Error::Frame::TooLong if @frame.data.size > ::WebSocket.max_frame_size msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/) if msg msg.gsub!(/\A\x00|\xff\z/, '') msg.force_encoding('UTF-8') @frame.class.new(version: @frame.version, type: :text, data: msg, decoded: true) end end end end end end end websocket-1.2.9/lib/websocket/frame/base.rb0000644000004100000410000000413214001403022020610 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame # @abstract Subclass and override to implement custom frames class Base include ExceptionHandler include NiceInspect attr_reader :type, :version, :error attr_accessor :data, :code # Initialize frame # @param args [Hash] Arguments for frame # @option args [String] :data default data for frame # @option args [String] :type Type of frame - available types are "text", "binary", "ping", "pong" and "close"(support depends on draft version) # @option args [Integer] :code Code for close frame. Supported by drafts > 05. # @option args [Integer] :version Version of draft. Currently supported version are 75, 76 and 00-13. def initialize(args = {}) @type = args[:type].to_sym if args[:type] @code = args[:code] @data = Data.new(args[:data].to_s) @version = args[:version] || DEFAULT_VERSION @handler = nil include_version end rescue_method :initialize # Check if some errors occured # @return [Boolean] True if error is set def error? !@error.nil? end # Is selected type supported for selected handler? def support_type? @handler.supported_frames.include?(@type) end # Implement in submodules def supported_frames raise NotImplementedError end private # Include set of methods for selected protocol version # @return [Boolean] false if protocol number is unknown, otherwise true def include_version @handler = case @version when 75..76 then Handler::Handler75.new(self) when 0..2 then Handler::Handler75.new(self) when 3 then Handler::Handler03.new(self) when 4 then Handler::Handler04.new(self) when 5..6 then Handler::Handler05.new(self) when 7..13 then Handler::Handler07.new(self) else raise WebSocket::Error::Frame::UnknownVersion end end end end end websocket-1.2.9/lib/websocket/frame/outgoing.rb0000644000004100000410000000231214001403022021527 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame # Construct or parse incoming WebSocket Frame. # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04) # # @example # frame = WebSocket::Frame::Outgoing::Server.new(version: @handshake.version, data: "Hello", type: :text) # frame.to_s # "\x81\x05\x48\x65\x6c\x6c\x6f" class Outgoing < Base autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/outgoing/client" autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/outgoing/server" # Is selected type supported by current draft version? # @return [Boolean] true if frame type is supported def supported? support_type? end # Should current frame be sent? Exclude empty frames etc. # @return [Boolean] true if frame should be sent def require_sending? !error? end # Return raw frame formatted for sending. def to_s raise WebSocket::Error::Frame::UnknownFrameType unless supported? @handler.encode_frame end rescue_method :to_s end end end websocket-1.2.9/lib/websocket/frame/data.rb0000644000004100000410000000244714001403022020616 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame class Data < String def initialize(*args) super(*convert_args(args)) @masking_key = nil end def <<(*args) super(*convert_args(args)) end # Convert all arguments to ASCII-8BIT for easier traversing def convert_args(args) args.collect { |arg| arg.dup.force_encoding('ASCII-8BIT') } end # Extract mask from 4 first bytes according to spec def set_mask raise WebSocket::Error::Frame::MaskTooShort if bytesize < 4 @masking_key = self[0..3].bytes.to_a end # Remove mask flag - it will still be present in payload def unset_mask @masking_key = nil end # Extract `count` bytes starting from `start_index` and unmask it if needed. def getbytes(start_index, count) data = self[start_index, count] data = mask(data.bytes.to_a, @masking_key).pack('C*') if @masking_key data end # Mask whole payload using mask key def mask(payload, mask) return mask_native(payload, mask) if respond_to?(:mask_native) result = [] payload.each_with_index do |byte, i| result[i] = byte ^ mask[i % 4] end result end end end end websocket-1.2.9/lib/websocket/frame/incoming.rb0000644000004100000410000000360514001403022021505 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame # Construct or parse incoming WebSocket Frame. # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04) # # @example # frame = WebSocket::Frame::Incoming::Server.new(version: @handshake.version) # frame << "\x81\x05\x48\x65\x6c\x6c\x6f\x81\x06\x77\x6f\x72\x6c\x64\x21" # frame.next # "Hello" # frame.next # "world!"" class Incoming < Base autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/incoming/client" autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/incoming/server" def initialize(args = {}) @decoded = args[:decoded] || false super end # If data is still encoded after receiving then this is false. After calling "next" you will receive # another instance of incoming frame, but with data decoded - this function will return true and # to_s will return frame content instead of raw data. # @return [Boolean] If frame already decoded? def decoded? @decoded end # Add provided string as raw incoming frame. # @param data [String] Raw frame def <<(data) @data << data end # Return next complete frame. # This function will merge together splitted frames and return as combined content. # Check #error if nil received to check for eventual parsing errors # @return [WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available. def next @handler.decode_frame unless decoded? end rescue_method :next # If decoded then this will return frame content. Otherwise it will return raw frame. # @return [String] Data of frame def to_s @data end end end end websocket-1.2.9/lib/websocket/frame/handler.rb0000644000004100000410000000113014001403022021306 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame module Handler autoload :Base, "#{::WebSocket::ROOT}/websocket/frame/handler/base" autoload :Handler03, "#{::WebSocket::ROOT}/websocket/frame/handler/handler03" autoload :Handler04, "#{::WebSocket::ROOT}/websocket/frame/handler/handler04" autoload :Handler05, "#{::WebSocket::ROOT}/websocket/frame/handler/handler05" autoload :Handler07, "#{::WebSocket::ROOT}/websocket/frame/handler/handler07" autoload :Handler75, "#{::WebSocket::ROOT}/websocket/frame/handler/handler75" end end end websocket-1.2.9/lib/websocket/frame/outgoing/0000755000004100000410000000000014001403022021204 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/frame/outgoing/client.rb0000644000004100000410000000041514001403022023007 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame class Outgoing class Client < Outgoing def incoming_masking? false end def outgoing_masking? @handler.masking? end end end end end websocket-1.2.9/lib/websocket/frame/outgoing/server.rb0000644000004100000410000000041514001403022023037 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame class Outgoing class Server < Outgoing def incoming_masking? @handler.masking? end def outgoing_masking? false end end end end end websocket-1.2.9/lib/websocket/version.rb0000644000004100000410000000011714001403022020270 0ustar www-datawww-data# frozen_string_literal: true module WebSocket VERSION = '1.2.9'.freeze end websocket-1.2.9/lib/websocket/handshake.rb0000644000004100000410000000056014001403022020533 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/base" autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/client" autoload :Handler, "#{::WebSocket::ROOT}/websocket/handshake/handler" autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/server" end end websocket-1.2.9/lib/websocket/error.rb0000644000004100000410000000550714001403022017744 0ustar www-datawww-data# frozen_string_literal: true module WebSocket class Error < RuntimeError class Frame < ::WebSocket::Error class ControlFramePayloadTooLong < ::WebSocket::Error::Frame def message :control_frame_payload_too_long end end class DataFrameInsteadContinuation < ::WebSocket::Error::Frame def message :data_frame_instead_continuation end end class FragmentedControlFrame < ::WebSocket::Error::Frame def message :fragmented_control_frame end end class Invalid < ::WebSocket::Error::Frame def message :invalid_frame end end class InvalidPayloadEncoding < ::WebSocket::Error::Frame def message :invalid_payload_encoding end end class MaskTooShort < ::WebSocket::Error::Frame def message :mask_is_too_short end end class ReservedBitUsed < ::WebSocket::Error::Frame def message :reserved_bit_used end end class TooLong < ::WebSocket::Error::Frame def message :frame_too_long end end class UnexpectedContinuationFrame < ::WebSocket::Error::Frame def message :unexpected_continuation_frame end end class UnknownFrameType < ::WebSocket::Error::Frame def message :unknown_frame_type end end class UnknownOpcode < ::WebSocket::Error::Frame def message :unknown_opcode end end class UnknownCloseCode < ::WebSocket::Error::Frame def message :unknown_close_code end end class UnknownVersion < ::WebSocket::Error::Frame def message :unknown_protocol_version end end end class Handshake < ::WebSocket::Error class GetRequestRequired < ::WebSocket::Error::Handshake def message :get_request_required end end class InvalidAuthentication < ::WebSocket::Error::Handshake def message :invalid_handshake_authentication end end class InvalidHeader < ::WebSocket::Error::Handshake def message :invalid_header end end class UnsupportedProtocol < ::WebSocket::Error::Handshake def message :unsupported_protocol end end class InvalidStatusCode < ::WebSocket::Error::Handshake def message :invalid_status_code end end class NoHostProvided < ::WebSocket::Error::Handshake def message :no_host_provided end end class UnknownVersion < ::WebSocket::Error::Handshake def message :unknown_protocol_version end end end end end websocket-1.2.9/lib/websocket/frame.rb0000644000004100000410000000065014001403022017677 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Frame autoload :Base, "#{::WebSocket::ROOT}/websocket/frame/base" autoload :Data, "#{::WebSocket::ROOT}/websocket/frame/data" autoload :Handler, "#{::WebSocket::ROOT}/websocket/frame/handler" autoload :Incoming, "#{::WebSocket::ROOT}/websocket/frame/incoming" autoload :Outgoing, "#{::WebSocket::ROOT}/websocket/frame/outgoing" end end websocket-1.2.9/lib/websocket/exception_handler.rb0000644000004100000410000000174114001403022022302 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module ExceptionHandler attr_accessor :error def self.included(base) base.extend(ClassMethods) end module ClassMethods # Rescue from WebSocket::Error errors. # # @param [String] method_name Name of method that should be wrapped and rescued # @param [Hash] options Options for rescue # # @option options [Any] :return Value that should be returned instead of raised error def rescue_method(method_name, options = {}) define_method "#{method_name}_with_rescue" do |*args| begin send("#{method_name}_without_rescue", *args) rescue WebSocket::Error => e self.error = e.message.to_sym WebSocket.should_raise ? raise : options[:return] end end alias_method "#{method_name}_without_rescue", method_name alias_method method_name, "#{method_name}_with_rescue" end end end end websocket-1.2.9/lib/websocket/handshake/0000755000004100000410000000000014001403022020205 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/handshake/handler/0000755000004100000410000000000014001403022021622 5ustar www-datawww-datawebsocket-1.2.9/lib/websocket/handshake/handler/server75.rb0000644000004100000410000000210614001403022023630 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler class Server75 < Server private def headers { origin: 'WebSocket-Origin', location: 'WebSocket-Location', protocol: 'WebSocket-Protocol' }.freeze end # @see WebSocket::Handshake::Handler::Base#header_line def header_line 'HTTP/1.1 101 Web Socket Protocol Handshake' end # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys [ %w[Upgrade WebSocket], %w[Connection Upgrade], [headers[:origin], @handshake.headers['origin']], [headers[:location], @handshake.uri] ] + protocol end def protocol return [] unless @handshake.headers.key?(headers[:protocol].downcase) proto = @handshake.headers[headers[:protocol].downcase] [[headers[:protocol], @handshake.protocols.include?(proto) ? proto : nil]] end end end end end websocket-1.2.9/lib/websocket/handshake/handler/client04.rb0000644000004100000410000000370114001403022023572 0ustar www-datawww-data# frozen_string_literal: true require 'digest/sha1' require 'base64' module WebSocket module Handshake module Handler class Client04 < Client # @see WebSocket::Handshake::Base#valid? def valid? super && verify_accept && verify_protocol end private # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys keys = [ %w[Upgrade websocket], %w[Connection Upgrade] ] host = @handshake.host host += ":#{@handshake.port}" if @handshake.port keys << ['Host', host] keys += super keys << ['Sec-WebSocket-Origin', @handshake.origin] if @handshake.origin keys << ['Sec-WebSocket-Version', @handshake.version] keys << ['Sec-WebSocket-Key', key] keys << ['Sec-WebSocket-Protocol', @handshake.protocols.join(', ')] if @handshake.protocols.any? keys end # Sec-WebSocket-Key value # @return [String] key def key @key ||= Base64.encode64((1..16).map { rand(255).chr } * '').strip end # Value of Sec-WebSocket-Accept that should be delivered back by server # @return [Sering] accept def accept @accept ||= Base64.encode64(Digest::SHA1.digest(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')).strip end # Verify if received header Sec-WebSocket-Accept matches generated one. # @return [Boolean] True if accept is matching. False otherwise(appropriate error is set) def verify_accept raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.headers['sec-websocket-accept'] == accept true end def supported_protocols @handshake.protocols end def provided_protocols @handshake.headers['sec-websocket-protocol'].to_s.split(/ *, */) end end end end end websocket-1.2.9/lib/websocket/handshake/handler/server76.rb0000644000004100000410000000446314001403022023641 0ustar www-datawww-data# frozen_string_literal: true require 'digest/md5' module WebSocket module Handshake module Handler class Server76 < Server75 # @see WebSocket::Handshake::Base#valid? def valid? super && !finishing_line.nil? end private def headers { origin: 'Sec-WebSocket-Origin', location: 'Sec-WebSocket-Location', protocol: 'Sec-WebSocket-Protocol' }.freeze end # @see WebSocket::Handshake::Base#reserved_leftover_lines def reserved_leftover_lines 1 end # @see WebSocket::Handshake::Handler::Base#header_line def header_line 'HTTP/1.1 101 WebSocket Protocol Handshake' end # @see WebSocket::Handshake::Handler::Base#finishing_line def finishing_line @finishing_line ||= challenge_response end # Response to client challenge from request Sec-WebSocket-Key1, Sec-WebSocket-Key2 and leftovers # @return [String] Challenge response or nil if error occured def challenge_response # Refer to 5.2 4-9 of the draft 76 first = numbers_over_spaces(@handshake.headers['sec-websocket-key1'].to_s) second = numbers_over_spaces(@handshake.headers['sec-websocket-key2'].to_s) third = @handshake.leftovers sum = [first].pack('N*') + [second].pack('N*') + third Digest::MD5.digest(sum) end # Calculate numbers over spaces, according to spec 5.2 # @param [String] string Key to parse # @return [Integer] Result of calculations or nil if error occured def numbers_over_spaces(string) numbers = string.scan(/[0-9]/).join.to_i spaces = string.scan(/ /).size # As per 5.2.5, abort the connection if spaces are zero. raise WebSocket::Error::Handshake::InvalidAuthentication if spaces.zero? # As per 5.2.6, abort if numbers is not an integral multiple of spaces raise WebSocket::Error::Handshake::InvalidAuthentication if numbers % spaces != 0 quotient = numbers / spaces raise WebSocket::Error::Handshake::InvalidAuthentication if quotient > 2**32 - 1 quotient end end end end end websocket-1.2.9/lib/websocket/handshake/handler/client76.rb0000644000004100000410000000566314001403022023614 0ustar www-datawww-data# frozen_string_literal: true require 'digest/md5' module WebSocket module Handshake module Handler class Client76 < Client75 # @see WebSocket::Handshake::Base#valid? def valid? super && verify_challenge && verify_protocol end private # @see WebSocket::Handshake::Base#reserved_leftover_lines def reserved_leftover_lines 1 end # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys keys = super keys << ['Sec-WebSocket-Key1', key1] keys << ['Sec-WebSocket-Key2', key2] keys end # @see WebSocket::Handshake::Handler::Base#finishing_line def finishing_line key3 end # Sec-WebSocket-Key1 value # @return [String] key def key1 @key1 ||= generate_key(:key1) end # Sec-WebSocket-Key2 value # @return [String] key def key2 @key2 ||= generate_key(:key2) end # Value of third key, sent in body # @return [String] key def key3 @key3 ||= generate_key3 end # Expected challenge that should be sent by server # @return [String] challenge def challenge return @challenge if defined?(@challenge) key1 && key2 sum = [@key1_number].pack('N*') + [@key2_number].pack('N*') + key3 @challenge = Digest::MD5.digest(sum).strip end # Verify if challenge sent by server match generated one # @return [Boolena] True if challenge matches, false otherwise(sets appropriate error) def verify_challenge raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.leftovers == challenge true end NOISE_CHARS = ("\x21".."\x2f").to_a + ("\x3a".."\x7e").to_a # Generate Sec-WebSocket-Key1 and Sec-WebSocket-Key2 # @param key [String] name of key. Will be used to set number variable needed later. Valid values: key1, key2 # @return [String] generated key def generate_key(key) spaces = rand(1..12) max = 0xffffffff / spaces number = rand(max + 1) instance_variable_set("@#{key}_number", number) key = (number * spaces).to_s rand(1..12).times do char = NOISE_CHARS[rand(NOISE_CHARS.size)] pos = rand(key.size + 1) key[pos...pos] = char end spaces.times do pos = 1 + rand(key.size - 1) key[pos...pos] = ' ' end key end # Generate third key def generate_key3 [rand(0x100000000)].pack('N') + [rand(0x100000000)].pack('N') end def provided_protocols Array(@handshake.headers['sec-websocket-protocol'].to_s.strip) end end end end end websocket-1.2.9/lib/websocket/handshake/handler/client.rb0000644000004100000410000000166214001403022023432 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler class Client < Base private # @see WebSocket::Handshake::Handler::Base#header_line def header_line path = @handshake.path path += '?' + @handshake.query if @handshake.query "GET #{path} HTTP/1.1" end # @see WebSocket::Handshake::Handler::Base#header_handshake_keys def handshake_keys super + @handshake.headers.to_a end # Verify if received header matches with one of the sent ones # @return [Boolean] True if matching. False otherwise(appropriate error is set) def verify_protocol return true if supported_protocols.empty? protos = provided_protocols & supported_protocols raise WebSocket::Error::Handshake::UnsupportedProtocol if protos.empty? true end end end end end websocket-1.2.9/lib/websocket/handshake/handler/base.rb0000644000004100000410000000235414001403022023065 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler # This class and it's descendants are included in client or server handshake in order to extend basic functionality class Base def initialize(handshake) @handshake = handshake end # @see WebSocket::Handshake::Base#to_s def to_s result = [header_line] handshake_keys.each do |key| result << key.join(': ') end result << '' result << finishing_line result.join("\r\n") end def valid? true end private # Set first line of text representation according to specification. # @return [String] First line of HTTP header def header_line '' end # Set handshake headers. Provided as array because some protocol version require specific order of fields. # @return [Array] List of headers as arrays [key, value] def handshake_keys [] end # Set data to send after headers. In most cases it will be blank data. # @return [String] data def finishing_line '' end end end end end websocket-1.2.9/lib/websocket/handshake/handler/server04.rb0000644000004100000410000000274614001403022023632 0ustar www-datawww-data# frozen_string_literal: true require 'digest/sha1' require 'base64' module WebSocket module Handshake module Handler class Server04 < Server # @see WebSocket::Handshake::Base#valid? def valid? super && verify_key end private # @see WebSocket::Handshake::Handler::Base#header_line def header_line 'HTTP/1.1 101 Switching Protocols' end # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys [ %w[Upgrade websocket], %w[Connection Upgrade], ['Sec-WebSocket-Accept', signature] ] + protocol end # Signature of response, created from client request Sec-WebSocket-Key # @return [String] signature def signature return unless key string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11" Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp end def verify_key raise WebSocket::Error::Handshake::InvalidAuthentication unless key true end def key @handshake.headers['sec-websocket-key'] end def protocol return [] unless @handshake.headers.key?('sec-websocket-protocol') protos = @handshake.headers['sec-websocket-protocol'].split(/ *, */) & @handshake.protocols [['Sec-WebSocket-Protocol', protos.first]] end end end end end websocket-1.2.9/lib/websocket/handshake/handler/client11.rb0000644000004100000410000000072314001403022023571 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler class Client11 < Client04 private # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys super.collect do |key_pair| if key_pair[0] == 'Sec-WebSocket-Origin' ['Origin', key_pair[1]] else key_pair end end end end end end end websocket-1.2.9/lib/websocket/handshake/handler/client01.rb0000644000004100000410000000060614001403022023570 0ustar www-datawww-data# frozen_string_literal: true require 'digest/md5' module WebSocket module Handshake module Handler class Client01 < Client76 private # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys keys = super keys << ['Sec-WebSocket-Draft', @handshake.version] keys end end end end end websocket-1.2.9/lib/websocket/handshake/handler/server.rb0000644000004100000410000000021414001403022023452 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler class Server < Base end end end end websocket-1.2.9/lib/websocket/handshake/handler/client75.rb0000644000004100000410000000177014001403022023606 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler class Client75 < Client # @see WebSocket::Handshake::Base#valid? def valid? super && verify_protocol end private # @see WebSocket::Handshake::Handler::Base#handshake_keys def handshake_keys keys = [ %w[Upgrade WebSocket], %w[Connection Upgrade] ] host = @handshake.host host += ":#{@handshake.port}" if @handshake.port keys << ['Host', host] keys << ['Origin', @handshake.origin] if @handshake.origin keys << ['WebSocket-Protocol', @handshake.protocols.first] if @handshake.protocols.any? keys += super keys end def supported_protocols Array(@handshake.protocols.first) end def provided_protocols Array(@handshake.headers['websocket-protocol'].to_s.strip) end end end end end websocket-1.2.9/lib/websocket/handshake/client.rb0000644000004100000410000001126614001403022022016 0ustar www-datawww-data# frozen_string_literal: true require 'uri' module WebSocket module Handshake # Construct or parse a client WebSocket handshake. # # @example # @handshake = WebSocket::Handshake::Client.new(url: 'ws://example.com') # # # Create request # @handshake.to_s # GET /demo HTTP/1.1 # # Upgrade: websocket # # Connection: Upgrade # # Host: example.com # # Origin: http://example.com # # Sec-WebSocket-Version: 13 # # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # # # Parse server response # @handshake << <] :protocols An array of supported sub-protocols # @option args [Integer] :version Version of WebSocket to use. Default: 13 (this is version from RFC) # @option args [Hash] :headers HTTP headers to use in the handshake # # @example # Websocket::Handshake::Client.new(url: "ws://example.com/path?query=true") def initialize(args = {}) super if @url || @uri uri = URI.parse(@url || @uri) @secure ||= (uri.scheme == 'wss') @host ||= uri.host @port ||= uri.port @path ||= uri.path @query ||= uri.query end @path = '/' if @path.nil? || @path.empty? @version ||= DEFAULT_VERSION raise WebSocket::Error::Handshake::NoHostProvided unless @host include_version end rescue_method :initialize # Add text of response from Server. This method will parse content immediately and update state and error(if neccessary) # # @param [String] data Data to add # # @example # @handshake << < "ws://example.com/path?query=true" def uri uri = String.new(secure ? 'wss://' : 'ws://') uri << host uri << ":#{port}" if port uri << path uri << "?#{query}" if query uri end private # Number of lines after header that should be handled as belonging to handshake. Any data after those lines will be handled as leftovers. # @return [Integer] Number of lines def reserved_leftover_lines 0 end # Changes state to error and sets error message # @param [String] message Error message to set def error=(message) @state = :error super end HEADER = /^([^:]+):\s*(.+)$/ # Parse data imported to handshake and sets state to finished if necessary. # @return [Boolean] True if finished parsing. False if not all data received yet. def parse_data header, @leftovers = @data.split("\r\n\r\n", 2) return false unless @leftovers # The whole header has not been received yet. lines = header.split("\r\n") first_line = lines.shift parse_first_line(first_line) lines.each do |line| h = HEADER.match(line) next unless h # Skip any invalid headers key = h[1].strip.downcase val = h[2].strip # If the header is already set and refers to the websocket protocol, append the new value if @headers.key?(key) && key =~ /^(sec-)?websocket-protocol$/ @headers[key] << ", #{val}" else @headers[key] = val end end @state = :finished true end end end end websocket-1.2.9/lib/websocket/handshake/handler.rb0000644000004100000410000000202414001403022022145 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake module Handler autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/handler/base" autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/handler/client" autoload :Client01, "#{::WebSocket::ROOT}/websocket/handshake/handler/client01" autoload :Client04, "#{::WebSocket::ROOT}/websocket/handshake/handler/client04" autoload :Client11, "#{::WebSocket::ROOT}/websocket/handshake/handler/client11" autoload :Client75, "#{::WebSocket::ROOT}/websocket/handshake/handler/client75" autoload :Client76, "#{::WebSocket::ROOT}/websocket/handshake/handler/client76" autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/handler/server" autoload :Server04, "#{::WebSocket::ROOT}/websocket/handshake/handler/server04" autoload :Server75, "#{::WebSocket::ROOT}/websocket/handshake/handler/server75" autoload :Server76, "#{::WebSocket::ROOT}/websocket/handshake/handler/server76" end end end websocket-1.2.9/lib/websocket/handshake/server.rb0000644000004100000410000001307414001403022022045 0ustar www-datawww-data# frozen_string_literal: true module WebSocket module Handshake # Construct or parse a server WebSocket handshake. # # @example # handshake = WebSocket::Handshake::Server.new # # # Parse client request # @handshake << <] :protocols an array of supported sub-protocols # # @example # Websocket::Handshake::Server.new(secure: true) def initialize(args = {}) super @secure ||= false end # Add text of request from Client. This method will parse content immediately and update version, state and error(if neccessary) # # @param [String] data Data to add # # @example # @handshake << <" end end end websocket-1.2.9/Gemfile0000644000004100000410000000055514001403022015023 0ustar www-datawww-data# frozen_string_literal: true source 'http://rubygems.org' group :development do gem 'rake' gem 'rspec', '~> 3.7' # Use same version as Code Climate for consistency with CI # https://github.com/codeclimate/codeclimate-rubocop/blob/master/Gemfile.lock gem 'rubocop', '0.52.1', require: false gem 'rubocop-rspec', '1.21.0', require: false end gemspec websocket-1.2.9/.github/0000755000004100000410000000000014001403022015063 5ustar www-datawww-datawebsocket-1.2.9/.github/workflows/0000755000004100000410000000000014001403022017120 5ustar www-datawww-datawebsocket-1.2.9/.github/workflows/publish.yml0000644000004100000410000000053114001403022021310 0ustar www-datawww-dataname: Publish Gem on: push: tags: - v* jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Release Gem uses: cadwallion/publish-rubygems-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}} websocket-1.2.9/websocket.gemspec0000644000004100000410000000150714001403022017061 0ustar www-datawww-data# frozen_string_literal: true $LOAD_PATH.push File.expand_path('../lib', __FILE__) require 'websocket/version' Gem::Specification.new do |s| s.name = 'websocket' s.version = WebSocket::VERSION s.platform = Gem::Platform::RUBY s.authors = ['Bernard Potocki'] s.email = ['bernard.potocki@imanel.org'] s.homepage = 'http://github.com/imanel/websocket-ruby' s.summary = 'Universal Ruby library to handle WebSocket protocol' s.description = 'Universal Ruby library to handle WebSocket protocol' s.license = 'MIT' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } s.require_paths = ['lib'] s.required_ruby_version = '>= 2.0' end