websocket-driver 0.7.1
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 +7 -0
- data/CHANGELOG.md +136 -0
- data/LICENSE.md +12 -0
- data/README.md +380 -0
- data/ext/websocket-driver/WebsocketMaskService.java +57 -0
- data/ext/websocket-driver/extconf.rb +4 -0
- data/ext/websocket-driver/websocket_mask.c +32 -0
- data/lib/websocket/driver.rb +233 -0
- data/lib/websocket/driver/client.rb +140 -0
- data/lib/websocket/driver/draft75.rb +102 -0
- data/lib/websocket/driver/draft76.rb +98 -0
- data/lib/websocket/driver/event_emitter.rb +54 -0
- data/lib/websocket/driver/headers.rb +45 -0
- data/lib/websocket/driver/hybi.rb +414 -0
- data/lib/websocket/driver/hybi/frame.rb +20 -0
- data/lib/websocket/driver/hybi/message.rb +31 -0
- data/lib/websocket/driver/proxy.rb +68 -0
- data/lib/websocket/driver/server.rb +80 -0
- data/lib/websocket/driver/stream_reader.rb +55 -0
- data/lib/websocket/http.rb +15 -0
- data/lib/websocket/http/headers.rb +112 -0
- data/lib/websocket/http/request.rb +45 -0
- data/lib/websocket/http/response.rb +29 -0
- data/lib/websocket/mask.rb +14 -0
- data/lib/websocket/websocket_mask.rb +2 -0
- metadata +142 -0
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            module WebSocket
         | 
| 2 | 
            +
              class Driver
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class Draft75 < Driver
         | 
| 5 | 
            +
                  def initialize(socket, options = {})
         | 
| 6 | 
            +
                    super
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    @stage   = 0
         | 
| 9 | 
            +
                    @closing = false
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    @headers['Upgrade']            = 'WebSocket'
         | 
| 12 | 
            +
                    @headers['Connection']         = 'Upgrade'
         | 
| 13 | 
            +
                    @headers['WebSocket-Origin']   = @socket.env['HTTP_ORIGIN']
         | 
| 14 | 
            +
                    @headers['WebSocket-Location'] = @socket.url
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def version
         | 
| 18 | 
            +
                    'hixie-75'
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def close(reason = nil, code = nil)
         | 
| 22 | 
            +
                    return false if @ready_state == 3
         | 
| 23 | 
            +
                    @ready_state = 3
         | 
| 24 | 
            +
                    emit(:close, CloseEvent.new(nil, nil))
         | 
| 25 | 
            +
                    true
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def parse(chunk)
         | 
| 29 | 
            +
                    return if @ready_state > 1
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    @reader.put(chunk)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    @reader.each_byte do |octet|
         | 
| 34 | 
            +
                      case @stage
         | 
| 35 | 
            +
                        when -1 then
         | 
| 36 | 
            +
                          @body << octet
         | 
| 37 | 
            +
                          send_handshake_body
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                        when 0 then
         | 
| 40 | 
            +
                          parse_leading_byte(octet)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        when 1 then
         | 
| 43 | 
            +
                          @length = (octet & 0x7F) + 128 * @length
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                          if @closing and @length.zero?
         | 
| 46 | 
            +
                            return close
         | 
| 47 | 
            +
                          elsif (octet & 0x80) != 0x80
         | 
| 48 | 
            +
                            if @length.zero?
         | 
| 49 | 
            +
                              @stage = 0
         | 
| 50 | 
            +
                            else
         | 
| 51 | 
            +
                              @skipped = 0
         | 
| 52 | 
            +
                              @stage   = 2
         | 
| 53 | 
            +
                            end
         | 
| 54 | 
            +
                          end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                        when 2 then
         | 
| 57 | 
            +
                          if octet == 0xFF
         | 
| 58 | 
            +
                            @stage = 0
         | 
| 59 | 
            +
                            emit(:message, MessageEvent.new(Driver.encode(@buffer, UNICODE)))
         | 
| 60 | 
            +
                          else
         | 
| 61 | 
            +
                            if @length
         | 
