@bobfrankston/rmfmail 1.2.217 → 1.2.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mailx.js +77 -1
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +68 -1
- package/client/prewarm.html +16 -0
- package/package.json +7 -7
- package/packages/mailx-imap/index.d.ts +17 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +91 -3
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +84 -4
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +63 -15
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +50 -14
- package/packages/mailx-service/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-36516 → node_modules.npmglobalize-stash-142156}/.package-lock.json +0 -0
|
@@ -3443,26 +3443,62 @@ export class MailxService implements MailxApi {
|
|
|
3443
3443
|
if (ext) safeName += ext;
|
|
3444
3444
|
}
|
|
3445
3445
|
const target = path.join(dir, safeName);
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
try { fs.writeFileSync(`${
|
|
3446
|
+
// Mark-of-the-Web: tag the staged file as coming from the Internet
|
|
3447
|
+
// (Zone.Identifier ADS, ZoneId 3) so Office opens it in Protected
|
|
3448
|
+
// View and SmartScreen vets executables — same treatment a browser
|
|
3449
|
+
// download gets. Email attachments are exactly the threat model
|
|
3450
|
+
// MotW exists for (Bob 2026-07-02: "the file should be flagged as
|
|
3451
|
+
// being saved from an unknown source"). Best-effort: ADS needs
|
|
3452
|
+
// NTFS; FAT/exFAT volumes throw and we still open the file.
|
|
3453
|
+
const markOfTheWeb = (p: string): void => {
|
|
3454
|
+
if (process.platform !== "win32") return;
|
|
3455
|
+
try { fs.writeFileSync(`${p}:Zone.Identifier`, "[ZoneTransfer]\r\nZoneId=3\r\n"); } catch { /* non-NTFS or locked */ }
|
|
3456
|
+
};
|
|
3457
|
+
// Staging path collides with itself when the SAME attachment is opened
|
|
3458
|
+
// twice: the viewer launched by the first open (Acrobat, Edge) holds
|
|
3459
|
+
// the file with a deny-write share, so the second writeFileSync throws
|
|
3460
|
+
// EBUSY and the user gets a red banner instead of their PDF (Bob
|
|
3461
|
+
// 2026-08-06, "278 Lake Ave Newton - Bob Frankston.pdf"). Two outs:
|
|
3462
|
+
// 1. Byte-identical file already staged → don't rewrite it at all.
|
|
3463
|
+
// This is the double-click case; just re-launch. Cheapest and it
|
|
3464
|
+
// never touches the lock.
|
|
3465
|
+
// 2. Different content but the name is locked (a DIFFERENT
|
|
3466
|
+
// attachment sharing a filename) → stage under a unique sibling
|
|
3467
|
+
// so both stay openable.
|
|
3468
|
+
let staged = target;
|
|
3469
|
+
let existing: Buffer | null = null;
|
|
3470
|
+
try { existing = fs.readFileSync(target); } catch { existing = null; }
|
|
3471
|
+
if (!existing || !existing.equals(att.content)) {
|
|
3472
|
+
try {
|
|
3473
|
+
fs.writeFileSync(target, att.content);
|
|
3474
|
+
markOfTheWeb(target);
|
|
3475
|
+
} catch (e: any) {
|
|
3476
|
+
const locked = e?.code === "EBUSY" || e?.code === "EPERM" || e?.code === "EACCES";
|
|
3477
|
+
if (!locked) throw e;
|
|
3478
|
+
const ext = path.extname(safeName);
|
|
3479
|
+
const stem = safeName.slice(0, safeName.length - ext.length);
|
|
3480
|
+
let n = 2;
|
|
3481
|
+
do { staged = path.join(dir, `${stem} (${n++})${ext}`); } while (fs.existsSync(staged) && n <= 99);
|
|
3482
|
+
if (fs.existsSync(staged)) throw e; // 99 locked variants — something is really wrong, surface it
|
|
3483
|
+
console.log(` [attachment] "${safeName}" locked by another app (${e.code}) — staging as "${path.basename(staged)}"`);
|
|
3484
|
+
fs.writeFileSync(staged, att.content);
|
|
3485
|
+
markOfTheWeb(staged);
|
|
3486
|
+
}
|
|
3487
|
+
} else {
|
|
3488
|
+
// Re-open of an already-staged copy. Re-assert MotW in case the
|
|
3489
|
+
// staged file predates the tagging (or the ADS was stripped);
|
|
3490
|
+
// no-op when it's already there.
|
|
3491
|
+
markOfTheWeb(target);
|
|
3456
3492
|
}
|
|
3457
3493
|
const { spawn } = await import("node:child_process");
|
|
3458
3494
|
if (process.platform === "win32") {
|
|
3459
|
-
spawn("cmd", ["/c", "start", "",
|
|
3495
|
+
spawn("cmd", ["/c", "start", "", staged], { detached: true, stdio: "ignore", windowsHide: true }).unref();
|
|
3460
3496
|
} else if (process.platform === "darwin") {
|
|
3461
|
-
spawn("open", [
|
|
3497
|
+
spawn("open", [staged], { detached: true, stdio: "ignore" }).unref();
|
|
3462
3498
|
} else {
|
|
3463
|
-
spawn("xdg-open", [
|
|
3499
|
+
spawn("xdg-open", [staged], { detached: true, stdio: "ignore" }).unref();
|
|
3464
3500
|
}
|
|
3465
|
-
return { ok: true, path:
|
|
3501
|
+
return { ok: true, path: staged };
|
|
3466
3502
|
}
|
|
3467
3503
|
|
|
3468
3504
|
/** Open an http/https/mailto URL in the OS default browser/handler from the
|