Update form detection and popup icon display (#622)

This commit is contained in:
Leendert de Borst
2025-02-27 15:48:56 +01:00
parent 4ab5be17c0
commit f8ea8fc7ce
5 changed files with 104 additions and 17 deletions

View File

@@ -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<void> => {
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<ArrayBufferLike>);
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<Uint8Array | null> {
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('<?xml') || header.includes('<svg');
};
/**
* Check if the file is an ICO file.
*/
const isIco = () : boolean => {
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';
}

View File

@@ -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 {

View File

@@ -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';

View File

@@ -0,0 +1,6 @@
<html lang="en"><head><meta name="referrer" content="origin"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" href="y18.svg"><style>com-strongbox-extension {visibility: visible !important; /* Ensure visibility for Strongbox Extension */}</style><style>com-strongbox-extension {visibility: visible !important; /* Ensure visibility for Strongbox Extension */}</style><style>com-strongbox-extension {visibility: visible !important; /* Ensure visibility for Strongbox Extension */}</style></head><body><b>Login</b><br><br>
<form action="login" method="post"><input type="hidden" name="goto" value="news"><table border="0"><tbody><tr><td>username:</td><td><input type="text" name="acct" size="20" autocorrect="off" spellcheck="false" autocapitalize="off" autofocus="true" id="aliasvault-input-xahcz4tlf" autocomplete="false"></td></tr><tr><td>password:</td><td><input type="password" name="pw" size="20"></td></tr></tbody></table><br>
<input type="submit" value="login"></form><a href="forgot">Forgot your password?</a><br><br>
<b>Create Account</b><br><br>
<form action="login" method="post"><input type="hidden" name="goto" value="news"><input type="hidden" name="creating" value="t"><table border="0"><tbody><tr><td>username:</td><td><input type="text" name="acct" size="20" autocorrect="off" spellcheck="false" autocapitalize="off" id="aliasvault-input-7owmnahd9" autocomplete="false"></td></tr><tr><td>password:</td><td><input type="password" name="pw" size="20" id="aliasvault-input-ienw3qgxv" autocomplete="false"></td></tr></tbody></table><br>
<input type="submit" value="create account"></form></body></html>

View File

@@ -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 |