| 62 | 
            +
                              @skipped += 1
         | 
| 63 | 
            +
                              @stage = 0 if @skipped == @length
         | 
| 64 | 
            +
                            else
         | 
| 65 | 
            +
                              @buffer << octet
         | 
| 66 | 
            +
                              return close if @buffer.size > @max_length
         | 
| 67 | 
            +
                            end
         | 
| 68 | 
            +
                          end
         | 
| 69 | 
            +
                      end
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def frame(buffer, type = nil, error_type = nil)
         | 
| 74 | 
            +
                    return queue([buffer, type, error_type]) if @ready_state == 0
         | 
| 75 | 
            +
                    frame = [0x00, buffer, 0xFF].pack('CA*C')
         | 
| 76 | 
            +
                    @socket.write(frame)
         | 
| 77 | 
            +
                    true
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                private
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  def handshake_response
         | 
| 83 | 
            +
                    start   = 'HTTP/1.1 101 Web Socket Protocol Handshake'
         | 
| 84 | 
            +
                    headers = [start, @headers.to_s, '']
         | 
| 85 | 
            +
                    headers.join("\r\n")
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def parse_leading_byte(octet)
         | 
| 89 | 
            +
                    if (octet & 0x80) == 0x80
         | 
| 90 | 
            +
                      @length = 0
         | 
| 91 | 
            +
                      @stage  = 1
         | 
| 92 | 
            +
                    else
         | 
| 93 | 
            +
                      @length  = nil
         | 
| 94 | 
            +
                      @skipped = nil
         | 
| 95 | 
            +
                      @buffer  = []
         | 
| 96 | 
            +
                      @stage   = 2
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            module WebSocket
         | 
| 2 | 
            +
              class Driver
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class Draft76 < Draft75
         | 
| 5 | 
            +
                  BODY_SIZE = 8
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(socket, options = {})
         | 
| 8 | 
            +
                    super
         | 
| 9 | 
            +
                    input  = @socket.env['rack.input']
         | 
| 10 | 
            +
                    @stage = -1
         | 
| 11 | 
            +
                    @body  = (input ? input.read : String.new('')).force_encoding(BINARY)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    @headers.clear
         | 
| 14 | 
            +
                    @headers['Upgrade'] = 'WebSocket'
         | 
| 15 | 
            +
                    @headers['Connection'] = 'Upgrade'
         | 
| 16 | 
            +
                    @headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
         | 
| 17 | 
            +
                    @headers['Sec-WebSocket-Location'] = @socket.url
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def version
         | 
| 21 | 
            +
                    'hixie-76'
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def start
         | 
| 25 | 
            +
                    return false unless super
         | 
| 26 | 
            +
                    send_handshake_body
         | 
| 27 | 
            +
                    true
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def close(reason = nil, code = nil)
         | 
| 31 | 
            +
                    return false if @ready_state == 3
         | 
| 32 | 
            +
                    @socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
         | 
| 33 | 
            +
                    @ready_state = 3
         | 
| 34 | 
            +
                    emit(:close, CloseEvent.new(nil, nil))
         | 
| 35 | 
            +
                    true
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def handshake_response
         | 
| 41 | 
            +
                    env     = @socket.env
         | 
| 42 | 
            +
                    key1    = env['HTTP_SEC_WEBSOCKET_KEY1']
         | 
| 43 | 
            +
                    key2    = env['HTTP_SEC_WEBSOCKET_KEY2']
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
         | 
| 46 | 
            +
                    raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    number1 = number_from_key(key1)
         | 
| 49 | 
            +
                    spaces1 = spaces_in_key(key1)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    number2 = number_from_key(key2)
         | 
| 52 | 
            +
                    spaces2 = spaces_in_key(key2)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    if number1 % spaces1 != 0 or number2 % spaces2 != 0
         | 
| 55 | 
            +
                      raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    @key_values = [number1 / spaces1, number2 / spaces2]
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    start   = 'HTTP/1.1 101 WebSocket Protocol Handshake'
         | 
| 61 | 
            +
                    headers = [start, @headers.to_s, '']
         | 
