primcli 1.52.0 → 1.54.0
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 +4 -1
- package/bin/claude-pending-mail.mjs +14 -5
- package/bin/claude-wake.mjs +2 -1
- package/dist/{api-command-DWTysV8D.js → api-command-C_Fn9Zef.js} +126 -93
- package/dist/{machine-session-D4UbgQCW.js → machine-session-Do3mw2_w.js} +3 -3
- package/dist/{notify-session-CPdFxyt7.js → notify-session-DhV0DLAA.js} +2 -2
- package/dist/oclif/auto-signal-worker.js +2 -2
- package/dist/oclif/auto-signals.js +1 -1
- package/dist/oclif/index.js +66 -40
- package/dist/oclif/receiver-heal.js +1 -1
- package/dist/{repeats-D2xtLium-B8Wq41vG.js → repeats-D2xtLium-DmtVD6ol.js} +536 -352
- package/dist/{scoped-chat-BJVYBRim.js → scoped-chat-BrLq4dXH.js} +1 -1
- package/dist/{signal-command-CaKUsE4H.js → signal-command-CqfODMCp.js} +3 -3
- package/package.json +1 -1
|
@@ -1,368 +1,556 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "./rolldown-runtime-CNZpIYwj.js";
|
|
2
|
-
//#region ../node_modules/.pnpm/nodemailer@
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
else if (token.value) {
|
|
134
|
-
if (state === "address") token.value = token.value.replace(/^[^<]*<\s*/, "");
|
|
135
|
-
const parts = data[state];
|
|
136
|
-
if (prevToken && prevToken.noBreak && parts.length && (prevToken.value !== ")" || parts[parts.length - 1].slice(-1) === "@" || token.value.charAt(0) === "@")) {
|
|
137
|
-
data[state][data[state].length - 1] += token.value;
|
|
138
|
-
if (state === "text" && insideQuotes) data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
|
139
|
-
} else {
|
|
140
|
-
data[state].push(token.value);
|
|
141
|
-
if (state === "text") data.textWasQuoted.push(insideQuotes);
|
|
2
|
+
//#region ../node_modules/.pnpm/nodemailer@10.0.15/node_modules/nodemailer/dist/esm/addressparser/index.js
|
|
3
|
+
/**
|
|
4
|
+
* Restores the quoting of a local part that was read out of a quoted string.
|
|
5
|
+
*
|
|
6
|
+
* RFC 5321 allows '@' inside a quoted local part, so handing '"user@evil.com"@good.com'
|
|
7
|
+
* on as the bare 'user@evil.com@good.com' leaves it to the consumer which '@' splits the
|
|
8
|
+
* domain off. Getting that wrong is a misrouting vector, so the quotes go back on. The
|
|
9
|
+
* same holds for the other specials: a ',' or a ';' that loses its quotes reads as a
|
|
10
|
+
* recipient separator once the consumer puts the address back into a header.
|
|
11
|
+
*
|
|
12
|
+
* This module has no dependencies so that it can ship on its own, which is why the two
|
|
13
|
+
* grammar tests below are spelled out here instead of shared with src/mime-node. Keeping
|
|
14
|
+
* only what is ambiguous quoted is deliberate, mime-node applies the stricter RFC 5321
|
|
15
|
+
* dot-atom rule on top of this when it emits an address.
|
|
16
|
+
*
|
|
17
|
+
* @param address Address with an unquoted local part
|
|
18
|
+
* @return Address with the local part as a quoted-string
|
|
19
|
+
*/
|
|
20
|
+
function _quoteLocalPart(address) {
|
|
21
|
+
const lastAt = address.lastIndexOf("@");
|
|
22
|
+
if (lastAt < 0) return address;
|
|
23
|
+
const user = address.substr(0, lastAt);
|
|
24
|
+
if (/^[^\s"(),:;<>@[\\\]]+$/.test(user) || /^"(?:[^"\\]|\\[\s\S])*"$/.test(user)) return address;
|
|
25
|
+
return "\"" + user.replace(/["\\]/g, "\\$&") + "\"@" + address.substr(lastAt + 1);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reached for every parsed address, so it is built once rather than per call.
|
|
29
|
+
*/
|
|
30
|
+
const HAS_WHITESPACE = /\s/;
|
|
31
|
+
/**
|
|
32
|
+
* An addr-spec that carries its whitespace legally, inside a quoted local part. The
|
|
33
|
+
* optional tail is the malformed shape: a real mailbox with wreckage trailing it.
|
|
34
|
+
*/
|
|
35
|
+
const QUOTED_LOCAL_ADDR = /^("(?:[^"\\]|\\[\s\S])*"@\S+)(?:\s+([\s\S]+))?$/;
|
|
36
|
+
/**
|
|
37
|
+
* One run holding a single '@' and no whitespace, the shape an addr-spec has to have.
|
|
38
|
+
*/
|
|
39
|
+
const ADDR_SPEC = /^[^@\s]+@[^@\s]+$/;
|
|
40
|
+
/**
|
|
41
|
+
* The looser reading applied once the strict one finds nothing, which tolerates the
|
|
42
|
+
* further '@' that a domain should not have but malformed headers carry anyway.
|
|
43
|
+
*/
|
|
44
|
+
const LOOSE_ADDR_SPEC = /^[^@\s]+@\S+$/;
|
|
45
|
+
/**
|
|
46
|
+
* An addr-spec sitting inside free text, together with the whitespace around it. Sticky
|
|
47
|
+
* on purpose: it is run at the one offset _looseAddressStart picks rather than being let
|
|
48
|
+
* loose to search, see there.
|
|
49
|
+
*/
|
|
50
|
+
const LOOSE_TEXT_ADDR = /\s*\b[^@\s]+@[^\s]+\b\s*/y;
|
|
51
|
+
/**
|
|
52
|
+
* The characters JS `\s` matches, which the scan below has to agree with to land on the
|
|
53
|
+
* same match the pattern would.
|
|
54
|
+
*/
|
|
55
|
+
function _isSpaceCode(code) {
|
|
56
|
+
return code === 32 || code >= 9 && code <= 13 || code === 160 || code === 5760 || code >= 8192 && code <= 8202 || code === 8232 || code === 8233 || code === 8239 || code === 8287 || code === 12288 || code === 65279;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The characters JS `\w` matches without the unicode flag, the set the `\b` in
|
|
60
|
+
* LOOSE_TEXT_ADDR is read against. charCodeAt off either end of the string gives NaN,
|
|
61
|
+
* which compares false throughout, so out of range reads as the non-word the pattern
|
|
62
|
+
* treats them as.
|
|
63
|
+
*/
|
|
64
|
+
function _isWordCode(code) {
|
|
65
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether `\b` holds at an offset
|
|
69
|
+
*/
|
|
70
|
+
function _isBoundary(text, at) {
|
|
71
|
+
return _isWordCode(text.charCodeAt(at - 1)) !== _isWordCode(text.charCodeAt(at));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Offset of the first '@' in `text` between `from` and `to`, or -1 when the range holds none.
|
|
75
|
+
*
|
|
76
|
+
* indexOf would scan on to the end of the value, and the value here is a whole header. The
|
|
77
|
+
* walk below steps one whitespace delimited run at a time and only ever uses a '@' that sits
|
|
78
|
+
* inside the run it is on, so an unbounded probe rescans everything behind that run once per
|
|
79
|
+
* run and grows with the square of the header: 400KB of free text carrying no '@' took a
|
|
80
|
+
* quarter of a second. GHSA-v53p-9fqp-m79j took the pattern search out of this walk and left
|
|
81
|
+
* the probe unbounded behind it.
|
|
82
|
+
*
|
|
83
|
+
* @param text Text to look in
|
|
84
|
+
* @param from Offset to start at
|
|
85
|
+
* @param to Offset to stop before
|
|
86
|
+
* @return Offset of the '@', or -1
|
|
87
|
+
*/
|
|
88
|
+
function _indexOfAt(text, from, to) {
|
|
89
|
+
for (let i = from; i < to; i++) if (text.charCodeAt(i) === 64) return i;
|
|
90
|
+
return -1;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Finds the offset LOOSE_TEXT_ADDR matches at, or -1 when it does not match at all.
|
|
94
|
+
*
|
|
95
|
+
* Letting the pattern search for itself is quadratic: '[^@\s]+' is retried from every
|
|
96
|
+
* offset and rescans the run to the next '@' each time, so 140KB of header holding no
|
|
97
|
+
* usable '@' blocks the event loop for about ten seconds (GHSA-v53p-9fqp-m79j). The search is also unnecessary.
|
|
98
|
+
* '[^@\s]+' crosses neither whitespace nor a '@', so a match can only begin at the head of
|
|
99
|
+
* a whitespace delimited run or just past a '@' inside one, and '[^\s]+\b' gives characters
|
|
100
|
+
* back until it lands on a boundary, so the only end it can take in that run is the last
|
|
101
|
+
* boundary in it. Both are found in one pass, and the pattern is then run at that single
|
|
102
|
+
* offset.
|
|
103
|
+
*
|
|
104
|
+
* @param text Free text to look in
|
|
105
|
+
* @return Offset to match at, or -1
|
|
106
|
+
*/
|
|
107
|
+
function _looseAddressStart(text) {
|
|
108
|
+
const len = text.length;
|
|
109
|
+
let pos = 0;
|
|
110
|
+
while (pos < len) {
|
|
111
|
+
while (pos < len && _isSpaceCode(text.charCodeAt(pos))) pos++;
|
|
112
|
+
if (pos >= len) break;
|
|
113
|
+
const runStart = pos;
|
|
114
|
+
let runEnd = pos;
|
|
115
|
+
while (runEnd < len && !_isSpaceCode(text.charCodeAt(runEnd))) runEnd++;
|
|
116
|
+
let at = _indexOfAt(text, runStart, runEnd);
|
|
117
|
+
if (at >= 0) {
|
|
118
|
+
let lastBoundary = -1;
|
|
119
|
+
for (let k = runEnd; k > runStart; k--) if (_isBoundary(text, k)) {
|
|
120
|
+
lastBoundary = k;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
let atomStart = runStart;
|
|
124
|
+
while (lastBoundary >= 0 && at >= 0) {
|
|
125
|
+
if (at > atomStart && runEnd > at + 1 && lastBoundary > at + 1) {
|
|
126
|
+
for (let start = atomStart; start < at; start++) if (_isBoundary(text, start)) {
|
|
127
|
+
if (start > runStart) return start;
|
|
128
|
+
let padded = runStart;
|
|
129
|
+
while (padded > 0 && _isSpaceCode(text.charCodeAt(padded - 1))) padded--;
|
|
130
|
+
return padded;
|
|
131
|
+
}
|
|
142
132
|
}
|
|
133
|
+
atomStart = at + 1;
|
|
134
|
+
at = _indexOfAt(text, atomStart, runEnd);
|
|
143
135
|
}
|
|
144
136
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
pos = runEnd;
|
|
138
|
+
}
|
|
139
|
+
return -1;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Recovers the addr-spec from an angle-addr that came back holding unquoted whitespace.
|
|
143
|
+
*
|
|
144
|
+
* A malformed header can put more than a mailbox between the angle brackets, most often
|
|
145
|
+
* because the generator wrote the recipient twice: '<user@example.com user@example.com>'
|
|
146
|
+
* or '<example.com user@example.com>'. Whitespace is not addr-spec, so the whole run can
|
|
147
|
+
* never be a mailbox anyone could deliver to, and passing it on as the address loses the
|
|
148
|
+
* recipient that is sitting right there in the header.
|
|
149
|
+
*
|
|
150
|
+
* The run that still reads as an addr-spec is kept and whatever is left over becomes
|
|
151
|
+
* display text rather than being dropped. Candidates are read strictly first and then
|
|
152
|
+
* under the looser grammar, the same two tiers the unquoted-text branch below applies to
|
|
153
|
+
* the same problem, so that '<a@b@c.com junk>' and a bare 'a@b@c.com junk' agree on the
|
|
154
|
+
* recipient. When several runs qualify the first wins, which is what that branch's looser
|
|
155
|
+
* tier does within a token.
|
|
156
|
+
*
|
|
157
|
+
* A quoted local part is left alone: RFC 5321 allows whitespace inside it, so
|
|
158
|
+
* '<"user name"@example.com>' is well formed and means exactly what it says.
|
|
159
|
+
*
|
|
160
|
+
* @param data Collected address parts, mutated in place
|
|
161
|
+
*/
|
|
162
|
+
function _recoverAddrSpec(data) {
|
|
163
|
+
if (!HAS_WHITESPACE.test(data.address)) return;
|
|
164
|
+
let address;
|
|
165
|
+
let rest;
|
|
166
|
+
const quoted = data.address.match(QUOTED_LOCAL_ADDR);
|
|
167
|
+
if (quoted) {
|
|
168
|
+
if (!quoted[2]) return;
|
|
169
|
+
address = quoted[1];
|
|
170
|
+
rest = [quoted[2]];
|
|
171
|
+
} else {
|
|
172
|
+
if (data.address.indexOf("\"") >= 0) return;
|
|
173
|
+
const parts = data.address.split(/\s+/);
|
|
174
|
+
let addrIndex = parts.findIndex((part) => ADDR_SPEC.test(part));
|
|
175
|
+
if (addrIndex < 0) addrIndex = parts.findIndex((part) => LOOSE_ADDR_SPEC.test(part));
|
|
176
|
+
if (addrIndex < 0) return;
|
|
177
|
+
address = parts.splice(addrIndex, 1)[0];
|
|
178
|
+
rest = parts;
|
|
179
|
+
}
|
|
180
|
+
data.address = address;
|
|
181
|
+
data.text = [data.text].concat(rest).filter((part) => part).join(" ");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Takes the RFC 5322 comments out of the contents of an angle-addr.
|
|
185
|
+
*
|
|
186
|
+
* The tokenizer tracks a single open operator, so once '<' is open a '(' is plain text and
|
|
187
|
+
* a comment inside the brackets reached the address verbatim: 'Name <user@example.com(x)evil.com>'
|
|
188
|
+
* put 'user@example.com(x)evil.com' into the envelope, a value that is no mailbox and that a
|
|
189
|
+
* receiver stripping the comment reads as 'user@example.comevil.com' (GHSA-g73g-hqqh-jr95).
|
|
190
|
+
* A comment is folding whitespace, so it is read as a space that splits two atoms and as
|
|
191
|
+
* nothing next to an '@', the same rule the token walk applies outside the brackets. Quoted
|
|
192
|
+
* strings and domain literals are copied through untouched, a '(' in there is not a comment.
|
|
193
|
+
*
|
|
194
|
+
* @param address Contents of the angle brackets
|
|
195
|
+
* @return The address with the comments removed, and the text of the comments
|
|
196
|
+
*/
|
|
197
|
+
function _stripAddressComments(address) {
|
|
198
|
+
const comments = [];
|
|
199
|
+
let result = "";
|
|
200
|
+
let comment = "";
|
|
201
|
+
let depth = 0;
|
|
202
|
+
let closer = "";
|
|
203
|
+
let lastChar = "";
|
|
204
|
+
for (let i = 0, len = address.length; i < len; i++) {
|
|
205
|
+
const chr = address.charAt(i);
|
|
206
|
+
if (depth) {
|
|
207
|
+
if (chr === "\\" && i < len - 1) comment += address.charAt(++i);
|
|
208
|
+
else if (chr === "(") {
|
|
209
|
+
depth++;
|
|
210
|
+
comment += chr;
|
|
211
|
+
} else if (chr === ")" && !--depth) {
|
|
212
|
+
comments.push(comment.trim());
|
|
213
|
+
comment = "";
|
|
214
|
+
if (lastChar !== "@" && address.charAt(i + 1) !== "@") {
|
|
215
|
+
result += " ";
|
|
216
|
+
lastChar = " ";
|
|
217
|
+
}
|
|
218
|
+
} else comment += chr;
|
|
219
|
+
continue;
|
|
148
220
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
else groupMembers.push(member);
|
|
155
|
-
});
|
|
156
|
-
addresses.push({
|
|
157
|
-
name: data.text || "",
|
|
158
|
-
group: groupMembers
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
if (!data.address.length && data.text.length) {
|
|
162
|
-
for (let i = data.text.length - 1; i >= 0; i--) if (!data.textWasQuoted[i] && ADDR_SPEC.test(data.text[i])) {
|
|
163
|
-
data.address = data.text.splice(i, 1);
|
|
164
|
-
data.textWasQuoted.splice(i, 1);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
if (!data.address.length) {
|
|
168
|
-
let extracted = false;
|
|
169
|
-
for (let i = data.text.length - 1; i >= 0; i--) if (!data.textWasQuoted[i]) {
|
|
170
|
-
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, (match) => {
|
|
171
|
-
if (!extracted) {
|
|
172
|
-
data.address = [match.trim()];
|
|
173
|
-
extracted = true;
|
|
174
|
-
return " ";
|
|
175
|
-
}
|
|
176
|
-
return match;
|
|
177
|
-
}).trim();
|
|
178
|
-
if (extracted) break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
221
|
+
if (closer) {
|
|
222
|
+
if (chr === "\\" && closer === "\"" && i < len - 1) {
|
|
223
|
+
result += chr + address.charAt(++i);
|
|
224
|
+
lastChar = address.charAt(i);
|
|
225
|
+
continue;
|
|
181
226
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
227
|
+
if (chr === closer) closer = "";
|
|
228
|
+
} else if (chr === "\"") closer = "\"";
|
|
229
|
+
else if (chr === "[") closer = "]";
|
|
230
|
+
else if (chr === "(") {
|
|
231
|
+
depth = 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
result += chr;
|
|
235
|
+
lastChar = chr;
|
|
236
|
+
}
|
|
237
|
+
if (depth) comments.push(comment.trim());
|
|
238
|
+
return {
|
|
239
|
+
address: result.trim(),
|
|
240
|
+
comments: comments.filter((text) => text)
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Converts tokens for a single address into an address object
|
|
245
|
+
*
|
|
246
|
+
* @param tokens Tokens object
|
|
247
|
+
* @param depth Current recursion depth for nested group protection
|
|
248
|
+
* @return Address object
|
|
249
|
+
*/
|
|
250
|
+
function _handleAddress(tokens, depth) {
|
|
251
|
+
let isGroup = false;
|
|
252
|
+
let state = "text";
|
|
253
|
+
const addresses = [];
|
|
254
|
+
const data = {
|
|
255
|
+
address: [],
|
|
256
|
+
comment: [],
|
|
257
|
+
group: [],
|
|
258
|
+
text: [],
|
|
259
|
+
textWasQuoted: []
|
|
260
|
+
};
|
|
261
|
+
let insideQuotes = false;
|
|
262
|
+
const lastChars = {
|
|
263
|
+
address: "",
|
|
264
|
+
comment: "",
|
|
265
|
+
group: "",
|
|
266
|
+
text: ""
|
|
267
|
+
};
|
|
268
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
269
|
+
const token = tokens[i];
|
|
270
|
+
const prevToken = i ? tokens[i - 1] : null;
|
|
271
|
+
if (token.type === "operator") switch (token.value) {
|
|
272
|
+
case "<":
|
|
273
|
+
state = "address";
|
|
274
|
+
insideQuotes = false;
|
|
275
|
+
break;
|
|
276
|
+
case "(":
|
|
277
|
+
state = "comment";
|
|
278
|
+
insideQuotes = false;
|
|
279
|
+
break;
|
|
280
|
+
case ":":
|
|
281
|
+
state = "group";
|
|
282
|
+
isGroup = true;
|
|
283
|
+
insideQuotes = false;
|
|
284
|
+
break;
|
|
285
|
+
case "\"":
|
|
286
|
+
insideQuotes = !insideQuotes;
|
|
287
|
+
state = "text";
|
|
288
|
+
break;
|
|
289
|
+
default:
|
|
290
|
+
state = "text";
|
|
291
|
+
insideQuotes = false;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
else if (token.value) {
|
|
295
|
+
const prevPrevToken = i > 1 ? tokens[i - 2] : null;
|
|
296
|
+
const opensAfterEmptyQuotedString = prevToken?.type === "operator" && prevToken.value === "\"" && !!prevToken.noBreak && prevPrevToken?.type === "operator" && prevPrevToken.value === "\"";
|
|
297
|
+
if (state === "address") token.value = token.value.replace(/^[^<]*<\s*/, "");
|
|
298
|
+
const parts = data[state];
|
|
299
|
+
if (prevToken && prevToken.noBreak && parts.length && (prevToken.value !== ")" || lastChars[state] === "@" || token.value.charAt(0) === "@")) {
|
|
300
|
+
data[state][data[state].length - 1] += token.value;
|
|
301
|
+
if (token.value) lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
302
|
+
if (state === "text" && insideQuotes) data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
|
303
|
+
} else {
|
|
304
|
+
data[state].push(token.value);
|
|
305
|
+
lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
306
|
+
if (state === "text") data.textWasQuoted.push(insideQuotes || opensAfterEmptyQuotedString);
|
|
185
307
|
}
|
|
186
|
-
if (data.address.length > 1) data.text = data.text.concat(data.address.splice(1));
|
|
187
|
-
const addressFromQuotedText = !data.address.length && data.textWasQuoted.some((wasQuoted) => wasQuoted);
|
|
188
|
-
data.text = data.text.join(" ");
|
|
189
|
-
data.address = data.address.join(" ");
|
|
190
|
-
_recoverAddrSpec(data);
|
|
191
|
-
const address = {
|
|
192
|
-
address: data.address || data.text || "",
|
|
193
|
-
name: data.text || data.address || ""
|
|
194
|
-
};
|
|
195
|
-
if (address.address === address.name) if (/@/.test(address.address || "")) address.name = "";
|
|
196
|
-
else address.address = "";
|
|
197
|
-
if (addressFromQuotedText && address.address) address.address = _quoteLocalPart(address.address);
|
|
198
|
-
addresses.push(address);
|
|
199
308
|
}
|
|
200
|
-
return addresses;
|
|
201
309
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
310
|
+
if (!data.text.length && data.comment.length) {
|
|
311
|
+
data.text = data.comment;
|
|
312
|
+
data.comment = [];
|
|
313
|
+
}
|
|
314
|
+
if (isGroup) {
|
|
315
|
+
data.text = data.text.join(" ");
|
|
316
|
+
let groupMembers = [];
|
|
317
|
+
if (data.group.length) _parseAddressList(data.group.join(","), depth + 1).forEach((member) => {
|
|
318
|
+
if (member.group) groupMembers = groupMembers.concat(member.group);
|
|
319
|
+
else groupMembers.push(member);
|
|
320
|
+
});
|
|
321
|
+
addresses.push({
|
|
322
|
+
name: data.text || "",
|
|
323
|
+
group: groupMembers
|
|
324
|
+
});
|
|
325
|
+
} else {
|
|
326
|
+
const addressComments = [];
|
|
327
|
+
const addressParts = [];
|
|
328
|
+
for (const part of data.address) {
|
|
329
|
+
if (part.indexOf("(") < 0) {
|
|
330
|
+
addressParts.push(part);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const stripped = _stripAddressComments(part);
|
|
334
|
+
for (const comment of stripped.comments) addressComments.push(comment);
|
|
335
|
+
if (stripped.address) addressParts.push(stripped.address);
|
|
336
|
+
}
|
|
337
|
+
data.address = addressParts;
|
|
338
|
+
if (!data.address.length && data.text.length) {
|
|
339
|
+
for (let i = data.text.length - 1; i >= 0; i--) if (!data.textWasQuoted[i] && ADDR_SPEC.test(data.text[i])) {
|
|
340
|
+
data.address = data.text.splice(i, 1);
|
|
341
|
+
data.textWasQuoted.splice(i, 1);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
if (!data.address.length) {
|
|
345
|
+
let extracted = false;
|
|
346
|
+
for (let i = data.text.length - 1; i >= 0; i--) if (!data.textWasQuoted[i]) {
|
|
347
|
+
const part = data.text[i];
|
|
348
|
+
let remainder = part;
|
|
349
|
+
const at = _looseAddressStart(part);
|
|
350
|
+
if (at >= 0) {
|
|
351
|
+
LOOSE_TEXT_ADDR.lastIndex = at;
|
|
352
|
+
const match = LOOSE_TEXT_ADDR.exec(part);
|
|
353
|
+
if (match) {
|
|
354
|
+
data.address = [match[0].trim()];
|
|
355
|
+
extracted = true;
|
|
356
|
+
remainder = part.slice(0, at) + " " + part.slice(at + match[0].length);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
data.text[i] = remainder.trim();
|
|
360
|
+
if (extracted) break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
228
363
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
* @return {Array} An array of operator|text tokens
|
|
233
|
-
*/
|
|
234
|
-
tokenize() {
|
|
235
|
-
const list = [];
|
|
236
|
-
for (let i = 0, len = this.str.length; i < len; i++) {
|
|
237
|
-
const chr = this.str.charAt(i);
|
|
238
|
-
const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
|
239
|
-
this.checkChar(chr, nextChr);
|
|
240
|
-
}
|
|
241
|
-
this.list.forEach((node) => {
|
|
242
|
-
node.value = (node.value || "").toString().trim();
|
|
243
|
-
if (node.value) list.push(node);
|
|
244
|
-
});
|
|
245
|
-
return list;
|
|
364
|
+
if (!data.text.length && data.comment.length) {
|
|
365
|
+
data.text = data.comment;
|
|
366
|
+
data.comment = [];
|
|
246
367
|
}
|
|
368
|
+
if (data.address.length > 1) data.text = data.text.concat(data.address.splice(1));
|
|
369
|
+
const addressFromQuotedText = !data.address.length && data.textWasQuoted.some((wasQuoted) => wasQuoted);
|
|
370
|
+
data.text = data.text.join(" ");
|
|
371
|
+
data.address = data.address.join(" ");
|
|
372
|
+
if (addressFromQuotedText && data.text.indexOf("@") >= 0) {
|
|
373
|
+
data.address = _quoteLocalPart(data.text);
|
|
374
|
+
data.text = "";
|
|
375
|
+
}
|
|
376
|
+
_recoverAddrSpec(data);
|
|
377
|
+
if (!data.text && addressComments.length) data.text = addressComments.join(" ");
|
|
378
|
+
const address = {
|
|
379
|
+
address: data.address || data.text || "",
|
|
380
|
+
name: data.text || data.address || ""
|
|
381
|
+
};
|
|
382
|
+
if (address.address === address.name) if (/@/.test(address.address || "")) address.name = "";
|
|
383
|
+
else address.address = "";
|
|
384
|
+
addresses.push(address);
|
|
385
|
+
}
|
|
386
|
+
return addresses;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Creates a Tokenizer object for tokenizing address field strings
|
|
390
|
+
*
|
|
391
|
+
* @constructor
|
|
392
|
+
* @param str Address field string
|
|
393
|
+
*/
|
|
394
|
+
var Tokenizer = class {
|
|
395
|
+
constructor(str) {
|
|
396
|
+
this.str = (str || "").toString();
|
|
397
|
+
this.operatorCurrent = "";
|
|
398
|
+
this.operatorExpecting = "";
|
|
399
|
+
this.node = null;
|
|
400
|
+
this.escaped = false;
|
|
401
|
+
this.inDomainLiteral = false;
|
|
402
|
+
this.afterAt = false;
|
|
403
|
+
this.list = [];
|
|
247
404
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* @param {String} chr Character from the address field
|
|
405
|
+
* Operator tokens and which tokens are expected to end the sequence
|
|
251
406
|
*/
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
};
|
|
262
|
-
if (nextChr && ![
|
|
263
|
-
" ",
|
|
264
|
-
" ",
|
|
265
|
-
"\r",
|
|
266
|
-
"\n",
|
|
267
|
-
",",
|
|
268
|
-
";"
|
|
269
|
-
].includes(nextChr)) this.node.noBreak = true;
|
|
270
|
-
this.list.push(this.node);
|
|
271
|
-
this.node = null;
|
|
272
|
-
this.operatorExpecting = "";
|
|
273
|
-
this.escaped = false;
|
|
274
|
-
return;
|
|
275
|
-
} else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
|
|
276
|
-
this.node = {
|
|
277
|
-
type: "operator",
|
|
278
|
-
value: chr
|
|
279
|
-
};
|
|
280
|
-
this.list.push(this.node);
|
|
281
|
-
this.node = null;
|
|
282
|
-
this.operatorExpecting = this.operators[chr];
|
|
283
|
-
this.escaped = false;
|
|
284
|
-
return;
|
|
285
|
-
} else if (["\"", "'"].includes(this.operatorExpecting) && chr === "\\") {
|
|
286
|
-
this.escaped = true;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (!this.node) {
|
|
290
|
-
this.node = {
|
|
291
|
-
type: "text",
|
|
292
|
-
value: ""
|
|
293
|
-
};
|
|
294
|
-
this.list.push(this.node);
|
|
295
|
-
}
|
|
296
|
-
if (chr === "\n") chr = " ";
|
|
297
|
-
if (chr.charCodeAt(0) >= 33 || [" ", " "].includes(chr)) this.node.value += chr;
|
|
298
|
-
this.escaped = false;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
407
|
+
this.operators = {
|
|
408
|
+
"\"": "\"",
|
|
409
|
+
"(": ")",
|
|
410
|
+
"<": ">",
|
|
411
|
+
",": "",
|
|
412
|
+
":": ";",
|
|
413
|
+
";": ""
|
|
414
|
+
};
|
|
415
|
+
}
|
|
301
416
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
417
|
+
* Tokenizes the original input string
|
|
418
|
+
*
|
|
419
|
+
* @return An array of operator|text tokens
|
|
305
420
|
*/
|
|
306
|
-
|
|
421
|
+
tokenize() {
|
|
422
|
+
const list = [];
|
|
423
|
+
for (let i = 0, len = this.str.length; i < len; i++) {
|
|
424
|
+
const chr = this.str.charAt(i);
|
|
425
|
+
const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
|
426
|
+
this.checkChar(chr, nextChr);
|
|
427
|
+
}
|
|
428
|
+
this.list.forEach((node) => {
|
|
429
|
+
node.value = (node.value || "").toString().trim();
|
|
430
|
+
if (node.value) list.push(node);
|
|
431
|
+
});
|
|
432
|
+
return list;
|
|
433
|
+
}
|
|
307
434
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* Example:
|
|
311
|
-
*
|
|
312
|
-
* 'Name <address@domain>'
|
|
435
|
+
* Checks if a character is an operator or text and acts accordingly
|
|
313
436
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* [{name: 'Name', address: 'address@domain'}]
|
|
317
|
-
*
|
|
318
|
-
* @param {String} str Address field
|
|
319
|
-
* @param {Object} options Optional options object
|
|
320
|
-
* @param {Number} options._depth Internal recursion depth counter (do not set manually)
|
|
321
|
-
* @return {Array} An array of address objects
|
|
437
|
+
* @param chr Character from the address field
|
|
438
|
+
* @param nextChr Character following chr, null at the end of the field
|
|
322
439
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (token.type === "operator" && (token.value === "," || token.value === ";")) {
|
|
333
|
-
if (address.length) addresses.push(address);
|
|
334
|
-
address = [];
|
|
335
|
-
} else address.push(token);
|
|
336
|
-
});
|
|
337
|
-
if (address.length) addresses.push(address);
|
|
338
|
-
addresses.forEach((addr) => {
|
|
339
|
-
const handled = _handleAddress(addr, depth);
|
|
340
|
-
for (let i = 0; i < handled.length; i++) parsedAddresses.push(handled[i]);
|
|
341
|
-
});
|
|
342
|
-
const mergedAddresses = [];
|
|
343
|
-
for (let i = parsedAddresses.length - 1; i >= 0; i--) {
|
|
344
|
-
const current = parsedAddresses[i];
|
|
345
|
-
const next = mergedAddresses.length ? mergedAddresses[mergedAddresses.length - 1] : null;
|
|
346
|
-
if (next && current.address === "" && current.name && !current.group && next.address && next.name) next.name = current.name + ", " + next.name;
|
|
347
|
-
else mergedAddresses.push(current);
|
|
348
|
-
}
|
|
349
|
-
mergedAddresses.reverse();
|
|
350
|
-
parsedAddresses = mergedAddresses;
|
|
351
|
-
if (options.flatten) {
|
|
352
|
-
const flatAddresses = [];
|
|
353
|
-
const walkAddressList = (list) => {
|
|
354
|
-
list.forEach((entry) => {
|
|
355
|
-
if (entry.group) return walkAddressList(entry.group);
|
|
356
|
-
flatAddresses.push(entry);
|
|
357
|
-
});
|
|
440
|
+
checkChar(chr, nextChr) {
|
|
441
|
+
if (!this.escaped && !this.operatorExpecting) {
|
|
442
|
+
if (!this.inDomainLiteral && chr === "[" && this.afterAt) this.inDomainLiteral = true;
|
|
443
|
+
else if (this.inDomainLiteral && (chr === "]" || chr === "," || chr === ";")) this.inDomainLiteral = false;
|
|
444
|
+
}
|
|
445
|
+
if (this.escaped) {} else if (chr === this.operatorExpecting) {
|
|
446
|
+
this.node = {
|
|
447
|
+
type: "operator",
|
|
448
|
+
value: chr
|
|
358
449
|
};
|
|
359
|
-
|
|
360
|
-
|
|
450
|
+
if (nextChr && ![
|
|
451
|
+
" ",
|
|
452
|
+
" ",
|
|
453
|
+
"\r",
|
|
454
|
+
"\n",
|
|
455
|
+
",",
|
|
456
|
+
";"
|
|
457
|
+
].includes(nextChr)) this.node.noBreak = true;
|
|
458
|
+
this.list.push(this.node);
|
|
459
|
+
this.node = null;
|
|
460
|
+
if (chr !== ")") this.afterAt = false;
|
|
461
|
+
this.operatorExpecting = "";
|
|
462
|
+
this.escaped = false;
|
|
463
|
+
return;
|
|
464
|
+
} else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ":") && chr in this.operators) {
|
|
465
|
+
this.node = {
|
|
466
|
+
type: "operator",
|
|
467
|
+
value: chr
|
|
468
|
+
};
|
|
469
|
+
this.list.push(this.node);
|
|
470
|
+
this.node = null;
|
|
471
|
+
if (chr !== "(") this.afterAt = false;
|
|
472
|
+
this.operatorExpecting = this.operators[chr];
|
|
473
|
+
this.escaped = false;
|
|
474
|
+
return;
|
|
475
|
+
} else if (["\"", "'"].includes(this.operatorExpecting) && chr === "\\") {
|
|
476
|
+
this.escaped = true;
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (!this.node) {
|
|
480
|
+
this.node = {
|
|
481
|
+
type: "text",
|
|
482
|
+
value: ""
|
|
483
|
+
};
|
|
484
|
+
this.list.push(this.node);
|
|
485
|
+
}
|
|
486
|
+
if (chr === "\n") chr = " ";
|
|
487
|
+
if (chr.charCodeAt(0) >= 33 || [" ", " "].includes(chr)) {
|
|
488
|
+
this.node.value += chr;
|
|
489
|
+
if (!this.operatorExpecting && chr !== " " && chr !== " ") this.afterAt = chr === "@";
|
|
361
490
|
}
|
|
362
|
-
|
|
491
|
+
this.escaped = false;
|
|
363
492
|
}
|
|
364
|
-
|
|
365
|
-
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Maximum recursion depth for parsing nested groups.
|
|
496
|
+
* RFC 5322 doesn't allow nested groups, so this is a safeguard against
|
|
497
|
+
* malicious input that could cause stack overflow.
|
|
498
|
+
*/
|
|
499
|
+
const MAX_NESTED_GROUP_DEPTH = 50;
|
|
500
|
+
/**
|
|
501
|
+
* Parses an address list, recursing into the groups it holds.
|
|
502
|
+
*
|
|
503
|
+
* The depth is threaded through the calls rather than carried in an options object, so a
|
|
504
|
+
* caller supplied one can not seed it and lift the recursion limit: `{ _depth: -1e9 }`
|
|
505
|
+
* bought a billion levels of nesting and turned the guard below into the stack overflow it
|
|
506
|
+
* is there to prevent.
|
|
507
|
+
*
|
|
508
|
+
* @param str Address field
|
|
509
|
+
* @param depth Current recursion depth for nested group protection
|
|
510
|
+
* @return An array of address objects
|
|
511
|
+
*/
|
|
512
|
+
function _parseAddressList(str, depth) {
|
|
513
|
+
if (depth > MAX_NESTED_GROUP_DEPTH) return [];
|
|
514
|
+
const tokens = new Tokenizer(str).tokenize();
|
|
515
|
+
const addresses = [];
|
|
516
|
+
let address = [];
|
|
517
|
+
const parsedAddresses = [];
|
|
518
|
+
tokens.forEach((token) => {
|
|
519
|
+
if (token.type === "operator" && (token.value === "," || token.value === ";")) {
|
|
520
|
+
if (address.length) addresses.push(address);
|
|
521
|
+
address = [];
|
|
522
|
+
} else address.push(token);
|
|
523
|
+
});
|
|
524
|
+
if (address.length) addresses.push(address);
|
|
525
|
+
addresses.forEach((addr) => {
|
|
526
|
+
const handled = _handleAddress(addr, depth);
|
|
527
|
+
for (let i = 0; i < handled.length; i++) parsedAddresses.push(handled[i]);
|
|
528
|
+
});
|
|
529
|
+
const mergedAddresses = [];
|
|
530
|
+
for (let i = parsedAddresses.length - 1; i >= 0; i--) {
|
|
531
|
+
const current = parsedAddresses[i];
|
|
532
|
+
const next = mergedAddresses.length ? mergedAddresses[mergedAddresses.length - 1] : null;
|
|
533
|
+
if (next && current.address === "" && current.name && !current.group && next.address && next.name) next.name = current.name + ", " + next.name;
|
|
534
|
+
else mergedAddresses.push(current);
|
|
535
|
+
}
|
|
536
|
+
mergedAddresses.reverse();
|
|
537
|
+
return mergedAddresses;
|
|
538
|
+
}
|
|
539
|
+
function addressparser(str, options) {
|
|
540
|
+
const parsedAddresses = _parseAddressList(str, 0);
|
|
541
|
+
if (options?.flatten) {
|
|
542
|
+
const flatAddresses = [];
|
|
543
|
+
const walkAddressList = (list) => {
|
|
544
|
+
list.forEach((entry) => {
|
|
545
|
+
if (entry.group) return walkAddressList(entry.group);
|
|
546
|
+
flatAddresses.push(entry);
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
walkAddressList(parsedAddresses);
|
|
550
|
+
return flatAddresses;
|
|
551
|
+
}
|
|
552
|
+
return parsedAddresses;
|
|
553
|
+
}
|
|
366
554
|
//#endregion
|
|
367
555
|
//#region ../node_modules/.pnpm/validator@13.15.35/node_modules/validator/lib/util/assertString.js
|
|
368
556
|
var require_assertString = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -557,8 +745,8 @@ var require_isIP = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
557
745
|
module.exports.default = exports.default;
|
|
558
746
|
}));
|
|
559
747
|
//#endregion
|
|
560
|
-
//#region ../
|
|
561
|
-
var
|
|
748
|
+
//#region ../sdk-node/dist/address-parser-kzXOIiuh.js
|
|
749
|
+
var import_isEmail = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
562
750
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
563
751
|
exports.default = isEmail;
|
|
564
752
|
var _assertString = _interopRequireDefault(require_assertString());
|
|
@@ -657,11 +845,7 @@ var require_isEmail = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
657
845
|
}
|
|
658
846
|
module.exports = exports.default;
|
|
659
847
|
module.exports.default = exports.default;
|
|
660
|
-
}));
|
|
661
|
-
//#endregion
|
|
662
|
-
//#region ../sdk-node/dist/address-parser-kzXOIiuh.js
|
|
663
|
-
var import_addressparser = /* @__PURE__ */ __toESM(require_addressparser(), 1);
|
|
664
|
-
var import_isEmail = /* @__PURE__ */ __toESM(require_isEmail(), 1);
|
|
848
|
+
})))(), 1);
|
|
665
849
|
const MAX_HEADER_LENGTH = 998;
|
|
666
850
|
const IS_EMAIL_OPTIONS = {
|
|
667
851
|
allow_ip_domain: true,
|
|
@@ -711,7 +895,7 @@ function parseFromHeader(header) {
|
|
|
711
895
|
ok: false,
|
|
712
896
|
reason: "too_long"
|
|
713
897
|
};
|
|
714
|
-
const parsed = (
|
|
898
|
+
const parsed = addressparser(trimmed);
|
|
715
899
|
if (parsed.length > 1) return {
|
|
716
900
|
ok: false,
|
|
717
901
|
reason: "multiple_addresses"
|