diff --git a/keepassxc-browser/content/define.js b/keepassxc-browser/content/define.js index c1da93c..80e4216 100644 --- a/keepassxc-browser/content/define.js +++ b/keepassxc-browser/content/define.js @@ -382,20 +382,20 @@ kpxcDefine.confirm = async function() { } if (kpxcDefine.selection.username) { - kpxcDefine.selection.username = kpxcFields.getId(kpxcDefine.selection.username.originalElement); + kpxcDefine.selection.username = kpxcFields.setId(kpxcDefine.selection.username.originalElement); } if (kpxcDefine.selection.password) { - kpxcDefine.selection.password = kpxcFields.getId(kpxcDefine.selection.password.originalElement); + kpxcDefine.selection.password = kpxcFields.setId(kpxcDefine.selection.password.originalElement); } if (kpxcDefine.selection.totp) { - kpxcDefine.selection.totp = kpxcFields.getId(kpxcDefine.selection.totp.originalElement); + kpxcDefine.selection.totp = kpxcFields.setId(kpxcDefine.selection.totp.originalElement); } const fieldIds = []; for (const i of kpxcDefine.selection.fields) { - fieldIds.push(kpxcFields.getId(i)); + fieldIds.push(kpxcFields.setId(i)); } const location = kpxc.getDocumentLocation(); diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 64ef84d..652ad60 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -430,8 +430,75 @@ kpxcFields.getCombination = async function(field, givenType) { return undefined; }; -// Gets of generates an unique ID for the element -kpxcFields.getId = function(target) { +// Sets and returns unique ID's for the element +kpxcFields.setId = function(target) { + return [ kpxcFields.getIdFromXPath(target), kpxcFields.getIdFromProperties(target) ]; +}; + +// Returns generated unique ID's for the element. If XPath ID fails, return the fallback one. +kpxcFields.getId = function(idArray, inputField) { + if (!idArray) { + return ''; + } + + // Legacy ID is used. Convert it to the new one if possible + if (!Array.isArray(idArray) && idArray.length > 0) { + if (idArray === kpxcFields.getLegacyId(inputField)) { + idArray = kpxcFields.setId(inputField); + } + } + + const elementFromXPath = kpxcFields.getElementFromXPathId(idArray[0]); + const fallbackId = kpxcFields.getIdFromProperties(inputField); + + return elementFromXPath || (fallbackId === idArray[1] ? inputField : ''); +}; + +// Returns element XPath +kpxcFields.getIdFromXPath = function(target) { + let xpath = ''; + let pos; + let temp; + + while (target !== document.documentElement) { + pos = 0; + temp = target; + while (temp) { + if (temp.nodeType === 1 && temp.nodeName === target.nodeName) { + pos += 1; + } + + temp = temp.previousSibling; + } + + xpath = `${target.nodeName.toLowerCase()}${(pos > 1 ? `[${pos}]/` : '/')}${xpath}`; + target = target.parentNode; + } + + xpath = `/${document.documentElement.nodeName.toLowerCase()}/${xpath}`; + xpath = xpath.replace(/\/$/, ''); + return xpath; +}; + +// Generate uniqe ID from properties (new method) +kpxcFields.getIdFromProperties = function(target) { + if (target.name) { + return `${target.nodeName} ${target.type} ${target.name} ${target.placeholder}`; + } + + if (target.classList && target.classList.length > 0) { + return `${target.nodeName} ${target.type} ${target.classList.value} ${target.placeholder}`; + } + + if (target.id && target.id !== '') { + return `${target.nodeName} ${target.type} ${kpxcFields.prepareId(target.id)} ${target.placeholder}`; + } + + return `kpxc ${target.type} ${target.clientTop}${target.clientLeft}${target.clientWidth}${target.clientHeight}${target.offsetTop}${target.offsetLeft}`; +}; + +// Legacy unique ID generation for converting +kpxcFields.getLegacyId = function(target) { if (target.classList.length > 0) { return `${target.nodeName} ${target.type} ${target.classList.value} ${target.name} ${target.placeholder}`; } @@ -443,6 +510,10 @@ kpxcFields.getId = function(target) { return `kpxc ${target.type} ${target.clientTop}${target.clientLeft}${target.clientWidth}${target.clientHeight}${target.offsetTop}${target.offsetLeft}`; }; +kpxcFields.getElementFromXPathId = function(xpath) { + return (new XPathEvaluator()).evaluate(xpath, document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +}; + // Check for new password via autocomplete attribute kpxcFields.isAutocompleteAppropriate = function(field) { const autocomplete = field.getLowerCaseAttribute('autocomplete'); @@ -556,9 +627,9 @@ kpxcFields.useCustomLoginFields = async function() { } // Finds the input field based on the stored ID - const findInputField = async function(inputFields, id) { - if (id) { - const input = inputFields.find(e => kpxcFields.getId(e) === id); + const findInputField = async function(inputFields, idArray) { + if (idArray) { + const input = inputFields.find(e => e === kpxcFields.getId(idArray, e)); if (input) { return input; } @@ -596,6 +667,12 @@ kpxcFields.useCustomLoginFields = async function() { kpxcTOTPIcons.newIcon(totp, kpxc.databaseState, true); } + // If not all expected fields are identified, return an empty combination + if ((creds.username && !username) || (creds.password && !password) || (creds.totp && !totp) + || (creds.fields.length !== stringFields.length)) { + return []; + } + const combinations = []; combinations.push({ username: username, @@ -1112,10 +1189,15 @@ kpxc.initCombinations = async function(inputs = []) { return []; } - const combinations = kpxcFields.isCustomLoginFieldsUsed() + const isCustomLoginFieldsUsed = kpxcFields.isCustomLoginFieldsUsed(); + const combinations = isCustomLoginFieldsUsed ? await kpxcFields.useCustomLoginFields() : await kpxcFields.getAllCombinations(inputs); if (!combinations || combinations.length === 0) { + if (isCustomLoginFieldsUsed) { + kpxcUI.createNotification('warning', tr('optionsCustomFieldsNotFound')); + } + return []; }