email-domain-check
Version:
Comprehensive email domain validation library with DNS, MX, SMTP, DKIM, DMARC and MTA-STS support.
124 lines (123 loc) • 4.29 kB
JavaScript
import net from 'node:net';
import { domainToASCII } from 'node:url';
export var IPKind;
(function (IPKind) {
IPKind[IPKind["None"] = 0] = "None";
IPKind[IPKind["IPv4"] = 4] = "IPv4";
IPKind[IPKind["IPv6"] = 6] = "IPv6";
})(IPKind || (IPKind = {}));
export class Address {
source;
ipKind;
user;
hostname;
constructor(mailOrHost) {
this.source = mailOrHost;
const atPos = mailOrHost.indexOf('@');
if (atPos >= 0) {
//email literal
this.user = mailOrHost.slice(0, atPos);
const domainPart = mailOrHost.slice(atPos + 1);
this.ipKind = net.isIP(domainPart);
if (this.ipKind !== IPKind.None) {
this.hostname = domainPart;
}
else {
// RFC 5321 / RFC 5322
// Check if the domain looks like an IP literal. IP addresses need to be enclosed in square brackets
// user@[127.0.0.1]
// user@[IPv6:2001:db8:1ff::a0b:dbd0]
if (/^\[(ipv6:)?[^\]]+\]$/i.test(domainPart)) {
const cleanDomain = domainPart.replace(/^\[(ipv6:)?|\]$/gi, '');
this.ipKind = net.isIP(cleanDomain);
if (this.ipKind !== IPKind.None) {
this.hostname = cleanDomain;
}
else {
throw new Error(`Parse error source:${domainPart}`);
}
}
else {
const noDot = domainPart.replace(/\.$/, '');
this.hostname = domainToASCII(noDot).toLowerCase();
}
}
}
else {
// hostname literal
this.ipKind = net.isIP(mailOrHost);
if (this.ipKind !== IPKind.None) {
this.hostname = mailOrHost;
}
else {
const noDot = mailOrHost.replace(/\.$/, '');
this.hostname = domainToASCII(noDot).toLowerCase();
}
}
}
static loadFromTarget(target) {
if (target instanceof Address) {
return target;
}
else if (target instanceof URL) {
return new Address(target.hostname);
}
else {
return new Address(target);
}
}
get isIP() {
return this.ipKind !== IPKind.None;
}
get hasPunycode() {
if (this.hostname) {
return this.hostname.split('.').some((part) => part.startsWith('xn--'));
}
return false;
}
// Special IPv4 address ranges.
// See also https://en.wikipedia.org/wiki/Reserved_IP_addresses
get isLocal() {
if (this.ipKind === IPKind.IPv4) {
const parts = this.hostname.split('.').map(Number);
// Loopback: 127.0.0.0/8
if (parts[0] === 127)
return true;
// Private: 10.0.0.0/8
if (parts[0] === 10)
return true;
// Private: 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
return true;
// Private: 192.168.0.0/16
if (parts[0] === 192 && parts[1] === 168)
return true;
// Unspecified/This network: 0.0.0.0/8
if (parts[0] === 0)
return true;
// Link-local: 169.254.0.0/16
if (parts[0] === 169 && parts[1] === 254)
return true;
}
else if (this.ipKind === IPKind.IPv6) {
const lower = this.hostname.toLowerCase();
// Loopback: ::1
if (lower === '::1' || lower === '0:0:0:0:0:0:0:1')
return true;
// Link-local: fe80::/10
if (lower.startsWith('fe80:'))
return true;
// Unique local: fc00::/7
if (lower.startsWith('fc') || lower.startsWith('fd'))
return true;
// Unspecified: ::
if (lower === '::')
return true;
}
else {
if (this.hostname === 'localhost')
return true;
}
return false;
}
}