diff --git a/browser-extensions/chrome/src/contentScript/Popup.ts b/browser-extensions/chrome/src/contentScript/Popup.ts index e54e4b727..a310626de 100644 --- a/browser-extensions/chrome/src/contentScript/Popup.ts +++ b/browser-extensions/chrome/src/contentScript/Popup.ts @@ -228,8 +228,10 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden createButton.style.backgroundColor = isDarkMode() ? '#374151' : '#f3f4f6'; }); - // Handle create button click - const handleCreateClick = async (e: Event) => { + /** + * Handle create button click + */ + const handleCreateClick = async (e: Event) : Promise => { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); @@ -682,8 +684,11 @@ function createCredentialList(credentials: Credential[], input: HTMLInputElement // Handle base64 image data if (cred.Logo) { try { - const base64Logo = base64Encode(cred.Logo as Uint8Array); - imgElement.src = `data:image/x-icon;base64,${base64Logo}`; + const logoBytes = toUint8Array(cred.Logo); + const base64Logo = base64Encode(logoBytes); + // Detect image type from first few bytes + const mimeType = detectMimeType(logoBytes); + imgElement.src = `data:${mimeType};base64,${base64Logo}`; } catch (error) { console.error('Error setting logo:', error); imgElement.src = `data:image/x-icon;base64,${placeholderBase64}`; @@ -1070,23 +1075,33 @@ export function openAutofillPopup(input: HTMLInputElement) : void { }); } +/** + * Convert various binary data formats to Uint8Array + */ +function toUint8Array(buffer: Uint8Array | number[] | {[key: number]: number}): Uint8Array { + if (buffer instanceof Uint8Array) { + return buffer; + } + + if (Array.isArray(buffer)) { + return new Uint8Array(buffer); + } + + // Handle object with numeric keys + const length = Object.keys(buffer).length; + const arr = new Uint8Array(length); + for (let i = 0; i < length; i++) { + arr[i] = buffer[i]; + } + return arr; +} + /** * Base64 encode binary data. */ function base64Encode(buffer: Uint8Array | number[] | {[key: number]: number}): string | null { try { - // Handle object with numeric keys - if (typeof buffer === 'object' && !Array.isArray(buffer) && !(buffer instanceof Uint8Array)) { - const length = Object.keys(buffer).length; - const arr = new Uint8Array(length); - for (let i = 0; i < length; i++) { - arr[i] = buffer[i]; - } - buffer = arr; - } - - // Convert to array if Uint8Array - const arr = Array.from(buffer as Uint8Array | number[]); + const arr = Array.from(toUint8Array(buffer)); return btoa(arr.reduce((data, byte) => data + String.fromCharCode(byte), '')); } catch (error) { console.error('Error encoding to base64:', error); @@ -1143,3 +1158,43 @@ async function getFaviconBytes(document: Document): Promise { return null; // Return null if no favicon could be downloaded } + +/** + * Detect MIME type from file signature (magic numbers) + */ +function detectMimeType(bytes: Uint8Array): string { + /** + * Check if the file is an SVG file. + */ + const isSvg = () : boolean => { + const header = new TextDecoder().decode(bytes.slice(0, 5)).toLowerCase(); + return header.includes(' { + return bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] === 0x01 && bytes[3] === 0x00; + }; + + /** + * Check if the file is an PNG file. + */ + const isPng = () : boolean => { + return bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47; + }; + + if (isSvg()) { + return 'image/svg+xml'; + } + if (isIco()) { + return 'image/x-icon'; + } + if (isPng()) { + return 'image/png'; + } + + // Default to x-icon if unknown + return 'image/x-icon'; +} diff --git a/browser-extensions/chrome/src/shared/formDetector/FormDetector.ts b/browser-extensions/chrome/src/shared/formDetector/FormDetector.ts index 79fa12ffe..946cb12a3 100644 --- a/browser-extensions/chrome/src/shared/formDetector/FormDetector.ts +++ b/browser-extensions/chrome/src/shared/formDetector/FormDetector.ts @@ -101,15 +101,33 @@ export class FormDetector { } } - // Check for parent label + // Check for parent label and table cell structure let currentElement = input; for (let i = 0; i < 3; i++) { + // Check for parent label const parentLabel = currentElement.closest('label'); if (parentLabel) { attributes.push(parentLabel.textContent?.toLowerCase() ?? ''); break; } + // Check for table cell structure + const parentTd = currentElement.closest('td'); + if (parentTd) { + // Get the parent row + const parentTr = parentTd.closest('tr'); + if (parentTr) { + // Check all sibling cells in the row + const siblingTds = parentTr.querySelectorAll('td'); + for (const td of siblingTds) { + if (td !== parentTd) { // Skip the cell containing the input + attributes.push(td.textContent?.toLowerCase() ?? ''); + } + } + } + break; // Found table structure, no need to continue up the tree + } + if (currentElement.parentElement) { currentElement = currentElement.parentElement as HTMLInputElement; } else { diff --git a/browser-extensions/chrome/src/shared/formDetector/__tests__/FormDetector.en.test.ts b/browser-extensions/chrome/src/shared/formDetector/__tests__/FormDetector.en.test.ts index c694de033..a90656b4e 100644 --- a/browser-extensions/chrome/src/shared/formDetector/__tests__/FormDetector.en.test.ts +++ b/browser-extensions/chrome/src/shared/formDetector/__tests__/FormDetector.en.test.ts @@ -44,6 +44,13 @@ describe('FormDetector English tests', () => { testField(FormField.LastName, 'fbclc_lName', htmlFile); }); + describe('English registration form 5 detection', () => { + const htmlFile = 'en-registration-form5.html'; + + testField(FormField.Username, 'aliasvault-input-7owmnahd9', htmlFile); + testField(FormField.Password, 'aliasvault-input-ienw3qgxv', htmlFile); + }); + describe('English email form 1 detection', () => { const htmlFile = 'en-email-form1.html'; diff --git a/browser-extensions/chrome/src/shared/formDetector/__tests__/test-forms/en-registration-form5.html b/browser-extensions/chrome/src/shared/formDetector/__tests__/test-forms/en-registration-form5.html new file mode 100644 index 000000000..2e52e3cfd --- /dev/null +++ b/browser-extensions/chrome/src/shared/formDetector/__tests__/test-forms/en-registration-form5.html @@ -0,0 +1,6 @@ +Login

+
username:
password:

+
Forgot your password?

+ Create Account

+
username:
password:

+
\ No newline at end of file diff --git a/docs/misc/dev/browser-extensions.md b/docs/misc/dev/browser-extensions.md index b189fd0d8..ef8e1eb5a 100644 --- a/docs/misc/dev/browser-extensions.md +++ b/docs/misc/dev/browser-extensions.md @@ -57,3 +57,4 @@ The following websites have been known to cause issues in the past (but should b | https://www.paprika-shopping.nl/nieuwsbrief/newsletter-register-landing.html | Popup CSS style conflicts | | https://bloshing.com/inschrijven-nieuwsbrief | Popup CSS style conflicts | | https://gamefaqs.gamespot.com/user | Popup buttons not working | +| https://news.ycombinator.com/login?goto=news | Popup logo not showing due to SVG format after identity creation | \ No newline at end of file