Skip to content

Commit 7214544

Browse files
committed
Merge branch 'main' into release/v1.3
2 parents b34f3ea + 7256d75 commit 7214544

10 files changed

Lines changed: 94 additions & 16 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptcat",
3-
"version": "1.3.0-beta.4",
3+
"version": "1.3.0",
44
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
55
"author": "CodFrm",
66
"license": "GPLv3",
@@ -36,6 +36,7 @@
3636
"crypto-js": "^4.2.0",
3737
"dayjs": "^1.11.13",
3838
"dexie": "^4.0.10",
39+
"dompurify": "^3.3.1",
3940
"eslint-linter-browserify": "9.26.0",
4041
"eventemitter3": "^5.0.1",
4142
"fast-xml-parser": "^5.3.6",

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/repo/resource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class ResourceDAO extends Repo<Resource> {
6464
}
6565

6666
// CompiledResource结构变更时,建议修改 CompiledResourceNamespace 以删除旧Cache
67-
export const CompiledResourceNamespace = "a51b9167-fdde-467a-a86f-75e5636adda2";
67+
export const CompiledResourceNamespace = "57d79c56-231a-42d3-b6e3-d2004ba0866f";
6868

6969
export class CompiledResourceDAO extends Repo<CompiledResource> {
7070
constructor() {

src/app/service/content/script_executor.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,13 @@ export class ScriptExecutor {
106106
// "@exclude /REGEX/" 的情况下,MV3 UserScripts API 基础匹配范围不会扩大,然后在 earlyScript 把符合 REGEX 的匹配除去
107107
// (Any @exclude = true -> 除去)
108108
// 注:如果一早已被除排,根本不会被 MV3 UserScripts API 注入。所以只考虑排除「多余的匹配」。(略过注入)
109-
if (isUrlExcluded(window.location.href, detail.scriptInfo.scriptUrlPatterns)) {
110-
// 「多余的匹配」-> 略过注入
111-
return;
109+
try {
110+
if (isUrlExcluded(window.location.href, detail.scriptInfo.scriptUrlPatterns)) {
111+
// 「多余的匹配」-> 略过注入
112+
return;
113+
}
114+
} catch (e) {
115+
console.warn("Unexpected match error", e);
112116
}
113117
}
114118
this.execEarlyScript(scriptFlag, detail.scriptInfo, envInfo);

src/pages/popup/App.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Discord, DocumentationSite, ExtVersion, ExtServer } from "@App/app/const";
2+
import { sanitizeHTML } from "@App/pkg/utils/sanitize";
23
import { Alert, Badge, Button, Card, Collapse, Dropdown, Menu, Switch, Tooltip } from "@arco-design/web-react";
34
import {
45
IconBook,
@@ -273,6 +274,9 @@ function App() {
273274
systemConfig.getCheckUpdate(),
274275
]);
275276
if (!hookMgr.isMounted) return;
277+
if (typeof checkUpdate.notice === "string") {
278+
checkUpdate.notice = sanitizeHTML(checkUpdate.notice);
279+
}
276280
setIsEnableScript(isEnableScript);
277281
setCheckUpdate(checkUpdate);
278282
};
@@ -374,13 +378,16 @@ function App() {
374378
]).then(([resp]: [{ data: { notice: string; version: string } } | null | undefined, any]) => {
375379
let newCheckUpdateState = 0;
376380
if (resp?.data) {
381+
let notice = "";
382+
if (typeof resp.data.notice === "string") notice = sanitizeHTML(resp.data.notice);
383+
const version = resp.data.version;
377384
setCheckUpdate((items) => {
378-
if (resp.data.version === items.version) {
385+
if (version === items.version) {
379386
newCheckUpdateState = 2;
380387
return items;
381388
}
382-
const isRead = items.notice !== resp.data.notice ? false : items.isRead;
383-
const newCheckUpdate = { ...resp.data, isRead };
389+
const isRead = items.notice !== notice ? false : items.isRead;
390+
const newCheckUpdate = { version, notice, isRead };
384391
systemConfig.setCheckUpdate(newCheckUpdate);
385392
return newCheckUpdate;
386393
});
@@ -482,7 +489,11 @@ function App() {
482489
<Alert
483490
style={{ display: showAlert ? "flex" : "none" }}
484491
type="info"
485-
content={<div dangerouslySetInnerHTML={{ __html: checkUpdate.notice || "" }} />}
492+
content={
493+
<div
494+
dangerouslySetInnerHTML={{ __html: checkUpdate.notice /* notice is already sanitized by dompurify */ }}
495+
/>
496+
}
486497
/>
487498
<Collapse
488499
bordered={false}

src/pkg/config/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,14 @@ export class SystemConfig {
393393
});
394394
}
395395

396-
getCheckUpdate() {
397-
return this._get<Parameters<typeof this.setCheckUpdate>[0]>("check_update", {
396+
async getCheckUpdate(opts?: { sanitizeHTML?: (html: string) => string }) {
397+
const result = await this._get<Parameters<typeof this.setCheckUpdate>[0]>("check_update", {
398398
notice: "",
399399
isRead: false,
400400
version: ExtVersion,
401401
});
402+
if (typeof opts?.sanitizeHTML === "function") result.notice = opts.sanitizeHTML(result.notice);
403+
return result;
402404
}
403405

404406
setEnableScript(enable: boolean) {

src/pkg/utils/match.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,3 +889,13 @@ describe.concurrent("@include /REGEX/", () => {
889889
expect(isUrlIncluded("http://www.hlample.com/", url.rulesMap.get("ok1")!)).toEqual(false);
890890
});
891891
});
892+
893+
describe.concurrent("invalid or unsupported glob #1271", () => {
894+
const url = new UrlMatch<string>();
895+
url.addInclude("*://*?*", "ok1");
896+
url.addInclude("*://*?page*", "ok2");
897+
it.concurrent("include *://*?*", () => {
898+
expect(url.urlMatch("http://www.example.com/?a=1")).toEqual(["ok1"]);
899+
expect(url.urlMatch("http://www.example.com/?page=1")).toEqual(["ok1", "ok2"]);
900+
});
901+
});

src/pkg/utils/match.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ export class UrlMatch<T> {
1818
if (cacheMap.has(url)) return cacheMap.get(url) as T[];
1919
const res: T[] = [];
2020
for (const [uuid, rules] of this.rulesMap) {
21-
if (isUrlIncluded(url, rules)) {
22-
res.push(uuid);
21+
try {
22+
if (isUrlIncluded(url, rules)) {
23+
res.push(uuid);
24+
}
25+
} catch (e) {
26+
console.warn("Unexpected match error", e);
2327
}
2428
}
2529
const sorter = this.sorter;

src/pkg/utils/sanitize.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import DOMPurify from "dompurify";
2+
3+
// 允许的安全 CSS 属性白名单
4+
const ALLOWED_CSS_PROPERTIES = new Set(["color", "font-size", "font-weight", "font-style"]);
5+
6+
// 过滤不安全的 CSS 属性,只保留白名单中的属性
7+
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
8+
if (node instanceof HTMLElement && node.hasAttribute("style")) {
9+
const { style } = node;
10+
for (let i = style.length - 1; i >= 0; i--) {
11+
if (!ALLOWED_CSS_PROPERTIES.has(style[i])) {
12+
style.removeProperty(style[i]);
13+
}
14+
}
15+
}
16+
});
17+
18+
// 对 HTML 进行清理,只保留安全的标签和属性
19+
export function sanitizeHTML(html: string): string {
20+
return DOMPurify.sanitize(html, {
21+
ALLOWED_TAGS: ["b", "i", "a", "br", "p", "strong", "em", "span"],
22+
ALLOWED_ATTR: ["href", "target", "style"],
23+
});
24+
}

src/pkg/utils/url_matcher.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,15 @@ export function checkUrlMatch(s: string) {
5757
}
5858

5959
const globSplit = (text: string) => {
60-
text = text.replace(/\*{2,}/g, "*"); // api定义的 glob * 是等价于 glob **
61-
text = text.replace(/\*(\?+)/g, "$1*"); // "*????" 改成 "????*",避免 backward 处理
62-
return text.split(/([*?])/g);
60+
const split = text.split(/([*?]{2,})/g);
61+
for (let i = 1; i < split.length; i += 2) {
62+
// "*????" 改成 "????*",避免 backward 处理
63+
// api定义的 glob * 是等价于 glob **
64+
const p = split[i]; // **??**??**
65+
const q = p.replace(/\*/g, ""); // ????
66+
if (p !== q) split[i] = `${q}*`; // ????*
67+
}
68+
return split.join("").split(/([*?])/g);
6369
};
6470

6571
export const extractUrlPatterns = (lines: string[]): URLRuleEntry[] => {

0 commit comments

Comments
 (0)