Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/net/websockets/common.py : 85%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2019 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import os
7import uuid
8from hashlib import sha1
9from base64 import b64encode
11from xpra.os_util import strtobytes, bytestostr, monotonic_time
12from xpra.log import Logger
14log = Logger("websocket")
16MAX_WRITE_TIME = 5
17MAX_READ_TIME = 5
18READ_CHUNK_SIZE = 4096
20HEADERS_MODULES = os.environ.get("XPRA_WEBSOCKET_HEADERS_MODULES", "default").split(",")
23def make_websocket_accept_hash(key):
24 GUID = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
25 accept = sha1(strtobytes(key) + GUID).digest()
26 return b64encode(accept)
28def get_headers(host, port):
29 headers = {}
30 for mod_name in HEADERS_MODULES:
31 try:
32 header_module = __import__("xpra.net.websockets.headers.%s" % mod_name, {}, {}, ["get_headers"])
33 v = header_module.get_headers(host, port)
34 log("%s.get_headers(%s, %s)=%s", mod_name, host, port, v)
35 headers.update(v)
36 except ImportError as e:
37 log("import %s", mod_name, exc_info=True)
38 log.error("Error: websocket header module %s not available", mod_name)
39 log.error(" %s", e)
40 except Exception as e:
41 log("get_headers %s", mod_name, exc_info=True)
42 log.error("Error: cannot get headers from '%s'", mod_name)
43 log.error(" %s", e)
44 return headers
47def client_upgrade(read, write, host, port):
48 lines = [b"GET / HTTP/1.1"]
49 key = b64encode(uuid.uuid4().bytes)
50 headers = get_headers(host, port)
51 headers[b"Sec-WebSocket-Key"] = key
52 for k,v in headers.items():
53 lines.append(b"%s: %s" % (k, v))
54 lines.append(b"")
55 lines.append(b"")
56 http_request = b"\r\n".join(lines)
57 log("client_upgrade: sending http headers: %s", headers)
58 now = monotonic_time()
59 while http_request and monotonic_time()-now<MAX_WRITE_TIME:
60 w = write(http_request)
61 http_request = http_request[w:]
63 now = monotonic_time()
64 response = b""
65 while ("sec-websocket-protocol" not in response.decode("utf-8").lower()) and monotonic_time()-now<MAX_READ_TIME:
66 response += read(READ_CHUNK_SIZE)
67 headers = parse_response_header(response)
68 verify_response_headers(headers, key)
69 log("client_upgrade: done")
71def parse_response_header(response):
72 #parse response:
73 head = response.split(b"\r\n\r\n", 1)[0]
74 lines = head.split(b"\r\n")
75 headers = {}
76 for line in lines:
77 parts = line.split(b": ", 1)
78 if len(parts)==2:
79 headers[parts[0].lower()] = parts[1]
80 return headers
82def verify_response_headers(headers, key):
83 log("verify_response_headers(%s)", headers)
84 if not headers:
85 raise Exception("no http headers found in response")
86 upgrade = headers.get(b"upgrade", b"")
87 if upgrade!=b"websocket":
88 raise Exception("invalid http upgrade: '%s'" % upgrade)
89 protocol = headers.get(b"sec-websocket-protocol", b"")
90 if protocol!=b"binary":
91 raise Exception("invalid websocket protocol: '%s'" % protocol)
92 accept_key = headers.get(b"sec-websocket-accept", b"")
93 if not accept_key:
94 raise Exception("websocket accept key is missing")
95 expected_key = make_websocket_accept_hash(key)
96 if bytestostr(accept_key)!=bytestostr(expected_key):
97 raise Exception("websocket accept key is invalid")