Raise ValueError if the URL must not be fetched (SSRF prevention). Only http and https schemes are allowed. Hostnames that resolve to loopback, private, link-local, or otherwise non-routable addresses are rejected.
(url)
| 83 | |
| 84 | |
| 85 | def _validate_fetch_url(url): |
| 86 | """Raise ValueError if the URL must not be fetched (SSRF prevention). |
| 87 | |
| 88 | Only http and https schemes are allowed. Hostnames that resolve to |
| 89 | loopback, private, link-local, or otherwise non-routable addresses |
| 90 | are rejected. |
| 91 | """ |
| 92 | parsed = urlparse(url) |
| 93 | if parsed.scheme not in ("http", "https"): |
| 94 | raise ValueError( |
| 95 | f"URL scheme '{parsed.scheme}' is not allowed; only http and https are permitted." |
| 96 | ) |
| 97 | hostname = parsed.hostname |
| 98 | if not hostname: |
| 99 | raise ValueError("URL has no hostname.") |
| 100 | try: |
| 101 | infos = socket.getaddrinfo(hostname, None) |
| 102 | except socket.gaierror as exc: |
| 103 | raise ValueError(f"Could not resolve hostname '{hostname}': {exc}") from exc |
| 104 | for _family, _type, _proto, _canon, sockaddr in infos: |
| 105 | addr_str = sockaddr[0] |
| 106 | try: |
| 107 | ip = ipaddress.ip_address(addr_str) |
| 108 | except ValueError: |
| 109 | continue |
| 110 | if ip.is_loopback or ip.is_link_local or ip.is_private or ip.is_reserved or ip.is_unspecified: |
| 111 | raise ValueError( |
| 112 | f"URL resolves to a non-routable address ({addr_str}) and cannot be fetched." |
| 113 | ) |
| 114 | |
| 115 | |
| 116 | def _absolutize_logo_url(request, url: str | None) -> str | None: |
no outgoing calls
no test coverage detected