Logo Search packages:      
Sourcecode: pan version File versions  Download package

decode.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * 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; version 2 of the License.
 *
 * 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 <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <glib.h>

#include <gmime/gmime.h>
#include <gmime/gmime-stream-file.h>

#include <pan/base/acache.h>
#include <pan/base/debug.h>
#include <pan/base/decode.h>
#include <pan/base/log.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/util-file.h>
#include <pan/base/util-mime.h>

/**
*** Private Routines
**/

static gchar*
create_filename (const gchar       * path,
                 const gchar       * subject,
                 const gchar       * default_filename,
                 const GMimePart   * part)
{
      int i;
      GString * filename;
      debug_enter ("create_filename");

      g_return_val_if_fail (is_nonempty_string(path), NULL);
      g_return_val_if_fail (part!=NULL, NULL);

            filename = g_string_new (NULL);

      /* first try the filename specified by the user */
      if (!filename->len && is_nonempty_string(default_filename))
            g_string_assign (filename, default_filename);

      /* otherwise try the filename specified by the article */
      if (!filename->len) {
            const gchar * pch = g_mime_part_get_filename (part);
            if (is_nonempty_string(pch)) {
                  /* if some bozo included the windows pathname in the filename, strip that out */
                  if (isalpha((guchar)pch[0]) && pch[1]==':' && pch[2]=='\\')
                        pch = strrchr (pch, '\\') + 1;
                  g_string_assign (filename, pch);
            }
      }

      /* otherwise try the article's subject */
      if (!filename->len && is_nonempty_string(subject))
            g_string_assign (filename, subject);

      /* otherwise punt */
      if (!filename->len)
            g_string_assign (filename, _("UNKNOWN"));

      /* filter out directory characters */
      if (1) {
            const gchar * in;
            gchar * buf = g_malloc (filename->len*2);
            gchar * out = buf;
            for (in=filename->str; *in; ++in) {
                  if (*in==G_DIR_SEPARATOR) {
                        *out++ = '_';
                  }
                  else if (*in=='\\') {
                        *out++ = '\\',
                        *out++ = '\\';
                  }
                  else {
                        *out++ = *in;
                  }
            }
            *out = '\0';
            g_string_assign (filename, buf);
            g_free (buf);
      }

      /* add the directory & look for uniqueness */
      for (i=1;; ++i)
      {
            const char * front = filename->str;
            const gchar * lastdot = strrchr (front, '.');
            gchar * unique;
            gchar * lead;
            gchar * tail;
            if (lastdot == NULL) {
                  lead = g_strdup (front);
                  tail = g_strdup ("");
            } else {
                  lead = g_strndup (front, lastdot-front);
                  tail = g_strdup (lastdot);
            }

            if (i==1 && is_nonempty_string(path))
            {
                  unique = g_strdup_printf ("%s%c%s%s",
                                            path, G_DIR_SEPARATOR,
                                            lead, tail);
            }
            else if (i==1)
            {
                  unique = g_strdup_printf ("%s%s", lead, tail);
            }
            else if (is_nonempty_string(path))
            {
                  unique = g_strdup_printf ("%s%c%s_copy_%d%s",
                                            path, G_DIR_SEPARATOR,
                                            lead, i, tail);
            }
            else
            {
                  unique = g_strdup_printf ("%s_copy_%d%s", lead, i, tail);
            }

            /* cleanup */
            g_free (lead);
            g_free (tail);

            if (!pan_file_exists (unique)) {
                  g_string_assign (filename, unique);
                  g_free (unique);
                  break;
            }

            g_free (unique);
      }

      debug_exit ("create_filename");
      return pan_file_normalize_inplace (g_string_free (filename, FALSE));
}


/*****
******
*****/

