From e97df10c6081356ee50d51567e225a3d1099a3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20V=C3=A4nttinen?= Date: Fri, 17 Feb 2023 21:20:55 +0200 Subject: [PATCH] Replace Match Patterns regex with a simpler function (#1851) Replace Match Patterns regex with a simpler function --- keepassxc-browser/common/global.js | 110 ++++++++++------------------- keepassxc-browser/common/sites.js | 45 ++---------- tests/assert.js | 9 +-- tests/tests.html | 5 +- tests/tests.js | 28 ++++++++ 5 files changed, 78 insertions(+), 119 deletions(-) diff --git a/keepassxc-browser/common/global.js b/keepassxc-browser/common/global.js index 58e48dd..65a5c4a 100755 --- a/keepassxc-browser/common/global.js +++ b/keepassxc-browser/common/global.js @@ -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) { diff --git a/keepassxc-browser/common/sites.js b/keepassxc-browser/common/sites.js index d9ca047..a4c4f24 100644 --- a/keepassxc-browser/common/sites.js +++ b/keepassxc-browser/common/sites.js @@ -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/*', diff --git a/tests/assert.js b/tests/assert.js index 443d488..45b72a3 100644 --- a/tests/assert.js +++ b/tests/assert.js @@ -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) { diff --git a/tests/tests.html b/tests/tests.html index a127575..5e8769e 100644 --- a/tests/tests.html +++ b/tests/tests.html @@ -16,14 +16,13 @@ - - --> + @@ -131,7 +130,7 @@ -
+
diff --git a/tests/tests.js b/tests/tests.js index fcd3254..ed69f32 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -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) {