| from __future__ import annotations |
| |
| from urllib.parse import quote as encode_uri_component |
| |
| TYPE_CHECKING = False |
| if TYPE_CHECKING: |
| from collections.abc import Sequence |
| |
| ASCII_LETTERS_AND_DIGITS = ( |
| "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" |
| ) |
| HEXDIGITS = "0123456789" "abcdef" "ABCDEF" |
| |
| ENCODE_DEFAULT_CHARS = ";/?:@&=+$,-_.!~*'()#" |
| ENCODE_COMPONENT_CHARS = "-_.!~*'()" |
| |
| encode_cache: dict[str, list[str]] = {} |
| |
| |
| # Create a lookup array where anything but characters in `chars` string |
| # and alphanumeric chars is percent-encoded. |
| def get_encode_cache(exclude: str) -> Sequence[str]: |
| if exclude in encode_cache: |
| return encode_cache[exclude] |
| |
| cache: list[str] = [] |
| encode_cache[exclude] = cache |
| |
| for i in range(128): |
| ch = chr(i) |
| |
| if ch in ASCII_LETTERS_AND_DIGITS: |
| # always allow unencoded alphanumeric characters |
| cache.append(ch) |
| else: |
| cache.append("%" + ("0" + hex(i)[2:].upper())[-2:]) |
| |
| for i in range(len(exclude)): |
| cache[ord(exclude[i])] = exclude[i] |
| |
| return cache |
| |
| |
| # Encode unsafe characters with percent-encoding, skipping already |
| # encoded sequences. |
| # |
| # - string - string to encode |
| # - exclude - list of characters to ignore (in addition to a-zA-Z0-9) |
| # - keepEscaped - don't encode '%' in a correct escape sequence (default: true) |
| def encode( |
| string: str, exclude: str = ENCODE_DEFAULT_CHARS, *, keep_escaped: bool = True |
| ) -> str: |
| result = "" |
| |
| cache = get_encode_cache(exclude) |
| |
| l = len(string) # noqa: E741 |
| i = 0 |
| while i < l: |
| code = ord(string[i]) |
| |
| # % |
| if keep_escaped and code == 0x25 and i + 2 < l: |
| if all(c in HEXDIGITS for c in string[i + 1 : i + 3]): |
| result += string[i : i + 3] |
| i += 2 |
| i += 1 # JS for loop statement3 |
| continue |
| |
| if code < 128: |
| result += cache[code] |
| i += 1 # JS for loop statement3 |
| continue |
| |
| if code >= 0xD800 and code <= 0xDFFF: |
| if code >= 0xD800 and code <= 0xDBFF and i + 1 < l: |
| next_code = ord(string[i + 1]) |
| if next_code >= 0xDC00 and next_code <= 0xDFFF: |
| result += encode_uri_component(string[i] + string[i + 1]) |
| i += 1 |
| i += 1 # JS for loop statement3 |
| continue |
| result += "%EF%BF%BD" |
| i += 1 # JS for loop statement3 |
| continue |
| |
| result += encode_uri_component(string[i]) |
| i += 1 # JS for loop statement3 |
| |
| return result |