valia
Version:
A runtime data validator in TypeScript with advanced type inference, built-in validation functions, and seamless integration for server and client environments.
126 lines (103 loc) • 3.65 kB
text/typescript
/*
Composition :
atom = 1*atext
dot-local = atom *("." atom)
quoted-local = DQUOTE *QcontentSMTP DQUOTE
ip-address = IPv4-address-literal / IPv6-address-literal
general-address = General-address-literal
local = dot-local / quote-local
domain = Domain
address = ip-address / general-address
mailbox = local "@" (domain / address)
Sources :
RFC 5234 Appendix B.1 : DQUOTE
RFC 5322 Section 3.2.3 : atext
RFC 5321 Section 4.1.3 : IPv4-address-literal
IPv6-address-literal
General-address-literal
RFC 5321 Section 4.1.2 : QcontentSMTP
Domain
Links :
https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3
https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.3
https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2
*/
import { ipV4Pattern, ipV6Pattern } from "./isIp";
import { isDomain } from "./isDomain";
import { weak } from "../utils";
interface EmailObject {
local: string;
domain: string;
}
interface EmailParams {
/** **Default:** `false` */
allowQuotedString?: boolean;
/** **Default:** `false` */
allowIpAddress?: boolean;
/** **Default:** `false` */
allowGeneralAddress?: boolean;
}
const dotStringPattern = "(?:[-!=?A-B\\x23-\\x27\\x2A-\\x2B\\x2F-\\x39\\x5E-\\x7E]+(?:\\.[-!=?A-B\\x23-\\x27\\x2A-\\x2B\\x2F-\\x39\\x5E-\\x7E]+)*)";
const quotedStringPattern = "(?:\"(?:[\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]|\\\\[\\x20-\\x7E])*\")";
const dotLocalRegex = new RegExp(`^${dotStringPattern}$`);
const dotOrQuoteLocalRegex = weak(() => new RegExp(`^(?:${dotStringPattern}|${quotedStringPattern})$`));
const ipAddressRegex = weak(() => new RegExp(`^\\[(?:IPv6:${ipV6Pattern}|${ipV4Pattern})\\]$`));
const generalAddressRegex = weak(() => new RegExp(`(?:[a-zA-Z0-9-]*[a-zA-Z0-9]+:[\\x21-\\x5A\\x5E-\\x7E]+)`));
function parseEmail(str: string): EmailObject | null {
const length = str.length;
let i = 0;
// EXTRACT LOCAL
const localStart = i;
if (str[localStart] === "\"") {
while (++i < length) {
if (str[i] === "\\") i++;
else if (str[i] === "\"") {
i++;
break;
}
}
} else {
while (i < length && str[i] !== "@") i++;
}
if (i === localStart || str[i] !== "@") return (null);
const localEnd = i;
// EXTRACT DOMAIN
const domainStart = ++i;
const domainEnd = length;
if (domainStart === domainEnd) return (null);
return ({
local: str.slice(localStart, localEnd),
domain: str.slice(domainStart, domainEnd)
});
}
function isValidLocal(str: string, params?: EmailParams): boolean {
if (dotLocalRegex.test(str)) return (true);
if (params?.allowQuotedString
&& dotOrQuoteLocalRegex().test(str)) return (true);
return (false);
}
function isValidDomain(str: string, params?: EmailParams): boolean {
if (isDomain(str)) return (true);
if (params?.allowIpAddress
&& ipAddressRegex().test(str)) return (true);
if (params?.allowGeneralAddress
&& generalAddressRegex().test(str)) return (true);
return (false);
}
/**
* **Standard :** RFC 5321
*
* @version 2.0.0
*/
export function isEmail(str: string, params?: EmailParams): boolean {
const email = parseEmail(str);
if (!email) return (false);
// CHECK LOCAL
if (!isValidLocal(email.local, params)) return (false);
// CHECK DOMAIN
if (!isValidDomain(email.domain, params)) return (false);
// RFC 5321 4.5.3.1.2 : Length restriction
if (!email.domain.length || email.domain.length > 255) return (false);
return (true);
}