Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ImageProxy code #270

Merged
merged 4 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ parameters:
excludePaths:
analyse:
- ../FreshRSS
- xExtension-ImageProxy/configure.phtml # TODO pass
- xExtension-ImageProxy/extension.php # TODO pass
analyseAndScan:
- .git/*?
- node_modules/*?
Expand All @@ -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
1 change: 1 addition & 0 deletions xExtension-ImageProxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions xExtension-ImageProxy/configure.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>

Expand Down
115 changes: 66 additions & 49 deletions xExtension-ImageProxy/extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
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()) {
Expand All @@ -19,114 +22,122 @@ public function init(): void {
$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) {
FreshRSS_Context::userConf()->save();
}
}

/**
* @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) {
$url = substr($url, 7); // http://
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) {
$url = substr($url, 8); // https://
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
} else { // do not proxy unschemed ("//path/...") URLs
return $url;
}
} else { // unknown/unsupported (non-http) scheme
} 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;
}

$doc = new DOMDocument();
libxml_use_internal_errors(true); // prevent tag soup errors from showing
libxml_use_internal_errors(true); // prevent tag soup errors from showing
$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);
/*
/*
Due to the URL change, FreshRSS is not aware of already rendered enclosures.
Adding data-xextension-imageproxy-original-src / srcset ensures that original URLs are present in the content for the renderer check FreshRSS_Entry->containsLink.
*/
Expand All @@ -146,12 +157,18 @@ public static function swapUris(string $content): string {
$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())
Expand Down
2 changes: 1 addition & 1 deletion xExtension-ImageProxy/i18n/tr/ext.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
'scheme_default' => 'Belirtilmemiş Vekil Sunucusu',
'scheme_include' => 'Bağlantıya http*:// ekle',
'url_encode' => 'Bağlantıyı kodla',
),
),
);
2 changes: 1 addition & 1 deletion xExtension-ImageProxy/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
4 changes: 2 additions & 2 deletions xExtension-YouTube/extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,13 @@ public function getHtml(FreshRSS_Entry $entry, string $url): string
// but we keep it in the content anyway, so RSS clients can extract it to display a preview where it wants (in article listing,
// by example, like with Reeder).
if ($thumbnails->length > 0 && $thumbnails[0] instanceof DOMNode) {
$content .= '<p hidden><img class="enclosure-thumbnail" src="' . $thumbnails[0]->nodeValue . '" alt=""/></p>';
$content .= '<p hidden><img class="enclosure-thumbnail" src="' . $thumbnails[0]->nodeValue . '" alt="" /></p>';
}

$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";
Expand Down