You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

181 lines
5.8 KiB

  1. """
  2. :mod:`websockets.handshake` provides helpers for the WebSocket handshake.
  3. See `section 4 of RFC 6455`_.
  4. .. _section 4 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4
  5. Some checks cannot be performed because they depend too much on the
  6. context; instead, they're documented below.
  7. To accept a connection, a server must:
  8. - Read the request, check that the method is GET, and check the headers with
  9. :func:`check_request`,
  10. - Send a 101 response to the client with the headers created by
  11. :func:`build_response` if the request is valid; otherwise, send an
  12. appropriate HTTP error code.
  13. To open a connection, a client must:
  14. - Send a GET request to the server with the headers created by
  15. :func:`build_request`,
  16. - Read the response, check that the status code is 101, and check the headers
  17. with :func:`check_response`.
  18. """
  19. import base64
  20. import binascii
  21. import hashlib
  22. import random
  23. from .exceptions import InvalidHeader, InvalidHeaderValue, InvalidUpgrade
  24. from .headers import parse_connection, parse_upgrade
  25. from .http import Headers, MultipleValuesError
  26. __all__ = ["build_request", "check_request", "build_response", "check_response"]
  27. GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  28. def build_request(headers: Headers) -> str:
  29. """
  30. Build a handshake request to send to the server.
  31. Update request headers passed in argument.
  32. :param headers: request headers
  33. :returns: ``key`` which must be passed to :func:`check_response`
  34. """
  35. raw_key = bytes(random.getrandbits(8) for _ in range(16))
  36. key = base64.b64encode(raw_key).decode()
  37. headers["Upgrade"] = "websocket"
  38. headers["Connection"] = "Upgrade"
  39. headers["Sec-WebSocket-Key"] = key
  40. headers["Sec-WebSocket-Version"] = "13"
  41. return key
  42. def check_request(headers: Headers) -> str:
  43. """
  44. Check a handshake request received from the client.
  45. This function doesn't verify that the request is an HTTP/1.1 or higher GET
  46. request and doesn't perform ``Host`` and ``Origin`` checks. These controls
  47. are usually performed earlier in the HTTP request handling code. They're
  48. the responsibility of the caller.
  49. :param headers: request headers
  50. :returns: ``key`` which must be passed to :func:`build_response`
  51. :raises ~websockets.exceptions.InvalidHandshake: if the handshake request
  52. is invalid; then the server must return 400 Bad Request error
  53. """
  54. connection = sum(
  55. [parse_connection(value) for value in headers.get_all("Connection")], []
  56. )
  57. if not any(value.lower() == "upgrade" for value in connection):
  58. raise InvalidUpgrade("Connection", ", ".join(connection))
  59. upgrade = sum([parse_upgrade(value) for value in headers.get_all("Upgrade")], [])
  60. # For compatibility with non-strict implementations, ignore case when
  61. # checking the Upgrade header. It's supposed to be 'WebSocket'.
  62. if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
  63. raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
  64. try:
  65. s_w_key = headers["Sec-WebSocket-Key"]
  66. except KeyError:
  67. raise InvalidHeader("Sec-WebSocket-Key")
  68. except MultipleValuesError:
  69. raise InvalidHeader(
  70. "Sec-WebSocket-Key", "more than one Sec-WebSocket-Key header found"
  71. )
  72. try:
  73. raw_key = base64.b64decode(s_w_key.encode(), validate=True)
  74. except binascii.Error:
  75. raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key)
  76. if len(raw_key) != 16:
  77. raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key)
  78. try:
  79. s_w_version = headers["Sec-WebSocket-Version"]
  80. except KeyError:
  81. raise InvalidHeader("Sec-WebSocket-Version")
  82. except MultipleValuesError:
  83. raise InvalidHeader(
  84. "Sec-WebSocket-Version", "more than one Sec-WebSocket-Version header found"
  85. )
  86. if s_w_version != "13":
  87. raise InvalidHeaderValue("Sec-WebSocket-Version", s_w_version)
  88. return s_w_key
  89. def build_response(headers: Headers, key: str) -> None:
  90. """
  91. Build a handshake response to send to the client.
  92. Update response headers passed in argument.
  93. :param headers: response headers
  94. :param key: comes from :func:`check_request`
  95. """
  96. headers["Upgrade"] = "websocket"
  97. headers["Connection"] = "Upgrade"
  98. headers["Sec-WebSocket-Accept"] = accept(key)
  99. def check_response(headers: Headers, key: str) -> None:
  100. """
  101. Check a handshake response received from the server.
  102. This function doesn't verify that the response is an HTTP/1.1 or higher
  103. response with a 101 status code. These controls are the responsibility of
  104. the caller.
  105. :param headers: response headers
  106. :param key: comes from :func:`build_request`
  107. :raises ~websockets.exceptions.InvalidHandshake: if the handshake response
  108. is invalid
  109. """
  110. connection = sum(
  111. [parse_connection(value) for value in headers.get_all("Connection")], []
  112. )
  113. if not any(value.lower() == "upgrade" for value in connection):
  114. raise InvalidUpgrade("Connection", " ".join(connection))
  115. upgrade = sum([parse_upgrade(value) for value in headers.get_all("Upgrade")], [])
  116. # For compatibility with non-strict implementations, ignore case when
  117. # checking the Upgrade header. It's supposed to be 'WebSocket'.
  118. if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
  119. raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
  120. try:
  121. s_w_accept = headers["Sec-WebSocket-Accept"]
  122. except KeyError:
  123. raise InvalidHeader("Sec-WebSocket-Accept")
  124. except MultipleValuesError:
  125. raise InvalidHeader(
  126. "Sec-WebSocket-Accept", "more than one Sec-WebSocket-Accept header found"
  127. )
  128. if s_w_accept != accept(key):
  129. raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept)
  130. def accept(key: str) -> str:
  131. sha1 = hashlib.sha1((key + GUID).encode()).digest()
  132. return base64.b64encode(sha1).decode()