From 102ac72d894f999bf810031c2d693fe4a705d4b9 Mon Sep 17 00:00:00 2001 From: xiyidaiwa Date: Mon, 10 Aug 2026 23:40:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=89=E5=85=A8=E5=8A=A0=E5=9B=BA=20+?= =?UTF-8?q?=20=E9=A6=96=E9=A1=B5=E6=8E=A8=E8=8D=90=E4=BC=98=E5=85=88?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E8=A7=A3=E8=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install-manager: queue-install JSON.parse 加 try-catch 防异常 payload - App.vue: openExternalUrl 协议白名单(http/https) 防恶意 scheme - AppDetailModal: sanitizeMoreContent 改用 textContent 防 v-html XSS - storeConfig: loadPriorityConfig 校验顶层结构,畸形数据回退 APM - App.vue onMounted: 首页推荐(loadHome+preloadHomeListApps) 与全量 应用加载(loadApps) 解耦,进入软件即优先显示首页推荐 --- electron/main/backend/install-manager.ts | 14 ++++++--- src/App.vue | 36 +++++++++++++++--------- src/components/AppDetailModal.vue | 13 +++++---- src/global/storeConfig.ts | 8 +++++- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 2fd0abc5..2d45b192 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -285,10 +285,16 @@ const parseUpgradableList = (output: string) => { // Listen for download requests from renderer process ipcMain.on("queue-install", async (event, download_json) => { - const download = - typeof download_json === "string" - ? JSON.parse(download_json) - : download_json; + let download: unknown; + try { + download = + typeof download_json === "string" + ? JSON.parse(download_json) + : download_json; + } catch (err) { + logger.error({ err }, "queue-install: invalid JSON payload, ignoring task"); + return; + } const { id, pkgname, metalinkUrl, filename, origin, upgradeOnly } = download || {}; diff --git a/src/App.vue b/src/App.vue index 20e96fdb..362142b7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1134,7 +1134,10 @@ const openDetail = async (app: App | OpenDetailInput) => { // 非强制分支:清除可能由「已安装页」入口遗留的 forceViewingOrigin 粘性标志, // 避免同一应用从其他页面再次打开时被锁死在旧来源(表现为设置切换不生效)。 finalApp.forceViewingOrigin = false; - if (forceOrigin && (forceOrigin === "spark" ? finalApp.sparkApp : finalApp.apmApp)) { + if ( + forceOrigin && + (forceOrigin === "spark" ? finalApp.sparkApp : finalApp.apmApp) + ) { // 已安装页 + 唯一安装来源 → 强制展示该安装类型,优先级高于用户标签策略 finalApp.viewingOrigin = forceOrigin; finalApp.forceViewingOrigin = true; @@ -1544,9 +1547,8 @@ const loadHomeListApps = async (entryId: string) => { rawApps.map((a) => { // 首页推荐列表的 jsonUrl 形如 /home/lists/xxx.json;服务端原始数据不含 category 字段, // 这里提取 URL 首段目录作为分类(无真实分类时回退 "unknown"),避免污染后续优先级匹配。 - const urlCategory = (urls[mode] || "") - .split("/") - .filter(Boolean)[0] || "unknown"; + const urlCategory = + (urls[mode] || "").split("/").filter(Boolean)[0] || "unknown"; const category = a.Category || a.category || urlCategory; let img_urls: string[] = []; @@ -2176,7 +2178,14 @@ const closeSettingsModal = () => { }; const openExternalUrl = (url: string) => { - window.open(url, "_blank", "noopener,noreferrer"); + try { + const parsed = new URL(url); + // 仅允许 http/https 协议,防止 javascript:/data: 等造成 XSS + if (!["http:", "https:"].includes(parsed.protocol)) return; + window.open(url, "_blank", "noopener,noreferrer"); + } catch { + // 非法 URL 直接忽略,不打开 + } }; const openReviewUserProfile = (review: AppReview): void => { @@ -3426,14 +3435,15 @@ onMounted(async () => { loading.value = true; homeLoading.value = true; - // 区域1(links) 与 区域2 板块(homeList) 并行;全量应用也并行加载 - await Promise.all([ - loadHome(), - preloadHomeListApps(), - loadApps(() => { - loading.value = false; // 首屏:已有部分应用即可显示,排行稍后刷新 - }), - ]); + // 首页推荐(区域1 links + 区域2 homeList 板块)轻量且独立, + // 不依赖全量应用,进入软件即优先并行加载并显示,不再与 loadApps 耦合。 + await Promise.all([loadHome(), preloadHomeListApps()]); + + // 全量应用后台加载(不阻塞首页推荐):首个分类成功即关闭首屏遮罩, + // 增量渲染,排行稍后由 loadRanking 刷新。 + void loadApps(() => { + loading.value = false; // 首屏:已有部分应用即可显示 + }); // 全量应用加载完成后再刷新排行榜,确保 spark/apm 应用均已就绪 loadRanking(); diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index 5f0ac9c8..0c457b4a 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -587,15 +587,16 @@ import type { App, AppReview, ReviewTags } from "../global/typedefinition"; /** * 净化后端返回的应用描述(v-html 前严格去除所有标签)。 - * 程序先将用户内容中的所有 HTML 标签剥离,再将 \n 转为
。 - *
是纯程序生成,不含任何用户输入,可安全放入 v-html。 + * 使用 textContent 做 HTML 实体转义,彻底杜绝