From b0e93a3afed8e5f813b7f022feadf467d58ddfdd Mon Sep 17 00:00:00 2001 From: Si Zexian <113019519+Sizexian@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:15:44 +0800 Subject: [PATCH] Fix RSS feeds failing to load due to strict request header validation. --- src/bridges/utils.ts | 16 +++++++++ src/components/article.tsx | 13 +++++-- src/main/utils.ts | 70 ++++++++++++++++++++++++++++++++++++++ src/scripts/utils.ts | 48 +++++++++----------------- 4 files changed, 113 insertions(+), 34 deletions(-) diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index d042022f..fdaa9d9a 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -174,6 +174,22 @@ const utilsBridge = { initFontList: (): Promise> => { return ipcRenderer.invoke("init-font-list") }, + + fetchRSS: (url: string): Promise => { + return ipcRenderer.invoke("fetch-rss", url) + }, + + fetchFaviconHTML: (url: string): Promise => { + return ipcRenderer.invoke("fetch-favicon-html", url) + }, + + validateFavicon: (url: string): Promise => { + return ipcRenderer.invoke("validate-favicon", url) + }, + + fetchArticleHTML: (url: string): Promise => { + return ipcRenderer.invoke("fetch-article-html", url) + } } declare global { diff --git a/src/components/article.tsx b/src/components/article.tsx index 18a640ab..baea5322 100644 --- a/src/components/article.tsx +++ b/src/components/article.tsx @@ -330,9 +330,16 @@ class Article extends React.Component { this.setState({ fullContent: "", loaded: false, error: false }) const link = this.props.item.link try { - const result = await fetch(link) - if (!result || !result.ok) throw new Error() - const html = await decodeFetchResponse(result, true) + const buffer = await window.utils.fetchArticleHTML(link) + + if (!buffer) throw new Error() + + const response = new Response(buffer, { + headers: { "content-type": "text/html" }, + }) + + const html = await decodeFetchResponse(response, true) + if (link === this.props.item.link) { this.setState({ fullContent: html }) } diff --git a/src/main/utils.ts b/src/main/utils.ts index 1f18ca76..9c418576 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -308,4 +308,74 @@ export function setUtilsListeners(manager: WindowManager) { disableQuoting: true, }) }) + + ipcMain.handle("fetch-rss", async (_, url: string) => { + const res = await fetch(url, { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0", + "Accept": "application/xml,text/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9", + }, + }) + + if (!res.ok) { + dialog.showErrorBox("Failed to fetch RSS", `${res.status} ${res.statusText}`) + } + + return await res.text() + }) + + ipcMain.handle("fetch-favicon-html", async (_, url: string) => { + const res = await fetch(url, { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0", + Accept: "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8", + }, + redirect: "follow", + }) + + if (!res.ok) return null + return await res.text() + }) + + ipcMain.handle("validate-favicon", async (_, url: string) => { + try { + const res = await fetch(url, { + method: "GET", + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0", + "Accept": + "image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5", + "Accept-Language": "zh-CN,zh;q=0.9", + }, + }) + + if (!res.ok) return false + + const type = res.headers.get("content-type") ?? "" + return type.startsWith("image/") + } catch { + return false + } + }) + + ipcMain.handle("fetch-article-html", async (_, url: string) => { + const res = await fetch(url, { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0", + "Accept": + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": + "zh-CN,zh;q=0.9,en-US;q=0.6,en;q=0.5", + }, + redirect: "follow", + }) + + if (!res.ok) throw new Error(res.statusText) + return await res.arrayBuffer() + }) } diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index c62ac7db..c643434c 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -74,22 +74,18 @@ export async function decodeFetchResponse(response: Response, isHTML = false) { } export async function parseRSS(url: string) { - let result: Response + let xml: string + try { - result = await fetch(url, { credentials: "omit" }) + xml = await window.utils.fetchRSS(url) } catch { throw new Error(intl.get("log.networkError")) } - if (result && result.ok) { - try { - return await rssParser.parseString( - await decodeFetchResponse(result) - ) - } catch { - throw new Error(intl.get("log.parseError")) - } - } else { - throw new Error(result.status + " " + result.statusText) + + try { + return await rssParser.parseString(xml) + } catch { + throw new Error(intl.get("log.parseError")) } } @@ -98,11 +94,11 @@ export const domParser = new DOMParser() export async function fetchFavicon(url: string) { try { url = url.split("/").slice(0, 3).join("/") - let result = await fetch(url, { credentials: "omit" }) - if (result.ok) { - let html = await result.text() + let html = await window.utils.fetchFaviconHTML(url) + if (html) { let dom = domParser.parseFromString(html, "text/html") let links = dom.getElementsByTagName("link") + for (let link of links) { let rel = link.getAttribute("rel") if ( @@ -111,15 +107,17 @@ export async function fetchFavicon(url: string) { ) { let href = link.getAttribute("href") let parsedUrl = Url.parse(url) + if (href.startsWith("//")) return parsedUrl.protocol + href else if (href.startsWith("/")) return url + href else return href } } } - url = url + "/favicon.ico" - if (await validateFavicon(url)) { - return url + + const fallback = url + "/favicon.ico" + if (await validateFavicon(fallback)) { + return fallback } else { return null } @@ -129,19 +127,7 @@ export async function fetchFavicon(url: string) { } export async function validateFavicon(url: string) { - let flag = false - try { - const result = await fetch(url, { credentials: "omit" }) - if ( - result.status == 200 && - result.headers.has("Content-Type") && - result.headers.get("Content-Type").startsWith("image") - ) { - flag = true - } - } finally { - return flag - } + return window.utils.validateFavicon(url) } export function htmlDecode(input: string) {