-
Notifications
You must be signed in to change notification settings - Fork 328
Expand file tree
/
Copy pathmatch.ts
More file actions
155 lines (142 loc) · 4.6 KB
/
match.ts
File metadata and controls
155 lines (142 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { isUrlMatch, extractUrlPatterns, RuleTypeBit, type URLRuleEntry } from "./url_matcher";
import { randNum } from "./utils";
export class UrlMatch<T> {
public readonly rulesMap = new Map<T, URLRuleEntry[]>();
public readonly cacheMap = new Map<string, T[]>();
private sorter: Partial<Record<string, number>> | null = null;
public addRules(uuid: T, rules: URLRuleEntry[]) {
this.cacheMap.clear();
let map = this.rulesMap.get(uuid);
if (!map) this.rulesMap.set(uuid, (map = []));
map.push(...rules);
}
public urlMatch(url: string): T[] {
const cacheMap = this.cacheMap;
if (cacheMap.has(url)) return cacheMap.get(url) as T[];
const res: T[] = [];
for (const [uuid, rules] of this.rulesMap) {
try {
if (isUrlIncluded(url, rules)) {
res.push(uuid);
}
} catch (e) {
console.warn("Unexpected match error", e);
}
}
const sorter = this.sorter;
if (sorter !== null && typeof sorter === "object" && typeof res[0] === "string") {
(res as string[]).sort((a, b) => {
const p = sorter[a];
const q = sorter[b];
if (p! > -1 && q! > -1) {
return p! - q!;
}
return a.localeCompare(b);
});
}
cacheMap.set(url, res);
return res;
}
public clearRules(uuid: T) {
this.cacheMap.clear();
this.rulesMap.delete(uuid);
}
// 测试用
public addInclude(rulePattern: string, uuid: T) {
// @include xxxxx
const rules = extractUrlPatterns([rulePattern].map((e) => `@include ${e}`));
this.addRules(uuid, rules);
}
// 测试用
public addMatch(rulePattern: string, uuid: T) {
// @match xxxxx
const rules = extractUrlPatterns([rulePattern].map((e) => `@match ${e}`));
this.addRules(uuid, rules);
}
// 测试用
public addExclude(rulePattern: string, uuid: T) {
// @exclude xxxxx
const rules = extractUrlPatterns([rulePattern].map((e) => `@exclude ${e}`));
this.addRules(uuid, rules);
}
public setupSorter(sorter: Partial<Record<string, number>> | null) {
if (this.sorter !== sorter) {
this.cacheMap.clear();
this.sorter = sorter;
}
}
}
// 检查单一网址是否符合 Inclusion 原则
// 即匹配任一@include/@match且不匹配任何@exclude
export function isUrlIncluded(url: string, rules: URLRuleEntry[]): boolean {
let anyInclusionRule = false;
let anyExclusionRule = false;
for (const rule of rules) {
if (rule.ruleType & RuleTypeBit.INCLUSION) {
// include
if (!anyInclusionRule && isUrlMatch(url, rule)) {
// 符合 inclusion
anyInclusionRule = true;
}
} else {
// exclude
if (!isUrlMatch(url, rule)) {
// 符合 exclusion
anyExclusionRule = true;
break;
}
}
}
// true 条件: ( Any @include/@match = true ) AND ( All @exclude = false )
return anyInclusionRule && !anyExclusionRule;
}
// 检查单一网址是否符合 Exclusion 原则
// 即匹配任何@exclude或所有@include/@match皆不匹配
export function isUrlExcluded(url: string, rules: URLRuleEntry[]): boolean {
let anyInclusionRule = false;
let anyExclusionRule = false;
for (const rule of rules) {
if (rule.ruleType & RuleTypeBit.INCLUSION) {
// include
if (!anyInclusionRule && isUrlMatch(url, rule)) {
// 符合 inclusion
anyInclusionRule = true;
}
} else {
// exclude
if (!isUrlMatch(url, rule)) {
// 符合 exclusion
anyExclusionRule = true;
break;
}
}
}
// true 条件: ( All @include/@match = false ) OR ( Any @exclude = true )
return !anyInclusionRule || anyExclusionRule;
}
export const blackListSelfCheck = (blacklist: string[] | null | undefined) => {
blacklist = blacklist || [];
const scriptUrlPatterns = extractUrlPatterns([...blacklist.map((e) => `@include ${e}`)]);
const blackMatch = new UrlMatch<string>();
blackMatch.addRules("BK", scriptUrlPatterns);
for (const line of blacklist) {
const templateLine = line.replace(/[*?]/g, (a) => {
// ?: 置换成1个英文字母
if (a === "?") return String.fromCharCode(randNum(97, 122));
// *: 置换成3~5个英文字母
const s = [];
for (let i = randNum(3, 5); i > 0; i--) {
s.push(randNum(97, 122));
}
return String.fromCharCode(...s);
});
if (blackMatch.urlMatch(templateLine)[0] !== "BK") {
// 无效的复合规则
// 生成的字串不能被匹对、例如正则表达式
return { ok: false, line };
}
}
// 有效的复合规则
// 只包含 match pattern 及 glob pattern
return { ok: true };
};