(host: str)
| 346 | |
| 347 | |
| 348 | def encode_host(host: str) -> str: |
| 349 | if not host: |
| 350 | return "" |
| 351 | |
| 352 | elif IPv4_STYLE_HOSTNAME.match(host): |
| 353 | # Validate IPv4 hostnames like #.#.#.# |
| 354 | # |
| 355 | # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 |
| 356 | # |
| 357 | # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet |
| 358 | try: |
| 359 | ipaddress.IPv4Address(host) |
| 360 | except ipaddress.AddressValueError: |
| 361 | raise InvalidURL(f"Invalid IPv4 address: {host!r}") |
| 362 | return host |
| 363 | |
| 364 | elif IPv6_STYLE_HOSTNAME.match(host): |
| 365 | # Validate IPv6 hostnames like [...] |
| 366 | # |
| 367 | # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 |
| 368 | # |
| 369 | # "A host identified by an Internet Protocol literal address, version 6 |
| 370 | # [RFC3513] or later, is distinguished by enclosing the IP literal |
| 371 | # within square brackets ("[" and "]"). This is the only place where |
| 372 | # square bracket characters are allowed in the URI syntax." |
| 373 | try: |
| 374 | ipaddress.IPv6Address(host[1:-1]) |
| 375 | except ipaddress.AddressValueError: |
| 376 | raise InvalidURL(f"Invalid IPv6 address: {host!r}") |
| 377 | return host[1:-1] |
| 378 | |
| 379 | elif host.isascii(): |
| 380 | # Regular ASCII hostnames |
| 381 | # |
| 382 | # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 |
| 383 | # |
| 384 | # reg-name = *( unreserved / pct-encoded / sub-delims ) |
| 385 | WHATWG_SAFE = '"`{}%|\\' |
| 386 | return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE) |
| 387 | |
| 388 | # IDNA hostnames |
| 389 | try: |
| 390 | return idna.encode(host.lower()).decode("ascii") |
| 391 | except idna.IDNAError: |
| 392 | raise InvalidURL(f"Invalid IDNA hostname: {host!r}") |
| 393 | |
| 394 | |
| 395 | def normalize_port(port: str | int | None, scheme: str) -> int | None: |
no test coverage detected