@bobfrankston/rmfmail 1.2.225 → 1.2.227

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.
Files changed (42) hide show
  1. package/client/android-bootstrap.bundle.js +69 -2
  2. package/client/android-bootstrap.bundle.js.map +2 -2
  3. package/client/app.bundle.js +62 -1
  4. package/client/app.bundle.js.map +2 -2
  5. package/client/components/message-viewer.js +89 -0
  6. package/client/components/message-viewer.js.map +1 -1
  7. package/client/components/message-viewer.ts +87 -0
  8. package/package.json +5 -5
  9. package/packages/mailx-core/index.ts +3 -2
  10. package/packages/mailx-core/package.json +1 -1
  11. package/packages/mailx-imap/package-lock.json +2 -2
  12. package/packages/mailx-imap/package.json +1 -1
  13. package/packages/mailx-send/index.d.ts.map +1 -1
  14. package/packages/mailx-send/index.js +10 -5
  15. package/packages/mailx-send/index.js.map +1 -1
  16. package/packages/mailx-send/index.ts +10 -5
  17. package/packages/mailx-send/mailsend/index.ts +8 -5
  18. package/packages/mailx-send/package.json +1 -1
  19. package/packages/mailx-service/index.d.ts.map +1 -1
  20. package/packages/mailx-service/index.js +10 -7
  21. package/packages/mailx-service/index.js.map +1 -1
  22. package/packages/mailx-service/index.ts +10 -7
  23. package/packages/mailx-service/jsonrpc.d.ts.map +1 -1
  24. package/packages/mailx-service/jsonrpc.js +72 -1
  25. package/packages/mailx-service/jsonrpc.js.map +1 -1
  26. package/packages/mailx-service/jsonrpc.ts +73 -1
  27. package/packages/mailx-service/package.json +1 -1
  28. package/packages/mailx-settings/docs/TODO-prereorg-snapshot-2026-07-15.md +1130 -0
  29. package/packages/mailx-settings/package.json +1 -1
  30. package/packages/mailx-store/package.json +1 -1
  31. package/packages/mailx-store-web/package.json +1 -1
  32. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  33. package/packages/mailx-store-web/web-service.js +3 -3
  34. package/packages/mailx-store-web/web-service.js.map +1 -1
  35. package/packages/mailx-store-web/web-service.ts +3 -3
  36. package/packages/mailx-types/index.d.ts +40 -0
  37. package/packages/mailx-types/index.d.ts.map +1 -1
  38. package/packages/mailx-types/index.js +117 -0
  39. package/packages/mailx-types/index.js.map +1 -1
  40. package/packages/mailx-types/index.ts +103 -0
  41. package/packages/mailx-types/package.json +1 -1
  42. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-67672 → node_modules.npmglobalize-stash-70108}/.package-lock.json +0 -0
@@ -450,6 +450,109 @@ export function sanitizeHtml(html: string): { html: string; hasRemoteContent: bo
450
450
  return { html: clean, hasRemoteContent };
451
451
  }
452
452
 
