@bobfrankston/mailx-store 0.1.16 → 0.1.17

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 (2) hide show
  1. package/db.js +36 -0
  2. package/package.json +1 -1
package/db.js CHANGED
@@ -7,7 +7,29 @@ import { DatabaseSync } from "node:sqlite";
7
7
  import { randomUUID } from "node:crypto";
8
8
  import * as path from "node:path";
9
9
  import * as fs from "node:fs";
10
+ // libmime has no @types; declare module for the one symbol we use.
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ const _libmimeMod = await import("libmime");
10
13
  import { CONTACT_RULES } from "@bobfrankston/mailx-types";
14
+ /** RFC 2047 encoded-word decode. mailparser handles the standard cases
15
+ * (Subject, unquoted display names) but leaves encoded-words inside
16
+ * quoted-strings raw because they're RFC-strictly forbidden there —
17
+ * in practice senders do it anyway. Running libmime.decodeWords as a
18
+ * uniform pass over every header string at the storage seam means
19
+ * every code path (IMAP fetch, Gmail API, manual insert, eml replay)
20
+ * gets the same treatment. Safe on already-decoded strings (no `=?`
21
+ * markers → no-op). */
22
+ const _libmime = _libmimeMod.default || _libmimeMod;
23
+ function decodeHeaderWords(s) {
24
+ if (!s || s.indexOf("=?") < 0)
25
+ return s || "";
26
+ try {
27
+ return _libmime.decodeWords(s);
28
+ }
29
+ catch {
30
+ return s;
31
+ }
32
+ }
11
33
  /** Addresses that have no business in autocomplete. Patterns load from
12
34
  * contact-rules.jsonc in mailx-types — a single source of truth shipped
13
35
  * with every release. To change the rules edit the JSONC and rebuild. */
@@ -1191,6 +1213,20 @@ export class MailxDB {
1191
1213
  return true;
1192
1214
  }
1193
1215
  upsertMessage(msg) {
1216
+ // Uniform RFC 2047 encoded-word decode at the storage seam. Every
1217
+ // path that lands a message here (IMAP fetch, Gmail API, manual
1218
+ // insert) gets the same treatment — no per-callsite patches.
1219
+ // mailparser handles many cases but not encoded-words inside
1220
+ // quoted-strings (RFC-strictly forbidden, common in the wild). Run
1221
+ // libmime over Subject and every address display name once; safe
1222
+ // to call on already-decoded text (no `=?...?=` markers → no-op).
1223
+ msg.subject = decodeHeaderWords(msg.subject);
1224
+ if (msg.from)
1225
+ msg.from = { name: decodeHeaderWords(msg.from.name || ""), address: msg.from.address || "" };
1226
+ if (msg.to)
1227
+ msg.to = msg.to.map(a => ({ name: decodeHeaderWords(a.name || ""), address: a.address || "" }));
1228
+ if (msg.cc)
1229
+ msg.cc = msg.cc.map(a => ({ name: decodeHeaderWords(a.name || ""), address: a.address || "" }));
1194
1230
  const existing = this.db.prepare("SELECT id, provider_id FROM messages WHERE account_id = ? AND folder_id = ? AND uid = ?").get(msg.accountId, msg.folderId, msg.uid);
1195
1231
  if (existing) {
1196
1232
  // Backfill provider_id on existing rows that predate this column —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",