aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2025-08-01 23:27:35 +0200
committerGravatar GitHub <noreply@github.com> 2025-08-01 23:27:35 +0200
commitb817598f5711b784c05579e473847e1030b4f75e (patch)
tree72e43bcf531a9b8dbf739bc1f689664103c12618 /lib
parent536c476f0d5c5bfce324215eb7d9aea353c36298 (diff)
Bump SimplePie with PHPStan Level 8 (#7775)
* Bump SimplePie with PHPStan Level 8 * https://github.com/FreshRSS/simplepie/pull/45 SimplePie increased to PHPStan Level 8: * https://github.com/simplepie/simplepie/pull/857 * Merge upstream Including my two PRs: * https://github.com/simplepie/simplepie/pull/932 * https://github.com/simplepie/simplepie/pull/933 * Resolve upstream sync of Expose HTTP status * https://github.com/FreshRSS/simplepie/pull/47 Finalise merge, following: * https://github.com/simplepie/simplepie/pull/905#issuecomment-3007605779 * https://github.com/simplepie/simplepie/pull/909 * https://github.com/FreshRSS/FreshRSS/issues/7038
Diffstat (limited to 'lib')
-rw-r--r--lib/composer.json2
-rw-r--r--lib/simplepie/simplepie/.gitignore1
-rw-r--r--lib/simplepie/simplepie/phpstan.neon.dist7
-rw-r--r--lib/simplepie/simplepie/src/Cache.php9
-rw-r--r--lib/simplepie/simplepie/src/Cache/CallableNameFilter.php5
-rw-r--r--lib/simplepie/simplepie/src/Cache/File.php2
-rw-r--r--lib/simplepie/simplepie/src/Cache/MySQL.php4
-rw-r--r--lib/simplepie/simplepie/src/Enclosure.php26
-rw-r--r--lib/simplepie/simplepie/src/File.php75
-rw-r--r--lib/simplepie/simplepie/src/Gzdecode.php15
-rw-r--r--lib/simplepie/simplepie/src/HTTP/FileClient.php2
-rw-r--r--lib/simplepie/simplepie/src/HTTP/Parser.php47
-rw-r--r--lib/simplepie/simplepie/src/HTTP/Psr7Response.php10
-rw-r--r--lib/simplepie/simplepie/src/HTTP/RawTextResponse.php25
-rw-r--r--lib/simplepie/simplepie/src/HTTP/Response.php16
-rw-r--r--lib/simplepie/simplepie/src/IRI.php35
-rw-r--r--lib/simplepie/simplepie/src/Item.php29
-rw-r--r--lib/simplepie/simplepie/src/Locator.php43
-rw-r--r--lib/simplepie/simplepie/src/Misc.php35
-rw-r--r--lib/simplepie/simplepie/src/Net/IPv6.php14
-rw-r--r--lib/simplepie/simplepie/src/Parse/Date.php9
-rw-r--r--lib/simplepie/simplepie/src/Parser.php60
-rw-r--r--lib/simplepie/simplepie/src/Registry.php28
-rw-r--r--lib/simplepie/simplepie/src/Sanitize.php80
-rw-r--r--lib/simplepie/simplepie/src/SimplePie.php70
-rw-r--r--lib/simplepie/simplepie/src/Source.php2
-rw-r--r--lib/simplepie/simplepie/src/XML/Declaration/Parser.php6
27 files changed, 447 insertions, 210 deletions
diff --git a/lib/composer.json b/lib/composer.json
index 0901f62a6..8c62f66ee 100644
--- a/lib/composer.json
+++ b/lib/composer.json
@@ -14,7 +14,7 @@
"marienfressinaud/lib_opml": "0.5.1",
"phpgt/cssxpath": "v1.3.0",
"phpmailer/phpmailer": "6.10.0",
- "simplepie/simplepie": "dev-freshrss#d80757267ea1fcbe13d1d1e3a73c2e81f23440de"
+ "simplepie/simplepie": "dev-freshrss#3cdee69fde431e6b461b87413177a791c2018098"
},
"config": {
"sort-packages": true,
diff --git a/lib/simplepie/simplepie/.gitignore b/lib/simplepie/simplepie/.gitignore
index 94dd5ecc9..805c1f9b9 100644
--- a/lib/simplepie/simplepie/.gitignore
+++ b/lib/simplepie/simplepie/.gitignore
@@ -1,7 +1,6 @@
*sandbox*
demo/cache/*
SimplePie.compiled.php
-bin/
vendor/
composer.lock
phpstan.neon
diff --git a/lib/simplepie/simplepie/phpstan.neon.dist b/lib/simplepie/simplepie/phpstan.neon.dist
index d922910f9..da1d19fc6 100644
--- a/lib/simplepie/simplepie/phpstan.neon.dist
+++ b/lib/simplepie/simplepie/phpstan.neon.dist
@@ -1,5 +1,5 @@
parameters:
- level: 6
+ level: 8
paths:
- library/
@@ -55,11 +55,6 @@ parameters:
# Only occurs on PHP ≤ 7.4
reportUnmatched: false
- -
- message: '(^Unable to resolve the template type T in call to method SimplePie\\Registry::get_class\(\)$)'
- count: 2
- path: tests/Unit/RegistryTest.php
-
# PHPStan stubs bug https://github.com/phpstan/phpstan/issues/8629
-
message: '(^Access to an undefined property XMLReader::\$\w+\.$)'
diff --git a/lib/simplepie/simplepie/src/Cache.php b/lib/simplepie/simplepie/src/Cache.php
index 00c606707..587fb1522 100644
--- a/lib/simplepie/simplepie/src/Cache.php
+++ b/lib/simplepie/simplepie/src/Cache.php
@@ -97,8 +97,13 @@ class Cache
*/
public static function parse_URL(string $url)
{
- $params = parse_url($url);
- $params['extras'] = [];
+ $parsedUrl = parse_url($url);
+
+ if ($parsedUrl === false) {
+ return [];
+ }
+
+ $params = array_merge($parsedUrl, ['extras' => []]);
if (isset($params['query'])) {
parse_str($params['query'], $params['extras']);
}
diff --git a/lib/simplepie/simplepie/src/Cache/CallableNameFilter.php b/lib/simplepie/simplepie/src/Cache/CallableNameFilter.php
index 6a128a3db..e95fa2917 100644
--- a/lib/simplepie/simplepie/src/Cache/CallableNameFilter.php
+++ b/lib/simplepie/simplepie/src/Cache/CallableNameFilter.php
@@ -13,10 +13,13 @@ namespace SimplePie\Cache;
final class CallableNameFilter implements NameFilter
{
/**
- * @var callable
+ * @var callable(string): string
*/
private $callable;
+ /**
+ * @param callable(string): string $callable
+ */
public function __construct(callable $callable)
{
$this->callable = $callable;
diff --git a/lib/simplepie/simplepie/src/Cache/File.php b/lib/simplepie/simplepie/src/Cache/File.php
index 95afdc898..110a77c43 100644
--- a/lib/simplepie/simplepie/src/Cache/File.php
+++ b/lib/simplepie/simplepie/src/Cache/File.php
@@ -85,7 +85,7 @@ class File implements Base
public function load()
{
if (file_exists($this->name) && is_readable($this->name)) {
- return unserialize(file_get_contents($this->name));
+ return unserialize((string) file_get_contents($this->name));
}
return false;
}
diff --git a/lib/simplepie/simplepie/src/Cache/MySQL.php b/lib/simplepie/simplepie/src/Cache/MySQL.php
index dedcd354b..73699ad75 100644
--- a/lib/simplepie/simplepie/src/Cache/MySQL.php
+++ b/lib/simplepie/simplepie/src/Cache/MySQL.php
@@ -271,7 +271,7 @@ class MySQL extends DB
$query->bindValue(':feed', $this->id);
if ($query->execute()) {
while ($row = $query->fetchColumn()) {
- $feed['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'][] = unserialize($row);
+ $feed['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'][] = unserialize((string) $row);
}
} else {
return false;
@@ -297,7 +297,7 @@ class MySQL extends DB
$query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
$query->bindValue(':id', $this->id);
if ($query->execute() && ($time = $query->fetchColumn())) {
- return $time;
+ return (int) $time;
}
return false;
diff --git a/lib/simplepie/simplepie/src/Enclosure.php b/lib/simplepie/simplepie/src/Enclosure.php
index 9d27aa4b7..89231c7f9 100644
--- a/lib/simplepie/simplepie/src/Enclosure.php
+++ b/lib/simplepie/simplepie/src/Enclosure.php
@@ -253,7 +253,7 @@ class Enclosure
if (function_exists('idn_to_ascii')) {
$parsed = \SimplePie\Misc::parse_url($link ?? '');
if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) {
- $authority = \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46);
+ $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46);
$this->link = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], $parsed['fragment']);
}
}
@@ -931,7 +931,7 @@ class Enclosure
}
}
- $mime = explode('/', $type, 2);
+ $mime = explode('/', (string) $type, 2);
$mime = $mime[0];
// Process values for 'auto'
@@ -992,7 +992,10 @@ class Enclosure
// Flash Media Player file types.
// Preferred handler for MP3 file types.
elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) {
- $height += 20;
+ if (is_numeric($height)) {
+ $height += 20;
+ }
+
if ($native) {
$embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>";
} else {
@@ -1003,7 +1006,10 @@ class Enclosure
// QuickTime 7 file types. Need to test with QuickTime 6.
// Only handle MP3's if the Flash Media Player is not present.
elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) {
- $height += 16;
+ if (is_numeric($height)) {
+ $height += 16;
+ }
+
if ($native) {
if ($placeholder !== '') {
$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
@@ -1017,7 +1023,10 @@ class Enclosure
// Windows Media
elseif ($handler === 'wmedia') {
- $height += 45;
+ if (is_numeric($height)) {
+ $height += 45;
+ }
+
if ($native) {
$embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>";
} else {
@@ -1053,10 +1062,9 @@ class Enclosure
$types_wmedia = ['application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx']; // Windows Media
$types_mp3 = ['audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg']; // MP3
- if ($this->get_type() !== null) {
- $type = strtolower($this->type);
- } else {
- $type = null;
+ $type = $this->get_type();
+ if ($type !== null) {
+ $type = strtolower($type);
}
// If we encounter an unsupported mime-type, check the file extension and guess intelligently.
diff --git a/lib/simplepie/simplepie/src/File.php b/lib/simplepie/simplepie/src/File.php
index 93c943624..4a8cb157c 100644
--- a/lib/simplepie/simplepie/src/File.php
+++ b/lib/simplepie/simplepie/src/File.php
@@ -57,7 +57,7 @@ class File implements Response
*/
public $status_code = 0;
- /** @var int Number of redirect that were already performed during this request sequence. */
+ /** @var non-negative-int Number of redirect that were already performed during this request sequence. */
public $redirects = 0;
/** @var ?string */
@@ -91,7 +91,7 @@ class File implements Response
if (function_exists('idn_to_ascii')) {
$parsed = \SimplePie\Misc::parse_url($url);
if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) {
- $authority = \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46);
+ $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46);
$url = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], null);
}
}
@@ -102,7 +102,7 @@ class File implements Response
$this->useragent = $useragent;
if (preg_match('/^http(s)?:\/\//i', $url)) {
if ($useragent === null) {
- $useragent = ini_get('user_agent');
+ $useragent = (string) ini_get('user_agent');
$this->useragent = $useragent;
}
if (!is_array($headers)) {
@@ -127,7 +127,7 @@ class File implements Response
curl_setopt($fp, CURLOPT_URL, $url);
curl_setopt($fp, CURLOPT_HEADER, 1);
curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1);
- // curl_setopt($fp, CURLOPT_FAILONERROR, 1); // FreshRSS removed to retrieve headers even on HTTP errors
+ curl_setopt($fp, CURLOPT_FAILONERROR, 1);
curl_setopt($fp, CURLOPT_TIMEOUT, $timeout);
curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout);
// curl_setopt($fp, CURLOPT_REFERER, \SimplePie\Misc::url_remove_credentials($url)); // FreshRSS removed
@@ -138,10 +138,9 @@ class File implements Response
}
$responseHeaders = curl_exec($fp);
- if (curl_errno($fp) === 23 || curl_errno($fp) === 61) {
+ if (curl_errno($fp) === CURLE_WRITE_ERROR || curl_errno($fp) === CURLE_BAD_CONTENT_ENCODING) {
$this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); // FreshRSS
- $this->status_code = curl_getinfo($fp, CURLINFO_HTTP_CODE); // FreshRSS
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
$this->error = null; // FreshRSS
curl_setopt($fp, CURLOPT_ENCODING, 'none');
$responseHeaders = curl_exec($fp);
@@ -150,15 +149,17 @@ class File implements Response
if (curl_errno($fp)) {
$this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp);
$this->success = false;
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
} else {
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
// Use the updated url provided by curl_getinfo after any redirects.
if ($info = curl_getinfo($fp)) {
$this->url = $info['url'];
}
+ // For PHPStan: We already checked that error did not occur.
+ assert(is_array($info) && $info['redirect_count'] >= 0);
curl_close($fp);
- $responseHeaders = \SimplePie\HTTP\Parser::prepareHeaders($responseHeaders, $info['redirect_count'] + 1);
+ $responseHeaders = \SimplePie\HTTP\Parser::prepareHeaders((string) $responseHeaders, $info['redirect_count'] + 1);
$parser = new \SimplePie\HTTP\Parser($responseHeaders, true);
if ($parser->parse()) {
$this->set_headers($parser->headers);
@@ -167,6 +168,11 @@ class File implements Response
if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) {
$this->redirects++;
$location = \SimplePie\Misc::absolutize_url($locationHeader, $url);
+ if ($location === false) {
+ $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”";
+ $this->success = false;
+ return;
+ }
$this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308);
$this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options);
return;
@@ -175,10 +181,15 @@ class File implements Response
}
} else {
$this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_FSOCKOPEN;
- $url_parts = parse_url($url);
+ if (($url_parts = parse_url($url)) === false) {
+ throw new \InvalidArgumentException('Malformed URL: ' . $url);
+ }
+ if (!isset($url_parts['host'])) {
+ throw new \InvalidArgumentException('Missing hostname: ' . $url);
+ }
$socket_host = $url_parts['host'];
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
- $socket_host = "ssl://$url_parts[host]";
+ $socket_host = 'ssl://' . $socket_host;
$url_parts['port'] = 443;
}
if (!isset($url_parts['port'])) {
@@ -188,7 +199,7 @@ class File implements Response
if (!$fp) {
$this->error = 'fsockopen error: ' . $errstr;
$this->success = false;
- $this->on_http_response(false);
+ $this->on_http_response();
} else {
stream_set_timeout($fp, $timeout);
if (isset($url_parts['path'])) {
@@ -229,15 +240,21 @@ class File implements Response
$this->set_headers($parser->headers);
$this->body = $parser->body;
$this->status_code = $parser->status_code;
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) {
$this->redirects++;
$location = \SimplePie\Misc::absolutize_url($locationHeader, $url);
$this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308);
+ if ($location === false) {
+ $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”";
+ $this->success = false;
+ return;
+ }
$this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options);
return;
}
if (($contentEncodingHeader = $this->get_header_line('content-encoding')) !== '') {
+ assert($this->body !== null); // For PHPStan // FreshRSS
// Hey, we act dumb elsewhere, so let's do that here too
switch (strtolower(trim($contentEncodingHeader, "\x09\x0A\x0D\x20"))) {
case 'gzip':
@@ -271,12 +288,12 @@ class File implements Response
} else {
$this->error = 'Could not parse'; // FreshRSS
$this->success = false; // FreshRSS
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
}
} else {
$this->error = 'fsocket timed out';
$this->success = false;
- $this->on_http_response($responseHeaders);
+ $this->on_http_response();
}
fclose($fp);
}
@@ -291,22 +308,23 @@ class File implements Response
$this->body = $filebody;
$this->status_code = 200;
}
- $this->on_http_response($filebody);
+ $this->on_http_response();
}
if ($this->success) {
- // (Leading) whitespace may cause XML parsing errors so we trim it,
- // but we must not trim \x00 to avoid breaking BOM or multibyte characters
- $this->body = trim($this->body, " \n\r\t\v");
+ assert($this->body !== null); // For PHPStan
+ // Leading whitespace may cause XML parsing errors (XML declaration cannot be preceded by anything other than BOM) so we trim it.
+ // Note that unlike built-in `trim` function’s default settings, we do not trim `\x00` to avoid breaking characters in UTF-16 or UTF-32 encoded strings.
+ // We also only do that when the whitespace is followed by `<`, so that we do not break e.g. UTF-16LE encoded whitespace like `\n\x00` in half.
+ $this->body = preg_replace('/^[ \n\r\t\v]+</', '<', $this->body);
}
}
/**
* Event to allow inheriting classes to e.g. log the HTTP responses.
* Triggered just after an HTTP response is received.
- * @param string|false $response The raw HTTP response headers and body, or false in case of failure (as returned by curl_exec()).
* FreshRSS.
*/
- protected function on_http_response(string|false $response): void
+ protected function on_http_response(): void
{
}
@@ -343,6 +361,19 @@ class File implements Response
return $this->parsed_headers[strtolower($name)] ?? [];
}
+ public function with_header(string $name, $value)
+ {
+ $this->maybe_update_headers();
+ $new = clone $this;
+
+ $newHeader = [
+ strtolower($name) => (array) $value,
+ ];
+ $new->set_headers($newHeader + $this->get_headers());
+
+ return $new;
+ }
+
public function get_header_line(string $name): string
{
$this->maybe_update_headers();
diff --git a/lib/simplepie/simplepie/src/Gzdecode.php b/lib/simplepie/simplepie/src/Gzdecode.php
index f331d1dc7..2c9770783 100644
--- a/lib/simplepie/simplepie/src/Gzdecode.php
+++ b/lib/simplepie/simplepie/src/Gzdecode.php
@@ -187,10 +187,10 @@ class Gzdecode
// MTIME
$mtime = substr($this->compressed_data, $this->position, 4);
// Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness
- if (current(unpack('S', "\x00\x01")) === 1) {
+ if (current((array) unpack('S', "\x00\x01")) === 1) {
$mtime = strrev($mtime);
}
- $this->MTIME = current(unpack('l', $mtime));
+ $this->MTIME = current((array) unpack('l', $mtime));
$this->position += 4;
// Get the XFL (eXtra FLags)
@@ -211,7 +211,7 @@ class Gzdecode
}
// Get the length of the extra field
- $len = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+ $len = current((array) unpack('v', substr($this->compressed_data, $this->position, 2)));
$this->position += 2;
// Check the length of the string is still valid
@@ -263,7 +263,7 @@ class Gzdecode
$this->min_compressed_size += $len + 2;
if ($this->compressed_size >= $this->min_compressed_size) {
// Read the CRC
- $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+ $crc = current((array) unpack('v', substr($this->compressed_data, $this->position, 2)));
// Check the CRC matches
if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) {
@@ -277,14 +277,15 @@ class Gzdecode
}
// Decompress the actual data
- if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) {
+ if (($data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) {
return false;
}
+ $this->data = $data;
$this->position = $this->compressed_size - 8;
// Check CRC of data
- $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+ $crc = current((array) unpack('V', substr($this->compressed_data, $this->position, 4)));
$this->position += 4;
/*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc))
{
@@ -292,7 +293,7 @@ class Gzdecode
}*/
// Check ISIZE of data
- $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+ $isize = current((array) unpack('V', substr($this->compressed_data, $this->position, 4)));
$this->position += 4;
if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) {
return false;
diff --git a/lib/simplepie/simplepie/src/HTTP/FileClient.php b/lib/simplepie/simplepie/src/HTTP/FileClient.php
index 2cbae4d3b..90fefebb0 100644
--- a/lib/simplepie/simplepie/src/HTTP/FileClient.php
+++ b/lib/simplepie/simplepie/src/HTTP/FileClient.php
@@ -68,7 +68,7 @@ final class FileClient implements Client
throw new ClientException($th->getMessage(), $th->getCode(), $th);
}
- if (!$file->success && $file->get_status_code() === 0) {
+ if ($file->error !== null && $file->get_status_code() === 0) {
throw new ClientException($file->error);
}
diff --git a/lib/simplepie/simplepie/src/HTTP/Parser.php b/lib/simplepie/simplepie/src/HTTP/Parser.php
index 4612bdb02..e9bcc4671 100644
--- a/lib/simplepie/simplepie/src/HTTP/Parser.php
+++ b/lib/simplepie/simplepie/src/HTTP/Parser.php
@@ -234,6 +234,36 @@ class Parser
$this->state = self::STATE_NEW_LINE;
}
+ private function add_header(string $name, string $value): void
+ {
+ if ($this->psr7Compatible) {
+ // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough.
+ /** @var array<string, non-empty-array<string>> */
+ $headers = &$this->headers;
+ $headers[$name][] = $value;
+ } else {
+ // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough.
+ /** @var array<string, string>) */
+ $headers = &$this->headers;
+ $headers[$name] .= ', ' . $value;
+ }
+ }
+
+ private function replace_header(string $name, string $value): void
+ {
+ if ($this->psr7Compatible) {
+ // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough.
+ /** @var array<string, non-empty-array<string>> */
+ $headers = &$this->headers;
+ $headers[$name] = [$value];
+ } else {
+ // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough.
+ /** @var array<string, string>) */
+ $headers = &$this->headers;
+ $headers[$name] = $value;
+ }
+ }
+
/**
* Deal with a new line, shifting data around as needed
* @return void
@@ -245,17 +275,9 @@ class Parser
$this->name = strtolower($this->name);
// We should only use the last Content-Type header. c.f. issue #1
if (isset($this->headers[$this->name]) && $this->name !== 'content-type') {
- if ($this->psr7Compatible) {
- $this->headers[$this->name][] = $this->value;
- } else {
- $this->headers[$this->name] .= ', ' . $this->value;
- }
+ $this->add_header($this->name, $this->value);
} else {
- if ($this->psr7Compatible) {
- $this->headers[$this->name] = [$this->value];
- } else {
- $this->headers[$this->name] = $this->value;
- }
+ $this->replace_header($this->name, $this->value);
}
}
$this->name = '';
@@ -449,6 +471,9 @@ class Parser
}
$length = hexdec(trim($matches[1]));
+ // For PHPStan: this will only be float when larger than PHP_INT_MAX.
+ // But even on 32-bit systems, it would mean 2GiB chunk, which sounds unlikely.
+ \assert(\is_int($length), "Length needs to be shorter than PHP_INT_MAX");
if ($length === 0) {
// Ignore trailer headers
$this->state = self::STATE_EMIT;
@@ -475,7 +500,7 @@ class Parser
* Prepare headers (take care of proxies headers)
*
* @param string $headers Raw headers
- * @param int $count Redirection count. Default to 1.
+ * @param non-negative-int $count Redirection count. Default to 1.
*
* @return string
*/
diff --git a/lib/simplepie/simplepie/src/HTTP/Psr7Response.php b/lib/simplepie/simplepie/src/HTTP/Psr7Response.php
index 7a52c8ec6..418fddf52 100644
--- a/lib/simplepie/simplepie/src/HTTP/Psr7Response.php
+++ b/lib/simplepie/simplepie/src/HTTP/Psr7Response.php
@@ -58,7 +58,10 @@ final class Psr7Response implements Response
public function get_headers(): array
{
- return $this->response->getHeaders();
+ // The filtering is probably redundant but let’s make PHPStan happy.
+ return array_filter($this->response->getHeaders(), function (array $header): bool {
+ return count($header) >= 1;
+ });
}
public function has_header(string $name): bool
@@ -66,6 +69,11 @@ final class Psr7Response implements Response
return $this->response->hasHeader($name);
}
+ public function with_header(string $name, $value)
+ {
+ return new self($this->response->withHeader($name, $value), $this->permanent_url, $this->requested_url);
+ }
+
public function get_header(string $name): array
{
return $this->response->getHeader($name);
diff --git a/lib/simplepie/simplepie/src/HTTP/RawTextResponse.php b/lib/simplepie/simplepie/src/HTTP/RawTextResponse.php
index 1cb8fd46d..fee5e5372 100644
--- a/lib/simplepie/simplepie/src/HTTP/RawTextResponse.php
+++ b/lib/simplepie/simplepie/src/HTTP/RawTextResponse.php
@@ -28,6 +28,11 @@ final class RawTextResponse implements Response
private $permanent_url;
/**
+ * @var array<non-empty-array<string>>
+ */
+ private $headers = [];
+
+ /**
* @var string
*/
private $requested_url;
@@ -56,22 +61,34 @@ final class RawTextResponse implements Response
public function get_headers(): array
{
- return [];
+ return $this->headers;
}
public function has_header(string $name): bool
{
- return false;
+ return isset($this->headers[strtolower($name)]);
}
public function get_header(string $name): array
{
- return [];
+ return isset($this->headers[strtolower($name)]) ? $this->headers[$name] : [];
+ }
+
+ public function with_header(string $name, $value)
+ {
+ $new = clone $this;
+
+ $newHeader = [
+ strtolower($name) => (array) $value,
+ ];
+ $new->headers = $newHeader + $this->headers;
+
+ return $new;
}
public function get_header_line(string $name): string
{
- return '';
+ return isset($this->headers[strtolower($name)]) ? implode(", ", $this->headers[$name]) : '';
}
public function get_body_content(): string
diff --git a/lib/simplepie/simplepie/src/HTTP/Response.php b/lib/simplepie/simplepie/src/HTTP/Response.php
index f27515e33..cc5296758 100644
--- a/lib/simplepie/simplepie/src/HTTP/Response.php
+++ b/lib/simplepie/simplepie/src/HTTP/Response.php
@@ -95,7 +95,7 @@ interface Response
* }
* }
*
- * @return string[][] Returns an associative array of the message's headers.
+ * @return array<non-empty-array<string>> Returns an associative array of the message's headers.
* Each key MUST be a header name, and each value MUST be an array of
* strings for that header.
*/
@@ -128,6 +128,20 @@ interface Response
public function get_header(string $name): array;
/**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|non-empty-array<string> $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function with_header(string $name, $value);
+
+ /**
* Retrieves a comma-separated string of the values for a single header.
*
* This method returns all of the header values of the given
diff --git a/lib/simplepie/simplepie/src/IRI.php b/lib/simplepie/simplepie/src/IRI.php
index 8543b2a52..7fc538cd4 100644
--- a/lib/simplepie/simplepie/src/IRI.php
+++ b/lib/simplepie/simplepie/src/IRI.php
@@ -107,7 +107,7 @@ class IRI
*/
public function __toString()
{
- return $this->get_iri();
+ return (string) $this->get_iri();
}
/**
@@ -119,8 +119,9 @@ class IRI
*/
public function __set(string $name, $value)
{
- if (method_exists($this, 'set_' . $name)) {
- call_user_func([$this, 'set_' . $name], $value);
+ $callable = [$this, 'set_' . $name];
+ if (is_callable($callable)) {
+ call_user_func($callable, $value);
} elseif (
$name === 'iauthority'
|| $name === 'iuserinfo'
@@ -195,8 +196,9 @@ class IRI
*/
public function __unset(string $name)
{
- if (method_exists($this, 'set_' . $name)) {
- call_user_func([$this, 'set_' . $name], '');
+ $callable = [$this, 'set_' . $name];
+ if (is_callable($callable)) {
+ call_user_func($callable, '');
}
}
@@ -292,7 +294,13 @@ class IRI
* Parse an IRI into scheme/authority/path/query/fragment segments
*
* @param string $iri
- * @return array<string, mixed>|false
+ * @return array{
+ * scheme: string|null,
+ * authority: string|null,
+ * path: string,
+ * query: string|null,
+ * fragment: string|null,
+ * }|false
*/
protected function parse_iri(string $iri)
{
@@ -367,9 +375,11 @@ class IRI
{
// Normalize as many pct-encoded sections as possible
$string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', [$this, 'remove_iunreserved_percent_encoded'], $string);
+ \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid");
// Replace invalid percent characters
$string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
+ \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid");
// Add unreserved and % to $extra_chars (the latter is safe because all
// pct-encoded sections are now valid).
@@ -484,7 +494,7 @@ class IRI
* Removes sequences of percent encoded bytes that represent UTF-8
* encoded characters in iunreserved
*
- * @param array<int, string> $match PCRE match
+ * @param array{string} $match PCRE match, a capture group #0 consisting of a sequence of valid percent-encoded bytes
* @return string Replacement
*/
protected function remove_iunreserved_percent_encoded(array $match)
@@ -590,7 +600,8 @@ class IRI
}
} else {
for ($j = $start; $j <= $i; $j++) {
- $string .= chr(hexdec($bytes[$j]));
+ // Cast for PHPStan, this will always be a number between 0 and 0xFF so hexdec will return int.
+ $string .= chr((int) hexdec($bytes[$j]));
}
}
}
@@ -782,7 +793,9 @@ class IRI
$remaining = $authority;
if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
- $iuserinfo = substr($remaining, 0, $iuserinfo_end);
+ // Cast for PHPStan on PHP < 8.0. It does not detect that
+ // the range is not flipped so substr cannot return false.
+ $iuserinfo = (string) substr($remaining, 0, $iuserinfo_end);
$remaining = substr($remaining, $iuserinfo_end + 1);
} else {
$iuserinfo = null;
@@ -885,7 +898,7 @@ class IRI
if ($port === null) {
$this->port = null;
return true;
- } elseif (strspn($port, '0123456789') === strlen($port)) {
+ } elseif (strspn((string) $port, '0123456789') === strlen((string) $port)) {
$this->port = (int) $port;
$this->scheme_normalization();
return true;
@@ -1026,7 +1039,7 @@ class IRI
*/
public function get_uri()
{
- return $this->to_uri($this->get_iri());
+ return $this->to_uri((string) $this->get_iri());
}
/**
diff --git a/lib/simplepie/simplepie/src/Item.php b/lib/simplepie/simplepie/src/Item.php
index 2b0201d77..c2f7460c6 100644
--- a/lib/simplepie/simplepie/src/Item.php
+++ b/lib/simplepie/simplepie/src/Item.php
@@ -161,11 +161,12 @@ class Item implements RegistryAware
* @see \SimplePie\SimplePie::sanitize()
* @param string $data Data to sanitize
* @param int-mask-of<SimplePie::CONSTRUCT_*> $type
- * @param string|null $base Base URL to resolve URLs against
+ * @param string $base Base URL to resolve URLs against
* @return string Sanitized data
*/
- public function sanitize(string $data, int $type, ?string $base = '')
+ public function sanitize(string $data, int $type, string $base = '')
{
+ // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations.
return $this->feed->sanitize($data, $type, $base);
}
@@ -347,7 +348,7 @@ class Item implements RegistryAware
* Uses `<media:thumbnail>`
*
*
- * @return array<string, mixed>|null
+ * @return array{url: string, height?: string, width?: string, time?: string}|null
*/
public function get_thumbnail()
{
@@ -632,7 +633,7 @@ class Item implements RegistryAware
* @since Beta 2 (previously called `get_item_date` since 0.8)
*
* @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data)
- * @return int|string|null
+ * @return ($date_format is 'U' ? ?int : ?string)
*/
public function get_date(string $date_format = 'j F Y, g:i a')
{
@@ -687,7 +688,7 @@ class Item implements RegistryAware
* {@see get_gmdate}
*
* @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data)
- * @return int|string|null
+ * @return ($date_format is 'U' ? ?int : ?string)
*/
public function get_updated_date(string $date_format = 'j F Y, g:i a')
{
@@ -730,12 +731,16 @@ class Item implements RegistryAware
* @since 1.0
*
* @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data)
- * @return int|string|null
+ * @return string|null|false see `strftime` for when this can return `false`
*/
public function get_local_date(string $date_format = '%c')
{
- if (!$date_format) {
- return $this->sanitize($this->get_date(''), \SimplePie\SimplePie::CONSTRUCT_TEXT);
+ if ($date_format === '') {
+ if (($raw_date = $this->get_date('')) === null) {
+ return null;
+ }
+
+ return $this->sanitize($raw_date, \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif (($date = $this->get_date('U')) !== null && $date !== false) {
return strftime($date_format, $date);
}
@@ -748,7 +753,7 @@ class Item implements RegistryAware
*
* @see get_date
* @param string $date_format Supports any PHP date format from {@see http://php.net/date}
- * @return int|string|null
+ * @return string|null
*/
public function get_gmdate(string $date_format = 'j F Y, g:i a')
{
@@ -765,7 +770,7 @@ class Item implements RegistryAware
*
* @see get_updated_date
* @param string $date_format Supports any PHP date format from {@see http://php.net/date}
- * @return int|string|null
+ * @return string|null
*/
public function get_updated_gmdate(string $date_format = 'j F Y, g:i a')
{
@@ -867,8 +872,8 @@ class Item implements RegistryAware
} else {
$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key];
}
- } elseif (substr($key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) {
- $this->data['links'][substr($key, 41)] = &$this->data['links'][$key];
+ } elseif (substr((string) $key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) {
+ $this->data['links'][substr((string) $key, 41)] = &$this->data['links'][$key];
}
$this->data['links'][$key] = array_unique($this->data['links'][$key]);
}
diff --git a/lib/simplepie/simplepie/src/Locator.php b/lib/simplepie/simplepie/src/Locator.php
index 2f06c3f99..30a7fe525 100644
--- a/lib/simplepie/simplepie/src/Locator.php
+++ b/lib/simplepie/simplepie/src/Locator.php
@@ -118,6 +118,8 @@ class Locator implements RegistryAware
*/
public function find(int $type = \SimplePie\SimplePie::LOCATOR_ALL, ?array &$working = null)
{
+ assert($this->registry !== null);
+
if ($this->is_feed($this->file)) {
return $this->file;
}
@@ -162,6 +164,8 @@ class Locator implements RegistryAware
*/
public function is_feed(Response $file, bool $check_html = false)
{
+ assert($this->registry !== null);
+
if (Misc::is_remote_uri($file->get_final_requested_uri())) {
$sniffer = $this->registry->create(Content\Type\Sniffer::class, [$file]);
$sniffed = $sniffer->get_type();
@@ -185,6 +189,8 @@ class Locator implements RegistryAware
*/
public function get_base()
{
+ assert($this->registry !== null);
+
if ($this->dom === null) {
throw new \SimplePie\Exception('DOMDocument not found, unable to use locator');
}
@@ -229,6 +235,8 @@ class Locator implements RegistryAware
*/
protected function search_elements_by_tag(string $name, array &$done, array $feeds)
{
+ assert($this->registry !== null);
+
if ($this->dom === null) {
throw new \SimplePie\Exception('DOMDocument not found, unable to use locator');
}
@@ -279,6 +287,8 @@ class Locator implements RegistryAware
*/
public function get_links()
{
+ assert($this->registry !== null);
+
if ($this->dom === null) {
throw new \SimplePie\Exception('DOMDocument not found, unable to use locator');
}
@@ -317,10 +327,14 @@ class Locator implements RegistryAware
}
/**
+ * Extracts first `link` element with given `rel` attribute inside the `head` element.
+ *
* @return string|null
*/
public function get_rel_link(string $rel)
{
+ assert($this->registry !== null);
+
if ($this->dom === null) {
throw new \SimplePie\Exception('DOMDocument not found, unable to use '.
'locator');
@@ -331,9 +345,10 @@ class Locator implements RegistryAware
}
$xpath = new \DOMXpath($this->dom);
- $query = '//a[@rel and @href] | //link[@rel and @href]';
- foreach ($xpath->query($query) as $link) {
- /** @var \DOMElement $link */
+ $query = '(//head)[1]/link[@rel and @href]';
+ /** @var \DOMNodeList<\DOMElement> */
+ $queryResult = $xpath->query($query);
+ foreach ($queryResult as $link) {
$href = trim($link->getAttribute('href'));
$parsed = $this->registry->call(Misc::class, 'parse_url', [$href]);
if ($parsed['scheme'] === '' ||
@@ -361,6 +376,7 @@ class Locator implements RegistryAware
}
}
}
+
return null;
}
@@ -435,16 +451,23 @@ class Locator implements RegistryAware
*/
private function get_http_client(): Client
{
+ assert($this->registry !== null);
+
if ($this->http_client === null) {
+ $options = [
+ 'timeout' => $this->timeout,
+ 'redirects' => 5,
+ 'force_fsockopen' => $this->force_fsockopen,
+ 'curl_options' => $this->curl_options,
+ ];
+
+ if ($this->useragent !== null) {
+ $options['useragent'] = $this->useragent;
+ }
+
return new FileClient(
$this->registry,
- [
- 'timeout' => $this->timeout,
- 'redirects' => 5,
- 'useragent' => $this->useragent,
- 'force_fsockopen' => $this->force_fsockopen,
- 'curl_options' => $this->curl_options,
- ]
+ $options
);
}
diff --git a/lib/simplepie/simplepie/src/Misc.php b/lib/simplepie/simplepie/src/Misc.php
index 42885db5c..a31c22bb2 100644
--- a/lib/simplepie/simplepie/src/Misc.php
+++ b/lib/simplepie/simplepie/src/Misc.php
@@ -71,7 +71,7 @@ class Misc
* @deprecated since SimplePie 1.3, use DOMDocument instead (parsing HTML with regex is bad!)
* @param string $realname Element name (including namespace prefix if applicable)
* @param string $string HTML document
- * @return array<array{tag: string, self_closing: bool, attribs: array<string, array{data: string}>, content: string}>
+ * @return array<array{tag: string, self_closing: bool, attribs: array<string, array{data: string}>, content?: string}>
*/
public static function get_element(string $realname, string $string)
{
@@ -92,11 +92,11 @@ class Misc
}
$return[$i]['attribs'] = [];
if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) {
- for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++) {
- if (count($attribs[$j]) === 2) {
- $attribs[$j][2] = $attribs[$j][1];
+ foreach ($attribs as $attrib) {
+ if (count($attrib) === 2) {
+ $attrib[2] = $attrib[1];
}
- $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = Misc::entities_decode(end($attribs[$j]));
+ $return[$i]['attribs'][strtolower($attrib[1])]['data'] = Misc::entities_decode(end($attrib));
}
}
}
@@ -261,7 +261,8 @@ class Misc
{
$integer = hexdec($match[1]);
if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) {
- return chr($integer);
+ // Cast for PHPStan, the value would only be float when above PHP_INT_MAX, which would not go in this branch.
+ return chr((int) $integer);
}
return strtoupper($match[0]);
@@ -287,7 +288,7 @@ class Misc
* @param string $data Raw data in $input encoding
* @param string $input Encoding of $data
* @param string $output Encoding you want
- * @return string|bool False if we can't convert it
+ * @return string|false False if we can't convert it
*/
public static function change_encoding(string $data, string $input, string $output)
{
@@ -391,7 +392,8 @@ class Misc
public static function encoding(string $charset)
{
// Normalization from UTS #22
- switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) {
+ // Cast for PHPStan, the regex should not fail.
+ switch (strtolower((string) preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) {
case 'adobestandardencoding':
case 'csadobestandardencoding':
return 'Adobe-Standard-Encoding';
@@ -2097,7 +2099,7 @@ class Misc
header('Cache-Control: must-revalidate');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days
- $body = <<<END
+ $body = <<<JS
function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) {
if (placeholder != '') {
document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
@@ -2118,7 +2120,7 @@ function embed_flv(width, height, link, placeholder, loop, player) {
function embed_wmedia(width, height, link) {
document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>');
}
-END;
+JS;
echo $body;
}
@@ -2162,7 +2164,8 @@ END;
$info = 'SimplePie ' . \SimplePie\SimplePie::VERSION . ' Build ' . static::get_build() . "\n";
$info .= 'PHP ' . PHP_VERSION . "\n";
if ($sp->error() !== null) {
- $info .= 'Error occurred: ' . $sp->error() . "\n";
+ // TODO: Remove cast with multifeeds.
+ $info .= 'Error occurred: ' . implode(', ', (array) $sp->error()) . "\n";
} else {
$info .= "No error found.\n";
}
@@ -2176,12 +2179,9 @@ END;
$info .= ' Version ' . PCRE_VERSION . "\n";
break;
case 'curl':
- $version = curl_version();
+ $version = (array) curl_version();
$info .= ' Version ' . $version['version'] . "\n";
break;
- case 'mbstring':
- $info .= ' Overloading: ' . mb_get_info('func_overload') . "\n";
- break;
case 'iconv':
$info .= ' Version ' . ICONV_VERSION . "\n";
break;
@@ -2212,7 +2212,10 @@ END;
*/
public static function url_remove_credentials(string $url)
{
- return preg_replace('#^(https?://)[^/:@]+:[^/:@]+@#i', '$1', $url);
+ // Cast for PHPStan: I do not think this can fail.
+ // The regex is valid and there should be no backtracking.
+ // https://github.com/phpstan/phpstan/issues/11547
+ return (string) preg_replace('#^(https?://)[^/:@]+:[^/:@]+@#i', '$1', $url);
}
}
diff --git a/lib/simplepie/simplepie/src/Net/IPv6.php b/lib/simplepie/simplepie/src/Net/IPv6.php
index 73ef7be1b..db931529f 100644
--- a/lib/simplepie/simplepie/src/Net/IPv6.php
+++ b/lib/simplepie/simplepie/src/Net/IPv6.php
@@ -23,7 +23,7 @@ class IPv6
/**
* Uncompresses an IPv6 address
*
- * RFC 4291 allows you to compress concecutive zero pieces in an address to
+ * RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and expands the '::' to
* the required number of zero pieces.
*
@@ -83,7 +83,7 @@ class IPv6
/**
* Compresses an IPv6 address
*
- * RFC 4291 allows you to compress concecutive zero pieces in an address to
+ * RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and compresses consecutive
* zero pieces to '::'.
*
@@ -101,7 +101,7 @@ class IPv6
$ip_parts = self::split_v6_v4($ip);
// Replace all leading zeros
- $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
+ $ip_parts[0] = (string) preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
// Find bunches of zeros
if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
@@ -114,6 +114,7 @@ class IPv6
}
}
+ assert($pos !== null, 'For PHPStan: Since the regex matched, there is at least one match. And because the pattern is non-empty, the loop will always end with $pos ≥ 1.');
$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
}
@@ -140,6 +141,7 @@ class IPv6
{
if (strpos($ip, '.') !== false) {
$pos = strrpos($ip, ':');
+ assert($pos !== false, 'For PHPStan: IPv6 address must contain colon, since split_v6_v4 is only ever called after uncompress.');
$ipv6_part = substr($ip, 0, $pos);
$ipv4_part = substr($ip, $pos + 1);
return [$ipv6_part, $ipv4_part];
@@ -182,7 +184,11 @@ class IPv6
// Check the value is valid
$value = hexdec($ipv6_part);
- if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
+ if ($value < 0 || $value > 0xFFFF) {
+ return false;
+ }
+ assert(is_int($value), 'For PHPStan: $value is only float when $ipv6_part > PHP_INT_MAX');
+ if (dechex($value) !== strtolower($ipv6_part)) {
return false;
}
}
diff --git a/lib/simplepie/simplepie/src/Parse/Date.php b/lib/simplepie/simplepie/src/Parse/Date.php
index 4b533e70f..57318e0b4 100644
--- a/lib/simplepie/simplepie/src/Parse/Date.php
+++ b/lib/simplepie/simplepie/src/Parse/Date.php
@@ -544,7 +544,7 @@ class Date
* Array of user-added callback methods
*
* @access private
- * @var array<callable(string): string|false>
+ * @var array<callable(string): (int|false)>
*/
public $user = [];
@@ -602,12 +602,15 @@ class Date
{
foreach ($this->user as $method) {
if (($returned = call_user_func($method, $date)) !== false) {
- return $returned;
+ return (int) $returned;
}
}
foreach ($this->built_in as $method) {
- if (($returned = call_user_func([$this, $method], $date)) !== false) {
+ // TODO: we should really check this in constructor but that would require private properties.
+ /** @var callable(string): (int|false) */
+ $callable = [$this, $method];
+ if (($returned = call_user_func($callable, $date)) !== false) {
return $returned;
}
}
diff --git a/lib/simplepie/simplepie/src/Parser.php b/lib/simplepie/simplepie/src/Parser.php
index eb171d9dd..2db05b7a2 100644
--- a/lib/simplepie/simplepie/src/Parser.php
+++ b/lib/simplepie/simplepie/src/Parser.php
@@ -72,6 +72,7 @@ class Parser implements RegistryAware
// and a list of entries without an h-feed wrapper are both valid.
$query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '.
'contains(concat(" ", @class, " "), " h-entry ")]';
+ /** @var \DOMNodeList<\DOMElement> $result */
$result = $xpath->query($query);
if ($result->length !== 0) {
return $this->parse_microformats($data, $url);
@@ -146,12 +147,11 @@ class Parser implements RegistryAware
rewind($stream)) {
//Parse by chunks not to use too much memory
do {
- $stream_data = fread($stream, 1048576);
- // NB: At some point between PHP 7.3 and 7.4, the signature for `fread()` has changed
- // from returning `string` to returning `string|false`, hence the falsy check:
- if (!xml_parse($xml, $stream_data == false ? '' : $stream_data, feof($stream))) {
+ $stream_data = (string) fread($stream, 1048576);
+
+ if (!xml_parse($xml, $stream_data, feof($stream))) {
$this->error_code = xml_get_error_code($xml);
- $this->error_string = xml_error_string($this->error_code);
+ $this->error_string = xml_error_string($this->error_code) ?: "Unknown";
$return = false;
break;
}
@@ -173,7 +173,7 @@ class Parser implements RegistryAware
$xml->xml($data);
while (@$xml->read()) {
switch ($xml->nodeType) {
- case constant('XMLReader::END_ELEMENT'):
+ case \XMLReader::END_ELEMENT:
if ($xml->namespaceURI !== '') {
$tagName = $xml->namespaceURI . $this->separator . $xml->localName;
} else {
@@ -181,7 +181,7 @@ class Parser implements RegistryAware
}
$this->tag_close(null, $tagName);
break;
- case constant('XMLReader::ELEMENT'):
+ case \XMLReader::ELEMENT:
$empty = $xml->isEmptyElement;
if ($xml->namespaceURI !== '') {
$tagName = $xml->namespaceURI . $this->separator . $xml->localName;
@@ -202,9 +202,9 @@ class Parser implements RegistryAware
$this->tag_close(null, $tagName);
}
break;
- case constant('XMLReader::TEXT'):
+ case \XMLReader::TEXT:
- case constant('XMLReader::CDATA'):
+ case \XMLReader::CDATA:
$this->cdata(null, $xml->value);
break;
}
@@ -290,14 +290,14 @@ class Parser implements RegistryAware
$this->xml_base_explicit[] = true;
}
} else {
- $this->xml_base[] = end($this->xml_base);
+ $this->xml_base[] = end($this->xml_base) ?: '';
$this->xml_base_explicit[] = end($this->xml_base_explicit);
}
if (isset($attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang'])) {
$this->xml_lang[] = $attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang'];
} else {
- $this->xml_lang[] = end($this->xml_lang);
+ $this->xml_lang[] = end($this->xml_lang) ?: '';
}
if ($this->current_xhtml_construct >= 0) {
@@ -428,6 +428,9 @@ class Parser implements RegistryAware
*/
private function parse_microformats(string &$data, string $url): bool
{
+ // For PHPStan, we already check that in call site.
+ \assert(function_exists('Mf2\parse'));
+ \assert(function_exists('Mf2\fetch'));
$feed_title = '';
$feed_author = null;
$author_cache = [];
@@ -513,23 +516,24 @@ class Parser implements RegistryAware
if (isset($author_cache[$author])) {
$author = $author_cache[$author];
} else {
- $mf = \Mf2\fetch($author);
- foreach ($mf['items'] as $hcard) {
- // Only interested in an h-card by itself in this case.
- if (!in_array('h-card', $hcard['type'])) {
- continue;
- }
- // It must have a url property matching what we fetched.
- if (!isset($hcard['properties']['url']) ||
- !(in_array($author, $hcard['properties']['url']))) {
- continue;
+ if ($mf = \Mf2\fetch($author)) {
+ foreach ($mf['items'] as $hcard) {
+ // Only interested in an h-card by itself in this case.
+ if (!in_array('h-card', $hcard['type'])) {
+ continue;
+ }
+ // It must have a url property matching what we fetched.
+ if (!isset($hcard['properties']['url']) ||
+ !(in_array($author, $hcard['properties']['url']))) {
+ continue;
+ }
+ // Save parse_hcard the trouble of finding the correct url.
+ $hcard['properties']['url'][0] = $author;
+ // Cache this h-card for the next h-entry to check.
+ $author_cache[$author] = $this->parse_hcard($hcard);
+ $author = $author_cache[$author];
+ break;
}
- // Save parse_hcard the trouble of finding the correct url.
- $hcard['properties']['url'][0] = $author;
- // Cache this h-card for the next h-entry to check.
- $author_cache[$author] = $this->parse_hcard($hcard);
- $author = $author_cache[$author];
- break;
}
}
}
@@ -650,7 +654,7 @@ class Parser implements RegistryAware
private static function set_doctype(string $data): string
{
// Strip DOCTYPE except if containing an [internal subset]
- $data = preg_replace('/^\\s*<!DOCTYPE\\s[^>\\[\\]]*>\s*/', '', $data);
+ $data = preg_replace('/^\\s*<!DOCTYPE\\s[^>\\[\\]]*>\s*/', '', $data) ?? $data;
// Declare HTML entities only if no remaining DOCTYPE
$doctype = preg_match('/^\\s*<!DOCTYPE\\s/', $data) ? '' : self::declare_html_entities();
return $doctype . $data;
diff --git a/lib/simplepie/simplepie/src/Registry.php b/lib/simplepie/simplepie/src/Registry.php
index d04c22c98..228f8c33c 100644
--- a/lib/simplepie/simplepie/src/Registry.php
+++ b/lib/simplepie/simplepie/src/Registry.php
@@ -7,6 +7,7 @@ declare(strict_types=1);
namespace SimplePie;
+use InvalidArgumentException;
use SimplePie\Content\Type\Sniffer;
use SimplePie\Parse\Date;
use SimplePie\XML\Declaration\Parser as DeclarationParser;
@@ -161,9 +162,13 @@ class Registry
return null;
}
+ // For PHPStan: values in $default should be subtypes of keys.
+ /** @var class-string<T> */
$class = $this->default[$type];
if (array_key_exists($type, $this->classes)) {
+ // For PHPStan: values in $classes should be subtypes of keys.
+ /** @var class-string<T> */
$class = $this->classes[$type];
}
@@ -181,11 +186,20 @@ class Registry
public function &create($type, array $parameters = [])
{
$class = $this->get_class($type);
+ if ($class === null) {
+ throw new InvalidArgumentException(sprintf(
+ '%s(): Argument #1 ($type) "%s" not found in class list.',
+ __METHOD__,
+ $type
+ ), 1);
+ }
if (!method_exists($class, '__construct')) {
$instance = new $class();
} else {
$reflector = new \ReflectionClass($class);
+ // For PHPStan: $class is T.
+ /** @var T */
$instance = $reflector->newInstanceArgs($parameters);
}
@@ -195,6 +209,7 @@ class Registry
trigger_error(sprintf('Using the method "set_registry()" without implementing "%s" is deprecated since SimplePie 1.8.0, implement "%s" in "%s".', RegistryAware::class, RegistryAware::class, $class), \E_USER_DEPRECATED);
$instance->set_registry($this);
}
+
return $instance;
}
@@ -209,6 +224,13 @@ class Registry
public function &call($type, string $method, array $parameters = [])
{
$class = $this->get_class($type);
+ if ($class === null) {
+ throw new InvalidArgumentException(sprintf(
+ '%s(): Argument #1 ($type) "%s" not found in class list.',
+ __METHOD__,
+ $type
+ ), 1);
+ }
if (in_array($class, $this->legacy)) {
switch ($type) {
@@ -217,6 +239,8 @@ class Registry
// Cache::create() methods in PHP < 8.0.
// No longer supported as of PHP 8.0.
if ($method === 'get_handler') {
+ // Fixing this PHPStan error breaks CacheTest::testDirectOverrideLegacy()
+ /** @phpstan-ignore argument.type */
$result = @call_user_func_array([$class, 'create'], $parameters);
return $result;
}
@@ -224,7 +248,9 @@ class Registry
}
}
- $result = call_user_func_array([$class, $method], $parameters);
+ $callable = [$class, $method];
+ assert(is_callable($callable), 'For PHPstan');
+ $result = call_user_func_array($callable, $parameters);
return $result;
}
}
diff --git a/lib/simplepie/simplepie/src/Sanitize.php b/lib/simplepie/simplepie/src/Sanitize.php
index 9af3a6b12..c8aa2dce6 100644
--- a/lib/simplepie/simplepie/src/Sanitize.php
+++ b/lib/simplepie/simplepie/src/Sanitize.php
@@ -60,7 +60,7 @@ class Sanitize implements RegistryAware
public $enable_cache = true;
/** @var string */
public $cache_location = './cache';
- /** @var string */
+ /** @var string&(callable(string): string) */
public $cache_name_function = 'md5';
/**
@@ -144,7 +144,7 @@ class Sanitize implements RegistryAware
}
/**
- * @param string|NameFilter $cache_name_function
+ * @param (string&(callable(string): string))|NameFilter $cache_name_function
* @param class-string<Cache> $cache_class
* @return void
*/
@@ -168,7 +168,7 @@ class Sanitize implements RegistryAware
// BC: $cache_name_function could be a callable as string
if (is_string($cache_name_function)) {
// trigger_error(sprintf('Providing $cache_name_function as string in "%s()" is deprecated since SimplePie 1.8.0, provide as "%s" instead.', __METHOD__, NameFilter::class), \E_USER_DEPRECATED);
- $this->cache_name_function = (string) $cache_name_function;
+ $this->cache_name_function = $cache_name_function;
$cache_name_function = new CallableNameFilter($cache_name_function);
}
@@ -220,7 +220,7 @@ class Sanitize implements RegistryAware
}
/**
- * @param string[]|string $tags
+ * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing.
* @return void
*/
public function strip_htmltags($tags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'])
@@ -411,7 +411,7 @@ class Sanitize implements RegistryAware
/**
* @param int-mask-of<SimplePie::CONSTRUCT_*> $type
* @param string $base
- * @return string|bool|string[]
+ * @return string Sanitized data; false if output encoding is changed to something other than UTF-8 and conversion fails
*/
public function sanitize(string $data, int $type, string $base = '')
{
@@ -436,6 +436,10 @@ class Sanitize implements RegistryAware
$document = new \DOMDocument();
$document->encoding = 'UTF-8';
+ // PHPStan seems to have trouble resolving int-mask because bitwise
+ // operators are used when operators are used when passing this parameter.
+ // https://github.com/phpstan/phpstan/issues/9384
+ /** @var int-mask-of<SimplePie::CONSTRUCT_*> $type */
$data = $this->preprocess($data, $type);
set_error_handler([Misc::class, 'silence_errors']);
@@ -446,10 +450,13 @@ class Sanitize implements RegistryAware
// Strip comments
if ($this->strip_comments) {
+ /** @var \DOMNodeList<\DOMComment> */
$comments = $xpath->query('//comment()');
foreach ($comments as $comment) {
- $comment->parentNode->removeChild($comment);
+ $parentNode = $comment->parentNode;
+ assert($parentNode !== null, 'For PHPStan, comment must have a parent');
+ $parentNode->removeChild($comment);
}
}
@@ -521,18 +528,23 @@ class Sanitize implements RegistryAware
}
// Get content node
- $div = $document->getElementsByTagName('body')->item(0)->firstChild;
+ $div = null;
+ if (($item = $document->getElementsByTagName('body')->item(0)) !== null) {
+ $div = $item->firstChild;
+ }
// Finally, convert to a HTML string
- $data = trim($document->saveHTML($div));
+ $data = trim((string) $document->saveHTML($div));
if ($this->remove_div) {
$data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '', $data);
- $data = preg_replace('/<\/div>$/', '', $data);
+ // Cast for PHPStan, it is unable to validate a non-literal regex above.
+ $data = preg_replace('/<\/div>$/', '', (string) $data);
} else {
$data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '<div>', $data);
}
- $data = str_replace('</source>', '', $data);
+ // Cast for PHPStan, it is unable to validate a non-literal regex above.
+ $data = str_replace('</source>', '', (string) $data);
}
if ($type & \SimplePie\SimplePie::CONSTRUCT_IRI) {
@@ -547,6 +559,8 @@ class Sanitize implements RegistryAware
}
if ($this->output_encoding !== 'UTF-8') {
+ // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations.
+ /** @var string */
$data = $this->registry->call(Misc::class, 'change_encoding', [$data, 'UTF-8', $this->output_encoding]);
}
}
@@ -632,6 +646,14 @@ class Sanitize implements RegistryAware
protected function strip_tag(string $tag, DOMDocument $document, DOMXPath $xpath, int $type)
{
$elements = $xpath->query('body//' . $tag);
+
+ if ($elements === false) {
+ throw new \SimplePie\Exception(sprintf(
+ '%s(): Possibly malformed expression, check argument #1 ($tag)',
+ __METHOD__
+ ), 1);
+ }
+
if ($this->encode_instead_of_strip) {
foreach ($elements as $element) {
$fragment = $document->createDocumentFragment();
@@ -639,7 +661,7 @@ class Sanitize implements RegistryAware
// For elements which aren't script or style, include the tag itself
if (!in_array($tag, ['script', 'style'])) {
$text = '<' . $tag;
- if ($element->hasAttributes()) {
+ if ($element->attributes !== null) {
$attrs = [];
foreach ($element->attributes as $name => $attr) {
$value = $attr->value;
@@ -665,21 +687,26 @@ class Sanitize implements RegistryAware
$number = $element->childNodes->length;
for ($i = $number; $i > 0; $i--) {
- $child = $element->childNodes->item(0);
- $fragment->appendChild($child);
+ if (($child = $element->childNodes->item(0)) !== null) {
+ $fragment->appendChild($child);
+ }
}
if (!in_array($tag, ['script', 'style'])) {
$fragment->appendChild(new \DOMText('</' . $tag . '>'));
}
- $element->parentNode->replaceChild($fragment, $element);
+ if (($parentNode = $element->parentNode) !== null) {
+ $parentNode->replaceChild($fragment, $element);
+ }
}
return;
} elseif (in_array($tag, ['script', 'style'])) {
foreach ($elements as $element) {
- $element->parentNode->removeChild($element);
+ if (($parentNode = $element->parentNode) !== null) {
+ $parentNode->removeChild($element);
+ }
}
return;
@@ -688,11 +715,14 @@ class Sanitize implements RegistryAware
$fragment = $document->createDocumentFragment();
$number = $element->childNodes->length;
for ($i = $number; $i > 0; $i--) {
- $child = $element->childNodes->item(0);
- $fragment->appendChild($child);
+ if (($child = $element->childNodes->item(0)) !== null) {
+ $fragment->appendChild($child);
+ }
}
- $element->parentNode->replaceChild($fragment, $element);
+ if (($parentNode = $element->parentNode) !== null) {
+ $parentNode->replaceChild($fragment, $element);
+ }
}
}
}
@@ -704,6 +734,13 @@ class Sanitize implements RegistryAware
{
$elements = $xpath->query('//*[@' . $attrib . ']');
+ if ($elements === false) {
+ throw new \SimplePie\Exception(sprintf(
+ '%s(): Possibly malformed expression, check argument #1 ($attrib)',
+ __METHOD__
+ ), 1);
+ }
+
/** @var \DOMElement $element */
foreach ($elements as $element) {
$element->removeAttribute($attrib);
@@ -717,6 +754,13 @@ class Sanitize implements RegistryAware
{
$elements = $xpath->query('//*[@' . $attrib . ']');
+ if ($elements === false) {
+ throw new \SimplePie\Exception(sprintf(
+ '%s(): Possibly malformed expression, check argument #1 ($attrib)',
+ __METHOD__
+ ), 1);
+ }
+
/** @var \DOMElement $element */
foreach ($elements as $element) {
$element->setAttribute('data-sanitized-' . $attrib, $element->getAttribute($attrib));
diff --git a/lib/simplepie/simplepie/src/SimplePie.php b/lib/simplepie/simplepie/src/SimplePie.php
index a61e7158a..b351b1215 100644
--- a/lib/simplepie/simplepie/src/SimplePie.php
+++ b/lib/simplepie/simplepie/src/SimplePie.php
@@ -546,7 +546,7 @@ class SimplePie
public $cache_location = './cache';
/**
- * @var string Function that creates the cache filename
+ * @var string&(callable(string): string) Function that creates the cache filename
* @see SimplePie::set_cache_name_function()
* @access private
*/
@@ -1451,10 +1451,10 @@ class SimplePie
*
* @deprecated since SimplePie 1.8.0, use {@see set_cache_namefilter()} instead
*
- * @param ?callable(string): string $function Callback function
+ * @param (string&(callable(string): string))|null $function Callback function
* @return void
*/
- public function set_cache_name_function(?callable $function = null)
+ public function set_cache_name_function(?string $function = null)
{
// trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache_namefilter()" instead.', __METHOD__), \E_USER_DEPRECATED);
@@ -1510,7 +1510,7 @@ class SimplePie
}
/**
- * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags or false, to strip nothing.
+ * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing.
* @return void
*/
public function strip_htmltags($tags = '', ?bool $encode = null)
@@ -1699,7 +1699,7 @@ class SimplePie
],
'',
$stream_data
- )
+ ) ?? ''
);
}
fclose($stream);
@@ -1749,11 +1749,13 @@ class SimplePie
// Pass whatever was set with config options over to the sanitizer.
// Pass the classes in for legacy support; new classes should use the registry instead
+ $cache = $this->registry->get_class(Cache::class);
+ \assert($cache !== null, 'Cache must be defined');
$this->sanitize->pass_cache_data(
$this->enable_cache,
$this->cache_location,
$this->cache_namefilter,
- $this->registry->get_class(Cache::class),
+ $cache,
$this->cache
);
@@ -1925,7 +1927,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
- * @return array{array<string, string>, string}|array{}|bool Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type
+ * @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)
{
@@ -1999,7 +2001,7 @@ class SimplePie
$this->status_code = $file->get_status_code();
} catch (ClientException $th) {
$this->check_modified = false;
- $this->status_code = $th->getCode(); // FreshRSS https://github.com/simplepie/simplepie/pull/905
+ $this->status_code = 0;
if ($this->force_cache_fallback) {
$this->data['cache_expiration_time'] = \SimplePie\HTTP\Utils::negociate_cache_expiration_time($this->data['headers'] ?? [], $this->cache_duration, $this->cache_duration_min, $this->cache_duration_max); // FreshRSS
@@ -2093,7 +2095,6 @@ class SimplePie
} catch (ClientException $th) {
// If the file connection has an error, set SimplePie::error to that and quit
$this->error = $th->getMessage();
- $this->status_code = $th->getCode(); // FreshRSS https://github.com/simplepie/simplepie/pull/905
return !empty($this->data);
}
@@ -2141,6 +2142,8 @@ class SimplePie
// and a list of entries without an h-feed wrapper are both valid.
$query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '.
'contains(concat(" ", @class, " "), " h-entry ")]';
+
+ /** @var \DOMNodeList<\DOMElement> $result */
$result = $xpath->query($query);
$microformats = $result->length !== 0;
}
@@ -2151,11 +2154,10 @@ class SimplePie
$this->all_discovered_feeds
);
if ($microformats) {
- if ($hub = $locate->get_rel_link('hub')) {
- $self = $locate->get_rel_link('self');
- if ($file instanceof File) {
- $this->store_links($file, $hub, $self);
- }
+ $hub = $locate->get_rel_link('hub');
+ $self = $locate->get_rel_link('self');
+ if ($hub || $self) {
+ $file = $this->store_links($file, $hub, $self);
}
// Push the current file onto all_discovered feeds so the user can
// be shown this as one of the options.
@@ -2621,13 +2623,14 @@ class SimplePie
* @access private
* @see Sanitize::sanitize()
* @param string $data Data to sanitize
- * @param self::CONSTRUCT_* $type One of the self::CONSTRUCT_* constants
+ * @param int-mask-of<SimplePie::CONSTRUCT_*> $type
* @param string $base Base URL to resolve URLs against
* @return string Sanitized data
*/
public function sanitize(string $data, int $type, string $base = '')
{
try {
+ // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations.
return $this->sanitize->sanitize($data, $type, $base);
} catch (SimplePieException $e) {
if (!$this->enable_exceptions) {
@@ -3318,7 +3321,7 @@ class SimplePie
* @since Beta 2
* @param int $start Index to start at
* @param int $end Number of items to return. 0 for all items after `$start`
- * @return Item[]|null List of {@see Item} objects
+ * @return Item[] List of {@see Item} objects
*/
public function get_items(int $start = 0, int $end = 0)
{
@@ -3434,8 +3437,8 @@ class SimplePie
$class = get_class($this);
$trace = debug_backtrace();
- $file = $trace[0]['file'];
- $line = $trace[0]['line'];
+ $file = $trace[0]['file'] ?? '';
+ $line = $trace[0]['line'] ?? '';
throw new SimplePieException("Call to undefined method $class::$method() in $file on line $line");
}
@@ -3522,28 +3525,25 @@ class SimplePie
*
* There is no way to find PuSH links in the body of a microformats feed,
* so they are added to the headers when found, to be used later by get_links.
- * @param string $hub
- * @param string $self
*/
- private function store_links(File &$file, string $hub, string $self): void
+ private function store_links(Response $file, ?string $hub, ?string $self): Response
{
- if (isset($file->headers['link']) && preg_match('/rel=hub/', $file->headers['link'])) {
- return;
+ $linkHeaderLine = $file->get_header_line('link');
+ $linkHeader = $file->get_header('link');
+
+ if ($hub && !preg_match('/rel=hub/', $linkHeaderLine)) {
+ $linkHeader[] = '<'.$hub.'>; rel=hub';
}
- if ($hub) {
- if (isset($file->headers['link'])) {
- if ($file->headers['link'] !== '') {
- $file->headers['link'] = ', ';
- }
- } else {
- $file->headers['link'] = '';
- }
- $file->headers['link'] .= '<'.$hub.'>; rel=hub';
- if ($self) {
- $file->headers['link'] .= ', <'.$self.'>; rel=self';
- }
+ if ($self && !preg_match('/rel=self/', $linkHeaderLine)) {
+ $linkHeader[] = '<'.$self.'>; rel=self';
}
+
+ if (count($linkHeader) > 0) {
+ $file = $file->with_header('link', $linkHeader);
+ }
+
+ return $file;
}
/**
diff --git a/lib/simplepie/simplepie/src/Source.php b/lib/simplepie/simplepie/src/Source.php
index bd672f328..932fb84d9 100644
--- a/lib/simplepie/simplepie/src/Source.php
+++ b/lib/simplepie/simplepie/src/Source.php
@@ -373,6 +373,8 @@ class Source implements RegistryAware
$keys = array_keys($this->data['links']);
foreach ($keys as $key) {
+ $key = (string) $key;
+
if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) {
if (isset($this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key])) {
$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]);
diff --git a/lib/simplepie/simplepie/src/XML/Declaration/Parser.php b/lib/simplepie/simplepie/src/XML/Declaration/Parser.php
index 0d5a38a4c..1fcd22b9f 100644
--- a/lib/simplepie/simplepie/src/XML/Declaration/Parser.php
+++ b/lib/simplepie/simplepie/src/XML/Declaration/Parser.php
@@ -204,7 +204,8 @@ class Parser
public function version_value(): void
{
- if ($this->version = $this->get_value()) {
+ if ($version = $this->get_value()) {
+ $this->version = $version;
$this->skip_whitespace();
if ($this->has_data()) {
$this->state = self::STATE_ENCODING_NAME;
@@ -240,7 +241,8 @@ class Parser
public function encoding_value(): void
{
- if ($this->encoding = $this->get_value()) {
+ if ($encoding = $this->get_value()) {
+ $this->encoding = $encoding;
$this->skip_whitespace();
if ($this->has_data()) {
$this->state = self::STATE_STANDALONE_NAME;