/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-multipart.c : Abstract class for a multipart
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Bertrand Guiheneuf
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include /* strlen() */
#include /* for time */
#include /* for getpid */
#include "camel-mime-part.h"
#include "camel-multipart.h"
#include "camel-stream-mem.h"
#define CAMEL_MULTIPART_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_MULTIPART, CamelMultipartPrivate))
struct _CamelMultipartPrivate {
GPtrArray *parts;
gchar *preface;
gchar *postface;
};
G_DEFINE_TYPE (CamelMultipart, camel_multipart, CAMEL_TYPE_DATA_WRAPPER)
static void
multipart_dispose (GObject *object)
{
CamelMultipartPrivate *priv;
priv = CAMEL_MULTIPART_GET_PRIVATE (object);
g_ptr_array_set_size (priv->parts, 0);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (camel_multipart_parent_class)->dispose (object);
}
static void
multipart_finalize (GObject *object)
{
CamelMultipartPrivate *priv;
priv = CAMEL_MULTIPART_GET_PRIVATE (object);
g_ptr_array_unref (priv->parts);
g_free (priv->preface);
g_free (priv->postface);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_multipart_parent_class)->finalize (object);
}
static gboolean
multipart_is_offline (CamelDataWrapper *data_wrapper)
{
CamelMultipartPrivate *priv;
CamelDataWrapper *part;
guint ii;
priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
/* Chain up to parent's is_offline() method. */
if (CAMEL_DATA_WRAPPER_CLASS (camel_multipart_parent_class)->is_offline (data_wrapper))
return TRUE;
for (ii = 0; ii < priv->parts->len; ii++) {
part = g_ptr_array_index (priv->parts, ii);
if (camel_data_wrapper_is_offline (part))
return TRUE;
}
return FALSE;
}
/* this is MIME specific, doesn't belong here really */
static gssize
multipart_write_to_stream_sync (CamelDataWrapper *data_wrapper,
CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
CamelMultipartPrivate *priv;
const gchar *boundary;
gchar *content;
gssize total = 0;
gssize count;
guint ii;
priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
/* get the bundary text */
boundary = camel_multipart_get_boundary (
CAMEL_MULTIPART (data_wrapper));
/* we cannot write a multipart without a boundary string */
g_return_val_if_fail (boundary, -1);
/*
* write the preface text (usually something like
* "This is a mime message, if you see this, then
* your mail client probably doesn't support ...."
*/
if (priv->preface != NULL) {
count = camel_stream_write_string (
stream, priv->preface, cancellable, error);
if (count == -1)
return -1;
total += count;
}
/*
* Now, write all the parts, separated by the boundary
* delimiter
*/
for (ii = 0; ii < priv->parts->len; ii++) {
CamelDataWrapper *part;
part = g_ptr_array_index (priv->parts, ii);
content = g_strdup_printf ("\n--%s\n", boundary);
count = camel_stream_write_string (
stream, content, cancellable, error);
g_free (content);
if (count == -1)
return -1;
total += count;
count = camel_data_wrapper_write_to_stream_sync (
part, stream, cancellable, error);
if (count == -1)
return -1;
total += count;
}
/* write the terminating boudary delimiter */
content = g_strdup_printf ("\n--%s--\n", boundary);
count = camel_stream_write_string (
stream, content, cancellable, error);
g_free (content);
if (count == -1)
return -1;
total += count;
/* and finally the postface */
if (priv->postface != NULL) {
count = camel_stream_write_string (
stream, priv->postface, cancellable, error);
if (count == -1)
return -1;
total += count;
}
return total;
}
/* this is MIME specific, doesn't belong here really */
static gssize
multipart_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
GOutputStream *output_stream,
GCancellable *cancellable,
GError **error)
{
CamelMultipartPrivate *priv;
const gchar *boundary;
gchar *content;
gsize bytes_written;
gssize total = 0;
gboolean success;
guint ii;
priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
/* get the bundary text */
boundary = camel_multipart_get_boundary (
CAMEL_MULTIPART (data_wrapper));
/* we cannot write a multipart without a boundary string */
g_return_val_if_fail (boundary, -1);
/*
* write the preface text (usually something like
* "This is a mime message, if you see this, then
* your mail client probably doesn't support ...."
*/
if (priv->preface != NULL) {
success = g_output_stream_write_all (
output_stream,
priv->preface, strlen (priv->preface),
&bytes_written, cancellable, error);
if (!success)
return -1;
total += (gsize) bytes_written;
}
/*
* Now, write all the parts, separated by the boundary
* delimiter
*/
for (ii = 0; ii < priv->parts->len; ii++) {
CamelDataWrapper *part;
gssize result;
part = g_ptr_array_index (priv->parts, ii);
content = g_strdup_printf ("\n--%s\n", boundary);
success = g_output_stream_write_all (
output_stream,
content, strlen (content),
&bytes_written, cancellable, error);
g_free (content);
if (!success)
return -1;
total += (gsize) bytes_written;
result = camel_data_wrapper_write_to_output_stream_sync (
part, output_stream, cancellable, error);
if (result == -1)
return -1;
total += result;
}
/* write the terminating boudary delimiter */
content = g_strdup_printf ("\n--%s--\n", boundary);
success = g_output_stream_write_all (
output_stream,
content, strlen (content),
&bytes_written, cancellable, error);
g_free (content);
if (!success)
return -1;
total += (gsize) bytes_written;
/* and finally the postface */
if (priv->postface != NULL) {
success = g_output_stream_write_all (
output_stream,
priv->postface, strlen (priv->postface),
&bytes_written, cancellable, error);
if (!success)
return -1;
total += (gsize) bytes_written;
}
return total;
}
static void
multipart_add_part (CamelMultipart *multipart,
CamelMimePart *part)
{
g_ptr_array_add (multipart->priv->parts, g_object_ref (part));
}
static CamelMimePart *
multipart_get_part (CamelMultipart *multipart,
guint index)
{
if (index >= multipart->priv->parts->len)
return NULL;
return g_ptr_array_index (multipart->priv->parts, index);
}
static guint
multipart_get_number (CamelMultipart *multipart)
{
return multipart->priv->parts->len;
}
static void
multipart_set_boundary (CamelMultipart *multipart,
const gchar *boundary)
{
CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
gchar *bgen, bbuf[27], *p;
guint8 *digest;
gsize length;
gint state, save;
g_return_if_fail (cdw->mime_type != NULL);
length = g_checksum_type_get_length (G_CHECKSUM_MD5);
digest = g_alloca (length);
if (!boundary) {
GChecksum *checksum;
/* Generate a fairly random boundary string. */
bgen = g_strdup_printf (
"%p:%lu:%lu",
(gpointer) multipart,
(gulong) getpid (),
(gulong) time (NULL));
checksum = g_checksum_new (G_CHECKSUM_MD5);
g_checksum_update (checksum, (guchar *) bgen, -1);
g_checksum_get_digest (checksum, digest, &length);
g_checksum_free (checksum);
g_free (bgen);
g_strlcpy (bbuf, "=-", sizeof (bbuf));
p = bbuf + 2;
state = save = 0;
p += g_base64_encode_step (
(guchar *) digest, length, FALSE, p, &state, &save);
*p = '\0';
boundary = bbuf;
}
camel_content_type_set_param (cdw->mime_type, "boundary", boundary);
}
static const gchar *
multipart_get_boundary (CamelMultipart *multipart)
{
CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
g_return_val_if_fail (cdw->mime_type != NULL, NULL);
return camel_content_type_param (cdw->mime_type, "boundary");
}
static gint
multipart_construct_from_parser (CamelMultipart *multipart,
CamelMimeParser *mp)
{
gint err;
CamelContentType *content_type;
CamelMimePart *bodypart;
gchar *buf;
gsize len;
g_return_val_if_fail (camel_mime_parser_state (mp) == CAMEL_MIME_PARSER_STATE_MULTIPART, -1);
content_type = camel_mime_parser_content_type (mp);
camel_multipart_set_boundary (
multipart,
camel_content_type_param (content_type, "boundary"));
while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
camel_mime_parser_unstep (mp);
bodypart = camel_mime_part_new ();
camel_mime_part_construct_from_parser_sync (
bodypart, mp, NULL, NULL);
camel_multipart_add_part (multipart, bodypart);
g_object_unref (bodypart);
}
/* these are only return valid data in the MULTIPART_END state */
camel_multipart_set_preface (multipart, camel_mime_parser_preface (mp));
camel_multipart_set_postface (multipart, camel_mime_parser_postface (mp));
err = camel_mime_parser_errno (mp);
if (err != 0) {
errno = err;
return -1;
} else
return 0;
}
static void
camel_multipart_class_init (CamelMultipartClass *class)
{
GObjectClass *object_class;
CamelDataWrapperClass *data_wrapper_class;
g_type_class_add_private (class, sizeof (CamelMultipartPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->dispose = multipart_dispose;
object_class->finalize = multipart_finalize;
data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
data_wrapper_class->is_offline = multipart_is_offline;
data_wrapper_class->write_to_stream_sync = multipart_write_to_stream_sync;
data_wrapper_class->decode_to_stream_sync = multipart_write_to_stream_sync;
data_wrapper_class->write_to_output_stream_sync = multipart_write_to_output_stream_sync;
data_wrapper_class->decode_to_output_stream_sync = multipart_write_to_output_stream_sync;
class->add_part = multipart_add_part;
class->get_part = multipart_get_part;
class->get_number = multipart_get_number;
class->set_boundary = multipart_set_boundary;
class->get_boundary = multipart_get_boundary;
class->construct_from_parser = multipart_construct_from_parser;
}
static void
camel_multipart_init (CamelMultipart *multipart)
{
multipart->priv = CAMEL_MULTIPART_GET_PRIVATE (multipart);
multipart->priv->parts =
g_ptr_array_new_with_free_func (g_object_unref);
camel_data_wrapper_set_mime_type (
CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
}
/**
* camel_multipart_new:
*
* Create a new #CamelMultipart object.
*
* Returns: a new #CamelMultipart object
**/
CamelMultipart *
camel_multipart_new (void)
{
return g_object_new (CAMEL_TYPE_MULTIPART, NULL);
}
/**
* camel_multipart_add_part:
* @multipart: a #CamelMultipart object
* @part: a #CamelMimePart to add
*
* Appends the part to the multipart object.
**/
void
camel_multipart_add_part (CamelMultipart *multipart,
CamelMimePart *part)
{
CamelMultipartClass *class;
g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
g_return_if_fail (CAMEL_IS_MIME_PART (part));
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_if_fail (class->add_part != NULL);
class->add_part (multipart, part);
}
/**
* camel_multipart_get_part:
* @multipart: a #CamelMultipart object
* @index: a zero-based index indicating the part to get
*
* Returns: the indicated subpart, or %NULL
**/
CamelMimePart *
camel_multipart_get_part (CamelMultipart *multipart,
guint index)
{
CamelMultipartClass *class;
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_val_if_fail (class->get_part != NULL, NULL);
return class->get_part (multipart, index);
}
/**
* camel_multipart_get_number:
* @multipart: a #CamelMultipart object
*
* Returns: the number of subparts in @multipart
**/
guint
camel_multipart_get_number (CamelMultipart *multipart)
{
CamelMultipartClass *class;
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), 0);
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_val_if_fail (class->get_number != NULL, 0);
return class->get_number (multipart);
}
/**
* camel_multipart_get_boundary:
* @multipart: a #CamelMultipart object
*
* Returns: the boundary
**/
const gchar *
camel_multipart_get_boundary (CamelMultipart *multipart)
{
CamelMultipartClass *class;
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_val_if_fail (class->get_boundary != NULL, NULL);
return class->get_boundary (multipart);
}
/**
* camel_multipart_set_boundary:
* @multipart: a #CamelMultipart object
* @boundary: the message boundary, or %NULL
*
* Sets the message boundary for @multipart to @boundary. This should
* be a string which does not occur anywhere in any of @multipart's
* subparts. If @boundary is %NULL, a randomly-generated boundary will
* be used.
**/
void
camel_multipart_set_boundary (CamelMultipart *multipart,
const gchar *boundary)
{
CamelMultipartClass *class;
g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_if_fail (class->set_boundary != NULL);
class->set_boundary (multipart, boundary);
}
/**
* camel_multipart_get_preface:
* @multipart: a #CamelMultipart
*
* Returns the preface text for @multipart.
*
* Returns: the preface text
*
* Since: 3.12
**/
const gchar *
camel_multipart_get_preface (CamelMultipart *multipart)
{
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
return multipart->priv->preface;
}
/**
* camel_multipart_set_preface:
* @multipart: a #CamelMultipart object
* @preface: the multipart preface
*
* Set the preface text for this multipart. Will be written out infront
* of the multipart. This text should only include US-ASCII strings, and
* be relatively short, and will be ignored by any MIME mail client.
**/
void
camel_multipart_set_preface (CamelMultipart *multipart,
const gchar *preface)
{
g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
if (multipart->priv->preface == preface)
return;
g_free (multipart->priv->preface);
multipart->priv->preface = g_strdup (preface);
}
/**
* camel_multipart_get_postface:
* @multipart: a #CamelMultipart
*
* Returns the postface text for @multipart.
*
* Returns: the postface text
*
* Since: 3.12
**/
const gchar *
camel_multipart_get_postface (CamelMultipart *multipart)
{
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
return multipart->priv->postface;
}
/**
* camel_multipart_set_postface:
* @multipart: a #CamelMultipart object
* @postface: multipat postface
*
* Set the postface text for this multipart. Will be written out after
* the last boundary of the multipart, and ignored by any MIME mail
* client.
*
* Generally postface texts should not be sent with multipart messages.
**/
void
camel_multipart_set_postface (CamelMultipart *multipart,
const gchar *postface)
{
g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
if (multipart->priv->postface == postface)
return;
g_free (multipart->priv->postface);
multipart->priv->postface = g_strdup (postface);
}
/**
* camel_multipart_construct_from_parser:
* @multipart: a #CamelMultipart object
* @parser: a #CamelMimeParser object
*
* Construct a multipart from a parser.
*
* Returns: %0 on success or %-1 on fail
**/
gint
camel_multipart_construct_from_parser (CamelMultipart *multipart,
CamelMimeParser *mp)
{
CamelMultipartClass *class;
g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), -1);
g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), -1);
class = CAMEL_MULTIPART_GET_CLASS (multipart);
g_return_val_if_fail (class->construct_from_parser != NULL, -1);
return class->construct_from_parser (multipart, mp);
}