|
| 1 | +import { isIdnHostname } from "./idn-hostname.js"; |
| 2 | + |
| 3 | + |
| 4 | +const ucschar = `[\\u{A0}-\\u{D7FF}\\u{F900}-\\u{FDCF}\\u{FDF0}-\\u{FFEF}\\u{10000}-\\u{1FFFD}\\u{20000}-\\u{2FFFD}\\u{30000}-\\u{3FFFD}\\u{40000}-\\u{4FFFD}\\u{50000}-\\u{5FFFD}\\u{60000}-\\u{6FFFD}\\u{70000}-\\u{7FFFD}\\u{80000}-\\u{8FFFD}\\u{90000}-\\u{9FFFD}\\u{A0000}-\\u{AFFFD}\\u{B0000}-\\u{BFFFD}\\u{C0000}-\\u{CFFFD}\\u{D0000}-\\u{DFFFD}\\u{E1000}-\\u{EFFFD}]`; |
| 5 | + |
| 6 | +const alpha = `[a-zA-Z]`; |
| 7 | +const hexdig = `[\\da-fA-F]`; |
| 8 | + |
| 9 | +// Printable US-ASCII characters not including specials. |
| 10 | +const atext = `(?:[\\w!#$%&'*+\\-/=?^\`{|}~]|${ucschar})`; |
| 11 | +const atom = `${atext}+`; |
| 12 | +const dotString = `${atom}(?:\\.${atom})*`; |
| 13 | + |
| 14 | +// Any ASCII graphic or space without blackslash-quoting except double-quote and the backslash itself. |
| 15 | +const qtextSMTP = `(?:[\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]|${ucschar})`; |
| 16 | +// backslash followed by any ASCII graphic or space |
| 17 | +const quotedPairSMTP = `\\\\[\\x20-\\x7E]`; |
| 18 | +const qcontentSMTP = `(?:${qtextSMTP}|${quotedPairSMTP})`; |
| 19 | +const quotedString = `"${qcontentSMTP}*"`; |
| 20 | + |
| 21 | +const localPart = `(?:${dotString}|${quotedString})`; // MAY be case-sensitive |
| 22 | + |
| 23 | +const letDig = `(?:${alpha}|\\d)`; |
| 24 | +const ldhStr = `(?:${letDig}|-)*${letDig}`; |
| 25 | +const letDigUcs = `(?:${alpha}|\\d|${ucschar})`; |
| 26 | +const ldhStrUcs = `(?:${letDigUcs}|-)*${letDigUcs}`; |
| 27 | +const subDomain = `${letDigUcs}${ldhStrUcs}?`; |
| 28 | +const domain = `${subDomain}(?:\\.${subDomain})*`; |
| 29 | + |
| 30 | +const decOctet = `(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])`; |
| 31 | +const ipv4Address = `${decOctet}\\.${decOctet}\\.${decOctet}\\.${decOctet}`; |
| 32 | + |
| 33 | +const h16 = `${hexdig}{1,4}`; |
| 34 | +const ls32 = `(?:${h16}:${h16}|${ipv4Address})`; |
| 35 | +const ipv6Address = `(?:(?:${h16}:){6}${ls32}|::(?:${h16}:){5}${ls32}|(?:${h16})?::(?:${h16}:){4}${ls32}|(?:(?:${h16}:){0,1}${h16})?::(?:${h16}:){3}${ls32}|(?:(?:${h16}:){0,2}${h16})?::(?:${h16}:){2}${ls32}|(?:(?:${h16}:){0,3}${h16})?::(?:${h16}:){1}${ls32}|(?:(?:${h16}:){0,4}${h16})?::${ls32}|(?:(?:${h16}:){0,5}${h16})?::${h16}|(?:(?:${h16}:){0,6}${h16})?::)`; |
| 36 | +const ipv6AddressLiteral = `IPv6:${ipv6Address}`; |
| 37 | + |
| 38 | +const dcontent = `[\\x21-\\x5A\\x5E-\\x7E]`; // Printable US-ASCII excluding "[", "\", "]" |
| 39 | +const generalAddressLiteral = `${ldhStr}:${dcontent}+`; |
| 40 | + |
| 41 | +const addressLiteral = `\\[(?:${ipv4Address}|${ipv6AddressLiteral}|${generalAddressLiteral})\\]`; |
| 42 | + |
| 43 | +const mailbox = `(?<localPart>${localPart})@(?:(?<ip>${addressLiteral})|(?<domain>${domain}))`; |
| 44 | + |
| 45 | +const mailboxPattern = new RegExp(`^${mailbox}$`, "u"); |
| 46 | +export const isIdnEmail = (email) => { |
| 47 | + const parsedEmail = mailboxPattern.exec(email)?.groups; |
| 48 | + |
| 49 | + return !!parsedEmail && (!parsedEmail.domain || isIdnHostname(parsedEmail.domain)); |
| 50 | +}; |
| 51 | + |
| 52 | +export default { |
| 53 | + id: "https://json-schema.org/format/idn-email", |
| 54 | + handler: (email) => typeof email !== "string" || isIdnEmail(email) |
| 55 | +}; |
0 commit comments