/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* GMime * Copyright (C) 2000-2007 Jeffrey Stedfast * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "gmime-message-partial.h" #include "gmime-stream-cat.h" #include "gmime-stream-mem.h" #include "gmime-parser.h" /* GObject class methods */ static void g_mime_message_partial_class_init (GMimeMessagePartialClass *klass); static void g_mime_message_partial_init (GMimeMessagePartial *catpart, GMimeMessagePartialClass *klass); static void g_mime_message_partial_finalize (GObject *object); /* GMimeObject class methods */ static void message_partial_init (GMimeObject *object); static void message_partial_add_header (GMimeObject *object, const char *header, const char *value); static void message_partial_set_header (GMimeObject *object, const char *header, const char *value); static void message_partial_set_content_type (GMimeObject *object, GMimeContentType *content_type); static GMimePartClass *parent_class = NULL; GType g_mime_message_partial_get_type (void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof (GMimeMessagePartialClass), NULL, /* base_class_init */ NULL, /* base_class_finalize */ (GClassInitFunc) g_mime_message_partial_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GMimeMessagePartial), 0, /* n_preallocs */ (GInstanceInitFunc) g_mime_message_partial_init, }; type = g_type_register_static (GMIME_TYPE_PART, "GMimeMessagePartial", &info, 0); } return type; } static void g_mime_message_partial_class_init (GMimeMessagePartialClass *klass) { GMimeObjectClass *object_class = GMIME_OBJECT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_ref (GMIME_TYPE_PART); gobject_class->finalize = g_mime_message_partial_finalize; object_class->init = message_partial_init; object_class->add_header = message_partial_add_header; object_class->set_header = message_partial_set_header; object_class->set_content_type = message_partial_set_content_type; } static void g_mime_message_partial_init (GMimeMessagePartial *partial, GMimeMessagePartialClass *klass) { partial->id = NULL; partial->number = -1; partial->total = -1; } static void g_mime_message_partial_finalize (GObject *object) { GMimeMessagePartial *partial = (GMimeMessagePartial *) object; g_free (partial->id); G_OBJECT_CLASS (parent_class)->finalize (object); } static void message_partial_init (GMimeObject *object) { /* no-op */ GMIME_OBJECT_CLASS (parent_class)->init (object); } static void message_partial_add_header (GMimeObject *object, const char *header, const char *value) { /* RFC 1864 states that you cannot set a Content-MD5 on a message part */ if (!g_ascii_strcasecmp ("Content-MD5", header)) return; /* Make sure that the header is a Content-* header, else it doesn't belong on a mime part */ if (!g_ascii_strncasecmp ("Content-", header, 8)) GMIME_OBJECT_CLASS (parent_class)->add_header (object, header, value); } static void message_partial_set_header (GMimeObject *object, const char *header, const char *value) { /* RFC 1864 states that you cannot set a Content-MD5 on a message part */ if (!g_ascii_strcasecmp ("Content-MD5", header)) return; /* Make sure that the header is a Content-* header, else it doesn't belong on a mime part */ if (!g_ascii_strncasecmp ("Content-", header, 8)) GMIME_OBJECT_CLASS (parent_class)->set_header (object, header, value); } static void message_partial_set_content_type (GMimeObject *object, GMimeContentType *content_type) { GMimeMessagePartial *partial = (GMimeMessagePartial *) object; const char *value; value = g_mime_content_type_get_parameter (content_type, "id"); g_free (partial->id); partial->id = g_strdup (value); value = g_mime_content_type_get_parameter (content_type, "number"); partial->number = value ? strtol (value, NULL, 10) : -1; value = g_mime_content_type_get_parameter (content_type, "total"); partial->total = value ? strtol (value, NULL, 10) : -1; GMIME_OBJECT_CLASS (parent_class)->set_content_type (object, content_type); } /** * g_mime_message_partial_new: * @id: message/partial part id * @number: message/partial part number * @total: total number of message/partial parts * * Creates a new MIME message/partial object. * * Returns an empty MIME message/partial object. **/ GMimeMessagePartial * g_mime_message_partial_new (const char *id, int number, int total) { GMimeMessagePartial *partial; GMimeContentType *type; char *num; partial = g_object_new (GMIME_TYPE_MESSAGE_PARTIAL, NULL); type = g_mime_content_type_new ("message", "partial"); partial->id = g_strdup (id); g_mime_content_type_set_parameter (type, "id", id); partial->number = number; num = g_strdup_printf ("%d", number); g_mime_content_type_set_parameter (type, "number", num); g_free (num); partial->total = total; num = g_strdup_printf ("%d", total); g_mime_content_type_set_parameter (type, "total", num); g_free (num); g_mime_object_set_content_type (GMIME_OBJECT (partial), type); return partial; } /** * g_mime_message_partial_get_id: * @partial: message/partial object * * Gets the message/partial id parameter value. * * Returns the message/partial id or %NULL on fail. **/ const char * g_mime_message_partial_get_id (GMimeMessagePartial *partial) { g_return_val_if_fail (GMIME_IS_MESSAGE_PARTIAL (partial), NULL); return partial->id; } /** * g_mime_message_partial_get_number: * @partial: message/partial object * * Gets the message/partial part number. * * Returns the message/partial part number or %-1 on fail. **/ int g_mime_message_partial_get_number (GMimeMessagePartial *partial) { g_return_val_if_fail (GMIME_IS_MESSAGE_PARTIAL (partial), -1); return partial->number; } /** * g_mime_message_partial_get_total: * @partial: message/partial object * * Gets the total number of message/partial parts needed to * reconstruct the original message. * * Returns the total number of message/partial parts needed to * reconstruct the original message or -1 on fail. **/ int g_mime_message_partial_get_total (GMimeMessagePartial *partial) { g_return_val_if_fail (GMIME_IS_MESSAGE_PARTIAL (partial), -1); return partial->total; } static int partial_compare (const void *v1, const void *v2) { GMimeMessagePartial **partial1 = (GMimeMessagePartial **) v1; GMimeMessagePartial **partial2 = (GMimeMessagePartial **) v2; int num1, num2; num1 = g_mime_message_partial_get_number (*partial1); num2 = g_mime_message_partial_get_number (*partial2); return num1 - num2; } /** * g_mime_message_partial_reconstruct_message: * @partials: an array of message/partial mime parts * @num: the number of elements in @partials * * Reconstructs the GMimeMessage from the given message/partial parts * in @partials. * * Returns a GMimeMessage object on success or %NULL on fail. **/ GMimeMessage * g_mime_message_partial_reconstruct_message (GMimeMessagePartial **partials, size_t num) { GMimeMessagePartial *partial; GMimeDataWrapper *wrapper; GMimeStream *cat, *stream; GMimeMessage *message; GMimeParser *parser; int total, number; const char *id; size_t i; g_return_val_if_fail (num > 0, NULL); if (!(id = g_mime_message_partial_get_id (partials[0]))) return NULL; /* get them into the correct order... */ qsort ((void *) partials, num, sizeof (gpointer), partial_compare); /* only the last message/partial part is REQUIRED to have the total parameter */ total = g_mime_message_partial_get_total (partials[num - 1]); if (num != total) return NULL; cat = g_mime_stream_cat_new (); for (i = 0; i < num; i++) { const char *partial_id; partial = partials[i]; /* sanity check to make sure this part belongs... */ partial_id = g_mime_message_partial_get_id (partial); if (!partial_id || strcmp (id, partial_id)) goto exception; /* sanity check to make sure we aren't missing any parts */ number = g_mime_message_partial_get_number (partial); if (number != i + 1) goto exception; wrapper = g_mime_part_get_content_object (GMIME_PART (partial)); stream = g_mime_data_wrapper_get_stream (wrapper); g_object_unref (wrapper); g_mime_stream_reset (stream); g_mime_stream_cat_add_source (GMIME_STREAM_CAT (cat), stream); g_object_unref (stream); } parser = g_mime_parser_new (); g_mime_parser_init_with_stream (parser, cat); g_object_unref (cat); message = g_mime_parser_construct_message (parser); g_object_unref (parser); return message; exception: g_object_unref (cat); return NULL; } static void header_copy (const char *name, const char *value, gpointer user_data) { GMimeMessage *message = (GMimeMessage *) user_data; if (value) g_mime_object_add_header (GMIME_OBJECT (message), name, value); } static GMimeMessage * message_partial_message_new (GMimeMessage *base) { GMimeMessage *message; message = g_mime_message_new (FALSE); g_mime_header_foreach (GMIME_OBJECT (base)->headers, header_copy, message); return message; } /** * g_mime_message_partial_split_message: * @message: message object * @max_size: max size * @nparts: number of parts * * Splits @message into an array of #GMimeMessage objects each * containing a single #GMimeMessagePartial object containing * @max_size bytes or fewer. @nparts is set to the number of * #GMimeMessagePartial objects created. * * Returns an array of #GMimeMessage objects and sets @nparts to th * number of messages returned or %NULL on fail. **/ GMimeMessage ** g_mime_message_partial_split_message (GMimeMessage *message, size_t max_size, size_t *nparts) { GMimeMessage **messages; GMimeMessagePartial *partial; GMimeStream *stream, *substream; GMimeDataWrapper *wrapper; const unsigned char *buf; GPtrArray *parts; size_t len, end; const char *id; off_t start; int i; *nparts = 0; g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL); stream = g_mime_stream_mem_new (); if (g_mime_object_write_to_stream (GMIME_OBJECT (message), stream) == -1) { g_object_unref (stream); return NULL; } g_mime_stream_reset (stream); len = g_mime_stream_length (stream); /* optimization */ if (len <= max_size) { g_object_unref (stream); g_object_ref (message); messages = g_malloc (sizeof (void *)); messages[0] = message; *nparts = 1; return messages; } start = 0; parts = g_ptr_array_new (); buf = ((GMimeStreamMem *) stream)->buffer->data; while (start < len) { /* Preferably, we'd split on whole-lines if we can, * but if that's not possible, split on max size */ if ((end = MIN (len, start + max_size)) < len) { register size_t ebx; /* end boundary */ ebx = end; while (ebx > (start + 1) && ebx[buf] != '\n') ebx--; if (ebx[buf] == '\n') end = ebx + 1; } substream = g_mime_stream_substream (stream, start, end); g_ptr_array_add (parts, substream); start = end; } id = g_mime_message_get_message_id (message); for (i = 0; i < parts->len; i++) { partial = g_mime_message_partial_new (id, i + 1, parts->len); wrapper = g_mime_data_wrapper_new_with_stream (GMIME_STREAM (parts->pdata[i]), GMIME_PART_ENCODING_DEFAULT); g_object_unref (parts->pdata[i]); g_mime_part_set_content_object (GMIME_PART (partial), wrapper); g_object_unref (wrapper); parts->pdata[i] = message_partial_message_new (message); g_mime_message_set_mime_part (GMIME_MESSAGE (parts->pdata[i]), GMIME_OBJECT (partial)); g_object_unref (partial); } g_object_unref (stream); messages = (GMimeMessage **) parts->pdata; *nparts = parts->len; g_ptr_array_free (parts, FALSE); return messages; }