sently 0.4.6 → 0.5.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/CHANGELOG.md +81 -0
- package/README.md +180 -43
- package/dist/adapters/bun.js +2 -183
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.js +2 -77
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.js +2 -72
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.js +2 -180
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.js +2 -13
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/chunk-32dj0v9g.js +5 -0
- package/dist/{chunk-794hc3m4.js.map → chunk-32dj0v9g.js.map} +2 -2
- package/dist/chunk-cvmk9h1r.js +4 -0
- package/dist/chunk-cvmk9h1r.js.map +10 -0
- package/dist/chunk-j5m37k1c.js +4 -0
- package/dist/{chunk-ym3zzv8b.js.map → chunk-j5m37k1c.js.map} +2 -2
- package/dist/chunk-kdqxhybf.js +4 -0
- package/dist/chunk-kdqxhybf.js.map +10 -0
- package/dist/chunk-m2njnxre.js +13 -0
- package/dist/{chunk-tymfm441.js.map → chunk-m2njnxre.js.map} +2 -2
- package/dist/chunk-nhec1319.js +5 -0
- package/dist/chunk-nhec1319.js.map +10 -0
- package/dist/chunk-nj3wmg69.js +3 -0
- package/dist/{chunk-7fqv71z1.js.map → chunk-nj3wmg69.js.map} +2 -2
- package/dist/chunk-ntqmn6pe.js +4 -0
- package/dist/chunk-ntqmn6pe.js.map +10 -0
- package/dist/chunk-pjzsvsm6.js +4 -0
- package/dist/chunk-pjzsvsm6.js.map +11 -0
- package/dist/chunk-rhade81v.js +4 -0
- package/dist/{chunk-v0bahtg2.js.map → chunk-rhade81v.js.map} +1 -1
- package/dist/chunk-zwm5bfa4.js +4 -0
- package/dist/chunk-zwm5bfa4.js.map +10 -0
- package/dist/core/idempotency-key.d.ts +6 -0
- package/dist/core/rate-limiter.d.ts +23 -0
- package/dist/core/smtp.js +2 -31
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +42 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.js +2 -180
- package/dist/detect.js.map +4 -5
- package/dist/dkim.d.ts +21 -0
- package/dist/dkim.js +9 -0
- package/dist/dkim.js.map +10 -0
- package/dist/idempotency.d.ts +46 -0
- package/dist/idempotency.js +3 -0
- package/dist/idempotency.js.map +10 -0
- package/dist/index.d.ts +106 -16
- package/dist/index.js +115 -14
- package/dist/mailer.d.ts +17 -0
- package/dist/mailer.js +3 -0
- package/dist/mailer.js.map +9 -0
- package/dist/plugins/react.d.ts +35 -0
- package/dist/plugins/react.js +3 -0
- package/dist/plugins/react.js.map +10 -0
- package/dist/plugins/template.js +2 -28
- package/dist/plugins/template.js.map +2 -2
- package/dist/pool/pool.d.ts +1 -23
- package/dist/pool/pool.js +2 -16
- package/dist/pool/pool.js.map +5 -3
- package/dist/react.d.ts +20 -0
- package/dist/react.js +3 -0
- package/dist/react.js.map +9 -0
- package/dist/transports/brevo.js +2 -115
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.js +2 -119
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.js +2 -113
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -72
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.d.ts +16 -0
- package/dist/transports/resend.js +2 -109
- package/dist/transports/resend.js.map +3 -3
- package/dist/transports/retry.d.ts +2 -0
- package/dist/transports/retry.js +2 -78
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.d.ts +8 -0
- package/dist/transports/sendgrid.js +2 -132
- package/dist/transports/sendgrid.js.map +3 -3
- package/dist/transports/ses.js +5 -251
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -26
- package/dist/transports/smtp.js.map +1 -1
- package/dist/webhooks/brevo.d.ts +3 -0
- package/dist/webhooks/mailgun.d.ts +13 -0
- package/dist/webhooks/postmark.d.ts +3 -0
- package/dist/webhooks/resend.d.ts +12 -0
- package/dist/webhooks/sendgrid.d.ts +3 -0
- package/dist/webhooks/ses.d.ts +6 -0
- package/dist/webhooks/timing-safe-equal.d.ts +9 -0
- package/dist/webhooks/types.d.ts +32 -0
- package/dist/webhooks.d.ts +26 -0
- package/dist/webhooks.js +3 -0
- package/dist/webhooks.js.map +9 -0
- package/package.json +45 -6
- package/dist/chunk-794hc3m4.js +0 -105
- package/dist/chunk-7fqv71z1.js +0 -251
- package/dist/chunk-f4c9ttmr.js +0 -154
- package/dist/chunk-f4c9ttmr.js.map +0 -11
- package/dist/chunk-mp5c9bfd.js +0 -270
- package/dist/chunk-mp5c9bfd.js.map +0 -11
- package/dist/chunk-tymfm441.js +0 -405
- package/dist/chunk-v0bahtg2.js +0 -6
- package/dist/chunk-x3szga4k.js +0 -367
- package/dist/chunk-x3szga4k.js.map +0 -11
- package/dist/chunk-ym3zzv8b.js +0 -74
package/dist/chunk-794hc3m4.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// src/core/base64.ts
|
|
2
|
-
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
-
var BASE64_LINE_LENGTH = 76;
|
|
4
|
-
var encoder = new TextEncoder;
|
|
5
|
-
var decoder = new TextDecoder;
|
|
6
|
-
function encodeBase64(data) {
|
|
7
|
-
const bytes = typeof data === "string" ? encoder.encode(data) : data;
|
|
8
|
-
let result = "";
|
|
9
|
-
let i = 0;
|
|
10
|
-
while (i < bytes.length) {
|
|
11
|
-
const b0 = bytes[i] ?? 0;
|
|
12
|
-
const b1 = bytes[i + 1];
|
|
13
|
-
const b2 = bytes[i + 2];
|
|
14
|
-
if (b1 === undefined) {
|
|
15
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
16
|
-
result += BASE64_CHARS[(b0 & 3) << 4];
|
|
17
|
-
result += "==";
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
if (b2 === undefined) {
|
|
21
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
22
|
-
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
23
|
-
result += BASE64_CHARS[(b1 & 15) << 2];
|
|
24
|
-
result += "=";
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
28
|
-
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
29
|
-
result += BASE64_CHARS[(b1 & 15) << 2 | b2 >> 6];
|
|
30
|
-
result += BASE64_CHARS[b2 & 63];
|
|
31
|
-
i += 3;
|
|
32
|
-
}
|
|
33
|
-
return wrapBase64Lines(result);
|
|
34
|
-
}
|
|
35
|
-
function decodeBase64(data) {
|
|
36
|
-
const cleaned = data.replace(/\s/g, "");
|
|
37
|
-
const len = cleaned.length;
|
|
38
|
-
if (len === 0) {
|
|
39
|
-
return new Uint8Array(0);
|
|
40
|
-
}
|
|
41
|
-
if (len % 4 !== 0) {
|
|
42
|
-
throw new Error("Invalid base64 string length");
|
|
43
|
-
}
|
|
44
|
-
const padding = cleaned.endsWith("==") ? 2 : cleaned.endsWith("=") ? 1 : 0;
|
|
45
|
-
const outputLen = len * 3 / 4 - padding;
|
|
46
|
-
const output = new Uint8Array(outputLen);
|
|
47
|
-
let outIndex = 0;
|
|
48
|
-
for (let i = 0;i < len; i += 4) {
|
|
49
|
-
const c0 = base64CharToValue(cleaned[i] ?? "=");
|
|
50
|
-
const c1 = base64CharToValue(cleaned[i + 1] ?? "=");
|
|
51
|
-
const c2 = base64CharToValue(cleaned[i + 2] ?? "=");
|
|
52
|
-
const c3 = base64CharToValue(cleaned[i + 3] ?? "=");
|
|
53
|
-
const triple = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
54
|
-
if (outIndex < outputLen) {
|
|
55
|
-
output[outIndex++] = triple >> 16 & 255;
|
|
56
|
-
}
|
|
57
|
-
if (outIndex < outputLen) {
|
|
58
|
-
output[outIndex++] = triple >> 8 & 255;
|
|
59
|
-
}
|
|
60
|
-
if (outIndex < outputLen) {
|
|
61
|
-
output[outIndex++] = triple & 255;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return output;
|
|
65
|
-
}
|
|
66
|
-
function encodeHeader(value) {
|
|
67
|
-
if (!needsEncoding(value)) {
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
const encoded = encodeBase64(value).replace(/\r\n/g, "");
|
|
71
|
-
return `=?UTF-8?B?${encoded}?=`;
|
|
72
|
-
}
|
|
73
|
-
function needsEncoding(text) {
|
|
74
|
-
for (let i = 0;i < text.length; i++) {
|
|
75
|
-
if (text.charCodeAt(i) > 127) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
function base64CharToValue(char) {
|
|
82
|
-
if (char === "=") {
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
const index = BASE64_CHARS.indexOf(char);
|
|
86
|
-
if (index === -1) {
|
|
87
|
-
throw new Error(`Invalid base64 character: ${char}`);
|
|
88
|
-
}
|
|
89
|
-
return index;
|
|
90
|
-
}
|
|
91
|
-
function wrapBase64Lines(base64) {
|
|
92
|
-
const lines = [];
|
|
93
|
-
for (let i = 0;i < base64.length; i += BASE64_LINE_LENGTH) {
|
|
94
|
-
lines.push(base64.slice(i, i + BASE64_LINE_LENGTH));
|
|
95
|
-
}
|
|
96
|
-
return lines.join(`\r
|
|
97
|
-
`);
|
|
98
|
-
}
|
|
99
|
-
function encodeUtf8(text) {
|
|
100
|
-
return encoder.encode(text);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export { encodeBase64, decodeBase64, encodeHeader, encodeUtf8 };
|
|
104
|
-
|
|
105
|
-
//# debugId=93C9FABDDF825C4864756E2164756E21
|
package/dist/chunk-7fqv71z1.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
OAuth2Client
|
|
3
|
-
} from "./chunk-ym3zzv8b.js";
|
|
4
|
-
import {
|
|
5
|
-
buildMIME
|
|
6
|
-
} from "./chunk-x3szga4k.js";
|
|
7
|
-
import {
|
|
8
|
-
SMTPError,
|
|
9
|
-
accumulateResponse,
|
|
10
|
-
assertResponse,
|
|
11
|
-
computeCRAMMD5,
|
|
12
|
-
encodeAuthLoginPass,
|
|
13
|
-
encodeAuthLoginUser,
|
|
14
|
-
encodeCommand,
|
|
15
|
-
encodeLine,
|
|
16
|
-
parseEHLO,
|
|
17
|
-
parseResponse,
|
|
18
|
-
selectAuthMethod
|
|
19
|
-
} from "./chunk-tymfm441.js";
|
|
20
|
-
import {
|
|
21
|
-
resolveAttachments
|
|
22
|
-
} from "./chunk-f4c9ttmr.js";
|
|
23
|
-
import {
|
|
24
|
-
__require
|
|
25
|
-
} from "./chunk-v0bahtg2.js";
|
|
26
|
-
|
|
27
|
-
// src/transports/smtp.ts
|
|
28
|
-
class SMTPTransport {
|
|
29
|
-
config;
|
|
30
|
-
adapter = null;
|
|
31
|
-
constructor(config) {
|
|
32
|
-
this.config = resolveSMTPConfig(config);
|
|
33
|
-
}
|
|
34
|
-
async send(options) {
|
|
35
|
-
const resolvedOptions = {
|
|
36
|
-
...options,
|
|
37
|
-
attachments: await resolveAttachments(options.attachments)
|
|
38
|
-
};
|
|
39
|
-
const mime = await buildMIME(resolvedOptions, this.config.dkim);
|
|
40
|
-
const adapter = await this.getAdapter();
|
|
41
|
-
const host = this.config.direct ? await resolveMX(mime.envelope.from.split("@")[1] ?? this.config.host) : this.config.host;
|
|
42
|
-
await adapter.connect(host, this.config.port);
|
|
43
|
-
this.adapter = adapter;
|
|
44
|
-
try {
|
|
45
|
-
await openSMTPSession(adapter, this.config);
|
|
46
|
-
return await deliverSMTPMessage(adapter, mime);
|
|
47
|
-
} finally {
|
|
48
|
-
await closeSMTPSession(adapter);
|
|
49
|
-
this.adapter = null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async verify() {
|
|
53
|
-
try {
|
|
54
|
-
const adapter = await this.getAdapter();
|
|
55
|
-
await adapter.connect(this.config.host, this.config.port);
|
|
56
|
-
try {
|
|
57
|
-
await openSMTPSession(adapter, this.config);
|
|
58
|
-
return { ok: true, provider: "smtp" };
|
|
59
|
-
} finally {
|
|
60
|
-
await closeSMTPSession(adapter);
|
|
61
|
-
}
|
|
62
|
-
} catch (err) {
|
|
63
|
-
return {
|
|
64
|
-
ok: false,
|
|
65
|
-
provider: "smtp",
|
|
66
|
-
message: err instanceof Error ? err.message : String(err)
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async close() {
|
|
71
|
-
if (this.adapter) {
|
|
72
|
-
await this.adapter.close();
|
|
73
|
-
this.adapter = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async getAdapter() {
|
|
77
|
-
if (!this.config.adapter) {
|
|
78
|
-
throw new SMTPError("No socket adapter configured", 0, "CONNECT", "");
|
|
79
|
-
}
|
|
80
|
-
return this.config.adapter;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function resolveSMTPConfig(config) {
|
|
84
|
-
const secure = config.secure ?? false;
|
|
85
|
-
return {
|
|
86
|
-
host: config.host,
|
|
87
|
-
port: config.port ?? (secure ? 465 : 587),
|
|
88
|
-
secure,
|
|
89
|
-
...config.auth !== undefined ? { auth: config.auth } : {},
|
|
90
|
-
...config.requireTLS !== undefined ? { requireTLS: config.requireTLS } : {},
|
|
91
|
-
...config.dkim !== undefined ? { dkim: config.dkim } : {},
|
|
92
|
-
...config.tls !== undefined ? { tls: config.tls } : {},
|
|
93
|
-
...config.connectionTimeout !== undefined ? { connectionTimeout: config.connectionTimeout } : {},
|
|
94
|
-
...config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {},
|
|
95
|
-
...config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {},
|
|
96
|
-
...config.direct !== undefined ? { direct: config.direct } : {},
|
|
97
|
-
...config.adapter !== undefined ? { adapter: config.adapter } : {}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
async function openSMTPSession(adapter, config) {
|
|
101
|
-
const greeting = await readSMTPResponse(adapter);
|
|
102
|
-
assertResponse(greeting, [220], "greeting");
|
|
103
|
-
let capabilities = await ehlo(adapter, config.host);
|
|
104
|
-
const supportsStartTls = capabilities.some((cap) => cap.toUpperCase() === "STARTTLS");
|
|
105
|
-
if (!config.secure && !adapter.secure && supportsStartTls) {
|
|
106
|
-
await sendRaw(adapter, encodeCommand({ type: "STARTTLS" }));
|
|
107
|
-
const starttlsResp = await readSMTPResponse(adapter);
|
|
108
|
-
assertResponse(starttlsResp, [220], "STARTTLS");
|
|
109
|
-
await adapter.startTLS(config.tls);
|
|
110
|
-
capabilities = await ehlo(adapter, config.host);
|
|
111
|
-
}
|
|
112
|
-
if (config.auth) {
|
|
113
|
-
const tlsRequired = config.requireTLS ?? true;
|
|
114
|
-
const encrypted = adapter.secure || config.secure;
|
|
115
|
-
if (tlsRequired && !encrypted) {
|
|
116
|
-
throw new SMTPError("Refusing to authenticate over unencrypted connection. " + "Set requireTLS: false to disable this check (not recommended).", 0, "AUTH", "");
|
|
117
|
-
}
|
|
118
|
-
await authenticate(adapter, config.auth, capabilities);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function deliverSMTPMessage(adapter, mime) {
|
|
122
|
-
await sendCommand(adapter, { type: "MAIL_FROM", address: mime.envelope.from });
|
|
123
|
-
const mailResp = await readSMTPResponse(adapter);
|
|
124
|
-
assertResponse(mailResp, [250], "MAIL FROM");
|
|
125
|
-
const accepted = [];
|
|
126
|
-
const rejected = [];
|
|
127
|
-
for (const recipient of mime.envelope.to) {
|
|
128
|
-
await sendRaw(adapter, encodeCommand({ type: "RCPT_TO", address: recipient }));
|
|
129
|
-
const rcptResp = await readSMTPResponse(adapter);
|
|
130
|
-
if (rcptResp.isSuccess) {
|
|
131
|
-
accepted.push(recipient);
|
|
132
|
-
} else {
|
|
133
|
-
rejected.push(recipient);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
await sendCommand(adapter, { type: "DATA" });
|
|
137
|
-
const dataResp = await readSMTPResponse(adapter);
|
|
138
|
-
assertResponse(dataResp, [354], "DATA");
|
|
139
|
-
let finalResp;
|
|
140
|
-
try {
|
|
141
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
142
|
-
finalResp = await readSMTPResponse(adapter);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
145
|
-
finalResp = await readSMTPResponse(adapter);
|
|
146
|
-
if (finalResp.isError) {
|
|
147
|
-
throw err;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
assertResponse(finalResp, [250], "DATA end");
|
|
151
|
-
return {
|
|
152
|
-
messageId: mime.messageId,
|
|
153
|
-
accepted,
|
|
154
|
-
rejected,
|
|
155
|
-
response: finalResp.message,
|
|
156
|
-
envelope: mime.envelope
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
async function closeSMTPSession(adapter) {
|
|
160
|
-
try {
|
|
161
|
-
await sendCommand(adapter, { type: "QUIT" });
|
|
162
|
-
await readSMTPResponse(adapter);
|
|
163
|
-
} catch {} finally {
|
|
164
|
-
await adapter.close();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function ehlo(adapter, host) {
|
|
168
|
-
await sendCommand(adapter, { type: "EHLO", domain: host });
|
|
169
|
-
const response = await readSMTPResponse(adapter);
|
|
170
|
-
assertResponse(response, [250], "EHLO");
|
|
171
|
-
return parseEHLO(response);
|
|
172
|
-
}
|
|
173
|
-
async function authenticate(adapter, auth, capabilities) {
|
|
174
|
-
if (auth.type === "OAUTH2" && auth.oauth2) {
|
|
175
|
-
const client = new OAuth2Client(auth.oauth2);
|
|
176
|
-
const xoauth2 = await client.buildXOAUTH2();
|
|
177
|
-
await sendCommand(adapter, { type: "AUTH_XOAUTH2", xoauth2String: xoauth2 });
|
|
178
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
179
|
-
if (resp2.code === 334) {
|
|
180
|
-
await sendRaw(adapter, encodeLine(""));
|
|
181
|
-
resp2 = await readSMTPResponse(adapter);
|
|
182
|
-
}
|
|
183
|
-
assertResponse(resp2, [235], "AUTH XOAUTH2");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const method = auth.type ?? selectAuthMethod(capabilities);
|
|
187
|
-
if (method === "CRAM-MD5") {
|
|
188
|
-
const pass2 = requirePassword(auth, "CRAM-MD5");
|
|
189
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_INIT" });
|
|
190
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
191
|
-
assertResponse(resp2, [334], "AUTH CRAM-MD5");
|
|
192
|
-
const challenge = resp2.message.trim();
|
|
193
|
-
const response = await computeCRAMMD5(challenge, auth.user, pass2);
|
|
194
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_RESPONSE", response });
|
|
195
|
-
resp2 = await readSMTPResponse(adapter);
|
|
196
|
-
assertResponse(resp2, [235], "AUTH CRAM-MD5 response");
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (method === "PLAIN") {
|
|
200
|
-
const pass2 = requirePassword(auth, "PLAIN");
|
|
201
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_PLAIN", user: auth.user, pass: pass2 }));
|
|
202
|
-
const resp2 = await readSMTPResponse(adapter);
|
|
203
|
-
assertResponse(resp2, [235], "AUTH PLAIN");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const pass = requirePassword(auth, "LOGIN");
|
|
207
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_LOGIN", user: auth.user, pass }));
|
|
208
|
-
let resp = await readSMTPResponse(adapter);
|
|
209
|
-
assertResponse(resp, [334], "AUTH LOGIN");
|
|
210
|
-
await sendRaw(adapter, encodeAuthLoginUser(auth.user));
|
|
211
|
-
resp = await readSMTPResponse(adapter);
|
|
212
|
-
assertResponse(resp, [334], "AUTH LOGIN user");
|
|
213
|
-
await sendRaw(adapter, encodeAuthLoginPass(pass));
|
|
214
|
-
resp = await readSMTPResponse(adapter);
|
|
215
|
-
assertResponse(resp, [235], "AUTH LOGIN pass");
|
|
216
|
-
}
|
|
217
|
-
function requirePassword(auth, method) {
|
|
218
|
-
if (!auth.pass) {
|
|
219
|
-
throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, "");
|
|
220
|
-
}
|
|
221
|
-
return auth.pass;
|
|
222
|
-
}
|
|
223
|
-
async function sendCommand(adapter, command) {
|
|
224
|
-
await sendRaw(adapter, encodeCommand(command));
|
|
225
|
-
}
|
|
226
|
-
async function sendRaw(adapter, data) {
|
|
227
|
-
await adapter.write(data);
|
|
228
|
-
}
|
|
229
|
-
async function readSMTPResponse(adapter) {
|
|
230
|
-
const chunks = [];
|
|
231
|
-
for await (const chunk of adapter.read()) {
|
|
232
|
-
chunks.push(chunk);
|
|
233
|
-
const complete = accumulateResponse(chunks);
|
|
234
|
-
if (complete) {
|
|
235
|
-
return parseResponse(complete);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
throw new SMTPError("Connection closed while reading SMTP response", 0, "READ", "");
|
|
239
|
-
}
|
|
240
|
-
async function resolveMX(domain) {
|
|
241
|
-
const dns = await import("node:dns/promises");
|
|
242
|
-
const records = await dns.resolveMx(domain);
|
|
243
|
-
if (records.length === 0) {
|
|
244
|
-
throw new SMTPError(`No MX records for ${domain}`, 0, "MX", "");
|
|
245
|
-
}
|
|
246
|
-
records.sort((a, b) => a.priority - b.priority);
|
|
247
|
-
return records[0]?.exchange ?? domain;
|
|
248
|
-
}
|
|
249
|
-
export { SMTPTransport, resolveSMTPConfig, openSMTPSession, deliverSMTPMessage, closeSMTPSession, readSMTPResponse };
|
|
250
|
-
|
|
251
|
-
//# debugId=736A9106E3164FC464756E2164756E21
|
package/dist/chunk-f4c9ttmr.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
encodeHeader
|
|
3
|
-
} from "./chunk-794hc3m4.js";
|
|
4
|
-
import {
|
|
5
|
-
__require
|
|
6
|
-
} from "./chunk-v0bahtg2.js";
|
|
7
|
-
|
|
8
|
-
// src/core/address.ts
|
|
9
|
-
function findForbiddenChar(value) {
|
|
10
|
-
for (let i = 0;i < value.length; i++) {
|
|
11
|
-
const code = value.charCodeAt(i);
|
|
12
|
-
if (code <= 31 || code === 127 || code === 8232 || code === 8233) {
|
|
13
|
-
return code;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return -1;
|
|
17
|
-
}
|
|
18
|
-
function assertSafeAddress(value, label = "address") {
|
|
19
|
-
const code = findForbiddenChar(value);
|
|
20
|
-
if (code !== -1) {
|
|
21
|
-
const hex = code.toString(16).padStart(2, "0");
|
|
22
|
-
throw new Error(`Email ${label} contains a forbidden control character (0x${hex}). ` + "CR, LF, NUL, and other control characters are not allowed.");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function parseAddresses(input) {
|
|
26
|
-
if (Array.isArray(input)) {
|
|
27
|
-
return input.flatMap((item) => parseAddresses(item));
|
|
28
|
-
}
|
|
29
|
-
if (typeof input === "object") {
|
|
30
|
-
assertSafeAddress(input.address, "address");
|
|
31
|
-
if (input.name !== undefined) {
|
|
32
|
-
assertSafeAddress(input.name, "display name");
|
|
33
|
-
}
|
|
34
|
-
return [{ ...input }];
|
|
35
|
-
}
|
|
36
|
-
assertSafeAddress(input, "address");
|
|
37
|
-
const trimmed = input.trim();
|
|
38
|
-
if (!trimmed) {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
return splitAddressList(trimmed).map(parseSingleAddress);
|
|
42
|
-
}
|
|
43
|
-
function toMIMEHeader(address) {
|
|
44
|
-
assertSafeAddress(address.address, "address");
|
|
45
|
-
if (address.name) {
|
|
46
|
-
assertSafeAddress(address.name, "display name");
|
|
47
|
-
const name = encodeHeader(address.name);
|
|
48
|
-
return `${name} <${address.address}>`;
|
|
49
|
-
}
|
|
50
|
-
return address.address;
|
|
51
|
-
}
|
|
52
|
-
function extractEmails(input) {
|
|
53
|
-
return parseAddresses(input).map((addr) => addr.address);
|
|
54
|
-
}
|
|
55
|
-
function isValidEmail(email) {
|
|
56
|
-
if (findForbiddenChar(email) !== -1)
|
|
57
|
-
return false;
|
|
58
|
-
return /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(email);
|
|
59
|
-
}
|
|
60
|
-
function splitAddressList(input) {
|
|
61
|
-
const parts = [];
|
|
62
|
-
let current = "";
|
|
63
|
-
let inQuotes = false;
|
|
64
|
-
let inAngle = false;
|
|
65
|
-
for (let i = 0;i < input.length; i++) {
|
|
66
|
-
const char = input[i] ?? "";
|
|
67
|
-
if (char === '"' && input[i - 1] !== "\\") {
|
|
68
|
-
inQuotes = !inQuotes;
|
|
69
|
-
current += char;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (char === "<" && !inQuotes) {
|
|
73
|
-
inAngle = true;
|
|
74
|
-
current += char;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (char === ">" && !inQuotes) {
|
|
78
|
-
inAngle = false;
|
|
79
|
-
current += char;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (char === "," && !inQuotes && !inAngle) {
|
|
83
|
-
if (current.trim()) {
|
|
84
|
-
parts.push(current.trim());
|
|
85
|
-
}
|
|
86
|
-
current = "";
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
current += char;
|
|
90
|
-
}
|
|
91
|
-
if (current.trim()) {
|
|
92
|
-
parts.push(current.trim());
|
|
93
|
-
}
|
|
94
|
-
return parts;
|
|
95
|
-
}
|
|
96
|
-
function parseSingleAddress(input) {
|
|
97
|
-
const trimmed = input.trim();
|
|
98
|
-
const angleMatch = trimmed.match(/^(?:"([^"]*)"|([^<]*?))\s*<([^>]+)>$/);
|
|
99
|
-
if (angleMatch) {
|
|
100
|
-
const name = (angleMatch[1] ?? angleMatch[2] ?? "").trim();
|
|
101
|
-
const address = (angleMatch[3] ?? "").trim();
|
|
102
|
-
if (name) {
|
|
103
|
-
return { name, address };
|
|
104
|
-
}
|
|
105
|
-
return { address };
|
|
106
|
-
}
|
|
107
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
108
|
-
return { address: trimmed.slice(1, -1) };
|
|
109
|
-
}
|
|
110
|
-
return { address: trimmed };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/transports/resolve-attachments.ts
|
|
114
|
-
async function resolveAttachments(attachments, options) {
|
|
115
|
-
const list = attachments ?? [];
|
|
116
|
-
const resolved = [];
|
|
117
|
-
for (const attachment of list) {
|
|
118
|
-
if (attachment.content instanceof Uint8Array) {
|
|
119
|
-
resolved.push(attachment);
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
if (attachment.path) {
|
|
123
|
-
let fs;
|
|
124
|
-
try {
|
|
125
|
-
fs = await import("node:fs/promises");
|
|
126
|
-
} catch {
|
|
127
|
-
throw new Error("attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead");
|
|
128
|
-
}
|
|
129
|
-
if (options?.basePath) {
|
|
130
|
-
const { resolve, sep } = await import("node:path");
|
|
131
|
-
const resolvedPath = resolve(attachment.path);
|
|
132
|
-
const resolvedBase = resolve(options.basePath);
|
|
133
|
-
const isWithin = resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + sep);
|
|
134
|
-
if (!isWithin) {
|
|
135
|
-
throw new Error(`[sently] Attachment path "${resolvedPath}" escapes basePath "${resolvedBase}". Use absolute paths within the allowed directory.`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const data = await fs.readFile(attachment.path);
|
|
139
|
-
const { path: _path, ...rest } = attachment;
|
|
140
|
-
resolved.push({ ...rest, content: new Uint8Array(data) });
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (typeof attachment.content === "string") {
|
|
144
|
-
resolved.push(attachment);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
resolved.push(attachment);
|
|
148
|
-
}
|
|
149
|
-
return resolved;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export { assertSafeAddress, parseAddresses, toMIMEHeader, extractEmails, isValidEmail, resolveAttachments };
|
|
153
|
-
|
|
154
|
-
//# debugId=419CD2BE8A029CA164756E2164756E21
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/core/address.ts", "../src/transports/resolve-attachments.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"// src/core/address.ts\nimport { encodeHeader } from \"./base64.js\";\nimport type { Address, AddressInput } from \"./types.js\";\n\n/**\n * Find the first forbidden control character in a string.\n *\n * Forbidden characters must never appear in an email address or display name,\n * because each enables a distinct attack:\n * - CR (0x0D) / LF (0x0A): email header injection and SMTP command injection\n * - NUL (0x00): C-string truncation / parser confusion in downstream agents\n * - other C0 controls (0x01–0x1F), DEL (0x7F): header/parser confusion\n * - U+2028 / U+2029: line separators that some parsers treat as newlines\n *\n * @returns The char code of the first forbidden character, or -1 if none.\n */\nfunction findForbiddenChar(value: string): number {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code <= 0x1f || code === 0x7f || code === 0x2028 || code === 0x2029) {\n return code;\n }\n }\n return -1;\n}\n\n/**\n * Assert that a raw address or display-name value contains no forbidden\n * control characters. Throws immediately (fail closed) — the library never\n * attempts to strip or rewrite hostile input into an accepted value.\n *\n * The check is intentionally performed on the RAW input, before any trimming\n * or normalization, so hostile values are rejected rather than repaired.\n *\n * @param value - The raw, untransformed string to validate.\n * @param label - Field label used in the error message (e.g. \"address\").\n * @throws {Error} If the value contains any forbidden control character.\n */\nexport function assertSafeAddress(value: string, label = \"address\"): void {\n const code = findForbiddenChar(value);\n if (code !== -1) {\n const hex = code.toString(16).padStart(2, \"0\");\n throw new Error(\n `Email ${label} contains a forbidden control character (0x${hex}). ` +\n \"CR, LF, NUL, and other control characters are not allowed.\",\n );\n }\n}\n\n/**\n * Normalize any AddressInput form into Address[].\n *\n * Every address and display name is validated against control-character\n * injection before any transformation. This is the single chokepoint shared\n * by all transports and address fields (From, To, Cc, Bcc, Reply-To), so the\n * protection is uniform and secure by default.\n *\n * @throws {Error} If any address or name contains a forbidden control character.\n */\nexport function parseAddresses(input: AddressInput): Address[] {\n if (Array.isArray(input)) {\n return input.flatMap((item) => parseAddresses(item));\n }\n\n if (typeof input === \"object\") {\n assertSafeAddress(input.address, \"address\");\n if (input.name !== undefined) {\n assertSafeAddress(input.name, \"display name\");\n }\n return [{ ...input }];\n }\n\n // Validate the raw input string before splitting or trimming so injected\n // newlines cannot hide inside a multi-address list.\n assertSafeAddress(input, \"address\");\n\n const trimmed = input.trim();\n if (!trimmed) {\n return [];\n }\n\n return splitAddressList(trimmed).map(parseSingleAddress);\n}\n\n/**\n * Format an Address for SMTP envelope commands (MAIL FROM / RCPT TO).\n */\nexport function toEnvelope(address: Address): string {\n return address.address;\n}\n\n/**\n * Format an Address for use in a MIME header (From, To, CC, etc.).\n *\n * Re-validates the address and name at render time so a header can never be\n * emitted with an embedded control character, even if the {@link Address} was\n * constructed without going through {@link parseAddresses}.\n *\n * @throws {Error} If the address or name contains a forbidden control character.\n */\nexport function toMIMEHeader(address: Address): string {\n assertSafeAddress(address.address, \"address\");\n if (address.name) {\n assertSafeAddress(address.name, \"display name\");\n const name = encodeHeader(address.name);\n return `${name} <${address.address}>`;\n }\n return address.address;\n}\n\n/**\n * Extract plain email strings from any AddressInput.\n */\nexport function extractEmails(input: AddressInput): string[] {\n return parseAddresses(input).map((addr) => addr.address);\n}\n\n/**\n * Basic email format validation (format only, no DNS lookup).\n *\n * Rejects any control character (including CR, LF, tab, and NUL) before\n * applying the structural check, so a \"valid\" result is always safe to place\n * into a header or SMTP command.\n */\nexport function isValidEmail(email: string): boolean {\n if (findForbiddenChar(email) !== -1) return false;\n return /^[^\\s@<>]+@[^\\s@<>]+\\.[^\\s@<>]+$/.test(email);\n}\n\nfunction splitAddressList(input: string): string[] {\n const parts: string[] = [];\n let current = \"\";\n let inQuotes = false;\n let inAngle = false;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i] ?? \"\";\n if (char === '\"' && input[i - 1] !== \"\\\\\") {\n inQuotes = !inQuotes;\n current += char;\n continue;\n }\n if (char === \"<\" && !inQuotes) {\n inAngle = true;\n current += char;\n continue;\n }\n if (char === \">\" && !inQuotes) {\n inAngle = false;\n current += char;\n continue;\n }\n if (char === \",\" && !inQuotes && !inAngle) {\n if (current.trim()) {\n parts.push(current.trim());\n }\n current = \"\";\n continue;\n }\n current += char;\n }\n\n if (current.trim()) {\n parts.push(current.trim());\n }\n\n return parts;\n}\n\nfunction parseSingleAddress(input: string): Address {\n const trimmed = input.trim();\n\n const angleMatch = trimmed.match(/^(?:\"([^\"]*)\"|([^<]*?))\\s*<([^>]+)>$/);\n if (angleMatch) {\n const name = (angleMatch[1] ?? angleMatch[2] ?? \"\").trim();\n const address = (angleMatch[3] ?? \"\").trim();\n if (name) {\n return { name, address };\n }\n return { address };\n }\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n return { address: trimmed.slice(1, -1) };\n }\n\n return { address: trimmed };\n}\n",
|
|
6
|
-
"import type { Attachment } from \"../core/types.js\";\n\n/** Options for {@link resolveAttachments}. */\nexport interface ResolveAttachmentsOptions {\n /**\n * If set, attachment paths must resolve within this directory.\n * Prevents path traversal via `..` segments and sibling-directory\n * prefix matches. Opt-in only.\n *\n * Note: this check uses `node:path` `resolve()`, which does NOT\n * dereference symlinks. A symlink located inside `basePath` that\n * points outside of it will pass this check. If symlink traversal is\n * a concern, resolve paths with `fs.realpath()` before passing them in.\n */\n basePath?: string;\n}\n\n/**\n * Resolve attachment.path to in-memory Uint8Array content.\n * @throws When attachment.path is used on runtimes without node:fs/promises\n */\nexport async function resolveAttachments(\n attachments: Attachment[] | undefined,\n options?: ResolveAttachmentsOptions,\n): Promise<Attachment[]> {\n const list = attachments ?? [];\n const resolved: Attachment[] = [];\n\n for (const attachment of list) {\n if (attachment.content instanceof Uint8Array) {\n resolved.push(attachment);\n continue;\n }\n\n if (attachment.path) {\n let fs: typeof import(\"node:fs/promises\");\n try {\n fs = await import(\"node:fs/promises\");\n } catch {\n throw new Error(\n \"attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead\",\n );\n }\n\n if (options?.basePath) {\n const { resolve, sep } = await import(\"node:path\");\n const resolvedPath = resolve(attachment.path);\n const resolvedBase = resolve(options.basePath);\n // startsWith alone is vulnerable: \"/var/data-secret\" passes \"/var/data\".\n // Require an exact match or a trailing path separator.\n const isWithin =\n resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + sep);\n if (!isWithin) {\n throw new Error(\n `[sently] Attachment path \"${resolvedPath}\" escapes basePath \"${resolvedBase}\". ` +\n \"Use absolute paths within the allowed directory.\",\n );\n }\n }\n\n const data = await fs.readFile(attachment.path);\n const { path: _path, ...rest } = attachment;\n resolved.push({ ...rest, content: new Uint8Array(data) });\n continue;\n }\n\n if (typeof attachment.content === \"string\") {\n resolved.push(attachment);\n continue;\n }\n\n resolved.push(attachment);\n }\n\n return resolved;\n}\n"
|
|
7
|
-
],
|
|
8
|
-
"mappings": "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",
|
|
9
|
-
"debugId": "419CD2BE8A029CA164756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|