1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-multipart.c : Abstract class for a multipart
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
19  */
20 
21 #include "evolution-data-server-config.h"
22 
23 #include <errno.h>
24 #include <string.h> /* strlen() */
25 #include <time.h>   /* for time */
26 #include <unistd.h> /* for getpid */
27 
28 #include "camel-mime-part.h"
29 #include "camel-multipart.h"
30 #include "camel-stream-mem.h"
31 
32 struct _CamelMultipartPrivate {
33 	GPtrArray *parts;
34 	gchar *preface;
35 	gchar *postface;
36 };
37 
G_DEFINE_TYPE_WITH_PRIVATE(CamelMultipart,camel_multipart,CAMEL_TYPE_DATA_WRAPPER)38 G_DEFINE_TYPE_WITH_PRIVATE (CamelMultipart, camel_multipart, CAMEL_TYPE_DATA_WRAPPER)
39 
40 static void
41 multipart_dispose (GObject *object)
42 {
43 	CamelMultipartPrivate *priv;
44 
45 	priv = CAMEL_MULTIPART (object)->priv;
46 
47 	g_ptr_array_set_size (priv->parts, 0);
48 
49 	/* Chain up to parent's dispose() method. */
50 	G_OBJECT_CLASS (camel_multipart_parent_class)->dispose (object);
51 }
52 
53 static void
multipart_finalize(GObject * object)54 multipart_finalize (GObject *object)
55 {
56 	CamelMultipartPrivate *priv;
57 
58 	priv = CAMEL_MULTIPART (object)->priv;
59 
60 	g_ptr_array_unref (priv->parts);
61 
62 	g_free (priv->preface);
63 	g_free (priv->postface);
64 
65 	/* Chain up to parent's finalize() method. */
66 	G_OBJECT_CLASS (camel_multipart_parent_class)->finalize (object);
67 }
68 
69 static gboolean
multipart_is_offline(CamelDataWrapper * data_wrapper)70 multipart_is_offline (CamelDataWrapper *data_wrapper)
71 {
72 	CamelMultipartPrivate *priv;
73 	CamelDataWrapper *part;
74 	guint ii;
75 
76 	priv = CAMEL_MULTIPART (data_wrapper)->priv;
77 
78 	/* Chain up to parent's is_offline() method. */
79 	if (CAMEL_DATA_WRAPPER_CLASS (camel_multipart_parent_class)->is_offline (data_wrapper))
80 		return TRUE;
81 
82 	for (ii = 0; ii < priv->parts->len; ii++) {
83 		part = g_ptr_array_index (priv->parts, ii);
84 		if (camel_data_wrapper_is_offline (part))
85 			return TRUE;
86 	}
87 
88 	return FALSE;
89 }
90 
91 /* this is MIME specific, doesn't belong here really */
92 static gssize
multipart_write_to_stream_sync(CamelDataWrapper * data_wrapper,CamelStream * stream,GCancellable * cancellable,GError ** error)93 multipart_write_to_stream_sync (CamelDataWrapper *data_wrapper,
94                                 CamelStream *stream,
95                                 GCancellable *cancellable,
96                                 GError **error)
97 {
98 	CamelMultipartPrivate *priv;
99 	const gchar *boundary;
100 	gchar *content;
101 	gssize total = 0;
102 	gssize count;
103 	guint ii;
104 
105 	priv = CAMEL_MULTIPART (data_wrapper)->priv;
106 
107 	/* get the bundary text */
108 	boundary = camel_multipart_get_boundary (
109 		CAMEL_MULTIPART (data_wrapper));
110 
111 	/* we cannot write a multipart without a boundary string */
112 	g_return_val_if_fail (boundary, -1);
113 
114 	/*
115 	 * write the preface text (usually something like
116 	 *   "This is a mime message, if you see this, then
117 	 *    your mail client probably doesn't support ...."
118 	 */
119 	if (priv->preface != NULL) {
120 		count = camel_stream_write_string (
121 			stream, priv->preface, cancellable, error);
122 		if (count == -1)
123 			return -1;
124 		total += count;
125 	}
126 
127 	/*
128 	 * Now, write all the parts, separated by the boundary
129 	 * delimiter
130 	 */
131 	for (ii = 0; ii < priv->parts->len; ii++) {
132 		CamelDataWrapper *part;
133 
134 		part = g_ptr_array_index (priv->parts, ii);
135 
136 		content = g_strdup_printf ("\n--%s\n", boundary);
137 		count = camel_stream_write_string (
138 			stream, content, cancellable, error);
139 		g_free (content);
140 		if (count == -1)
141 			return -1;
142 		total += count;
143 
144 		count = camel_data_wrapper_write_to_stream_sync (
145 			part, stream, cancellable, error);
146 		if (count == -1)
147 			return -1;
148 		total += count;
149 	}
150 
151 	/* write the terminating boudary delimiter */
152 	content = g_strdup_printf ("\n--%s--\n", boundary);
153 	count = camel_stream_write_string (
154 		stream, content, cancellable, error);
155 	g_free (content);
156 	if (count == -1)
157 		return -1;
158 	total += count;
159 
160 	/* and finally the postface */
161 	if (priv->postface != NULL) {
162 		count = camel_stream_write_string (
163 			stream, priv->postface, cancellable, error);
164 		if (count == -1)
165 			return -1;
166 		total += count;
167 	}
168 
169 	return total;
170 }
171 
172 /* this is MIME specific, doesn't belong here really */
173 static gssize
multipart_write_to_output_stream_sync(CamelDataWrapper * data_wrapper,GOutputStream * output_stream,GCancellable * cancellable,GError ** error)174 multipart_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
175                                        GOutputStream *output_stream,
176                                        GCancellable *cancellable,
177                                        GError **error)
178 {
179 	CamelMultipartPrivate *priv;
180 	const gchar *boundary;
181 	gchar *content;
182 	gsize bytes_written;
183 	gssize total = 0;
184 	gboolean success;
185 	guint ii;
186 
187 	priv = CAMEL_MULTIPART (data_wrapper)->priv;
188 
189 	/* get the bundary text */
190 	boundary = camel_multipart_get_boundary (
191 		CAMEL_MULTIPART (data_wrapper));
192 
193 	/* we cannot write a multipart without a boundary string */
194 	g_return_val_if_fail (boundary, -1);
195 
196 	/*
197 	 * write the preface text (usually something like
198 	 *   "This is a mime message, if you see this, then
199 	 *    your mail client probably doesn't support ...."
200 	 */
201 	if (priv->preface != NULL) {
202 		success = g_output_stream_write_all (
203 			output_stream,
204 			priv->preface, strlen (priv->preface),
205 			&bytes_written, cancellable, error);
206 		if (!success)
207 			return -1;
208 		total += (gsize) bytes_written;
209 	}
210 
211 	/*
212 	 * Now, write all the parts, separated by the boundary
213 	 * delimiter
214 	 */
215 	for (ii = 0; ii < priv->parts->len; ii++) {
216 		CamelDataWrapper *part;
217 		gssize result;
218 
219 		part = g_ptr_array_index (priv->parts, ii);
220 
221 		content = g_strdup_printf ("\n--%s\n", boundary);
222 		success = g_output_stream_write_all (
223 			output_stream,
224 			content, strlen (content),
225 			&bytes_written, cancellable, error);
226 		g_free (content);
227 		if (!success)
228 			return -1;
229 		total += (gsize) bytes_written;
230 
231 		result = camel_data_wrapper_write_to_output_stream_sync (
232 			part, output_stream, cancellable, error);
233 		if (result == -1)
234 			return -1;
235 		total += result;
236 	}
237 
238 	/* write the terminating boudary delimiter */
239 	content = g_strdup_printf ("\n--%s--\n", boundary);
240 	success = g_output_stream_write_all (
241 		output_stream,
242 		content, strlen (content),
243 		&bytes_written, cancellable, error);
244 	g_free (content);
245 	if (!success)
246 		return -1;
247 	total += (gsize) bytes_written;
248 
249 	/* and finally the postface */
250 	if (priv->postface != NULL) {
251 		success = g_output_stream_write_all (
252 			output_stream,
253 			priv->postface, strlen (priv->postface),
254 			&bytes_written, cancellable, error);
255 		if (!success)
256 			return -1;
257 		total += (gsize) bytes_written;
258 	}
259 
260 	return total;
261 }
262 
263 static void
multipart_add_part(CamelMultipart * multipart,CamelMimePart * part)264 multipart_add_part (CamelMultipart *multipart,
265                     CamelMimePart *part)
266 {
267 	g_ptr_array_add (multipart->priv->parts, g_object_ref (part));
268 }
269 
270 static CamelMimePart *
multipart_get_part(CamelMultipart * multipart,guint index)271 multipart_get_part (CamelMultipart *multipart,
272                     guint index)
273 {
274 	if (index >= multipart->priv->parts->len)
275 		return NULL;
276 
277 	return g_ptr_array_index (multipart->priv->parts, index);
278 }
279 
280 static guint
multipart_get_number(CamelMultipart * multipart)281 multipart_get_number (CamelMultipart *multipart)
282 {
283 	return multipart->priv->parts->len;
284 }
285 
286 static void
multipart_set_boundary(CamelMultipart * multipart,const gchar * boundary)287 multipart_set_boundary (CamelMultipart *multipart,
288                         const gchar *boundary)
289 {
290 	CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
291 	gchar *bgen, bbuf[27], *p;
292 	guint8 *digest;
293 	gsize length;
294 	gint state, save;
295 
296 	g_return_if_fail (camel_data_wrapper_get_mime_type_field (cdw) != NULL);
297 
298 	length = g_checksum_type_get_length (G_CHECKSUM_MD5);
299 	digest = g_alloca (length);
300 
301 	if (!boundary) {
302 		GChecksum *checksum;
303 
304 		/* Generate a fairly random boundary string. */
305 		bgen = g_strdup_printf (
306 			"%p:%lu:%lu",
307 			(gpointer) multipart,
308 			(gulong) getpid (),
309 			(gulong) time (NULL));
310 
311 		checksum = g_checksum_new (G_CHECKSUM_MD5);
312 		g_checksum_update (checksum, (guchar *) bgen, -1);
313 		g_checksum_get_digest (checksum, digest, &length);
314 		g_checksum_free (checksum);
315 
316 		g_free (bgen);
317 		g_strlcpy (bbuf, "=-", sizeof (bbuf));
318 		p = bbuf + 2;
319 		state = save = 0;
320 		p += g_base64_encode_step (
321 			(guchar *) digest, length, FALSE, p, &state, &save);
322 		*p = '\0';
323 
324 		boundary = bbuf;
325 	}
326 
327 	camel_content_type_set_param (camel_data_wrapper_get_mime_type_field (cdw), "boundary", boundary);
328 }
329 
330 static const gchar *
multipart_get_boundary(CamelMultipart * multipart)331 multipart_get_boundary (CamelMultipart *multipart)
332 {
333 	CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
334 
335 	g_return_val_if_fail (camel_data_wrapper_get_mime_type_field (cdw) != NULL, NULL);
336 	return camel_content_type_param (camel_data_wrapper_get_mime_type_field (cdw), "boundary");
337 }
338 
339 static gint
multipart_construct_from_parser(CamelMultipart * multipart,CamelMimeParser * mp)340 multipart_construct_from_parser (CamelMultipart *multipart,
341                                  CamelMimeParser *mp)
342 {
343 	gint err;
344 	CamelContentType *content_type;
345 	CamelMimePart *bodypart;
346 	gchar *buf;
347 	gsize len;
348 
349 	g_return_val_if_fail (camel_mime_parser_state (mp) == CAMEL_MIME_PARSER_STATE_MULTIPART, -1);
350 
351 	content_type = camel_mime_parser_content_type (mp);
352 	camel_multipart_set_boundary (
353 		multipart,
354 		camel_content_type_param (content_type, "boundary"));
355 
356 	while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
357 		camel_mime_parser_unstep (mp);
358 		bodypart = camel_mime_part_new ();
359 		camel_mime_part_construct_from_parser_sync (
360 			bodypart, mp, NULL, NULL);
361 		camel_multipart_add_part (multipart, bodypart);
362 		g_object_unref (bodypart);
363 	}
364 
365 	/* these are only return valid data in the MULTIPART_END state */
366 	camel_multipart_set_preface (multipart, camel_mime_parser_preface (mp));
367 	camel_multipart_set_postface (multipart, camel_mime_parser_postface (mp));
368 
369 	err = camel_mime_parser_errno (mp);
370 	if (err != 0) {
371 		errno = err;
372 		return -1;
373 	} else
374 		return 0;
375 }
376 
377 static void
camel_multipart_class_init(CamelMultipartClass * class)378 camel_multipart_class_init (CamelMultipartClass *class)
379 {
380 	GObjectClass *object_class;
381 	CamelDataWrapperClass *data_wrapper_class;
382 
383 	object_class = G_OBJECT_CLASS (class);
384 	object_class->dispose = multipart_dispose;
385 	object_class->finalize = multipart_finalize;
386 
387 	data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
388 	data_wrapper_class->is_offline = multipart_is_offline;
389 	data_wrapper_class->write_to_stream_sync = multipart_write_to_stream_sync;
390 	data_wrapper_class->decode_to_stream_sync = multipart_write_to_stream_sync;
391 	data_wrapper_class->write_to_output_stream_sync = multipart_write_to_output_stream_sync;
392 	data_wrapper_class->decode_to_output_stream_sync = multipart_write_to_output_stream_sync;
393 
394 	class->add_part = multipart_add_part;
395 	class->get_part = multipart_get_part;
396 	class->get_number = multipart_get_number;
397 	class->set_boundary = multipart_set_boundary;
398 	class->get_boundary = multipart_get_boundary;
399 	class->construct_from_parser = multipart_construct_from_parser;
400 }
401 
402 static void
camel_multipart_init(CamelMultipart * multipart)403 camel_multipart_init (CamelMultipart *multipart)
404 {
405 	multipart->priv = camel_multipart_get_instance_private (multipart);
406 
407 	multipart->priv->parts =
408 		g_ptr_array_new_with_free_func (g_object_unref);
409 
410 	camel_data_wrapper_set_mime_type (
411 		CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
412 }
413 
414 /**
415  * camel_multipart_new:
416  *
417  * Create a new #CamelMultipart object.
418  *
419  * Returns: a new #CamelMultipart object
420  **/
421 CamelMultipart *
camel_multipart_new(void)422 camel_multipart_new (void)
423 {
424 	return g_object_new (CAMEL_TYPE_MULTIPART, NULL);
425 }
426 
427 /**
428  * camel_multipart_add_part:
429  * @multipart: a #CamelMultipart object
430  * @part: a #CamelMimePart to add
431  *
432  * Appends the part to the multipart object.
433  **/
434 void
camel_multipart_add_part(CamelMultipart * multipart,CamelMimePart * part)435 camel_multipart_add_part (CamelMultipart *multipart,
436                           CamelMimePart *part)
437 {
438 	CamelMultipartClass *class;
439 
440 	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
441 	g_return_if_fail (CAMEL_IS_MIME_PART (part));
442 
443 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
444 	g_return_if_fail (class != NULL);
445 	g_return_if_fail (class->add_part != NULL);
446 
447 	class->add_part (multipart, part);
448 }
449 
450 /**
451  * camel_multipart_get_part:
452  * @multipart: a #CamelMultipart object
453  * @index: a zero-based index indicating the part to get
454  *
455  * Returns: (transfer none): the indicated subpart, or %NULL
456  **/
457 CamelMimePart *
camel_multipart_get_part(CamelMultipart * multipart,guint index)458 camel_multipart_get_part (CamelMultipart *multipart,
459                           guint index)
460 {
461 	CamelMultipartClass *class;
462 
463 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
464 
465 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
466 	g_return_val_if_fail (class != NULL, NULL);
467 	g_return_val_if_fail (class->get_part != NULL, NULL);
468 
469 	return class->get_part (multipart, index);
470 }
471 
472 /**
473  * camel_multipart_get_number:
474  * @multipart: a #CamelMultipart object
475  *
476  * Returns: the number of subparts in @multipart
477  **/
478 guint
camel_multipart_get_number(CamelMultipart * multipart)479 camel_multipart_get_number (CamelMultipart *multipart)
480 {
481 	CamelMultipartClass *class;
482 
483 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), 0);
484 
485 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
486 	g_return_val_if_fail (class != NULL, 0);
487 	g_return_val_if_fail (class->get_number != NULL, 0);
488 
489 	return class->get_number (multipart);
490 }
491 
492 /**
493  * camel_multipart_get_boundary:
494  * @multipart: a #CamelMultipart object
495  *
496  * Returns: the boundary
497  **/
498 const gchar *
camel_multipart_get_boundary(CamelMultipart * multipart)499 camel_multipart_get_boundary (CamelMultipart *multipart)
500 {
501 	CamelMultipartClass *class;
502 
503 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
504 
505 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
506 	g_return_val_if_fail (class != NULL, NULL);
507 	g_return_val_if_fail (class->get_boundary != NULL, NULL);
508 
509 	return class->get_boundary (multipart);
510 }
511 
512 /**
513  * camel_multipart_set_boundary:
514  * @multipart: a #CamelMultipart object
515  * @boundary: (nullable): the message boundary, or %NULL
516  *
517  * Sets the message boundary for @multipart to @boundary. This should
518  * be a string which does not occur anywhere in any of @multipart's
519  * subparts. If @boundary is %NULL, a randomly-generated boundary will
520  * be used.
521  **/
522 void
camel_multipart_set_boundary(CamelMultipart * multipart,const gchar * boundary)523 camel_multipart_set_boundary (CamelMultipart *multipart,
524                               const gchar *boundary)
525 {
526 	CamelMultipartClass *class;
527 
528 	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
529 
530 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
531 	g_return_if_fail (class != NULL);
532 	g_return_if_fail (class->set_boundary != NULL);
533 
534 	class->set_boundary (multipart, boundary);
535 }
536 
537 /**
538  * camel_multipart_get_preface:
539  * @multipart: a #CamelMultipart
540  *
541  * Returns the preface text for @multipart.
542  *
543  * Returns: the preface text
544  *
545  * Since: 3.12
546  **/
547 const gchar *
camel_multipart_get_preface(CamelMultipart * multipart)548 camel_multipart_get_preface (CamelMultipart *multipart)
549 {
550 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
551 
552 	return multipart->priv->preface;
553 }
554 
555 /**
556  * camel_multipart_set_preface:
557  * @multipart: a #CamelMultipart object
558  * @preface: the multipart preface
559  *
560  * Set the preface text for this multipart.  Will be written out infront
561  * of the multipart.  This text should only include US-ASCII strings, and
562  * be relatively short, and will be ignored by any MIME mail client.
563  **/
564 void
camel_multipart_set_preface(CamelMultipart * multipart,const gchar * preface)565 camel_multipart_set_preface (CamelMultipart *multipart,
566                              const gchar *preface)
567 {
568 	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
569 
570 	if (multipart->priv->preface == preface)
571 		return;
572 
573 	g_free (multipart->priv->preface);
574 	multipart->priv->preface = g_strdup (preface);
575 }
576 
577 /**
578  * camel_multipart_get_postface:
579  * @multipart: a #CamelMultipart
580  *
581  * Returns the postface text for @multipart.
582  *
583  * Returns: the postface text
584  *
585  * Since: 3.12
586  **/
587 const gchar *
camel_multipart_get_postface(CamelMultipart * multipart)588 camel_multipart_get_postface (CamelMultipart *multipart)
589 {
590 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
591 
592 	return multipart->priv->postface;
593 }
594 
595 /**
596  * camel_multipart_set_postface:
597  * @multipart: a #CamelMultipart object
598  * @postface: multipat postface
599  *
600  * Set the postface text for this multipart.  Will be written out after
601  * the last boundary of the multipart, and ignored by any MIME mail
602  * client.
603  *
604  * Generally postface texts should not be sent with multipart messages.
605  **/
606 void
camel_multipart_set_postface(CamelMultipart * multipart,const gchar * postface)607 camel_multipart_set_postface (CamelMultipart *multipart,
608                               const gchar *postface)
609 {
610 	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
611 
612 	if (multipart->priv->postface == postface)
613 		return;
614 
615 	g_free (multipart->priv->postface);
616 	multipart->priv->postface = g_strdup (postface);
617 }
618 
619 /**
620  * camel_multipart_construct_from_parser:
621  * @multipart: a #CamelMultipart object
622  * @parser: a #CamelMimeParser object
623  *
624  * Construct a multipart from a parser.
625  *
626  * Returns: 0 on success or -1 on fail
627  **/
628 gint
camel_multipart_construct_from_parser(CamelMultipart * multipart,CamelMimeParser * mp)629 camel_multipart_construct_from_parser (CamelMultipart *multipart,
630                                        CamelMimeParser *mp)
631 {
632 	CamelMultipartClass *class;
633 
634 	g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), -1);
635 	g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), -1);
636 
637 	class = CAMEL_MULTIPART_GET_CLASS (multipart);
638 	g_return_val_if_fail (class != NULL, -1);
639 	g_return_val_if_fail (class->construct_from_parser != NULL, -1);
640 
641 	return class->construct_from_parser (multipart, mp);
642 }
643