diff options
| -rw-r--r-- | app/Utils/httpUtil.php | 5 | ||||
| -rw-r--r-- | lib/Minz/Request.php | 59 |
2 files changed, 46 insertions, 18 deletions
diff --git a/app/Utils/httpUtil.php b/app/Utils/httpUtil.php index ffba3712a..df0d14ab5 100644 --- a/app/Utils/httpUtil.php +++ b/app/Utils/httpUtil.php @@ -10,11 +10,14 @@ final class FreshRSS_http_Util { if (!is_string($domain) || $domain === '') { return ''; } + $domainWide = Minz_Request::serverIsPublic($domain); $port = parse_url($url, PHP_URL_PORT); if (is_int($port)) { $domain .= ':' . $port; } - return self::RETRY_AFTER_PATH . urlencode($domain) . (empty($proxy) ? '' : ('_' . urlencode($proxy))) . '.txt'; + return self::RETRY_AFTER_PATH . urlencode($domain) . + ($domainWide ? '' : '_' . hash('sha256', $url)) . + (empty($proxy) ? '' : '_' . urlencode($proxy)) . '.txt'; } /** diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 973e46f2c..3355058f1 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -369,14 +369,37 @@ class Minz_Request { } /** - * Test if a given server address is publicly accessible. + * Resolve a hostname to its first found IP address (IPv4 or IPv6). * - * Note: for the moment it tests only if address is corresponding to a - * localhost address. + * @param string $hostname the hostname to resolve (from IP to DNS) + * @return string|null the resolved IP address, or null if resolution fails + */ + private static function resolveHostname(string $hostname): ?string { + if (filter_var($hostname, FILTER_VALIDATE_IP) !== false) { + return $hostname; // Already an IP address + } + + $fqdn = rtrim($hostname, '.') . '.'; // Ensure fully qualified domain name + $records = @dns_get_record($fqdn, DNS_A + DNS_AAAA); + if (!is_array($records) || empty($records)) { + return null; + } + + // Return the first resolved IP (IPv4 or IPv6) + if (is_string($records[0]['ip'] ?? null)) { + return $records[0]['ip']; + } + if (is_string($records[0]['ipv6'] ?? null)) { + return $records[0]['ipv6']; + } + return null; + } + + /** + * Test whether a given server address appears to be publicly accessible. * - * @param string $address the address to test, can be an IP or a URL. - * @return bool true if server is accessible, false otherwise. - * @todo improve test with a more valid technique (e.g. test with an external server?) + * @param string $address the address to test, which can be an URL with a DNS or an IP. + * @return bool true if server does not appear to be on some kind of local network, false otherwise (probably public). */ public static function serverIsPublic(string $address): bool { if (strlen($address) < strlen('http://a.bc')) { @@ -387,18 +410,20 @@ class Minz_Request { return false; } - $is_public = !in_array($host, [ - 'localhost', - 'localhost.localdomain', - '[::1]', - 'ip6-localhost', - 'localhost6', - 'localhost6.localdomain6', - ], true); + $is_public = (str_contains($host, '.') || str_contains($host, ':')) // TLD + && !preg_match('/(^|\\.)(ipv6-)?(internal|intranet|lan|local|localdomain|localhost)6?$/', $host) // DNS + && !preg_match('/^(10|127|172[.](1[6-9]|2[0-9]|3[01])|192[.]168)[.]/', $host) // IPv4 + && !preg_match('/^(\\[)?(::1|f[c-d][0-9a-f]{2}:|fe80:)(\\])?/i', $host); // IPv6 - if ($is_public) { - $is_public &= !preg_match('/^(10|127|172[.]16|192[.]168)[.]/', $host); - $is_public &= !preg_match('/^(\[)?(::1$|fc00::|fe80::)/i', $host); + // If $host looks public and is not an IP address, try to resolve it + if ($is_public && filter_var($host, FILTER_VALIDATE_IP) === false) { + $resolvedIp = self::resolveHostname($host); + if ($resolvedIp !== null && $resolvedIp !== $host) { + $resolvedAddress = str_contains($resolvedIp, ':') ? "http://[{$resolvedIp}]/" : "http://{$resolvedIp}/"; + if ($resolvedAddress !== $address) { + $is_public &= self::serverIsPublic($resolvedAddress); + } + } } return (bool)$is_public; |
