diff --git a/app/Utils/httpUtil.php b/app/Utils/httpUtil.php index 39f9ae12d..0ddf5f6da 100644 --- a/app/Utils/httpUtil.php +++ b/app/Utils/httpUtil.php @@ -295,6 +295,14 @@ final class FreshRSS_http_Util { ($url1['port'] ?? '') === ($url2['port'] ?? ''); } + /** + * Return 0 if values on either side are equal ignoring the HTTP vs HTTPS differences, or 1/-1 if they differ. + */ + public static function compareUrlIgnoringHttps(string $url1, string $url2): int { + $normalizeScheme = static fn(string $url): string => preg_replace('#^https?://#i', '//', trim($url)) ?? $url; + return $normalizeScheme($url1) <=> $normalizeScheme($url2); + } + /** * Returns a value for CURLOPT_RESOLVE as an array, null if no allowed IPs were found, false if the domain failed to resolve. * diff --git a/p/api/pshb.php b/p/api/pshb.php index 5937f579b..5dacd48c7 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -121,14 +121,14 @@ if ($httpLink !== '' && preg_match_all('/<([^>]+)>;\\s*rel="([^"]+)"/', $httpLin // } if (!empty($links['self'])) { $httpSelf = FreshRSS_http_Util::checkUrl($links['self']) ?: ''; - if ($self !== '' && $self !== $httpSelf) { + if ($self !== '' && FreshRSS_http_Util::compareUrlIgnoringHttps($self, $httpSelf) !== 0) { Minz_Log::warning('Warning: Self URL mismatch between XML [' . $self . '] and HTTP!: ' . $httpSelf, PSHB_LOG); } $self = $httpSelf; } } -if ($self !== $canonical) { +if (FreshRSS_http_Util::compareUrlIgnoringHttps($self, $canonical) !== 0) { //header('HTTP/1.1 422 Unprocessable Entity'); Minz_Log::warning('Warning: Self URL [' . $self . '] does not match registered canonical URL!: ' . $canonical, PSHB_LOG); //die('Self URL does not match registered canonical URL!'); diff --git a/tests/app/Utils/httpUtilTest.php b/tests/app/Utils/httpUtilTest.php new file mode 100644 index 000000000..ee00ec06c --- /dev/null +++ b/tests/app/Utils/httpUtilTest.php @@ -0,0 +1,41 @@ + */ + public static function provideUrlsIgnoringHttps(): array { + return [ + // Only the scheme differs → equal + ['http://www.blogger.com/feeds/1/posts', 'https://www.blogger.com/feeds/1/posts', true], + ['https://example.net/feed.xml?a=1&b=2', 'http://example.net/feed.xml?a=1&b=2', true], + ['HTTP://Example.net/Feed', 'https://Example.net/Feed', true], + ['HTTPS://Example.net/Feed', 'http://Example.net/Feed', true], + + // Fully identical → equal + ['https://example.net/feed', 'https://example.net/feed', true], + ['', '', true], + + // Path differs → not equal (scheme-only tolerance must not hide real mismatches) + ['http://example.net/a', 'https://example.net/b', false], + // Trailing slash is a path difference → not equal + ['http://example.net/', 'https://example.net', false], + // Host differs → not equal + ['http://a.example.net/feed', 'https://b.example.net/feed', false], + // Query differs → not equal + ['https://example.net/feed?a=1', 'http://example.net/feed?a=2', false], + // Non-http(s) schemes are compared as-is + ['ftp://example.net/feed', 'https://example.net/feed', false], + ]; + } +}