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 mime part. 26 * @class 27 * This class represents a mime part. Note that the content of the node is 28 * not copied into this object, for performance reasons. It is typically 29 * available via the 'bodyParts' list that is populated during node parsing. 30 * 31 * @extends ZmModel 32 */ 33 ZmMimePart = function(parent) { 34 35 ZmModel.call(this, ZmEvent.S_ATT); 36 37 this.parent = parent; 38 this.children = new AjxVector(); 39 }; 40 41 ZmMimePart.prototype = new ZmModel; 42 ZmMimePart.prototype.constructor = ZmMimePart; 43 44 ZmMimePart.prototype.isZmMimePart = true; 45 ZmMimePart.prototype.toString = function() { return "ZmMimePart"; }; 46 47 /** 48 * Returns a ZmMimePart constructed from the given JSON object. If a context 49 * hash is provided with 'attachments' and 'bodyParts' arrays, and a hash 50 * 'contentTypes', those will be populated as the node is recursively parsed. 51 * 52 * @param {object} node JSON representation of MIME part 53 * @param {hash} ctxt optional context 54 * @return {ZmMimePart} a MIME part 55 */ 56 ZmMimePart.createFromDom = 57 function(node, ctxt, parent) { 58 var mimePart = new ZmMimePart(parent); 59 mimePart._loadFromDom(node, ctxt); 60 return mimePart; 61 }; 62 63 /** 64 * Returns this part's content. 65 * 66 * @return {string} content the content 67 */ 68 ZmMimePart.prototype.getContent = 69 function() { 70 return this.content || (this.node && this.node.content) || ""; 71 }; 72 73 /** 74 * Returns content of the given type, in or below this part. 75 * 76 * @param {string} contentType the content type 77 * @return {string} the content 78 * 79 */ 80 ZmMimePart.prototype.getContentForType = 81 function(contentType) { 82 83 if (this.contentType == contentType) { 84 return this.getContent(); 85 } 86 else { 87 var children = this.children.getArray(); 88 if (children.length) { 89 for (var i = 0; i < children.length; i++) { 90 var content = children[i].getContentForType(contentType); 91 if (content) { 92 return content; 93 } 94 } 95 } 96 } 97 return ""; 98 }; 99 100 /** 101 * Sets the content, overriding the original content. 102 * 103 * @param {string} content the content 104 */ 105 ZmMimePart.prototype.setContent = 106 function(content) { 107 this.content = content; 108 }; 109 110 /** 111 * Returns the content disposition. 112 * 113 * @return {string} the content disposition 114 */ 115 ZmMimePart.prototype.getContentDisposition = 116 function() { 117 return this.contentDisposition; 118 }; 119 120 /** 121 * Returns the content type. 122 * 123 * @return {string} the content type 124 */ 125 ZmMimePart.prototype.getContentType = 126 function() { 127 return this.contentType; 128 }; 129 130 /** 131 * Sets the content type, , overriding the original content type. 132 * 133 * @param {string} contentType the content type 134 */ 135 ZmMimePart.prototype.setContentType = 136 function(contentType) { 137 this.contentType = contentType; 138 }; 139 140 /** 141 * Sets the 'is body' flag, overriding the original part's value. 142 * 143 * @param {boolean} isBody if true, this part is the body 144 */ 145 ZmMimePart.prototype.setIsBody = 146 function(isBody) { 147 this.isBody = isBody; 148 }; 149 150 /** 151 * Returns the filename. 152 * 153 * @return {string} the filename 154 */ 155 ZmMimePart.prototype.getFilename = 156 function() { 157 return this.fileName; 158 }; 159 160 /** 161 * Returns true if this part should not be considered to be an attachment. 162 * 163 * @return {boolean} 164 */ 165 ZmMimePart.prototype.isIgnoredPart = 166 function() { 167 // bug fix #5889 - if parent node was multipart/appledouble, 168 // ignore all application/applefile attachments - YUCK 169 if (this.parent && this.parent.contentType == ZmMimeTable.MULTI_APPLE_DBL && 170 this.contentType == ZmMimeTable.APP_APPLE_DOUBLE) 171 { 172 return true; 173 } 174 175 // bug fix #7271 - dont show renderable body parts as attachments anymore 176 if (this.isBody && this.getContent() && 177 (this.contentType == ZmMimeTable.TEXT_HTML || this.contentType == ZmMimeTable.TEXT_PLAIN)) 178 { 179 return true; 180 } 181 182 if (this.contentType == ZmMimeTable.MULTI_DIGEST) { 183 return true; 184 } 185 186 return false; 187 }; 188 189 ZmMimePart.prototype._loadFromDom = 190 function(node, ctxt) { 191 192 this._loadProps(node); 193 194 if (node.content) { 195 this._loaded = true; 196 } 197 198 if (ctxt.contentTypes) { 199 ctxt.contentTypes[node.ct] = true; 200 } 201 202 var isAtt = false; 203 if (this.contentDisposition == "attachment" || 204 this.contentType == ZmMimeTable.MSG_RFC822 || this.contentType == ZmMimeTable.TEXT_CAL || 205 this.fileName || this.contentId || this.contentLocation) { 206 207 if (!this.isIgnoredPart()) { 208 if (ctxt.attachments) { 209 ctxt.attachments.push(this); 210 } 211 isAtt = true; 212 } 213 } 214 215 if (this.isBody) { 216 var hasContent = AjxUtil.isSpecified(node.content); 217 if ((ZmMimeTable.isRenderableImage(this.contentType) || hasContent)) { 218 if (ctxt.bodyParts) { 219 if (this.contentType == ZmMimeTable.MULTI_ALT) { 220 ctxt.bodyParts.push({}); 221 } 222 else if (ZmMimePart._isPartOfMultipartAlternative(this)) { 223 var altPart = {}; 224 altPart[this.contentType] = this; 225 ctxt.bodyParts.push(altPart); 226 } 227 else { 228 ctxt.bodyParts.push(this); 229 } 230 } 231 if (isAtt && ctxt.attachments) { 232 //To avoid duplication, Remove attachment that was just added as bodypart. 233 ctxt.attachments.pop(); 234 } 235 } else if (!isAtt && this.size != 0 && !this.isIgnoredPart()){ 236 if (ctxt.attachments) { 237 ctxt.attachments.push(this); 238 } 239 } 240 } 241 242 // bug fix #4616 - dont add attachments part of a rfc822 msg part 243 if (node.mp && this.contentType != ZmMimeTable.MSG_RFC822) { 244 for (var i = 0; i < node.mp.length; i++) { 245 this.children.add(ZmMimePart.createFromDom(node.mp[i], ctxt, this)); 246 } 247 } 248 }; 249 250 ZmMimePart.prototype._loadProps = 251 function(node) { 252 253 this.node = node; 254 255 // the middle column is for backward compatibility 256 this.contentType = this.ct = node.ct; 257 this.format = node.format; // optional arg for text/plain 258 this.name = node.name; 259 this.part = node.part; 260 this.cachekey = node.cachekey; 261 this.size = this.s = node.s; 262 this.contentDisposition = this.cd = node.cd; 263 var ci = node.ci; 264 //in some cases the content ID is not wrapped by angle brackets (mistake by the mail application), so make sure we wrap it if not 265 this.contentId = this.ci = ci && ci.indexOf("<") !== 0 ? "<" + ci + ">" : ci; 266 this.contentLocation = this.cl = node.cl; 267 this.fileName = this.filename = node.filename; 268 this.isTruncated = this.truncated = !!(node.truncated); 269 this.isBody = this.body = !!(node.body); 270 }; 271 272 /** 273 * @param {object} parentNode 274 * @return {true/false} true if one of the parent in the hierarchy is multipart/alternative otherwise false. 275 */ 276 ZmMimePart._isPartOfMultipartAlternative = 277 function(part){ 278 if (!part) { return false; } 279 if (part.contentType == ZmMimeTable.MULTI_ALT) { return true; } 280 return ZmMimePart._isPartOfMultipartAlternative(part.parent); 281 }; 282 283 /** 284 * Checks within the given node tree for content within a multipart/alternative part that 285 * we don't have, and then creates and adds a MIME part for it. Assumes that there will be 286 * at most one multipart/alternative. 287 * 288 * @param {object} node 289 * @param {string} contentType 290 * @param {int} index 291 * 292 * @return {ZmMimePart} the MIME part that was created and added 293 */ 294 ZmMimePart.prototype.addAlternativePart = 295 function(node, contentType, index) { 296 297 // replace this part if we got new content 298 if (node.ct == contentType && ZmMimePart._isPartOfMultipartAlternative(this.parent) && node.body && !this.getContent()) { 299 var mimePart = new ZmMimePart(this); 300 mimePart._loadProps(node); 301 this.parent.children.replace(index, mimePart); 302 return mimePart; 303 } 304 if (node.mp && node.mp.length) { 305 for (var i = 0; i < node.mp.length; i++) { 306 var mimePart = this.children.get(i); 307 var altPart = mimePart && mimePart.addAlternativePart(node.mp[i], contentType, i); 308 if (altPart) { 309 return altPart; 310 } 311 } 312 } 313 }; 314