handshake.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """
  2. :mod:`websockets.legacy.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. from typing import List
  22. from ..datastructures import Headers, MultipleValuesError
  23. from ..exceptions import InvalidHeader, InvalidHeaderValue, InvalidUpgrade
  24. from ..headers import parse_connection, parse_upgrade
  25. from ..typing import ConnectionOption, UpgradeProtocol
  26. from ..utils import accept_key as accept, generate_key
  27. __all__ = ["build_request", "check_request", "build_response", "check_response"]
  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. key = generate_key()
  36. headers["Upgrade"] = "websocket"
  37. headers["Connection"] = "Upgrade"
  38. headers["Sec-WebSocket-Key"] = key
  39. headers["Sec-WebSocket-Version"] = "13"
  40. return key
  41. def check_request(headers: Headers) -> str:
  42. """
  43. Check a handshake request received from the client.
  44. This function doesn't verify that the request is an HTTP/1.1 or higher GET
  45. request and doesn't perform ``Host`` and ``Origin`` checks. These controls
  46. are usually performed earlier in the HTTP request handling code. They're
  47. the responsibility of the caller.
  48. :param headers: request headers
  49. :returns: ``key`` which must be passed to :func:`build_response`
  50. :raises ~websockets.exceptions.InvalidHandshake: if the handshake request
  51. is invalid; then the server must return 400 Bad Request error
  52. """
  53. connection: List[ConnectionOption] = sum(
  54. [parse_connection(value) for value in headers.get_all("Connection")], []
  55. )
  56. if not any(value.lower() == "upgrade" for value in connection):
  57. raise InvalidUpgrade("Connection", ", ".join(connection))
  58. upgrade: List[UpgradeProtocol] = sum(
  59. [parse_upgrade(value) for value in headers.get_all("Upgrade")], []
  60. )
  61. # For compatibility with non-strict implementations, ignore case when
  62. # checking the Upgrade header. The RFC always uses "websocket", except
  63. # in section 11.2. (IANA registration) where it uses "WebSocket".
  64. if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
  65. raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
  66. try:
  67. s_w_key = headers["Sec-WebSocket-Key"]
  68. except KeyError as exc:
  69. raise InvalidHeader("Sec-WebSocket-Key") from exc
  70. except MultipleValuesError as exc:
  71. raise InvalidHeader(
  72. "Sec-WebSocket-Key", "more than one Sec-WebSocket-Key header found"
  73. ) from exc
  74. try:
  75. raw_key = base64.b64decode(s_w_key.encode(), validate=True)
  76. except binascii.Error as exc:
  77. raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) from exc
  78. if len(raw_key) != 16:
  79. raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key)
  80. try:
  81. s_w_version = headers["Sec-WebSocket-Version"]
  82. except KeyError as exc:
  83. raise InvalidHeader("Sec-WebSocket-Version") from exc
  84. except MultipleValuesError as exc:
  85. raise InvalidHeader(
  86. "Sec-WebSocket-Version", "more than one Sec-WebSocket-Version header found"
  87. ) from exc
  88. if s_w_version != "13":
  89. raise InvalidHeaderValue("Sec-WebSocket-Version", s_w_version)
  90. return s_w_key
  91. def build_response(headers: Headers, key: str) -> None:
  92. """
  93. Build a handshake response to send to the client.
  94. Update response headers passed in argument.
  95. :param headers: response headers
  96. :param key: comes from :func:`check_request`
  97. """
  98. headers["Upgrade"] = "websocket"
  99. headers["Connection"] = "Upgrade"
  100. headers["Sec-WebSocket-Accept"] = accept(key)
  101. def check_response(headers: Headers, key: str) -> None:
  102. """
  103. Check a handshake response received from the server.
  104. This function doesn't verify that the response is an HTTP/1.1 or higher
  105. response with a 101 status code. These controls are the responsibility of
  106. the caller.
  107. :param headers: response headers
  108. :param key: comes from :func:`build_request`
  109. :raises ~websockets.exceptions.InvalidHandshake: if the handshake response
  110. is invalid
  111. """
  112. connection: List[ConnectionOption] = sum(
  113. [parse_connection(value) for value in headers.get_all("Connection")], []
  114. )
  115. if not any(value.lower() == "upgrade" for value in connection):
  116. raise InvalidUpgrade("Connection", " ".join(connection))
  117. upgrade: List[UpgradeProtocol] = sum(
  118. [parse_upgrade(value) for value in headers.get_all("Upgrade")], []
  119. )
  120. # For compatibility with non-strict implementations, ignore case when
  121. # checking the Upgrade header. The RFC always uses "websocket", except
  122. # in section 11.2. (IANA registration) where it uses "WebSocket".
  123. if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
  124. raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
  125. try:
  126. s_w_accept = headers["Sec-WebSocket-Accept"]
  127. except KeyError as exc:
  128. raise InvalidHeader("Sec-WebSocket-Accept") from exc
  129. except MultipleValuesError as exc:
  130. raise InvalidHeader(
  131. "Sec-WebSocket-Accept", "more than one Sec-WebSocket-Accept header found"
  132. ) from exc
  133. if s_w_accept != accept(key):
  134. raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept)