/
emailaddress.js
526 lines (445 loc) · 14 KB
/
emailaddress.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Provides functions to parse and manipulate email addresses.
*/
goog.provide('goog.format.EmailAddress');
goog.require('goog.i18n.bidi');
goog.require('goog.object');
goog.require('goog.string');
/**
* Formats an email address string for display, and allows for extraction of
* the individual components of the address.
* @param {string=} opt_address The email address.
* @param {string=} opt_name The name associated with the email address.
* @constructor
*/
goog.format.EmailAddress = function(opt_address, opt_name) {
'use strict';
/**
* The name or personal string associated with the address.
* @type {string}
* @private
*/
this.name_ = opt_name || '';
/**
* The email address.
* @type {string}
* @protected
*/
this.address = opt_address || '';
};
/**
* Match string for opening tokens.
* @type {string}
* @private
*/
goog.format.EmailAddress.OPENERS_ = '"<([';
/**
* Match string for closing tokens.
* @type {string}
* @private
*/
goog.format.EmailAddress.CLOSERS_ = '">)]';
/**
* Match string for characters that require display names to be quoted and are
* not address separators.
* @type {string}
* @const
* @package
*/
goog.format.EmailAddress.SPECIAL_CHARS = '()<>@:\\\".[]';
/**
* Match string for address separators.
* @type {string}
* @const
* @private
*/
goog.format.EmailAddress.ADDRESS_SEPARATORS_ = ',;';
/**
* Match string for characters that, when in a display name, require it to be
* quoted.
* @type {string}
* @const
* @private
*/
goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_ =
goog.format.EmailAddress.SPECIAL_CHARS +
goog.format.EmailAddress.ADDRESS_SEPARATORS_;
/**
* A RegExp to match all double quotes. Used in cleanAddress().
* @type {RegExp}
* @private
*/
goog.format.EmailAddress.ALL_DOUBLE_QUOTES_ = /\"/g;
/**
* A RegExp to match escaped double quotes. Used in parse().
* @type {RegExp}
* @private
*/
goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_ = /\\\"/g;
/**
* A RegExp to match all backslashes. Used in cleanAddress().
* @type {RegExp}
* @private
*/
goog.format.EmailAddress.ALL_BACKSLASHES_ = /\\/g;
/**
* A RegExp to match escaped backslashes. Used in parse().
* @type {RegExp}
* @private
*/
goog.format.EmailAddress.ESCAPED_BACKSLASHES_ = /\\\\/g;
/**
* A string representing the RegExp for the local part of an email address.
* @private {string}
*/
goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ =
'[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~-]+';
/**
* A string representing the RegExp for the domain part of an email address.
* @private {string}
*/
goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ =
'([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,63}';
/**
* A RegExp to match the local part of an email address.
* @private {!RegExp}
*/
goog.format.EmailAddress.LOCAL_PART_ =
new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '$');
/**
* A RegExp to match the domain part of an email address.
* @private {!RegExp}
*/
goog.format.EmailAddress.DOMAIN_PART_ =
new RegExp('^' + goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
/**
* A RegExp to match an email address.
* @private {!RegExp}
*/
goog.format.EmailAddress.EMAIL_ADDRESS_ = new RegExp(
'^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '@' +
goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
/**
* Regular expression for bidi format character replacement in text.
* @type {!RegExp}
* @private
*/
goog.format.EmailAddress.ALL_BIDI_FORMAT_CHARS_ = new RegExp(
'[' + goog.object.getValues(goog.i18n.bidi.Format).join('') + ']', 'g');
/**
* Get the name associated with the email address.
* @return {string} The name or personal portion of the address.
* @final
*/
goog.format.EmailAddress.prototype.getName = function() {
'use strict';
return this.name_;
};
/**
* Get the email address.
* @return {string} The email address.
* @final
*/
goog.format.EmailAddress.prototype.getAddress = function() {
'use strict';
return this.address;
};
/**
* Set the name associated with the email address.
* @param {string} name The name to associate.
* @final
*/
goog.format.EmailAddress.prototype.setName = function(name) {
'use strict';
this.name_ = name;
};
/**
* Set the email address.
* @param {string} address The email address.
* @final
*/
goog.format.EmailAddress.prototype.setAddress = function(address) {
'use strict';
this.address = address;
};
/**
* Return the address in a standard format:
* - remove extra spaces.
* - Surround name with quotes if it contains special characters.
* @return {string} The cleaned address.
* @override
*/
goog.format.EmailAddress.prototype.toString = function() {
'use strict';
return this.toStringInternal(goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_);
};
/**
* Check if a display name requires quoting.
* @param {string} name The display name
* @param {string} specialChars String that contains the characters that require
* the display name to be quoted. This may change based in whereas we are
* in EAI context or not.
* @return {boolean}
* @private
*/
goog.format.EmailAddress.isQuoteNeeded_ = function(name, specialChars) {
'use strict';
for (var i = 0; i < specialChars.length; i++) {
var specialChar = specialChars[i];
if (goog.string.contains(name, specialChar)) {
return true;
}
}
return false;
};
/**
* Return the address in a standard format:
* - remove extra spaces.
* - Surround name with quotes if it contains special characters.
* @param {string} specialChars String that contains the characters that require
* the display name to be quoted.
* @return {string} The cleaned address.
* @protected
*/
goog.format.EmailAddress.prototype.toStringInternal = function(specialChars) {
'use strict';
var name = this.getName();
// We intentionally remove double quotes in the name because escaping
// them to \" looks ugly.
name = name.replace(goog.format.EmailAddress.ALL_DOUBLE_QUOTES_, '');
// If the name has special characters, we need to quote it and escape \'s.
if (goog.format.EmailAddress.isQuoteNeeded_(name, specialChars)) {
name = '"' +
name.replace(goog.format.EmailAddress.ALL_BACKSLASHES_, '\\\\') + '"';
}
if (name == '') {
return this.address;
}
if (this.address == '') {
return name;
}
return name + ' <' + this.address + '>';
};
/**
* Determines if the current object is a valid email address.
* @return {boolean} Whether the email address is valid.
*/
goog.format.EmailAddress.prototype.isValid = function() {
'use strict';
return goog.format.EmailAddress.isValidAddrSpec(this.address);
};
/**
* Checks if the provided string is a valid email address. Supports both
* simple email addresses (address specs) and addresses that contain display
* names.
* @param {string} str The email address to check.
* @return {boolean} Whether the provided string is a valid address.
*/
goog.format.EmailAddress.isValidAddress = function(str) {
'use strict';
return goog.format.EmailAddress.parse(str).isValid();
};
/**
* Checks if the provided string is a valid address spec (local@domain.com).
* @param {string} str The email address to check.
* @return {boolean} Whether the provided string is a valid address spec.
*/
goog.format.EmailAddress.isValidAddrSpec = function(str) {
'use strict';
// This is a fairly naive implementation, but it covers 99% of use cases.
// For more details, see http://en.wikipedia.org/wiki/Email_address#Syntax
return goog.format.EmailAddress.EMAIL_ADDRESS_.test(str);
};
/**
* Checks if the provided string is a valid local part (part before the '@') of
* an email address.
* @param {string} str The local part to check.
* @return {boolean} Whether the provided string is a valid local part.
*/
goog.format.EmailAddress.isValidLocalPartSpec = function(str) {
'use strict';
return goog.format.EmailAddress.LOCAL_PART_.test(str);
};
/**
* Checks if the provided string is a valid domain part (part after the '@') of
* an email address.
* @param {string} str The domain part to check.
* @return {boolean} Whether the provided string is a valid domain part.
*/
goog.format.EmailAddress.isValidDomainPartSpec = function(str) {
'use strict';
return goog.format.EmailAddress.DOMAIN_PART_.test(str);
};
/**
* Parses an email address of the form "name" <address> ("name" is
* optional) into an email address.
* @param {string} addr The address string.
* @param {function(new: goog.format.EmailAddress, string=,string=)} ctor
* EmailAddress constructor to instantiate the output address.
* @return {!goog.format.EmailAddress} The parsed address.
* @protected
*/
goog.format.EmailAddress.parseInternal = function(addr, ctor) {
'use strict';
addr = goog.format.EmailAddress.stripBidiChars_(addr);
var name = '';
var address = '';
for (var i = 0; i < addr.length;) {
var token = goog.format.EmailAddress.getToken_(addr, i);
if (token.charAt(0) == '<' && token.indexOf('>') != -1) {
var end = token.indexOf('>');
address = token.substring(1, end);
} else if (address == '') {
name += token;
}
i += token.length;
}
// Check if it's a simple email address of the form "jlim@google.com".
if (address == '' && name.indexOf('@') != -1) {
address = name;
name = '';
}
name = goog.string.collapseWhitespace(name);
name = goog.string.stripQuotes(name, '\'');
name = goog.string.stripQuotes(name, '"');
// Replace escaped quotes and slashes.
name = name.replace(goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_, '"');
name = name.replace(goog.format.EmailAddress.ESCAPED_BACKSLASHES_, '\\');
address = goog.string.collapseWhitespace(address);
return new ctor(address, name);
};
/**
* Parses an email address of the form "name" <address> into
* an email address.
* @param {string} addr The address string.
* @return {!goog.format.EmailAddress} The parsed address.
*/
goog.format.EmailAddress.parse = function(addr) {
'use strict';
return goog.format.EmailAddress.parseInternal(addr, goog.format.EmailAddress);
};
/**
* Parse a string containing email addresses of the form
* "name" <address> into an array of email addresses.
* @param {string} str The address list.
* @param {function(string)} parser The parser to employ.
* @param {function(string):boolean} separatorChecker Accepts a character and
* returns whether it should be considered an address separator.
* @return {!Array<!goog.format.EmailAddress>} The parsed emails.
* @protected
*/
goog.format.EmailAddress.parseListInternal = function(
str, parser, separatorChecker) {
'use strict';
var result = [];
var email = '';
var token;
// Remove non-UNIX-style newlines that would otherwise cause getToken_ to
// choke. Remove multiple consecutive whitespace characters for the same
// reason.
str = goog.string.collapseWhitespace(str);
for (var i = 0; i < str.length;) {
token = goog.format.EmailAddress.getToken_(str, i);
if (separatorChecker(token) || (token == ' ' && parser(email).isValid())) {
if (!goog.string.isEmptyOrWhitespace(email)) {
result.push(parser(email));
}
email = '';
i++;
continue;
}
email += token;
i += token.length;
}
// Add the final token.
if (!goog.string.isEmptyOrWhitespace(email)) {
result.push(parser(email));
}
return result;
};
/**
* Parses a string containing email addresses of the form
* "name" <address> into an array of email addresses.
* @param {string} str The address list.
* @return {!Array<!goog.format.EmailAddress>} The parsed emails.
*/
goog.format.EmailAddress.parseList = function(str) {
'use strict';
return goog.format.EmailAddress.parseListInternal(
str, goog.format.EmailAddress.parse,
goog.format.EmailAddress.isAddressSeparator);
};
/**
* Get the next token from a position in an address string.
* @param {string} str the string.
* @param {number} pos the position.
* @return {string} the token.
* @private
*/
goog.format.EmailAddress.getToken_ = function(str, pos) {
'use strict';
var ch = str.charAt(pos);
var p = goog.format.EmailAddress.OPENERS_.indexOf(ch);
if (p == -1) {
return ch;
}
if (goog.format.EmailAddress.isEscapedDlQuote_(str, pos)) {
// If an opener is an escaped quote we do not treat it as a real opener
// and keep accumulating the token.
return ch;
}
var closerChar = goog.format.EmailAddress.CLOSERS_.charAt(p);
var endPos = str.indexOf(closerChar, pos + 1);
// If the closer is a quote we go forward skipping escaped quotes until we
// hit the real closing one.
while (endPos >= 0 &&
goog.format.EmailAddress.isEscapedDlQuote_(str, endPos)) {
endPos = str.indexOf(closerChar, endPos + 1);
}
var token = (endPos >= 0) ? str.substring(pos, endPos + 1) : ch;
return token;
};
/**
* Checks if the character in the current position is an escaped double quote
* ( \" ).
* @param {string} str the string.
* @param {number} pos the position.
* @return {boolean} true if the char is escaped double quote.
* @private
*/
goog.format.EmailAddress.isEscapedDlQuote_ = function(str, pos) {
'use strict';
if (str.charAt(pos) != '"') {
return false;
}
var slashCount = 0;
for (var idx = pos - 1; idx >= 0 && str.charAt(idx) == '\\'; idx--) {
slashCount++;
}
return ((slashCount % 2) != 0);
};
/**
* @param {string} ch The character to test.
* @return {boolean} Whether the provided character is an address separator.
*/
goog.format.EmailAddress.isAddressSeparator = function(ch) {
'use strict';
return goog.string.contains(goog.format.EmailAddress.ADDRESS_SEPARATORS_, ch);
};
/**
* Returns the input text without Unicode formatting characters
* and directionality string constants as defined in {@link
* goog.i18n.bidi.Format}.
* @param {string} str The given string.
* @return {string} The given string cleaned of formatting characters.
* @private
*/
goog.format.EmailAddress.stripBidiChars_ = function(str) {
'use strict';
return str.replace(goog.format.EmailAddress.ALL_BIDI_FORMAT_CHARS_, '');
};