| 62 | 
            +
                    headers.join("\r\n")
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def handshake_signature
         | 
| 66 | 
            +
                    return nil unless @body.bytesize >= BODY_SIZE
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    head = @body[0...BODY_SIZE]
         | 
| 69 | 
            +
                    Digest::MD5.digest((@key_values + [head]).pack('N2A*'))
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def send_handshake_body
         | 
| 73 | 
            +
                    return unless signature = handshake_signature
         | 
| 74 | 
            +
                    @socket.write(signature)
         | 
| 75 | 
            +
                    @stage = 0
         | 
| 76 | 
            +
                    open
         | 
| 77 | 
            +
                    parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def parse_leading_byte(octet)
         | 
| 81 | 
            +
                    return super unless octet == 0xFF
         | 
| 82 | 
            +
                    @closing = true
         | 
| 83 | 
            +
                    @length  = 0
         | 
| 84 | 
            +
                    @stage   = 1
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def number_from_key(key)
         | 
| 88 | 
            +
                    number = key.scan(/[0-9]/).join('')
         | 
| 89 | 
            +
                    number == '' ? Float::NAN : number.to_i(10)
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def spaces_in_key(key)
         | 
| 93 | 
            +
                    key.scan(/ /).size
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module WebSocket
         | 
| 2 | 
            +
              class Driver
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                module EventEmitter
         | 
| 5 | 
            +
                  def initialize
         | 
| 6 | 
            +
                    @listeners = Hash.new { |h,k| h[k] = [] }
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def add_listener(event, callable = nil, &block)
         | 
| 10 | 
            +
                    listener = callable || block
         | 
| 11 | 
            +
                    @listeners[event.to_s] << listener
         | 
| 12 | 
            +
                    listener
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def on(event, callable = nil, &block)
         | 
| 16 | 
            +
                    if callable
         | 
| 17 | 
            +
                      add_listener(event, callable)
         | 
| 18 | 
            +
                    else
         | 
| 19 | 
            +
                      add_listener(event, &block)
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def remove_listener(event, callable = nil, &block)
         | 
| 24 | 
            +
                    listener = callable || block
         | 
| 25 | 
            +
                    @listeners[event.to_s].delete(listener)
         | 
| 26 | 
            +
                    listener
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def remove_all_listeners(event = nil)
         | 
| 30 | 
            +
                    if event
         | 
| 31 | 
            +
                      @listeners.delete(event.to_s)
         | 
| 32 | 
            +
                    else
         | 
| 33 | 
            +
                      @listeners.clear
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def emit(event, *args)
         | 
| 38 | 
            +
                    @listeners[event.to_s].dup.each do |listener|
         | 
| 39 | 
            +
                      listener.call(*args)
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def listener_count(event)
         | 
| 44 | 
            +
                    return 0 unless @listeners.has_key?(event.to_s)
         | 
| 45 | 
            +
                    @listeners[event.to_s].size
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def listeners(event)
         | 
| 49 | 
            +
                    @listeners[event.to_s]
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            module WebSocket
         | 
| 2 | 
            +
              class Driver
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class Headers
         | 
| 5 | 
            +
                  ALLOWED_DUPLICATES = %w[set-cookie set-cookie2 warning www-authenticate]
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(received = {})
         | 
| 8 | 
            +
                    @raw = received
         | 
| 9 | 
            +
                    clear
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    @received = {}
         | 
| 12 | 
            +
                    @raw.each { |k,v| @received[HTTP.normalize_header(k)] = v }
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def clear
         | 
| 16 | 
            +
                    @sent  = Set.new
         | 
| 17 | 
            +
                    @lines = []
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def [](name)
         | 
| 21 | 
            +
                    @received[HTTP.normalize_header(name)]
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def []=(name, value)
         | 
| 25 | 
            +
                    return if value.nil?
         | 
| 26 | 
            +
                    key = HTTP.normalize_header(name)
         | 
| 27 | 
            +
                    return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
         | 
| 28 | 
            +
                    @lines << "#{name.strip}: #{value.to_s.strip}\r\n"
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def inspect
         | 
