Hide keyboard shortcuts

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. 

5 

6import os 

7import uuid 

8from hashlib import sha1 

9from base64 import b64encode 

10 

11from xpra.os_util import strtobytes, bytestostr, monotonic_time 

12from xpra.log import Logger 

13 

14log = Logger("websocket") 

15 

16MAX_WRITE_TIME = 5 

17MAX_READ_TIME = 5 

18READ_CHUNK_SIZE = 4096 

19 

20HEADERS_MODULES = os.environ.get("XPRA_WEBSOCKET_HEADERS_MODULES", "default").split(",") 

21 

22 

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) 

27 

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 

45 

46 

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:] 

62 

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") 

70 

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 

81 

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")