fix: add support for KeePass CSV import format and enhance import parsing logic

This commit is contained in:
shuaiplus
2026-06-07 19:18:17 +08:00
parent cda654e1c3
commit bfea5d0a1c
5 changed files with 51 additions and 1 deletions
+1
View File
@@ -91,6 +91,7 @@ const COMMON_IMPORT_SOURCE_IDS: ImportSourceId[] = [
'lastpass',
'dashlane_csv',
'dashlane_json',
'keepass_csv',
'keepass_xml',
'keepassx_csv',
];
+1
View File
@@ -23,6 +23,7 @@ export const IMPORT_SOURCES = [
{ id: 'lastpass', label: 'LastPass (csv)' },
{ id: 'dashlane_csv', label: 'Dashlane (csv)' },
{ id: 'dashlane_json', label: 'Dashlane (json)' },
{ id: 'keepass_csv', label: 'KeePass 1.x (csv)' },
{ id: 'keepass_xml', label: 'KeePass 2 (xml)' },
{ id: 'keepassx_csv', label: 'KeePassX (csv)' },
{ id: 'arc_csv', label: 'Arc (csv)' },
+30 -1
View File
@@ -198,6 +198,7 @@ export function parseEncryptrCsv(textRaw: string): CiphersImportPayload {
export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
const rows = parseCsv(textRaw);
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
const standardColumns = new Set(['Group', 'Title', 'Username', 'Password', 'URL', 'Notes', 'TOTP']);
for (const row of rows) {
if (!txt(row.Title)) continue;
const cipher = makeLoginCipher();
@@ -209,12 +210,34 @@ export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
login.totp = val(row.TOTP);
const uri = normalizeUri(row.URL || '');
login.uris = uri ? [{ uri, match: null }] : null;
for (const [key, value] of Object.entries(row)) {
if (standardColumns.has(key)) continue;
processKvp(cipher, key, value, false);
}
const idx = result.ciphers.push(cipher) - 1;
addFolder(result, txt(row.Group).replace(/^Root\//, ''), idx);
}
return result;
}
export function parseKeePassCsv(textRaw: string): CiphersImportPayload {
const rows = parseCsv(textRaw);
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
for (const row of rows) {
if (!txt(row.Account)) continue;
const cipher = makeLoginCipher();
cipher.name = val(row.Account, '--');
cipher.notes = val(row.Comments);
const login = cipher.login as Record<string, unknown>;
login.username = val(row['Login Name']);
login.password = val(row.Password);
const uri = normalizeUri(row['Web Site'] || '');
login.uris = uri ? [{ uri, match: null }] : null;
result.ciphers.push(cipher);
}
return result;
}
export function parseLastPassCsv(textRaw: string): CiphersImportPayload {
const rows = parseCsv(textRaw);
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
@@ -350,7 +373,8 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
const cipher = makeLoginCipher();
for (const s of qd(entry, 'String')) {
const key = txt(qd(s, 'Key')[0]?.textContent);
const value = txt(qd(s, 'Value')[0]?.textContent);
const valueNode = qd(s, 'Value')[0];
const value = txt(valueNode?.textContent);
if (!value) continue;
const login = cipher.login as Record<string, unknown>;
if (key === 'Title') cipher.name = value;
@@ -361,6 +385,11 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
login.uris = uri ? [{ uri, match: null }] : null;
} else if (key === 'otp') login.totp = value.replace('key=', '');
else if (key === 'Notes') cipher.notes = `${txt(cipher.notes)}${txt(cipher.notes) ? '\n' : ''}${value}`;
else {
const hidden = ['True', 'true', '1'].includes(valueNode?.getAttribute('ProtectInMemory') || '')
|| ['True', 'true', '1'].includes(valueNode?.getAttribute('Protected') || '');
processKvp(cipher, key, value, hidden);
}
}
const idx = result.ciphers.push(cipher) - 1;
if (!isRoot && folder >= 0) result.folderRelationships.push({ key: idx, value: folder });
+2
View File
@@ -10,6 +10,7 @@ import {
parseDashlaneCsv,
parseDashlaneJson,
parseEncryptrCsv,
parseKeePassCsv,
parseKeePassXCsv,
parseKeePassXml,
parseLastPassCsv,
@@ -75,6 +76,7 @@ const IMPORT_SOURCE_PARSERS: Record<ImportSourceId, (textRaw: string) => Ciphers
lastpass: parseLastPassCsv,
dashlane_csv: parseDashlaneCsv,
dashlane_json: parseDashlaneJson,
keepass_csv: parseKeePassCsv,
keepass_xml: parseKeePassXml,
keepassx_csv: parseKeePassXCsv,
arc_csv: parseArcCsv,