| 32 | 
            +
                    @raw.inspect
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def to_h
         | 
| 36 | 
            +
                    @raw.dup
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def to_s
         | 
| 40 | 
            +
                    @lines.join('')
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,414 @@ | |
| 1 | 
            +
            module WebSocket
         | 
| 2 | 
            +
              class Driver
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class Hybi < Driver
         | 
| 5 | 
            +
                  root = File.expand_path('../hybi', __FILE__)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  autoload :Frame,   root + '/frame'
         | 
| 8 | 
            +
                  autoload :Message, root + '/message'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def self.generate_accept(key)
         | 
| 11 | 
            +
                    Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  VERSION = '13'
         | 
| 15 | 
            +
                  GUID    = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  BYTE       = 0b11111111
         | 
| 18 | 
            +
                  FIN = MASK = 0b10000000
         | 
| 19 | 
            +
                  RSV1       = 0b01000000
         | 
| 20 | 
            +
                  RSV2       = 0b00100000
         | 
| 21 | 
            +
                  RSV3       = 0b00010000
         | 
| 22 | 
            +
                  OPCODE     = 0b00001111
         | 
| 23 | 
            +
                  LENGTH     = 0b01111111
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  OPCODES = {
         | 
| 26 | 
            +
                    :continuation => 0,
         | 
| 27 | 
            +
                    :text         => 1,
         | 
| 28 | 
            +
                    :binary       => 2,
         | 
| 29 | 
            +
                    :close        => 8,
         | 
| 30 | 
            +
                    :ping         => 9,
         | 
| 31 | 
            +
                    :pong         => 10
         | 
| 32 | 
            +
                  }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  OPCODE_CODES    = OPCODES.values
         | 
| 35 | 
            +
                  MESSAGE_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
         | 
| 36 | 
            +
                  OPENING_OPCODES = OPCODES.values_at(:text, :binary)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  ERRORS = {
         | 
| 39 | 
            +
                    :normal_closure       => 1000,
         | 
| 40 | 
            +
                    :going_away           => 1001,
         | 
| 41 | 
            +
                    :protocol_error       => 1002,
         | 
| 42 | 
            +
                    :unacceptable         => 1003,
         | 
| 43 | 
            +
                    :encoding_error       => 1007,
         | 
| 44 | 
            +
                    :policy_violation     => 1008,
         | 
| 45 | 
            +
                    :too_large            => 1009,
         | 
| 46 | 
            +
                    :extension_error      => 1010,
         | 
| 47 | 
            +
                    :unexpected_condition => 1011
         | 
| 48 | 
            +
                  }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  ERROR_CODES        = ERRORS.values
         | 
| 51 | 
            +
                  DEFAULT_ERROR_CODE = 1000
         | 
| 52 | 
            +
                  MIN_RESERVED_ERROR = 3000
         | 
| 53 | 
            +
                  MAX_RESERVED_ERROR = 4999
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  PACK_FORMATS = {2 => 'n', 8 => 'Q>'}
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def initialize(socket, options = {})
         | 
| 58 | 
            +
                    super
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    @extensions      = ::WebSocket::Extensions.new
         | 
| 61 | 
            +
                    @stage           = 0
         | 
| 62 | 
            +
                    @masking         = options[:masking]
         | 
| 63 | 
            +
                    @protocols       = options[:protocols] || []
         | 
| 64 | 
            +
                    @protocols       = @protocols.strip.split(/ *, */) if String === @protocols
         | 
| 65 | 
            +
                    @require_masking = options[:require_masking]
         | 
| 66 | 
            +
                    @ping_callbacks  = {}
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    @frame = @message = nil
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    return unless @socket.respond_to?(:env)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
         | 
| 73 | 
            +
                      protos = protos.split(/ *, */) if String === protos
         | 
| 74 | 
            +
                      @protocol = protos.find { |p| @protocols.include?(p) }
         | 
| 75 | 
            +
                    else
         | 
| 76 | 
            +
                      @protocol = nil
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def version
         | 
