utils.py 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. from __future__ import annotations
  2. import base64
  3. import hashlib
  4. import secrets
  5. import socket
  6. import sys
  7. from .typing import BytesLike
  8. __all__ = ["accept_key", "apply_mask", "get_socket_name"]
  9. GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  10. def generate_key() -> str:
  11. """
  12. Generate a random key for the Sec-WebSocket-Key header.
  13. """
  14. key = secrets.token_bytes(16)
  15. return base64.b64encode(key).decode()
  16. def accept_key(key: str) -> str:
  17. """
  18. Compute the value of the Sec-WebSocket-Accept header.
  19. Args:
  20. key: Value of the Sec-WebSocket-Key header.
  21. """
  22. sha1 = hashlib.sha1((key + GUID).encode()).digest()
  23. return base64.b64encode(sha1).decode()
  24. def apply_mask(data: BytesLike, mask: bytes | bytearray) -> bytes:
  25. """
  26. Apply masking to the data of a WebSocket message.
  27. Args:
  28. data: Data to mask.
  29. mask: 4-bytes mask.
  30. """
  31. if len(mask) != 4:
  32. raise ValueError("mask must contain 4 bytes")
  33. # Python 3.15+ requires C-contiguous buffers for int.from_bytes().
  34. if isinstance(data, memoryview) and not data.c_contiguous:
  35. data = bytes(data)
  36. data_int = int.from_bytes(data, sys.byteorder)
  37. mask_repeated = mask * (len(data) // 4) + mask[: len(data) % 4]
  38. mask_int = int.from_bytes(mask_repeated, sys.byteorder)
  39. return (data_int ^ mask_int).to_bytes(len(data), sys.byteorder)
  40. def get_socket_name(sock: socket.socket) -> str:
  41. """
  42. Return a string representation of :meth:`~socket.socket.getsockname()`.
  43. """
  44. match sock.family:
  45. case socket.AF_INET:
  46. return "%s:%d" % sock.getsockname()
  47. case socket.AF_INET6:
  48. return "[%s]:%d" % sock.getsockname()[:2]
  49. case socket.AF_UNIX:
  50. return str(sock.getsockname())
  51. case _: # pragma: no cover
  52. # Don't crash in case someone runs a WebSocket server
  53. # on a protocol other than IP or Unix domain sockets.
  54. raise AssertionError("unsupported socket family")