@agentcomms/resend 0.26.0 → 0.26.2
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/THIRD_PARTY_LICENSES +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/{coerce-CqGigABD.mjs → coerce-DUN95oU4.mjs} +3 -3
- package/dist/{dist-C7DG2LdL-l0mj9_dH.mjs → dist-C7DG2LdL-6QTVBgXp.mjs} +1 -1
- package/dist/index.d.mts +984 -3041
- package/dist/index.mjs +2 -14
- package/dist/{iso-USuVU_sk.mjs → iso-5-9R47LC.mjs} +59 -26
- package/dist/{mcp-DIH4cS6P-CIWWZQbz.mjs → mcp-DIH4cS6P-C1s6ZIjc.mjs} +2 -2
- package/dist/{send-1tzjQOn0.mjs → send-x-cNN5xl.mjs} +609 -236
- package/dist/{server-D-DV-Uif.mjs → server-CKV-BqE0.mjs} +4 -4
- package/dist/{src-WCy6ifGf-DXrNj-15-D0oRtifb.mjs → src-WCy6ifGf-DXrNj-15-HcB9fGcS.mjs} +2 -2
- package/dist/{stdio-B8UXEGiG.mjs → stdio-BWbiQh9h.mjs} +1 -1
- package/dist/{stdio-CyjIgrch-59zcjSEt.mjs → stdio-CRbkOCqe-BpzkpnG6.mjs} +15 -15
- package/dist/{stdio-entry--Q0UoMIV.mjs → stdio-entry-CuXihVw3.mjs} +1 -1
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as unknown, S as union, b as strictObject, c as boolean, g as object, h as number, i as _enum, l as discriminatedUnion, m as looseObject, n as datetime, p as literal$1, s as array, x as string, y as record } from "./iso-
|
|
1
|
+
import { C as unknown, S as union, b as strictObject, c as boolean, g as object, h as number, i as _enum, l as discriminatedUnion, m as looseObject, n as datetime, p as literal$1, s as array, x as string, y as record } from "./iso-5-9R47LC.mjs";
|
|
2
2
|
import { access, chmod, constants, link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, unlink, utimes } from "node:fs/promises";
|
|
3
3
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
4
|
import { domainToASCII, fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -13,7 +13,7 @@ import { gunzipSync } from "node:zlib";
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/version.ts
|
|
15
15
|
/** The package version, read from package.json at build time. */
|
|
16
|
-
const VERSION$2 = "0.26.
|
|
16
|
+
const VERSION$2 = "0.26.2";
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/api/routes.ts
|
|
19
19
|
/**
|
|
@@ -202,7 +202,7 @@ const RESEND_CALLER = Object.freeze({
|
|
|
202
202
|
packageName: PACKAGE_NAME
|
|
203
203
|
});
|
|
204
204
|
//#endregion
|
|
205
|
-
//#region ../core/dist/update-check-
|
|
205
|
+
//#region ../core/dist/update-check-5wgoI9Yt.mjs
|
|
206
206
|
/**
|
|
207
207
|
* Every failure a user or an agent can act on carries a specific, stable code from this registry, a message, an
|
|
208
208
|
* optional one-line hint and structured `details`. The code decides the process exit status; agents and skills branch
|
|
@@ -5581,8 +5581,8 @@ async function mergeIntoJsonConfig(path, client, name, entry) {
|
|
|
5581
5581
|
}
|
|
5582
5582
|
/** Starts the server exactly as a client would, and completes the handshake. */
|
|
5583
5583
|
async function verifyEntry(entry, product) {
|
|
5584
|
-
const { Client } = await import("./dist-C7DG2LdL-
|
|
5585
|
-
const { StdioClientTransport } = await import("./stdio-
|
|
5584
|
+
const { Client } = await import("./dist-C7DG2LdL-6QTVBgXp.mjs");
|
|
5585
|
+
const { StdioClientTransport } = await import("./stdio-CRbkOCqe-BpzkpnG6.mjs");
|
|
5586
5586
|
const client = new Client({
|
|
5587
5587
|
name: `${product.binary}-install-check`,
|
|
5588
5588
|
version: product.version
|
|
@@ -12996,7 +12996,7 @@ function folding(platform) {
|
|
|
12996
12996
|
return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
|
|
12997
12997
|
}
|
|
12998
12998
|
/** The package version, read from package.json at build time. */
|
|
12999
|
-
const VERSION = "0.26.
|
|
12999
|
+
const VERSION = "0.26.2";
|
|
13000
13000
|
/**
|
|
13001
13001
|
* The command a person is told to run for one of this suite's own CLIs (design 2026-10-04, D1 and D5; CUE-403).
|
|
13002
13002
|
*
|
|
@@ -17191,7 +17191,7 @@ function serverPruneChange(core, env, request, own) {
|
|
|
17191
17191
|
};
|
|
17192
17192
|
const { packageName, version } = ownProduct(request.channel, own) ?? {
|
|
17193
17193
|
packageName: serverFacts(request.channel).packageName,
|
|
17194
|
-
version: "0.26.
|
|
17194
|
+
version: "0.26.2"
|
|
17195
17195
|
};
|
|
17196
17196
|
const product = {
|
|
17197
17197
|
packageName,
|
|
@@ -19339,7 +19339,7 @@ function terminalUpdateHooks(core, env, options = {}) {
|
|
|
19339
19339
|
} };
|
|
19340
19340
|
}
|
|
19341
19341
|
//#endregion
|
|
19342
|
-
//#region ../core/dist/tool-arguments-
|
|
19342
|
+
//#region ../core/dist/tool-arguments-C78lgSoS.mjs
|
|
19343
19343
|
const APPLIED = Symbol.for("agentcomms.strictToolArguments");
|
|
19344
19344
|
const TARGET = "draft-2020-12";
|
|
19345
19345
|
/**
|
|
@@ -19728,7 +19728,7 @@ function spoken(words, joiner) {
|
|
|
19728
19728
|
return `${words.slice(0, -1).join(", ")} ${joiner} ${words.at(-1)}`;
|
|
19729
19729
|
}
|
|
19730
19730
|
//#endregion
|
|
19731
|
-
//#region ../../node_modules/.pnpm/postal-mime@4.
|
|
19731
|
+
//#region ../../node_modules/.pnpm/postal-mime@4.1.4/node_modules/postal-mime/dist/esm/decode-strings.js
|
|
19732
19732
|
const textEncoder = new TextEncoder();
|
|
19733
19733
|
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19734
19734
|
const base64Lookup = /* @__PURE__ */ new Uint8Array(256);
|
|
@@ -19827,20 +19827,51 @@ const ENCODING_LABELS = new Set([
|
|
|
19827
19827
|
"cseuckr csksc56011987 euc-kr iso-ir-149 korean ks_c_5601-1987 ks_c_5601-1989 ksc5601 ksc_5601",
|
|
19828
19828
|
"windows-949",
|
|
19829
19829
|
"csiso2022kr hz-gb-2312 iso-2022-cn iso-2022-cn-ext iso-2022-kr replacement",
|
|
19830
|
-
"unicodefffe utf-16be",
|
|
19831
|
-
"csunicode iso-10646-ucs-2 ucs-2 unicode unicodefeff utf-16 utf-16le",
|
|
19832
19830
|
"x-user-defined"
|
|
19833
19831
|
].join(" ").split(" "));
|
|
19832
|
+
const REPLACEMENT_LABELS = /* @__PURE__ */ new Set([
|
|
19833
|
+
"csiso2022kr",
|
|
19834
|
+
"hz-gb-2312",
|
|
19835
|
+
"iso-2022-cn",
|
|
19836
|
+
"iso-2022-cn-ext",
|
|
19837
|
+
"iso-2022-kr",
|
|
19838
|
+
"replacement"
|
|
19839
|
+
]);
|
|
19840
|
+
const replacementDecoder = {
|
|
19841
|
+
encoding: "replacement",
|
|
19842
|
+
decode: (input) => input && input.byteLength ? "�" : ""
|
|
19843
|
+
};
|
|
19844
|
+
const UTF16_LABELS = /* @__PURE__ */ new Map([
|
|
19845
|
+
["utf-16", "utf-16"],
|
|
19846
|
+
["utf16", "utf-16"],
|
|
19847
|
+
["utf_16", "utf-16"],
|
|
19848
|
+
["utf-16le", "utf-16le"],
|
|
19849
|
+
["utf-16be", "utf-16be"]
|
|
19850
|
+
]);
|
|
19851
|
+
/**
|
|
19852
|
+
* The label of the Encoding Standard a charset resolves to, as written or through the
|
|
19853
|
+
* alias tables, or null when it names no encoding the standard lists
|
|
19854
|
+
*/
|
|
19855
|
+
function resolveLabel(charset) {
|
|
19856
|
+
charset = (charset || "utf8").trim().toLowerCase();
|
|
19857
|
+
if (ENCODING_LABELS.has(charset)) return charset;
|
|
19858
|
+
const utf16 = UTF16_LABELS.get(charset);
|
|
19859
|
+
if (utf16) return utf16;
|
|
19860
|
+
const normalized = normalizeCharset(charset);
|
|
19861
|
+
const codePage = normalized.match(codePagePattern);
|
|
19862
|
+
const alias = codePage && codePageAliases.get(codePage[1]) || charsetAliases.get(normalized) || normalized;
|
|
19863
|
+
return ENCODING_LABELS.has(alias) ? alias : null;
|
|
19864
|
+
}
|
|
19834
19865
|
const decoders = /* @__PURE__ */ new Map();
|
|
19835
|
-
function tryDecoder(
|
|
19836
|
-
if (
|
|
19837
|
-
let decoder = decoders.get(
|
|
19866
|
+
function tryDecoder(label) {
|
|
19867
|
+
if (REPLACEMENT_LABELS.has(label)) return replacementDecoder;
|
|
19868
|
+
let decoder = decoders.get(label);
|
|
19838
19869
|
if (decoder === void 0) {
|
|
19839
19870
|
decoder = null;
|
|
19840
19871
|
try {
|
|
19841
|
-
decoder = new TextDecoder(
|
|
19872
|
+
decoder = new TextDecoder(label);
|
|
19842
19873
|
} catch {}
|
|
19843
|
-
decoders.set(
|
|
19874
|
+
decoders.set(label, decoder);
|
|
19844
19875
|
}
|
|
19845
19876
|
return decoder;
|
|
19846
19877
|
}
|
|
@@ -19849,16 +19880,21 @@ const utf8Decoder = new TextDecoder();
|
|
|
19849
19880
|
* Finds the decoder for a charset label, or null when the runtime has no encoding for it
|
|
19850
19881
|
*/
|
|
19851
19882
|
function findDecoder(charset) {
|
|
19852
|
-
|
|
19853
|
-
|
|
19854
|
-
if (decoder) return decoder;
|
|
19855
|
-
const normalized = normalizeCharset(charset);
|
|
19856
|
-
const codePage = normalized.match(codePagePattern);
|
|
19857
|
-
return tryDecoder(codePage && codePageAliases.get(codePage[1]) || charsetAliases.get(normalized) || normalized);
|
|
19883
|
+
const label = resolveLabel(charset);
|
|
19884
|
+
return label ? tryDecoder(label) : null;
|
|
19858
19885
|
}
|
|
19859
19886
|
function getDecoder(charset) {
|
|
19860
19887
|
return findDecoder(charset) || tryDecoder("windows-1252") || utf8Decoder;
|
|
19861
19888
|
}
|
|
19889
|
+
const UTF16_UNORDERED_LABELS = /* @__PURE__ */ new Set(["utf-16"]);
|
|
19890
|
+
/**
|
|
19891
|
+
* The byte order a UTF-16 byte order mark at the start of the bytes names, or null without one
|
|
19892
|
+
*/
|
|
19893
|
+
function bomByteOrder(bytes) {
|
|
19894
|
+
if (bytes[0] === 254 && bytes[1] === 255) return "utf-16be";
|
|
19895
|
+
if (bytes[0] === 255 && bytes[1] === 254) return "utf-16le";
|
|
19896
|
+
return null;
|
|
19897
|
+
}
|
|
19862
19898
|
/**
|
|
19863
19899
|
* Whether the bytes are well formed UTF-8. A scan rather than a fatal TextDecoder, since
|
|
19864
19900
|
* the exception one throws costs more than the decode itself and a message decides how
|
|
@@ -19912,8 +19948,14 @@ function decodeUtf8OrWindows1252(bytes, utf8 = utf8Decoder) {
|
|
|
19912
19948
|
* @return Decoded text
|
|
19913
19949
|
*/
|
|
19914
19950
|
function decodeCharset(bytes, charset) {
|
|
19915
|
-
const
|
|
19916
|
-
|
|
19951
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
19952
|
+
const label = resolveLabel(charset);
|
|
19953
|
+
let decoder = label ? tryDecoder(label) : null;
|
|
19954
|
+
if (label && UTF16_UNORDERED_LABELS.has(label)) {
|
|
19955
|
+
const order = bomByteOrder(view);
|
|
19956
|
+
decoder = order && tryDecoder(order) || decoder;
|
|
19957
|
+
}
|
|
19958
|
+
return decoder ? decoder.decode(view) : decodeUtf8OrWindows1252(view);
|
|
19917
19959
|
}
|
|
19918
19960
|
/**
|
|
19919
19961
|
* Numeric value of an ASCII hex digit
|
|
@@ -20033,7 +20075,7 @@ function splitEncodedWords(str) {
|
|
|
20033
20075
|
function canJoinWords(left, right) {
|
|
20034
20076
|
const encoding = left.encoding.toUpperCase();
|
|
20035
20077
|
if (left.charset !== right.charset || encoding !== right.encoding.toUpperCase()) return false;
|
|
20036
|
-
if (encoding === "B") return left.encodedText.length % 4 === 0 &&
|
|
20078
|
+
if (encoding === "B") return left.encodedText.length % 4 === 0 && !left.padded;
|
|
20037
20079
|
return true;
|
|
20038
20080
|
}
|
|
20039
20081
|
/**
|
|
@@ -20060,13 +20102,15 @@ function renderTokens(tokens, joinWords) {
|
|
|
20060
20102
|
}
|
|
20061
20103
|
if (pending && joinWords && canJoinWords(pending, token)) {
|
|
20062
20104
|
pending.encodedText += token.encodedText;
|
|
20105
|
+
pending.padded = token.encodedText.endsWith("=");
|
|
20063
20106
|
continue;
|
|
20064
20107
|
}
|
|
20065
20108
|
if (pending) result += decodeWord(pending.charset, pending.encoding, pending.encodedText);
|
|
20066
20109
|
pending = {
|
|
20067
20110
|
charset: token.charset,
|
|
20068
20111
|
encoding: token.encoding,
|
|
20069
|
-
encodedText: token.encodedText
|
|
20112
|
+
encodedText: token.encodedText,
|
|
20113
|
+
padded: token.encodedText.endsWith("=")
|
|
20070
20114
|
};
|
|
20071
20115
|
}
|
|
20072
20116
|
if (pending) result += decodeWord(pending.charset, pending.encoding, pending.encodedText);
|
|
@@ -20085,7 +20129,86 @@ function decodeWords(str) {
|
|
|
20085
20129
|
}
|
|
20086
20130
|
new TextDecoder("utf-8", { ignoreBOM: true });
|
|
20087
20131
|
//#endregion
|
|
20088
|
-
//#region ../../node_modules/.pnpm/postal-mime@4.
|
|
20132
|
+
//#region ../../node_modules/.pnpm/postal-mime@4.1.4/node_modules/postal-mime/dist/esm/text-format.js
|
|
20133
|
+
const hexByte = (value) => value.toString(16).toUpperCase().padStart(2, "0");
|
|
20134
|
+
Array.from({ length: 256 }, (_, byte) => "%" + hexByte(byte));
|
|
20135
|
+
//#endregion
|
|
20136
|
+
//#region ../../node_modules/.pnpm/postal-mime@4.1.4/node_modules/postal-mime/dist/esm/address-parser.js
|
|
20137
|
+
/** The token is an operator, a text run otherwise */
|
|
20138
|
+
const TOKEN_OPERATOR = 1;
|
|
20139
|
+
/** The next character continues the same run without whitespace in between */
|
|
20140
|
+
const TOKEN_NO_BREAK = 2;
|
|
20141
|
+
/** The text run holds characters its value leaves out or changes, so it is no plain slice */
|
|
20142
|
+
const TOKEN_DIRTY = 4;
|
|
20143
|
+
/** A backslash escapes the next character in the text run, inside a quoted string or a comment */
|
|
20144
|
+
const TOKEN_ESCAPES = 8;
|
|
20145
|
+
/** The text run opens on a character an escaping backslash in front of it has escaped */
|
|
20146
|
+
const TOKEN_STARTS_ESCAPED = 16;
|
|
20147
|
+
/**
|
|
20148
|
+
* The tokens of an address field, kept as offsets into it.
|
|
20149
|
+
*
|
|
20150
|
+
* Every token used to be an object of its own, an operator for every ',' and a value string
|
|
20151
|
+
* for every run of text, and a list of one letter names, 'a,a,a,...', took some 65 times its
|
|
20152
|
+
* size in tokens before any address was read. A token is now three numbers in one typed
|
|
20153
|
+
* array, its kind and the range of the field it covers, and its value is sliced off the field
|
|
20154
|
+
* when it is read. The few text runs whose value differs from their slice, as they hold an
|
|
20155
|
+
* escaping backslash, a line break or a control character, are rebuilt once and kept aside.
|
|
20156
|
+
*/
|
|
20157
|
+
var TokenList = class {
|
|
20158
|
+
constructor(str) {
|
|
20159
|
+
this.str = str;
|
|
20160
|
+
this.slots = /* @__PURE__ */ new Int32Array(48);
|
|
20161
|
+
this.length = 0;
|
|
20162
|
+
this.cleaned = /* @__PURE__ */ new Map();
|
|
20163
|
+
}
|
|
20164
|
+
/**
|
|
20165
|
+
* Appends a token
|
|
20166
|
+
*
|
|
20167
|
+
* @param kind Kind and flags of the token
|
|
20168
|
+
* @param start Offset of its first character
|
|
20169
|
+
* @param end Offset just past its last character
|
|
20170
|
+
* @return Index of the token
|
|
20171
|
+
*/
|
|
20172
|
+
push(kind, start, end) {
|
|
20173
|
+
if ((this.length + 1) * 3 > this.slots.length) {
|
|
20174
|
+
const grown = new Int32Array(this.slots.length * 2);
|
|
20175
|
+
grown.set(this.slots);
|
|
20176
|
+
this.slots = grown;
|
|
20177
|
+
}
|
|
20178
|
+
const at = this.length * 3;
|
|
20179
|
+
this.slots[at] = kind;
|
|
20180
|
+
this.slots[at + 1] = start;
|
|
20181
|
+
this.slots[at + 2] = end;
|
|
20182
|
+
return this.length++;
|
|
20183
|
+
}
|
|
20184
|
+
/**
|
|
20185
|
+
* The character of an operator token, an empty string for a text run
|
|
20186
|
+
*/
|
|
20187
|
+
operatorAt(index) {
|
|
20188
|
+
const at = index * 3;
|
|
20189
|
+
return this.slots[at] & TOKEN_OPERATOR ? this.str.charAt(this.slots[at + 1]) : "";
|
|
20190
|
+
}
|
|
20191
|
+
/**
|
|
20192
|
+
* Whether a token is an operator
|
|
20193
|
+
*/
|
|
20194
|
+
isOperator(index) {
|
|
20195
|
+
return (this.slots[index * 3] & TOKEN_OPERATOR) !== 0;
|
|
20196
|
+
}
|
|
20197
|
+
/**
|
|
20198
|
+
* Whether the next character continues the run of a token without whitespace in between
|
|
20199
|
+
*/
|
|
20200
|
+
noBreak(index) {
|
|
20201
|
+
return (this.slots[index * 3] & TOKEN_NO_BREAK) !== 0;
|
|
20202
|
+
}
|
|
20203
|
+
/**
|
|
20204
|
+
* Value of a token
|
|
20205
|
+
*/
|
|
20206
|
+
value(index) {
|
|
20207
|
+
const cleaned = this.cleaned.get(index);
|
|
20208
|
+
if (cleaned !== void 0) return cleaned;
|
|
20209
|
+
return this.str.slice(this.slots[index * 3 + 1], this.slots[index * 3 + 2]);
|
|
20210
|
+
}
|
|
20211
|
+
};
|
|
20089
20212
|
/**
|
|
20090
20213
|
* Any whitespace, the test for an angle-addr that needs recovering
|
|
20091
20214
|
*/
|
|
@@ -20100,6 +20223,23 @@ const QUOTED_LOCAL_ADDR = /^("(?:[^"\\]|\\[\s\S])*"@\S+)(?:\s+([\s\S]+))?$/;
|
|
|
20100
20223
|
*/
|
|
20101
20224
|
const ADDR_SPEC = /^[^@\s]+@[^@\s]+$/;
|
|
20102
20225
|
/**
|
|
20226
|
+
* The same with a domain literal for the domain, which may hold an '@' of its own
|
|
20227
|
+
*/
|
|
20228
|
+
const LITERAL_ADDR_SPEC = /^[^@\s]+@\[[^[\]\\\s]*\]$/;
|
|
20229
|
+
/**
|
|
20230
|
+
* The '@' and domain that follow a quoted local part in the same run
|
|
20231
|
+
*/
|
|
20232
|
+
const QUOTED_LOCAL_DOMAIN = /^@(?:[^@\s"]+|\[[^[\]\\\s]*\])$/;
|
|
20233
|
+
/**
|
|
20234
|
+
* Whether a whitespace free run reads as an addr-spec under the strict grammar
|
|
20235
|
+
*
|
|
20236
|
+
* @param value Run to check
|
|
20237
|
+
* @return true when it does
|
|
20238
|
+
*/
|
|
20239
|
+
function isAddrSpec(value) {
|
|
20240
|
+
return ADDR_SPEC.test(value) || LITERAL_ADDR_SPEC.test(value);
|
|
20241
|
+
}
|
|
20242
|
+
/**
|
|
20103
20243
|
* The looser reading applied once the strict one finds nothing, which tolerates the
|
|
20104
20244
|
* further '@' that a domain should not have but malformed headers carry anyway
|
|
20105
20245
|
*/
|
|
@@ -20110,6 +20250,22 @@ const LOOSE_ADDR_SPEC = /^[^@\s]+@\S+$/;
|
|
|
20110
20250
|
*/
|
|
20111
20251
|
const LOOSE_TEXT_ADDR = /\s*\b[^@\s]+@[^\s]+\b\s*/y;
|
|
20112
20252
|
/**
|
|
20253
|
+
* A domain literal that ends an address. dtext includes '@' (RFC 5322 3.4.1), so the '@'
|
|
20254
|
+
* in front of the literal is the one that splits the domain off, not the last one: reading
|
|
20255
|
+
* 'a@[x@evil.com]' at its last '@' made it an address at evil.com.
|
|
20256
|
+
*/
|
|
20257
|
+
const DOMAIN_LITERAL_TAIL = /@(\[[^[\]\\]*\])$/;
|
|
20258
|
+
/**
|
|
20259
|
+
* Offset of the '@' that splits the domain off an address, or -1 when there is none
|
|
20260
|
+
*
|
|
20261
|
+
* @param address Address to split
|
|
20262
|
+
* @return Offset of the '@'
|
|
20263
|
+
*/
|
|
20264
|
+
function domainAt(address) {
|
|
20265
|
+
const literal = address.charAt(address.length - 1) === "]" ? DOMAIN_LITERAL_TAIL.exec(address) : null;
|
|
20266
|
+
return literal ? address.length - literal[1].length - 1 : address.lastIndexOf("@");
|
|
20267
|
+
}
|
|
20268
|
+
/**
|
|
20113
20269
|
* Restores the quoting of a local part that was read out of a quoted string.
|
|
20114
20270
|
*
|
|
20115
20271
|
* RFC 5321 allows '@' inside a quoted local part, so handing '"user@evil.com"@good.com'
|
|
@@ -20122,7 +20278,7 @@ const LOOSE_TEXT_ADDR = /\s*\b[^@\s]+@[^\s]+\b\s*/y;
|
|
|
20122
20278
|
* @return Address with the local part as a quoted-string
|
|
20123
20279
|
*/
|
|
20124
20280
|
function quoteLocalPart(address) {
|
|
20125
|
-
const lastAt = address
|
|
20281
|
+
const lastAt = domainAt(address);
|
|
20126
20282
|
if (lastAt < 0) return address;
|
|
20127
20283
|
const user = address.substring(0, lastAt);
|
|
20128
20284
|
if (/^[^\s"(),:;<>@[\\\]]+$/.test(user) || /^"(?:[^"\\]|\\[\s\S])*"$/.test(user)) return address;
|
|
@@ -20159,6 +20315,50 @@ function indexOfAt(text, from, to) {
|
|
|
20159
20315
|
return -1;
|
|
20160
20316
|
}
|
|
20161
20317
|
/**
|
|
20318
|
+
* A quoted-string a value opens with
|
|
20319
|
+
*/
|
|
20320
|
+
const LEADING_QUOTED_STRING = /^"(?:[^"\\]|\\[\s\S])*"/;
|
|
20321
|
+
/**
|
|
20322
|
+
* Drops the whitespace on either side of the '@' at an offset.
|
|
20323
|
+
*
|
|
20324
|
+
* obs-local-part and obs-domain allow CFWS around the '@' (RFC 5322 4.4), so 'user @ good.com'
|
|
20325
|
+
* names user@good.com. Kept, the whitespace became part of the address as '"user "@ good.com'.
|
|
20326
|
+
* The scan is by index, a pattern such as /\s+@/ backtracks over a long whitespace run that
|
|
20327
|
+
* no '@' follows.
|
|
20328
|
+
*
|
|
20329
|
+
* @param value Value holding the '@'
|
|
20330
|
+
* @param at Offset of the '@', or -1
|
|
20331
|
+
* @return The value with the whitespace beside that '@' removed
|
|
20332
|
+
*/
|
|
20333
|
+
function joinAtSpacing(value, at) {
|
|
20334
|
+
if (at < 0) return value;
|
|
20335
|
+
let start = at;
|
|
20336
|
+
while (start > 0 && isSpaceCode(value.charCodeAt(start - 1))) start--;
|
|
20337
|
+
let end = at + 1;
|
|
20338
|
+
while (end < value.length && isSpaceCode(value.charCodeAt(end))) end++;
|
|
20339
|
+
return start === at && end === at + 1 ? value : value.slice(0, start) + "@" + value.slice(end);
|
|
20340
|
+
}
|
|
20341
|
+
/**
|
|
20342
|
+
* Drops the whitespace beside the '@' that splits the domain off a value. That is the '@'
|
|
20343
|
+
* right after a quoted local part the value opens with, or else the only '@' it holds outside
|
|
20344
|
+
* any quoted string. With several it is not clear which one the whitespace belongs to, and
|
|
20345
|
+
* '<x@evil.com @good.com>' is left to the addr-spec recovery to read as it always has.
|
|
20346
|
+
*
|
|
20347
|
+
* @param value Value to clean up
|
|
20348
|
+
* @return The value with the whitespace beside that '@' removed
|
|
20349
|
+
*/
|
|
20350
|
+
function collapseAtSpacing(value) {
|
|
20351
|
+
const quoted = LEADING_QUOTED_STRING.exec(value);
|
|
20352
|
+
if (quoted) {
|
|
20353
|
+
let at = quoted[0].length;
|
|
20354
|
+
while (at < value.length && isSpaceCode(value.charCodeAt(at))) at++;
|
|
20355
|
+
if (value.charAt(at) === "@" && value.indexOf("@", at + 1) < 0) return joinAtSpacing(value, at);
|
|
20356
|
+
}
|
|
20357
|
+
const at = value.indexOf("@");
|
|
20358
|
+
if (at < 0 || value.indexOf("@", at + 1) >= 0 || value.lastIndexOf("\"") > at) return value;
|
|
20359
|
+
return joinAtSpacing(value, at);
|
|
20360
|
+
}
|
|
20361
|
+
/**
|
|
20162
20362
|
* Finds the offset LOOSE_TEXT_ADDR matches at, or -1 when it does not match at all.
|
|
20163
20363
|
*
|
|
20164
20364
|
* Letting the pattern search for itself is quadratic: '[^@\s]+' is retried from every
|
|
@@ -20229,6 +20429,7 @@ function looseAddressStart(text) {
|
|
|
20229
20429
|
* @return The address and the display text, with any leftovers moved to the text
|
|
20230
20430
|
*/
|
|
20231
20431
|
function recoverAddrSpec(address, text) {
|
|
20432
|
+
address = collapseAtSpacing(address);
|
|
20232
20433
|
if (!HAS_WHITESPACE.test(address)) return {
|
|
20233
20434
|
address,
|
|
20234
20435
|
text
|
|
@@ -20248,7 +20449,7 @@ function recoverAddrSpec(address, text) {
|
|
|
20248
20449
|
text
|
|
20249
20450
|
};
|
|
20250
20451
|
const parts = address.split(/\s+/);
|
|
20251
|
-
let addrIndex = parts.findIndex(
|
|
20452
|
+
let addrIndex = parts.findIndex(isAddrSpec);
|
|
20252
20453
|
if (addrIndex < 0) addrIndex = parts.findIndex((part) => LOOSE_ADDR_SPEC.test(part));
|
|
20253
20454
|
if (addrIndex < 0) return {
|
|
20254
20455
|
address,
|
|
@@ -20271,7 +20472,16 @@ function recoverAddrSpec(address, text) {
|
|
|
20271
20472
|
* consumer stripping the comment reads as 'user@example.comevil.com'. A comment is folding
|
|
20272
20473
|
* whitespace, so it is read as a space that splits two atoms and as nothing next to an '@',
|
|
20273
20474
|
* the same rule the token walk applies outside the brackets. Quoted strings and domain
|
|
20274
|
-
* literals are copied through untouched, a '(' in there is not a comment.
|
|
20475
|
+
* literals are copied through untouched, a '(' in there is not a comment. An unexpected '<'
|
|
20476
|
+
* outside them drops everything in front of it, comments included.
|
|
20477
|
+
*
|
|
20478
|
+
* RFC 822 also let an angle-addr name the relays to go through in front of the mailbox,
|
|
20479
|
+
* '<@relay1,@relay2:user@example.com>', which RFC 5322 still parses as obs-route. The route
|
|
20480
|
+
* is not part of the mailbox, and folded into the local part it made
|
|
20481
|
+
* '"@relay1,@relay2:user"@example.com' a recipient that does not exist. A local part can not
|
|
20482
|
+
* start with '@', so contents that do, up to the first ':' outside a domain literal and in
|
|
20483
|
+
* front of any quoted string, are a route and are dropped, as long as a mailbox with an '@'
|
|
20484
|
+
* of its own follows.
|
|
20275
20485
|
*
|
|
20276
20486
|
* @param address Contents of the angle brackets
|
|
20277
20487
|
* @return The address with the comments removed, and the text of the comments
|
|
@@ -20283,6 +20493,10 @@ function stripAddressComments(address) {
|
|
|
20283
20493
|
let depth = 0;
|
|
20284
20494
|
let closer = "";
|
|
20285
20495
|
let lastChar = "";
|
|
20496
|
+
let pendingSpace = false;
|
|
20497
|
+
let firstChar = "";
|
|
20498
|
+
let quoteSeen = false;
|
|
20499
|
+
let routeEnd = -1;
|
|
20286
20500
|
for (let i = 0, len = address.length; i < len; i++) {
|
|
20287
20501
|
const chr = address.charAt(i);
|
|
20288
20502
|
if (depth) {
|
|
@@ -20293,13 +20507,17 @@ function stripAddressComments(address) {
|
|
|
20293
20507
|
} else if (chr === ")" && !--depth) {
|
|
20294
20508
|
comments.push(comment.trim());
|
|
20295
20509
|
comment = "";
|
|
20296
|
-
|
|
20297
|
-
result += " ";
|
|
20298
|
-
lastChar = " ";
|
|
20299
|
-
}
|
|
20510
|
+
pendingSpace = lastChar !== "@";
|
|
20300
20511
|
} else comment += chr;
|
|
20301
20512
|
continue;
|
|
20302
20513
|
}
|
|
20514
|
+
if (pendingSpace && chr !== "(") {
|
|
20515
|
+
pendingSpace = false;
|
|
20516
|
+
if (chr !== "@" && chr !== "<" && !isSpaceCode(address.charCodeAt(i))) {
|
|
20517
|
+
result += " ";
|
|
20518
|
+
lastChar = " ";
|
|
20519
|
+
}
|
|
20520
|
+
}
|
|
20303
20521
|
if (closer) {
|
|
20304
20522
|
if (chr === "\\" && closer === "\"" && i < len - 1) {
|
|
20305
20523
|
result += chr + address.charAt(++i);
|
|
@@ -20307,15 +20525,28 @@ function stripAddressComments(address) {
|
|
|
20307
20525
|
continue;
|
|
20308
20526
|
}
|
|
20309
20527
|
if (chr === closer) closer = "";
|
|
20310
|
-
} else if (chr === "\"")
|
|
20528
|
+
} else if (chr === "\"") {
|
|
20529
|
+
closer = "\"";
|
|
20530
|
+
quoteSeen = true;
|
|
20531
|
+
} else if (chr === ":" && routeEnd < 0 && firstChar === "@" && !quoteSeen) routeEnd = result.length + 1;
|
|
20311
20532
|
else if (chr === "[") closer = "]";
|
|
20312
20533
|
else if (chr === "(") {
|
|
20313
20534
|
depth = 1;
|
|
20314
20535
|
continue;
|
|
20536
|
+
} else if (chr === "<") {
|
|
20537
|
+
result = "";
|
|
20538
|
+
lastChar = "";
|
|
20539
|
+
comments.length = 0;
|
|
20540
|
+
firstChar = "";
|
|
20541
|
+
quoteSeen = false;
|
|
20542
|
+
routeEnd = -1;
|
|
20543
|
+
continue;
|
|
20315
20544
|
}
|
|
20316
20545
|
result += chr;
|
|
20317
20546
|
lastChar = chr;
|
|
20547
|
+
if (!firstChar && !isSpaceCode(address.charCodeAt(i))) firstChar = chr;
|
|
20318
20548
|
}
|
|
20549
|
+
if (routeEnd >= 0 && result.indexOf("@", routeEnd) >= 0) result = result.slice(routeEnd);
|
|
20319
20550
|
if (depth) comments.push(comment.trim());
|
|
20320
20551
|
return {
|
|
20321
20552
|
address: result.trim(),
|
|
@@ -20323,62 +20554,36 @@ function stripAddressComments(address) {
|
|
|
20323
20554
|
};
|
|
20324
20555
|
}
|
|
20325
20556
|
/**
|
|
20326
|
-
*
|
|
20327
|
-
*
|
|
20328
|
-
*
|
|
20329
|
-
* Same answer as `/<[^<>]+@[^<>]+>/.test(text)`, which backtracks quadratically on a `<`
|
|
20330
|
-
* followed by a long run of '@' and no closing `>`.
|
|
20331
|
-
*
|
|
20332
|
-
* @param text Text to check
|
|
20333
|
-
* @return true if an angle bracket address is present
|
|
20334
|
-
*/
|
|
20335
|
-
function hasAngleAddress(text) {
|
|
20336
|
-
let start = -1;
|
|
20337
|
-
let at = -1;
|
|
20338
|
-
for (let i = 0; i < text.length; i++) {
|
|
20339
|
-
const chr = text.charAt(i);
|
|
20340
|
-
if (chr === "<") {
|
|
20341
|
-
start = i + 1;
|
|
20342
|
-
at = -1;
|
|
20343
|
-
} else if (chr === ">") {
|
|
20344
|
-
if (at >= 0 && at < i - 1) return true;
|
|
20345
|
-
start = -1;
|
|
20346
|
-
at = -1;
|
|
20347
|
-
} else if (chr === "@" && start >= 0 && at < 0 && i > start) at = i;
|
|
20348
|
-
}
|
|
20349
|
-
return false;
|
|
20350
|
-
}
|
|
20351
|
-
/**
|
|
20352
|
-
* Converts tokens for a single address into an address object
|
|
20557
|
+
* Walks the tokens of one entry of an address list and sorts them into the runs of display
|
|
20558
|
+
* text, angle-addr and comment they belong to
|
|
20353
20559
|
*
|
|
20354
|
-
* @param tokens Tokens
|
|
20355
|
-
* @param
|
|
20356
|
-
* @
|
|
20560
|
+
* @param tokens Tokens of the whole address list
|
|
20561
|
+
* @param from Index of the first token of the entry
|
|
20562
|
+
* @param to Index just past the last token of the entry
|
|
20563
|
+
* @return The collected runs, and whether the display text came out of a comment
|
|
20357
20564
|
*/
|
|
20358
|
-
function
|
|
20359
|
-
let isGroup = false;
|
|
20565
|
+
function collectAddressParts(tokens, from, to) {
|
|
20360
20566
|
let state = "text";
|
|
20361
|
-
let addresses = [];
|
|
20362
20567
|
let data = {
|
|
20363
20568
|
address: [],
|
|
20364
20569
|
comment: [],
|
|
20365
|
-
group: [],
|
|
20366
20570
|
text: [],
|
|
20367
|
-
textWasQuoted: []
|
|
20571
|
+
textWasQuoted: [],
|
|
20572
|
+
quotedLocalAt: []
|
|
20368
20573
|
};
|
|
20574
|
+
let lastTextPureQuoted = false;
|
|
20369
20575
|
let i;
|
|
20370
|
-
let len;
|
|
20371
20576
|
let insideQuotes = false;
|
|
20372
20577
|
const lastChars = {
|
|
20373
20578
|
address: "",
|
|
20374
20579
|
comment: "",
|
|
20375
|
-
group: "",
|
|
20376
20580
|
text: ""
|
|
20377
20581
|
};
|
|
20378
|
-
for (i =
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20582
|
+
for (i = from; i < to; i++) {
|
|
20583
|
+
const value = tokens.value(i);
|
|
20584
|
+
const prevOperator = i > from ? tokens.operatorAt(i - 1) : "";
|
|
20585
|
+
const prevNoBreak = i > from && tokens.noBreak(i - 1);
|
|
20586
|
+
if (tokens.isOperator(i)) switch (value) {
|
|
20382
20587
|
case "<":
|
|
20383
20588
|
state = "address";
|
|
20384
20589
|
insideQuotes = false;
|
|
@@ -20387,11 +20592,6 @@ function _handleAddress(tokens, depth) {
|
|
|
20387
20592
|
state = "comment";
|
|
20388
20593
|
insideQuotes = false;
|
|
20389
20594
|
break;
|
|
20390
|
-
case ":":
|
|
20391
|
-
state = "group";
|
|
20392
|
-
isGroup = true;
|
|
20393
|
-
insideQuotes = false;
|
|
20394
|
-
break;
|
|
20395
20595
|
case "\"":
|
|
20396
20596
|
insideQuotes = !insideQuotes;
|
|
20397
20597
|
state = "text";
|
|
@@ -20400,108 +20600,132 @@ function _handleAddress(tokens, depth) {
|
|
|
20400
20600
|
state = "text";
|
|
20401
20601
|
insideQuotes = false;
|
|
20402
20602
|
}
|
|
20403
|
-
else
|
|
20404
|
-
const
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20603
|
+
else {
|
|
20604
|
+
const opensAfterEmptyQuotedString = prevOperator === "\"" && prevNoBreak && i > from + 1 && tokens.operatorAt(i - 2) === "\"";
|
|
20605
|
+
if (prevNoBreak && data[state].length && (prevOperator !== ")" || lastChars[state] === "@" || value.charAt(0) === "@")) {
|
|
20606
|
+
const last = data[state].length - 1;
|
|
20607
|
+
if (state === "text" && !insideQuotes) {
|
|
20608
|
+
if (lastTextPureQuoted && value.charAt(0) === "@") data.quotedLocalAt[last] = data.text[last].length;
|
|
20609
|
+
lastTextPureQuoted = false;
|
|
20610
|
+
}
|
|
20611
|
+
data[state][last] += value;
|
|
20612
|
+
if (state === "text" && insideQuotes) data.textWasQuoted[last] = true;
|
|
20410
20613
|
} else {
|
|
20411
|
-
data[state].push(
|
|
20412
|
-
if (state === "text")
|
|
20614
|
+
data[state].push(value);
|
|
20615
|
+
if (state === "text") {
|
|
20616
|
+
data.textWasQuoted.push(insideQuotes || opensAfterEmptyQuotedString);
|
|
20617
|
+
data.quotedLocalAt.push(opensAfterEmptyQuotedString && value.charAt(0) === "@" ? 0 : -1);
|
|
20618
|
+
lastTextPureQuoted = insideQuotes;
|
|
20619
|
+
}
|
|
20413
20620
|
}
|
|
20414
|
-
lastChars[state] =
|
|
20621
|
+
lastChars[state] = value.charAt(value.length - 1);
|
|
20415
20622
|
}
|
|
20416
20623
|
}
|
|
20624
|
+
let textFromComment = false;
|
|
20417
20625
|
if (!data.text.length && data.comment.length) {
|
|
20418
20626
|
data.text = data.comment;
|
|
20419
20627
|
data.comment = [];
|
|
20628
|
+
textFromComment = true;
|
|
20420
20629
|
}
|
|
20421
|
-
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
|
|
20429
|
-
|
|
20430
|
-
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
|
|
20434
|
-
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
|
|
20440
|
-
|
|
20441
|
-
|
|
20442
|
-
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
|
|
20630
|
+
return {
|
|
20631
|
+
data,
|
|
20632
|
+
textFromComment
|
|
20633
|
+
};
|
|
20634
|
+
}
|
|
20635
|
+
/**
|
|
20636
|
+
* Name of a group, from the tokens in front of its ':'. A comment names it when no text
|
|
20637
|
+
* does, as it names a mailbox.
|
|
20638
|
+
*
|
|
20639
|
+
* @param tokens Tokens of the whole address list
|
|
20640
|
+
* @param from Index of the first token in front of the ':'
|
|
20641
|
+
* @param to Index of the ':'
|
|
20642
|
+
* @return Decoded name of the group
|
|
20643
|
+
*/
|
|
20644
|
+
function groupName(tokens, from, to) {
|
|
20645
|
+
return decodeWords(collectAddressParts(tokens, from, to).data.text.join(" "));
|
|
20646
|
+
}
|
|
20647
|
+
/**
|
|
20648
|
+
* Converts tokens for a single address into an address object
|
|
20649
|
+
*
|
|
20650
|
+
* @param tokens Tokens of the whole address list
|
|
20651
|
+
* @param from Index of the first token of this address
|
|
20652
|
+
* @param to Index just past the last token of this address
|
|
20653
|
+
* @return The address object
|
|
20654
|
+
*/
|
|
20655
|
+
function _handleAddress(tokens, from, to) {
|
|
20656
|
+
const collected = collectAddressParts(tokens, from, to);
|
|
20657
|
+
const data = collected.data;
|
|
20658
|
+
let textFromComment = collected.textFromComment;
|
|
20659
|
+
let i;
|
|
20660
|
+
const addressComments = [];
|
|
20661
|
+
const addressParts = [];
|
|
20662
|
+
for (const part of data.address) {
|
|
20663
|
+
const stripped = stripAddressComments(part);
|
|
20664
|
+
for (const comment of stripped.comments) addressComments.push(comment);
|
|
20665
|
+
if (stripped.address) addressParts.push(stripped.address);
|
|
20666
|
+
}
|
|
20667
|
+
data.address = addressParts;
|
|
20668
|
+
if (!data.address.length && data.text.length && !textFromComment) {
|
|
20669
|
+
const candidates = data.text.map((part) => collapseAtSpacing(part));
|
|
20670
|
+
for (i = 0; i < data.text.length; i++) {
|
|
20671
|
+
const local = data.quotedLocalAt[i];
|
|
20672
|
+
const quotedLocal = data.textWasQuoted[i] && local >= 0 && QUOTED_LOCAL_DOMAIN.test(data.text[i].slice(local)) ? quoteLocalPart(data.text[i]) : "";
|
|
20673
|
+
if (quotedLocal || !data.textWasQuoted[i] && isAddrSpec(candidates[i])) {
|
|
20674
|
+
data.address = [quotedLocal || candidates[i]];
|
|
20675
|
+
data.text.splice(i, 1);
|
|
20448
20676
|
data.textWasQuoted.splice(i, 1);
|
|
20677
|
+
data.quotedLocalAt.splice(i, 1);
|
|
20449
20678
|
break;
|
|
20450
20679
|
}
|
|
20451
|
-
|
|
20452
|
-
|
|
20453
|
-
|
|
20454
|
-
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
|
|
20458
|
-
|
|
20459
|
-
|
|
20460
|
-
|
|
20461
|
-
|
|
20462
|
-
|
|
20680
|
+
}
|
|
20681
|
+
if (!data.address.length) {
|
|
20682
|
+
for (i = 0; i < data.text.length; i++) if (!data.textWasQuoted[i]) {
|
|
20683
|
+
const part = candidates[i];
|
|
20684
|
+
const start = looseAddressStart(part);
|
|
20685
|
+
if (start >= 0) {
|
|
20686
|
+
LOOSE_TEXT_ADDR.lastIndex = start;
|
|
20687
|
+
const match = LOOSE_TEXT_ADDR.exec(part);
|
|
20688
|
+
if (match) {
|
|
20689
|
+
data.address = [match[0].trim()];
|
|
20690
|
+
data.text[i] = (part.slice(0, start) + " " + part.slice(start + match[0].length)).trim();
|
|
20691
|
+
break;
|
|
20463
20692
|
}
|
|
20464
|
-
data.text[i] = part.trim();
|
|
20465
20693
|
}
|
|
20694
|
+
data.text[i] = data.text[i].trim();
|
|
20466
20695
|
}
|
|
20467
20696
|
}
|
|
20468
|
-
if (!data.text.length && data.comment.length) {
|
|
20469
|
-
data.text = data.comment;
|
|
20470
|
-
data.comment = [];
|
|
20471
|
-
}
|
|
20472
|
-
if (data.address.length > 1) data.text = data.text.concat(data.address.splice(1));
|
|
20473
|
-
const addressFromQuotedText = !data.address.length && data.textWasQuoted.some((wasQuoted) => wasQuoted);
|
|
20474
|
-
let text = data.text.join(" ");
|
|
20475
|
-
let addressValue = data.address.join(" ");
|
|
20476
|
-
if (!addressValue && isEncodedWordsOnly(text.trim())) {
|
|
20477
|
-
const decodedText = decodeWords(text);
|
|
20478
|
-
if (!addressFromQuotedText && hasAngleAddress(decodedText)) {
|
|
20479
|
-
const parsedSubAddresses = parseAddressList$1(decodedText, depth + 1);
|
|
20480
|
-
if (parsedSubAddresses.length) return parsedSubAddresses;
|
|
20481
|
-
}
|
|
20482
|
-
return [{
|
|
20483
|
-
address: "",
|
|
20484
|
-
name: decodedText
|
|
20485
|
-
}];
|
|
20486
|
-
}
|
|
20487
|
-
if (addressFromQuotedText && text.indexOf("@") >= 0) {
|
|
20488
|
-
addressValue = quoteLocalPart(text);
|
|
20489
|
-
text = "";
|
|
20490
|
-
}
|
|
20491
|
-
({address: addressValue, text} = recoverAddrSpec(addressValue, text));
|
|
20492
|
-
if (!text && addressComments.length) text = addressComments.join(" ");
|
|
20493
|
-
const rawName = text || addressValue || "";
|
|
20494
|
-
const address = {
|
|
20495
|
-
address: addressValue || text || "",
|
|
20496
|
-
name: decodeWords(rawName)
|
|
20497
|
-
};
|
|
20498
|
-
if (address.address === rawName || address.address === address.name) {
|
|
20499
|
-
if (address.address.match(/@/)) address.name = "";
|
|
20500
|
-
else address.address = "";
|
|
20501
|
-
}
|
|
20502
|
-
addresses.push(address);
|
|
20503
20697
|
}
|
|
20504
|
-
|
|
20698
|
+
if (!data.text.length && data.comment.length) {
|
|
20699
|
+
data.text = data.comment;
|
|
20700
|
+
data.comment = [];
|
|
20701
|
+
}
|
|
20702
|
+
if (data.address.length > 1) data.text = data.text.concat(data.address.splice(1));
|
|
20703
|
+
const addressFromQuotedText = !data.address.length && data.textWasQuoted.some((wasQuoted) => wasQuoted);
|
|
20704
|
+
let text = data.text.join(" ");
|
|
20705
|
+
let addressValue = data.address.join(" ");
|
|
20706
|
+
if (!addressValue && isEncodedWordsOnly(text.trim())) return {
|
|
20707
|
+
address: "",
|
|
20708
|
+
name: decodeWords(text)
|
|
20709
|
+
};
|
|
20710
|
+
if (addressFromQuotedText && text.indexOf("@") >= 0) {
|
|
20711
|
+
addressValue = quoteLocalPart(joinAtSpacing(text, domainAt(text)));
|
|
20712
|
+
text = "";
|
|
20713
|
+
}
|
|
20714
|
+
({address: addressValue, text} = recoverAddrSpec(addressValue, text));
|
|
20715
|
+
if (!text && addressComments.length) {
|
|
20716
|
+
text = addressComments.join(" ");
|
|
20717
|
+
textFromComment = true;
|
|
20718
|
+
}
|
|
20719
|
+
const rawName = text || addressValue || "";
|
|
20720
|
+
const address = {
|
|
20721
|
+
address: addressValue || (textFromComment ? "" : text) || "",
|
|
20722
|
+
name: decodeWords(rawName)
|
|
20723
|
+
};
|
|
20724
|
+
if (address.address === rawName || address.address === address.name) {
|
|
20725
|
+
if (address.address.match(/@/)) address.name = "";
|
|
20726
|
+
else address.address = "";
|
|
20727
|
+
}
|
|
20728
|
+
return address;
|
|
20505
20729
|
}
|
|
20506
20730
|
/**
|
|
20507
20731
|
* Tokenizer for address field strings
|
|
@@ -20514,17 +20738,22 @@ var Tokenizer$2 = class {
|
|
|
20514
20738
|
this.str = (str || "").toString();
|
|
20515
20739
|
this.operatorCurrent = "";
|
|
20516
20740
|
this.operatorExpecting = "";
|
|
20517
|
-
this.node =
|
|
20741
|
+
this.node = -1;
|
|
20518
20742
|
this.escaped = false;
|
|
20519
20743
|
this.inDomainLiteral = false;
|
|
20520
20744
|
this.afterAt = false;
|
|
20521
|
-
this.
|
|
20745
|
+
this.inGroup = false;
|
|
20746
|
+
this.innerQuoted = false;
|
|
20747
|
+
this.innerComment = 0;
|
|
20748
|
+
this.innerLiteral = false;
|
|
20749
|
+
this.innerEscaped = false;
|
|
20750
|
+
this.list = new TokenList(this.str);
|
|
20522
20751
|
this.operators = {
|
|
20523
20752
|
"\"": "\"",
|
|
20524
20753
|
"(": ")",
|
|
20525
20754
|
"<": ">",
|
|
20526
20755
|
",": "",
|
|
20527
|
-
":": "
|
|
20756
|
+
":": "",
|
|
20528
20757
|
";": ""
|
|
20529
20758
|
};
|
|
20530
20759
|
}
|
|
@@ -20534,115 +20763,259 @@ var Tokenizer$2 = class {
|
|
|
20534
20763
|
* @return An array of operator|text tokens
|
|
20535
20764
|
*/
|
|
20536
20765
|
tokenize() {
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20766
|
+
const str = this.str;
|
|
20767
|
+
const len = str.length;
|
|
20768
|
+
for (let i = 0; i < len; i++) this.checkChar(str.charAt(i), i);
|
|
20769
|
+
this.closeText(len);
|
|
20770
|
+
const list = this.list;
|
|
20771
|
+
const slots = list.slots;
|
|
20772
|
+
let kept = 0;
|
|
20773
|
+
for (let i = 0, count = list.length; i < count; i++) {
|
|
20774
|
+
const kind = slots[i * 3];
|
|
20775
|
+
let start = slots[i * 3 + 1];
|
|
20776
|
+
let end = slots[i * 3 + 2];
|
|
20777
|
+
if (kind & TOKEN_DIRTY) {
|
|
20778
|
+
const value = this.cleanValue(start, end, kind).trim();
|
|
20779
|
+
if (!value) continue;
|
|
20780
|
+
list.cleaned.set(kept, value);
|
|
20781
|
+
} else if (!(kind & TOKEN_OPERATOR)) {
|
|
20782
|
+
while (start < end && isSpaceCode(str.charCodeAt(start))) start++;
|
|
20783
|
+
while (end > start && isSpaceCode(str.charCodeAt(end - 1))) end--;
|
|
20784
|
+
if (start === end) continue;
|
|
20785
|
+
}
|
|
20786
|
+
slots[kept * 3] = kind;
|
|
20787
|
+
slots[kept * 3 + 1] = start;
|
|
20788
|
+
slots[kept * 3 + 2] = end;
|
|
20789
|
+
kept++;
|
|
20790
|
+
}
|
|
20791
|
+
list.length = kept;
|
|
20547
20792
|
return list;
|
|
20548
20793
|
}
|
|
20549
20794
|
/**
|
|
20795
|
+
* Ends the text run being collected, if there is one
|
|
20796
|
+
*
|
|
20797
|
+
* @param end Offset just past its last character
|
|
20798
|
+
*/
|
|
20799
|
+
closeText(end) {
|
|
20800
|
+
if (this.node >= 0) {
|
|
20801
|
+
this.list.slots[this.node * 3 + 2] = end;
|
|
20802
|
+
this.node = -1;
|
|
20803
|
+
}
|
|
20804
|
+
}
|
|
20805
|
+
/**
|
|
20806
|
+
* Rebuilds the value of a text run that is no plain slice of the field, applying the
|
|
20807
|
+
* rules checkChar collects characters by: an escaping backslash is dropped, a line break
|
|
20808
|
+
* reads as a space and any other control character is left out
|
|
20809
|
+
*
|
|
20810
|
+
* @param start Offset of the first character of the run
|
|
20811
|
+
* @param end Offset just past its last character
|
|
20812
|
+
* @param kind Kind and flags of the run
|
|
20813
|
+
* @return Value of the run
|
|
20814
|
+
*/
|
|
20815
|
+
cleanValue(start, end, kind) {
|
|
20816
|
+
const str = this.str;
|
|
20817
|
+
const escapes = (kind & TOKEN_ESCAPES) !== 0;
|
|
20818
|
+
let escaped = (kind & TOKEN_STARTS_ESCAPED) !== 0;
|
|
20819
|
+
let value = "";
|
|
20820
|
+
for (let i = start; i < end; i++) {
|
|
20821
|
+
const chr = str.charAt(i);
|
|
20822
|
+
if (escapes && !escaped && chr === "\\") {
|
|
20823
|
+
escaped = true;
|
|
20824
|
+
continue;
|
|
20825
|
+
}
|
|
20826
|
+
escaped = false;
|
|
20827
|
+
if (chr === "\n") value += " ";
|
|
20828
|
+
else if (chr.charCodeAt(0) >= 33 || chr === " " || chr === " ") value += chr;
|
|
20829
|
+
}
|
|
20830
|
+
return value;
|
|
20831
|
+
}
|
|
20832
|
+
/**
|
|
20550
20833
|
* Checks if a character is an operator or text and acts accordingly
|
|
20551
20834
|
*
|
|
20552
20835
|
* @param chr Character from the address field
|
|
20553
|
-
* @param
|
|
20836
|
+
* @param i Offset of the character in the field
|
|
20554
20837
|
*/
|
|
20555
|
-
checkChar(chr,
|
|
20838
|
+
checkChar(chr, i) {
|
|
20556
20839
|
if (!this.escaped && !this.operatorExpecting) {
|
|
20557
20840
|
if (!this.inDomainLiteral && chr === "[" && this.afterAt) this.inDomainLiteral = true;
|
|
20558
20841
|
else if (this.inDomainLiteral && (chr === "]" || chr === "," || chr === ";")) this.inDomainLiteral = false;
|
|
20559
20842
|
}
|
|
20560
|
-
|
|
20561
|
-
|
|
20562
|
-
|
|
20563
|
-
|
|
20564
|
-
|
|
20565
|
-
|
|
20843
|
+
let nested = false;
|
|
20844
|
+
if (this.operatorExpecting === ">") {
|
|
20845
|
+
nested = this.trackInner(chr);
|
|
20846
|
+
if (!nested && chr === ";" && this.inGroup) {
|
|
20847
|
+
this.closeText(i);
|
|
20848
|
+
this.operatorExpecting = "";
|
|
20849
|
+
this.resetInner();
|
|
20850
|
+
}
|
|
20851
|
+
} else if (this.operatorExpecting === ")" && !this.escaped) {
|
|
20852
|
+
if (chr === "(") this.innerComment++;
|
|
20853
|
+
else if (chr === ")" && this.innerComment) {
|
|
20854
|
+
this.innerComment--;
|
|
20855
|
+
nested = true;
|
|
20856
|
+
}
|
|
20857
|
+
}
|
|
20858
|
+
if (this.escaped) {} else if (chr === this.operatorExpecting && !nested) {
|
|
20859
|
+
const nextChr = this.str.charAt(i + 1);
|
|
20860
|
+
this.closeText(i);
|
|
20861
|
+
this.list.push(nextChr && ![
|
|
20566
20862
|
" ",
|
|
20567
20863
|
" ",
|
|
20568
20864
|
"\r",
|
|
20569
20865
|
"\n",
|
|
20570
20866
|
",",
|
|
20571
20867
|
";"
|
|
20572
|
-
].includes(nextChr)
|
|
20573
|
-
this.list.push(this.node);
|
|
20574
|
-
this.node = null;
|
|
20868
|
+
].includes(nextChr) ? 3 : TOKEN_OPERATOR, i, i + 1);
|
|
20575
20869
|
if (chr !== ")") this.afterAt = false;
|
|
20576
20870
|
this.operatorExpecting = "";
|
|
20577
20871
|
this.escaped = false;
|
|
20872
|
+
this.resetInner();
|
|
20578
20873
|
return;
|
|
20579
20874
|
} else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ":") && chr in this.operators) {
|
|
20580
|
-
this.
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
this.list.push(this.node);
|
|
20585
|
-
this.node = null;
|
|
20875
|
+
this.closeText(i);
|
|
20876
|
+
this.list.push(TOKEN_OPERATOR, i, i + 1);
|
|
20877
|
+
if (chr === ":") this.inGroup = true;
|
|
20878
|
+
else if (chr === ";") this.inGroup = false;
|
|
20586
20879
|
if (chr !== "(") this.afterAt = false;
|
|
20587
20880
|
this.operatorExpecting = this.operators[chr];
|
|
20588
20881
|
this.escaped = false;
|
|
20882
|
+
this.resetInner();
|
|
20589
20883
|
return;
|
|
20590
20884
|
} else if ((this.operatorExpecting === "\"" || this.operatorExpecting === ")") && chr === "\\") {
|
|
20591
20885
|
this.escaped = true;
|
|
20886
|
+
if (this.node >= 0) this.list.slots[this.node * 3] |= TOKEN_DIRTY;
|
|
20592
20887
|
return;
|
|
20593
20888
|
}
|
|
20594
|
-
if (
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
|
|
20598
|
-
};
|
|
20599
|
-
this.list.push(this.node);
|
|
20889
|
+
if (this.node < 0) this.node = this.list.push((this.operatorExpecting === "\"" || this.operatorExpecting === ")" ? TOKEN_ESCAPES : 0) | (this.escaped ? TOKEN_STARTS_ESCAPED : 0), i, i);
|
|
20890
|
+
if (chr === "\n") {
|
|
20891
|
+
chr = " ";
|
|
20892
|
+
this.list.slots[this.node * 3] |= TOKEN_DIRTY;
|
|
20600
20893
|
}
|
|
20601
|
-
if (chr === "
|
|
20602
|
-
if (chr.charCodeAt(0) >= 33 || [" ", " "].includes(chr)) {
|
|
20603
|
-
this.node.value += chr;
|
|
20894
|
+
if (chr.charCodeAt(0) >= 33 || chr === " " || chr === " ") {
|
|
20604
20895
|
if (!this.operatorExpecting && chr !== " " && chr !== " ") this.afterAt = chr === "@";
|
|
20605
|
-
}
|
|
20896
|
+
} else this.list.slots[this.node * 3] |= TOKEN_DIRTY;
|
|
20606
20897
|
this.escaped = false;
|
|
20607
20898
|
}
|
|
20899
|
+
/**
|
|
20900
|
+
* Tracks the quoted strings, comments and domain literals inside an angle-addr, which its
|
|
20901
|
+
* closer must not be read from. A backslash escapes the next character inside a quoted
|
|
20902
|
+
* string and a comment. A domain literal opens right after an "@" and ends at the "]".
|
|
20903
|
+
* dtext holds the list separators as well, and inside the brackets they never split the
|
|
20904
|
+
* list anyway.
|
|
20905
|
+
*
|
|
20906
|
+
* @param chr Character from the address field
|
|
20907
|
+
* @return true while the character sits inside one of them, the one that closes it included
|
|
20908
|
+
*/
|
|
20909
|
+
trackInner(chr) {
|
|
20910
|
+
if (this.innerEscaped) {
|
|
20911
|
+
this.innerEscaped = false;
|
|
20912
|
+
return true;
|
|
20913
|
+
}
|
|
20914
|
+
if (this.innerQuoted) {
|
|
20915
|
+
if (chr === "\\") this.innerEscaped = true;
|
|
20916
|
+
else if (chr === "\"") this.innerQuoted = false;
|
|
20917
|
+
return true;
|
|
20918
|
+
}
|
|
20919
|
+
if (this.innerComment) {
|
|
20920
|
+
if (chr === "\\") this.innerEscaped = true;
|
|
20921
|
+
else if (chr === "(") this.innerComment++;
|
|
20922
|
+
else if (chr === ")") this.innerComment--;
|
|
20923
|
+
return true;
|
|
20924
|
+
}
|
|
20925
|
+
if (this.innerLiteral) {
|
|
20926
|
+
if (chr === "]") this.innerLiteral = false;
|
|
20927
|
+
return true;
|
|
20928
|
+
}
|
|
20929
|
+
if (chr === "\"") {
|
|
20930
|
+
this.innerQuoted = true;
|
|
20931
|
+
return true;
|
|
20932
|
+
}
|
|
20933
|
+
if (chr === "(") {
|
|
20934
|
+
this.innerComment = 1;
|
|
20935
|
+
return true;
|
|
20936
|
+
}
|
|
20937
|
+
if (chr === "[" && this.afterAt) {
|
|
20938
|
+
this.innerLiteral = true;
|
|
20939
|
+
return true;
|
|
20940
|
+
}
|
|
20941
|
+
if (!isSpaceCode(chr.charCodeAt(0))) this.afterAt = chr === "@";
|
|
20942
|
+
return false;
|
|
20943
|
+
}
|
|
20944
|
+
/**
|
|
20945
|
+
* Forgets the nesting collected for the operator that just opened or ended
|
|
20946
|
+
*/
|
|
20947
|
+
resetInner() {
|
|
20948
|
+
this.innerQuoted = false;
|
|
20949
|
+
this.innerComment = 0;
|
|
20950
|
+
this.innerLiteral = false;
|
|
20951
|
+
this.innerEscaped = false;
|
|
20952
|
+
}
|
|
20608
20953
|
};
|
|
20609
20954
|
/**
|
|
20610
|
-
*
|
|
20611
|
-
*
|
|
20612
|
-
*
|
|
20955
|
+
* Folds the fragments an unquoted display name holding a comma is split into back together.
|
|
20956
|
+
*
|
|
20957
|
+
* "Joe Foo, PhD <joe@example.com>" is split on the comma into
|
|
20958
|
+
* [{name:"Joe Foo", address:""}, {name:"PhD", address:"joe@example.com"}]. A name-only entry
|
|
20959
|
+
* followed by an entry with both name and address is recombined. Walked back to front so
|
|
20960
|
+
* that a run of fragments folds into one entry in a single pass. Splicing each fragment out
|
|
20961
|
+
* of the list instead would be quadratic.
|
|
20962
|
+
*
|
|
20963
|
+
* @param list Parsed entries, in order
|
|
20964
|
+
* @return The entries with the fragments folded in
|
|
20613
20965
|
*/
|
|
20614
|
-
|
|
20966
|
+
function mergeNameFragments(list) {
|
|
20967
|
+
const merged = [];
|
|
20968
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
20969
|
+
const current = list[i];
|
|
20970
|
+
const next = merged.length ? merged[merged.length - 1] : null;
|
|
20971
|
+
if (next && current.address === "" && current.name && !current.group && next.address && next.name) next.name = current.name + ", " + next.name;
|
|
20972
|
+
else merged.push(current);
|
|
20973
|
+
}
|
|
20974
|
+
return merged.reverse();
|
|
20975
|
+
}
|
|
20615
20976
|
/**
|
|
20616
|
-
* Parses an address list,
|
|
20617
|
-
*
|
|
20618
|
-
*
|
|
20977
|
+
* Parses an address list, groups included, in a single pass over its tokens.
|
|
20978
|
+
*
|
|
20979
|
+
* A group used to be cut out of the field at the ';' that ends it and parsed again as an
|
|
20980
|
+
* address list of its own, recursively, so every level of nesting parsed what was left of
|
|
20981
|
+
* the field once more. RFC 5322 does not nest groups at all, and the nested ones were
|
|
20982
|
+
* flattened anyway. The tokens are now walked once: a ':' opens a group, its members are
|
|
20983
|
+
* read as they come, a ';' closes it. A ':' inside an open group stands for a group nested
|
|
20984
|
+
* in it, whose name is dropped and whose members belong to the outer one, and the first ';'
|
|
20985
|
+
* ends the group, nested or not, as before.
|
|
20619
20986
|
*
|
|
20620
20987
|
* @param str Address field
|
|
20621
|
-
* @param depth Current recursion depth for nested group protection
|
|
20622
20988
|
* @return An array of address objects
|
|
20623
20989
|
*/
|
|
20624
|
-
function parseAddressList$1(str
|
|
20625
|
-
|
|
20626
|
-
let tokens = new Tokenizer$2(str).tokenize();
|
|
20627
|
-
let addresses = [];
|
|
20628
|
-
let address = [];
|
|
20990
|
+
function parseAddressList$1(str) {
|
|
20991
|
+
const tokens = new Tokenizer$2(str).tokenize();
|
|
20629
20992
|
const parsedAddresses = [];
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
|
|
20637
|
-
|
|
20638
|
-
|
|
20639
|
-
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20993
|
+
let group = null;
|
|
20994
|
+
let members = [];
|
|
20995
|
+
let from = 0;
|
|
20996
|
+
for (let i = 0, len = tokens.length; i <= len; i++) {
|
|
20997
|
+
const operator = i < len ? tokens.operatorAt(i) : "";
|
|
20998
|
+
if (i < len && operator !== "," && operator !== ";" && operator !== ":") continue;
|
|
20999
|
+
if (operator === ":") {
|
|
21000
|
+
if (!group) {
|
|
21001
|
+
group = {
|
|
21002
|
+
name: groupName(tokens, from, i),
|
|
21003
|
+
group: []
|
|
21004
|
+
};
|
|
21005
|
+
members = [];
|
|
21006
|
+
}
|
|
21007
|
+
from = i + 1;
|
|
21008
|
+
continue;
|
|
21009
|
+
}
|
|
21010
|
+
if (i > from) (group ? members : parsedAddresses).push(_handleAddress(tokens, from, i));
|
|
21011
|
+
from = i + 1;
|
|
21012
|
+
if (group && (operator === ";" || i === len)) {
|
|
21013
|
+
group.group = mergeNameFragments(members);
|
|
21014
|
+
parsedAddresses.push(group);
|
|
21015
|
+
group = null;
|
|
21016
|
+
}
|
|
20644
21017
|
}
|
|
20645
|
-
return
|
|
21018
|
+
return mergeNameFragments(parsedAddresses);
|
|
20646
21019
|
}
|
|
20647
21020
|
/**
|
|
20648
21021
|
* Parses structured e-mail addresses from an address field
|
|
@@ -20660,7 +21033,7 @@ function parseAddressList$1(str, depth) {
|
|
|
20660
21033
|
* @return An array of address objects
|
|
20661
21034
|
*/
|
|
20662
21035
|
function addressParser(str, options) {
|
|
20663
|
-
const parsedAddresses = parseAddressList$1(str
|
|
21036
|
+
const parsedAddresses = parseAddressList$1(str);
|
|
20664
21037
|
if (options && options.flatten) {
|
|
20665
21038
|
let addresses = [];
|
|
20666
21039
|
let walkAddressList = (list) => {
|