1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a new emal address, either by parsing an email string or from component parts.
 26  * @constructor
 27  * @class
 28  * This class represents an email address and defines some related constants. The class does not attempt full compliance
 29  * with RFC2822, so there are limitations for some of the edge cases.
 30  *
 31  * @author Conrad Damon
 32  * 
 33  * @param {string}	address		an email string, or just the address portion
 34  * @param {constant}	type		from, to, cc, bcc, or reply-to
 35  * @param {string}	name		the personal name portion
 36  * @param {string}	dispName	a brief display version of the name
 37  * @param {boolean}	isGroup		if <code>true</code>, the address param is really a list of email addresses
 38  * 
 39  */
 40 AjxEmailAddress = function(address, type, name, dispName, isGroup, canExpand) {
 41 	this.address = address;
 42 	this.name = this._setName(name);
 43 	this.dispName = dispName;
 44 	this.type = type || AjxEmailAddress.TO;
 45 	this.isGroup = isGroup;
 46 	this.canExpand = canExpand;
 47 };
 48 
 49 AjxEmailAddress.prototype.isAjxEmailAddress = true;
 50 
 51 /**
 52  * Defines list of custom invalid RegEx patterns that are set in LDAP
 53  */
 54 AjxEmailAddress.customInvalidEmailPats = [];
 55 
 56 /**
 57  * Defines the "from" type.
 58  */
 59 AjxEmailAddress.FROM		= "FROM";
 60 /**
 61  * Defines the "to" type.
 62  */
 63 AjxEmailAddress.TO			= "TO";
 64 /**
 65  * Defines the "cc" type.
 66  */
 67 AjxEmailAddress.CC			= "CC";
 68 /**
 69  * Defines the "bcc" type.
 70  */
 71 AjxEmailAddress.BCC			= "BCC";
 72 AjxEmailAddress.REPLY_TO	= "REPLY_TO";
 73 AjxEmailAddress.SENDER		= "SENDER";
 74 AjxEmailAddress.READ_RECEIPT= "READ_RECEIPT";
 75 AjxEmailAddress.RESENT_FROM = "RESENT_FROM";
 76 
 77 AjxEmailAddress.TYPE_STRING = {};
 78 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.FROM]			= "from";
 79 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.TO]				= "to";
 80 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.CC]				= "cc";
 81 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.BCC]			= "bcc";
 82 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.REPLY_TO]		= "replyTo";
 83 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.SENDER]			= "sender";
 84 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.READ_RECEIPT]	= "readReceipt";
 85 AjxEmailAddress.TYPE_STRING[AjxEmailAddress.RESENT_FROM]	= "resentFrom";
 86 
 87 AjxEmailAddress.fromSoapType = {};
 88 AjxEmailAddress.fromSoapType["f"]  = AjxEmailAddress.FROM;
 89 AjxEmailAddress.fromSoapType["t"]  = AjxEmailAddress.TO;
 90 AjxEmailAddress.fromSoapType["c"]  = AjxEmailAddress.CC;
 91 AjxEmailAddress.fromSoapType["b"]  = AjxEmailAddress.BCC;
 92 AjxEmailAddress.fromSoapType["r"]  = AjxEmailAddress.REPLY_TO;
 93 AjxEmailAddress.fromSoapType["s"]  = AjxEmailAddress.SENDER;
 94 AjxEmailAddress.fromSoapType["n"]  = AjxEmailAddress.READ_RECEIPT;
 95 AjxEmailAddress.fromSoapType["rf"] = AjxEmailAddress.RESENT_FROM;
 96 
 97 AjxEmailAddress.toSoapType = {};
 98 AjxEmailAddress.toSoapType[AjxEmailAddress.FROM]		= "f";
 99 AjxEmailAddress.toSoapType[AjxEmailAddress.TO]			= "t";
