Skip to content
This repository
Browse code

Supporting new protocol using Sec-WebSocket-Key1, etc.

  • Loading branch information...
commit 36d4db95b23eba869369f993be15c9eead922e1b 1 parent 47821e8
Hiroshi Ichikawa authored

Showing 1 changed file with 68 additions and 5 deletions. Show diff stats Hide diff stats

  1. 73  lib/web_socket.rb
73  lib/web_socket.rb
@@ -4,6 +4,7 @@
4 4
 
5 5
 require "socket"
6 6
 require "uri"
  7
+require "digest/md5"
7 8
 
8 9
 
9 10
 class WebSocket
@@ -29,6 +30,12 @@ def initialize(arg, params = {})
29 30
         end
30 31
         @path = $1
31 32
         read_header()
  33
+        if !@header["Sec-WebSocket-Key1"] || !@header["Sec-WebSocket-Key2"]
  34
+          raise(WebSocket::Error,
  35
+            "Client speaks old WebSocket protocol, " +
  36
+            "missing header Sec-WebSocket-Key1 and Sec-WebSocket-Key2")
  37
+        end
  38
+        @key3 = read(8)
32 39
         if !@server.accepted_origin?(self.origin)
33 40
           raise(WebSocket::Error,
34 41
             ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
@@ -50,6 +57,10 @@ def initialize(arg, params = {})
50 57
         @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
51 58
         host = uri.host + (uri.port == 80 ? "" : ":#{uri.port}")
52 59
         origin = params[:origin] || "http://#{uri.host}"
  60
+        key1 = generate_key()
  61
+        key2 = generate_key()
  62
+        key3 = generate_key3()
  63
+        
53 64
         @socket = TCPSocket.new(uri.host, uri.port || 80)
54 65
         write(
55 66
           "GET #{@path} HTTP/1.1\r\n" +
@@ -57,15 +68,25 @@ def initialize(arg, params = {})
57 68
           "Connection: Upgrade\r\n" +
58 69
           "Host: #{host}\r\n" +
59 70
           "Origin: #{origin}\r\n" +
60  
-          "\r\n")
  71
+          "Sec-WebSocket-Key1: #{key1}\r\n" +
  72
+          "Sec-WebSocket-Key2: #{key2}\r\n" +
  73
+          "\r\n" +
  74
+          "#{key3}")
61 75
         flush()
  76
+        
62 77
         line = gets().chomp()
63 78
         raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
64 79
         read_header()
65  
-        if @header["WebSocket-Origin"] != origin
  80
+        if @header["Sec-WebSocket-Origin"] != origin
66 81
           raise(WebSocket::Error,
67 82
             "origin doesn't match: '#{@header["WebSocket-Origin"]}' != '#{origin}'")
68 83
         end
  84
+        reply_digest = read(16)
  85
+        expected_digest = security_digest(key1, key2, key3)
  86
+        if reply_digest != expected_digest
  87
+          raise(WebSocket::Error,
  88
+            "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
  89
+        end
69 90
         @handshaked = true
70 91
         
71 92
       end
@@ -81,17 +102,19 @@ def handshake(status = nil, header = {})
81 102
       end
82 103
       status ||= "101 Web Socket Protocol Handshake"
83 104
       def_header = {
84  
-        "WebSocket-Origin" => self.origin,
85  
-        "WebSocket-Location" => self.location,
  105
+        "Sec-WebSocket-Origin" => self.origin,
  106
+        "Sec-WebSocket-Location" => self.location,
86 107
       }
87 108
       header = def_header.merge(header)
88 109
       header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
  110
+      digest = security_digest(
  111
+        @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
89 112
       # Note that Upgrade and Connection must appear in this order.
90 113
       write(
91 114
         "HTTP/1.1 #{status}\r\n" +
92 115
         "Upgrade: WebSocket\r\n" +
93 116
         "Connection: Upgrade\r\n" +
94  
-        "#{header_str}\r\n")
  117
+        "#{header_str}\r\n#{digest}")
95 118
       flush()
96 119
       @handshaked = true
97 120
     end
@@ -139,6 +162,8 @@ def close()
139 162
 
140 163
   private
141 164
     
  165
+    NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
  166
+    
142 167
     def read_header()
143 168
       @header = {}
144 169
       while line = gets()
@@ -163,6 +188,12 @@ def gets(rs = $/)
163 188
       return line
164 189
     end
165 190
     
  191
+    def read(num_bytes)
  192
+      str = @socket.read(num_bytes)
  193
+      $stderr.printf("recv> %p\n", str) if WebSocket.debug
  194
+      return str
  195
+    end
  196
+    
166 197
     def write(data)
167 198
       if WebSocket.debug
168 199
         data.scan(/\G(.*?(\n|\z))/n) do
@@ -176,6 +207,38 @@ def flush()
176 207
       @socket.flush()
177 208
     end
178 209
     
  210
+    def security_digest(key1, key2, key3)
  211
+      bytes1 = websocket_key_to_bytes(key1)
  212
+      bytes2 = websocket_key_to_bytes(key2)
  213
+      return Digest::MD5.digest(bytes1 + bytes2 + key3)
  214
+    end
  215
+    
  216
+    def generate_key()
  217
+      spaces = 1 + rand(12)
  218
+      max = 0xffffffff / spaces
  219
+      number = rand(max + 1)
  220
+      key = (number * spaces).to_s()
  221
+      (1 + rand(12)).times() do
  222
+        char = NOISE_CHARS[rand(NOISE_CHARS.size)]
  223
+        pos = rand(key.size + 1)
  224
+        key[pos...pos] = char
  225
+      end
  226
+      spaces.times() do
  227
+        pos = 1 + rand(key.size - 1)
  228
+        key[pos...pos] = " "
  229
+      end
  230
+      return key
  231
+    end
  232
+    
  233
+    def generate_key3()
  234
+      return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
  235
+    end
  236
+    
  237
+    def websocket_key_to_bytes(key)
  238
+      num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
  239
+      return [num].pack("N")
  240
+    end
  241
+    
179 242
     def force_encoding(str, encoding)
180 243
       if str.respond_to?(:force_encoding)
181 244
         return str.force_encoding(encoding)

0 notes on commit 36d4db9

Please sign in to comment.
Something went wrong with that request. Please try again.