1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 * camel-multipart.c : Abstract class for a multipart
4 * Authors: Michael Zucchi <notzed@ximian.com>
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
33 #include <glib/gi18n-lib.h>
35 #include "camel-mime-filter-canon.h"
36 #include "camel-mime-filter-crlf.h"
37 #include "camel-mime-message.h"
38 #include "camel-mime-parser.h"
39 #include "camel-mime-part.h"
40 #include "camel-multipart-signed.h"
41 #include "camel-stream-filter.h"
42 #include "camel-stream-mem.h"
44 #define d(x) /* (printf("%s(%d): ", __FILE__, __LINE__),(x)) */
46 G_DEFINE_TYPE (CamelMultipartSigned, camel_multipart_signed, CAMEL_TYPE_MULTIPART)
49 multipart_signed_clip_stream (CamelMultipartSigned *mps,
53 CamelDataWrapper *data_wrapper;
57 g_return_val_if_fail (start != -1, NULL);
58 g_return_val_if_fail (end != -1, NULL);
59 g_return_val_if_fail (end >= start, NULL);
61 data_wrapper = CAMEL_DATA_WRAPPER (mps);
63 src = camel_data_wrapper_get_byte_array (data_wrapper);
64 dst = g_byte_array_new ();
66 if (start >= 0 && end < src->len) {
67 const guint8 *data = &src->data[start];
68 g_byte_array_append (dst, data, end - start);
71 /* Stream takes ownership of the byte array. */
72 return camel_stream_mem_new_with_byte_array (dst);
76 multipart_signed_skip_content (CamelMimeParser *cmp)
82 switch (camel_mime_parser_state (cmp)) {
83 case CAMEL_MIME_PARSER_STATE_HEADER:
85 while (camel_mime_parser_step (cmp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
88 case CAMEL_MIME_PARSER_STATE_MESSAGE:
89 /* message body part */
90 (void) camel_mime_parser_step (cmp, &buf, &len);
91 multipart_signed_skip_content (cmp);
93 /* clean up followon state if any, see camel-mime-message.c */
94 state = camel_mime_parser_step (cmp, &buf, &len);
96 case CAMEL_MIME_PARSER_STATE_EOF:
97 case CAMEL_MIME_PARSER_STATE_FROM_END: /* these doesn't belong to us */
98 camel_mime_parser_unstep (cmp);
99 case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
102 g_error ("Bad parser state: Expecting MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (cmp));
103 camel_mime_parser_unstep (cmp);
107 case CAMEL_MIME_PARSER_STATE_MULTIPART:
108 /* embedded multipart */
109 while (camel_mime_parser_step (cmp, &buf, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END)
110 multipart_signed_skip_content (cmp);
113 g_warning ("Invalid state encountered???: %u", camel_mime_parser_state (cmp));
120 multipart_signed_parse_content (CamelMultipartSigned *mps)
122 CamelMimeParser *cmp;
123 CamelMultipart *mp = (CamelMultipart *) mps;
124 CamelDataWrapper *data_wrapper;
125 GByteArray *byte_array;
127 const gchar *boundary;
132 boundary = camel_multipart_get_boundary (mp);
133 g_return_val_if_fail (boundary != NULL, -1);
135 data_wrapper = CAMEL_DATA_WRAPPER (mps);
136 byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
138 stream = camel_stream_mem_new ();
140 /* Do not give the stream ownership of the byte array. */
141 camel_stream_mem_set_byte_array (
142 CAMEL_STREAM_MEM (stream), byte_array);
144 /* This is all seriously complex.
145 * This is so we can parse all cases properly, without altering the content.
146 * All we are doing is finding part offsets. */
148 cmp = camel_mime_parser_new ();
149 camel_mime_parser_init_with_stream (cmp, stream, NULL);
150 camel_mime_parser_push_state (cmp, CAMEL_MIME_PARSER_STATE_MULTIPART, boundary);
157 while ((state = camel_mime_parser_step (cmp, &buf, &len)) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
158 if (mps->start1 == -1) {
159 mps->start1 = camel_mime_parser_tell_start_headers (cmp);
160 } else if (mps->start2 == -1) {
161 mps->start2 = camel_mime_parser_tell_start_headers (cmp);
162 mps->end1 = camel_mime_parser_tell_start_boundary (cmp);
163 if (mps->end1 > mps->start1 && byte_array->data[mps->end1 - 1] == '\n')
165 if (mps->end1 > mps->start1 && byte_array->data[mps->end1 - 1] == '\r')
168 g_warning ("multipart/signed has more than 2 parts, remaining parts ignored");
169 state = CAMEL_MIME_PARSER_STATE_MULTIPART_END;
173 if (multipart_signed_skip_content (cmp) == -1)
177 if (state == CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
178 mps->end2 = camel_mime_parser_tell_start_boundary (cmp);
180 camel_multipart_set_preface (mp, camel_mime_parser_preface (cmp));
181 camel_multipart_set_postface (mp, camel_mime_parser_postface (cmp));
184 g_object_unref (cmp);
185 g_object_unref (stream);
187 if (mps->end2 == -1 || mps->start2 == -1) {
198 multipart_signed_dispose (GObject *object)
200 CamelMultipartSigned *multipart;
202 multipart = CAMEL_MULTIPART_SIGNED (object);
204 if (multipart->signature != NULL) {
205 g_object_unref (multipart->signature);
206 multipart->signature = NULL;
209 if (multipart->content != NULL) {
210 g_object_unref (multipart->content);
211 multipart->content = NULL;
214 if (multipart->contentraw != NULL) {
215 g_object_unref (multipart->contentraw);
216 multipart->contentraw = NULL;
219 /* Chain up to parent's dispose() method. */
220 G_OBJECT_CLASS (camel_multipart_signed_parent_class)->dispose (object);
224 multipart_signed_finalize (GObject *object)
226 CamelMultipartSigned *multipart;
228 multipart = CAMEL_MULTIPART_SIGNED (object);
230 g_free (multipart->protocol);
231 g_free (multipart->micalg);
233 /* Chain up to parent's finalize() method. */
234 G_OBJECT_CLASS (camel_multipart_signed_parent_class)->finalize (object);
238 multipart_signed_set_mime_type_field (CamelDataWrapper *data_wrapper,
239 CamelContentType *mime_type)
241 CamelDataWrapperClass *data_wrapper_class;
242 CamelMultipartSigned *mps = (CamelMultipartSigned *) data_wrapper;
244 /* we snoop the mime type to get boundary and hash info */
246 /* Chain up to parent's set_mime_type_field() method. */
247 data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (camel_multipart_signed_parent_class);
248 data_wrapper_class->set_mime_type_field (data_wrapper, mime_type);
251 const gchar *micalg, *protocol;
253 protocol = camel_content_type_param (mime_type, "protocol");
254 g_free (mps->protocol);
255 mps->protocol = g_strdup (protocol);
257 micalg = camel_content_type_param (mime_type, "micalg");
258 g_free (mps->micalg);
259 mps->micalg = g_strdup (micalg);
264 multipart_signed_write_to_stream_sync (CamelDataWrapper *data_wrapper,
266 GCancellable *cancellable,
269 CamelMultipartSigned *mps = (CamelMultipartSigned *) data_wrapper;
270 CamelMultipart *mp = (CamelMultipart *) mps;
271 GByteArray *byte_array;
272 const gchar *boundary;
277 byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
279 /* we have 3 basic cases:
280 * 1. constructed, we write out the data wrapper stream we got
281 * 2. signed content, we create and write out a new stream
286 /* FIXME: locking? */
287 if (byte_array->len > 0) {
288 return camel_stream_write (
289 stream, (gchar *) byte_array->data,
290 byte_array->len, cancellable, error);
294 if (mps->contentraw == NULL) {
296 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
297 _("No content available"));
302 if (mps->signature == NULL) {
304 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
305 _("No signature available"));
310 boundary = camel_multipart_get_boundary (mp);
312 count = camel_stream_write_string (
313 stream, mp->preface, cancellable, error);
320 content = g_strdup_printf ("\n--%s\n", boundary);
321 count = camel_stream_write_string (
322 stream, content, cancellable, error);
328 /* output content part */
329 /* XXX Both CamelGpgContext and CamelSMIMEContext set this
330 * to a memory stream, so assume it's always seekable. */
332 G_SEEKABLE (mps->contentraw), 0, G_SEEK_SET, NULL, NULL);
333 count = camel_stream_write_to_stream (
334 mps->contentraw, stream, cancellable, error);
340 content = g_strdup_printf ("\n--%s\n", boundary);
341 count = camel_stream_write_string (
342 stream, content, cancellable, error);
349 count = camel_data_wrapper_write_to_stream_sync (
350 CAMEL_DATA_WRAPPER (mps->signature),
351 stream, cancellable, error);
356 /* write the terminating boudary delimiter */
357 content = g_strdup_printf ("\n--%s--\n", boundary);
358 count = camel_stream_write_string (
359 stream, content, cancellable, error);
365 /* and finally the postface */
367 count = camel_stream_write_string (
368 stream, mp->postface, cancellable, error);
378 multipart_signed_construct_from_stream_sync (CamelDataWrapper *data_wrapper,
380 GCancellable *cancellable,
383 CamelDataWrapperClass *parent_class;
384 CamelMultipartSigned *mps;
387 mps = CAMEL_MULTIPART_SIGNED (data_wrapper);
389 /* Chain up to parent's construct_from_stream_sync() method. */
390 parent_class = CAMEL_DATA_WRAPPER_CLASS (
391 camel_multipart_signed_parent_class);
392 success = parent_class->construct_from_stream_sync (
393 data_wrapper, stream, cancellable, error);
397 if (mps->content != NULL) {
398 g_object_unref (mps->content);
401 if (mps->contentraw != NULL) {
402 g_object_unref (mps->contentraw);
403 mps->contentraw = NULL;
405 if (mps->signature != NULL) {
406 g_object_unref (mps->signature);
407 mps->signature = NULL;
415 multipart_signed_add_part (CamelMultipart *multipart,
418 g_warning ("Cannot add parts to a signed part using add_part");
422 multipart_signed_add_part_at (CamelMultipart *multipart,
426 g_warning ("Cannot add parts to a signed part using add_part_at");
430 multipart_signed_remove_part (CamelMultipart *multipart,
433 g_warning ("Cannot remove parts from a signed part using remove_part");
436 static CamelMimePart *
437 multipart_signed_remove_part_at (CamelMultipart *multipart,
440 g_warning ("Cannot remove parts from a signed part using remove_part");
445 static CamelMimePart *
446 multipart_signed_get_part (CamelMultipart *multipart,
449 CamelMultipartSigned *mps = (CamelMultipartSigned *) multipart;
450 CamelDataWrapper *data_wrapper;
452 GByteArray *byte_array;
454 data_wrapper = CAMEL_DATA_WRAPPER (multipart);
455 byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
458 case CAMEL_MULTIPART_SIGNED_CONTENT:
461 if (mps->contentraw) {
462 g_return_val_if_fail (
463 G_IS_SEEKABLE (mps->contentraw), NULL);
464 stream = g_object_ref (mps->contentraw);
465 } else if (mps->start1 == -1
466 && multipart_signed_parse_content (mps) == -1
467 && byte_array->len == 0) {
468 g_warning ("Trying to get content on an invalid multipart/signed");
470 } else if (byte_array->len == 0) {
472 } else if (mps->start1 == -1) {
473 stream = camel_stream_mem_new ();
474 camel_stream_mem_set_byte_array (
475 CAMEL_STREAM_MEM (stream), byte_array);
477 stream = multipart_signed_clip_stream (
478 mps, mps->start1, mps->end1);
481 G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
482 mps->content = camel_mime_part_new ();
483 camel_data_wrapper_construct_from_stream_sync (
484 CAMEL_DATA_WRAPPER (mps->content), stream, NULL, NULL);
485 g_object_unref (stream);
487 case CAMEL_MULTIPART_SIGNED_SIGNATURE:
489 return mps->signature;
490 if (mps->start1 == -1
491 && multipart_signed_parse_content (mps) == -1) {
492 g_warning ("Trying to get signature on invalid multipart/signed");
494 } else if (byte_array->len == 0) {
497 stream = multipart_signed_clip_stream (
498 mps, mps->start2, mps->end2);
500 G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
501 mps->signature = camel_mime_part_new ();
502 camel_data_wrapper_construct_from_stream_sync (
503 CAMEL_DATA_WRAPPER (mps->signature),
505 g_object_unref (stream);
506 return mps->signature;
508 g_warning ("trying to get object out of bounds for multipart");
515 multipart_signed_get_number (CamelMultipart *multipart)
517 CamelMultipartSigned *mps = (CamelMultipartSigned *) multipart;
518 CamelDataWrapper *data_wrapper;
519 GByteArray *byte_array;
521 data_wrapper = CAMEL_DATA_WRAPPER (multipart);
522 byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
524 /* check what we have, so we return something reasonable */
526 if ((mps->content || mps->contentraw) && mps->signature)
529 if (mps->start1 == -1 && multipart_signed_parse_content (mps) == -1) {
530 if (byte_array->len == 0)
540 multipart_signed_construct_from_parser (CamelMultipart *multipart,
544 CamelContentType *content_type;
545 CamelMultipartSigned *mps = (CamelMultipartSigned *) multipart;
546 CamelDataWrapper *data_wrapper;
547 GByteArray *byte_array;
551 /* we *must not* be in multipart state, otherwise the mime parser will
552 * parse the headers which is a no no @#$@# stupid multipart/signed spec */
553 g_assert (camel_mime_parser_state (mp) == CAMEL_MIME_PARSER_STATE_HEADER);
555 /* All we do is copy it to a memstream */
556 content_type = camel_mime_parser_content_type (mp);
557 camel_multipart_set_boundary (multipart, camel_content_type_param (content_type, "boundary"));
559 data_wrapper = CAMEL_DATA_WRAPPER (multipart);
560 byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
562 /* Wipe any previous contents from the byte array. */
563 g_byte_array_set_size (byte_array, 0);
565 while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
566 g_byte_array_append (byte_array, (guint8 *) buf, len);
570 g_object_unref (mps->content);
573 if (mps->contentraw) {
574 g_object_unref (mps->contentraw);
575 mps->contentraw = NULL;
577 if (mps->signature) {
578 g_object_unref (mps->signature);
579 mps->signature = NULL;
582 err = camel_mime_parser_errno (mp);
591 camel_multipart_signed_class_init (CamelMultipartSignedClass *class)
593 GObjectClass *object_class;
594 CamelDataWrapperClass *data_wrapper_class;
595 CamelMultipartClass *multipart_class;
597 object_class = G_OBJECT_CLASS (class);
598 object_class->dispose = multipart_signed_dispose;
599 object_class->finalize = multipart_signed_finalize;
601 data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
602 data_wrapper_class->set_mime_type_field = multipart_signed_set_mime_type_field;
603 data_wrapper_class->write_to_stream_sync = multipart_signed_write_to_stream_sync;
604 data_wrapper_class->decode_to_stream_sync = multipart_signed_write_to_stream_sync;
605 data_wrapper_class->construct_from_stream_sync = multipart_signed_construct_from_stream_sync;
607 multipart_class = CAMEL_MULTIPART_CLASS (class);
608 multipart_class->add_part = multipart_signed_add_part;
609 multipart_class->add_part_at = multipart_signed_add_part_at;
610 multipart_class->remove_part = multipart_signed_remove_part;
611 multipart_class->remove_part_at = multipart_signed_remove_part_at;
612 multipart_class->get_part = multipart_signed_get_part;
613 multipart_class->get_number = multipart_signed_get_number;
614 multipart_class->construct_from_parser = multipart_signed_construct_from_parser;
618 camel_multipart_signed_init (CamelMultipartSigned *multipart)
620 camel_data_wrapper_set_mime_type (
621 CAMEL_DATA_WRAPPER (multipart), "multipart/signed");
623 multipart->start1 = -1;
627 * camel_multipart_signed_new:
629 * Create a new #CamelMultipartSigned object.
631 * A MultipartSigned should be used to store and create parts of
632 * type "multipart/signed". This is because multipart/signed is
633 * entirely broken-by-design (tm) and uses completely
634 * different semantics to other mutlipart types. It must be treated
635 * as opaque data by any transport. See rfc 3156 for details.
637 * There are 3 ways to create the part:
638 * Use construct_from_stream. If this is used, then you must
639 * set the mime_type appropriately to match the data uses, so
640 * that the multiple parts my be extracted.
642 * Use construct_from_parser. The parser MUST be in the #CAMEL_MIME_PARSER_STATE_HEADER
643 * state, and the current content_type MUST be "multipart/signed" with
644 * the appropriate boundary and it SHOULD include the appropriate protocol
645 * and hash specifiers.
647 * Use sign_part. A signature part will automatically be created
648 * and the whole part may be written using write_to_stream to
649 * create a 'transport-safe' version (as safe as can be expected with
650 * such a broken specification).
652 * Returns: a new #CamelMultipartSigned object
654 CamelMultipartSigned *
655 camel_multipart_signed_new (void)
657 return g_object_new (CAMEL_TYPE_MULTIPART_SIGNED, NULL);
661 * camel_multipart_signed_get_content_stream:
662 * @mps: a #CamlMultipartSigned object
663 * @error: return location for a #GError, or %NULL
665 * Get the raw signed content stream of the multipart/signed MIME part
666 * suitable for use with verification of the signature.
668 * Returns: the signed content stream
671 camel_multipart_signed_get_content_stream (CamelMultipartSigned *mps,
674 CamelStream *constream;
676 /* we need to be able to verify stuff we just signed as well as stuff we loaded from a stream/parser */
678 if (mps->contentraw) {
679 constream = g_object_ref (mps->contentraw);
681 CamelStream *base_stream;
682 CamelStream *filter_stream;
683 CamelMimeFilter *canon_filter;
685 if (mps->start1 == -1 && multipart_signed_parse_content (mps) == -1) {
693 /* first, prepare our parts */
695 base_stream = multipart_signed_clip_stream (
696 mps, mps->start1, mps->end1);
698 filter_stream = camel_stream_filter_new (base_stream);
700 /* Note: see rfc2015 or rfc3156, section 5 */
701 canon_filter = camel_mime_filter_canon_new (
702 CAMEL_MIME_FILTER_CANON_CRLF);
703 camel_stream_filter_add (
704 CAMEL_STREAM_FILTER (filter_stream), canon_filter);
705 g_object_unref (canon_filter);
707 /* We want to return a pure memory stream,
708 * so apply the CRLF filter ahead of time. */
709 constream = camel_stream_mem_new ();
710 camel_stream_write_to_stream (
711 filter_stream, constream, NULL, NULL);
713 g_object_unref (filter_stream);
714 g_object_unref (base_stream);
716 /* rewind to the beginning of a stream */
717 g_seekable_seek (G_SEEKABLE (constream), 0, G_SEEK_SET, NULL, NULL);