diff --git a/CHANGELOG b/CHANGELOG
index 4c27d13..7cd8353 100755
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,38 @@
+1.2.0 (29-07-2018)
+=========================
+- Replace "Ignored Sites" with new "Site Preferences" settings page [#208]
+- Fix checks throwing errors [#207, #222]
+- Ignore more non-input elements [#212, 210]
+- Fix URL matching when there is a trailing slash [#231]
+- Allow infiting waiting on "Remember Credentials" popup [#232]
+- Minor user interface adjustments [#233, #230, #213]
+- Improve search field detection [#195]
+
+1.1.7 (13-06-2018)
+=========================
+- Fix credential field detection regression [#199]
+
+1.1.6 (12-06-2018)
+=========================
+- Disable single username field detection [#194]
+- Fix ignored sites [#196]
+- Detect credential fields without type [#198]
+
+1.1.5 (11-06-2018)
+=========================
+- Fix search fields being detected as username fields [#189]
+
+1.1.4 (10-06-2018)
+=========================
+- Improve performance of field detection and limit it to 100 fields [#166,#157,185]
+- Improve option to ignore fields [#170]
+- Automatically retrieve credentials after unlocking the database [#153]
+- Fix option to only save the domain name not working as intended [#151]
+- Fix credentials popup not being shown on some websites [#154]
+- Improve detection of username fields [#164]
+- Allow filling of TOTP fields when they are on a separate page [#162]
+- Ignore invisible input fields more strictly [#176]
+
1.1.3 (11-05-2018)
=========================
- Remove autoreconnect to prevent proxy process leakage on Windows [#147]
diff --git a/keepassxc-browser/background/browserAction.js b/keepassxc-browser/background/browserAction.js
index c728f6d..caf095f 100755
--- a/keepassxc-browser/background/browserAction.js
+++ b/keepassxc-browser/background/browserAction.js
@@ -8,7 +8,7 @@ const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
browserAction.show = function(callback, tab) {
let data = {};
- if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length == 0) {
+ if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length === 0) {
browserAction.showDefault(callback, tab);
return;
}
@@ -36,7 +36,7 @@ browserAction.update = function(interval) {
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
- if (typeof data.visibleForMilliSeconds !== 'undefined') {
+ if (data.visibleForMilliSeconds !== undefined && data.visibleForMilliSeconds !== -1) {
if (data.visibleForMilliSeconds <= 0) {
browserAction.stackPop(page.currentTabId);
browserAction.show(null, {'id': page.currentTabId});
@@ -87,7 +87,7 @@ browserAction.showDefault = function(callback, tab) {
});
};
-browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
+browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
const id = tab.id || page.currentTabId;
if (!level) {
@@ -103,15 +103,15 @@ browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visib
stackData.popup = popup;
}
- if (visibleForMilliSeconds) {
+ if (visibleForMilliSeconds !== undefined) {
stackData.visibleForMilliSeconds = visibleForMilliSeconds;
}
- if (visibleForPageUpdates) {
+ if (visibleForPageUpdates !== undefined) {
stackData.visibleForPageUpdates = visibleForPageUpdates;
}
- if (redirectOffset) {
+ if (redirectOffset !== undefined) {
stackData.redirectOffset = redirectOffset;
}
@@ -181,7 +181,7 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
return;
}
- if( page.tabs[tab.id].stack.length == 0) {
+ if (page.tabs[tab.id].stack.length === 0) {
page.clearCredentials(tab.id);
return;
}
@@ -205,21 +205,23 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna
browser.storage.local.get({'settings': {}}).then(function(item) {
const settings = item.settings;
- // Don't show anything if the site is in the ignore list
- for (const site in settings.ignoredSites) {
- if (site === url) {
- return;
+ // Don't show anything if the site is in the ignore
+ if (settings.sitePreferences !== undefined) {
+ for (const site of settings.sitePreferences) {
+ if (site.ignore === IGNORE_NORMAL && (site.url === url || siteMatch(site.url, url))) {
+ return;
+ }
}
}
const id = tabId || page.currentTabId;
- let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, 0));
+ let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, -1));
if (timeoutMinMillis > 0) {
timeoutMinMillis += Date.now();
}
- const blinkTimeout = getValueOrDefault(settings, 'blinkTimeout', BLINK_TIMEOUT_DEFAULT, 0);
+ const blinkTimeout = getValueOrDefault(settings, 'blinkTimeout', BLINK_TIMEOUT_DEFAULT, -1);
const pageUpdateAllowance = getValueOrDefault(settings, 'allowedRedirect', BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
const stackData = {
diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js
index 8d1b39c..244ded0 100755
--- a/keepassxc-browser/background/event.js
+++ b/keepassxc-browser/background/event.js
@@ -159,6 +159,9 @@ kpxcEvent.onReconnect = function(callback, tab) {
// Add a small timeout after reconnecting. Just to make sure. It's not pretty, I know :(
setTimeout(() => {
keepass.reconnect(callback, tab).then((configured) => {
+ browser.tabs.sendMessage(tab.id, {
+ action: 'redetect_fields'
+ });
kpxcEvent.showStatus(configured, tab, callback);
});
}, 500);
@@ -269,6 +272,14 @@ kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
callback();
};
+kpxcEvent.pageGetLoginId = function(callback, tab) {
+ callback(page.loginId);
+};
+
+kpxcEvent.pageSetLoginId = function(callback, tab, loginId) {
+ page.loginId = loginId;
+};
+
// all methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
@@ -284,6 +295,8 @@ kpxcEvent.messageHandlers = {
'load_settings': kpxcEvent.onLoadSettings,
'lock-database': kpxcEvent.lockDatabase,
'page_clear_logins': kpxcEvent.pageClearLogins,
+ 'page_get_login_id': kpxcEvent.pageGetLoginId,
+ 'page_set_login_id': kpxcEvent.pageSetLoginId,
'pop_stack': kpxcEvent.onPopStack,
'popup_login': kpxcEvent.onLoginPopup,
'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup,
diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js
index ce7fed7..0e015ce 100644
--- a/keepassxc-browser/background/init.js
+++ b/keepassxc-browser/background/init.js
@@ -16,7 +16,6 @@ keepass.migrateKeyRing().then(() => {
// Milliseconds for intervall (e.g. to update browserAction)
const _interval = 250;
-
/**
* Generate information structure for created tab and invoke all needed
* functions if tab is created in foreground
@@ -71,7 +70,13 @@ browser.tabs.onActivated.addListener((activeInfo) => {
* @param {object} changeInfo
*/
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ // If the tab URL has changed (e.g. logged in) clear credentials
+ if (changeInfo.url) {
+ page.clearLogins(tabId);
+ }
+
if (changeInfo.status === 'complete') {
+ browserAction.showDefault(null, tab);
kpxcEvent.invoke(browserAction.removeRememberPopup, null, tabId, []);
}
});
diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js
index e8fe811..698bb36 100755
--- a/keepassxc-browser/background/keepass.js
+++ b/keepassxc-browser/background/keepass.js
@@ -185,6 +185,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
}
else if (response.error && response.errorCode) {
keepass.handleError(tab, response.errorCode, response.error);
+ callback('error');
}
else {
browserAction.showDefault(null, tab);
@@ -839,6 +840,17 @@ keepass.onNativeMessage = function(response) {
keepass.testAssociation((associationResponse) => {
keepass.isConfigured().then((configured) => {
keepass.updatePopup(configured ? 'normal' : 'cross');
+
+ // Send message to content script
+ browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
+ if (tabs.length) {
+ browser.tabs.sendMessage(tabs[0].id, {
+ action: 'check_database_hash',
+ hash: {old: keepass.previousDatabaseHash, new: keepass.databaseHash}
+ });
+ keepass.previousDatabaseHash = keepass.databaseHash;
+ }
+ });
});
}, null);
}
@@ -1041,7 +1053,9 @@ keepass.reconnect = function(callback, tab) {
};
keepass.updatePopup = function(iconType) {
- const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
- data.iconType = iconType;
- browserAction.show(null, {'id': page.currentTabId});
+ if (page && page.tabs.length > 0) {
+ const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
+ data.iconType = iconType;
+ browserAction.show(null, {'id': page.currentTabId});
+ }
};
diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js
index 60be892..f56ebac 100755
--- a/keepassxc-browser/background/page.js
+++ b/keepassxc-browser/background/page.js
@@ -16,6 +16,7 @@ var page = {};
page.tabs = [];
page.currentTabId = -1;
page.blockedTabs = [];
+page.loginId = -1;
page.initSettings = function() {
return new Promise((resolve, reject) => {
@@ -104,6 +105,10 @@ page.clearCredentials = function(tabId, complete) {
};
page.clearLogins = function(tabId) {
+ if (!page.tabs[tabId]) {
+ return;
+ }
+
page.tabs[tabId].loginList = [];
};
diff --git a/keepassxc-browser/global.js b/keepassxc-browser/global.js
index 55456cd..23cbd06 100755
--- a/keepassxc-browser/global.js
+++ b/keepassxc-browser/global.js
@@ -1,5 +1,13 @@
'use strict';
+const IGNORE_NOTHING = 'ignoreNothing';
+const IGNORE_NORMAL = 'ignoreNormal';
+const IGNORE_FULL = 'ignoreFull';
+
+var schemeSegment = '(\\*|http|https|ws|wss|file|ftp)';
+var hostSegment = '(\\*|(?:\\*\\.)?(?:[^/*]+))?';
+var pathSegment = '(.*)';
+
var isFirefox = function() {
if (!(/Chrome/.test(navigator.userAgent) && /Google/.test(navigator.vendor))) {
return true;
@@ -15,3 +23,71 @@ var showNotification = function(message) {
'message': message
});
};
+
+/**
+ * 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
+ */
+var matchPatternToRegExp = function(pattern) {
+ if (pattern === '') {
+ return (/^(?:http|https|file|ftp|app):\/\//);
+ }
+
+ const matchPatternRegExp = new RegExp(
+ `^${schemeSegment}://${hostSegment}/${pathSegment}$`
+ );
+
+ let match = matchPatternRegExp.exec(pattern);
+ if (!match) {
+ throw new TypeError(pattern + ' is not a valid MatchPattern');
+ }
+
+ let [, scheme, host, path] = match;
+ if (!host) {
+ throw new TypeError(pattern + ' does not have a valid host');
+ }
+
+ 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) {
+ if (path === '*') {
+ regex += '(/.*)?';
+ } else if (path.charAt(0) !== '/') {
+ regex += '/';
+ regex += path.replace(/\./g, '\\.').replace(/\*/g, '.*?');
+ regex += '/?';
+ }
+ }
+
+ regex += '$';
+ return new RegExp(regex);
+};
+
+var siteMatch = function(site, url) {
+ const rx = matchPatternToRegExp(site);
+ return url.match(rx);
+};
\ No newline at end of file
diff --git a/keepassxc-browser/keepassxc-browser.js b/keepassxc-browser/keepassxc-browser.js
index 490f675..3d90c51 100755
--- a/keepassxc-browser/keepassxc-browser.js
+++ b/keepassxc-browser/keepassxc-browser.js
@@ -6,6 +6,8 @@ _called.retrieveCredentials = false;
_called.clearLogins = false;
_called.manualFillRequested = 'none';
let _loginId = -1;
+let _singleInputEnabledForPage = false;
+const _maximumInputs = 100;
// Count of detected form fields on the page
var _detectedFields = 0;
@@ -21,12 +23,16 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
if (cip.u) {
cip.setValueWithChange(cip.u, cip.credentials[req.id].login);
combination = cipFields.getCombination('username', cip.u);
- _loginId = req.id;
+ browser.runtime.sendMessage({
+ action: 'page_set_login_id', args: [req.id]
+ });
cip.u.focus();
}
if (cip.p) {
cip.setValueWithChange(cip.p, cip.credentials[req.id].password);
- _loginId = req.id;
+ browser.runtime.sendMessage({
+ action: 'page_set_login_id', args: [req.id]
+ });
combination = cipFields.getCombination('password', cip.p);
}
@@ -497,7 +503,7 @@ cipForm.init = function(form, credentialFields) {
// TODO: could be called multiple times --> update credentialFields
// not already initialized && password-field is not null
- if (!form.data('cipForm-initialized') && (credentialFields.password || credentialFields.username)) {
+ if (!form.data('cipForm-initialized') && (credentialFields.password || (_singleInputEnabledForPage && credentialFields.username))) {
form.data('cipForm-initialized', true);
cipForm.setInputFields(form, credentialFields);
form.submit(cipForm.onSubmit);
@@ -614,8 +620,8 @@ cipDefine.initDescription = function() {
.addClass('btn').addClass('btn-primary')
.css('margin-right', '15px')
.click(function(e) {
- if (!cip.settings['defined-credential-fields']) {
- cip.settings['defined-credential-fields'] = {};
+ if (!cip.settings['defined-custom-fields']) {
+ cip.settings['defined-custom-fields'] = {};
}
if (cipDefine.selection.username) {
@@ -632,7 +638,8 @@ cipDefine.initDescription = function() {
fieldIds.push(cipFields.prepareId(i));
}
- cip.settings['defined-credential-fields'][document.location.href] = {
+ const location = cip.getDocumentLocation();
+ cip.settings['defined-custom-fields'][location] = {
username: cipDefine.selection.username,
password: cipDefine.selection.password,
fields: fieldIds
@@ -652,7 +659,8 @@ cipDefine.initDescription = function() {
$description.append($btnAgain);
$description.append($btnDismiss);
- if (cip.settings['defined-credential-fields'] && cip.settings['defined-credential-fields'][document.location.href]) {
+ const location = cip.getDocumentLocation();
+ if (cip.settings['defined-custom-fields'] && cip.settings['defined-custom-fields'][location]) {
const $p = jQuery('
').html('For this page credential fields are already selected and will be overwritten.
');
const $btnDiscard = jQuery('