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