Replace Match Patterns regex with a simpler function (#1851)

Replace Match Patterns regex with a simpler function
This commit is contained in:
Sami Vänttinen 2023-02-17 21:20:55 +02:00 committed by GitHub
parent 88a4acb1e6
commit e97df10c60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 119 deletions

View file

@ -22,9 +22,9 @@ const CHECK_UPDATE_THREE_DAYS = 3;
const CHECK_UPDATE_ONE_WEEK = 7;
const CHECK_UPDATE_ONE_MONTH = 30;
const URL_WILDCARD = '1kpxcwc1';
const schemeSegment = '(\\*|http|https|ws|wss|ftp)';
const hostSegment = '(\\*|(?:\\*\\.)?(?:[^/*]+))?';
const pathSegment = '(.*)';
const isFirefox = function() {
return navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
@ -56,86 +56,54 @@ const ManualFill = {
BOTH: 2
};
/**
* Transforms a valid match pattern into a regular expression
* which matches all URLs included by that pattern.
*
* @param {string} pattern The pattern to transform.
* @return {RegExp} The pattern's equivalent as a RegExp.
* @throws {TypeError} If the pattern is not a valid MatchPattern
*
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns
*/
const matchPatternToRegExp = function(pattern) {
if (pattern === '') {
return (/^(?:http|https|file|ftp|app):\/\//);
// Match hostname or path with wildcards
const matchWithRegex = function(firstUrlPart, secondUrlPart, hostnameUsed = false) {
if (firstUrlPart === secondUrlPart) {
return true;
}
// special handling of file:// since there is no host
if (pattern.startsWith('file://')) {
let regex = '^';
pattern = pattern.replace(/\./g, '\\.');
if (pattern.endsWith('*')) {
regex += pattern.slice(0, -1);
} else {
regex += `${pattern}$`;
}
return new RegExp(regex);
// If there's no wildcard with hostname, just compare directly
if (hostnameUsed && !firstUrlPart.includes(URL_WILDCARD) && firstUrlPart !== secondUrlPart) {
return false;
}
const matchPatternRegExp = new RegExp(
`^${schemeSegment}://${hostSegment}/${pathSegment}$`
);
const match = matchPatternRegExp.exec(pattern);
if (!match) {
throw new TypeError(`"${pattern}" is not a valid MatchPattern`);
// Escape illegal characters
let re = firstUrlPart.replaceAll(/[!\^$\+\-\(\)@<>]/g, '\\$&');
if (hostnameUsed) {
// Replace all host parts with wildcards so e.g. https://*.example.com is accepted with https://example.com
re = re.replaceAll(`${URL_WILDCARD}.`, '(.*?)');
}
let [ , scheme, host, path ] = match;
if (!host) {
throw new TypeError(`"${pattern}" does not have a valid host`);
}
// Replace any remaining wildcards for paths
re = re.replaceAll(URL_WILDCARD, '(.*?)');
let regex = '^';
if (scheme === '*') {
regex += '(http|https)';
} else {
regex += scheme;
}
regex += '://';
if (host && host === '*') {
regex += '[^/]+?';
} else if (host) {
if (host.match(/^\*\./)) {
regex += '[^/]*?';
host = host.substring(2);
}
regex += host.replace(/\./g, '\\.');
}
if (path) {
path = trimURL(path);
if (path === '*') {
regex += '(/.*)?';
} else if (path.charAt(0) !== '/') {
regex += '/';
regex += path.replace(/\./g, '\\.').replace(/\*/g, '.*?');
regex += '/?';
}
}
regex += '$';
return new RegExp(regex);
return secondUrlPart.match(new RegExp(re));
};
// Matches URL in Site Preferences with the current URL
const siteMatch = function(site, url) {
const rx = matchPatternToRegExp(site);
return url.match(rx);
try {
site = site.replaceAll('*', URL_WILDCARD);
const siteUrl = new URL(site);
const currentUrl = new URL(url);
// Match scheme and port
if (siteUrl.protocol !== currentUrl.protocol || siteUrl.port !== currentUrl.port) {
return false;
}
// Match hostname and path
if (!matchWithRegex(siteUrl.hostname, currentUrl.hostname, true)
|| !matchWithRegex(siteUrl.pathname, currentUrl.pathname)) {
return false;
}
return true;
} catch(e) {
logError(e);
}
return false;
};
const slashNeededForUrl = function(pattern) {

View file

@ -11,28 +11,8 @@ const PREDEFINED_SITELIST = [
'https://outlook.live.com/*',
'https://login.live.com/*',
'https://odc.officeapps.live.com/*',
'https://login.microsoftonline.com/*',
'https://login.microsoftonline.us/*',
'https://www.amazon.ae/ap/*',
'https://www.amazon.ca/ap/*',
'https://www.amazon.cn/ap/*',
'https://www.amazon.co.jp/ap/*',
'https://www.amazon.co.uk/ap/*',
'https://www.amazon.com/ap/*',
'https://www.amazon.com.au/ap/*',
'https://www.amazon.com.br/ap/*',
'https://www.amazon.com.mx/ap/*',
'https://www.amazon.com.tr/ap/*',
'https://www.amazon.de/ap/*',
'https://www.amazon.es/ap/*',
'https://www.amazon.fr/ap/*',
'https://www.amazon.in/ap/*',
'https://www.amazon.it/ap/*',
'https://www.amazon.nl/ap/*',
'https://www.amazon.pl/ap/*',
'https://www.amazon.sa/ap/*',
'https://www.amazon.se/ap/*',
'https://www.amazon.sg/ap/*',
'https://login.microsoftonline.*/*',
'https://www.amazon.*/ap/*',
'https://signin.aws.amazon.com/*',
'https://www.upwork.com/ab/*',
'https://home.personalcapital.com/*',
@ -41,25 +21,8 @@ const PREDEFINED_SITELIST = [
'https://secure.soundcloud.com/*',
'https://icloud.com/*',
'https://signin.benl.ebay.be/*',
'https://signin.ebay.de/*',
'https://signin.ebay.com/*',
'https://signin.ebay.com.au/*',
'https://signin.ebay.com.cn/*',
'https://signin.ebay.com.hk/*',
'https://signin.ebay.com.my/*',
'https://signin.ebay.com.sg/*',
'https://signin.ebay.it/*',
'https://signin.ebay.co.uk/*',
'https://signin.ebay.ca/*',
'https://signin.ebay.at/*',
'https://signin.ebay.be/*',
'https://signin.ebay.fr/*',
'https://signin.ebay.ie/*',
'https://signin.ebay.nl/*',
'https://signin.ebay.es/*',
'https://signin.ebay.ch/*',
'https://signin.ebay.in/*',
'https://signin.ebay.ph/*',
'https://signin.ebay.*/*',
'https://www.ebay.*/signin/*',
'https://login.yahoo.com/*',
'https://id.atlassian.com/*',
'https://www.fidelity.com/*',

View file

@ -9,14 +9,15 @@ function kpxcAssert(func, expected, card, testName) {
createResult(card, false, `Test failed: ${testName}. Result is: ${func}`);
}
function assertRegex(func, expected, card, testName) {
if ((func === null && expected === false)
|| (func && (func.length > 0) === expected)) {
function assertRegex(res, expected, card, testName) {
if ((res === null && expected === false)
|| (res && (res.length > 0) === expected)
|| (res === expected)) {
createResult(card, true, `Test passed: ${testName}`);
return;
}
createResult(card, false, `Test failed: ${testName}. Result is: ${func}`);
createResult(card, false, `Test failed: ${testName}. Result is: ${res}`);
}
async function assertInputFields(localDiv, expectedFieldCount, actionElementId) {

View file

@ -16,14 +16,13 @@
<script defer src="../common/sites.js"></script>
<script defer src="../content/ui.js"></script>
<script defer src="../content/pwgen.js"></script>
<script defer src="../content/define.js"></script>
<script defer src="../content/autocomplete.js"></script>
<script defer src="../content/banner.js"></script>
<script defer src="../content/credential-autocomplete.js"></script>
<script defer src="../content/fields.js"></script>
<script defer src="../content/form.js"></script>
<script defer src="../content/fill.js"></script>
<script defer src="../content/keepassxc-browser.js"></script>-->
<script defer src="../content/keepassxc-browser.js"></script>
<script defer src="../content/observer-helper.js"></script>
<script defer src="../content/totp-autocomplete.js"></script>
<script defer src="../content/totp-field.js"></script>
@ -131,7 +130,7 @@
<input placeholder="outsideLeft" type="password" name="outsideLeft">
</div>
<div style="position: absolute; margin-top: -1500px;">
<div style="position: absolute; top: -500px;">
<input placeholder="outsideTop" type="password" name="outsideTop">
</div>
</div>

View file

@ -29,15 +29,43 @@ async function testGeneral() {
// Consider using slighly different URL's for the tests cases.
const matches = [
[ 'https://example.com/*', 'https://example.com/login_page', true ],
[ 'https://*.lexample.com/*', 'https://example.com/login_page', false ],
[ 'https://example.com/*', 'https://example2.com/login_page', false ],
[ 'https://example.com/*', 'https://subdomain.example.com/login_page', false ],
[ 'https://example.com', 'https://subdomain.example.com/login_page', false ],
[ 'https://*.example.com/*', 'https://example.com/login_page', true ],
[ 'https://*.example.com/*', 'https://test.example.com/login_page', true ],
[ 'https://test.example.com/*', 'https://subdomain.example.com/login_page', false ],
[ 'https://test.example.com/page/*', 'https://test.example.com/page/login_page', true ],
[ 'https://test.example.com/page/*', 'https://test.example.com/page/login_page?dontcare=aboutme', true ],
[ 'https://test.example.com/page/another_page/*', 'https://test.example.com/page/login', false ],
[ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/a/', true ],
[ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/b/', false ],
[ 'https://test.example.com/*/another/a/', 'https://test.example.com/path/another/a/', true ],
[ 'https://test.example.com/path/*/a/', 'https://test.example.com/path/another/a/', true ],
[ 'https://test.example.com/path2/*/a/', 'https://test.example.com/path/another/a/', false ],
[ 'https://example.com:8448/', 'https://example.com/', false ],
[ 'https://example.com:8448/', 'https://example.com:8448/', true ],
[ 'https://example.com:8448/login/page', 'https://example.com/login/page', false ],
[ 'https://example.com:8448/*', 'https://example.com:8448/login/page', true ],
[ 'https://example.com/$/*', 'https://example.com/$/login_page', true ], // Special character in URL
[ 'https://example.com/*/*', 'https://example.com/$/login_page', true ],
[ 'https://example.com/*/*', 'https://example.com/login_page', false ],
[ 'https://*.com/*', 'https://example.com/$/login_page', true ],
[ 'https://*.com/*', 'https://example.org/$/login_page', false ],
[ 'https://*.*/*', 'https://example.org/$/login_page', false ],
// IP based URL's
[ 'https://127.128.129.130:8448/', 'https://127.128.129.130:8448/', true ],
[ 'https://127.128.129.*:8448/', 'https://127.128.129.130:8448/', true ],
[ 'https://127.128.*/', 'https://127.128.129.130/', true ],
[ 'https://127.128.*/', 'https://127.1.129.130/', false ],
[ 'https://127.128.129.130/', 'https://127.128.129.130:8448/', false ],
[ 'https://127.128.129.*/', 'https://127.128.129.130:8448/', false ],
// Invalid URL's
[ '', 'https://example.com', false ],
[ 'abcdefgetc', 'https://example.com', false ],
[ '{TOTP}\\no', 'https://example.com', false ],
[ 'https://320.320.320.320', 'https://example.com', false ]
];
for (const m of matches) {