100 AjxEmailAddress.toSoapType[AjxEmailAddress.CC]			= "c";
101 AjxEmailAddress.toSoapType[AjxEmailAddress.BCC]			= "b";
102 AjxEmailAddress.toSoapType[AjxEmailAddress.REPLY_TO]	= "r";
103 AjxEmailAddress.toSoapType[AjxEmailAddress.SENDER]		= "s";
104 AjxEmailAddress.toSoapType[AjxEmailAddress.READ_RECEIPT]= "n";
105 
106 AjxEmailAddress.SEPARATOR = "; ";				// used to join addresses
107 AjxEmailAddress.DELIMS = [';', ',', '\n', ' '];	// recognized as address delimiters
108 AjxEmailAddress.IS_DELIM = AjxUtil.arrayAsHash(AjxEmailAddress.DELIMS);
109 
110 // validation patterns
111 
112 AjxEmailAddress.addrAnglePat = /(\s*<(((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*)))>\s*)/;
113 AjxEmailAddress.addrOnlyPat = /^((((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*))))$/;
114 
115 AjxEmailAddress.addrAngleQuotePat = /(\s*<'(((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*)))'>\s*)/;
116 // use addrPat to validate strings as email addresses
117 AjxEmailAddress.addrPat = /(((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*)))/;
118 // use addrPat1 to parse email addresses - pattern is lenient in that it will allow the following:
119 // 		"Joe Smith" joe@x.com
120 //		"Joe Smith"joe@x.com
121 // (RFC822 wants the address part to be in <> if preceded by name part)
122 AjxEmailAddress.addrPat1 = /(^|"|\s)(((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*)))/;
123 // pattern below is for account part of address (before @)
124 AjxEmailAddress.accountPat = /^([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)$/;
125 // Pattern below hangs on an unclosed comment, so use simpler one if parsing for comments
126 //AjxEmailAddress.commentPat = /(\s*\((\s*(([^()\\])|(\\([^\x0A\x0D]))|(\s*\((\s*(([^()\\])|(\\([^\x0A\x0D]))|(\s*\((\s*(([^()\\])|(\\([^\x0A\x0D]))|(\s*\((\s*(([^()\\])|(\\([^\x0A\x0D]))|(\s*\((\s*(([^()\\])|(\\([^\x0A\x0D]))|)+)*\s*\)\s*))+)*\s*\)\s*))+)*\s*\)\s*))+)*\s*\)\s*))+)*\s*\)\s*)/;
127 AjxEmailAddress.commentPat = /\((.*)\)/g;
128 AjxEmailAddress.phrasePat = /(((\s*[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\"\s]+\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))+)/;
129 AjxEmailAddress.boundAddrPat = /(\s*<?(((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*"(([^\\"])|(\\([^\x0A\x0D])))+"\s*))\@((\s*([^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+(\.[^\x00-\x1F\x7F\u0080-\uFFFF()<>\[\]:;@\,."\s]+)*)\s*)|(\s*\[(\s*(([^\[\]\\])|(\\([^\x0A\x0D])))+)*\s*\]\s*)))>?\s*)$/;
130 
131 AjxEmailAddress.validateAddress =
132 function (str) {
133 	str = AjxStringUtil.trim(str);
134 	return AjxEmailAddress._prelimCheck(str) && AjxEmailAddress.addrOnlyPat.test(str);
135 };
136 
137 /**
138  * Parses an email address string into its component parts. The parsing is adapted from the perl module 
139  * <a href="http://search.cpan.org/~cwest/Email-Address-1.2/lib/Email/Address.pm">Email::Address</a>. Check that out if you
140  * want to know how the gory regexes that do the parsing were built. They are based on RFC2822, but don't represent a full 
141  * implementation. We don't really need or want that, since we don't want to be overly restrictive or bloated. It was easier
142  * to just use the resulting regexes from the Perl module, rather than go through all the rigmarole of building them up from
143  * atoms.
144  * <p>
145  * If the address parses successfully, the current object's properties will be set.
146  * </p>
147  * 
148  * @param	{string}	str		the string to parse
149  * @return	{AjxEmailAddress}	the email address or <code>null</code>
150  */
151 AjxEmailAddress.parse = function(str) {
152 
153 	var addr, name;
154 	var str = AjxStringUtil.trim(str);
155 	var prelimOkay = AjxEmailAddress._prelimCheck(str);
156 	if (!(prelimOkay && str.match(AjxEmailAddress.addrPat))) {
157 		return null;
158 	}
159 
160 	// Note: It would be nice if you could get back the matching parenthesized subexpressions from replace,
161 	// then we wouldn't have to do both a match and a replace. The parsing works by removing parts after it
162 	// finds them.
163 	
164 	// First find the address (and remove it)
165 	var parts = str.match(AjxEmailAddress.addrAngleQuotePat) || str.match(AjxEmailAddress.addrAnglePat);
166 	if (parts && parts.length) {
167 		addr = parts[2];
168 		str = str.replace(AjxEmailAddress.addrAnglePat, '');
169 	}
170 	else {
171 		parts = str.match(AjxEmailAddress.addrPat1);
172 		if (parts && parts.length) {
173 			if (parts[1] === '"') {
174 				return null;	// unmatched quote
175 			}
176             // AjxEmailAddress.addrPat recognizes the email better than using parts[0] from AjxEmailAddress.addrPat1
177             var parts1 = str.match(AjxEmailAddress.addrPat);
178             addr = parts1 && parts1.length && parts1[0] ? AjxStringUtil.trim(parts1[0]) : parts[0];
179 			str = str.replace(AjxEmailAddress.addrPat, '');
180 		}
181 	}
182 
183 	// double-check with validateAddress(), which uses addrOnlyPat
184 	if (!addr || !AjxEmailAddress.validateAddress(addr)) {
185 		return null;
186 	}
187 
188 	// Validate against any customer-provided patterns
189 	for (var i = 0; i < AjxEmailAddress.customInvalidEmailPats.length; i++) {
190 		if (AjxEmailAddress.customInvalidEmailPats[i].test(addr)) {
191 			return null;
192 		}
193 	}
194 
195 	// What remains is the name
196 	if (str) {
197 		name = AjxStringUtil.trim(str);
198 
199 		// Trim off leading and trailing quotes, but leave escaped quotes and unescape them
200 		name = name.replace(/\\"/g,""");
201 		name = AjxStringUtil.trim(name, null, '"');
202 		name = name.replace(/"/g, '"');
203 	}
204 	
205 	return new AjxEmailAddress(addr, null, name);
206 };
207 
208 /**
209  * Parses a string with one or more addresses and parses it. An object with lists of good addresses, bad
210  * addresses, and all addresses is returned. Strict RFC822 validation (at least as far as it goes in the
211  * regexes we have) is optional. If it's off, we'll retry a failed address after quoting the personal part.
212  *
213  * @param	{string}	emailStr	an email string with one or more addresses
214  * @param	{constant}	type		address type of the string
215  * @param	{boolean}	strict		if <code>true</code>, do strict checking
216  * @return	{hash}		the good/bad/all addresses
217  */
218 AjxEmailAddress.parseEmailString =
219 function(emailStr, type, strict) {
220 	var good = new AjxVector();
221 	var bad = new AjxVector();
222 	var all = new AjxVector();
223 	var addrList = AjxEmailAddress.split(emailStr);
224 	for (var i = 0; i < addrList.length; i++) {
225 		var addrStr = AjxStringUtil.trim(addrList[i]);
226 		if (addrStr) {
227 			var addr = AjxEmailAddress.parse(addrStr);
228 			if (!addr && !strict) {
229 				var temp = addrStr;
230 				var parts = temp.match(AjxEmailAddress.addrAnglePat);
231 				if (parts && parts.length) {
232 					var name = temp.replace(AjxEmailAddress.addrAnglePat, '');
233 					var newAddr = ['"', name, '" ', parts[0]].join("");
234 					addr = AjxEmailAddress.parse(newAddr);
235 					if (addr) {
236 						addr.name = name; // reset name to original unquoted form
237 					}
238 				}
239 			}
240 			if (addr) {
241 				addr.type = type;
242 				good.add(addr);
243 				all.add(addr);
244 			} else {
245 				bad.add(addrStr);
246 				all.add(new AjxEmailAddress(addrStr));
247 			}
248 		}
249 	}
250 	return {good: good, bad: bad, all: all};
251 };
252 
253 /**
254  * Returns an AjxVector with valid email addresses
255  *
256  * @param	{string}	emailStr	an email string with one or more addresses
257  * @param	{constant}	type		address type of the string
258  * @param	{boolean}	strict		if <code>true</code>, do strict checking
259  * @return	{AjxVector}				valid addresses
260  */
261 AjxEmailAddress.getValidAddresses =
262 function(emailStr, type, strict) {
263 	return AjxEmailAddress.parseEmailString(emailStr, type, strict).good;
264 };
265 
266 /**
267  * Checks if a string to see if it's a valid email string according to our mailbox pattern.
268  *
269  * @param {string}	str		an email string
270  * @return	{boolean}	<code>true</code> if the string is valid
271  */
272 AjxEmailAddress.isValid = function(str) {
273 	return AjxEmailAddress.parse(str) != null;
274 };
275 
276 AjxEmailAddress._prelimCheck =
277 function(str) {
278 	// Do preliminary check for @ since we don't support local addresses, and as workaround for Mozilla bug
279 	// https://bugzilla.mozilla.org/show_bug.cgi?id=225094
280 	// Also check for . since we require FQDN
281 	var atIndex = str.indexOf('@');
282 	var dotIndex = str.lastIndexOf('.');
283 	return ((atIndex != -1) && (dotIndex != -1) && (dotIndex > atIndex) && (dotIndex != str.length - 1));
284 };
285 
286 /**
287  * Splits a string into (possible) email address strings based on delimiters. Tries to
288  * be flexible about what it will accept. The following delimiters are recognized, under
289  * the following conditions:
290  *
291  * <ul>
292  * <li><i>return</i> -- always</li>
293  * <li><i>semicolon</i> -- must not be inside quoted or comment text</li>
294  * <li><i>comma</i> -- must not be inside quoted or comment text, and must follow an address (which may be in angle brackets)</li>
295  * <li><i>space</i> -- can only separate plain addresses (no quoted or comment text)</li>
296  * </ul>
297  * 
298  * The requirement that a comma follow an address allows us to be lenient when a mailer
299  * doesn't quote the friendly part, so that a string such as the one below is split correctly:
300  * <code>Smith, John <jsmith@aol.com></code>
301  *
302  * @param {string}	str	the string to be split
303  * @return	{array}	the list of {String} addresses
304  */
305 AjxEmailAddress.split =
306 function(str) {
307 	str = AjxStringUtil.trim(str);
308 	// first, construct a list of ranges to ignore because they are quoted or comment text
309 	var ignore = [];
310 	var pos = 0, startPos = 0;
311 	var prevCh = "", startCh = "";
312 	var inside = false;
313 	while (pos < str.length) {
314 		var ch = str.charAt(pos);
315 		if ((ch == '"' || ch == '(') && prevCh != "\\") {
316 			inside = true;
317 			startCh = ch;
318 			startPos = pos;
319 			pos++;
320 			while (inside && pos < str.length) {
321 				var ch = str.charAt(pos);
322 				if (((startCh == '"' && ch == '"') || (startCh == '(' && ch == ')')) && (prevCh != "\\")) {
323 					ignore.push({start: startPos, end: pos});
324 					inside = false;
325 				}
326 				pos++;
327 				prevCh = ch;
328 			}
329 		} else {
330 			pos++;
331 		}
332 		prevCh = ch;
333 	}
334 	if (ignore.length) {
335 		AjxEmailAddress.IS_DELIM[" "] = false;
336 	}
337 	
338 	// Progressively scan the string for delimiters. Once an email string has been found, continue with
339 	// the remainder of the original string.
340 	startPos = 0;
341 	var addrList = [];
342 	while (startPos < str.length) {
343 		var sub = str.substring(startPos, str.length);
344 		pos = 0;
345 		var delimPos = sub.length;
346 		while ((delimPos == sub.length) && (pos < sub.length)) {
347 			var ch = sub.charAt(pos);
348 			if (AjxEmailAddress.IS_DELIM[ch]) {
349 				var doIgnore = false;
350 				if (ch != "\n") {
351 					for (var i = 0; i < ignore.length; i++) {
352 						var range = ignore[i];
353 						var absPos = startPos + pos;
354 						doIgnore = (absPos >= range.start && absPos <= range.end);
355 						if (doIgnore) break;
356 					}
357 				}
358 				if (!doIgnore) {
359 					var doAdd = true;
360 					var test = sub.substring(0, pos);
361 					if (ch == "," || ch == " ") {
362 						// comma/space allowed as non-delimeter outside quote/comment,
363 						// so we make sure it follows an actual address
364 						doAdd = test.match(AjxEmailAddress.boundAddrPat);
365 					}
366 					if (doAdd) {
367 						addrList.push(AjxStringUtil.trim(test));
368 						delimPos = pos;
369 						startPos += test.length + 1;
370 					}
371 				}
372 				// strip extra delimeters
373 				ch = str.charAt(startPos);
374 				while ((startPos < str.length) && AjxEmailAddress.IS_DELIM[ch]) {
375 					startPos++;
376 					ch = str.charAt(startPos);
377 				}
378 				pos++;
379 			} else {
380 				pos++;
381 			}
382 		}
383 		if (delimPos == sub.length) {
384 			addrList.push(AjxStringUtil.trim(sub));
385 			startPos += sub.length + 1;
386 		}
387 	}
388 	AjxEmailAddress.IS_DELIM[" "] = true;
389 
390 	return addrList;
391 };
392 
393 /**
394  * Returns a string representation of this object.
395  * 
396  * @param {boolean}		shortForm	if true, return a brief version (name if available, otherwise email)
397  * @param {boolean}		forceUnescape	if true, name will not be in quotes and any quotes inside the name will be unescaped (e.g. "John \"JD\" Doe" <jd@zimbra.com> becomes John "JD" Doe <jd@zimbra.com>
398  * 
399  * @return	{string}		a string representation of this object
400  */
401 AjxEmailAddress.prototype.toString =
402 function(shortForm, forceUnescape) {
403 
404 	if (this.name) {
405 		var name = this.name;
406 		if (!shortForm && !forceUnescape) {
407 			name = name.replace(/\\+"/g, '"');	// unescape double quotes (avoid double-escaping)
408 			name = name.replace(/"/g,'\\"');  // escape quotes
409 		}
410 		var buffer = (shortForm || forceUnescape) ? [name] : ['"', name, '"'];
411 		if (this.address && !shortForm) {
412 			buffer.push(" <", this.address, ">");
413 		}
414 		return buffer.join("");	// quote friendly part
415 	} else {
416 		return this.address;
417 	}
418 };
419 
420 /**
421  * Gets the address.
422  * 
423  * @return	{string}	the address
424  */
425 AjxEmailAddress.prototype.getAddress =
426 function() {
427 	return this.address;
428 };
429 
430 /**
431  * Sets the address.
432  * 
433  * @param	{string}	addr		the address
434  */
435 AjxEmailAddress.prototype.setAddress =
436 function(addr) {
437 	this.address = addr;
438 };
439 
440 /**
441  * Gets the type (to/from/cc/bcc).
442  * 
443  * @return	{constant}	the type
444  */
445 AjxEmailAddress.prototype.getType =
446 function() {
447 	return this.type;
448 };
449 
450 /**
451  * Sets the type.
452  * 
453  * @param	{constant}	type		the type (to/from/cc/bcc)
454  */
455 AjxEmailAddress.prototype.setType =
456 function(type) {
457 	this.type = type;
458 };
459 
460 /**
461  * Gets the type as a string.
462  * 
463  * @return	{string}	the type (to/from/cc/bcc)
464  */
465 AjxEmailAddress.prototype.getTypeAsString =
466 function() {
467 	return AjxEmailAddress.TYPE_STRING[this.type];
468 };
469 
470 /**
471  * Gets the name.
472  * 
473  * @return	{string}	the name
474  */
475 AjxEmailAddress.prototype.getName =
476 function() {
477 	return this.name;
478 };
479 
480 /**
481  * Gets the display name.
482  * 
483  * @return	{string}	the name
484  */
485 AjxEmailAddress.prototype.getDispName =
486 function() {
487 	return this.dispName;
488 };
489 
490 /*
491  * We use this for displaying the actual text in the cell (or group when grouping by "from")
492  */
493 AjxEmailAddress.prototype.getText =
494 function() {
495 	return this.getName() || this.getDispName() || this.getAddress();
496 };
497 
498 /**
499  * Clones this email address.
500  * 
501  * @return	{AjxEmailAddress}	a clone of this email address
502  */
503 AjxEmailAddress.prototype.clone =
504 function() {
505 	var addr = new AjxEmailAddress(this.address, this.type, this.name, this.dispName, this.isGroup, this.canExpand);
506 	addr.icon = this.icon;
507 	return addr;
508 };
509 
510 /**
511  * Copies the email address.
512  * 
513  * @param	{AjxEmailAddress}	obj		the email to copy
514  * @return	{AjxEmailAddress}	the newly copied email address
515  */
516 AjxEmailAddress.copy =
517 function(obj){    
518     var addr = new AjxEmailAddress(obj.address, obj.type, obj.name, obj.dispName, obj.isGroup, obj.canExpand);
519     addr.icon = obj.icon;
520     return addr;
521 };
522 
523 AjxEmailAddress.prototype._setName =
524 function(name) {
525 	if (!name) return "";
526 	
527 	// remove wrapping single quotes from name if present
528 	if (name && name.charAt(0) == "'" && name.charAt(name.length - 1) == "'")
529 		name = name.substring(1, name.length - 1);
530 		
531 	return name;		
532 };
533 
534 AjxEmailAddress.sortCompareByAddress =
535 function(a, b) {
536 
537 	var addrA = a.getAddress() || "";
538 	var addrB = b.getAddress() || "";
539 	if (addrA.toLowerCase() > addrB.toLowerCase()) { return 1; }
540 	if (addrA.toLowerCase() < addrB.toLowerCase()) { return -1; }
541 	return 0;
542 };
543 
544 /**
545  * Returns the list of addresses with duplicates (based on email) removed.
546  * 
547  * @param {array}	addrs	list of AjxEmailAddress
548  */
549 AjxEmailAddress.dedup =
550 function(addrs) {
551 	var list = [], used = {};
552 	if (addrs && addrs.length) {
553 		for (var i = 0; i < addrs.length; i++) {
554 			var addr = addrs[i];
555 			if (!used[addr.address]) {
556 				list.push(addr);
557 			}
558 			used[addr.address] = true;
559 		}
560 	}
561 	return list;
562 };
563