| 81 | 
            +
                    "hybi-#{VERSION}"
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def add_extension(extension)
         | 
| 85 | 
            +
                    @extensions.add(extension)
         | 
| 86 | 
            +
                    true
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def parse(chunk)
         | 
| 90 | 
            +
                    @reader.put(chunk)
         | 
| 91 | 
            +
                    buffer = true
         | 
| 92 | 
            +
                    while buffer
         | 
| 93 | 
            +
                      case @stage
         | 
| 94 | 
            +
                        when 0 then
         | 
| 95 | 
            +
                          buffer = @reader.read(1)
         | 
| 96 | 
            +
                          parse_opcode(buffer.getbyte(0)) if buffer
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        when 1 then
         | 
| 99 | 
            +
                          buffer = @reader.read(1)
         | 
| 100 | 
            +
                          parse_length(buffer.getbyte(0)) if buffer
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                        when 2 then
         | 
| 103 | 
            +
                          buffer = @reader.read(@frame.length_bytes)
         | 
| 104 | 
            +
                          parse_extended_length(buffer) if buffer
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                        when 3 then
         | 
| 107 | 
            +
                          buffer = @reader.read(4)
         | 
| 108 | 
            +
                          if buffer
         | 
| 109 | 
            +
                            @stage = 4
         | 
| 110 | 
            +
                            @frame.masking_key = buffer
         | 
| 111 | 
            +
                          end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                        when 4 then
         | 
| 114 | 
            +
                          buffer = @reader.read(@frame.length)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                          if buffer
         | 
| 117 | 
            +
                            @stage = 0
         | 
| 118 | 
            +
                            emit_frame(buffer)
         | 
| 119 | 
            +
                          end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                        else
         | 
| 122 | 
            +
                          buffer = nil
         | 
| 123 | 
            +
                      end
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  def binary(message)
         | 
| 128 | 
            +
                    frame(message, :binary)
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  def ping(message = '', &callback)
         | 
| 132 | 
            +
                    @ping_callbacks[message] = callback if callback
         | 
| 133 | 
            +
                    frame(message, :ping)
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  def pong(message = '')
         | 
| 137 | 
            +
                    frame(message, :pong)
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def close(reason = nil, code = nil)
         | 
| 141 | 
            +
                    reason ||= ''
         | 
| 142 | 
            +
                    code   ||= ERRORS[:normal_closure]
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    if @ready_state <= 0
         | 
| 145 | 
            +
                      @ready_state = 3
         | 
| 146 | 
            +
                      emit(:close, CloseEvent.new(code, reason))
         | 
| 147 | 
            +
                      true
         | 
| 148 | 
            +
                    elsif @ready_state == 1
         | 
| 149 | 
            +
                      frame(reason, :close, code)
         | 
| 150 | 
            +
                      @ready_state = 2
         | 
| 151 | 
            +
                      true
         | 
| 152 | 
            +
                    else
         | 
| 153 | 
            +
                      false
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  def frame(buffer, type = nil, code = nil)
         | 
| 158 | 
            +
                    return queue([buffer, type, code]) if @ready_state <= 0
         | 
| 159 | 
            +
                    return false unless @ready_state == 1
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    message = Message.new
         | 
| 162 | 
            +
                    frame   = Frame.new
         | 
| 163 | 
            +
                    is_text = String === buffer
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    message.rsv1   = message.rsv2 = message.rsv3 = false
         | 
| 166 | 
            +
                    message.opcode = OPCODES[type || (is_text ? :text : :binary)]
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                    payload = is_text ? buffer.bytes.to_a : buffer
         | 
| 169 | 
            +
                    payload = [code].pack(PACK_FORMATS[2]).bytes.to_a + payload if code
         | 
| 170 | 
            +
                    message.data = payload.pack('C*')
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    if MESSAGE_OPCODES.include?(message.opcode)
         | 
| 173 | 
            +
                      message = @extensions.process_outgoing_message(message)
         | 
| 174 | 
            +
                    end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    frame.final       = true
         | 
| 177 | 
            +
                    frame.rsv1        = message.rsv1
         | 
