diff options
| author | 2025-07-19 22:52:06 +0200 | |
|---|---|---|
| committer | 2025-07-19 22:52:06 +0200 | |
| commit | 01eae00ca2a5532b18682dfc55c83fce8725ee30 (patch) | |
| tree | 969da6ceff8b1b06ee8ba98888f30cb869b17e5f | |
| parent | 0d158f37621e116767383c0fa7b8c4a141a360a7 (diff) | |
WebSub: only perform a redirect when coming from WebSub (#7738)
And add support for HTTP Link header for "self" URL
Changing URL based on "self" URL will only be done when coming from a WebSub push
fix https://github.com/FreshRSS/FreshRSS/issues/7737
| -rwxr-xr-x | app/Controllers/feedController.php | 41 | ||||
| -rw-r--r-- | app/Models/Feed.php | 4 | ||||
| -rw-r--r-- | p/api/pshb.php | 36 |
3 files changed, 55 insertions, 26 deletions
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 075ecb5e6..2aae5a0a8 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -393,12 +393,14 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { } /** + * @param \SimplePie\SimplePie|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates + * @param string $selfUrl Used by WebSub (PubSubHubbub) to override the feed URL * @return array{0:int,1:FreshRSS_Feed|null,2:int,3:array<FreshRSS_Feed>} Number of updated feeds, first feed or null, number of new articles, * list of feeds for which a cache refresh is needed * @throws FreshRSS_BadUrl_Exception */ public static function actualizeFeeds(?int $feed_id = null, ?string $feed_url = null, ?int $maxFeeds = null, - ?\SimplePie\SimplePie $simplePiePush = null): array { + ?\SimplePie\SimplePie $simplePiePush = null, string $selfUrl = ''): array { if (function_exists('set_time_limit')) { @set_time_limit(300); } @@ -422,6 +424,9 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { if ($feed_id !== null || $feed_url !== null) { $feed = $feed_id !== null ? $feedDAO->searchById($feed_id) : $feedDAO->searchByUrl($feed_url); if ($feed !== null && $feed->id() > 0) { + if ($selfUrl !== '') { + $feed->_selfUrl($selfUrl); + } $feeds[] = $feed; $feed_id = $feed->id(); } @@ -692,22 +697,19 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { $feedProperties['attributes'] = $feed->attributes(); } - if ($pubsubhubbubEnabledGeneral && $feed->hubUrl() !== '' && $feed->selfUrl() !== '') { //selfUrl has priority for WebSub - if ($feed->selfUrl() !== $url) { // https://github.com/pubsubhubbub/PubSubHubbub/wiki/Moving-Feeds-or-changing-Hubs - $selfUrl = checkUrl($feed->selfUrl()); - if ($selfUrl != false) { - Minz_Log::debug('WebSub unsubscribe ' . $feed->url(false)); - if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe - Minz_Log::warning('Error while WebSub unsubscribing from ' . $feed->url(false)); - } - $feed->_url($selfUrl, false); - Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url(false)); - $feedDAO->updateFeed($feed->id(), ['url' => $feed->url()]); - } + if ($feed->url() !== $url) { // HTTP 301 Moved Permanently + Minz_Log::warning('Feed ' . \SimplePie\Misc::url_remove_credentials($url) . + ' moved permanently to ' . $feed->url(includeCredentials: false)); + $feedProperties['url'] = $feed->url(); + } elseif ($simplePiePush !== null && $selfUrl !== '' && $selfUrl !== $feed->url()) { // selfUrl has priority for WebSub + // https://github.com/pubsubhubbub/PubSubHubbub/wiki/Moving-Feeds-or-changing-Hubs + Minz_Log::debug('WebSub unsubscribe ' . $feed->url(includeCredentials: false)); + if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe + Minz_Log::warning('Error while WebSub unsubscribing from ' . $feed->url(includeCredentials: false)); } - } elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently - Minz_Log::notice('Feed ' . \SimplePie\Misc::url_remove_credentials($url) . - ' moved permanently to ' . \SimplePie\Misc::url_remove_credentials($feed->url(false))); + $feed->_url($selfUrl); + Minz_Log::warning('Feed ' . \SimplePie\Misc::url_remove_credentials($url) . + ' canonical address moved to ' . $feed->url(includeCredentials: false)); $feedProperties['url'] = $feed->url(); } @@ -826,14 +828,17 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { } /** + * @param \SimplePie\SimplePie|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates + * @param string $selfUrl Used by WebSub (PubSubHubbub) to override the feed URL * @return array{0:int,1:FreshRSS_Feed|null,2:int,3:array<FreshRSS_Feed>} Number of updated feeds, first feed or null, number of new articles, * list of feeds for which a cache refresh is needed * @throws FreshRSS_BadUrl_Exception */ public static function actualizeFeedsAndCommit(?int $feed_id = null, ?string $feed_url = null, ?int $maxFeeds = null, - ?SimplePie\SimplePie $simplePiePush = null): array { + ?SimplePie\SimplePie $simplePiePush = null, string $selfUrl = ''): array { $entryDAO = FreshRSS_Factory::createEntryDao(); - [$nbUpdatedFeeds, $feed, $nbNewArticles, $feedsCacheToRefresh] = FreshRSS_feed_Controller::actualizeFeeds($feed_id, $feed_url, $maxFeeds, $simplePiePush); + [$nbUpdatedFeeds, $feed, $nbNewArticles, $feedsCacheToRefresh] = + FreshRSS_feed_Controller::actualizeFeeds($feed_id, $feed_url, $maxFeeds, $simplePiePush, $selfUrl); if ($nbNewArticles > 0) { $entryDAO->beginTransaction(); FreshRSS_feed_Controller::commitNewEntries(); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index b91d1af75..171b054ca 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -463,6 +463,10 @@ class FreshRSS_Feed extends Minz_Model { $this->url = $url; } + public function _selfUrl(string $value): void { + $this->selfUrl = $value; + } + public function _kind(int $value): void { $this->kind = $value; } diff --git a/p/api/pshb.php b/p/api/pshb.php index 6b5bda4b5..91dd4e901 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -104,13 +104,33 @@ $simplePie->init(); unset($ORIGINAL_INPUT); $links = $simplePie->get_links('self'); -$self = $links[0] ?? null; +$self = $links[0] ?? ''; + +// Support HTTP header `Link: <http://example.net/hub.php>; rel="hub", <http://example.net/feed.php>; rel="self"` +$httpLink = is_string($_SERVER['HTTP_LINK'] ?? null) ? $_SERVER['HTTP_LINK'] : ''; +if ($httpLink !== '' && preg_match_all('/<([^>]+)>;\\s*rel="([^"]+)"/', $httpLink, $matches, PREG_SET_ORDER)) { + $links = []; + foreach ($matches as $match) { + if (!empty($match[1]) && !empty($match[2])) { + $links[$match[2]] = $match[1]; + } + } + // if (!empty($links['hub'])) { + // // TODO: Support WebSub hub redirection + // } + if (!empty($links['self'])) { + $httpSelf = checkUrl($links['self']) ?: ''; + if ($self !== '' && $self !== $httpSelf) { + Minz_Log::warning('Warning: Self URL mismatch between XML [' . $self . '] and HTTP!: ' . $httpSelf, PSHB_LOG); + } + $self = $httpSelf; + } +} if ($self !== $canonical) { //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!'); - $self = $canonical; } Minz_ExtensionManager::init(); @@ -120,7 +140,7 @@ $nb = 0; foreach ($users as $userFilename) { $username = basename($userFilename, '.txt'); if (!file_exists(USERS_PATH . '/' . $username . '/config.php')) { - Minz_Log::warning('Warning: Removing broken user link: ' . $username . ' for ' . $self, PSHB_LOG); + Minz_Log::warning('Warning: Removing broken user link: ' . $username . ' for ' . $canonical, PSHB_LOG); unlink($userFilename); continue; } @@ -134,15 +154,15 @@ foreach ($users as $userFilename) { Minz_ExtensionManager::enableByList(FreshRSS_Context::userConf()->extensions_enabled, 'user'); Minz_Translate::reset(FreshRSS_Context::userConf()->language); - [$nbUpdatedFeeds, ] = FreshRSS_feed_Controller::actualizeFeedsAndCommit(null, $self, null, $simplePie); + [$nbUpdatedFeeds, ] = FreshRSS_feed_Controller::actualizeFeedsAndCommit(feed_url: $canonical, simplePiePush: $simplePie, selfUrl: $self); if ($nbUpdatedFeeds > 0) { $nb++; } else { - Minz_Log::warning('Warning: User ' . $username . ' does not subscribe anymore to ' . $self, PSHB_LOG); + Minz_Log::warning('Warning: User ' . $username . ' does not subscribe anymore to ' . $canonical, PSHB_LOG); unlink($userFilename); } } catch (Exception $e) { - Minz_Log::error('Error: ' . $e->getMessage() . ' for user ' . $username . ' and feed ' . $self, PSHB_LOG); + Minz_Log::error('Error: ' . $e->getMessage() . ' for user ' . $username . ' and feed ' . $canonical, PSHB_LOG); } } @@ -151,12 +171,12 @@ unset($simplePie); if ($nb === 0) { header('HTTP/1.1 410 Gone'); - Minz_Log::warning('Warning: Nobody subscribes to this feed anymore after all!: ' . $self, PSHB_LOG); + Minz_Log::warning('Warning: Nobody subscribes to this feed anymore after all!: ' . $canonical, PSHB_LOG); die('Nobody subscribes to this feed anymore after all!'); } elseif (!empty($hubJson['error'])) { $hubJson['error'] = false; file_put_contents('./!hub.json', json_encode($hubJson)); } -Minz_Log::notice('WebSub ' . $self . ' done: ' . $nb, PSHB_LOG); +Minz_Log::notice('WebSub ' . $canonical . ' done: ' . $nb, PSHB_LOG); exit('Done: ' . $nb . "\n"); |
