mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-04-16 20:37:11 -04:00
* prefer feed.icon Closes #5518 Changes proposed in this pull request: - When a feed provides an icon URL (<image><url> in RSS 2.0/1.0, <atom:icon>/<atom:logo> in Atom, icon/favicon fields in JSON Feed), that URL is stored as a feedIconUrl attribute on the feed and used as the primary source for favicon downloads, instead of scraping the feed's website for <link rel="icon"> tags. - If the feed-provided icon URL fails to return a valid image, the existing fallback chain (website HTML favicon search → /favicon.ico) is preserved. Custom favicons uploaded by users always take priority and are never overridden. How to test the feature manually: 1. Add an RSS feed that includes a <image><url> element (e.g. an RSSHub feed: `https://rsshub.app/youtube/channel/UC2cRwTuSWxxEtrRnT4lrlQA`). After actualization, confirm the feed's favicon matches the avatar image from the feed, not the Bilibili site favicon. 2. Add an Atom feed containing <atom:icon> or <atom:logo> Confirm the feed icon is used. 3. Add a JSON Feed (spec: icon field). Confirm icon is preferred over favicon when both are present. 4. Temporarily point a feed's <image><url> to a broken URL. Confirm FreshRSS falls back to the website favicon silently. 5. Upload a custom favicon for a feed, then actualize it. Confirm the custom favicon is not replaced. <img width="470" height="317" alt="image" src="https://github.com/user-attachments/assets/17445154-d94c-44d6-b7e7-019bf24c5767" /> * fix(favicon): use htmlspecialchars_decode for feed image URL * Decode quotes as well * New function in our SimplePie fork https://github.com/FreshRSS/simplepie/pull/73 --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
77 lines
2.3 KiB
PHP
77 lines
2.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require dirname(__DIR__) . '/constants.php';
|
|
require LIB_PATH . '/lib_rss.php'; //Includes class autoloader
|
|
require LIB_PATH . '/favicons.php';
|
|
require LIB_PATH . '/http-conditional.php';
|
|
|
|
FreshRSS_Context::initSystem();
|
|
if (!FreshRSS_Context::hasSystemConf()) {
|
|
header('HTTP/1.1 500 Internal Server Error');
|
|
die('Invalid system init!');
|
|
}
|
|
$frameAncestors = FreshRSS_Context::systemConf()->attributeString('csp.frame-ancestors') ?? "'none'";
|
|
header("Content-Security-Policy: default-src 'none'; frame-ancestors $frameAncestors; sandbox");
|
|
header('X-Content-Type-Options: nosniff');
|
|
|
|
$no_cache = file_exists(DATA_PATH . '/no-cache.txt');
|
|
|
|
function show_default_favicon(int $cacheSeconds = 3600): void {
|
|
global $no_cache;
|
|
$default_mtime = @filemtime(DEFAULT_FAVICON) ?: 0;
|
|
if ($no_cache || !httpConditional($default_mtime, $cacheSeconds, 2)) {
|
|
header('Content-Type: image/x-icon');
|
|
header('Content-Disposition: attachment; filename="default_favicon.ico"');
|
|
readfile(DEFAULT_FAVICON);
|
|
}
|
|
}
|
|
|
|
$id = $_GET['h'] ?? '0';
|
|
if (!is_string($id) || !ctype_xdigit($id)) {
|
|
$id = '0';
|
|
}
|
|
|
|
$txt = FAVICONS_DIR . $id . '.txt';
|
|
$ico = FAVICONS_DIR . $id . '.ico';
|
|
|
|
$ico_mtime = @filemtime($ico) ?: 0;
|
|
$txt_mtime = @filemtime($txt) ?: 0;
|
|
|
|
$is_custom_favicon = $ico_mtime != false && $txt_mtime == false;
|
|
|
|
if (($ico_mtime == false || $ico_mtime < $txt_mtime || ($ico_mtime < time() - (rand(15, 20) * 86400))) && !$is_custom_favicon) {
|
|
if ($txt_mtime == false) {
|
|
show_default_favicon(1800);
|
|
exit();
|
|
}
|
|
|
|
// no ico file or we should download a new one.
|
|
$url = file_get_contents($txt);
|
|
if ($url === false) {
|
|
show_default_favicon(1800);
|
|
exit();
|
|
}
|
|
|
|
// Try downloading the URL as a direct image first (e.g. from a feed's <image><url>),
|
|
// then fall back to HTML favicon search if it is not a valid image.
|
|
if (!download_favicon_from_image_url($url, $ico) && !download_favicon($url, $ico)) {
|
|
// Download failed
|
|
if ($ico_mtime == false) {
|
|
show_default_favicon(86400);
|
|
exit();
|
|
}
|
|
|
|
touch($ico);
|
|
}
|
|
}
|
|
|
|
if ($no_cache || !httpConditional($ico_mtime, rand(14, 21) * 86400, 2)) {
|
|
$ico_content_type = contentType($ico);
|
|
header('Content-Type: ' . $ico_content_type);
|
|
header('Content-Disposition: attachment; filename="' . $id . '.ico"');
|
|
if (!$no_cache && isset($_GET['t'])) {
|
|
header('Cache-Control: immutable');
|
|
}
|
|
readfile($ico);
|
|
}
|