static void
get_array_of_decodable_parts_func (GMimeObject *part, gpointer data)
{
      GPtrArray * a = (GPtrArray*) data;
      const GMimeContentType * type;
      
      if (GMIME_IS_MULTIPART (part))
            return;

      type = g_mime_object_get_content_type (part);
      if ((is_nonempty_string (g_mime_part_get_filename (GMIME_PART (part)))) ||
          (!g_mime_content_type_is_type (type, "text", "*")))
            g_ptr_array_add (a, part);
}


/**
*** Public Routines
**/

gboolean
decode_article (const decode_data * dd)
{
      PString ** mids;
      GMimeMessage * mm;
      GPtrArray * attachments = NULL;
      int i = 0;
      gboolean success = TRUE;
      GString * filenames;
      char * filename = NULL;
      GMimeStream * stream;
      GMimeStream * stream_file = NULL;
      GMimeFilterYenc * yenc_filter;
      debug_enter ("decode_article");

      /* sanity clause */
      g_return_val_if_fail (dd!=NULL, FALSE);
      g_return_val_if_fail (server_is_valid (dd->server), FALSE);
      g_return_val_if_fail (dd->mid_qty>0, FALSE);
      g_return_val_if_fail (is_nonempty_string(dd->acache_key), FALSE);
      g_return_val_if_fail (message_identifiers_are_valid ((const MessageIdentifier**)dd->mids, dd->mid_qty), FALSE);

      /* get the message from the cache, and make an array of its attachments */
      attachments = g_ptr_array_new ();
      status_item_emit_status_va (dd->item, _("Decoding \"%s\""), dd->subject);
      mids = g_newa (PString*, dd->mid_qty);
      for (i=0; i<dd->mid_qty; ++i)
            mids[i] = &dd->mids[i]->message_id;
      mm = acache_get_message (dd->acache_key, (const PString**)mids, dd->mid_qty);
      if (mm != NULL)
            g_mime_message_foreach_part (mm, get_array_of_decodable_parts_func, attachments);

      /* decode & save the parts */
      success = attachments->len != 0;
      filenames = g_string_new (NULL);
      stream = NULL;
      yenc_filter = NULL;
      for (i=0; success && i!=attachments->len; ++i)
      {
            const GMimePart * part = (const GMimePart*) g_ptr_array_index (attachments, i);
            GMimeDataWrapper * content;
            YencInfo * yenc = g_object_get_data (G_OBJECT(part), "yenc");
            int part_number = -1;

            if (yenc != NULL)
                  part_number = yenc->part;

            if (part_number < 2)
            {
                  g_free (filename);
                  filename = create_filename (dd->path, dd->subject, dd->filename, part);

                  /* remember this filename */
                  g_string_append_printf (filenames, "%s\n", filename);

                  /* access the path */
                  if (success) {
                        char * path = g_dirname (filename);
                        gboolean path_ok = pan_file_ensure_path_exists (path);
                        if (!path_ok) {
                              log_add_va (LOG_ERROR, _("Decode can't access path \"%s\""), path);
                              success = FALSE;
                        }
                        g_free (path);
                  }

                  /* open the output file */
                  if (success) {
                        FILE * fp;
                        errno = 0;
                        fp = fopen (filename, "wb+");
                        if (stream != NULL) {
                              g_object_unref (stream);
                              stream = NULL;
                        }
                        if (fp != NULL) {
                              stream = stream_file = g_mime_stream_file_new (fp);
                              if (yenc != NULL) {
                                    GMimeStream * inner_stream = stream;
                                    GMimeFilter * filter;
                                    stream = g_mime_stream_filter_new_with_stream (inner_stream);
                                    filter = g_mime_filter_yenc_new (GMIME_FILTER_YENC_DIRECTION_DECODE);
                                    g_mime_stream_filter_add (GMIME_STREAM_FILTER(stream), filter);
                                    yenc_filter = (GMimeFilterYenc*) filter;
                                    g_mime_stream_unref (inner_stream);
                              }
                        }
                        else {
                              log_add_va (LOG_ERROR, _("Can't create file \"%s\" %s"),
                                          filename,
                                          errno==0 ? "" : g_strerror(errno));
                              success = FALSE;
                        }
                  }
            }

            /* make sure we have the necessary info to continue */          
            if (success) {
                  if (stream == NULL || filename == NULL ||
                      (yenc != NULL && yenc_filter == NULL)) { 
                        log_add (LOG_ERROR, _("Could not decode article - file may be corrupt/incomplete"));
                        success = FALSE;
                  }
            }
            
            /* make sure there's content to write */
            content = g_mime_part_get_content_object (part);
            if (!content)
                  success = FALSE;

            /* write the content */
            if (success)
            {
                  /* write the stream */
                  g_mime_data_wrapper_write_to_stream ((GMimeDataWrapper*)content, stream);
                  g_mime_stream_flush (stream);
                  
                  if (yenc != NULL)
                  {
                        /* log any crc errors */
                        if ((yenc->pcrc!=0) && (yenc->pcrc != g_mime_filter_yenc_get_pcrc(yenc_filter)))
                              log_add_va (LOG_ERROR,
                                        _("Checksum for `%s' part %d failed - file is corrupt"),
                                        filename, part_number);
                        if ((yenc->crc!=0) && (yenc->crc != g_mime_filter_yenc_get_crc(yenc_filter)))
                              log_add_va (LOG_ERROR,
                                        _("Checksum for `%s' failed - file may be corrupt"),
                                        filename);

                        /* reset the filter to get ready for the next part */
                        yenc_filter->pcrc = GMIME_YENCODE_CRC_INIT;
                        yenc_filter->state = GMIME_YENCODE_STATE_INIT;
                  }

                  if (stream_file!=NULL && !ferror (GMIME_STREAM_FILE(stream_file)->fp))
                        status_item_emit_status_va (dd->item, _("Saved \"%s\""), filename);
                  else {
                        log_add_va (LOG_ERROR, _("Error saving file \"%s\" - is the disk full?"), filename);
                        unlink (filename);
                        success = FALSE;
                  }
            }

            g_object_unref (content);
      }

      /* log the success */
      if (success) {
            const PString * group_name = message_identifier_get_primary_group (dd->mids[0]);
            const PString tmp = group_name ? *group_name : pstring_shallow ("???", 3);
            char * msg = g_strdup_printf (_("Decoded \"%s\" from group \"%*.*s\", \"%s\""),
                  filename, tmp.len, tmp.len, tmp.str, dd->subject);
            log_add (LOG_INFO, msg);
            debug0 (DEBUG_DECODE, msg);
            g_free (msg);
      }

      /* cleanup */
      if (stream)
            g_object_unref (stream);
      stream = NULL;
      g_free (filename);
      filename = NULL;

      /* if the Article objects are in memory, update them  as decoded or failed */
      if (1) {
            Group * group = NULL;
            const PString * group_name = message_identifier_get_primary_group (dd->mids[0]);

            if (pstring_is_set (group_name))
                  group = server_get_named_group (dd->server, group_name);

            if (group!=NULL && group_ref_articles_if_loaded (group)) {
                  int qty = 0;
                  Article ** articles = g_newa (Article*, dd->mid_qty);
                  for (i=0; i<dd->mid_qty; ++i) {
                        Article * a = group_get_article_by_message_id (group, &dd->mids[i]->message_id);
                        if (a != NULL)
                              articles[qty++] = a;
                  }
                  articles_set_decode_state (articles, qty, success ? DECODE_STATE_DECODED : DECODE_STATE_FAILED);
                  group_unref_articles (group, NULL);
            }
      }

      if (success)
            message_identifiers_mark_read ((const MessageIdentifier**)dd->mids, dd->mid_qty, TRUE, SERVER_GROUPS_SUBSCRIBED);

      /* cleanup some more */
      if (mm != NULL)
            g_mime_object_unref (GMIME_OBJECT(mm));
      g_string_free (filenames, TRUE);
      g_ptr_array_free (attachments, TRUE);
      debug_exit ("decode_article");

      return success;
}

Generated by  Doxygen 1.6.0   Back to index