|
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import re |
| 8 | +import socket |
8 | 9 | from urllib.parse import urlparse, urlunparse |
9 | 10 | from typing import Optional, List, Set |
10 | 11 | from ipaddress import ip_address, ip_network |
@@ -110,17 +111,49 @@ def validate_url(self, url: str) -> str: |
110 | 111 | return sanitized |
111 | 112 |
|
112 | 113 | def _validate_ip_address(self, host: str) -> None: |
113 | | - """Validate IP address against blocked networks.""" |
| 114 | + """Validate IP address against blocked networks, resolving hostnames via DNS.""" |
114 | 115 | try: |
115 | 116 | ip = ip_address(host) |
116 | | - except ValueError: |
| 117 | + self._check_blocked_ip(ip, host) |
117 | 118 | return |
118 | | - |
| 119 | + except ValueError: |
| 120 | + pass |
| 121 | + |
| 122 | + try: |
| 123 | + addrinfo = socket.getaddrinfo(host, None) |
| 124 | + except socket.gaierror: |
| 125 | + raise InvalidURLError(f"Failed to resolve hostname: {host}") |
| 126 | + |
| 127 | + seen = set() |
| 128 | + for _, _, _, _, sockaddr in addrinfo: |
| 129 | + ip = ip_address(sockaddr[0]) |
| 130 | + if ip in seen: |
| 131 | + continue |
| 132 | + seen.add(ip) |
| 133 | + self._check_blocked_ip(ip, host) |
| 134 | + |
| 135 | + def _check_blocked_ip(self, ip, host): |
| 136 | + """Check a single IP against blocked networks.""" |
119 | 137 | for network in self._blocked_networks: |
120 | 138 | if ip in network: |
121 | 139 | if not (self.allow_localhost and ip.is_loopback): |
122 | 140 | if not (self.allow_private and ip.is_private): |
123 | 141 | raise InvalidURLError(f"Blocked IP address: {host}") |
| 142 | + |
| 143 | + def _validate_port(self, port: Optional[int]) -> None: |
| 144 | + """Validate port number against allowed/blocked lists.""" |
| 145 | + if port is None: |
| 146 | + return |
| 147 | + if port < 1 or port > 65535: |
| 148 | + raise InvalidURLError(f"Invalid port number: {port}") |
| 149 | + if self.allowed_ports is not None: |
| 150 | + if port not in self.allowed_ports: |
| 151 | + raise InvalidURLError( |
| 152 | + f"Port {port} is not allowed. " |
| 153 | + f"Allowed ports: {', '.join(str(p) for p in self.allowed_ports)}" |
| 154 | + ) |
| 155 | + elif port in self.BLOCKED_PORTS: |
| 156 | + raise InvalidURLError(f"Blocked port: {port}") |
124 | 157 |
|
125 | 158 | def _validate_port(self, port: Optional[int]) -> None: |
126 | 159 | """Validate port number against allowed/blocked lists.""" |
|
0 commit comments