diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 2a9519292e..eb70f0f7dc 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -3380,6 +3380,34 @@ describe("AutofillService", () => { expect(value).toBe(false); }); + + it("validates attribute identifiers with mixed camel case and non-alpha characters", () => { + const attributes: Record = { + _$1_go_look: true, + go_look: true, + goLook: true, + go1look: true, + "go look": true, + look_go: true, + findPerson: true, + query$1: true, + look_goo: false, + golook: false, + lookgo: false, + logonField: false, + ego_input: false, + "Gold Password": false, + searching_for: false, + person_finder: false, + }; + const autofillFieldMocks = Object.keys(attributes).map((key) => + createAutofillFieldMock({ htmlID: key }), + ); + autofillFieldMocks.forEach((field) => { + const value = AutofillService["isSearchField"](field); + expect(value).toBe(attributes[field.htmlID]); + }); + }); }); describe("isFieldMatch", () => { diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 3cb73dd72b..3a809af0c3 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -44,6 +44,7 @@ export default class AutofillService implements AutofillServiceInterface { private openPasswordRepromptPopoutDebounce: NodeJS.Timeout; private currentlyOpeningPasswordRepromptPopout = false; private autofillScriptPortsSet = new Set(); + static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); constructor( private cipherService: CipherService, @@ -1380,11 +1381,33 @@ export default class AutofillService implements AutofillServiceInterface { return excludedTypes.indexOf(type) > -1; } + /** + * Identifies if a passed field contains text artifacts that identify it as a search field. + * + * @param field - The autofill field that we are validating as a search field + */ private static isSearchField(field: AutofillField) { const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder]; - const matchPattern = new RegExp(AutoFillConstants.SearchFieldNames.join("|"), "gi"); + for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) { + if (!matchFieldAttributeValues[attrIndex]) { + continue; + } - return Boolean(matchFieldAttributeValues.join(" ").match(matchPattern)); + // Separate camel case words and case them to lower case values + const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex] + .replace(/([a-z])([A-Z])/g, "$1 $2") + .toLowerCase(); + // Split the attribute by non-alphabetical characters to get the keywords + const attributeKeywords = camelCaseSeparatedFieldAttribute.split(/[^a-z]/gi); + + for (let keywordIndex = 0; keywordIndex < attributeKeywords.length; keywordIndex++) { + if (AutofillService.searchFieldNamesSet.has(attributeKeywords[keywordIndex])) { + return true; + } + } + } + + return false; } static isExcludedFieldType(field: AutofillField, excludedTypes: string[]) { @@ -1397,11 +1420,7 @@ export default class AutofillService implements AutofillServiceInterface { } // Check if the input is an untyped/mistyped search input - if (this.isSearchField(field)) { - return true; - } - - return false; + return this.isSearchField(field); } /** @@ -1525,11 +1544,7 @@ export default class AutofillService implements AutofillServiceInterface { return false; } - if (AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1)) { - return false; - } - - return true; + return !AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1); } static fieldHasDisqualifyingAttributeValue(field: AutofillField) { @@ -1572,7 +1587,11 @@ export default class AutofillService implements AutofillServiceInterface { const arr: AutofillField[] = []; pageDetails.fields.forEach((f) => { - if (AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes)) { + const isPassword = f.type === "password"; + if ( + !isPassword && + AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes) + ) { return; } @@ -1581,23 +1600,16 @@ export default class AutofillService implements AutofillServiceInterface { return; } - const isPassword = f.type === "password"; - const isLikePassword = () => { if (f.type !== "text") { return false; } - if (AutofillService.valueIsLikePassword(f.htmlID)) { - return true; - } - - if (AutofillService.valueIsLikePassword(f.htmlName)) { - return true; - } - - if (AutofillService.valueIsLikePassword(f.placeholder)) { - return true; + const testedValues = [f.htmlID, f.htmlName, f.placeholder]; + for (let i = 0; i < testedValues.length; i++) { + if (AutofillService.valueIsLikePassword(testedValues[i])) { + return true; + } } return false;