Ticket #4173: rlotun-websocket-76.patch
| File rlotun-websocket-76.patch, 10.1 KB (added by rlotun, 3 years ago) |
|---|
-
twisted/web/test/test_websocket.py
67 67 68 68 69 69 def renderRequest(self, headers=None, url="/test", ssl=False, 70 queued=False ):70 queued=False, body=None): 71 71 """ 72 72 Render a request against C{self.site}, writing the WebSocket 73 73 handshake. … … 85 85 request.requestHeaders.addRawHeader(k, v) 86 86 request.gotLength(0) 87 87 request.requestReceived("GET", url, "HTTP/1.1") 88 if body: 89 request.channel._transferDecoder.finishCallback(body) 88 90 return channel 89 91 90 92 … … 214 216 "WebSocket-Location: ws://localhost/test\r\n\r\n") 215 217 self.assertFalse(channel.transport.disconnected) 216 218 219 def test_render_handShake76(self): 220 """ 221 Test a hixie-76 handShake. 222 """ 223 # we need to construct a challenge 224 key1 = '1x0x0 0y00 0' # 1000000 225 key2 = '1b0b0 000 0' # 1000000 226 body = '12345678' 227 headers = [ 228 ("Upgrade", "WebSocket"), ("Connection", "Upgrade"), 229 ("Host", "localhost"), ("Origin", "http://localhost/"), 230 ("Sec-WebSocket-Key1", key1), ("Sec-WebSocket-Key2", key2)] 231 channel = self.renderRequest(headers=headers, body=body) 217 232 233 self.assertTrue(channel.raw) 234 235 result = channel.transport.written.getvalue() 236 237 headers, response = result.split('\r\n\r\n') 238 239 self.assertEquals( 240 headers, 241 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" 242 "Upgrade: WebSocket\r\n" 243 "Connection: Upgrade\r\n" 244 "Sec-WebSocket-Origin: http://localhost/\r\n" 245 "Sec-WebSocket-Location: ws://localhost/test") 246 247 # check challenge is correct 248 from hashlib import md5 249 import struct 250 self.assertEquals(md5(struct.pack('>ii8s', 500000, 500000, body)).digest(), response) 251 252 self.assertFalse(channel.transport.disconnected) 253 218 254 def test_secureRender(self): 219 255 """ 220 256 If the WebSocket connection is over SSL, the I{WebSocket-Location} -
twisted/web/websocket.py
11 11 @since: 10.1 12 12 """ 13 13 14 from hashlib import md5 15 import struct 14 16 15 17 from twisted.web.http import datetimeToString 18 from twisted.web.http import _IdentityTransferDecoder 16 19 from twisted.web.server import Request, Site, version, unquote 17 20 18 21 22 _ascii_numbers = frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) 19 23 20 24 class WebSocketRequest(Request): 21 25 """ … … 47 51 self.renderWebSocket() 48 52 49 53 54 def _clientHandshake76(self): 55 """ 56 Complete hixie-76 handshake, which consists of a challenge and response. 57 58 If the request is not identified with a proper WebSocket handshake, the 59 connection will be closed. Otherwise, the response to the handshake is 60 sent and a C{WebSocketHandler} is created to handle the request. 61 """ 62 def finish(): 63 self.channel.transport.loseConnection() 64 if self.queued: 65 return finish() 66 67 secKey1 = self.requestHeaders.getRawHeaders("Sec-WebSocket-Key1", []) 68 secKey2 = self.requestHeaders.getRawHeaders("Sec-WebSocket-Key2", []) 69 70 if len(secKey1) != 1 or len(secKey2) != 1: 71 return finish() 72 73 # copied 74 originHeaders = self.requestHeaders.getRawHeaders("Origin", []) 75 if len(originHeaders) != 1: 76 return finish() 77 hostHeaders = self.requestHeaders.getRawHeaders("Host", []) 78 if len(hostHeaders) != 1: 79 return finish() 80 handlerFactory = self.site.handlers.get(self.uri) 81 if not handlerFactory: 82 return finish() 83 transport = WebSocketTransport(self) 84 handler = handlerFactory(transport) 85 transport._attachHandler(handler) 86 87 # key1 and key2 exist and are a string of characters 88 # filter both keys to get a string with all numbers in order 89 key1 = secKey1[0] 90 key2 = secKey2[0] 91 numBuffer1 = ''.join([x for x in key1 if x in _ascii_numbers]) 92 numBuffer2 = ''.join([x for x in key2 if x in _ascii_numbers]) 93 94 # make sure numbers actually exist 95 if not numBuffer1 or not numBuffer2: 96 return finish() 97 98 # these should be int-like 99 num1 = int(numBuffer1) 100 num2 = int(numBuffer2) 101 102 # count the number of spaces in each character string 103 numSpaces1 = 0 104 for x in key1: 105 if x == ' ': 106 numSpaces1 += 1 107 numSpaces2 = 0 108 for x in key2: 109 if x == ' ': 110 numSpaces2 += 1 111 112 # there should be at least one space in each 113 if numSpaces1 == 0 or numSpaces2 == 0: 114 return finish() 115 116 # get two resulting numbers, as specified in hixie-76 117 num1 = num1 / numSpaces1 118 num2 = num2 / numSpaces2 119 120 self.channel.setRawMode() 121 122 def finishHandshake(nonce): 123 """ Receive nonce value from request body, and calculate repsonse. """ 124 protocolHeaders = self.requestHeaders.getRawHeaders( 125 "WebSocket-Protocol", []) 126 if len(protocolHeaders) not in (0, 1): 127 return finish() 128 if protocolHeaders: 129 if protocolHeaders[0] not in self.site.supportedProtocols: 130 return finish() 131 protocolHeader = protocolHeaders[0] 132 else: 133 protocolHeader = None 134 135 handler = handlerFactory(transport) 136 check = originHeaders[0], hostHeaders[0], protocolHeader, handler 137 138 originHeader, hostHeader, protocolHeader, handler = check 139 self.startedWriting = True 140 handshake = [ 141 "HTTP/1.1 101 Web Socket Protocol Handshake", 142 "Upgrade: WebSocket", 143 "Connection: Upgrade"] 144 handshake.append("Sec-WebSocket-Origin: %s" % (originHeader)) 145 if self.isSecure(): 146 scheme = "wss" 147 else: 148 scheme = "ws" 149 handshake.append( 150 "Sec-WebSocket-Location: %s://%s%s" % ( 151 scheme, hostHeader, self.uri)) 152 153 if protocolHeader is not None: 154 handshake.append("Sec-WebSocket-Protocol: %s" % protocolHeader) 155 156 for header in handshake: 157 self.write("%s\r\n" % header) 158 159 self.write("\r\n") 160 161 # concatenate num1 (32 bit in), num2 (32 bit int), nonce, and take md5 of result 162 res = struct.pack('>ii8s', num1, num2, nonce) 163 server_response = md5(res).digest() 164 self.write(server_response) 165 166 # XXX we probably don't want to set _transferDecoder 167 self.channel._transferDecoder = WebSocketFrameDecoder( 168 self, handler) 169 170 # we need the nonce from the request body 171 self.channel._transferDecoder = _IdentityTransferDecoder(0, lambda _ : None, finishHandshake) 172 173 50 174 def _checkClientHandshake(self): 51 175 """ 52 176 Verify client handshake, closing the connection in case of problem. … … 95 219 connection will be closed. Otherwise, the response to the handshake is 96 220 sent and a C{WebSocketHandler} is created to handle the request. 97 221 """ 98 check = self._checkClientHandshake() 99 if check is None: 100 return 101 originHeader, hostHeader, protocolHeader, handler = check 102 self.startedWriting = True 103 handshake = [ 104 "HTTP/1.1 101 Web Socket Protocol Handshake", 105 "Upgrade: WebSocket", 106 "Connection: Upgrade"] 107 handshake.append("WebSocket-Origin: %s" % (originHeader)) 108 if self.isSecure(): 109 scheme = "wss" 222 # check for post-75 handshake requests 223 isSecHandshake = self.requestHeaders.getRawHeaders("Sec-WebSocket-Key1", []) 224 if isSecHandshake: 225 self._clientHandshake76() 110 226 else: 111 scheme = "ws" 112 handshake.append( 113 "WebSocket-Location: %s://%s%s" % ( 114 scheme, hostHeader, self.uri)) 227 check = self._checkClientHandshake() 228 if check is None: 229 return 230 originHeader, hostHeader, protocolHeader, handler = check 231 self.startedWriting = True 232 handshake = [ 233 "HTTP/1.1 101 Web Socket Protocol Handshake", 234 "Upgrade: WebSocket", 235 "Connection: Upgrade"] 236 handshake.append("WebSocket-Origin: %s" % (originHeader)) 237 if self.isSecure(): 238 scheme = "wss" 239 else: 240 scheme = "ws" 241 handshake.append( 242 "WebSocket-Location: %s://%s%s" % ( 243 scheme, hostHeader, self.uri)) 115 244 116 if protocolHeader is not None:117 handshake.append("WebSocket-Protocol: %s" % protocolHeader)245 if protocolHeader is not None: 246 handshake.append("WebSocket-Protocol: %s" % protocolHeader) 118 247 119 for header in handshake:120 self.write("%s\r\n" % header)248 for header in handshake: 249 self.write("%s\r\n" % header) 121 250 122 self.write("\r\n")123 self.channel.setRawMode()124 # XXX we probably don't want to set _transferDecoder125 self.channel._transferDecoder = WebSocketFrameDecoder(126 self, handler)127 return251 self.write("\r\n") 252 self.channel.setRawMode() 253 # XXX we probably don't want to set _transferDecoder 254 self.channel._transferDecoder = WebSocketFrameDecoder( 255 self, handler) 256 return 128 257 129 258 130 259 … … 316 445 317 446 318 447 ___all__ = ["WebSocketHandler", "WebSocketSite"] 448
