You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							169 lines
						
					
					
						
							5.0 KiB
						
					
					
				
			
		
		
	
	
							169 lines
						
					
					
						
							5.0 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
const Util = require('util'); | 
						|
 | 
						|
const Domain = require('./domain'); | 
						|
 | 
						|
 | 
						|
const internals = { | 
						|
    nonAsciiRx: /[^\x00-\x7f]/, | 
						|
    encoder: new (Util.TextEncoder || TextEncoder)()                                            // $lab:coverage:ignore$ | 
						|
}; | 
						|
 | 
						|
 | 
						|
exports.analyze = function (email, options) { | 
						|
 | 
						|
    return internals.email(email, options); | 
						|
}; | 
						|
 | 
						|
 | 
						|
exports.isValid = function (email, options) { | 
						|
 | 
						|
    return !internals.email(email, options); | 
						|
}; | 
						|
 | 
						|
 | 
						|
internals.email = function (email, options = {}) { | 
						|
 | 
						|
    if (typeof email !== 'string') { | 
						|
        throw new Error('Invalid input: email must be a string'); | 
						|
    } | 
						|
 | 
						|
    if (!email) { | 
						|
        return { error: 'Address must be a non-empty string' }; | 
						|
    } | 
						|
 | 
						|
    // Unicode | 
						|
 | 
						|
    const ascii = !internals.nonAsciiRx.test(email); | 
						|
    if (!ascii) { | 
						|
        if (options.allowUnicode === false) {                                                   // Defaults to true | 
						|
            return { error: 'Address contains forbidden Unicode characters' }; | 
						|
        } | 
						|
 | 
						|
        email = email.normalize('NFC'); | 
						|
    } | 
						|
 | 
						|
    // Basic structure | 
						|
 | 
						|
    const parts = email.split('@'); | 
						|
    if (parts.length !== 2) { | 
						|
        return { error: parts.length > 2 ? 'Address cannot contain more than one @ character' : 'Address must contain one @ character' }; | 
						|
    } | 
						|
 | 
						|
    const [local, domain] = parts; | 
						|
 | 
						|
    if (!local) { | 
						|
        return { error: 'Address local part cannot be empty' }; | 
						|
    } | 
						|
 | 
						|
    if (!options.ignoreLength) { | 
						|
        if (email.length > 254) {                                           // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3 | 
						|
            return { error: 'Address too long' }; | 
						|
        } | 
						|
 | 
						|
        if (internals.encoder.encode(local).length > 64) {                  // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 | 
						|
            return { error: 'Address local part too long' }; | 
						|
        } | 
						|
    } | 
						|
 | 
						|
    // Validate parts | 
						|
 | 
						|
    return internals.local(local, ascii) || Domain.analyze(domain, options); | 
						|
}; | 
						|
 | 
						|
 | 
						|
internals.local = function (local, ascii) { | 
						|
 | 
						|
    const segments = local.split('.'); | 
						|
    for (const segment of segments) { | 
						|
        if (!segment.length) { | 
						|
            return { error: 'Address local part contains empty dot-separated segment' }; | 
						|
        } | 
						|
 | 
						|
        if (ascii) { | 
						|
            if (!internals.atextRx.test(segment)) { | 
						|
                return { error: 'Address local part contains invalid character' }; | 
						|
            } | 
						|
 | 
						|
            continue; | 
						|
        } | 
						|
 | 
						|
        for (const char of segment) { | 
						|
            if (internals.atextRx.test(char)) { | 
						|
                continue; | 
						|
            } | 
						|
 | 
						|
            const binary = internals.binary(char); | 
						|
            if (!internals.atomRx.test(binary)) { | 
						|
                return { error: 'Address local part contains invalid character' }; | 
						|
            } | 
						|
        } | 
						|
    } | 
						|
}; | 
						|
 | 
						|
 | 
						|
