mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-20 21:50:11 +08:00
fix: 排序魔法数字提取常量 + sidebar-config 远程校验类型守卫
- InstalledAppsModal 提取 ORIGIN_PRIORITY 具名常量与 originPriority(), 消除排序比较函数中的 0/1 魔法数字(保留 hasOrigin 双来源语义) - useCatalog.loadSidebarConfig 新增 isValidSidebarEntry 类型守卫,对远程 sidebar-config.json 入口做字段类型/长度/type 白名单校验,防止畸形数据进入 渲染层(type 白名单与 typedefinition.ts 的 SidebarEntry.type 严格一致) - debian/changelog 测试版本号 5.2.1.1-test - 验证:vue-tsc 0 / eslint 0 / dpkg-buildpackage 打包成功 审查项实证: - 改进③(filteredApps 多次 filter) 误报:当前已为单 filter 遍历(.24 已改), 审查贴旧片段且退化 a.origin 会破坏双来源,不改 - 改进①/② 为真改进,已采纳并保真实现
This commit is contained in:
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
spark-store (5.2.1.0) UNRELEASED; urgency=medium
|
||||
spark-store (5.2.1.1-test) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
|
||||
|
||||
|
||||
@@ -455,6 +455,11 @@ const props = defineProps<{
|
||||
const hasOrigin = (app: App, origin: "spark" | "apm"): boolean =>
|
||||
app.origins?.includes(origin) ?? app.origin === origin;
|
||||
|
||||
// 来源排序优先级:APM 始终排在前面(值越小越靠前),与排序比较函数共用,避免魔法数字
|
||||
const ORIGIN_PRIORITY: Record<"spark" | "apm", number> = { apm: 0, spark: 1 };
|
||||
const originPriority = (app: App): number =>
|
||||
hasOrigin(app, "apm") ? ORIGIN_PRIORITY.apm : ORIGIN_PRIORITY.spark;
|
||||
|
||||
// APM / Spark 分别统计实际安装的包数量(同一 pkgname 同时装两种来源时各计一次)
|
||||
// 搜索过滤后的全量(不叠加来源筛选),用于顶部统计徽章实时同步搜索结果,
|
||||
// 避免搜索时列表缩减而徽章数字仍显示全量造成误导。
|
||||
@@ -506,9 +511,9 @@ const filteredApps = computed(() => {
|
||||
|
||||
// 返回新数组排序:APM 应用始终排在前面(默认全部视图也遵守此规则)
|
||||
return [...matched].sort((a, b) => {
|
||||
const aApm = hasOrigin(a, "apm") ? 0 : 1;
|
||||
const bApm = hasOrigin(b, "apm") ? 0 : 1;
|
||||
if (aApm !== bApm) return aApm - bApm;
|
||||
const pa = originPriority(a);
|
||||
const pb = originPriority(b);
|
||||
if (pa !== pb) return pa - pb;
|
||||
// 同类内保持原有的字母序,体验更一致
|
||||
return a.pkgname.localeCompare(b.pkgname);
|
||||
});
|
||||
|
||||
@@ -69,6 +69,35 @@ export const loadCategories = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 远程 sidebar-config.json 入口的类型守卫:在写入渲染层前拦截异常字段。
|
||||
// 字段类型/长度限制防止畸形数据(如超长 name 撑破布局、非法 type 触发未知分支)。
|
||||
// type 白名单必须与 global/typedefinition.ts 的 SidebarEntry.type 保持一致。
|
||||
const VALID_SIDEBAR_TYPES = ["category", "search", "link", "homeList"] as const;
|
||||
const isValidSidebarEntry = (e: unknown): e is SidebarEntry => {
|
||||
if (typeof e !== "object" || e === null) return false;
|
||||
const entry = e as Record<string, unknown>;
|
||||
if (
|
||||
typeof entry.id !== "string" ||
|
||||
entry.id.length === 0 ||
|
||||
entry.id.length > 64
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
typeof entry.name !== "string" ||
|
||||
entry.name.length === 0 ||
|
||||
entry.name.length > 128
|
||||
)
|
||||
return false;
|
||||
if (entry.icon !== undefined && typeof entry.icon !== "string") return false;
|
||||
if (
|
||||
entry.type !== undefined &&
|
||||
!VALID_SIDEBAR_TYPES.includes(entry.type as (typeof VALID_SIDEBAR_TYPES)[number])
|
||||
)
|
||||
return false;
|
||||
if (entry.value !== undefined && typeof entry.value !== "string") return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const loadSidebarConfig = async () => {
|
||||
try {
|
||||
const arch = window.apm_store.arch || "amd64";
|
||||
@@ -86,7 +115,8 @@ export const loadSidebarConfig = async () => {
|
||||
const entries = Array.isArray(data) ? data : data.entries || [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.id && entry.name) {
|
||||
// 严格校验远程配置,避免畸形/恶意字段进入渲染层(Vue 模板自动转义已兜底 XSS)
|
||||
if (!isValidSidebarEntry(entry)) continue;
|
||||
const existing = entryMap.get(entry.id);
|
||||
if (existing) {
|
||||
// 多仓库共有入口,合并来源
|
||||
@@ -97,14 +127,13 @@ export const loadSidebarConfig = async () => {
|
||||
entryMap.set(entry.id, {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
icon: entry.icon || "",
|
||||
type: entry.type || "category",
|
||||
value: entry.value || entry.id,
|
||||
icon: entry.icon ?? "",
|
||||
type: entry.type ?? "category",
|
||||
value: entry.value ?? entry.id,
|
||||
origins: [mode],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`读取 ${mode} sidebar-config.json 失败:`, e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user