| 178 | 
            +
                    frame.rsv2        = message.rsv2
         | 
| 179 | 
            +
                    frame.rsv3        = message.rsv3
         | 
| 180 | 
            +
                    frame.opcode      = message.opcode
         | 
| 181 | 
            +
                    frame.masked      = !!@masking
         | 
| 182 | 
            +
                    frame.masking_key = SecureRandom.random_bytes(4) if frame.masked
         | 
| 183 | 
            +
                    frame.length      = message.data.bytesize
         | 
| 184 | 
            +
                    frame.payload     = message.data
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    send_frame(frame)
         | 
| 187 | 
            +
                    true
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  rescue ::WebSocket::Extensions::ExtensionError => error
         | 
| 190 | 
            +
                    fail(:extension_error, error.message)
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                private
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  def send_frame(frame)
         | 
| 196 | 
            +
                    length = frame.length
         | 
| 197 | 
            +
                    buffer = []
         | 
| 198 | 
            +
                    masked = frame.masked ? MASK : 0
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    buffer[0] = (frame.final ? FIN : 0) |
         | 
| 201 | 
            +
                                (frame.rsv1 ? RSV1 : 0) |
         | 
| 202 | 
            +
                                (frame.rsv2 ? RSV2 : 0) |
         | 
| 203 | 
            +
                                (frame.rsv3 ? RSV3 : 0) |
         | 
| 204 | 
            +
                                frame.opcode
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                    if length <= 125
         | 
| 207 | 
            +
                      buffer[1] = masked | length
         | 
| 208 | 
            +
                    elsif length <= 65535
         | 
| 209 | 
            +
                      buffer[1] = masked | 126
         | 
| 210 | 
            +
                      buffer[2..3] = [length].pack(PACK_FORMATS[2]).bytes.to_a
         | 
| 211 | 
            +
                    else
         | 
| 212 | 
            +
                      buffer[1] = masked | 127
         | 
| 213 | 
            +
                      buffer[2..9] = [length].pack(PACK_FORMATS[8]).bytes.to_a
         | 
| 214 | 
            +
                    end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                    if frame.masked
         | 
| 217 | 
            +
                      buffer.concat(frame.masking_key.bytes.to_a)
         | 
| 218 | 
            +
                      buffer.concat(Mask.mask(frame.payload, frame.masking_key).bytes.to_a)
         | 
| 219 | 
            +
                    else
         | 
| 220 | 
            +
                      buffer.concat(frame.payload.bytes.to_a)
         | 
| 221 | 
            +
                    end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                    @socket.write(buffer.pack('C*'))
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  def handshake_response
         | 
| 227 | 
            +
                    sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
         | 
| 228 | 
            +
                    version = @socket.env['HTTP_SEC_WEBSOCKET_VERSION']
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                    unless version == VERSION
         | 
| 231 | 
            +
                      raise ProtocolError.new("Unsupported WebSocket version: #{VERSION}")
         | 
| 232 | 
            +
                    end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    unless sec_key
         | 
| 235 | 
            +
                      raise ProtocolError.new('Missing handshake request header: Sec-WebSocket-Key')
         | 
| 236 | 
            +
                    end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                    @headers['Upgrade']              = 'websocket'
         | 
| 239 | 
            +
                    @headers['Connection']           = 'Upgrade'
         | 
| 240 | 
            +
                    @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                    @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                    extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
         | 
| 245 | 
            +
                    @headers['Sec-WebSocket-Extensions'] = extensions if extensions
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    start   = 'HTTP/1.1 101 Switching Protocols'
         | 
| 248 | 
            +
                    headers = [start, @headers.to_s, '']
         | 
| 249 | 
            +
                    headers.join("\r\n")
         | 
| 250 | 
            +
                  end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                  def shutdown(code, reason, error = false)
         | 
| 253 | 
            +
                    @frame = @message = nil
         | 
| 254 | 
            +
                    @stage = 5
         | 
| 255 | 
            +
                    @extensions.close
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                    frame(reason, :close, code) if @ready_state < 2
         | 
