From 45842474dadabf63aa17fe37c7f966d224c58bf4 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Wed, 24 Nov 2021 23:50:37 +1300 Subject: [PATCH 1/2] Some formatting tidyups to help merging in future. Areas targetted are ones likely to change when new features are added. - Arrays: Make sure one entry per line, and always trail with a comma - CSS selectors: One selector per line, a no-op fake selector to ensure every real selector ends with a trailing comma Both of the above mean when doing merges, the only lines changed are ones added/removed - not the final one in the list just because a comma was added to extend the list. index.html: Fixed up some indenting to make editing in future clearer. --- .../src/browser_action/main_popup.js | 11 +- ext/webextension/src/css/mpwd.css | 11 +- ext/webextension/src/options/globaloptions.js | 18 ++-- ext/webextension/src/options/index.html | 102 +++++++++--------- ext/webextension/src/options/options.html | 3 +- 5 files changed, 81 insertions(+), 64 deletions(-) diff --git a/ext/webextension/src/browser_action/main_popup.js b/ext/webextension/src/browser_action/main_popup.js index c53856c..994b9cc 100644 --- a/ext/webextension/src/browser_action/main_popup.js +++ b/ext/webextension/src/browser_action/main_popup.js @@ -301,7 +301,16 @@ function popup(masterkey) { } window.addEventListener('load', function () { - config.get(['username', 'key_id', 'defaulttype', 'pass_to_clipboard', 'pass_store', 'passwdtimeout', 'use_sync', 'defaultname']) + config.get([ + 'username', + 'key_id', + 'defaulttype', + 'pass_to_clipboard', + 'pass_store', + 'passwdtimeout', + 'use_sync', + 'defaultname', + ]) .then(v=>{ return runtimeSendMessage({action: 'masterkey_get', use_pass_store: !!v.pass_store}); }) diff --git a/ext/webextension/src/css/mpwd.css b/ext/webextension/src/css/mpwd.css index 291fed9..29a0c53 100644 --- a/ext/webextension/src/css/mpwd.css +++ b/ext/webextension/src/css/mpwd.css @@ -87,7 +87,10 @@ button:focus, input:focus, mp-combobox:focus-within { outline-style: auto; } -input, select, mp-combobox { +input, +select, +mp-combobox, +no-op { background: #1b1d23; color: #d7dae0; height: 2em; @@ -198,7 +201,8 @@ button#siteconfig_show { } #siteconfig > input, #siteconfig > select, -#siteconfig > option { +#siteconfig > option, +no-op { font-size: 1em; font-weight: normal; width: 6em; @@ -302,7 +306,8 @@ button#siteconfig_show { .configitem > input, .configitem > select, -.configitem > option { +.configitem > option, +no-op { margin-top:1.5em; margin-left:-8em; font-size: 1em; diff --git a/ext/webextension/src/options/globaloptions.js b/ext/webextension/src/options/globaloptions.js index 1ad9dfd..7ae479f 100644 --- a/ext/webextension/src/options/globaloptions.js +++ b/ext/webextension/src/options/globaloptions.js @@ -150,14 +150,16 @@ document.querySelector('#use_sync').addEventListener('change', async function() }); window.addEventListener('load', function() { - config.get(['defaulttype', - 'defaultname', - 'passwdtimeout', - 'pass_to_clipboard', - 'auto_submit_pass', - 'auto_submit_username', - 'pass_store', - 'use_sync']) + config.get([ + 'defaulttype', + 'defaultname', + 'passwdtimeout', + 'pass_to_clipboard', + 'auto_submit_pass', + 'auto_submit_username', + 'pass_store', + 'use_sync', + ]) .then(data => { data = Object.assign({defaulttype: 'l', passwdtimeout: 0, pass_to_clipboard: true, defaultname: '', diff --git a/ext/webextension/src/options/index.html b/ext/webextension/src/options/index.html index 78985e4..ff4ed19 100644 --- a/ext/webextension/src/options/index.html +++ b/ext/webextension/src/options/index.html @@ -34,59 +34,59 @@

Global settings

-
- - -
-
- - -
-
- - -
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/ext/webextension/src/options/options.html b/ext/webextension/src/options/options.html index f6a652a..107604d 100644 --- a/ext/webextension/src/options/options.html +++ b/ext/webextension/src/options/options.html @@ -26,7 +26,8 @@ input, select, -option { +option, +no-op { display:block; border: 0; width: 14em; From e6488c218485fa45c1c01f8bbe43585d04674743 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Wed, 24 Nov 2021 23:29:22 +1300 Subject: [PATCH 2/2] * Add new sync'd option 'treat_as_same_site', which allows configuration of which parts of a domain should be used to identify a site vs it's subdomains or base domains. Update both options pages to include this, with instructions how to use it. Default to sane values which preserve previous functionality (hardcoded recognition of .co.* domains) Rewrite domain matching routine to use new configuration option using regexes. This patch fixes an issue where countries that use second-level domains (.net.nz, .co.nz, .org.nz, .gov.nz) were misidentified with the second level domain part being used as the site name. It has sane defaults, but allows configuration so that users can customise for the particulars of their country and other websites that use a two-level base domain system. This should be fully future proof now. --- .../src/browser_action/main_popup.js | 66 ++++++++++++++----- ext/webextension/src/css/mpwd.css | 21 ++++++ ext/webextension/src/lib/config.js | 5 +- ext/webextension/src/lib/utils.js | 5 ++ ext/webextension/src/options/globaloptions.js | 5 ++ ext/webextension/src/options/index.html | 14 ++++ ext/webextension/src/options/options.html | 12 +++- 7 files changed, 110 insertions(+), 18 deletions(-) diff --git a/ext/webextension/src/browser_action/main_popup.js b/ext/webextension/src/browser_action/main_popup.js index 994b9cc..a4a129a 100644 --- a/ext/webextension/src/browser_action/main_popup.js +++ b/ext/webextension/src/browser_action/main_popup.js @@ -19,7 +19,7 @@ import {SiteStore} from "../lib/sitestore.js"; import {Site} from "../lib/sites.js"; -import {defer, copy_to_clipboard} from "../lib/utils.js"; +import {defer, copy_to_clipboard, regexpEscape} from "../lib/utils.js"; import {parseUri} from "../lib/uritools.js"; import config from "../lib/config.js"; import {ui} from "./ui.js"; @@ -241,21 +241,54 @@ function updateUIForDomainSettings(combined) } function extractDomainFromUrl(url) { - if (!url || url.startsWith('about:') - || url.startsWith('resource:') - || url.startsWith('moz-extension:') - || url.startsWith('chrome-extension:') - || url.startsWith('chrome:')) - url = ''; - let domain_parts = parseUri(url).domain.split("."); - let significant_parts = 2; - const common_slds = ['co','com','gov','govt','net','org','edu','priv','ac']; - let second_level_domain = domain_parts[domain_parts.length-2].toLowerCase(); - if (domain_parts.length > 2 && common_slds.includes(second_level_domain)) - significant_parts = 3; - - let significant_domain = domain_parts.slice(-significant_parts) - return [domain_parts, significant_domain]; + if (!url) + throw "extractDomainFromUrl: No url provided!"; + + var urlParsed = parseUri(url); + { + let protocolsIgnored = ["about", "resource", "moz-extension", "chrome-extension", "chrome", "edge", "brave"]; + if (protocolsIgnored.includes(urlParsed.protocol)) + throw "extractDomainFromUrl: Invalid url protocol provided"; + } + + let rxDomainMatchers = [ + /([^\.]+\.[^\.]+)$/i, // Default matcher matches two-part domain names (.* pattern) i.e. github.com + ]; + + config.treat_as_same_site.split('\n').forEach(function(pattern) { + pattern = pattern.trim(); + if (pattern.length == 0) return; + if (!pattern.startsWith('.')) return; + if (pattern.endsWith('.')) return; + //FIXME: More strict domain character parsing + // (should actually be config done at entry time) + pattern = '*' + pattern; + pattern = regexpEscape(pattern); + pattern = pattern.replace(/\\\*/g, '[^\.]+'); + rxDomainMatchers.push(new RegExp(pattern, 'i')); + }); + + let domainMatched = null; + + rxDomainMatchers.forEach(function(rx) { + let rxResult = rx.exec(urlParsed.domain); + if (!rxResult) return; + + if (domainMatched == null) + return domainMatched = rxResult[0]; + + // Select domain with most parts that match + let domainMatchedParts = domainMatched.split('.'); + let domainThisParts = rxResult[0].split('.'); + + if (domainThisParts.length > domainMatchedParts.length) + domainMatched = rxResult[0]; + }); + + if (domainMatched == null) + throw 'extractDomainFromUrl: Could not match any sites! Check your configuration settings!'; + + return [urlParsed.domain.split('.'), domainMatched.split('.')]; } function showSessionSetup() { @@ -310,6 +343,7 @@ window.addEventListener('load', function () { 'passwdtimeout', 'use_sync', 'defaultname', + 'treat_as_same_site', ]) .then(v=>{ return runtimeSendMessage({action: 'masterkey_get', use_pass_store: !!v.pass_store}); diff --git a/ext/webextension/src/css/mpwd.css b/ext/webextension/src/css/mpwd.css index 29a0c53..9fbf6b3 100644 --- a/ext/webextension/src/css/mpwd.css +++ b/ext/webextension/src/css/mpwd.css @@ -90,6 +90,7 @@ button:focus, input:focus, mp-combobox:focus-within { input, select, mp-combobox, +textarea, no-op { background: #1b1d23; color: #d7dae0; @@ -307,6 +308,7 @@ no-op { .configitem > input, .configitem > select, .configitem > option, +.configitem > textarea, no-op { margin-top:1.5em; margin-left:-8em; @@ -321,6 +323,25 @@ no-op { margin-top:0.2em; } +.configitem > textarea#treat_as_same_site { + width: 11.5em; + height: 13em; + font-size: 0.7em; + margin-left: -11.5em; + margin-top: 2em; + white-space: nowrap; +} + +/* FIXME: I don't understand how the left alignment of inputs works here, + using negative margins and wrapping - so I just manually margin-left + by eye to line up - however it would be better to align this p + the same way as the input it is annotating */ +.configitem > textarea#treat_as_same_site + p { + font-size: 0.6em; + margin-left: 6.7em; + line-height: normal; +} + #stored_sites { width:100%; text-align: center; diff --git a/ext/webextension/src/lib/config.js b/ext/webextension/src/lib/config.js index feabf60..18e13fa 100644 --- a/ext/webextension/src/lib/config.js +++ b/ext/webextension/src/lib/config.js @@ -53,6 +53,9 @@ class Config { get passwdtimeout() { if (typeof this._cache.passwdtimeout === 'undefined') throw new Error("need get(['passwdtimeout'])"); else return this._cache.passwdtimeout; } + get treat_as_same_site() { if (typeof this._cache.treat_as_same_site === 'undefined') + throw new Error("need get(['treat_as_same_site'])"); + else return this._cache.treat_as_same_site; } get use_sync() { if (typeof this._cache.use_sync === 'undefined') throw new Error("need get(['use_sync'])"); else return this._cache.use_sync; } @@ -137,7 +140,7 @@ class Config { if (lst.includes('username')) result.username = result.username || ''; if (lst.includes('pass_store')) result.pass_store = !!result.pass_store; if (lst.includes('passwdtimeout')) result.passwdtimeout = isNaN(result.passwdtimeout) ? -1 : result.passwdtimeout; - + if (lst.includes('treat_as_same_site')) result.treat_as_same_site = result.treat_as_same_site || [ '.ac.*', '.co.*', '.com.*', '.edu.*', '.geek.*', '.gov.*', '.govt.*', '.net.*', '.org.*', '.school.*' ].join('\n'); Object.assign(this._cache, result); return singlekey ? result[lst[0]] : result; diff --git a/ext/webextension/src/lib/utils.js b/ext/webextension/src/lib/utils.js index 5d8727d..cae4407 100644 --- a/ext/webextension/src/lib/utils.js +++ b/ext/webextension/src/lib/utils.js @@ -46,3 +46,8 @@ export function copy_to_clipboard(mimetype, data) { document.execCommand("Copy", false, null); document.oncopy=null; } + +export function regexpEscape(string) { + // https://stackoverflow.com/a/6969486 + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} diff --git a/ext/webextension/src/options/globaloptions.js b/ext/webextension/src/options/globaloptions.js index 7ae479f..5ae22b1 100644 --- a/ext/webextension/src/options/globaloptions.js +++ b/ext/webextension/src/options/globaloptions.js @@ -58,6 +58,9 @@ document.querySelector('#auto_submit_username').addEventListener('change', funct document.querySelector('#pass_store').addEventListener('change', function() { config.set({pass_store: this.checked}); }); +document.querySelector('#treat_as_same_site').addEventListener('change', function() { + config.set({treat_as_same_site: this.value}); +}); document.querySelector('#use_sync').addEventListener('change', async function() { let oldstore = (this.checked?chrome.storage.local:chrome.storage.sync); let newstore = (!this.checked?chrome.storage.local:chrome.storage.sync); @@ -158,6 +161,7 @@ window.addEventListener('load', function() { 'auto_submit_pass', 'auto_submit_username', 'pass_store', + 'treat_as_same_site', 'use_sync', ]) .then(data => { @@ -172,6 +176,7 @@ window.addEventListener('load', function() { document.querySelector('#auto_submit_pass').checked = data.auto_submit_pass; document.querySelector('#auto_submit_username').checked = data.auto_submit_username; document.querySelector('#pass_store').checked = data.pass_store; + document.querySelector('#treat_as_same_site').value = data.treat_as_same_site; document.querySelector('#use_sync').checked = data.use_sync; }); }); diff --git a/ext/webextension/src/options/index.html b/ext/webextension/src/options/index.html index ff4ed19..cd9d8d0 100644 --- a/ext/webextension/src/options/index.html +++ b/ext/webextension/src/options/index.html @@ -65,6 +65,20 @@

Global settings

+
+ + +

+ - Enter overrides above, one per line
+ - lines must start with a . (period)
+ - lines that cannot be parsed are ignored
+ - * is wildcard, will match anything except . (period)
+ - Example:
+      .net.* will treat x.net.nz as a site,
+      instead of ignoring x and using net.nz as the site +

+
+
diff --git a/ext/webextension/src/options/options.html b/ext/webextension/src/options/options.html index 107604d..4346173 100644 --- a/ext/webextension/src/options/options.html +++ b/ext/webextension/src/options/options.html @@ -8,7 +8,6 @@ div.item { break-inside: avoid; width:100%; - height:3em; display:flex; justify-content: space-between; align-items: center; @@ -27,6 +26,7 @@ input, select, option, +textarea, no-op { display:block; border: 0; @@ -37,6 +37,11 @@ input[type=checkbox] { width:1em; } +#treat_as_same_site { + height: 15em; + white-space: nowrap; + overflow: auto; +} @@ -72,6 +77,11 @@
+
+ + + +