1 //
2 // VMime library (http://www.vmime.org)
3 // Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 3 of
8 // the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // Linking this library statically or dynamically with other modules is making
20 // a combined work based on this library.  Thus, the terms and conditions of
21 // the GNU General Public License cover the whole combination.
22 //
23 
24 #include "vmime/attachmentHelper.hpp"
25 
26 #include "vmime/bodyPartAttachment.hpp"
27 #include "vmime/parsedMessageAttachment.hpp"
28 #include "vmime/generatedMessageAttachment.hpp"
29 
30 #include "vmime/disposition.hpp"
31 #include "vmime/emptyContentHandler.hpp"
32 
33 #include <iterator>
34 
35 
36 namespace vmime
37 {
38 
39 
40 // static
isBodyPartAnAttachment(shared_ptr<const bodyPart> part,const unsigned int options)41 bool attachmentHelper::isBodyPartAnAttachment
42 	(shared_ptr <const bodyPart> part, const unsigned int options)
43 {
44 	// First, try with "Content-Disposition" field.
45 	// If not present, we will try with "Content-Type" field.
46 	shared_ptr <const contentDispositionField> cdf =
47 		part->getHeader()->findField <contentDispositionField>(fields::CONTENT_DISPOSITION);
48 
49 	if (cdf)
50 	{
51 		const contentDisposition disp = *cdf->getValue <contentDisposition>();
52 
53 		if (disp.getName() != contentDispositionTypes::INLINE)
54 			return true;
55 
56 		if ((options & INLINE_OBJECTS) == 0)
57 		{
58 			// If the Content-Disposition is 'inline' and there is no
59 			// Content-Id or Content-Location field, it may be an attachment
60 			if (!part->getHeader()->hasField(vmime::fields::CONTENT_ID) &&
61 			    !part->getHeader()->hasField(vmime::fields::CONTENT_LOCATION))
62 			{
63 				// If this is the root part, it might not be an attachment
64 				if (part->getParentPart() == NULL)
65 					return false;
66 
67 				return true;
68 			}
69 
70 			return false;
71 		}
72 	}
73 
74 	// Assume "attachment" if type is not "text/..." or "multipart/...".
75 	mediaType type;
76 	bool hasContentTypeName = false;
77 
78 	shared_ptr <const contentTypeField> ctf =
79 		part->getHeader()->findField <contentTypeField>(fields::CONTENT_TYPE);
80 
81 	if (ctf)
82 	{
83 		type = *ctf->getValue <mediaType>();
84 
85 		if (ctf->hasParameter("name"))
86 			hasContentTypeName = true;
87 	}
88 	else
89 	{
90 		// If this is the root part and no Content-Type field is present,
91 		// then this may not be a MIME message, so do not assume it is
92 		// an attachment
93 		if (part->getParentPart() == NULL)
94 			return false;
95 
96 		// No "Content-type" field: assume "application/octet-stream".
97 		type = mediaType(mediaTypes::APPLICATION,
98 				     mediaTypes::APPLICATION_OCTET_STREAM);
99 	}
100 
101 	if (type.getType() != mediaTypes::TEXT &&
102 	    type.getType() != mediaTypes::MULTIPART)
103 	{
104 		// Compatibility with (obsolete) RFC-1341: if there is a "name" parameter
105 		// on the "Content-Type" field, then we assume it is an attachment
106 		if (hasContentTypeName)
107 			return true;
108 
109 		if ((options & INLINE_OBJECTS) == 0)
110 		{
111 			// If a "Content-Id" field is present, it might be an
112 			// embedded object (MHTML messages)
113 			if (part->getHeader()->hasField(vmime::fields::CONTENT_ID))
114 				return false;
115 		}
116 
117 		return true;
118 	}
119 
120 	return false;
121 }
122 
123 
124 // static
getBodyPartAttachment(shared_ptr<const bodyPart> part,const unsigned int options)125 shared_ptr <const attachment> attachmentHelper::getBodyPartAttachment
126 	(shared_ptr <const bodyPart> part, const unsigned int options)
127 {
128 	if (!isBodyPartAnAttachment(part, options))
129 		return null;
130 
131 	mediaType type;
132 
133 	shared_ptr <const contentTypeField> ctf =
134 		part->getHeader()->findField <contentTypeField>(fields::CONTENT_TYPE);
135 
136 	if (ctf)
137 	{
138 		type = *ctf->getValue <mediaType>();
139 	}
140 	else
141 	{
142 		// No "Content-type" field: assume "application/octet-stream".
143 		type = mediaType(mediaTypes::APPLICATION,
144 		                 mediaTypes::APPLICATION_OCTET_STREAM);
145 	}
146 
147 	if (type.getType() == mediaTypes::MESSAGE &&
148 	    type.getSubType() == mediaTypes::MESSAGE_RFC822)
149 	{
150 		return make_shared <generatedMessageAttachment>(part);
151 	}
152 	else
153 	{
154 		return make_shared <bodyPartAttachment>(part);
155 	}
156 }
157 
158 
159 // static
160 const std::vector <shared_ptr <const attachment> >
findAttachmentsInMessage(shared_ptr<const message> msg,const unsigned int options)161 	attachmentHelper::findAttachmentsInMessage
162 		(shared_ptr <const message> msg, const unsigned int options)
163 {
164 	return findAttachmentsInBodyPart(msg, options);
165 }
166 
167 
168 // static
169 const std::vector <shared_ptr <const attachment> >
findAttachmentsInBodyPart(shared_ptr<const bodyPart> part,const unsigned int options)170 	attachmentHelper::findAttachmentsInBodyPart
171 		(shared_ptr <const bodyPart> part, const unsigned int options)
172 {
173 	std::vector <shared_ptr <const attachment> > atts;
174 
175 	// Test this part
176 	if (isBodyPartAnAttachment(part, options))
177 	{
178 		atts.push_back(getBodyPartAttachment(part, options));
179 	}
180 	// Find in sub-parts
181 	else
182 	{
183 		shared_ptr <const body> bdy = part->getBody();
184 
185 		for (size_t i = 0 ; i < bdy->getPartCount() ; ++i)
186 		{
187 			std::vector <shared_ptr <const attachment> > partAtts =
188 				findAttachmentsInBodyPart(bdy->getPartAt(i), options);
189 
190 			std::copy(partAtts.begin(), partAtts.end(), std::back_inserter(atts));
191 		}
192 	}
193 
194 	return atts;
195 }
196 
197 
198 // static
addAttachment(shared_ptr<message> msg,shared_ptr<attachment> att)199 void attachmentHelper::addAttachment(shared_ptr <message> msg, shared_ptr <attachment> att)
200 {
201 	// We simply search for a "multipart/mixed" part. If no one exists,
202 	// create it in the root part. This (very simple) algorithm should
203 	// work in the most cases.
204 
205 	vmime::mediaType mpMixed(vmime::mediaTypes::MULTIPART,
206 	                         vmime::mediaTypes::MULTIPART_MIXED);
207 
208 	shared_ptr <bodyPart> part = findBodyPart(msg, mpMixed);
209 
210 	if (part == NULL)  // create it
211 	{
212 		if (msg->getBody()->getPartCount() != 0)
213 		{
214 			// Create a new container part for the parts that were in
215 			// the root part of the message
216 			shared_ptr <bodyPart> container = make_shared <bodyPart>();
217 
218 			if (msg->getHeader()->hasField(fields::CONTENT_TYPE))
219 			{
220 				container->getHeader()->ContentType()->setValue
221 					(msg->getHeader()->ContentType()->getValue());
222 			}
223 
224 			if (msg->getHeader()->hasField(fields::CONTENT_TRANSFER_ENCODING))
225 			{
226 				container->getHeader()->ContentTransferEncoding()->setValue
227 					(msg->getHeader()->ContentTransferEncoding()->getValue());
228 			}
229 
230 			// Move parts from the root part to this new part
231 			const std::vector <shared_ptr <bodyPart> > partList =
232 				msg->getBody()->getPartList();
233 
234 			msg->getBody()->removeAllParts();
235 
236 			for (unsigned int i = 0 ; i < partList.size() ; ++i)
237 				container->getBody()->appendPart(partList[i]);
238 
239 			msg->getBody()->appendPart(container);
240 		}
241 		else
242 		{
243 			// The message is a simple (RFC-822) message, and do not
244 			// contains any MIME part. Move the contents from the
245 			// root to a new child part.
246 			shared_ptr <bodyPart> child = make_shared <bodyPart>();
247 
248 			if (msg->getHeader()->hasField(fields::CONTENT_TYPE))
249 			{
250 				child->getHeader()->ContentType()->setValue
251 					(msg->getHeader()->ContentType()->getValue());
252 			}
253 
254 			if (msg->getHeader()->hasField(fields::CONTENT_TRANSFER_ENCODING))
255 			{
256 				child->getHeader()->ContentTransferEncoding()->setValue
257 					(msg->getHeader()->ContentTransferEncoding()->getValue());
258 			}
259 
260 			child->getBody()->setContents(msg->getBody()->getContents());
261 			msg->getBody()->setContents(make_shared <emptyContentHandler>());
262 
263 			msg->getBody()->appendPart(child);
264 		}
265 
266 		// Set the root part to 'multipart/mixed'
267 		msg->getHeader()->ContentType()->setValue(mpMixed);
268 
269 		msg->getHeader()->removeAllFields(vmime::fields::CONTENT_DISPOSITION);
270 		msg->getHeader()->removeAllFields(vmime::fields::CONTENT_TRANSFER_ENCODING);
271 
272 		part = msg;
273 	}
274 
275 	// Generate the attachment part
276 	att->generateIn(part);
277 }
278 
279 
280 // static
findBodyPart(shared_ptr<bodyPart> part,const mediaType & type)281 shared_ptr <bodyPart> attachmentHelper::findBodyPart
282 	(shared_ptr <bodyPart> part, const mediaType& type)
283 {
284 	if (part->getBody()->getContentType() == type)
285 		return part;
286 
287 	// Try in sub-parts
288 	shared_ptr <body> bdy = part->getBody();
289 
290 	for (size_t i = 0 ; i < bdy->getPartCount() ; ++i)
291 	{
292 		shared_ptr <bodyPart> found =
293 			findBodyPart(bdy->getPartAt(i), type);
294 
295 		if (found != NULL)
296 			return found;
297 	}
298 
299 	return null;
300 }
301 
302 
303 // static
addAttachment(shared_ptr<message> msg,shared_ptr<message> amsg)304 void attachmentHelper::addAttachment(shared_ptr <message> msg, shared_ptr <message> amsg)
305 {
306 	shared_ptr <attachment> att = make_shared <parsedMessageAttachment>(amsg);
307 	addAttachment(msg, att);
308 }
309 
310 
311 } // vmime
312 
313