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