summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/em-websocket-0.5.3/lib/em-websocket/handshake76.rb
blob: c18260de9750eb4d4259aa0ecef673ade67d8584 (plain)
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
require 'digest/md5'

module EventMachine::WebSocket
  module Handshake76
    class << self
      def handshake(headers, path, secure)
        challenge_response = solve_challenge(
          headers['sec-websocket-key1'],
          headers['sec-websocket-key2'],
          headers['third-key']
        )

        scheme = (secure ? "wss" : "ws")
        location = "#{scheme}://#{headers['host']}#{path}"

        upgrade =  "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
        upgrade << "Upgrade: WebSocket\r\n"
        upgrade << "Connection: Upgrade\r\n"
        upgrade << "Sec-WebSocket-Location: #{location}\r\n"
        upgrade << "Sec-WebSocket-Origin: #{headers['origin']}\r\n"
        if protocol = headers['sec-websocket-protocol']
          validate_protocol!(protocol)
          upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
        end
        upgrade << "\r\n"
        upgrade << challenge_response

        return upgrade
      end

      private

      def solve_challenge(first, second, third)
        # Refer to 5.2 4-9 of the draft 76
        sum = [numbers_over_spaces(first)].pack("N*") +
          [numbers_over_spaces(second)].pack("N*") +
          third
        Digest::MD5.digest(sum)
      end

      def numbers_over_spaces(string)
        unless string
          raise HandshakeError, "WebSocket key1 or key2 is missing"
        end

        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 HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0

        # As per 5.2.6, abort if numbers is not an integral multiple of spaces
        if numbers % spaces != 0
          raise HandshakeError, "Invalid Key #{string.inspect}"
        end

        quotient = numbers / spaces

        if quotient > 2**32-1
          raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
        end

        return quotient
      end

      def validate_protocol!(protocol)
        raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
        # TODO: Validate characters
      end
    end
  end
end