Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 1x 1x 3x 1x 2x 11x 11x 11x 11x 2x 2x 1x 11x 11x 11x 11x 1x 1x 1x 1x 1x 1x 11x 9x 11x 11x 11x 9x | "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildPassmanJson = buildPassmanJson;
/**
* `undefined` when the blob or reference is broken (matches importer-passmanjson.js dropping/failing that entry).
*/
function toExportJsonFile(file) {
if (file.broken || file.plaintext === undefined || file.file_id === undefined || file.guid === undefined) {
return undefined;
}
return {
file_id: file.file_id,
filename: file.filename,
guid: file.guid,
size: file.size ?? file.plaintext.length,
file_data: file.plaintext,
created: file.created ?? new Date().getTime(),
mimetype: file.mimetype ?? '',
};
}
/**
* Splits `materialized.files` back into a `files[]` array and file-type `custom_fields[].value` payloads,
* matching positions 1:1 against `decrypted.custom_fields` (both derive from the same, unreordered
* `collectFileRefs` traversal). Broken blobs are dropped rather than exported half-broken.
*/
function buildFilesAndCustomFields(materialized) {
const filesMaterialized = materialized.files.filter((file) => file.source === 'files');
const customFieldMaterialized = materialized.files.filter((file) => file.source === 'custom_field');
const files = [];
for (const materializedFile of filesMaterialized) {
const built = toExportJsonFile(materializedFile);
if (built) {
files.push(built);
}
}
const decryptedCustomFields = materialized.decrypted.custom_fields ?? [];
const custom_fields = [];
let fileFieldCursor = 0;
for (const field of decryptedCustomFields) {
Iif (field.field_type !== 'file') {
custom_fields.push(field);
continue;
}
const materializedFile = customFieldMaterialized[fileFieldCursor];
fileFieldCursor++;
const built = materializedFile ? toExportJsonFile(materializedFile) : undefined;
Eif (built) {
custom_fields.push({ ...field, value: built });
}
// drop broken file custom fields entirely, same policy as files[]
}
return { files, custom_fields };
}
/**
* Builds the classic top-level-array Passman JSON export format, matching what
* `importer-passmanjson.js` actually reads: no `vault_id` / `vault_key` / `shared_key` / `guid` / `acl`,
* files inlined as `file_data` (re-uploaded and re-encrypted by the importer on read).
*
* Known lossy, by design: the importer reads `item.otp.qrCode`, while the decrypted `otp` carries
* `qr_uri.image`. `secret` / `type` / `issuer` / `label` import fine; the QR image does not. The classic
* export has the same gap, so the decrypted shape is kept as-is rather than inventing a `qrCode` field.
*/
function buildPassmanJson(materialized) {
const entries = materialized.map((credential) => {
const { files, custom_fields } = buildFilesAndCustomFields(credential);
const decrypted = credential.decrypted;
return {
user_id: credential.user_id,
vault_id: credential.vault_id,
credential_id: credential.credential_id,
label: credential.label,
username: decrypted.username ?? null,
password: decrypted.password ?? null,
email: decrypted.email ?? null,
url: decrypted.url ?? null,
description: decrypted.description ?? null,
tags: decrypted.tags ?? null,
custom_fields,
otp: decrypted.otp ?? {},
compromised: decrypted.compromised ?? null,
files,
created: decrypted.created ?? null,
changed: decrypted.changed ?? null,
expire_time: decrypted.expire_time ?? null,
delete_time: decrypted.delete_time ?? null,
renew_interval: decrypted.renew_interval ?? null,
hidden: decrypted.hidden ?? false,
icon: decrypted.icon ?? null,
};
});
return JSON.stringify(entries);
}
|