| 258 | 
            +
                    @ready_state = 3
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                    emit(:error, ProtocolError.new(reason)) if error
         | 
| 261 | 
            +
                    emit(:close, CloseEvent.new(code, reason))
         | 
| 262 | 
            +
                  end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                  def fail(type, message)
         | 
| 265 | 
            +
                    return if @ready_state > 1
         | 
| 266 | 
            +
                    shutdown(ERRORS[type], message, true)
         | 
| 267 | 
            +
                  end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                  def parse_opcode(octet)
         | 
| 270 | 
            +
                    rsvs = [RSV1, RSV2, RSV3].map { |rsv| (octet & rsv) == rsv }
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                    @frame = Frame.new
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                    @frame.final  = (octet & FIN) == FIN
         | 
| 275 | 
            +
                    @frame.rsv1   = rsvs[0]
         | 
| 276 | 
            +
                    @frame.rsv2   = rsvs[1]
         | 
| 277 | 
            +
                    @frame.rsv3   = rsvs[2]
         | 
| 278 | 
            +
                    @frame.opcode = (octet & OPCODE)
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                    @stage = 1
         | 
| 281 | 
            +
             | 
| 282 | 
            +
                    unless @extensions.valid_frame_rsv?(@frame)
         | 
| 283 | 
            +
                      return fail(:protocol_error,
         | 
| 284 | 
            +
                          "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
         | 
| 285 | 
            +
                          ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
         | 
| 286 | 
            +
                          ", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
         | 
| 287 | 
            +
                    end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                    unless OPCODES.values.include?(@frame.opcode)
         | 
| 290 | 
            +
                      return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
         | 
| 291 | 
            +
                    end
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                    unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
         | 
| 294 | 
            +
                      return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
         | 
| 295 | 
            +
                    end
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                    if @message and OPENING_OPCODES.include?(@frame.opcode)
         | 
| 298 | 
            +
                      return fail(:protocol_error, 'Received new data frame but previous continuous frame is unfinished')
         | 
| 299 | 
            +
                    end
         | 
| 300 | 
            +
                  end
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                  def parse_length(octet)
         | 
| 303 | 
            +
                    @frame.masked = (octet & MASK) == MASK
         | 
| 304 | 
            +
                    @frame.length = (octet & LENGTH)
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                    if @frame.length >= 0 and @frame.length <= 125
         | 
| 307 | 
            +
                      @stage = @frame.masked ? 3 : 4
         | 
| 308 | 
            +
                      return unless check_frame_length
         | 
| 309 | 
            +
                    else
         | 
| 310 | 
            +
                      @stage = 2
         | 
| 311 | 
            +
                      @frame.length_bytes = (@frame.length == 126) ? 2 : 8
         | 
| 312 | 
            +
                    end
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                    if @require_masking and not @frame.masked
         | 
| 315 | 
            +
                      return fail(:unacceptable, 'Received unmasked frame but masking is required')
         | 
| 316 | 
            +
                    end
         | 
| 317 | 
            +
                  end
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                  def parse_extended_length(buffer)
         | 
| 320 | 
            +
                    @frame.length = buffer.unpack(PACK_FORMATS[buffer.bytesize]).first
         | 
| 321 | 
            +
                    @stage = @frame.masked ? 3 : 4
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                    unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
         | 
| 324 | 
            +
                      return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
         | 
| 325 | 
            +
                    end
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                    return unless check_frame_length
         | 
| 328 | 
            +
                  end
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                  def check_frame_length
         | 
| 331 | 
            +
                    length = @message ? @message.data.bytesize : 0
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                    if length + @frame.length > @max_length
         | 
| 334 | 
            +
                      fail(:too_large, 'WebSocket frame length too large')
         | 
| 335 | 
            +
                      false
         | 
| 336 | 
            +
                    else
         | 
| 337 | 
            +
                      true
         | 
| 338 | 
            +
                    end
         | 
| 339 | 
            +
                  end
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                  def emit_frame(buffer)
         | 
| 342 | 
            +
                    frame    = @frame
         | 
| 343 | 
            +
                    opcode   = frame.opcode
         | 
| 344 | 
            +
                    payload  = frame.payload = Mask.mask(buffer, @frame.masking_key)
         | 
| 345 | 
            +
                    bytesize = payload.bytesize
         | 
| 346 | 
            +
                    bytes    = payload.bytes.to_a
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                    @frame = nil
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                    case opcode
         | 
| 351 | 
            +
                      when OPCODES[:continuation] then
         | 
| 352 | 
            +
                        return fail(:protocol_error, 'Received unexpected continuation frame') unless @message
         | 
| 353 | 
            +
                        @message << frame
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                      when OPCODES[:text], OPCODES[:binary] then
         | 
| 356 | 
            +
                        @message = Message.new
         | 
| 357 | 
            +
                        @message << frame
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                      when OPCODES[:close] then
         | 
| 360 | 
            +
                        code   = (bytesize >= 2) ? payload.unpack(PACK_FORMATS[2]).first : nil
         | 
| 361 | 
            +
                        reason = (bytesize > 2)  ? Driver.encode(bytes[2..-1] || [], UNICODE) : nil
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                        unless (bytesize == 0) or
         | 
| 364 | 
            +
                               (code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or
         | 
| 365 | 
            +
                               ERROR_CODES.include?(code)
         | 
| 366 | 
            +
                          code = ERRORS[:protocol_error]
         | 
| 367 | 
            +
                        end
         | 
| 368 | 
            +
             | 
| 369 | 
            +
                        if bytesize > 125 or (bytesize > 2 and reason.nil?)
         | 
| 370 | 
            +
                          code = ERRORS[:protocol_error]
         | 
| 371 | 
            +
                        end
         | 
| 372 | 
            +
             | 
| 373 | 
            +
                        shutdown(code || DEFAULT_ERROR_CODE, reason || '')
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                      when OPCODES[:ping] then
         | 
| 376 | 
            +
                        frame(payload, :pong)
         | 
| 377 | 
            +
                        emit(:ping, PingEvent.new(payload))
         | 
| 378 | 
            +
             | 
| 379 | 
            +
                      when OPCODES[:pong] then
         | 
| 380 | 
            +
                        message = Driver.encode(payload, UNICODE)
         | 
| 381 | 
            +
                        callback = @ping_callbacks[message]
         | 
| 382 | 
            +
                        @ping_callbacks.delete(message)
         | 
| 383 | 
            +
                        callback.call if callback
         | 
| 384 | 
            +
                        emit(:pong, PongEvent.new(payload))
         | 
| 385 | 
            +
                    end
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                    emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
         | 
| 388 | 
            +
                  end
         | 
| 389 | 
            +
             | 
| 390 | 
            +
                  def emit_message
         | 
| 391 | 
            +
                    message  = @extensions.process_incoming_message(@message)
         | 
| 392 | 
            +
                    @message = nil
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                    payload = message.data
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                    case message.opcode
         | 
| 397 | 
            +
                      when OPCODES[:text] then
         | 
| 398 | 
            +
                        payload = Driver.encode(payload, UNICODE)
         | 
| 399 | 
            +
                      when OPCODES[:binary]
         | 
| 400 | 
            +
                        payload = payload.bytes.to_a
         | 
| 401 | 
            +
                    end
         | 
| 402 | 
            +
             | 
| 403 | 
            +
                    if payload
         | 
| 404 | 
            +
                      emit(:message, MessageEvent.new(payload))
         | 
| 405 | 
            +
                    else
         | 
| 406 | 
            +
                      fail(:encoding_error, 'Could not decode a text frame as UTF-8')
         | 
| 407 | 
            +
                    end
         | 
| 408 | 
            +
                  rescue ::WebSocket::Extensions::ExtensionError => error
         | 
| 409 | 
            +
                    fail(:extension_error, error.message)
         | 
| 410 | 
            +
                  end
         | 
| 411 | 
            +
                end
         | 
| 412 | 
            +
             | 
| 413 | 
            +
              end
         | 
| 414 | 
            +
            end
         |