453
+ /**
454
+ * Encode a header VALUE as RFC 2047 encoded-words when it contains non-ASCII.
455
+ *
456
+ * Why this has to exist: a header carrying raw 8-bit UTF-8 makes the whole
457
+ * message an EAI message (RFC 6530), which can only be relayed over SMTPUTF8
458
+ * (RFC 6531). Plenty of receivers still don't advertise that extension, and a
459
+ * sending MTA that can't downgrade has no choice but to bounce. mailx built
460
+ * every outgoing header with bare interpolation — `Subject: ${msg.subject}` —
461
+ * so ONE curly apostrophe was enough to make a message undeliverable:
462
+ *
463
+ * Subject: Re: … One of Canada’s Fastest Fiber Networks (U+2019, raw UTF-8)
464
+ * → <alland@soundbytesradio.com>: EAI message but server 17.57.152.5
465
+ * does not support SMTPUTF8 … This is a permanent error
466
+ *
467
+ * (Bob 2026-08-08, qmail bounce from gal.iecc.com — iCloud, Zoho and
468
+ * land-com all refused it; herot.com, gmail and bob.ma took it, so it looked
469
+ * like a partial failure.) Reply subjects inherit whatever punctuation the
470
+ * original used, and Word/paste smart quotes do the rest, so this fires
471
+ * constantly and silently.
472
+ *
473
+ * Pure-ASCII values are returned untouched — encoding them would be legal but
474
+ * needlessly unreadable in every mail client's raw-source view.
475
+ *
476
+ * Base64 rather than Q-encoding: it is uniform (no per-character escaping
477
+ * rules that differ between phrase and unstructured context) and never
478
+ * produces a token needing further escaping. Chunks are split so each
479
+ * encoded-word stays inside RFC 2047's 75-character limit, split on CHARACTER
480
+ * boundaries so a multi-byte sequence is never cut in half.
481
+ */
482
+ export function encodeHeaderWord(text: string): string {
483
+ if (!text) return "";
484
+ // eslint-disable-next-line no-control-regex
485
+ if (!/[^\x00-\x7F]/.test(text)) return text;
486
+ const encoder = new TextEncoder();
487
+ // "=?UTF-8?B?" + payload + "?=" must be ≤ 75 chars, so the base64 payload
488
+ // gets 75 - 12 = 63 chars, and base64 only splits cleanly every 4 chars →
489
+ // 60 chars of payload = 45 bytes of input per encoded-word.
490
+ const MAX_BYTES_PER_WORD = 45;
491
+ const words: number[][] = [];
492
+ let chunk: number[] = [];
493
+ for (const ch of text) { // iterate by code point
494
+ const bytes = Array.from(encoder.encode(ch));
495
+ if (chunk.length + bytes.length > MAX_BYTES_PER_WORD) {
496
+ words.push(chunk);
497
+ chunk = [];
498
+ }
499
+ chunk.push(...bytes);
500
+ }
501
+ if (chunk.length) words.push(chunk);
502
+ // Continuation lines are joined with CRLF + space: consecutive
503
+ // encoded-words separated by folding whitespace are concatenated by the
504
+ // receiver with the whitespace REMOVED (RFC 2047 §6.2), which is what we
505
+ // want — the original text had no space there.
506
+ return words
507
+ .map(b => `=?UTF-8?B?${bytesToBase64(Uint8Array.from(b))}?=`)
508
+ .join("\r\n ");
509
+ }
510
+
511
+ /** Base64 without assuming Buffer (this package is browser-capable). */
512
+ function bytesToBase64(bytes: Uint8Array): string {
513
+ let bin = "";
514
+ for (const b of bytes) bin += String.fromCharCode(b);
515
+ // btoa exists in browsers and in Node ≥16.
516
+ return btoa(bin);
517
+ }
518
+
519
+ /**
520
+ * Encode an address header value (From/To/Cc/Bcc/Reply-To), encoding only the
521
+ * DISPLAY NAME of each address. The addr-spec must stay literal: encoding it
522
+ * would corrupt the address, and a non-ASCII addr-spec is genuinely EAI and
523
+ * cannot be represented any other way.
524
+ *
525
+ * Splits on commas outside angle brackets and quotes so a display name
526
+ * containing a comma ("Frankston, Bob" <x@y>) survives.
527
+ */
528
+ export function encodeAddressHeader(value: string): string {
529
+ if (!value) return "";
530
+ // eslint-disable-next-line no-control-regex
531
+ if (!/[^\x00-\x7F]/.test(value)) return value;
532
+ const parts: string[] = [];
533
+ let cur = "";
534
+ let inAngle = false;
535
+ let inQuote = false;
536
+ for (const ch of value) {
537
+ if (ch === '"') inQuote = !inQuote;
538
+ else if (ch === "<" && !inQuote) inAngle = true;
539
+ else if (ch === ">" && !inQuote) inAngle = false;
540
+ if (ch === "," && !inAngle && !inQuote) { parts.push(cur); cur = ""; continue; }
541
+ cur += ch;
542
+ }
543
+ if (cur.trim()) parts.push(cur);
544
+ return parts.map(part => {
545
+ const p = part.trim();
546
+ const m = /^(.*?)\s*(<[^>]*>)\s*$/.exec(p);
547
+ if (!m) return p; // bare addr-spec, nothing to encode
548
+ const display = m[1].replace(/^"(.*)"$/s, "$1");
549
+ if (!display) return m[2];
550
+ // eslint-disable-next-line no-control-regex
551
+ if (!/[^\x00-\x7F]/.test(display)) return p; // ASCII name — leave the quoting alone
552
+ return `${encodeHeaderWord(display)} ${m[2]}`;
553
+ }).join(", ");
554
+ }
555
+
453
556
  /** Encode text as RFC 2045 quoted-printable. */
454
557
  export function encodeQuotedPrintable(text: string): string {
455
558
  const encoder = new TextEncoder();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-types",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",