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.

uri.py 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. from __future__ import annotations
  2. import dataclasses
  3. import urllib.parse
  4. from typing import Optional, Tuple
  5. from . import exceptions
  6. __all__ = ["parse_uri", "WebSocketURI"]
  7. @dataclasses.dataclass
  8. class WebSocketURI:
  9. """
  10. WebSocket URI.
  11. Attributes:
  12. secure: :obj:`True` for a ``wss`` URI, :obj:`False` for a ``ws`` URI.
  13. host: Normalized to lower case.
  14. port: Always set even if it's the default.
  15. path: May be empty.
  16. query: May be empty if the URI doesn't include a query component.
  17. username: Available when the URI contains `User Information`_.
  18. password: Available when the URI contains `User Information`_.
  19. .. _User Information: https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2.1
  20. """
  21. secure: bool
  22. host: str
  23. port: int
  24. path: str
  25. query: str
  26. username: Optional[str] = None
  27. password: Optional[str] = None
  28. @property
  29. def resource_name(self) -> str:
  30. if self.path:
  31. resource_name = self.path
  32. else:
  33. resource_name = "/"
  34. if self.query:
  35. resource_name += "?" + self.query
  36. return resource_name
  37. @property
  38. def user_info(self) -> Optional[Tuple[str, str]]:
  39. if self.username is None:
  40. return None
  41. assert self.password is not None
  42. return (self.username, self.password)
  43. # All characters from the gen-delims and sub-delims sets in RFC 3987.
  44. DELIMS = ":/?#[]@!$&'()*+,;="
  45. def parse_uri(uri: str) -> WebSocketURI:
  46. """
  47. Parse and validate a WebSocket URI.
  48. Args:
  49. uri: WebSocket URI.
  50. Returns:
  51. WebSocketURI: Parsed WebSocket URI.
  52. Raises:
  53. InvalidURI: if ``uri`` isn't a valid WebSocket URI.
  54. """
  55. parsed = urllib.parse.urlparse(uri)
  56. if parsed.scheme not in ["ws", "wss"]:
  57. raise exceptions.InvalidURI(uri, "scheme isn't ws or wss")
  58. if parsed.hostname is None:
  59. raise exceptions.InvalidURI(uri, "hostname isn't provided")
  60. if parsed.fragment != "":
  61. raise exceptions.InvalidURI(uri, "fragment identifier is meaningless")
  62. secure = parsed.scheme == "wss"
  63. host = parsed.hostname
  64. port = parsed.port or (443 if secure else 80)
  65. path = parsed.path
  66. query = parsed.query
  67. username = parsed.username
  68. password = parsed.password
  69. # urllib.parse.urlparse accepts URLs with a username but without a
  70. # password. This doesn't make sense for HTTP Basic Auth credentials.
  71. if username is not None and password is None:
  72. raise exceptions.InvalidURI(uri, "username provided without password")
  73. try:
  74. uri.encode("ascii")
  75. except UnicodeEncodeError:
  76. # Input contains non-ASCII characters.
  77. # It must be an IRI. Convert it to a URI.
  78. host = host.encode("idna").decode()
  79. path = urllib.parse.quote(path, safe=DELIMS)
  80. query = urllib.parse.quote(query, safe=DELIMS)
  81. if username is not None:
  82. assert password is not None
  83. username = urllib.parse.quote(username, safe=DELIMS)
  84. password = urllib.parse.quote(password, safe=DELIMS)
  85. return WebSocketURI(secure, host, port, path, query, username, password)