emailaddress.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. // Copyright 2010 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Provides functions to parse and manipulate email addresses.
  16. *
  17. */
  18. goog.provide('goog.format.EmailAddress');
  19. goog.require('goog.string');
  20. /**
  21. * Formats an email address string for display, and allows for extraction of
  22. * the individual components of the address.
  23. * @param {string=} opt_address The email address.
  24. * @param {string=} opt_name The name associated with the email address.
  25. * @constructor
  26. */
  27. goog.format.EmailAddress = function(opt_address, opt_name) {
  28. /**
  29. * The name or personal string associated with the address.
  30. * @type {string}
  31. * @private
  32. */
  33. this.name_ = opt_name || '';
  34. /**
  35. * The email address.
  36. * @type {string}
  37. * @protected
  38. */
  39. this.address = opt_address || '';
  40. };
  41. /**
  42. * Match string for opening tokens.
  43. * @type {string}
  44. * @private
  45. */
  46. goog.format.EmailAddress.OPENERS_ = '"<([';
  47. /**
  48. * Match string for closing tokens.
  49. * @type {string}
  50. * @private
  51. */
  52. goog.format.EmailAddress.CLOSERS_ = '">)]';
  53. /**
  54. * Match string for characters that require display names to be quoted and are
  55. * not address separators.
  56. * @type {string}
  57. * @const
  58. * @package
  59. */
  60. goog.format.EmailAddress.SPECIAL_CHARS = '()<>@:\\\".[]';
  61. /**
  62. * Match string for address separators.
  63. * @type {string}
  64. * @const
  65. * @private
  66. */
  67. goog.format.EmailAddress.ADDRESS_SEPARATORS_ = ',;';
  68. /**
  69. * Match string for characters that, when in a display name, require it to be
  70. * quoted.
  71. * @type {string}
  72. * @const
  73. * @private
  74. */
  75. goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_ =
  76. goog.format.EmailAddress.SPECIAL_CHARS +
  77. goog.format.EmailAddress.ADDRESS_SEPARATORS_;
  78. /**
  79. * A RegExp to match all double quotes. Used in cleanAddress().
  80. * @type {RegExp}
  81. * @private
  82. */
  83. goog.format.EmailAddress.ALL_DOUBLE_QUOTES_ = /\"/g;
  84. /**
  85. * A RegExp to match escaped double quotes. Used in parse().
  86. * @type {RegExp}
  87. * @private
  88. */
  89. goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_ = /\\\"/g;
  90. /**
  91. * A RegExp to match all backslashes. Used in cleanAddress().
  92. * @type {RegExp}
  93. * @private
  94. */
  95. goog.format.EmailAddress.ALL_BACKSLASHES_ = /\\/g;
  96. /**
  97. * A RegExp to match escaped backslashes. Used in parse().
  98. * @type {RegExp}
  99. * @private
  100. */
  101. goog.format.EmailAddress.ESCAPED_BACKSLASHES_ = /\\\\/g;
  102. /**
  103. * A string representing the RegExp for the local part of an email address.
  104. * @private {string}
  105. */
  106. goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ =
  107. '[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~-]+';
  108. /**
  109. * A string representing the RegExp for the domain part of an email address.
  110. * @private {string}
  111. */
  112. goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ =
  113. '([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,63}';
  114. /**
  115. * A RegExp to match the local part of an email address.
  116. * @private {!RegExp}
  117. */
  118. goog.format.EmailAddress.LOCAL_PART_ =
  119. new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '$');
  120. /**
  121. * A RegExp to match the domain part of an email address.
  122. * @private {!RegExp}
  123. */
  124. goog.format.EmailAddress.DOMAIN_PART_ =
  125. new RegExp('^' + goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
  126. /**
  127. * A RegExp to match an email address.
  128. * @private {!RegExp}
  129. */
  130. goog.format.EmailAddress.EMAIL_ADDRESS_ = new RegExp(
  131. '^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '@' +
  132. goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
  133. /**
  134. * Get the name associated with the email address.
  135. * @return {string} The name or personal portion of the address.
  136. * @final
  137. */
  138. goog.format.EmailAddress.prototype.getName = function() {
  139. return this.name_;
  140. };
  141. /**
  142. * Get the email address.
  143. * @return {string} The email address.
  144. * @final
  145. */
  146. goog.format.EmailAddress.prototype.getAddress = function() {
  147. return this.address;
  148. };
  149. /**
  150. * Set the name associated with the email address.
  151. * @param {string} name The name to associate.
  152. * @final
  153. */
  154. goog.format.EmailAddress.prototype.setName = function(name) {
  155. this.name_ = name;
  156. };
  157. /**
  158. * Set the email address.
  159. * @param {string} address The email address.
  160. * @final
  161. */
  162. goog.format.EmailAddress.prototype.setAddress = function(address) {
  163. this.address = address;
  164. };
  165. /**
  166. * Return the address in a standard format:
  167. * - remove extra spaces.
  168. * - Surround name with quotes if it contains special characters.
  169. * @return {string} The cleaned address.
  170. * @override
  171. */
  172. goog.format.EmailAddress.prototype.toString = function() {
  173. return this.toStringInternal(goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_);
  174. };
  175. /**
  176. * Check if a display name requires quoting.
  177. * @param {string} name The display name
  178. * @param {string} specialChars String that contains the characters that require
  179. * the display name to be quoted. This may change based in whereas we are
  180. * in EAI context or not.
  181. * @return {boolean}
  182. * @private
  183. */
  184. goog.format.EmailAddress.isQuoteNeeded_ = function(name, specialChars) {
  185. for (var i = 0; i < specialChars.length; i++) {
  186. var specialChar = specialChars[i];
  187. if (goog.string.contains(name, specialChar)) {
  188. return true;
  189. }
  190. }
  191. return false;
  192. };
  193. /**
  194. * Return the address in a standard format:
  195. * - remove extra spaces.
  196. * - Surround name with quotes if it contains special characters.
  197. * @param {string} specialChars String that contains the characters that require
  198. * the display name to be quoted.
  199. * @return {string} The cleaned address.
  200. * @protected
  201. */
  202. goog.format.EmailAddress.prototype.toStringInternal = function(specialChars) {
  203. var name = this.getName();
  204. // We intentionally remove double quotes in the name because escaping
  205. // them to \" looks ugly.
  206. name = name.replace(goog.format.EmailAddress.ALL_DOUBLE_QUOTES_, '');
  207. // If the name has special characters, we need to quote it and escape \'s.
  208. if (goog.format.EmailAddress.isQuoteNeeded_(name, specialChars)) {
  209. name = '"' +
  210. name.replace(goog.format.EmailAddress.ALL_BACKSLASHES_, '\\\\') + '"';
  211. }
  212. if (name == '') {
  213. return this.address;
  214. }
  215. if (this.address == '') {
  216. return name;
  217. }
  218. return name + ' <' + this.address + '>';
  219. };
  220. /**
  221. * Determines if the current object is a valid email address.
  222. * @return {boolean} Whether the email address is valid.
  223. */
  224. goog.format.EmailAddress.prototype.isValid = function() {
  225. return goog.format.EmailAddress.isValidAddrSpec(this.address);
  226. };
  227. /**
  228. * Checks if the provided string is a valid email address. Supports both
  229. * simple email addresses (address specs) and addresses that contain display
  230. * names.
  231. * @param {string} str The email address to check.
  232. * @return {boolean} Whether the provided string is a valid address.
  233. */
  234. goog.format.EmailAddress.isValidAddress = function(str) {
  235. return goog.format.EmailAddress.parse(str).isValid();
  236. };
  237. /**
  238. * Checks if the provided string is a valid address spec (local@domain.com).
  239. * @param {string} str The email address to check.
  240. * @return {boolean} Whether the provided string is a valid address spec.
  241. */
  242. goog.format.EmailAddress.isValidAddrSpec = function(str) {
  243. // This is a fairly naive implementation, but it covers 99% of use cases.
  244. // For more details, see http://en.wikipedia.org/wiki/Email_address#Syntax
  245. return goog.format.EmailAddress.EMAIL_ADDRESS_.test(str);
  246. };
  247. /**
  248. * Checks if the provided string is a valid local part (part before the '@') of
  249. * an email address.
  250. * @param {string} str The local part to check.
  251. * @return {boolean} Whether the provided string is a valid local part.
  252. */
  253. goog.format.EmailAddress.isValidLocalPartSpec = function(str) {
  254. return goog.format.EmailAddress.LOCAL_PART_.test(str);
  255. };
  256. /**
  257. * Checks if the provided string is a valid domain part (part after the '@') of
  258. * an email address.
  259. * @param {string} str The domain part to check.
  260. * @return {boolean} Whether the provided string is a valid domain part.
  261. */
  262. goog.format.EmailAddress.isValidDomainPartSpec = function(str) {
  263. return goog.format.EmailAddress.DOMAIN_PART_.test(str);
  264. };
  265. /**
  266. * Parses an email address of the form "name" &lt;address&gt; ("name" is
  267. * optional) into an email address.
  268. * @param {string} addr The address string.
  269. * @param {function(new: goog.format.EmailAddress, string=,string=)} ctor
  270. * EmailAddress constructor to instantiate the output address.
  271. * @return {!goog.format.EmailAddress} The parsed address.
  272. * @protected
  273. */
  274. goog.format.EmailAddress.parseInternal = function(addr, ctor) {
  275. // TODO(ecattell): Strip bidi markers.
  276. var name = '';
  277. var address = '';
  278. for (var i = 0; i < addr.length;) {
  279. var token = goog.format.EmailAddress.getToken_(addr, i);
  280. if (token.charAt(0) == '<' && token.indexOf('>') != -1) {
  281. var end = token.indexOf('>');
  282. address = token.substring(1, end);
  283. } else if (address == '') {
  284. name += token;
  285. }
  286. i += token.length;
  287. }
  288. // Check if it's a simple email address of the form "jlim@google.com".
  289. if (address == '' && name.indexOf('@') != -1) {
  290. address = name;
  291. name = '';
  292. }
  293. name = goog.string.collapseWhitespace(name);
  294. name = goog.string.stripQuotes(name, '\'');
  295. name = goog.string.stripQuotes(name, '"');
  296. // Replace escaped quotes and slashes.
  297. name = name.replace(goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_, '"');
  298. name = name.replace(goog.format.EmailAddress.ESCAPED_BACKSLASHES_, '\\');
  299. address = goog.string.collapseWhitespace(address);
  300. return new ctor(address, name);
  301. };
  302. /**
  303. * Parses an email address of the form "name" &lt;address&gt; into
  304. * an email address.
  305. * @param {string} addr The address string.
  306. * @return {!goog.format.EmailAddress} The parsed address.
  307. */
  308. goog.format.EmailAddress.parse = function(addr) {
  309. return goog.format.EmailAddress.parseInternal(addr, goog.format.EmailAddress);
  310. };
  311. /**
  312. * Parse a string containing email addresses of the form
  313. * "name" &lt;address&gt; into an array of email addresses.
  314. * @param {string} str The address list.
  315. * @param {function(string)} parser The parser to employ.
  316. * @param {function(string):boolean} separatorChecker Accepts a character and
  317. * returns whether it should be considered an address separator.
  318. * @return {!Array<!goog.format.EmailAddress>} The parsed emails.
  319. * @protected
  320. */
  321. goog.format.EmailAddress.parseListInternal = function(
  322. str, parser, separatorChecker) {
  323. var result = [];
  324. var email = '';
  325. var token;
  326. // Remove non-UNIX-style newlines that would otherwise cause getToken_ to
  327. // choke. Remove multiple consecutive whitespace characters for the same
  328. // reason.
  329. str = goog.string.collapseWhitespace(str);
  330. for (var i = 0; i < str.length;) {
  331. token = goog.format.EmailAddress.getToken_(str, i);
  332. if (separatorChecker(token) || (token == ' ' && parser(email).isValid())) {
  333. if (!goog.string.isEmptyOrWhitespace(email)) {
  334. result.push(parser(email));
  335. }
  336. email = '';
  337. i++;
  338. continue;
  339. }
  340. email += token;
  341. i += token.length;
  342. }
  343. // Add the final token.
  344. if (!goog.string.isEmptyOrWhitespace(email)) {
  345. result.push(parser(email));
  346. }
  347. return result;
  348. };
  349. /**
  350. * Parses a string containing email addresses of the form
  351. * "name" &lt;address&gt; into an array of email addresses.
  352. * @param {string} str The address list.
  353. * @return {!Array<!goog.format.EmailAddress>} The parsed emails.
  354. */
  355. goog.format.EmailAddress.parseList = function(str) {
  356. return goog.format.EmailAddress.parseListInternal(
  357. str, goog.format.EmailAddress.parse,
  358. goog.format.EmailAddress.isAddressSeparator);
  359. };
  360. /**
  361. * Get the next token from a position in an address string.
  362. * @param {string} str the string.
  363. * @param {number} pos the position.
  364. * @return {string} the token.
  365. * @private
  366. */
  367. goog.format.EmailAddress.getToken_ = function(str, pos) {
  368. var ch = str.charAt(pos);
  369. var p = goog.format.EmailAddress.OPENERS_.indexOf(ch);
  370. if (p == -1) {
  371. return ch;
  372. }
  373. if (goog.format.EmailAddress.isEscapedDlQuote_(str, pos)) {
  374. // If an opener is an escaped quote we do not treat it as a real opener
  375. // and keep accumulating the token.
  376. return ch;
  377. }
  378. var closerChar = goog.format.EmailAddress.CLOSERS_.charAt(p);
  379. var endPos = str.indexOf(closerChar, pos + 1);
  380. // If the closer is a quote we go forward skipping escaped quotes until we
  381. // hit the real closing one.
  382. while (endPos >= 0 &&
  383. goog.format.EmailAddress.isEscapedDlQuote_(str, endPos)) {
  384. endPos = str.indexOf(closerChar, endPos + 1);
  385. }
  386. var token = (endPos >= 0) ? str.substring(pos, endPos + 1) : ch;
  387. return token;
  388. };
  389. /**
  390. * Checks if the character in the current position is an escaped double quote
  391. * ( \" ).
  392. * @param {string} str the string.
  393. * @param {number} pos the position.
  394. * @return {boolean} true if the char is escaped double quote.
  395. * @private
  396. */
  397. goog.format.EmailAddress.isEscapedDlQuote_ = function(str, pos) {
  398. if (str.charAt(pos) != '"') {
  399. return false;
  400. }
  401. var slashCount = 0;
  402. for (var idx = pos - 1; idx >= 0 && str.charAt(idx) == '\\'; idx--) {
  403. slashCount++;
  404. }
  405. return ((slashCount % 2) != 0);
  406. };
  407. /**
  408. * @param {string} ch The character to test.
  409. * @return {boolean} Whether the provided character is an address separator.
  410. */
  411. goog.format.EmailAddress.isAddressSeparator = function(ch) {
  412. return goog.string.contains(goog.format.EmailAddress.ADDRESS_SEPARATORS_, ch);
  413. };