Logo Search packages:      
Sourcecode: balsa version File versions

body.c

/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
/* Balsa E-Mail Client
 *
 * Copyright (C) 1997-2002 Stuart Parmenter and others,
 *                         See the file AUTHORS for a list.
 *
 * 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, 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, Inc., 59 Temple Place - Suite 330, Boston, MA  
 * 02111-1307, USA.
 */

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <glib.h>
#include <fcntl.h>

#include "libbalsa.h"
#include "misc.h"
#include "i18n.h"

LibBalsaMessageBody *
libbalsa_message_body_new(LibBalsaMessage * message)
{
    LibBalsaMessageBody *body;

    body = g_new0(LibBalsaMessageBody, 1);

    body->message = message;
    body->buffer = NULL;
    body->embhdrs = NULL;
    body->content_type = NULL;
    body->filename = NULL;
    body->temp_filename = NULL;
    body->charset = NULL;

#ifdef HAVE_GPGME
    body->was_encrypted = FALSE;
    body->sig_info = NULL;
#endif

    body->next = NULL;
    body->parts = NULL;

    body->mime_part = NULL;

    return body;
}


void
libbalsa_message_body_free(LibBalsaMessageBody * body)
{
    if (body == NULL)
      return;

    g_free(body->buffer);
    libbalsa_message_headers_destroy(body->embhdrs);
    g_free(body->content_type);
    g_free(body->filename);

    if (body->temp_filename)
      unlink(body->temp_filename);
    g_free(body->temp_filename);

    g_free(body->charset);

#ifdef HAVE_GPGME
    if (body->sig_info)
      g_object_unref(G_OBJECT(body->sig_info));
#endif

    libbalsa_message_body_free(body->next);
    libbalsa_message_body_free(body->parts);

    if (body->mime_part)
      g_object_unref(body->mime_part);    
    
    g_free(body);
}


static LibBalsaMessageHeaders *
libbalsa_message_body_extract_embedded_headers(GMimeMessage* msg)
{
    LibBalsaMessageHeaders *ehdr;
    const char *subj;
    int offset;

    ehdr = g_new0(LibBalsaMessageHeaders, 1);

    libbalsa_message_headers_from_gmime(ehdr, msg);
    ehdr->user_hdrs = libbalsa_message_user_hdrs_from_gmime(msg);

    subj = g_mime_message_get_subject(msg);
    if (subj) {
      ehdr->subject = g_mime_utils_header_decode_text(subj);
      libbalsa_utf8_sanitize(&ehdr->subject, TRUE, NULL);
    } else 
      ehdr->subject = g_strdup(_("(No subject)"));
    g_mime_message_get_date(msg, &ehdr->date, &offset);

    return ehdr;
}

/* Create a LibBalsaMessageBody with structure matching the GMimeObject;
 * the body may already have the necessary parts, so we check before
 * allocating new ones; it may have too many, so we free any that are no
 * longer needed.
 */

/* First some helpers. */
static void
libbalsa_message_body_set_filename(LibBalsaMessageBody * body)
{
    if (GMIME_IS_PART(body->mime_part)) {
      g_free(body->filename);
      body->filename =
          g_strdup(g_mime_part_get_filename(GMIME_PART(body->mime_part)));
    }
}

