-
Notifications
You must be signed in to change notification settings - Fork 43
/
index.js
217 lines (164 loc) · 6.05 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
'use strict';
// Load modules
const Constants = require('./constants');
const Diagnoses = require('./diagnoses');
const Parser = require('./parser');
const Utils = require('./utils');
const Validation = require('./validation');
// Declare internals
const internals = {
hasOwn: Object.prototype.hasOwnProperty,
indexOf: Array.prototype.indexOf,
defaultThreshold: 16,
maxIPv6Groups: 8
};
internals.regex = {
nonASCII: /[^\x00-\x7f]/
};
internals.normalizeSupportsNul = '\0'.normalize('NFC') === '\0';
internals.normalize = function (email) {
return email.normalize('NFC');
};
// $lab:coverage:off$
if (!internals.normalizeSupportsNul) {
internals.nulNormalize = function (email) {
return email.replace(/[^\0]+/, (part) => part.normalize('NFC'));
};
internals.normalize = function (email) {
if (email.includes('\0')) {
return internals.nulNormalize(email);
}
return email.normalize('NFC');
};
}
// $lab:coverage:on$
/**
* Check whether the given value is a positive integer in the range [0,2^31-1].
*
* @param {number} value
* @return {boolean}
*/
internals.isPositiveInteger = function (value) {
return value === (0 | value) && value >= 0;
};
internals.isIterable = Array.isArray;
/* $lab:coverage:off$ */
if (typeof Symbol !== 'undefined') {
internals.isIterable = (value) => Array.isArray(value) || (!!value && typeof value === 'object' && typeof value[Symbol.iterator] === 'function');
}
/* $lab:coverage:on$ */
/**
* Check that an email address conforms to RFCs 5321, 5322, 6530 and others
*
* We distinguish clearly between a Mailbox as defined by RFC 5321 and an
* addr-spec as defined by RFC 5322. Depending on the context, either can be
* regarded as a valid email address. The RFC 5321 Mailbox specification is
* more restrictive (comments, white space and obsolete forms are not allowed).
*
* @param {string} email The email address to check. See README for specifics.
* @param {Object} options The (optional) options:
* {*} errorLevel Determines the boundary between valid and invalid
* addresses.
* {*} tldBlacklist The set of domains to consider invalid.
* {*} tldWhitelist The set of domains to consider valid.
* {*} allowUnicode Whether to allow non-ASCII characters, defaults to true.
* {*} minDomainAtoms The minimum number of domain atoms which must be present
* for the address to be valid.
* @param {function(number|boolean)} callback The (optional) callback handler.
* @return {*}
*/
exports.validate = internals.validate = function (email, options = {}, callback = null) {
if (typeof email !== 'string') {
throw new TypeError('expected string email');
}
email = internals.normalize(email);
// The callback function is deprecated.
// $lab:coverage:off$
if (typeof options === 'function') {
callback = options;
options = {};
}
if (typeof callback !== 'function') {
callback = null;
}
// $lab:coverage:on$
const {
errorLevel = false,
allowUnicode = true
} = options;
let diagnose;
let threshold;
// Support either a number or a boolean to control whether we report a true-
// false classification of validity or report a fine-grained error code for
// diagnostics.
if (typeof errorLevel === 'number') {
diagnose = true;
threshold = errorLevel;
}
else {
diagnose = !!errorLevel;
threshold = Constants.diagnoses.valid;
}
if (options.tldWhitelist) {
if (typeof options.tldWhitelist === 'string') {
options.tldWhitelist = [options.tldWhitelist];
}
else if (typeof options.tldWhitelist !== 'object') {
throw new TypeError('expected array or object tldWhitelist');
}
}
if (options.tldBlacklist) {
if (typeof options.tldBlacklist === 'string') {
options.tldBlacklist = [options.tldBlacklist];
}
else if (typeof options.tldBlacklist !== 'object') {
throw new TypeError('expected array or object tldBlacklist');
}
}
if (options.minDomainAtoms && !internals.isPositiveInteger(options.minDomainAtoms)) {
throw new TypeError('expected positive integer minDomainAtoms');
}
// Normalize the set of excluded diagnoses.
if (options.excludeDiagnoses) {
if (!internals.isIterable(options.excludeDiagnoses)) {
throw new TypeError('expected iterable excludeDiagnoses');
}
// This won't catch cross-realm Sets pre-Node 10, but it will cast the
// value to an in-realm Set representation.
if (!Utils.isSet(options.excludeDiagnoses)) {
options.excludeDiagnoses = new Set(options.excludeDiagnoses);
}
}
const diagnoses = new Diagnoses();
const parser = new Parser(email, diagnoses);
if (!allowUnicode) {
const match = internals.regex.nonASCII.exec(email);
if (match) {
diagnoses.diagnose(Constants.diagnoses.undesiredNonAscii, match.index);
}
}
// Actually parse the email address, and collect the errors in the diagnoses
// object.
const parse = parser.parse();
// If the email address prompted no fatal errors, consider other possible
// fatal errors based on the configuration.
if (parse) {
Validation.optionsValidation(parse, diagnoses, options);
}
const diagnosis = diagnoses.getLegacyDiagnosis(options.excludeDiagnoses || new Set());
let maxResult = diagnosis ? diagnosis.type : Constants.diagnoses.valid;
if (maxResult < threshold) {
maxResult = Constants.diagnoses.valid;
}
const finishResult = diagnose ? maxResult : maxResult < internals.defaultThreshold;
// $lab:coverage:off$
if (callback) {
callback(finishResult);
}
// $lab:coverage:on$
return finishResult;
};
// Export a copy of the diagnostic codes.
exports.diagnoses = internals.validate.diagnoses = Object.assign({}, Constants.diagnoses);
exports.normalize = internals.normalize;
exports.internals = internals;