Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

handshake.py 5.4KB

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