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