更大范围的脚本匹配

This commit is contained in:
王一之 2025-04-29 11:28:01 +08:00
parent 14baa176d9
commit ddd3219bae
4 changed files with 40 additions and 22 deletions

View File

@ -257,7 +257,7 @@ export class ResourceService {
return fetch(u.url) return fetch(u.url)
.then(async (resp) => { .then(async (resp) => {
if (resp.status !== 200) { if (resp.status !== 200) {
throw new Error(`resource response status not 200:${resp.status}`); throw new Error(`resource response status not 200: ${resp.status}`);
} }
return { return {
data: await resp.blob(), data: await resp.blob(),

View File

@ -15,20 +15,17 @@ import { subscribeScriptDelete, subscribeScriptEnable, subscribeScriptInstall }
import { ScriptService } from "./script"; import { ScriptService } from "./script";
import { runScript, stopScript } from "../offscreen/client"; import { runScript, stopScript } from "../offscreen/client";
import { getRunAt } from "./utils"; import { getRunAt } from "./utils";
import { InfoNotification, isUserScriptsAvailable, randomString } from "@App/pkg/utils/utils"; import { isUserScriptsAvailable, randomString } from "@App/pkg/utils/utils";
import Cache from "@App/app/cache"; import Cache from "@App/app/cache";
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match"; import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
import { ExtensionContentMessageSend } from "@Packages/message/extension_message"; import { ExtensionContentMessageSend } from "@Packages/message/extension_message";
import { sendMessage } from "@Packages/message/client"; import { sendMessage } from "@Packages/message/client";
import { compileInjectScript } from "../content/utils"; import { compileInjectScript } from "../content/utils";
import { PopupService } from "./popup";
import Logger from "@App/app/logger/logger";
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import PermissionVerify from "./permission_verify"; import PermissionVerify from "./permission_verify";
import { SystemConfig } from "@App/pkg/config/config"; import { SystemConfig } from "@App/pkg/config/config";
import { ResourceService } from "./resource"; import { ResourceService } from "./resource";
import { LocalStorageDAO } from "@App/app/repo/localStorage"; import { LocalStorageDAO } from "@App/app/repo/localStorage";
import i18n from "@App/locales/locales";
// 为了优化性能存储到缓存时删除了code、value与resource // 为了优化性能存储到缓存时删除了code、value与resource
export interface ScriptMatchInfo extends ScriptRunResouce { export interface ScriptMatchInfo extends ScriptRunResouce {

View File

@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { dealPatternMatches, parsePatternMatchesURL, UrlMatch } from "./match"; import { dealPatternMatches, parsePatternMatchesURL, UrlMatch } from "./match";
import path from "path";
// https://developer.chrome.com/docs/extensions/mv3/match_patterns/ // https://developer.chrome.com/docs/extensions/mv3/match_patterns/
describe("UrlMatch-google", () => { describe("UrlMatch-google", () => {
@ -39,16 +40,10 @@ describe("UrlMatch-google", () => {
describe("UrlMatch-google-error", () => { describe("UrlMatch-google-error", () => {
const url = new UrlMatch<string>(); const url = new UrlMatch<string>();
it("error-1", () => { it("error-1", () => {
expect(() => {
url.add("https://*foo/bar", "ok1");
}).toThrow(Error);
});
// 从v0.17.0开始允许这种
it("error-2", () => {
url.add("https://foo.*.bar/baz", "ok1"); url.add("https://foo.*.bar/baz", "ok1");
expect(url.match("https://foo.api.bar/baz")).toEqual(["ok1"]); expect(url.match("https://foo.api.bar/baz")).toEqual(["ok1"]);
}); });
it("error-3", () => { it("error-2", () => {
expect(() => { expect(() => {
url.add("http:/bar", "ok1"); url.add("http:/bar", "ok1");
}).toThrow(Error); }).toThrow(Error);
@ -77,6 +72,13 @@ describe("UrlMatch-search", () => {
expect(url.match("http://api.bar.example.com/")).toEqual(["ok1"]); expect(url.match("http://api.bar.example.com/")).toEqual(["ok1"]);
expect(url.match("http://api.example.com/")).toEqual([]); expect(url.match("http://api.example.com/")).toEqual([]);
}); });
it("*://example*/*/example.path*", () => {
const url = new UrlMatch<string>();
url.add("*://example*/*/example.path*", "ok1");
expect(url.match("https://example.com/foo/example.path")).toEqual(["ok1"]);
expect(url.match("https://example.com/foo/bar/example.path")).toEqual(["ok1"]);
expect(url.match("https://example.com/foo/bar/example.path2")).toEqual(["ok1"]);
});
}); });
describe("UrlMatch-port1", () => { describe("UrlMatch-port1", () => {
@ -191,7 +193,7 @@ describe("parsePatternMatchesURL", () => {
const matches = parsePatternMatchesURL("*://www.example.com*"); const matches = parsePatternMatchesURL("*://www.example.com*");
expect(matches).toEqual({ expect(matches).toEqual({
scheme: "*", scheme: "*",
host: "www.example.com", host: "*",
path: "*", path: "*",
}); });
}); });
@ -203,4 +205,24 @@ describe("parsePatternMatchesURL", () => {
path: "*", path: "*",
}); });
}); });
it("一些怪异的情况", () => {
let matches = parsePatternMatchesURL("*://*./*");
expect(matches).toEqual({
scheme: "*",
host: "*",
path: "*",
});
matches = parsePatternMatchesURL("*://example*/*");
expect(matches).toEqual({
scheme: "*",
host: "*",
path: "*",
});
matches = parsePatternMatchesURL("http*://*.example.com/*");
expect(matches).toEqual({
scheme: "*",
host: "*.example.com",
path: "*",
});
});
}); });

View File

@ -64,20 +64,11 @@ export default class Match<T> {
let pos = u.host.indexOf("*"); let pos = u.host.indexOf("*");
if (u.host === "*" || u.host === "**") { if (u.host === "*" || u.host === "**") {
pos = -1; pos = -1;
} else if (u.host.endsWith("*")) {
// 处理*结尾
if (!u.host.endsWith(":*")) {
u.host = u.host.substring(0, u.host.length - 1);
}
} }
u.host = u.host.replace(/\*/g, "[^/]*?"); u.host = u.host.replace(/\*/g, "[^/]*?");
// 处理 *.开头 // 处理 *.开头
if (u.host.startsWith("[^/]*?.")) { if (u.host.startsWith("[^/]*?.")) {
u.host = `([^/]*?\\.?)${u.host.substring(7)}`; u.host = `([^/]*?\\.?)${u.host.substring(7)}`;
} else if (pos !== -1) {
if (u.host.indexOf(".") === -1) {
return "";
}
} }
// 处理顶域 // 处理顶域
if (u.host.endsWith("tld")) { if (u.host.endsWith("tld")) {
@ -223,6 +214,7 @@ export interface PatternMatchesUrl {
} }
// 解析URL, 根据https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns?hl=zh-cn进行处理 // 解析URL, 根据https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns?hl=zh-cn进行处理
// 将一些异常情况直接转为通配用最大的范围去注册userScript在执行的时候再用UrlMatch去匹配过滤
export function parsePatternMatchesURL( export function parsePatternMatchesURL(
url: string, url: string,
options?: { options?: {
@ -251,6 +243,9 @@ export function parsePatternMatchesURL(
} }
} }
if (result) { if (result) {
if (result.scheme === "http*") {
result.scheme = "*";
}
if (result.host !== "*") { if (result.host !== "*") {
// *开头但是不是*.的情况 // *开头但是不是*.的情况
if (result.host.startsWith("*")) { if (result.host.startsWith("*")) {
@ -261,6 +256,10 @@ export function parsePatternMatchesURL(
} }
// 结尾是*的情况 // 结尾是*的情况
if (result.host.endsWith("*")) { if (result.host.endsWith("*")) {
result.host = "*";
}
// 结尾是.的情况
if (result.host.endsWith(".")) {
result.host = result.host.slice(0, -1); result.host = result.host.slice(0, -1);
} }
// 处理 www.*.example.com 的情况为 *.example.com // 处理 www.*.example.com 的情况为 *.example.com