-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.ts
145 lines (113 loc) · 4.31 KB
/
index.ts
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
import escapeStringRegexp from 'escape-string-regexp';
// Copied from https://github.com/mozilla/gecko-dev/blob/073cc24f53d0cf31403121d768812146e597cc9d/toolkit/components/extensions/schemas/manifest.json#L487-L491
export const patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/;
const isFirefox = globalThis.navigator?.userAgent.includes('Firefox/');
export const allStarsRegex = isFirefox
? /^(https?|wss?):[/][/][^/]+([/].*)?$/
: /^https?:[/][/][^/]+([/].*)?$/;
export const allUrlsRegex = /^(https?|file|ftp):[/]+/;
export function assertValidPattern(matchPattern: string): void {
if (!isValidPattern(matchPattern)) {
throw new Error(matchPattern + ' is an invalid pattern. See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns for more info.');
}
}
export function isValidPattern(matchPattern: string): boolean {
return matchPattern === '<all_urls>' || patternValidationRegex.test(matchPattern);
}
export function doesUrlMatchPatterns(url: string, ...patterns: string[]): boolean {
if (patterns.includes('<all_urls>') && allUrlsRegex.test(url)) {
return true;
}
if (patterns.includes('*://*/*') && allStarsRegex.test(url)) {
return true;
}
for (const pattern of patterns) {
if (patternToRegex(pattern).test(url)) {
return true;
}
}
return false;
}
export function findMatchingPatterns(url: string, ...patterns: string[]): string[] {
return patterns.filter(pattern => doesUrlMatchPatterns(url, pattern));
}
function getRawPatternRegex(matchPattern: string): string {
assertValidPattern(matchPattern);
// Host undefined for file:///
let [, protocol, host = '', pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/);
protocol = protocol!
.replace('*', isFirefox ? '(https?|wss?)' : 'https?') // Protocol wildcard
.replaceAll(/[/]/g, '[/]'); // Escape slashes
if (host === '*') {
host = '[^/]+';
}
host &&= host
.replace(/^[*][.]/, '([^/]+.)*') // Initial wildcard
.replaceAll(/[.]/g, '[.]') // Escape dots
.replace(/[*]$/, '[^.]+'); // Last wildcard
pathname = pathname!
.replaceAll(/[/]/g, '[/]') // Escape slashes
.replaceAll(/[.]/g, '[.]') // Escape dots
.replaceAll(/[*]/g, '.*'); // Any wildcard
return '^' + protocol + host + '(' + pathname + ')?$';
}
export function patternToRegex(...matchPatterns: readonly string[]): RegExp {
// No pattern, match nothing https://stackoverflow.com/q/14115522/288906
if (matchPatterns.length === 0) {
return /$./;
}
if (matchPatterns.includes('<all_urls>')) {
return allUrlsRegex;
}
if (matchPatterns.includes('*://*/*')) {
return allStarsRegex;
}
return new RegExp(matchPatterns.map(x => getRawPatternRegex(x)).join('|'));
}
// The parens are required by .split() to preserve the symbols
const globSymbols = /([?*]+)/;
function splitReplace(part: string, index: number) {
if (part === '') {
// Shortcut for speed
return '';
}
if (index % 2 === 0) {
// Raw text, escape it
return escapeStringRegexp(part);
}
// Else: Symbol
if (part.includes('*')) { // Can be more than one and it swallows surrounding question marks
return '.*';
}
return [...part].map(() => isFirefox ? '.' : '.?').join('');
}
function getRawGlobRegex(glob: string): string {
const regexString = glob
.split(globSymbols)
// eslint-disable-next-line unicorn/no-array-callback-reference -- tis ok 🤫
.map(splitReplace)
.join('');
// Drop "start with anything" and "end with anything" sequences because they're the default for regex
return ('^' + regexString + '$')
.replace(/^[.][*]/, '')
.replace(/[.][*]$/, '')
.replace(/^[$]$/, '.+'); // Catch `*` and `*`
}
export function globToRegex(...globs: readonly string[]): RegExp {
// No glob, match anything; `include_globs: []` is the default
if (globs.length === 0) {
return /.*/;
}
return new RegExp(globs.map(x => getRawGlobRegex(x)).join('|'));
}
export function excludeDuplicatePatterns(matchPatterns: readonly string[]): string[] {
if (matchPatterns.includes('<all_urls>')) {
return ['<all_urls>'];
}
if (matchPatterns.includes('*://*/*')) {
return ['*://*/*'];
}
return matchPatterns.filter(possibleSubset => !matchPatterns.some(possibleSuperset =>
possibleSubset !== possibleSuperset && patternToRegex(possibleSuperset).test(possibleSubset),
));
}