proton-mail-bridge-client 2.3.2 → 2.4.1
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/README.md +8 -2
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +348 -202
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +251 -133
- package/dist/index.js.map +1 -1
- package/dist/scripts/check-claude-desktop.d.ts +18 -0
- package/dist/scripts/check-claude-desktop.d.ts.map +1 -1
- package/dist/scripts/check-claude-desktop.js +91 -30
- package/dist/scripts/check-claude-desktop.js.map +1 -1
- package/dist/scripts/install-claude-desktop.d.ts +12 -0
- package/dist/scripts/install-claude-desktop.d.ts.map +1 -1
- package/dist/scripts/install-claude-desktop.js +211 -43
- package/dist/scripts/install-claude-desktop.js.map +1 -1
- package/dist/services/analytics-service.d.ts +1 -1
- package/dist/services/analytics-service.d.ts.map +1 -1
- package/dist/services/analytics-service.js +2 -2
- package/dist/services/analytics-service.js.map +1 -1
- package/dist/services/audit-service.d.ts.map +1 -1
- package/dist/services/audit-service.js +7 -2
- package/dist/services/audit-service.js.map +1 -1
- package/dist/services/background-sync-service.d.ts.map +1 -1
- package/dist/services/background-sync-service.js +10 -2
- package/dist/services/background-sync-service.js.map +1 -1
- package/dist/services/delivery-queue-service.d.ts.map +1 -1
- package/dist/services/delivery-queue-service.js +3 -8
- package/dist/services/delivery-queue-service.js.map +1 -1
- package/dist/services/draft-store-service.d.ts.map +1 -1
- package/dist/services/draft-store-service.js +3 -4
- package/dist/services/draft-store-service.js.map +1 -1
- package/dist/services/local-index-service.d.ts +2 -1
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +45 -9
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +21 -2
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +271 -103
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/smtp-service.d.ts.map +1 -1
- package/dist/services/smtp-service.js +15 -0
- package/dist/services/smtp-service.js.map +1 -1
- package/dist/services/snooze-service.d.ts.map +1 -1
- package/dist/services/snooze-service.js +40 -20
- package/dist/services/snooze-service.js.map +1 -1
- package/dist/services/template-service.d.ts +4 -2
- package/dist/services/template-service.d.ts.map +1 -1
- package/dist/services/template-service.js +43 -10
- package/dist/services/template-service.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts +2 -0
- package/dist/utils/atomic-write.d.ts.map +1 -0
- package/dist/utils/atomic-write.js +36 -0
- package/dist/utils/atomic-write.js.map +1 -0
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +39 -4
- package/dist/utils/file-lock.js.map +1 -1
- package/dist/utils/helpers.d.ts +14 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +222 -21
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +29 -12
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
package/dist/utils/file-lock.js
CHANGED
|
@@ -23,6 +23,39 @@ const LOCK_ACQUIRE_TIMEOUT_MS = 10_000;
|
|
|
23
23
|
// instead of blocking forever. Generous relative to how briefly these
|
|
24
24
|
// load-modify-save cycles actually take.
|
|
25
25
|
const STALE_LOCK_MS = 30_000;
|
|
26
|
+
// Taking over a stale lock is itself a critical section. Two waiters can both judge the same lock stale; if
|
|
27
|
+
// each then removes "the" lock and creates its own, the second removes the first's brand-new lock and both
|
|
28
|
+
// end up inside the protected section. So stealing is serialized behind a short-lived guard file, and the
|
|
29
|
+
// staleness verdict is taken again while holding it: a waiter that arrives second sees the first stealer's
|
|
30
|
+
// fresh lock (live PID) and backs off.
|
|
31
|
+
const STEAL_GUARD_STALE_MS = 5_000;
|
|
32
|
+
async function stealStale(lockPath) {
|
|
33
|
+
const guard = `${lockPath}.steal`;
|
|
34
|
+
let handle;
|
|
35
|
+
try {
|
|
36
|
+
handle = await open(guard, "wx");
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== "EEXIST")
|
|
40
|
+
throw error;
|
|
41
|
+
// Someone else is stealing right now. A guard left behind by a process that died mid-steal is removed
|
|
42
|
+
// once it is old; either way this waiter just loops and looks again.
|
|
43
|
+
const info = await stat(guard).catch(() => undefined);
|
|
44
|
+
if (info && Date.now() - info.mtimeMs > STEAL_GUARD_STALE_MS)
|
|
45
|
+
await unlink(guard).catch(() => undefined);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await handle.close();
|
|
50
|
+
if (await isStale(lockPath)) {
|
|
51
|
+
logger.warn(`Stale lock file detected, stealing it: ${lockPath}`, "FileLock");
|
|
52
|
+
await unlink(lockPath).catch(() => undefined);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await unlink(guard).catch(() => undefined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
26
59
|
function lockPathFor(storePath) {
|
|
27
60
|
return `${storePath}.lock`;
|
|
28
61
|
}
|
|
@@ -123,12 +156,14 @@ async function acquire(lockPath) {
|
|
|
123
156
|
}
|
|
124
157
|
catch (error) {
|
|
125
158
|
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
126
|
-
|
|
159
|
+
// Windows refuses to open a lock file that another process has just deleted (delete pending) with
|
|
160
|
+
// EPERM/EACCES/EBUSY instead of letting the create succeed; that is contention, not a real failure.
|
|
161
|
+
const contendedOnWindows = process.platform === "win32" && (code === "EPERM" || code === "EACCES" || code === "EBUSY");
|
|
162
|
+
if (code !== "EEXIST" && !contendedOnWindows) {
|
|
127
163
|
throw error;
|
|
128
164
|
}
|
|
129
|
-
if (await isStale(lockPath)) {
|
|
130
|
-
|
|
131
|
-
await unlink(lockPath).catch(() => undefined);
|
|
165
|
+
if (!contendedOnWindows && (await isStale(lockPath))) {
|
|
166
|
+
await stealStale(lockPath);
|
|
132
167
|
continue;
|
|
133
168
|
}
|
|
134
169
|
if (Date.now() > deadline) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -31,7 +31,13 @@ export declare function extractMessageIdList(value?: string | string[]): string[
|
|
|
31
31
|
export declare function extractAttachments(structure?: MessageStructureObject): EmailAttachmentSummary[];
|
|
32
32
|
export declare function dedupeEmails(emails: EmailSummary[]): EmailSummary[];
|
|
33
33
|
export declare function sortEmailsByNewest<T extends Pick<EmailSummary, "date" | "internalDate" | "uid">>(emails: T[]): T[];
|
|
34
|
+
export declare class InvalidArgumentError extends Error {
|
|
35
|
+
constructor(message: string);
|
|
36
|
+
}
|
|
34
37
|
export declare function normalizeLimit(value: unknown, defaultValue: number, min?: number, max?: number): number;
|
|
38
|
+
export declare function optionalInteger(value: unknown, min: number, max: number): number | undefined;
|
|
39
|
+
export declare function optionalNumber(value: unknown, min: number, max: number): number | undefined;
|
|
40
|
+
export declare function optionalBoolean(value: unknown): boolean | undefined;
|
|
35
41
|
export declare function normalizeBoolean(value: unknown, defaultValue: boolean): boolean;
|
|
36
42
|
export declare function parseDateInput(value?: string): Date | undefined;
|
|
37
43
|
export declare function nextDay(date: Date): Date;
|
|
@@ -54,10 +60,18 @@ export declare function stringifyForJson(value: unknown): string;
|
|
|
54
60
|
export declare function normalizeJsonValue(value: unknown): unknown;
|
|
55
61
|
export declare function lowerCaseAddress(value?: string): string | undefined;
|
|
56
62
|
export declare function isSelfAddress(candidate: string | undefined, ownerEmail: string | undefined): boolean;
|
|
63
|
+
export declare function isOutgoingMessage(message: {
|
|
64
|
+
from: Array<{
|
|
65
|
+
address?: string;
|
|
66
|
+
}>;
|
|
67
|
+
folder?: string;
|
|
68
|
+
}, ownerEmail?: string): boolean;
|
|
57
69
|
export declare function normalizeMessageId(value?: string): string | undefined;
|
|
58
70
|
export declare function extractDomain(address: string): string | undefined;
|
|
59
71
|
export declare function normalizeSubjectForThread(subject: string): string;
|
|
60
72
|
export declare function sanitizeFileName(filename?: string, fallback?: string): string;
|
|
73
|
+
export declare function isPathInside(parent: string, child: string, separator?: string): boolean;
|
|
74
|
+
export declare function decodeAttachmentText(content: Buffer, declaredCharset?: string): string | undefined;
|
|
61
75
|
export declare function isTextLikeMimeType(mimeType?: string): boolean;
|
|
62
76
|
export declare function classifyAttachment(input: {
|
|
63
77
|
filename?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { sep as pathSeparator } from "node:path";
|
|
2
3
|
import TurndownService from "turndown";
|
|
3
4
|
const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
4
5
|
const EMAIL_ID_SEPARATOR = "::";
|
|
@@ -435,11 +436,34 @@ export function sortEmailsByNewest(emails) {
|
|
|
435
436
|
return right.uid - left.uid;
|
|
436
437
|
});
|
|
437
438
|
}
|
|
439
|
+
// A tool argument that could not be read as what it should be. The server maps it to InvalidParams, so
|
|
440
|
+
// the caller is told, instead of the argument being ignored and a default used.
|
|
441
|
+
export class InvalidArgumentError extends Error {
|
|
442
|
+
constructor(message) {
|
|
443
|
+
super(message);
|
|
444
|
+
this.name = "InvalidArgumentError";
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Reads an integer argument: a number, or a string that holds one ("5"), clamped to [min, max]. Nothing given
|
|
448
|
+
// (undefined, null, "") uses the default. Anything else is an error rather than a silent default.
|
|
438
449
|
export function normalizeLimit(value, defaultValue, min = 1, max = 250) {
|
|
439
|
-
if (
|
|
450
|
+
if (value === undefined || value === null || value === "") {
|
|
440
451
|
return defaultValue;
|
|
441
452
|
}
|
|
442
|
-
|
|
453
|
+
let number;
|
|
454
|
+
if (typeof value === "number") {
|
|
455
|
+
number = value;
|
|
456
|
+
}
|
|
457
|
+
else if (typeof value === "string" && /^\s*-?\d+(\.\d+)?\s*$/.test(value)) {
|
|
458
|
+
number = Number(value);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
throw new InvalidArgumentError(`Expected a number but got ${JSON.stringify(value)}.`);
|
|
462
|
+
}
|
|
463
|
+
if (Number.isNaN(number)) {
|
|
464
|
+
throw new InvalidArgumentError("Expected a number but got NaN.");
|
|
465
|
+
}
|
|
466
|
+
const rounded = Math.trunc(number);
|
|
443
467
|
if (rounded < min) {
|
|
444
468
|
return min;
|
|
445
469
|
}
|
|
@@ -448,8 +472,58 @@ export function normalizeLimit(value, defaultValue, min = 1, max = 250) {
|
|
|
448
472
|
}
|
|
449
473
|
return rounded;
|
|
450
474
|
}
|
|
475
|
+
// Like normalizeLimit, but "nothing given" stays undefined, for arguments whose default lives in the service.
|
|
476
|
+
export function optionalInteger(value, min, max) {
|
|
477
|
+
return value === undefined || value === null || value === "" ? undefined : normalizeLimit(value, min, min, max);
|
|
478
|
+
}
|
|
479
|
+
// A number argument that may be absent (a filter such as sizeLarger): absent stays undefined, "1.5" and 1.5
|
|
480
|
+
// both read as 1.5, anything unreadable is an error, and the value is kept inside [min, max].
|
|
481
|
+
export function optionalNumber(value, min, max) {
|
|
482
|
+
if (value === undefined || value === null || value === "")
|
|
483
|
+
return undefined;
|
|
484
|
+
let number;
|
|
485
|
+
if (typeof value === "number") {
|
|
486
|
+
number = value;
|
|
487
|
+
}
|
|
488
|
+
else if (typeof value === "string" && /^\s*-?\d+(\.\d+)?\s*$/.test(value)) {
|
|
489
|
+
number = Number(value);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
throw new InvalidArgumentError(`Expected a number but got ${JSON.stringify(value)}.`);
|
|
493
|
+
}
|
|
494
|
+
if (Number.isNaN(number))
|
|
495
|
+
throw new InvalidArgumentError("Expected a number but got NaN.");
|
|
496
|
+
return Math.min(max, Math.max(min, number));
|
|
497
|
+
}
|
|
498
|
+
const TRUE_WORDS = new Set(["true", "yes", "1", "on"]);
|
|
499
|
+
const FALSE_WORDS = new Set(["false", "no", "0", "off"]);
|
|
500
|
+
// Reads a boolean argument. Clients and models do send "true" and 1; reading only a real boolean made
|
|
501
|
+
// dryRun:"true" run for real and isRead:"false" mark a message read. Nothing given uses the default; a value
|
|
502
|
+
// that is none of the recognised spellings is an error, never a silent default.
|
|
503
|
+
export function optionalBoolean(value) {
|
|
504
|
+
return value === undefined || value === null || value === "" ? undefined : normalizeBoolean(value, false);
|
|
505
|
+
}
|
|
451
506
|
export function normalizeBoolean(value, defaultValue) {
|
|
452
|
-
|
|
507
|
+
if (typeof value === "boolean")
|
|
508
|
+
return value;
|
|
509
|
+
if (value === undefined || value === null)
|
|
510
|
+
return defaultValue;
|
|
511
|
+
if (typeof value === "number") {
|
|
512
|
+
if (value === 1)
|
|
513
|
+
return true;
|
|
514
|
+
if (value === 0)
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
else if (typeof value === "string") {
|
|
518
|
+
const word = value.trim().toLowerCase();
|
|
519
|
+
if (word === "")
|
|
520
|
+
return defaultValue;
|
|
521
|
+
if (TRUE_WORDS.has(word))
|
|
522
|
+
return true;
|
|
523
|
+
if (FALSE_WORDS.has(word))
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
throw new InvalidArgumentError(`Expected true or false but got ${JSON.stringify(value)}.`);
|
|
453
527
|
}
|
|
454
528
|
export function parseDateInput(value) {
|
|
455
529
|
if (!value) {
|
|
@@ -474,8 +548,13 @@ const FOLD_EXTRA = { ß: "ss", æ: "ae", œ: "oe", ø: "o", ł: "l", đ: "d", ð
|
|
|
474
548
|
export function hasNonAscii(value) {
|
|
475
549
|
return value !== undefined && /[^\x00-\x7f]/.test(value);
|
|
476
550
|
}
|
|
551
|
+
const NON_ASCII = /[^\x00-\x7f]/;
|
|
477
552
|
// Lowercase, strip combining accents, and spell out the letters Unicode does not decompose.
|
|
478
553
|
export function foldSearchText(value) {
|
|
554
|
+
// Plain ASCII (most of the text the index compares) folds to just its lower case: skip the Unicode
|
|
555
|
+
// normalisation, which is most of the cost when this runs for every row of a large mailbox.
|
|
556
|
+
if (!NON_ASCII.test(value))
|
|
557
|
+
return value.toLowerCase();
|
|
479
558
|
return value
|
|
480
559
|
.normalize("NFKD")
|
|
481
560
|
.replace(/[\u0300-\u036f]/g, "")
|
|
@@ -487,6 +566,8 @@ export function foldSearchText(value) {
|
|
|
487
566
|
const SPELLED_OUT = { ä: "ae", ö: "oe", ü: "ue", ø: "oe", å: "aa" };
|
|
488
567
|
// The value with those letters spelled out as pairs, or undefined when it has none.
|
|
489
568
|
function spelledOutText(value) {
|
|
569
|
+
if (!NON_ASCII.test(value))
|
|
570
|
+
return undefined;
|
|
490
571
|
const lower = value.normalize("NFC").toLowerCase();
|
|
491
572
|
if (!/[äöüøå]/.test(lower))
|
|
492
573
|
return undefined;
|
|
@@ -698,6 +779,16 @@ export function isSelfAddress(candidate, ownerEmail) {
|
|
|
698
779
|
}
|
|
699
780
|
return stripPlusTag(a) === stripPlusTag(b);
|
|
700
781
|
}
|
|
782
|
+
// Whether a message was written by the mailbox owner. The login address is compared the way every other
|
|
783
|
+
// "is this me" check here does (case-insensitive, a +tag ignored), and anything in the Sent folder counts too: a
|
|
784
|
+
// reply sent from an alias (alias@pm.me) is the owner's, but its address is not the login address.
|
|
785
|
+
export function isOutgoingMessage(message, ownerEmail) {
|
|
786
|
+
if (normalizeMailboxLabel(message.folder) === "Sent")
|
|
787
|
+
return true;
|
|
788
|
+
if (!ownerEmail)
|
|
789
|
+
return false;
|
|
790
|
+
return message.from.some((entry) => isSelfAddress(entry.address, ownerEmail));
|
|
791
|
+
}
|
|
701
792
|
export function normalizeMessageId(value) {
|
|
702
793
|
const trimmed = value?.trim();
|
|
703
794
|
if (!trimmed) {
|
|
@@ -716,10 +807,87 @@ export function normalizeSubjectForThread(subject) {
|
|
|
716
807
|
}
|
|
717
808
|
return collapsed.replace(/^(?:(?:re|fw|fwd)\s*:\s*)+/i, "").trim() || "(no subject)";
|
|
718
809
|
}
|
|
810
|
+
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
811
|
+
// Well under the 255-byte limit of common filesystems, leaving room for a " (n)" suffix on a collision.
|
|
812
|
+
const MAX_FILE_NAME_BYTES = 200;
|
|
813
|
+
function cleanFileName(value) {
|
|
814
|
+
return value
|
|
815
|
+
.normalize("NFC")
|
|
816
|
+
.replace(/[\u0000-\u001f\u007f]/g, "")
|
|
817
|
+
.replace(/[\\/:*?"<>|]+/g, "_")
|
|
818
|
+
.replace(/\.\./g, "_")
|
|
819
|
+
.trim()
|
|
820
|
+
.replace(/[. ]+$/g, "");
|
|
821
|
+
}
|
|
822
|
+
// A file name taken from untrusted mail made safe to create on any platform: no path separators or control
|
|
823
|
+
// characters, no trailing dots or spaces (Windows drops them, so two names could collapse into one), no
|
|
824
|
+
// Windows device names (CON, NUL, COM1...), never "." or "..", and short enough for the filesystem.
|
|
719
825
|
export function sanitizeFileName(filename, fallback = "attachment") {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
826
|
+
// "." and ".." (and a name of only dots and spaces) are directory references, not file names.
|
|
827
|
+
let name = /^[\s.]*$/.test(filename ?? "") ? "" : cleanFileName(filename ?? "");
|
|
828
|
+
if (!name) {
|
|
829
|
+
name = cleanFileName(fallback) || "attachment";
|
|
830
|
+
}
|
|
831
|
+
if (WINDOWS_RESERVED_NAME.test(name.split(".")[0])) {
|
|
832
|
+
name = `_${name}`;
|
|
833
|
+
}
|
|
834
|
+
if (Buffer.byteLength(name) > MAX_FILE_NAME_BYTES) {
|
|
835
|
+
const dot = name.lastIndexOf(".");
|
|
836
|
+
const extension = dot > 0 && Buffer.byteLength(name.slice(dot)) <= 16 ? name.slice(dot) : "";
|
|
837
|
+
let stem = Array.from(extension ? name.slice(0, dot) : name);
|
|
838
|
+
while (stem.length > 1 && Buffer.byteLength(stem.join("")) + Buffer.byteLength(extension) > MAX_FILE_NAME_BYTES) {
|
|
839
|
+
stem = stem.slice(0, -1);
|
|
840
|
+
}
|
|
841
|
+
name = `${stem.join("")}${extension}`;
|
|
842
|
+
}
|
|
843
|
+
return name;
|
|
844
|
+
}
|
|
845
|
+
// Whether `child` is `parent` or lies below it. Appending the separator to a parent that already ends in one
|
|
846
|
+
// (a filesystem root: "/" or "C:\\") would give "//" and refuse every path.
|
|
847
|
+
export function isPathInside(parent, child, separator = pathSeparator) {
|
|
848
|
+
if (child === parent)
|
|
849
|
+
return true;
|
|
850
|
+
return child.startsWith(parent.endsWith(separator) ? parent : parent + separator);
|
|
851
|
+
}
|
|
852
|
+
// Decodes the bytes of a text attachment. The charset the sender declared is honoured (windows-1250,
|
|
853
|
+
// iso-8859-2... are common in mail from Poland and Czechia), a byte order mark is read and removed, and data
|
|
854
|
+
// that is really binary (NUL bytes, or mostly bytes that are not valid text) is not returned as text.
|
|
855
|
+
export function decodeAttachmentText(content, declaredCharset) {
|
|
856
|
+
if (content.length === 0)
|
|
857
|
+
return undefined;
|
|
858
|
+
let label = (declaredCharset ?? "").trim().toLowerCase().replace(/^["']|["']$/g, "");
|
|
859
|
+
let bytes = content;
|
|
860
|
+
if (content.length >= 3 && content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf) {
|
|
861
|
+
label = "utf-8";
|
|
862
|
+
bytes = content.subarray(3);
|
|
863
|
+
}
|
|
864
|
+
else if (content.length >= 2 && content[0] === 0xff && content[1] === 0xfe) {
|
|
865
|
+
label = "utf-16le";
|
|
866
|
+
bytes = content.subarray(2);
|
|
867
|
+
}
|
|
868
|
+
else if (content.length >= 2 && content[0] === 0xfe && content[1] === 0xff) {
|
|
869
|
+
label = "utf-16be";
|
|
870
|
+
bytes = content.subarray(2);
|
|
871
|
+
}
|
|
872
|
+
else if (bytes.subarray(0, 8192).includes(0)) {
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
let decoder;
|
|
876
|
+
try {
|
|
877
|
+
decoder = new TextDecoder(label || "utf-8");
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
decoder = new TextDecoder("utf-8");
|
|
881
|
+
}
|
|
882
|
+
const text = decoder.decode(bytes);
|
|
883
|
+
let replacements = 0;
|
|
884
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
885
|
+
if (text.charCodeAt(index) === 0xfffd)
|
|
886
|
+
replacements += 1;
|
|
887
|
+
}
|
|
888
|
+
if (text.length > 0 && replacements / text.length > 0.1)
|
|
889
|
+
return undefined;
|
|
890
|
+
return text;
|
|
723
891
|
}
|
|
724
892
|
export function isTextLikeMimeType(mimeType) {
|
|
725
893
|
if (!mimeType) {
|
|
@@ -776,21 +944,49 @@ export function classifyAttachment(input) {
|
|
|
776
944
|
}
|
|
777
945
|
return { kind: "other", isCalendarInvite: false, isSignature: false };
|
|
778
946
|
}
|
|
947
|
+
// Splits "NAME;PARAM=x:value" at the first colon that is not inside a quoted parameter value
|
|
948
|
+
// (ORGANIZER;CN="Doe: John":mailto:j@x.com), returning the upper-case name and the value.
|
|
949
|
+
function splitIcsProperty(line) {
|
|
950
|
+
let quoted = false;
|
|
951
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
952
|
+
const char = line[index];
|
|
953
|
+
if (char === '"') {
|
|
954
|
+
quoted = !quoted;
|
|
955
|
+
}
|
|
956
|
+
else if (char === ":" && !quoted) {
|
|
957
|
+
if (index === 0)
|
|
958
|
+
return undefined;
|
|
959
|
+
return { name: line.slice(0, index).split(";")[0].trim().toUpperCase(), value: line.slice(index + 1).trim() };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return undefined;
|
|
963
|
+
}
|
|
964
|
+
// RFC 5545 text escapes: \n is a line break, \, \; \\ are the literal character.
|
|
965
|
+
function unescapeIcsText(value) {
|
|
966
|
+
return value.replace(/\\([nN,;\\])/g, (_match, char) => (char === "n" || char === "N" ? " " : char));
|
|
967
|
+
}
|
|
968
|
+
const ICS_TEXT_FIELDS = new Set(["SUMMARY", "LOCATION", "DESCRIPTION"]);
|
|
969
|
+
// Components whose own properties (e.g. the DTSTART of a daylight-saving rule) are not the event's.
|
|
970
|
+
const ICS_NON_EVENT_COMPONENTS = new Set(["VTIMEZONE", "DAYLIGHT", "STANDARD", "VALARM"]);
|
|
779
971
|
export function summarizeCalendarText(value) {
|
|
780
|
-
const normalized = value.replace(/\r
|
|
972
|
+
const normalized = value.replace(/\r\n?/g, "\n");
|
|
973
|
+
// A line that starts with a space or tab continues the previous one (RFC 5545 line folding).
|
|
974
|
+
const lines = normalized.replace(/\n[ \t]/g, "").split("\n");
|
|
781
975
|
const fields = new Map();
|
|
782
|
-
const lines = normalized.split("\n");
|
|
783
|
-
// Only read the first VEVENT's own properties when one exists: a VTIMEZONE ahead of it
|
|
784
|
-
// carries its own DTSTART (e.g. 19700329T020000 for a DST rule), and a nested VALARM
|
|
785
|
-
// carries its own SUMMARY/DESCRIPTION.
|
|
786
|
-
const hasEvent = lines.some((line) => line.trim().toUpperCase() === "BEGIN:VEVENT");
|
|
787
976
|
const componentStack = [];
|
|
977
|
+
let method;
|
|
978
|
+
let eventCount = 0;
|
|
788
979
|
let seenEvent = false;
|
|
980
|
+
// Only the first VEVENT's own properties are read when there is one: a VTIMEZONE ahead of it carries
|
|
981
|
+
// its own DTSTART (e.g. 19700329T020000 for a DST rule) and a nested VALARM its own SUMMARY.
|
|
982
|
+
const hasEvent = lines.some((line) => line.trim().toUpperCase() === "BEGIN:VEVENT");
|
|
789
983
|
for (const line of lines) {
|
|
790
984
|
const marker = /^(BEGIN|END):(\S+)\s*$/i.exec(line.trim());
|
|
791
985
|
if (marker) {
|
|
792
986
|
const component = marker[2].toUpperCase();
|
|
793
987
|
if (marker[1].toUpperCase() === "BEGIN") {
|
|
988
|
+
if (component === "VEVENT")
|
|
989
|
+
eventCount += 1;
|
|
794
990
|
componentStack.push(component);
|
|
795
991
|
}
|
|
796
992
|
else {
|
|
@@ -801,25 +997,30 @@ export function summarizeCalendarText(value) {
|
|
|
801
997
|
}
|
|
802
998
|
continue;
|
|
803
999
|
}
|
|
804
|
-
|
|
1000
|
+
const property = splitIcsProperty(line);
|
|
1001
|
+
if (!property)
|
|
805
1002
|
continue;
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1003
|
+
const top = componentStack[componentStack.length - 1];
|
|
1004
|
+
if (top === "VCALENDAR" && property.name === "METHOD") {
|
|
1005
|
+
method = property.value.toUpperCase();
|
|
809
1006
|
continue;
|
|
810
1007
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (
|
|
814
|
-
|
|
1008
|
+
if (componentStack.some((component) => ICS_NON_EVENT_COMPONENTS.has(component)))
|
|
1009
|
+
continue;
|
|
1010
|
+
if (hasEvent && (seenEvent || top !== "VEVENT"))
|
|
1011
|
+
continue;
|
|
1012
|
+
if (property.value && !fields.has(property.name)) {
|
|
1013
|
+
fields.set(property.name, ICS_TEXT_FIELDS.has(property.name) ? unescapeIcsText(property.value) : property.value);
|
|
815
1014
|
}
|
|
816
1015
|
}
|
|
1016
|
+
const cancelled = method === "CANCEL" || fields.get("STATUS")?.toUpperCase() === "CANCELLED";
|
|
817
1017
|
const summary = [
|
|
818
|
-
fields.get("SUMMARY"),
|
|
1018
|
+
fields.get("SUMMARY") ? `${cancelled ? "[Cancelled] " : ""}${fields.get("SUMMARY")}` : cancelled ? "[Cancelled]" : undefined,
|
|
819
1019
|
fields.get("ORGANIZER"),
|
|
820
1020
|
fields.get("DTSTART") ? `Starts ${fields.get("DTSTART")}` : undefined,
|
|
821
1021
|
fields.get("DTEND") ? `Ends ${fields.get("DTEND")}` : undefined,
|
|
822
1022
|
fields.get("LOCATION") ? `Location ${fields.get("LOCATION")}` : undefined,
|
|
1023
|
+
eventCount > 1 ? `+${eventCount - 1} more event${eventCount === 2 ? "" : "s"}` : undefined,
|
|
823
1024
|
]
|
|
824
1025
|
.filter(Boolean)
|
|
825
1026
|
.join(" | ");
|