Team Ai
Datasetpublic

codekingpro/portable-devtools

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