Files
2026-01-20 21:53:59 +03:30

165 lines
4.9 KiB
JavaScript

export class Pattern {
// showError from options.js, not from migrate.js
static validate(str, type, showError) {
// --- match pattern
if (type === 'match') {
if (this.validMatchPattern(str)) { return true; }
// not valid
if (showError) {
const error = this.checkMatchPattern(str);
error && alert([browser.i18n.getMessage('regexError'), str, error].join('\n'));
}
return;
}
// --- wildcard & regex
const pat = this.get(str, type);
try {
new RegExp(pat);
return true;
}
catch (error) {
showError && alert([browser.i18n.getMessage('regexError'), str, error].join('\n'));
}
}
static get(str, type) {
return type === 'wildcard' ? this.convertWildcard(str) :
type === 'match' ? this.convertMatchPattern(str) : str;
}
// convert wildcard to regex string
static convertWildcard(str) {
// catch all
if (str === '*') { return '\\w+'; }
// no need to add scheme as search parameters are encoded url=https%3A%2F%2F
// escape regular expression special characters, minus * ?
return str.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/^\*|\*$/g, '') // trim start/end *
.replaceAll('*', '.*')
.replaceAll('?', '.');
}
// convert match pattern to regex string
static convertMatchPattern(str) {
// catch all
if (str === '<all_urls>') { return '\\w+'; }
// escape regular expression special characters, minus *
str = str.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
.replace('*://', '.+://') // convert * scheme
.replace('://*\\.', '://(.+\\.)?') // match domains & subdomains
.replaceAll('*', '.*');
// match pattern matches the whole URL
return '^' + str + '$';
}
static checkMatchPattern(str) {
// catch all
if (str === '<all_urls>') { return; }
const [, scheme, host] = str.match(/^(.+):\/\/([^/]+)\/(.*)$/) || [];
switch (true) {
case !scheme || !host:
// Invalid Pattern
return browser.i18n.getMessage('invalidPatternError');
case !['*', 'http', 'https', 'ws', 'wss'].includes(scheme):
// "*" in scheme must be the only character | Unsupported scheme
const msg = scheme.includes('*') ? 'schemeError' : 'unsupportedSchemeError';
return browser.i18n.getMessage(msg);
case host.substring(1).includes('*'):
// "*" in host must be at the start
return browser.i18n.getMessage('hostError');
case host.startsWith('*') && !host.startsWith('*.'):
// "*" in host must be the only character or be followed by "."
return browser.i18n.getMessage('hostDotError');
case host.includes(':'):
// Host must not include a port number
return browser.i18n.getMessage('portError');
}
}
// --- test match pattern validity
static validMatchPattern(p) {
// file: is not valid for proxying purpose
return p === '<all_urls>' ||
/^(https?|\*):\/\/(\*|\*\.[^*:/]+|[^*:/]+)\/.*$/i.test(p);
}
static getPassthrough(str) {
if (!str) { return [[], [], []]; }
// RegExp string
const regex = [];
// 10.0.0.0/24 -> [ip, mask] e.g ['10.0.0.0', '255.255.255.0']
const ipMask = [];
// 10.0.0.0/24 -> [start, end] e.g. ['010000000000', '010000000255']
const stEnd = [];
str.split(/[\s,;]+/).forEach(i => {
// The literal string <local> matches simple hostnames (no dots)
if (i === '<local>') {
regex.push('.+://[^.]+/');
return;
}
// --- CIDR
const [, ip, , mask] = i.match(/^(\d+(\.\d+){3})\/(\d+)$/) || [];
if (ip && mask) {
const netmask = this.getNetmask(mask);
ipMask.push(ip, netmask);
stEnd.push(this.getRange(ip, netmask));
return;
}
// --- pattern
i = i.replaceAll('.', '\\.') // literal '.'
.replaceAll('*', '.*'); // wildcard
// starting with '.'
i.startsWith('\\.') && (i = '.+://.+' + i);
// add scheme
!i.includes('://') && (i = '.+://' + i);
// add start assertion
// !i.startsWith('^') && (i = '^' + i);
// add pathname
i += '/';
regex.push(i);
});
return [regex, ipMask, stEnd];
}
// ---------- CIDR ---------------------------------------
// convert mask to netmask
static getNetmask(mask) {
return [...Array(4)].map(() => {
const n = Math.min(mask, 8);
mask -= n;
return 256 - Math.pow(2, 8 - n);
}).join('.');
}
// convert to padded start & end
static getRange(ip, mask) {
// ip array
let st = ip.split('.');
// mask array
const ma = mask.split('.');
// netmask wildcard array
let end = st.map((v, i) => Math.min(v - ma[i] + 255, 255) + '');
st = st.map(i => i.padStart(3, '0')).join('');
end = end.map(i => i.padStart(3, '0')).join('');
return [st, end];
}
}