isPublicIp($host)) { throw new \InvalidArgumentException('Blocked host.'); } return ['host' => $host, 'ip' => $host, 'port' => $port, 'path' => $parts['path'] ?? '/', 'query' => $parts['query'] ?? '']; } // Resolve all addresses; every one must be public. $records = @dns_get_record($host, DNS_A + DNS_AAAA); $ips = []; foreach ($records ?: [] as $record) { $ip = $record['type'] === 'A' ? ($record['ip'] ?? null) : ($record['ipv6'] ?? null); if ($ip !== null) { $ips[] = $ip; } } if ($ips === []) { // fall back to gethostbyname for simple A records $resolved = gethostbyname($host); if ($resolved === $host) { throw new \InvalidArgumentException('Could not resolve host.'); } $ips = [$resolved]; } foreach ($ips as $ip) { if (! $this->isPublicIp($ip)) { throw new \InvalidArgumentException('Blocked host.'); } } return ['host' => $host, 'ips' => $ips, 'port' => $port, 'path' => $parts['path'] ?? '/', 'query' => $parts['query'] ?? '']; } /** Host allowlist used for direct CDN fetches (thumbnails etc). */ public function isAllowedMediaHost(string $host): bool { return in_array(strtolower($host), config('Site')->urlFetchAllowlist, true); } public function isPublicIp(string $ip): bool { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && str_starts_with($ip, '::ffff:')) { $ip = substr($ip, 7); } $blockedFlags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; if (str_contains($ip, ':')) { return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | $blockedFlags) !== false; } return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | $blockedFlags) !== false; } }