From 31f103a1f0f6e95c6ac2d6b2cd5ddf1d4b057f4b Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Mon, 1 Jul 2024 18:50:13 +0200 Subject: [PATCH 1/3] Update feed URL after permanent redirect --- src/Model/Contact.php | 13 +++++++++++++ .../Capability/ICanHandleHttpResponses.php | 10 ++++++++++ src/Network/HTTPClient/Response/CurlResult.php | 16 ++++++++++++++-- .../HTTPClient/Response/GuzzleResponse.php | 15 +++++++++++++++ src/Worker/OnePoll.php | 11 +++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 5cd77048c9..0dc662659e 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -2445,6 +2445,19 @@ class Contact } } + /** + * Updates the poll URL of a contact. This is the right function to call if there is a redirect. + * + * @param integer $id contact id + * @param string $url The new URL to use for polling + * + * @throws \Exception + */ + public static function updatePollUrl(int $id, string $url) + { + self::update(['poll', $url], ['id' => $id]); + } + /** * Helper function for "updateFromProbe". Updates personal and public contact * diff --git a/src/Network/HTTPClient/Capability/ICanHandleHttpResponses.php b/src/Network/HTTPClient/Capability/ICanHandleHttpResponses.php index 68c8eda2d8..5eb2e9bf4d 100644 --- a/src/Network/HTTPClient/Capability/ICanHandleHttpResponses.php +++ b/src/Network/HTTPClient/Capability/ICanHandleHttpResponses.php @@ -90,10 +90,20 @@ interface ICanHandleHttpResponses public function getUrl(): string; /** + * If the request was redirected to another URL, gets the final URL requested * @return string */ public function getRedirectUrl(): string; + /** + * If the request was redirected to another URL, indicates if the redirect is permanent. + * If the request was not redirected, returns false. + * If the request was redirected multiple times, returns true only if all of the redirects were permanent. + * + * @return bool True if the redirect is permanent + */ + public function redirectIsPermanent(): bool; + /** * Getter for body * diff --git a/src/Network/HTTPClient/Response/CurlResult.php b/src/Network/HTTPClient/Response/CurlResult.php index 75d390eac7..79aa9c722f 100644 --- a/src/Network/HTTPClient/Response/CurlResult.php +++ b/src/Network/HTTPClient/Response/CurlResult.php @@ -81,6 +81,11 @@ class CurlResult implements ICanHandleHttpResponses */ private $isRedirectUrl; + /** + * @var boolean true if the URL has a permanent redirect + */ + private $redirectIsPermanent; + /** * @var boolean true if the curl request timed out */ @@ -197,7 +202,7 @@ class CurlResult implements ICanHandleHttpResponses $this->redirectUrl = $this->info['url']; } - if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode == 307) { + if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode == 307 || $this->returnCode == 308) { $redirect_parts = parse_url($this->info['redirect_url'] ?? ''); if (empty($redirect_parts)) { $redirect_parts = []; @@ -224,10 +229,11 @@ class CurlResult implements ICanHandleHttpResponses } $this->redirectUrl = (string)Uri::fromParts((array)$redirect_parts); - $this->isRedirectUrl = true; + $this->redirectIsPermanent = $this->returnCode == 301 or $this->returnCode == 308; } else { $this->isRedirectUrl = false; + $this->redirectIsPermanent = false; } } @@ -340,6 +346,12 @@ class CurlResult implements ICanHandleHttpResponses return $this->isRedirectUrl; } + /** {@inheritDoc} */ + public function redirectIsPermanent(): bool + { + return $this->redirectIsPermanent; + } + /** {@inheritDoc} */ public function getErrorNumber(): int { diff --git a/src/Network/HTTPClient/Response/GuzzleResponse.php b/src/Network/HTTPClient/Response/GuzzleResponse.php index d277f2a8df..6678a6b8a8 100644 --- a/src/Network/HTTPClient/Response/GuzzleResponse.php +++ b/src/Network/HTTPClient/Response/GuzzleResponse.php @@ -52,6 +52,8 @@ class GuzzleResponse extends Response implements ICanHandleHttpResponses, Respon private $redirectUrl = ''; /** @var bool */ private $isRedirectUrl = false; + /** @var bool */ + private $redirectIsPermanent = false; public function __construct(ResponseInterface $response, string $url, $errorNumber = 0, $error = '') { @@ -91,6 +93,13 @@ class GuzzleResponse extends Response implements ICanHandleHttpResponses, Respon if (count($headersRedirect) > 0) { $this->redirectUrl = $headersRedirect[0]; $this->isRedirectUrl = true; + + $this->redirectIsPermanent = true; + foreach (($response->getHeader(RedirectMiddleware::STATUS_HISTORY_HEADER) ?? []) as $history) { + if (preg_match('/30(2|3|4|7)/', $history)) { + $this->redirectIsPermanent = false; + } + } } } @@ -145,6 +154,12 @@ class GuzzleResponse extends Response implements ICanHandleHttpResponses, Respon return $this->isRedirectUrl; } + /** {@inheritDoc} */ + public function redirectIsPermanent(): bool + { + return $this->redirectIsPermanent; + } + /** {@inheritDoc} */ public function getErrorNumber(): int { diff --git a/src/Worker/OnePoll.php b/src/Worker/OnePoll.php index 838f30a4e4..7c784ab704 100644 --- a/src/Worker/OnePoll.php +++ b/src/Worker/OnePoll.php @@ -167,12 +167,23 @@ class OnePoll $cookiejar = tempnam(System::getTempPath(), 'cookiejar-onepoll-'); $curlResult = DI::httpClient()->get($contact['poll'], HttpClientAccept::FEED_XML, [HttpClientOptions::COOKIEJAR => $cookiejar, HttpClientOptions::REQUEST => HttpClientRequest::FEEDFETCHER]); unlink($cookiejar); + Logger::debug('Polled feed', ['url' => $contact['poll'], 'http-code' => $curlResult->getReturnCode(), 'redirect-url' => $curlResult->getRedirectUrl()]); if ($curlResult->isTimeout()) { Logger::notice('Polling timed out', ['id' => $contact['id'], 'url' => $contact['poll']]); return false; } + if ($curlResult->redirectIsPermanent()) { + Logger::notice('Poll address permanently changed', [ + 'id' => $contact['id'], + 'uid' => $contact['uid'], + 'old' => $contact['poll'], + 'new' => $curlResult->getRedirectUrl(), + ]); + $success = Contact::updatePollUrl($contact['id'], $curlResult->getRedirectUrl()); + } + $xml = $curlResult->getBodyString(); if (empty($xml)) { Logger::notice('Empty content', ['id' => $contact['id'], 'url' => $contact['poll']]); From c7937300db90674aeeeb51e3401df1a4387fdd11 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Wed, 3 Jul 2024 08:02:41 +0200 Subject: [PATCH 2/3] Remove duplicate space --- src/Model/Contact.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 0dc662659e..c9f5a8e501 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -2446,7 +2446,7 @@ class Contact } /** - * Updates the poll URL of a contact. This is the right function to call if there is a redirect. + * Updates the poll URL of a contact. This is the right function to call if there is a redirect. * * @param integer $id contact id * @param string $url The new URL to use for polling From f32592e1488934682bc86127d38012d4a590e8cf Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Wed, 3 Jul 2024 08:03:33 +0200 Subject: [PATCH 3/3] Use standard boolean operator --- src/Network/HTTPClient/Response/CurlResult.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network/HTTPClient/Response/CurlResult.php b/src/Network/HTTPClient/Response/CurlResult.php index 79aa9c722f..2680a8b806 100644 --- a/src/Network/HTTPClient/Response/CurlResult.php +++ b/src/Network/HTTPClient/Response/CurlResult.php @@ -230,7 +230,7 @@ class CurlResult implements ICanHandleHttpResponses $this->redirectUrl = (string)Uri::fromParts((array)$redirect_parts); $this->isRedirectUrl = true; - $this->redirectIsPermanent = $this->returnCode == 301 or $this->returnCode == 308; + $this->redirectIsPermanent = $this->returnCode == 301 || $this->returnCode == 308; } else { $this->isRedirectUrl = false; $this->redirectIsPermanent = false;