internals.binary = function (char) { | 
						|
 | 
						|
    return Array.from(internals.encoder.encode(char)).map((v) => String.fromCharCode(v)).join(''); | 
						|
}; | 
						|
 | 
						|
 | 
						|
/* | 
						|
    From RFC 5321: | 
						|
 | 
						|
        Mailbox         =   Local-part "@" ( Domain / address-literal ) | 
						|
 | 
						|
        Local-part      =   Dot-string / Quoted-string | 
						|
        Dot-string      =   Atom *("."  Atom) | 
						|
        Atom            =   1*atext | 
						|
        atext           =   ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~" | 
						|
 | 
						|
        Domain          =   sub-domain *("." sub-domain) | 
						|
        sub-domain      =   Let-dig [Ldh-str] | 
						|
        Let-dig         =   ALPHA / DIGIT | 
						|
        Ldh-str         =   *( ALPHA / DIGIT / "-" ) Let-dig | 
						|
 | 
						|
        ALPHA           =   %x41-5A / %x61-7A        ; a-z, A-Z | 
						|
        DIGIT           =   %x30-39                  ; 0-9 | 
						|
 | 
						|
    From RFC 6531: | 
						|
 | 
						|
        sub-domain      =/  U-label | 
						|
        atext           =/  UTF8-non-ascii | 
						|
 | 
						|
        UTF8-non-ascii  =   UTF8-2 / UTF8-3 / UTF8-4 | 
						|
 | 
						|
        UTF8-2          =   %xC2-DF UTF8-tail | 
						|
        UTF8-3          =   %xE0 %xA0-BF UTF8-tail / | 
						|
                            %xE1-EC 2( UTF8-tail ) / | 
						|
                            %xED %x80-9F UTF8-tail / | 
						|
                            %xEE-EF 2( UTF8-tail ) | 
						|
        UTF8-4          =   %xF0 %x90-BF 2( UTF8-tail ) / | 
						|
                            %xF1-F3 3( UTF8-tail ) / | 
						|
                            %xF4 %x80-8F 2( UTF8-tail ) | 
						|
 | 
						|
        UTF8-tail       =   %x80-BF | 
						|
 | 
						|
    Note: The following are not supported: | 
						|
 | 
						|
        RFC 5321: address-literal, Quoted-string | 
						|
        RFC 5322: obs-*, CFWS | 
						|
*/ | 
						|
 | 
						|
 | 
						|
internals.atextRx = /^[\w!#\$%&'\*\+\-/=\?\^`\{\|\}~]+$/;               // _ included in \w | 
						|
 | 
						|
 | 
						|
internals.atomRx = new RegExp([ | 
						|
 | 
						|
    //  %xC2-DF UTF8-tail | 
						|
    '(?:[\\xc2-\\xdf][\\x80-\\xbf])', | 
						|
 | 
						|
    //  %xE0 %xA0-BF UTF8-tail              %xE1-EC 2( UTF8-tail )            %xED %x80-9F UTF8-tail              %xEE-EF 2( UTF8-tail ) | 
						|
    '(?:\\xe0[\\xa0-\\xbf][\\x80-\\xbf])|(?:[\\xe1-\\xec][\\x80-\\xbf]{2})|(?:\\xed[\\x80-\\x9f][\\x80-\\xbf])|(?:[\\xee-\\xef][\\x80-\\xbf]{2})', | 
						|
 | 
						|
    //  %xF0 %x90-BF 2( UTF8-tail )            %xF1-F3 3( UTF8-tail )            %xF4 %x80-8F 2( UTF8-tail ) | 
						|
    '(?:\\xf0[\\x90-\\xbf][\\x80-\\xbf]{2})|(?:[\\xf1-\\xf3][\\x80-\\xbf]{3})|(?:\\xf4[\\x80-\\x8f][\\x80-\\xbf]{2})' | 
						|
 | 
						|
].join('|'));
 | 
						|
 |