Explorar el Código

Bump SimplePie (#9562)

In particular:
* File,Psr18Client: Remove support for local files https://github.com/simplepie/simplepie/pull/994
* Parser: ignore whitespace before the XML declaration https://github.com/simplepie/simplepie/pull/1005
* Require PHP 7.4 or newer https://github.com/simplepie/simplepie/pull/1006, https://github.com/simplepie/simplepie/pull/992
* Parser: treat https Atom 1.0 namespace as Atom https://github.com/simplepie/simplepie/pull/1010
Alexandre Alapetite hace 14 horas
padre
commit
e5ae053c03

+ 1 - 0
lib/.gitignore

@@ -42,4 +42,5 @@ simplepie/simplepie/idn/
 simplepie/simplepie/library/
 simplepie/simplepie/phpunit.xml.dist
 simplepie/simplepie/ROADMAP.md
+simplepie/simplepie/SECURITY.md
 simplepie/simplepie/tests/

+ 1 - 1
lib/composer.json

@@ -18,7 +18,7 @@
 		"marienfressinaud/lib_opml": "dev-main#f0e850b6394af90b898daf0e65fcc7363457b844",
 		"phpgt/cssxpath": "v1.6.0",
 		"phpmailer/phpmailer": "7.1.1",
-		"simplepie/simplepie": "dev-freshrss#760a0a3354a73c07d21c97e4400b85c04e1b57c7"
+		"simplepie/simplepie": "dev-freshrss#366b687cc1a39b76f1563ce9859fb027f85001d2"
 	},
 	"config": {
 		"sort-packages": true,

+ 25 - 3
lib/simplepie/simplepie/CHANGELOG.md

@@ -9,20 +9,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
-Nothing yet.
+- New method `SimplePie\SimplePie::get_icon_url()` for the feed favicon, taken from `atom:icon` or a square RSS 2.0 `image` (by @Alkarex in [#974](https://github.com/simplepie/simplepie/pull/974))
+- Publish [security policy](SECURITY.md) (by @jgalea in [#1013](https://github.com/simplepie/simplepie/pull/1013))
+- Feeds that incorrectly declare the namespace as `https://www.w3.org/2005/Atom` are now parsed as Atom (by @jgalea in [#1010](https://github.com/simplepie/simplepie/pull/1010))
 
 ### Changed
 
-Nothing yet.
+- Minimum supported PHP version is now 7.4 (by @jgalea)
+- `SimplePie\Item::get_date()` now returns `null` for a date that cannot be parsed, instead of the Unix epoch on PHP < 8.0 or the current time on PHP 8.0+ (by @arraintxo in [#964](https://github.com/simplepie/simplepie/pull/964))
+- Exclude more development files from the Composer package (by @johnbillion in [#955](https://github.com/simplepie/simplepie/pull/955))
 
 ### Fixed
 
-Nothing yet.
+- Normalize the declared encoding to uppercase, so `utf-8` and `UTF-8` are no longer treated as different encodings (by @Alkarex in [#936](https://github.com/simplepie/simplepie/pull/936))
+- `File`: Use `CURLOPT_ACCEPT_ENCODING` instead of the renamed `CURLOPT_ENCODING` on cURL versions that support it (by @mmeier86 in [#960](https://github.com/simplepie/simplepie/pull/960))
+- `File`: Disable cURL content decoding when the server sends a `Content-Encoding` header cURL cannot handle (by @mmeier86 in [#962](https://github.com/simplepie/simplepie/pull/962))
+- Do not trigger an undefined array key warning in `SimplePie\Item::get_thumbnail()` for an empty `<media:thumbnail />` (by @Alkarex in [#970](https://github.com/simplepie/simplepie/pull/970))
+- Fix `TypeError` in `SimplePie\Enclosure` when `channels` or `duration` were parsed as strings (by @Alkarex in [#975](https://github.com/simplepie/simplepie/pull/975))
+- Fix `TypeError` in `SimplePie\Enclosure` when a media player element has no `url` (by @Alkarex in [#978](https://github.com/simplepie/simplepie/pull/978))
+- Accept feeds larger than 10 MB with libxml 2.7.0 and newer, as older versions did; this only works on PHP 8.4.0 and newer (by @LeeXiaolan in [#977](https://github.com/simplepie/simplepie/pull/977))
+- Fix “Using null as an array offset” deprecations on PHP 8.5 (by @swissspidy in [#949](https://github.com/simplepie/simplepie/pull/949), @Alkarex in [#979](https://github.com/simplepie/simplepie/pull/979))
+- Fix incorrect argument typing information for static analyzers (by @jtojnar in [#946](https://github.com/simplepie/simplepie/pull/946))
+- `File`: Stop reporting interim instead of final status code (by @Alkarex in [#945](https://github.com/simplepie/simplepie/pull/945))
+- `Psr18Client` handles 308 redirects (by @jtojnar in [#938](https://github.com/simplepie/simplepie/pull/938))
+- `SimplePie\Content\Type\Sniffer::__construct()` expects `File` as parameter instead of `mixed` again. This reverts accidental BC break from 1.9.0. (by @jtojnar in [#938](https://github.com/simplepie/simplepie/pull/938))
+- Feeds with whitespace before the XML declaration are parsed instead of being rejected as invalid XML (by @jgalea in [#1005](https://github.com/simplepie/simplepie/pull/1005))
+- `Psr18Client` resolves relative `Location` redirect headers against the current URL (by @ziegenberg in [#1018](https://github.com/simplepie/simplepie/pull/1018))
 
 ### Deprecated
 
 - `SimplePie\SimplePie::set_item_limit()` is deprecated since it only affects multi-feed mode, which has been deprecated in SimplePie 1.9 (by @jtojnar in [#954](https://github.com/simplepie/simplepie/pull/954))
 
+### Removed
+
+- **Breaking:** Fetching non-http(s) URLs using `File` or `Psr18Client` is no longer supported. ([CVE-2026-111234](https://github.com/advisories/GHSA-9fwh-fc64-58p8)). (by @jtojnar in [#994](https://github.com/simplepie/simplepie/pull/994))
+- **Breaking:** Fetching local paths set via `set_feed_url` is no longer supported. Use `SimplePie::set_raw_data()` with the contents of the file, obtained e.g. using `file_get_contents`. (by @jtojnar in [#993](https://github.com/simplepie/simplepie/pull/993) and [#994](https://github.com/simplepie/simplepie/pull/994))
+
 ## [1.9.0](https://github.com/simplepie/simplepie/compare/1.8.1...1.9.0) - 2025-09-12
 
 ### Added

+ 2 - 2
lib/simplepie/simplepie/README.markdown

@@ -11,7 +11,7 @@ compatibility and standards compliance][what_is].
 
 Requirements
 ------------
-* PHP 7.2+ (Required since SimplePie 1.8.0)
+* PHP 7.4+
 * libxml2 (certain 2.7.x releases are too buggy for words, and will crash)
 * One of iconv, mbstring or intl extensions
 * Optionally, intl extension, [symfony/polyfill-intl-idn](https://github.com/symfony/polyfill-intl-idn) or cURL extension built with IDN support to support IDNs
@@ -116,7 +116,7 @@ Please open your PR against the appropriate branch.
 
 | branch                                                                     | requires    |
 |----------------------------------------------------------------------------|-------------|
-| [master](https://github.com/simplepie/simplepie/tree/master)               | PHP 7.2.0+  |
+| [master](https://github.com/simplepie/simplepie/tree/master)               | PHP 7.4.0+  |
 | [one-dot-seven](https://github.com/simplepie/simplepie/tree/one-dot-seven) | PHP 5.6.0+  |
 | [one-dot-three](https://github.com/simplepie/simplepie/tree/one-dot-three) | PHP 5.2.0+  |
 

+ 9 - 12
lib/simplepie/simplepie/src/File.php

@@ -214,8 +214,8 @@ class File implements Response
                 } else {
                     // For PHPStan: `curl_exec` returns `false` only on error so the `is_string` check will always pass.
                     \assert(is_string($responseBody));
-                    if (curl_getinfo($fp, CURLINFO_HTTP_CONNECTCODE) !== 0) {
-                        // TODO: Replace with `CURLOPT_SUPPRESS_CONNECT_HEADERS` once PHP 7.2 support is dropped.
+                    if (!\defined('CURLOPT_SUPPRESS_CONNECT_HEADERS') && curl_getinfo($fp, CURLINFO_HTTP_CONNECTCODE) !== 0) {
+                        // libcurl older than 7.54 has no CURLOPT_SUPPRESS_CONNECT_HEADERS.
                         $responseHeaders = \SimplePie\HTTP\Parser::prepareHeaders($responseHeaders);
                     }
                     $this->on_http_response($responseHeaders . $responseBody, $curl_options);
@@ -466,16 +466,10 @@ class File implements Response
             }
         } else {
             $this->method = \SimplePie\SimplePie::FILE_SOURCE_LOCAL | \SimplePie\SimplePie::FILE_SOURCE_FILE_GET_CONTENTS;
-            $filebody = false;
-            if (empty($url) || !is_readable($url) || false === ($filebody = file_get_contents($url))) {
-                $this->body = '';
-                $this->error = sprintf('file "%s" is not readable', $url);
-                $this->success = false;
-            } else {
-                $this->body = $filebody;
-                $this->status_code = 200;
-            }
-            $this->on_http_response($filebody);
+            $this->body = '';
+            $this->error = sprintf('Fetching non-http(s) URI scheme not allowed, "%s" given.', $url);
+            $this->success = false;
+            $this->on_http_response('');
         }
         if ($this->success) {
             assert($this->body !== null); // For PHPStan
@@ -682,6 +676,9 @@ class File implements Response
         }
         /** @var non-empty-string $url */
         curl_setopt($fp, CURLOPT_URL, $url);
+        if (\defined('CURLOPT_SUPPRESS_CONNECT_HEADERS')) {
+            curl_setopt($fp, CURLOPT_SUPPRESS_CONNECT_HEADERS, true);
+        }
         curl_setopt($fp, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($fp, CURLOPT_FAILONERROR, true);
         curl_setopt($fp, CURLOPT_TIMEOUT, $timeout);

+ 11 - 24
lib/simplepie/simplepie/src/HTTP/Psr18Client.php

@@ -13,7 +13,6 @@ use Psr\Http\Client\ClientInterface;
 use Psr\Http\Message\RequestFactoryInterface;
 use Psr\Http\Message\UriFactoryInterface;
 use SimplePie\Misc;
-use Throwable;
 
 /**
  * HTTP Client based on PSR-18 and PSR-17 implementations
@@ -83,11 +82,14 @@ final class Psr18Client implements Client
             ), 1);
         }
 
-        if (Misc::is_remote_uri($url)) {
-            return $this->requestUrl($method, $url, $headers);
+        if (!Misc::is_remote_uri($url)) {
+            throw new ClientException(sprintf(
+                'Fetching non-http(s) URI scheme not allowed, "%s" given.',
+                $url
+            ));
         }
 
-        return $this->requestLocalFile($url);
+        return $this->requestUrl($method, $url, $headers);
     }
 
     /**
@@ -129,7 +131,11 @@ final class Psr18Client implements Client
                 $remainingRedirects--;
                 $followRedirect = true;
 
-                $requestedUrl = $response->getHeaderLine('Location');
+                $location = $response->getHeaderLine('Location');
+
+                // Resolve relative redirects against the current URL before
+                // forwarding, as RFC 9110 permits relative Location headers.
+                $requestedUrl = Misc::absolutize_url($location, $requestedUrl) ?: $location;
 
                 if ($statusCode === 301 || $statusCode === 308) {
                     $permanentUrl = $requestedUrl;
@@ -141,23 +147,4 @@ final class Psr18Client implements Client
 
         return new Psr7Response($response, $permanentUrl, $requestedUrl);
     }
-
-    private function requestLocalFile(string $path): Response
-    {
-        if (!is_readable($path)) {
-            throw new ClientException(sprintf('file "%s" is not readable', $path));
-        }
-
-        try {
-            $raw = file_get_contents($path);
-        } catch (Throwable $th) {
-            throw new ClientException($th->getMessage(), $th->getCode(), $th);
-        }
-
-        if ($raw === false) {
-            throw new ClientException('file_get_contents() could not read the file', 1);
-        }
-
-        return new RawTextResponse($raw, $path);
-    }
 }

+ 0 - 3
lib/simplepie/simplepie/src/IRI.php

@@ -306,9 +306,6 @@ class IRI
     {
         $iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
         if (preg_match('/^(?:(?P<scheme>[^:\/?#]+):)?(:?\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?$/', $iri, $match, \PREG_UNMATCHED_AS_NULL)) {
-            // TODO: Remove once we require PHP ≥ 7.4.
-            $match['query'] = $match['query'] ?? null;
-            $match['fragment'] = $match['fragment'] ?? null;
             return $match;
         }
 

+ 4 - 5
lib/simplepie/simplepie/src/Locator.php

@@ -205,7 +205,7 @@ class Locator implements RegistryAware
                     continue;
                 }
                 $this->base = $base;
-                $this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0;
+                $this->base_location = $element->getLineNo();
                 break;
             }
         }
@@ -249,7 +249,7 @@ class Locator implements RegistryAware
             }
             if ($link->hasAttribute('href') && $link->hasAttribute('rel')) {
                 $rel = array_unique($this->registry->call(Misc::class, 'space_separated_tokens', [strtolower($link->getAttribute('rel'))]));
-                $line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1;
+                $line = $link->getLineNo();
 
                 if ($this->base_location < $line) {
                     $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]);
@@ -300,7 +300,7 @@ class Locator implements RegistryAware
                 $href = trim($link->getAttribute('href'));
                 $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]);
                 if ($parsed['scheme'] === '' || preg_match('/^(https?|feed)?$/i', $parsed['scheme'])) {
-                    if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) {
+                    if ($this->base_location < $link->getLineNo()) {
                         $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]);
                     } else {
                         $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base]);
@@ -354,8 +354,7 @@ class Locator implements RegistryAware
             $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]);
             if ($parsed['scheme'] === '' ||
                 preg_match('/^https?$/i', $parsed['scheme'])) {
-                if (method_exists($link, 'getLineNo') &&
-                    $this->base_location < $link->getLineNo()) {
+                if ($this->base_location < $link->getLineNo()) {
                     $href = $this->registry->call(
                         Misc::class,
                         'absolutize_url',

+ 14 - 0
lib/simplepie/simplepie/src/Parser.php

@@ -18,6 +18,11 @@ use XMLParser;
  */
 class Parser implements RegistryAware
 {
+    /**
+     * Atom 1.0 namespace with the scheme mistyped as https, treated as NAMESPACE_ATOM_10.
+     */
+    private const NAMESPACE_ATOM_10_WRONG = 'https://www.w3.org/2005/Atom';
+
     /** @var int */
     public $error_code;
     /** @var string */
@@ -108,6 +113,11 @@ class Parser implements RegistryAware
             $data = substr($data, 3);
         }
 
+        // The XML declaration must be the very first thing in the document, but
+        // plenty of feeds are served with a stray newline or space in front of it.
+        // Only strip it when followed by `<`, so a UTF-16LE character like `\n\x00` is not split.
+        $data = (string) preg_replace('/^[ \n\r\t\v]+</', '<', $data);
+
         if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) {
             $declaration = $this->registry->create(DeclarationParser::class, [substr($data, 5, $pos - 5)]);
             if ($declaration->parse()) {
@@ -387,6 +397,10 @@ class Parser implements RegistryAware
                     $namespace = \SimplePie\SimplePie::NAMESPACE_ITUNES;
                 }
 
+                if ($namespace === self::NAMESPACE_ATOM_10_WRONG) {
+                    $namespace = \SimplePie\SimplePie::NAMESPACE_ATOM_10;
+                }
+
                 // Normalize the Media RSS namespaces
                 if ($namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG ||
                     $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG2 ||

+ 12 - 4
lib/simplepie/simplepie/src/SimplePie.php

@@ -24,6 +24,7 @@ use SimplePie\HTTP\Client;
 use SimplePie\HTTP\ClientException;
 use SimplePie\HTTP\FileClient;
 use SimplePie\HTTP\Psr18Client;
+use SimplePie\HTTP\RawTextResponse;
 use SimplePie\HTTP\Response;
 
 /**
@@ -729,8 +730,8 @@ class SimplePie
      */
     public function __construct()
     {
-        if (version_compare(PHP_VERSION, '7.2', '<')) {
-            exit('Please upgrade to PHP 7.2 or newer.');
+        if (version_compare(PHP_VERSION, '7.4', '<')) {
+            exit('Please upgrade to PHP 7.4 or newer.');
         }
 
         $this->set_useragent();
@@ -1883,8 +1884,15 @@ class SimplePie
         if ($this->feed_url !== null) {
             $parsed_feed_url = $this->registry->call(Misc::class, 'parse_url', [$this->feed_url]);
 
+            $scheme = $parsed_feed_url['scheme'];
+            if (!Misc::is_remote_uri($this->feed_url)) {
+                $source = $scheme === '' ? 'local feeds' : "feeds from $scheme";
+                $this->error = "Fetching $source not supported";
+                return false;
+            }
+
             // Decide whether to enable caching
-            if ($this->enable_cache && $parsed_feed_url['scheme'] !== '') {
+            if ($this->enable_cache && $scheme !== '') {
                 $cache = $this->get_cache($this->feed_url);
             }
 
@@ -2011,7 +2019,7 @@ class SimplePie
      *
      * If the data is already cached, attempt to fetch it from there instead
      *
-     * @param Base|DataCache|false $cache Cache handler, or false to not load from the cache
+     * @param Base|DataCache|false $cache Cache handler (when handling a non-local file), or false to not load from the cache
      * @return array{array<string, string>, string}|bool Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type
      */
     protected function fetch_data(&$cache)