// Copyright 2010 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Provides functions to parse and manipulate email addresses. * */ goog.provide('goog.format.EmailAddress'); 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) { /** * 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_ + '$'); /** * 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() { return this.name_; }; /** * Get the email address. * @return {string} The email address. * @final */ goog.format.EmailAddress.prototype.getAddress = function() { 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) { this.name_ = name; }; /** * Set the email address. * @param {string} address The email address. * @final */ goog.format.EmailAddress.prototype.setAddress = function(address) { 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() { 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) { 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) { 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() { 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) { 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) { // 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) { 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) { 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) { // TODO(ecattell): Strip bidi markers. 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) { 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} The parsed emails. * @protected */ goog.format.EmailAddress.parseListInternal = function( str, parser, separatorChecker) { 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} The parsed emails. */ goog.format.EmailAddress.parseList = function(str) { 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) { 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) { 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) { return goog.string.contains(goog.format.EmailAddress.ADDRESS_SEPARATORS_, ch); };