File: draft76.rb

package info (click to toggle)
ruby-websocket-driver 0.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 204 kB
  • sloc: ruby: 1,236; java: 46; ansic: 25; makefile: 3
file content (99 lines) | stat: -rw-r--r-- 2,651 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
module WebSocket
  class Driver

    class Draft76 < Draft75
      BODY_SIZE = 8

      def initialize(socket, options = {})
        super
        input  = (@socket.env['rack.input'] || StringIO.new('')).read
        input  = input.dup if input.frozen?
        @stage = -1
        @body  = input.force_encoding(Encoding::BINARY)

        @headers.clear
        @headers['Upgrade'] = 'WebSocket'
        @headers['Connection'] = 'Upgrade'
        @headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
        @headers['Sec-WebSocket-Location'] = @socket.url
      end

      def version
        'hixie-76'
      end

      def start
        return false unless super
        send_handshake_body
        true
      end

      def close(reason = nil, code = nil)
        return false if @ready_state == 3
        @socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
        @ready_state = 3
        emit(:close, CloseEvent.new(nil, nil))
        true
      end

    private

      def handshake_response
        env     = @socket.env
        key1    = env['HTTP_SEC_WEBSOCKET_KEY1']
        key2    = env['HTTP_SEC_WEBSOCKET_KEY2']

        raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
        raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2

        number1 = number_from_key(key1)
        spaces1 = spaces_in_key(key1)

        number2 = number_from_key(key2)
        spaces2 = spaces_in_key(key2)

        if number1 % spaces1 != 0 or number2 % spaces2 != 0
          raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
        end

        @key_values = [number1 / spaces1, number2 / spaces2]

        start   = 'HTTP/1.1 101 WebSocket Protocol Handshake'
        headers = [start, @headers.to_s, '']
        headers.join("\r\n")
      end

      def handshake_signature
        return nil unless @body.bytesize >= BODY_SIZE

        head = @body[0...BODY_SIZE]
        Digest::MD5.digest((@key_values + [head]).pack('N2A*'))
      end

      def send_handshake_body
        return unless signature = handshake_signature
        @socket.write(signature)
        @stage = 0
        open
        parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
      end

      def parse_leading_byte(octet)
        return super unless octet == 0xFF
        @closing = true
        @length  = 0
        @stage   = 1
      end

      def number_from_key(key)
        number = key.scan(/[0-9]/).join('')
        number == '' ? Float::NAN : number.to_i(10)
      end

      def spaces_in_key(key)
        key.scan(/ /).size
      end
    end

  end
end