Update ImageProxy code (#270)

* Update ImageProxy code
fix https://github.com/FreshRSS/Extensions/issues/202
Note: breaking changes. Users will have to save their configuration again

* Readme changelog

* Fixes
This commit is contained in:
Alexandre Alapetite 2025-01-13 23:34:13 +01:00 committed by GitHub
parent 6b6ea64f0b
commit 4dd0450fbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 85 additions and 61 deletions

View file

@ -12,8 +12,6 @@ parameters:
excludePaths:
analyse:
- ../FreshRSS
- xExtension-ImageProxy/configure.phtml # TODO pass
- xExtension-ImageProxy/extension.php # TODO pass
analyseAndScan:
- .git/*?
- node_modules/*?
@ -39,5 +37,7 @@ parameters:
implicitThrows: false
checkedExceptionClasses:
- 'Minz_Exception'
ignoreErrors:
- '#Only booleans are allowed in (a negated boolean|a ternary operator condition|an elseif condition|an if condition|&&|\|\|), (bool|false|int(<[0-9, max]+>)?|true|null|\|)+ given.*#'
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon

View file

@ -6,6 +6,7 @@ To use it, upload this entire directory to the FreshRSS `./extensions` directory
## Changelog
* 1.0 Breaking changes due to significant code upgrade: settings must be saved again
* 0.7.3 Turkish language support added
## Configuration settings

View file

@ -7,26 +7,30 @@
<div class="form-group">
<label class="group-name" for="image_proxy_url"><?= _t('ext.imageproxy.proxy_url') ?></label>
<div class="group-controls">
<input type="url" name="image_proxy_url" id="image_proxy_url" value="<?= FreshRSS_Context::userConf()->image_proxy_url ?>">
<input type="url" name="image_proxy_url" id="image_proxy_url"
value="<?= htmlspecialchars(FreshRSS_Context::userConf()->attributeString('image_proxy_url') ?? '', ENT_COMPAT, 'UTF-8') ?>">
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_http"><?= _t('ext.imageproxy.scheme_http') ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_http" id="image_proxy_scheme_http" value="1" <?= FreshRSS_Context::userConf()->image_proxy_scheme_http ? 'checked' : '' ?>>
<input type="checkbox" name="image_proxy_scheme_http" id="image_proxy_scheme_http" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_http') ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_https"><?= _t('ext.imageproxy.scheme_https'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_https" id="image_proxy_scheme_https" value="1" <?= FreshRSS_Context::userConf()->image_proxy_scheme_https ? 'checked' : '' ?>>
<input type="checkbox" name="image_proxy_scheme_https" id="image_proxy_scheme_https" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_https') ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_default"><?= _t('ext.imageproxy.scheme_default'); ?></label>
<div class="group-controls">
<select name="image_proxy_scheme_default" id="image_proxy_scheme_default">
<option value="<?= htmlentities(FreshRSS_Context::userConf()->image_proxy_scheme_default ?? '') ?>" selected="selected"><?= htmlentities(FreshRSS_Context::userConf()->image_proxy_scheme_default ?? '') ?></option>
<option value="<?= htmlspecialchars(FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') ?? '', ENT_COMPAT, 'UTF-8') ?>" selected="selected"><?=
htmlspecialchars(FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') ?? '', ENT_COMPAT, 'UTF-8') ?></option>
<option value="-">-</option>
<option value="auto">auto</option>
<option value="http">http</option>
@ -37,13 +41,15 @@
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_include"><?= _t('ext.imageproxy.scheme_include'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_include" id="image_proxy_scheme_include" value="1" <?= FreshRSS_Context::userConf()->image_proxy_scheme_include ? 'checked' : '' ?>>
<input type="checkbox" name="image_proxy_scheme_include" id="image_proxy_scheme_include" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include') ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_url_encode"><?= _t('ext.imageproxy.url_encode'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_url_encode" id="image_proxy_url_encode" value="1" <?= FreshRSS_Context::userConf()->image_proxy_url_encode ? 'checked' : '' ?>>
<input type="checkbox" name="image_proxy_url_encode" id="image_proxy_url_encode" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('image_proxy_url_encode') ? 'checked' : '' ?>>
</div>
</div>

View file

@ -5,12 +5,15 @@ declare(strict_types=1);
final class ImageProxyExtension extends Minz_Extension {
// Defaults
private const PROXY_URL = 'https://wsrv.nl/?url=';
private const SCHEME_HTTP = '1';
private const SCHEME_HTTPS = '';
private const SCHEME_HTTP = true;
private const SCHEME_HTTPS = false;
private const SCHEME_DEFAULT = 'auto';
private const SCHEME_INCLUDE = '';
private const URL_ENCODE = '1';
private const SCHEME_INCLUDE = false;
private const URL_ENCODE = true;
/**
* @throws FreshRSS_Context_Exception
*/
#[\Override]
public function init(): void {
if (!FreshRSS_Context::hasSystemConf()) {
@ -19,33 +22,28 @@ final class ImageProxyExtension extends Minz_Extension {
$this->registerHook('entry_before_display', [self::class, 'setImageProxyHook']);
// Defaults
$save = false;
if (is_null(FreshRSS_Context::userConf()->image_proxy_url)) {
FreshRSS_Context::userConf()->image_proxy_url = self::PROXY_URL;
if (FreshRSS_Context::userConf()->attributeString('image_proxy_url') == null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_url', self::PROXY_URL);
$save = true;
}
if (is_null(FreshRSS_Context::userConf()->image_proxy_scheme_http)) {
FreshRSS_Context::userConf()->image_proxy_scheme_http = self::SCHEME_HTTP;
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_http') === null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_http', self::SCHEME_HTTP);
$save = true;
}
if (is_null(FreshRSS_Context::userConf()->image_proxy_scheme_https)) {
FreshRSS_Context::userConf()->image_proxy_scheme_https = self::SCHEME_HTTPS;
// Legacy
if (!is_null(FreshRSS_Context::userConf()->image_proxy_force)) {
FreshRSS_Context::userConf()->image_proxy_scheme_https = FreshRSS_Context::userConf()->image_proxy_force;
FreshRSS_Context::userConf()->image_proxy_force = null; // Minz -> unset
}
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_https') === null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_https', self::SCHEME_HTTPS);
$save = true;
}
if (is_null(FreshRSS_Context::userConf()->image_proxy_scheme_default)) {
FreshRSS_Context::userConf()->image_proxy_scheme_default = self::SCHEME_DEFAULT;
if (FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') === null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_default', self::SCHEME_DEFAULT);
$save = true;
}
if (is_null(FreshRSS_Context::userConf()->image_proxy_scheme_include)) {
FreshRSS_Context::userConf()->image_proxy_scheme_include = self::SCHEME_INCLUDE;
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include') === null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_include', self::SCHEME_INCLUDE);
$save = true;
}
if (is_null(FreshRSS_Context::userConf()->image_proxy_url_encode)) {
FreshRSS_Context::userConf()->image_proxy_url_encode = self::URL_ENCODE;
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_url_encode') === null) {
FreshRSS_Context::userConf()->_attribute('image_proxy_url_encode', self::URL_ENCODE);
$save = true;
}
if ($save) {
@ -53,46 +51,52 @@ final class ImageProxyExtension extends Minz_Extension {
}
}
/**
* @throws FreshRSS_Context_Exception
*/
#[\Override]
public function handleConfigureAction(): void {
$this->registerTranslates();
if (Minz_Request::isPost()) {
FreshRSS_Context::userConf()->image_proxy_url = Minz_Request::paramString('image_proxy_url', true) ?: self::PROXY_URL;
FreshRSS_Context::userConf()->image_proxy_scheme_http = Minz_Request::paramString('image_proxy_scheme_http');
FreshRSS_Context::userConf()->image_proxy_scheme_https = Minz_Request::paramString('image_proxy_scheme_https');
FreshRSS_Context::userConf()->image_proxy_scheme_default = Minz_Request::paramString('image_proxy_scheme_default') ?: self::SCHEME_DEFAULT;
FreshRSS_Context::userConf()->image_proxy_scheme_include = Minz_Request::paramString('image_proxy_scheme_include');
FreshRSS_Context::userConf()->image_proxy_url_encode = Minz_Request::paramString('image_proxy_url_encode');
FreshRSS_Context::userConf()->_attribute('image_proxy_url', Minz_Request::paramString('image_proxy_url', plaintext: true) ?: self::PROXY_URL);
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_http', Minz_Request::paramBoolean('image_proxy_scheme_http'));
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_https', Minz_Request::paramBoolean('image_proxy_scheme_https'));
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_default', Minz_Request::paramString('image_proxy_scheme_default', plaintext: true) ?: self::SCHEME_DEFAULT);
FreshRSS_Context::userConf()->_attribute('image_proxy_scheme_include', Minz_Request::paramBoolean('image_proxy_scheme_include'));
FreshRSS_Context::userConf()->_attribute('image_proxy_url_encode', Minz_Request::paramBoolean('image_proxy_url_encode'));
FreshRSS_Context::userConf()->save();
}
}
/**
* @throws FreshRSS_Context_Exception
*/
public static function getProxyImageUri(string $url): string {
$parsed_url = parse_url($url);
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] : null;
$scheme = $parsed_url['scheme'] ?? '';
if ($scheme === 'http') {
if (!FreshRSS_Context::userConf()->image_proxy_scheme_http) {
if (!FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_http')) {
return $url;
}
if (!FreshRSS_Context::userConf()->image_proxy_scheme_include) {
if (!FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include')) {
$url = substr($url, 7); // http://
}
} elseif ($scheme === 'https') {
if (!FreshRSS_Context::userConf()->image_proxy_scheme_https) {
if (!FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_https')) {
return $url;
}
if (!FreshRSS_Context::userConf()->image_proxy_scheme_include) {
if (!FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include')) {
$url = substr($url, 8); // https://
}
} elseif (empty($scheme)) {
if (FreshRSS_Context::userConf()->image_proxy_scheme_default === 'auto') {
if (FreshRSS_Context::userConf()->image_proxy_scheme_include) {
$url = ((!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') ? 'https:' : 'http:') . $url;
} elseif ($scheme === '') {
if (FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') === 'auto') {
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include')) {
$url = ((is_string($_SERVER['HTTPS'] ?? null) && strtolower($_SERVER['HTTPS']) !== 'off') ? 'https:' : 'http:') . $url;
}
} elseif (substr(FreshRSS_Context::userConf()->image_proxy_scheme_default, 0, 4) === 'http') {
if (FreshRSS_Context::userConf()->image_proxy_scheme_include) {
$url = FreshRSS_Context::userConf()->image_proxy_scheme_default . ':' . $url;
} elseif (str_starts_with(FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') ?? '', 'http')) {
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_scheme_include')) {
$url = FreshRSS_Context::userConf()->attributeString('image_proxy_scheme_default') . ':' . $url;
}
} else { // do not proxy unschemed ("//path/...") URLs
return $url;
@ -100,21 +104,25 @@ final class ImageProxyExtension extends Minz_Extension {
} else { // unknown/unsupported (non-http) scheme
return $url;
}
if (FreshRSS_Context::userConf()->image_proxy_url_encode) {
if (FreshRSS_Context::userConf()->attributeBool('image_proxy_url_encode')) {
$url = rawurlencode($url);
}
return FreshRSS_Context::userConf()->image_proxy_url . $url;
return FreshRSS_Context::userConf()->attributeString('image_proxy_url') . $url;
}
/**
* @param array<string> $matches
* @throws FreshRSS_Context_Exception
*/
public static function getSrcSetUris(array $matches): string {
return str_replace($matches[1], self::getProxyImageUri($matches[1]), $matches[0]);
}
/**
* @throws FreshRSS_Context_Exception
*/
public static function swapUris(string $content): string {
if (empty($content)) {
if ($content === '') {
return $content;
}
@ -123,6 +131,9 @@ final class ImageProxyExtension extends Minz_Extension {
$doc->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));
$imgs = $doc->getElementsByTagName('img');
foreach ($imgs as $img) {
if (!($img instanceof DOMElement)) {
continue;
}
if ($img->hasAttribute('src')) {
$src = $img->getAttribute('src');
$newSrc = self::getProxyImageUri($src);
@ -146,12 +157,18 @@ final class ImageProxyExtension extends Minz_Extension {
$body = $doc->getElementsByTagName('body')->item(0);
$output = $doc->saveHTML($body);
if ($output === false) {
return '';
}
$output = preg_replace('/^<body>|<\/body>$/', '', $output);
$output = preg_replace('/^<body>|<\/body>$/', '', $output) ?? '';
return $output;
}
/**
* @throws FreshRSS_Context_Exception
*/
public static function setImageProxyHook(FreshRSS_Entry $entry): FreshRSS_Entry {
$entry->_content(
self::swapUris($entry->content())

View file

@ -2,7 +2,7 @@
"name": "Image Proxy",
"author": "Frans de Jonge",
"description": "No insecure content warnings or disappearing images.",
"version": "0.7.3",
"version": "1.0",
"entrypoint": "ImageProxy",
"type": "user"
}

View file

@ -224,7 +224,7 @@ final class YouTubeExtension extends Minz_Extension
$content .= $iframe;
if ($descriptions->length > 0 && $descriptions[0] instanceof DOMNode) {
$content .= '<p class="enclosure-description">' . nl2br(htmlentities($descriptions[0]->nodeValue ?? '')) . '</p>';
$content .= '<p class="enclosure-description">' . nl2br(htmlspecialchars($descriptions[0]->nodeValue ?? '', ENT_COMPAT, 'UTF-8'), use_xhtml: true) . '</p>';
}
$content .= "</div>\n";