@emailcheck/email-validator-js 4.0.0-beta.1 → 4.0.0-beta.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/README.md +35 -0
- package/dist/cli/index.js +103 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +130 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +130 -5
- package/dist/index.js.map +1 -1
- package/dist/refine-reason.d.ts +1 -0
- package/dist/types.d.ts +25 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -197,6 +197,7 @@ exports.SMTPStep = void 0;
|
|
|
197
197
|
SMTPStep2["greeting"] = "GREETING";
|
|
198
198
|
SMTPStep2["ehlo"] = "EHLO";
|
|
199
199
|
SMTPStep2["helo"] = "HELO";
|
|
200
|
+
SMTPStep2["startTls"] = "STARTTLS";
|
|
200
201
|
SMTPStep2["mailFrom"] = "MAIL_FROM";
|
|
201
202
|
SMTPStep2["rcptTo"] = "RCPT_TO";
|
|
202
203
|
})(exports.SMTPStep || (exports.SMTPStep = {}));
|
|
@@ -1015,7 +1016,7 @@ function defaultProbeLocal() {
|
|
|
1015
1016
|
return `${node_crypto.randomBytes(8).toString("hex")}-noexist`;
|
|
1016
1017
|
}
|
|
1017
1018
|
async function verifyMailboxSMTP(params) {
|
|
1018
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1019
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1019
1020
|
const { local, domain, options = {} } = params;
|
|
1020
1021
|
const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
|
|
1021
1022
|
const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
|
|
@@ -1047,7 +1048,8 @@ async function verifyMailboxSMTP(params) {
|
|
|
1047
1048
|
sequence,
|
|
1048
1049
|
log,
|
|
1049
1050
|
catchAllProbeLocal: options.catchAllProbeLocal,
|
|
1050
|
-
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
|
|
1051
|
+
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto",
|
|
1052
|
+
startTls: (_j = options.startTls) !== null && _j !== void 0 ? _j : "auto"
|
|
1051
1053
|
};
|
|
1052
1054
|
const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
|
|
1053
1055
|
const verdictKey = `${primaryMx}:${local}@${domain}`;
|
|
@@ -1181,9 +1183,12 @@ class SMTPProbeConnection {
|
|
|
1181
1183
|
this.buffer = "";
|
|
1182
1184
|
this.resolved = false;
|
|
1183
1185
|
this.currentStepIndex = 0;
|
|
1186
|
+
this.tlsUpgrading = false;
|
|
1187
|
+
this.postUpgradeReEhlo = false;
|
|
1184
1188
|
this.transcript = [];
|
|
1185
1189
|
this.commands = [];
|
|
1186
1190
|
this.supportsPipelining = false;
|
|
1191
|
+
this.supportsStartTls = false;
|
|
1187
1192
|
this.dualPhase = "idle";
|
|
1188
1193
|
this.realOutcome = "pending";
|
|
1189
1194
|
this.probeOutcome = "pending";
|
|
@@ -1201,7 +1206,7 @@ class SMTPProbeConnection {
|
|
|
1201
1206
|
this.processLine(line);
|
|
1202
1207
|
}
|
|
1203
1208
|
};
|
|
1204
|
-
const defaultSteps = [exports.SMTPStep.greeting, exports.SMTPStep.ehlo, exports.SMTPStep.mailFrom, exports.SMTPStep.rcptTo];
|
|
1209
|
+
const defaultSteps = [exports.SMTPStep.greeting, exports.SMTPStep.ehlo, exports.SMTPStep.startTls, exports.SMTPStep.mailFrom, exports.SMTPStep.rcptTo];
|
|
1205
1210
|
this.steps = [...(_b = (_a = p.sequence) === null || _a === void 0 ? void 0 : _a.steps) !== null && _b !== void 0 ? _b : defaultSteps];
|
|
1206
1211
|
this.isTLS = PORT_TLS[p.port] === true;
|
|
1207
1212
|
const servername = isIPAddress(p.mxHost) ? void 0 : p.mxHost;
|
|
@@ -1221,7 +1226,8 @@ class SMTPProbeConnection {
|
|
|
1221
1226
|
this.connect();
|
|
1222
1227
|
this.armConnectionTimer();
|
|
1223
1228
|
} catch (error) {
|
|
1224
|
-
this.
|
|
1229
|
+
this.p.log(`connect threw: ${error instanceof Error ? error.message : "unknown"}`);
|
|
1230
|
+
this.finish(null, "connection_error");
|
|
1225
1231
|
}
|
|
1226
1232
|
});
|
|
1227
1233
|
}
|
|
@@ -1280,6 +1286,9 @@ class SMTPProbeConnection {
|
|
|
1280
1286
|
case exports.SMTPStep.helo:
|
|
1281
1287
|
this.send(`HELO ${this.p.hostname}`);
|
|
1282
1288
|
return;
|
|
1289
|
+
case exports.SMTPStep.startTls:
|
|
1290
|
+
this.executeStartTls();
|
|
1291
|
+
return;
|
|
1283
1292
|
case exports.SMTPStep.mailFrom: {
|
|
1284
1293
|
const from = (_b = (_a = this.p.sequence) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : `<${this.p.local}@${this.p.domain}>`;
|
|
1285
1294
|
this.send(`MAIL FROM:${from}`);
|
|
@@ -1290,6 +1299,73 @@ class SMTPProbeConnection {
|
|
|
1290
1299
|
return;
|
|
1291
1300
|
}
|
|
1292
1301
|
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Conditional STARTTLS upgrade. Skipped (advances to next step) when:
|
|
1304
|
+
* - already TLS (implicit-TLS port like 465 or already-upgraded)
|
|
1305
|
+
* - `startTls === 'never'`
|
|
1306
|
+
* - `startTls === 'auto'` AND the MX didn't advertise STARTTLS in EHLO
|
|
1307
|
+
*
|
|
1308
|
+
* Sends `STARTTLS` and waits for 220 when:
|
|
1309
|
+
* - `startTls === 'force'` (regardless of advertisement)
|
|
1310
|
+
* - `startTls === 'auto'` AND the MX advertised it
|
|
1311
|
+
*
|
|
1312
|
+
* On 220, `tls.connect()` wraps the existing socket. After the handshake
|
|
1313
|
+
* we re-EHLO (mandatory per RFC 3207 §4.2 — pre-TLS state must be
|
|
1314
|
+
* discarded) before continuing to MAIL FROM.
|
|
1315
|
+
*/
|
|
1316
|
+
executeStartTls() {
|
|
1317
|
+
const mode = this.p.startTls;
|
|
1318
|
+
const wantsUpgrade = !this.isTLS && mode !== "never" && (mode === "force" || mode === "auto" && this.supportsStartTls);
|
|
1319
|
+
if (!wantsUpgrade) {
|
|
1320
|
+
this.nextStep();
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
this.send("STARTTLS");
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Wrap the plaintext socket with TLS in place. Called after the server
|
|
1327
|
+
* answers our STARTTLS with 220. Detaches the plaintext-socket listeners
|
|
1328
|
+
* (TLS owns the underlying transport now), re-installs them on the wrapped
|
|
1329
|
+
* socket, resets EHLO-derived capabilities, and re-issues EHLO once the
|
|
1330
|
+
* handshake completes (RFC 3207 §4.2 mandates re-EHLO after upgrade —
|
|
1331
|
+
* pre-TLS state must be discarded).
|
|
1332
|
+
*/
|
|
1333
|
+
upgradeToTls() {
|
|
1334
|
+
const plainSocket = this.socket;
|
|
1335
|
+
if (!plainSocket) {
|
|
1336
|
+
this.finish(null, "tls_upgrade_failed");
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const detach = plainSocket.removeAllListeners;
|
|
1340
|
+
if (typeof detach === "function") {
|
|
1341
|
+
detach.call(plainSocket, "data");
|
|
1342
|
+
detach.call(plainSocket, "error");
|
|
1343
|
+
detach.call(plainSocket, "close");
|
|
1344
|
+
detach.call(plainSocket, "timeout");
|
|
1345
|
+
}
|
|
1346
|
+
try {
|
|
1347
|
+
plainSocket.setTimeout(0);
|
|
1348
|
+
} catch {
|
|
1349
|
+
}
|
|
1350
|
+
this.tlsUpgrading = true;
|
|
1351
|
+
const servername = isIPAddress(this.p.mxHost) ? void 0 : this.p.mxHost;
|
|
1352
|
+
const tlsSocket = tls__namespace.connect({ ...this.tlsOptions, socket: plainSocket, servername }, () => {
|
|
1353
|
+
this.tlsUpgrading = false;
|
|
1354
|
+
this.isTLS = true;
|
|
1355
|
+
this.buffer = "";
|
|
1356
|
+
this.supportsStartTls = false;
|
|
1357
|
+
this.supportsPipelining = false;
|
|
1358
|
+
this.postUpgradeReEhlo = true;
|
|
1359
|
+
this.send(`EHLO ${this.p.hostname}`);
|
|
1360
|
+
});
|
|
1361
|
+
this.socket = tlsSocket;
|
|
1362
|
+
this.socket.on("data", this.onData);
|
|
1363
|
+
this.socket.on("error", () => {
|
|
1364
|
+
this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
|
|
1365
|
+
});
|
|
1366
|
+
this.socket.on("close", () => this.finish(null, "connection_closed"));
|
|
1367
|
+
this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
|
|
1368
|
+
}
|
|
1293
1369
|
/**
|
|
1294
1370
|
* Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
|
|
1295
1371
|
* when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
|
|
@@ -1348,10 +1424,13 @@ ${rsetCmd}\r
|
|
|
1348
1424
|
}
|
|
1349
1425
|
if (MULTILINE_RE.test(line)) {
|
|
1350
1426
|
const step = this.steps[this.currentStepIndex];
|
|
1351
|
-
|
|
1427
|
+
const isEhloLike = step === exports.SMTPStep.ehlo || step === exports.SMTPStep.helo || step === exports.SMTPStep.startTls && this.postUpgradeReEhlo;
|
|
1428
|
+
if (isEhloLike && line.startsWith("250-")) {
|
|
1352
1429
|
const upper = line.toUpperCase();
|
|
1353
1430
|
if (upper.includes("PIPELINING"))
|
|
1354
1431
|
this.supportsPipelining = true;
|
|
1432
|
+
if (upper.includes("STARTTLS"))
|
|
1433
|
+
this.supportsStartTls = true;
|
|
1355
1434
|
}
|
|
1356
1435
|
return;
|
|
1357
1436
|
}
|
|
@@ -1386,6 +1465,21 @@ ${rsetCmd}\r
|
|
|
1386
1465
|
else
|
|
1387
1466
|
this.finish(null, "helo_failed");
|
|
1388
1467
|
return;
|
|
1468
|
+
case exports.SMTPStep.startTls:
|
|
1469
|
+
if (this.postUpgradeReEhlo) {
|
|
1470
|
+
this.postUpgradeReEhlo = false;
|
|
1471
|
+
if (code === 250)
|
|
1472
|
+
this.nextStep();
|
|
1473
|
+
else
|
|
1474
|
+
this.finish(null, "ehlo_failed");
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (code === 220) {
|
|
1478
|
+
this.upgradeToTls();
|
|
1479
|
+
} else {
|
|
1480
|
+
this.finish(null, "tls_upgrade_failed");
|
|
1481
|
+
}
|
|
1482
|
+
return;
|
|
1389
1483
|
case exports.SMTPStep.mailFrom:
|
|
1390
1484
|
if (code === 250)
|
|
1391
1485
|
this.nextStep();
|
|
@@ -2547,6 +2641,36 @@ function isSpamName(name) {
|
|
|
2547
2641
|
return true;
|
|
2548
2642
|
}
|
|
2549
2643
|
|
|
2644
|
+
const REFINEMENT_TABLE = {
|
|
2645
|
+
// X.1.x — addressing
|
|
2646
|
+
"5.1.1": "mailbox_does_not_exist",
|
|
2647
|
+
"5.1.2": "bad_destination_system",
|
|
2648
|
+
"5.1.3": "bad_destination_address",
|
|
2649
|
+
"5.1.6": "mailbox_moved",
|
|
2650
|
+
"5.1.10": "recipient_address_has_null_mx",
|
|
2651
|
+
// X.2.x — mailbox status
|
|
2652
|
+
"5.2.0": "mailbox_status_other",
|
|
2653
|
+
"5.2.1": "mailbox_disabled",
|
|
2654
|
+
"5.2.2": "mailbox_full",
|
|
2655
|
+
"5.2.3": "message_too_long",
|
|
2656
|
+
"5.2.4": "mailing_list_expansion_problem",
|
|
2657
|
+
// X.4.x — network / routing
|
|
2658
|
+
"4.4.1": "no_answer_from_host",
|
|
2659
|
+
"4.4.2": "bad_connection",
|
|
2660
|
+
// X.7.x — security / policy
|
|
2661
|
+
"5.7.0": "security_other",
|
|
2662
|
+
"5.7.1": "delivery_not_authorized",
|
|
2663
|
+
"5.7.25": "no_reverse_dns",
|
|
2664
|
+
"5.7.26": "multiple_authentication_failures"
|
|
2665
|
+
};
|
|
2666
|
+
function refineReasonByEnhancedStatus(reason, enhancedStatus) {
|
|
2667
|
+
var _a;
|
|
2668
|
+
const base = reason !== null && reason !== void 0 ? reason : "unknown";
|
|
2669
|
+
if (!enhancedStatus)
|
|
2670
|
+
return base;
|
|
2671
|
+
return (_a = REFINEMENT_TABLE[enhancedStatus]) !== null && _a !== void 0 ? _a : base;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2550
2674
|
const NETWORK_ERROR_PATTERNS = [
|
|
2551
2675
|
"etimedout",
|
|
2552
2676
|
"econnrefused",
|
|
@@ -2677,6 +2801,7 @@ exports.isSpamName = isSpamName;
|
|
|
2677
2801
|
exports.isValidEmail = isValidEmail;
|
|
2678
2802
|
exports.isValidEmailDomain = isValidEmailDomain;
|
|
2679
2803
|
exports.parseSmtpError = parseSmtpError;
|
|
2804
|
+
exports.refineReasonByEnhancedStatus = refineReasonByEnhancedStatus;
|
|
2680
2805
|
exports.suggestDomain = suggestDomain;
|
|
2681
2806
|
exports.suggestEmailDomain = suggestEmailDomain;
|
|
2682
2807
|
exports.verifyEmail = verifyEmail;
|