static void
libbalsa_message_body_set_types(LibBalsaMessageBody * body)
{
    const GMimeContentType *type;

    type = g_mime_object_get_content_type(body->mime_part);
    if      (g_mime_content_type_is_type(type, "audio", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_AUDIO;
    else if (g_mime_content_type_is_type(type, "application", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_APPLICATION;
    else if (g_mime_content_type_is_type(type, "image", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_IMAGE;
    else if (g_mime_content_type_is_type(type, "message", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE;
    else if (g_mime_content_type_is_type(type, "model", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MODEL;
    else if (g_mime_content_type_is_type(type, "multipart", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART;
    else if (g_mime_content_type_is_type(type, "text", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_TEXT;
    else if (g_mime_content_type_is_type(type, "video", "*"))
      body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_VIDEO;
    else body->body_type = LIBBALSA_MESSAGE_BODY_TYPE_OTHER;

    g_free(body->content_type);
    body->content_type = g_mime_content_type_to_string(type);
}

static LibBalsaMessageBody **
libbalsa_message_body_set_message_part(LibBalsaMessageBody * body,
                               LibBalsaMessageBody ** next_part)
{
    GMimeMessagePart *message_part;
    GMimeMessage *embedded_message;

    message_part = GMIME_MESSAGE_PART(body->mime_part);
    embedded_message = g_mime_message_part_get_message(message_part);

    libbalsa_message_headers_destroy(body->embhdrs);
    body->embhdrs =
      libbalsa_message_body_extract_embedded_headers(embedded_message);
    if (!*next_part)
      *next_part = libbalsa_message_body_new(body->message);
    libbalsa_message_body_set_mime_body(*next_part,
                              embedded_message->mime_part);
    g_object_unref(embedded_message);

    return &(*next_part)->next;
}

static LibBalsaMessageBody **
libbalsa_message_body_set_multipart(LibBalsaMessageBody * body,
                            LibBalsaMessageBody ** next_part)
{
    GList *child;

    for (child = GMIME_MULTIPART(body->mime_part)->subparts; child;
       child = child->next) {
      if (!*next_part)
          *next_part = libbalsa_message_body_new(body->message);
      libbalsa_message_body_set_mime_body(*next_part, child->data);
      next_part = &(*next_part)->next;
    }

    return next_part;
}

static void
libbalsa_message_body_set_parts(LibBalsaMessageBody * body)
{
    LibBalsaMessageBody **next_part = &body->parts;

    if (GMIME_IS_MESSAGE_PART(body->mime_part))
      next_part = libbalsa_message_body_set_message_part(body, next_part);
    else if (GMIME_IS_MULTIPART(body->mime_part))
      next_part = libbalsa_message_body_set_multipart(body, next_part);

    /* Free any parts that weren't used; the test isn't strictly
     * necessary, but it should fail unless something really strange has
     * happened, so it's worth including. */
    if (*next_part) {
      libbalsa_message_body_free(*next_part);
      *next_part = NULL;
    }
}

void
libbalsa_message_body_set_mime_body(LibBalsaMessageBody * body,
                            GMimeObject * mime_part)
{
    g_return_if_fail(body != NULL);
    g_return_if_fail(GMIME_IS_OBJECT(mime_part));

    g_object_ref(mime_part);
    if (body->mime_part)
      g_object_unref(body->mime_part);
    body->mime_part = mime_part;

    libbalsa_message_body_set_filename(body);
    libbalsa_message_body_set_types(body);
    libbalsa_message_body_set_parts(body);
}

LibBalsaMessageBodyType
libbalsa_message_body_type(LibBalsaMessageBody * body)
{
    /* FIXME: this could be a virtual function... OR not? */
    return body->body_type;
}

gchar *
libbalsa_message_body_get_parameter(LibBalsaMessageBody * body,
                            const gchar * param)
{
    gchar *res;

    g_return_val_if_fail(body != NULL, NULL);

    if (body->mime_part) {
      const GMimeContentType *type =
          g_mime_object_get_content_type(body->mime_part);
      res = g_strdup(g_mime_content_type_get_parameter(type, param));
    } else {
      GMimeContentType *type =
          g_mime_content_type_new_from_string(body->content_type);
      res = g_strdup(g_mime_content_type_get_parameter(type, param));
      g_mime_content_type_destroy(type);
    }

    return res;
}

/* libbalsa_message_body_save_temporary:
   check if body has already its copy in temporary file and if not,
   allocates a temporary file name and saves the body there.
*/
gboolean
libbalsa_message_body_save_temporary(LibBalsaMessageBody * body)
{
    if (body->temp_filename == NULL) {
      gint count = 100; /* Magic number, same as in g_mkstemp. */

      /* We want a temporary file with a name that ends with the same
       * set of suffices as body->filename (for the benefit of helpers
       * that depend on such things); however, g_file_open_tmp() works
       * only with templates that end with "XXXXXX", so we fake it. */
      do {
          gint fd;
          gchar *tmp_file_name;
          gchar *dotpos = NULL;
          GError *err = NULL;

          fd = g_file_open_tmp("balsa-body-XXXXXX", &tmp_file_name,
                         &err);
          if (err) {
            printf("libbalsa_message_body_save_temporary:\n %s\n",
                   err->message);
            g_error_free(err);
          }
          if (fd < 0)
            return FALSE;
          close(fd);
          unlink(tmp_file_name);

          if (body->filename) {
            gchar *seppos = strrchr(body->filename, G_DIR_SEPARATOR);
            dotpos = strchr(seppos ? seppos : body->filename, '.');
          }
          g_free(body->temp_filename);
          if (dotpos) {
            body->temp_filename =
                g_strdup_printf("%s%s", tmp_file_name, dotpos);
            g_free(tmp_file_name);
          } else
            body->temp_filename = tmp_file_name;
          fd = open(body->temp_filename, O_WRONLY | O_EXCL | O_CREAT, 0600);
          if (fd >= 0)
            return libbalsa_message_body_save_fd(body, fd, FALSE);
      } while (errno == EEXIST && --count > 0);

      /* Either we hit a real error, or we used up 100 attempts. */
      return FALSE;
    } else {
      /* the temporary name has been already allocated on previous
         save_temporary action. We just check if the file is still there.
      */
      struct stat s;
      if (stat(body->temp_filename, &s) == 0 && 
          S_ISREG(s.st_mode) && 
          s.st_uid == getuid())
          return TRUE;
      else
          return libbalsa_message_body_save(body, body->temp_filename,
                                              FALSE);
    }
}

/* libbalsa_message_body_save:
   NOTE: has to use libbalsa_safe_open to set the file access privileges
   to safe.
*/
gboolean
libbalsa_message_body_save(LibBalsaMessageBody * body,
                     const gchar * filename, gboolean filter_crlf)
{
    int fd;
    int flags = O_CREAT | O_EXCL | O_WRONLY;

#ifdef O_NOFOLLOW
    flags |= O_NOFOLLOW;
#endif

    if ((fd=libbalsa_safe_open(filename, flags)) < 0)
      return FALSE;
    return libbalsa_message_body_save_fd(body, fd, filter_crlf);
}

static GMimeStream *
libbalsa_message_body_stream_add_filter(GMimeStream * stream,
                                        GMimeFilter * filter)
{
    if (!GMIME_IS_STREAM_FILTER(stream)) {
        GMimeStream *filtered_stream =
            g_mime_stream_filter_new_with_stream(stream);
        g_object_unref(stream);
        stream = filtered_stream;
    }

    g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream), filter);
    g_object_unref(filter);

    return stream;
}

GMimeStream *
libbalsa_message_body_get_stream(LibBalsaMessageBody * body)
{
    GMimeStream *stream;
    GMimeFilter *filter;
    gchar *mime_type = NULL;
    const gchar *charset;

    g_return_val_if_fail(body != NULL, NULL);
    g_return_val_if_fail(body->message != NULL, NULL);

    if (body->message->mailbox
        && libbalsa_mailbox_get_message_part(body->message, body)) {
        GMimeDataWrapper *wrapper;
      GMimePartEncodingType encoding;

        wrapper =
            g_mime_part_get_content_object(GMIME_PART(body->mime_part));
        stream = g_mime_data_wrapper_get_stream(wrapper);
        encoding = g_mime_data_wrapper_get_encoding(wrapper);
        g_object_unref(wrapper);

        switch (encoding) {
        case GMIME_PART_ENCODING_BASE64:
            filter =
                g_mime_filter_basic_new_type(GMIME_FILTER_BASIC_BASE64_DEC);
            stream =
                libbalsa_message_body_stream_add_filter(stream, filter);
            break;
        case GMIME_PART_ENCODING_QUOTEDPRINTABLE:
            filter =
                g_mime_filter_basic_new_type(GMIME_FILTER_BASIC_QP_DEC);
            stream =
                libbalsa_message_body_stream_add_filter(stream, filter);
            break;
        case GMIME_PART_ENCODING_UUENCODE:
            filter =
                g_mime_filter_basic_new_type(GMIME_FILTER_BASIC_UU_DEC);
            stream =
                libbalsa_message_body_stream_add_filter(stream, filter);
            break;
        default:
            break;
        }
    } else if (body->mime_part) {
        /* Not a GMimePart... */
      GMimeObject *object = body->mime_part;
        if (GMIME_IS_MESSAGE_PART(object))
            object =
                GMIME_OBJECT(g_mime_message_part_get_message
                             ((GMimeMessagePart *) object));
      else
          g_object_ref(object);
        stream = g_mime_stream_mem_new();
        g_mime_object_write_to_stream(object, stream);
      g_object_unref(object);
    } else
        return NULL;

    /* convert text bodies but HTML - gtkhtml does conversion on its own. */
    if (libbalsa_message_body_type(body) == LIBBALSA_MESSAGE_BODY_TYPE_TEXT
      && strcmp(mime_type = libbalsa_message_body_get_mime_type(body),
                                    "text/html") != 0
      && (charset = libbalsa_message_body_charset(body)) != NULL
      && g_ascii_strcasecmp(charset, "unknown-8bit") != 0
      && (filter = g_mime_filter_charset_new(charset, "UTF-8")) != NULL)
      stream = libbalsa_message_body_stream_add_filter(stream, filter);

    g_free(mime_type);

    g_mime_stream_reset(stream);
    return stream;
}

gssize
libbalsa_message_body_get_content(LibBalsaMessageBody * body, gchar ** buf)
{
    GMimeStream *stream, *stream_mem;
    GByteArray *array;
    gssize len;

    g_return_val_if_fail(body != NULL, -1);
    g_return_val_if_fail(body->message != NULL, -1);
    g_return_val_if_fail(buf != NULL, -1);

    *buf = NULL;
    stream = libbalsa_message_body_get_stream(body);
    if (!stream)
        return -1;

    array = g_byte_array_new();
    stream_mem = g_mime_stream_mem_new_with_byte_array(array);
    g_mime_stream_mem_set_owner(GMIME_STREAM_MEM(stream_mem), FALSE);
    len = g_mime_stream_write_to_stream(stream, stream_mem);
    g_object_unref(stream_mem);
    g_object_unref(stream);

    if (len >= 0) {
        len = array->len;
      /* NULL-terminate, in case it is used as a string. */
      g_byte_array_append(array, "", 1);
        *buf = array->data;
        g_byte_array_free(array, FALSE);
    } else
        g_byte_array_free(array, TRUE);

    return len;
}

GdkPixbuf *
libbalsa_message_body_get_pixbuf(LibBalsaMessageBody * body, GError ** err)
{
    GdkPixbufLoader *loader;
    GMimeStream *stream;
    gssize count;
    gboolean ok = TRUE;
    gchar buf[4096];
    GdkPixbuf *pixbuf = NULL;

    stream = libbalsa_message_body_get_stream(body);
    if (!stream)
        return pixbuf;

    loader = gdk_pixbuf_loader_new();
    while ((count = g_mime_stream_read(stream, buf, sizeof(buf))) > 0) {
        if (!gdk_pixbuf_loader_write(loader, buf, count, err)) {
            ok = FALSE;
            break;
        }
    }
    g_object_unref(stream);

    if (!gdk_pixbuf_loader_close(loader, ok ? err : NULL))
      ok = FALSE;

    if (ok) {
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
      g_object_ref(pixbuf);
    }
    g_object_unref(loader);

    return pixbuf;
}

gboolean
libbalsa_message_body_save_fd(LibBalsaMessageBody * body, int fd,
                              gboolean filter_crlf)
{
    GMimeStream *stream, *stream_fs;
    gboolean retval = TRUE;

    stream = libbalsa_message_body_get_stream(body);
    stream_fs = g_mime_stream_fs_new(fd);

    if(filter_crlf) {
        GMimeFilter *filter;
        filter = g_mime_filter_crlf_new(GMIME_FILTER_CRLF_DECODE,
                                        GMIME_FILTER_CRLF_MODE_CRLF_ONLY);
        g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream), filter);
        g_object_unref(filter);
    }
    if (g_mime_stream_write_to_stream(stream, stream_fs) < 0)
      retval = FALSE;
    g_object_unref(stream);
    g_object_unref(stream_fs);

    return retval;
}

gchar *
libbalsa_message_body_get_mime_type(LibBalsaMessageBody * body)
{
    gchar *res, *tmp;

    g_return_val_if_fail(body != NULL, NULL);

    if (!body->content_type)
      return g_strdup("text/plain");

    tmp = strchr(body->content_type, ';');
    res = g_ascii_strdown(body->content_type,
                          tmp ? tmp-body->content_type : -1);
    return res;
}

gboolean
libbalsa_message_body_is_multipart(LibBalsaMessageBody * body)
{
    return body->mime_part ?
      GMIME_IS_MULTIPART(body->mime_part) :
      body->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART;
}

gboolean
libbalsa_message_body_is_inline(LibBalsaMessageBody * body)
{
    const gchar *disposition;

    g_return_val_if_fail(body->mime_part == NULL ||
                   GMIME_IS_OBJECT(body->mime_part), FALSE);

    if (body->mime_part)
      disposition = g_mime_object_get_header(body->mime_part,
                                     "Content-Disposition");
    else
      disposition = body->content_dsp;

    if (!disposition)
      /* Default disposition is in-line for text/plain, and generally
       * attachment for other content types.  Default content type is
       * text/plain except in multipart/digest, where it is
       * message/rfc822; in either case, we want to in-line the part.
       */
        return (body->content_type == NULL
                || g_ascii_strcasecmp(body->content_type,
                                      "message/rfc822") == 0
                || g_ascii_strcasecmp(body->content_type,
                                      "text/plain") == 0);

    return g_ascii_strncasecmp(disposition,
                               GMIME_DISPOSITION_INLINE,
                               strlen(GMIME_DISPOSITION_INLINE)) == 0;
}

/* libbalsa_message_body_is_flowed:
 * test whether a message body is format=flowed */
gboolean
libbalsa_message_body_is_flowed(LibBalsaMessageBody * body)
{
    gchar *content_type;
    gboolean flowed = FALSE;

    content_type = libbalsa_message_body_get_mime_type(body);
    if (g_ascii_strcasecmp(content_type, "text/plain") == 0) {
      gchar *format =
          libbalsa_message_body_get_parameter(body, "format");

      if (format) {
          flowed = (g_ascii_strcasecmp(format, "flowed") == 0);
          g_free(format);
      }
    }
    g_free(content_type);

    return flowed;
}

gboolean
libbalsa_message_body_is_delsp(LibBalsaMessageBody * body)
{
    gboolean delsp = FALSE;

    if (libbalsa_message_body_is_flowed(body)) {
      gchar *delsp_param =
          libbalsa_message_body_get_parameter(body, "delsp");

      if (delsp_param) {
          delsp = (g_ascii_strcasecmp(delsp_param, "yes") == 0);
          g_free(delsp_param);
      }
    }

    return delsp;
}
    
LibBalsaMessageBody *
libbalsa_message_body_get_by_id(LibBalsaMessageBody * body,
                        const gchar * id)
{
    LibBalsaMessageBody *res;

    g_return_val_if_fail(id != NULL, NULL);

    if (!body)
      return NULL;

    if (body->mime_part) {
      const gchar *bodyid =
          g_mime_object_get_content_id(body->mime_part);

      if (bodyid && strcmp(id, bodyid) == 0)
          return body;
    } else {
        if(body->content_id && strcmp(id, body->content_id) == 0)
            return body;
    }

    if ((res = libbalsa_message_body_get_by_id(body->parts, id)) != NULL)
      return res;

    return libbalsa_message_body_get_by_id(body->next, id);
}

Generated by  Doxygen 1.6.0   Back to index