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

util-mime.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 <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <gmime/gmime.h>

#include <pan/base/serverlist.h>
#include <pan/base/article.h>
#include <pan/base/debug.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/util-mime.h>

/***
**** YENC
***/

#define YENC_MARKER_BEGIN      "=ybegin"
#define YENC_MARKER_BEGIN_LEN  7
#define YENC_MARKER_PART       "=ypart"
#define YENC_MARKER_PART_LEN   6
#define YENC_MARKER_END        "=yend"
#define YENC_MARKER_END_LEN    5
#define YENC_TAG_PART          " part="
#define YENC_TAG_LINE          " line="
#define YENC_TAG_SIZE          " size="
#define YENC_TAG_NAME          " name="
#define YENC_TAG_BEGIN         " begin="
#define YENC_TAG_END           " end="
#define YENC_TAG_PCRC32                " pcrc32="
#define YENC_TAG_CRC32         " crc32="
#define YENC_FULL_LINE_LEN     256
#define YENC_HALF_LINE_LEN     128
#define YENC_ESC_NULL          "=@"
#define YENC_ESC_TAB           "=I"
#define YENC_ESC_LF            "=J"
#define YENC_ESC_CR            "=M"
#define YENC_ESC_ESC           "={"
#define YENC_SHIFT             42
#define YENC_QUOTE_SHIFT       64

static const char*
__yenc_extract_tag_val_char (const char * line, const char *tag)
{
      const char * retval = NULL;
      const char * tmp;
       
      tmp = strstr (line, tag);
      if (tmp != NULL) {
            tmp += strlen (tag);
            if (*tmp != '\0')
                  retval = tmp;
      }

      return retval;
}

static guint
__yenc_extract_tag_val_int_base (const char * line,
                                 const char * tag,
                                 int          base)
{
      guint retval = 0;
      const char * tmp;

      tmp = __yenc_extract_tag_val_char (line, tag);

      if (tmp != NULL) {
            char * tail = NULL;
            retval = strtoul (tmp, &tail, base);
            if (tmp == tail)
                  retval = 0;
      }

      return retval;
}

static int
__yenc_extract_tag_val_hex_int (const char * line,
                                const char * tag)
{
      return __yenc_extract_tag_val_int_base (line, tag, 16);
}

static int
__yenc_extract_tag_val_int (const char *line,
                            const char *tag)
{
      return __yenc_extract_tag_val_int_base (line, tag, 0);
}


static int
yenc_parse_end_line (const char * b,
                     size_t     * size,
                     int        * part,
                     guint      * pcrc,
                     guint      * crc)
{
      size_t _size = 0;
      int _part = 0;
      guint _pcrc = 0;
      guint _crc = 0;
      const char * pch;

      /* find size */
      pch = __yenc_extract_tag_val_char (b, YENC_TAG_SIZE);
      if (pch!=NULL)
            _size = strtoul(pch, NULL, 10);
      g_return_val_if_fail (_size!=0, -1);

      /* part is optional */
      _part = __yenc_extract_tag_val_int (b, YENC_TAG_PART);
      _pcrc = __yenc_extract_tag_val_hex_int (b, YENC_TAG_PCRC32);
      if (part != 0)
            g_return_val_if_fail( _pcrc != 0, -1 );

      _crc = __yenc_extract_tag_val_hex_int( b, YENC_TAG_CRC32);

      if (size)
            *size = _size;
      if (part)
            *part = _part;
      if (pcrc)
            *pcrc = _pcrc;
      if (crc)
            *crc = _crc;

      return 0;
}

/**
 * yenc_parse_being_line
 * @param line the line to check for "begin [part] line size filename"
 * @param filename if parse is successful, is set with the
 *        starting character of the filename.
 * @param line_len if parse is successful, is set with the line length
 * @param part is parse is successful & set to the cuurent attachements 
 *       part number
 * @return 0 on success, -1 on failure
 */
static int
yenc_parse_begin_line (const char  * b,
                       char       ** file,
                       int         * line_len,
                       int         * attach_size,
                       int         * part)
{
      int ll;
      int part_num;
      int a_sz;
      const char *fname;

      ll = __yenc_extract_tag_val_int (b, YENC_TAG_LINE);
      g_return_val_if_fail (ll != 0, -1);

      /* part is optional */
      part_num = __yenc_extract_tag_val_int (b, YENC_TAG_PART);
      
      a_sz = __yenc_extract_tag_val_int( b, YENC_TAG_SIZE );
      g_return_val_if_fail( a_sz != 0, -1 );
      
      fname = __yenc_extract_tag_val_char (b, YENC_TAG_NAME);
      g_return_val_if_fail( fname, -1 );

      if (line_len)
            *line_len = ll;
      if (file) {
            const char * pch = strchr (fname, '\n');
            *file = g_strstrip (g_strndup (fname, pch-fname));
      }
      if (part)
            *part = part_num;
      if (attach_size)
            *attach_size = a_sz;

      return 0;
}

/*
 * a =ypart line requires both begin & end offsets. These are the location
 * of the part inside the end file
 */
static int
yenc_parse_part_line (const char * b, guint *begin_offset, guint *end_offset)
{
      int bg;
      int end;

      bg = __yenc_extract_tag_val_int( b, YENC_TAG_BEGIN );
      if (bg == 0)
            return -1;

      end = __yenc_extract_tag_val_int( b, YENC_TAG_END );
      if (end == 0)
            return -1;

      if (begin_offset)
            *begin_offset = bg;
      if (end_offset)
            *end_offset = end;

      return 0;
}

/**
 * yenc_is_beginning
 * @param line line to test & see if it's the beginning of a yenc block
 * @return true if it is, false otherwise
 */
static gboolean
yenc_is_beginning_line (const char * line)
{
      return !strncmp (line, YENC_MARKER_BEGIN, YENC_MARKER_BEGIN_LEN) &&
             !yenc_parse_begin_line (line, NULL, NULL, NULL, NULL);
}

static gboolean
yenc_is_part_line (const char * line)
{
      return !strncmp (line, YENC_MARKER_PART, YENC_MARKER_PART_LEN) &&
             !yenc_parse_part_line (line, NULL, NULL);
}

/**
 * yenc_is_ending_line
 * @param line line to test & see if it's the end of a yenc block
 * @return true if it is, false otherwise
 */
static gboolean
yenc_is_ending_line (const char * line)
{
      return !strncmp (line, YENC_MARKER_END, YENC_MARKER_END_LEN) &&
             !yenc_parse_end_line (line, NULL, NULL, NULL, NULL);
}


/***
**** UU
***/

static int
uu_parse_begin_line (const PString    * begin,
                     char            ** setme_filename,
                     gulong           * setme_mode)
{
      int retval;
      char * end;
      PString tmp;
      gulong mode;

      g_return_val_if_fail (pstring_is_set (begin), -1);

      /* skip past the "begin " */
      tmp = pstring_substr_shallow (begin, begin->str+6, NULL);
      mode = strtoul (tmp.str, &end, 8);
      if (end==NULL || end-tmp.str<3) /* must have at least 3 octal digits */
            mode = 0ul;

      tmp = pstring_substr_shallow (&tmp, end, NULL); /* skip past the permissions */
      end = pstring_strchr (&tmp, '\n');              /* remove linefeed, if any */
      tmp = pstring_substr_shallow (&tmp, NULL, end); 
      tmp = pstring_strstrip_shallow (&tmp);          /* remove whitespace */

      if (mode==0 || !pstring_is_set(&tmp))
            retval = -1;
      else {
            if (setme_mode != NULL)
                  *setme_mode = mode;
            if (setme_filename != NULL)
                  *setme_filename = g_strndup (tmp.str, tmp.len);
            retval = 0;
      }

      return retval;
}

/**
 * uu_is_beginning
 * @param line line to test & see if it's the beginning of a uu-encoded block
 * @return true if it is, false otherwise
 */
static gboolean
uu_is_beginning_line (const PString * line)
{
      return pstring_is_set(line)
            && line->len>5
            && (!memcmp(line->str, "begin ", 6) || !memcmp(line->str, "BEGIN ", 6))
            && !uu_parse_begin_line (line, NULL, NULL);
}

/**
 * uu_is_ending
 * @param line line to test & see if it's the end of a uu-encoded block
 * @return true if it is, false otherwise
 */
static gboolean
uu_is_ending_line (const char * line)
{
      return line!=NULL && !g_strncasecmp(line,"end",3) && !strstr(line,"cut") && !strstr(line,"CUT");
}


static gboolean
is_uu_line (const char * line, int len)
{
      int octet_len;
      int char_len;
      const char * pch;
      const char * line_end;

      g_return_val_if_fail (line!=NULL, FALSE);

      if (*line=='\0' || len<1)
            return FALSE;

      if (len==1 && *line=='`')
            return TRUE;

      /* get octet length */
      octet_len = *line - 0x20;
      if (octet_len > 45)
            return FALSE;

      /* get character length */
      char_len = (octet_len / 3) * 4;
      switch (octet_len % 3) {
            case 0: break;
            case 1: char_len += 2; break;
            case 2: char_len += 3; break;
      }
      if (char_len+1 > len)
            return FALSE;

      /* if there's a lot of noise at the end, be suspicious.
         This is to keep out lines of nntp-server-generated
         taglines that have a space as the first character */
      if (char_len+10 < len)
            return FALSE;

      /* make sure each character is in the uuencoded range */
      for (pch=line+1, line_end=pch+char_len; pch!=line_end; ++pch)
            if (*pch<0x20 || *pch>0x60)
                  return FALSE;

      /* looks okay */
      return TRUE;
}

static guint
stream_readln (GMimeStream *stream, GByteArray *line, off_t* startpos)
{
      char linebuf[1024];
      gssize len;

      /* sanity clause */
      g_return_val_if_fail (stream!=NULL, 0u);
      g_return_val_if_fail (line!=NULL, 0u);
      g_return_val_if_fail (startpos!=NULL, 0u);

      /* where are we now? */
      *startpos = g_mime_stream_tell (stream);

      /* fill the line array */
      g_byte_array_set_size (line, 0);
      do {
            len = g_mime_stream_buffer_gets (stream, linebuf, sizeof(linebuf));
            if (len>0)
                  g_byte_array_append (line, (const guint8 *)linebuf, len);
      } while (len>0 && linebuf[len-1]!='\n');

      return line->len;
}

typedef enum
{
      ENC_PLAIN,
      ENC_YENC,
      ENC_UU
}
EncType;
typedef struct
{
      GMimeStream * cat;
      char * decoded_filename;

      int y_line_len;
      int y_attach_size;
      int y_part;
      guint y_offset_begin;
      guint y_offset_end;
      guint y_crc;
      guint y_pcrc;
      size_t y_size;

      EncType type;
}
EncTempFilePart;

static void
separate_encoded_parts (GMimeStream * istream,
                        GPtrArray   * appendme_file_parts)
{
      EncTempFilePart * cur = NULL;
      EncType type = ENC_PLAIN;
      GByteArray * line;
      gboolean yenc_looking_for_part_line = FALSE;
      off_t linestart_pos = 0;
      off_t sub_begin = 0;
      guint line_len;
      guint valid_encoded_lines_in_part;

      /* sanity clause */
      g_return_if_fail (istream!=NULL);
      g_return_if_fail (appendme_file_parts!=NULL);

      sub_begin = 0;
      line = g_byte_array_sized_new (4096);
      while ((line_len = stream_readln (istream, line, &linestart_pos)))
      {
            char * line_str = (char*) line->data;
            char * pch = strchr (line_str, '\n');
            if (pch != NULL) {
                  pch[1] = '\0';
                  line_len = pch - line_str;
            }

            switch (type)
            {
                  case ENC_PLAIN:
                  {
                        const PString line_pstr = pstring_shallow (line_str, line_len);

                        if (uu_is_beginning_line (&line_pstr))
                        {
                              gulong mode = 0ul;
                              char * decoded_filename = NULL;

                              /* flush the current entry */
                              if (cur != NULL)
                              {
                                    GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
                                    g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                                    g_mime_stream_unref (stream);
                                    g_ptr_array_add (appendme_file_parts, cur);
                                    cur = NULL;
                              }

                              sub_begin = linestart_pos;
                              valid_encoded_lines_in_part = 0u;
                              uu_parse_begin_line (&line_pstr, &decoded_filename, &mode);

                              cur = g_new0 (EncTempFilePart, 1);
                              cur->type = type = ENC_UU;
                              cur->decoded_filename = decoded_filename;
                              cur->cat = g_mime_stream_cat_new ();
                        }
                        else if (yenc_is_beginning_line (line_str))
                        {
                              /* flush the current entry */
                              if (cur != NULL)
                              {
                                    GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
                                    g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                                    g_mime_stream_unref (stream);
                                    g_ptr_array_add (appendme_file_parts, cur);
                                    cur = NULL;
                              }

                              sub_begin = linestart_pos;
                              valid_encoded_lines_in_part = 0u;

                              cur = g_new0 (EncTempFilePart, 1);
                              cur->type = type = ENC_YENC;
                              cur->cat = g_mime_stream_cat_new ();
                              yenc_parse_begin_line (line_str,
                                                     &cur->decoded_filename,
                                                     &cur->y_line_len,
                                                     &cur->y_attach_size,
                                                     &cur->y_part);
                              if (cur->y_part != 0)
                                    yenc_looking_for_part_line = TRUE;
                        }
                        else if (cur == NULL)
                        {
                              sub_begin = linestart_pos;
                              valid_encoded_lines_in_part = 0u;

                              cur = g_new0 (EncTempFilePart, 1);
                              cur->type = type = ENC_PLAIN;
                              cur->cat = g_mime_stream_cat_new ();
                        }
                        break;
                  }
                  case ENC_UU:
                  {
                        if (uu_is_ending_line(line_str))
                        {
                              GMimeStream * stream;
                              if (sub_begin < 0)
                                    sub_begin = linestart_pos;
                              stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
                              g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                              g_mime_stream_unref (stream);
                                    g_ptr_array_add (appendme_file_parts, cur);

                              cur = NULL;
                              type = ENC_PLAIN;
                        }
                        else if (!is_uu_line(line_str, line_len))
                        {
                              /* hm, this isn't a uenc line, so ending the cat and setting sub_begin to -1 */
                              if (sub_begin >= 0)
                              {
                                    GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
                                    g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                                    g_mime_stream_unref (stream);
                              }

                              sub_begin = -1;
                        }
                        else if (sub_begin == -1)
                        {
                              /* looks like they decided to start using uu lines again. */
                              ++valid_encoded_lines_in_part;
                              sub_begin = linestart_pos;
                        }
                        else
                        {
                              ++valid_encoded_lines_in_part;
                        }
                        break;
                  }
                  case ENC_YENC:
                  {
                        if (yenc_is_ending_line (line_str))
                        {
                              GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
                              g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                              g_mime_stream_unref (stream);
                              yenc_parse_end_line (line_str, &cur->y_size, NULL, &cur->y_pcrc, &cur->y_crc);
                                    g_ptr_array_add (appendme_file_parts, cur);

                              cur = NULL;
                              type = ENC_PLAIN;
                        }
                        else if (yenc_looking_for_part_line && yenc_is_part_line(line_str))
                        {
                              yenc_parse_part_line (line_str, &cur->y_offset_begin, &cur->y_offset_end);
                              yenc_looking_for_part_line = FALSE;
                              ++valid_encoded_lines_in_part;
                        }
                        else
                        {
                              ++valid_encoded_lines_in_part;
                        }
                        break;
                  }
            }
      }

      /* close last entry */
      if (cur != NULL)
      {
            if (sub_begin >= 0)
            {
                  GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
                  g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cur->cat), stream);
                  g_mime_stream_unref (stream);
            }

            /* just in case someone started with "yenc" or "begin 644 asf" in a text message to fuck with unwary newsreaders */
            if (valid_encoded_lines_in_part < 10u)
                  cur->type = ENC_PLAIN;

            g_ptr_array_add (appendme_file_parts, cur);
            cur = NULL;
            type = ENC_PLAIN;
      }

      g_byte_array_free (line, TRUE);
}

static void
guess_part_type_from_filename (const char   * filename,
                               const char  ** setme_type,
                               const char  ** setme_subtype)
{
      static const struct {
            const char * suffix;
            const char * type;
            const char * subtype;
      } suffixes[] = {
            { ".avi",   "video",        "vnd.msvideo" },
            { ".dtd",   "text",         "xml-dtd" },
            { ".gif",   "image",        "gif" },
            { ".htm",   "text",         "html" },
            { ".html",  "text",         "html" },
            { ".jpg",   "image",        "jpeg" },
            { ".jpeg",  "image",        "jpeg" },
            { ".md5",   "image",        "tiff" },
            { ".mp3",   "audio",        "mpeg" },
            { ".mpeg",  "video",        "mpeg" },
            { ".mpg",   "video",        "mpeg" },
            { ".mov",   "video",        "quicktime" },
            { ".nfo",   "text",         "plain" },
            { ".ogg",   "audio",        "x-vorbis" },
            { ".png",   "image",        "png" },
            { ".qt",    "video",        "quicktime" },
            { ".rar",   "application",  "x-rar" },
            { ".rv",    "video",        "vnd.rn-realvideo" },
            { ".tar",   "application",  "x-tar" },
            { ".tbz2",  "application",  "x-tar" },
            { ".tgz",   "application",  "x-tar" },
            { ".tiff",  "image",        "tiff" },
            { ".tif",   "image",        "tiff" },
            { ".txt",   "text",         "plain" },
            { ".uu",    "text",         "x-uuencode" },
            { ".uue",   "text",         "x-uuencode" },
            { ".xml",   "text",         "xml" },
            { ".xsl",   "text",         "xml" },
            { ".zip",   "application",  "zip" }
      };
      static const int suffix_qty = G_N_ELEMENTS (suffixes);
      const char * suffix;

      /* zero out the return values */
      g_return_if_fail (setme_type!=NULL);
      g_return_if_fail (setme_subtype!=NULL);
      *setme_type = *setme_subtype = NULL;

      /* sanity clause */
      g_return_if_fail (is_nonempty_string(filename));

            suffix = strrchr (filename, '.');
      if (suffix != NULL) {
            int i;
            for (i=0; i<suffix_qty; ++i) {
                  if (!g_ascii_strcasecmp (suffix, suffixes[i].suffix)) {
                        *setme_type = suffixes[i].type;
                        *setme_subtype = suffixes[i].subtype;
                        break;
                  }
            }
      }

      if (*setme_type == NULL) {
            *setme_type = "text";
            *setme_subtype = "plain";
      }
}

static void
free_yenc_info (gpointer p)
{
      YencInfo * y;

      g_return_if_fail (p!=NULL);

      y = (YencInfo*) p;
      g_free (y->filename);
      g_free (y);
}


static void
handle_inline_encoded_data (GMimeObject **part)
{
      const GMimeContentType *content_type;
      GMimeStream *stream, *istream;
      GMimeDataWrapper *content;
      GMimeMultipart *multipart;
      GPtrArray *parts;
      int i;
      
      content_type = g_mime_object_get_content_type (*part);

      /* if the part is a multipart, check its subparts */
      if (GMIME_IS_MULTIPART (*part)) {
            GMimeObject *subpart;
            GList *subparts;
            
            subparts = GMIME_MULTIPART (*part)->subparts;
            while (subparts) {
                  subpart = (GMimeObject *) subparts->data;
                  handle_inline_encoded_data (&subpart);
                  subparts->data = subpart;
                  subparts = subparts->next;
            }
            
            return;
      }

      /* if it's not a text part, we assume that it can't contain inline encoded blocks */
      if (!g_mime_content_type_is_type (content_type, "text", "plain"))
            return;
      
      content = (GMimeDataWrapper *) g_mime_part_get_content_object (GMIME_PART (*part));
      if (!content)
            return;

      stream = g_mime_data_wrapper_get_stream (content);
      g_mime_stream_reset (stream);
      istream = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_BLOCK_READ);
      g_mime_stream_unref (stream);
      g_object_unref (content);

      parts = g_ptr_array_new ();
      separate_encoded_parts (istream, parts);
      g_mime_stream_reset (istream);

      /* split? */
      if (parts->len > 1 || (parts->len == 1 && ((EncTempFilePart *) parts->pdata[0])->type != ENC_PLAIN))
      {
            /* create a parent multipart to contain the resulting subparts */
            multipart = g_mime_multipart_new_with_subtype ("mixed");
            
            /* add the subparts */
            for (i = 0; i < parts->len; i++)
            {
                  const EncTempFilePart *enc_part = parts->pdata[i];
                  GMimePart *subpart = NULL;
                  GMimeStream * filter_stream;
                  GMimeStream * substream;
                  const char * type = NULL;
                  const char * subtype = NULL;
                  
                  /* create the new subpart's stream */

                  substream = enc_part->cat;

                  if (enc_part->type == ENC_UU)
                  {
                        guess_part_type_from_filename (enc_part->decoded_filename, &type, &subtype);
                        
                        subpart = g_mime_part_new_with_type (type, subtype);

                        /* set part's attributes */
                        g_mime_part_set_filename (subpart, enc_part->decoded_filename);

                        filter_stream = g_mime_stream_filter_new_with_stream (substream);
                        g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter_stream), g_mime_filter_basic_new_type (GMIME_FILTER_BASIC_UU_DEC));
                        content = g_mime_data_wrapper_new_with_stream (filter_stream, GMIME_PART_ENCODING_DEFAULT);
                        g_mime_part_set_content_object (subpart, content);
                        g_object_unref (content);
                        g_mime_stream_unref (filter_stream);
                  }
                  else if (enc_part->type == ENC_YENC)
                  {
                        YencInfo *yenc_info;
                        guess_part_type_from_filename (enc_part->decoded_filename, &type, &subtype);
                        
                        subpart = g_mime_part_new_with_type (type, subtype);
                        
                        /* set part's attributes */
                        g_mime_part_set_filename (subpart, enc_part->decoded_filename);
                        
                        g_mime_part_set_content_disposition (subpart, "inline");
                        
                        /* set the part's content */
                        content = g_mime_data_wrapper_new_with_stream (substream,
                                                             GMIME_PART_ENCODING_DEFAULT);
                        g_mime_part_set_content_object (subpart, content);
                        g_object_unref (content);
                        
                        yenc_info = g_new0 (YencInfo, 1);
                        yenc_info->filename = g_strdup (enc_part->decoded_filename);
                        yenc_info->line_len = enc_part->y_line_len;
                        yenc_info->attach_size = enc_part->y_attach_size;
                        yenc_info->part = enc_part->y_part;
                        yenc_info->offset_begin = enc_part->y_offset_begin;
                        yenc_info->offset_end = enc_part->y_offset_end;
                        yenc_info->crc = enc_part->y_crc;
                        yenc_info->pcrc = enc_part->y_pcrc;
                        yenc_info->size = enc_part->y_size;
                        g_object_set_data_full (G_OBJECT(subpart), "yenc", yenc_info, free_yenc_info);
                  }
                  else
                  {
                        subpart = g_mime_part_new_with_type ("text", "plain");
                        
                        content = g_mime_data_wrapper_new_with_stream (substream,
                                                             GMIME_PART_ENCODING_8BIT);
                        
                        g_mime_part_set_content_object (subpart, content);
                        g_object_unref (content);
                  }
                  
                  g_mime_multipart_add_part (GMIME_MULTIPART (multipart), GMIME_OBJECT (subpart));
                  g_mime_object_unref (GMIME_OBJECT (subpart));
            }
            
            /* replace the old part with the new multipart */
            g_mime_object_unref (*part);
            *part = GMIME_OBJECT (multipart);
      }

      g_mime_stream_unref (istream);

      for (i=0; i<parts->len; ++i) {      
            EncTempFilePart * enc_part = parts->pdata[i];
            g_mime_stream_unref (enc_part->cat);
            g_free (enc_part->decoded_filename);
            g_free (enc_part);
      }
      
      g_ptr_array_free (parts, TRUE);
}

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

/**
 * @parent: only needed for the headers so that we can construct a true
 * message/partial part.  This is actually only used for the first part,
 * so feel free to use %NULL for all parts after the first.
 *
 * Returns a partial mime part.
 */
static GMimeMessagePartial*
pan_fake_partial (GMimeMessage  * parent,
                  GMimeStream   * content,
                  const char    * id,
                  int             number,
                  int             total)
{
      GMimeMessagePartial * partial;
      GMimeDataWrapper * wrapper;
      GMimeStream * cat = NULL;

      /* sanity clause */
      g_return_val_if_fail (content!=NULL, NULL);
      g_return_val_if_fail (is_nonempty_string(id), NULL);
      g_return_val_if_fail (number>0, NULL);
      g_return_val_if_fail (total>0, NULL);
      g_return_val_if_fail (total>=number, NULL);

      /* if it's the first message, add the headers */
      if (number == 1)
      {
            GMimeStream * stream;

            /* fill "stream" with the parent's headers */
            stream = g_mime_stream_mem_new ();
            g_mime_header_write_to_stream (GMIME_OBJECT(parent)->headers, stream);
            g_mime_stream_write (stream, "\n", 1);
            g_mime_stream_reset (stream);

            /* cat stream holds headers + body */
            cat = g_mime_stream_cat_new ();
            g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cat), stream);
            g_mime_stream_unref (stream);
            g_mime_stream_cat_add_source (GMIME_STREAM_CAT(cat), content);

            /* override the content variable s.t. headers get written too */
            partial = g_mime_message_partial_new (id, number, total);
            wrapper = g_mime_data_wrapper_new_with_stream (cat, GMIME_PART_ENCODING_DEFAULT);
            g_mime_part_set_content_object (GMIME_PART(partial), wrapper);
            g_object_unref (wrapper);
            g_mime_stream_unref (cat);
      }
      else
      {
            partial = g_mime_message_partial_new (id, number, total);
            wrapper = g_mime_data_wrapper_new_with_stream (content, GMIME_PART_ENCODING_DEFAULT);
            g_mime_part_set_content_object (GMIME_PART(partial), wrapper);
            g_object_unref (wrapper);
      }

      return partial;
}



GMimeMessage*
pan_g_mime_parser_construct_message (GMimeStream  ** istreams,
                                     int             qty)
{
      int i;
      const char * message_id = "Foo <bar@mum>";
      GMimeMessage * retval = NULL;
      GMimeMessage ** messages = NULL;
      GMimeParser * parser;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(message_id), NULL);
      g_return_val_if_fail (istreams!=NULL, NULL);
      g_return_val_if_fail (qty>=1, NULL);
      for (i=0; i<qty; ++i)
            g_return_val_if_fail (GMIME_IS_STREAM(istreams[i]), NULL);

      /* build the GMimeMessages */
      parser = g_mime_parser_new ();
      messages = g_newa (GMimeMessage*, qty);
      for (i=0; i<qty; ++i) {
            g_mime_parser_init_with_stream (parser, istreams[i]);
            messages[i] = g_mime_parser_construct_message (parser);
      }
      g_object_unref (parser);

      /* make a single message */
      if (qty == 1)
      {
            retval = messages[0];
      }
      else
      {
            gpointer * unref = g_newa (gpointer, qty);
            GMimeMessagePartial ** partials  = g_newa (GMimeMessagePartial*, qty);

            /* make an array of message/partials that to reconstruct a single message */
            for (i=0; i<qty; ++i)
            {
                  GMimeMessagePartial * partial;

                  if (GMIME_IS_MESSAGE_PARTIAL (messages[i]->mime_part))
                  {
                        partial = GMIME_MESSAGE_PARTIAL (messages[i]->mime_part);
                        unref[i] = messages[i];
                  }
                  else
                  {
                        GMimeMessage * message;
                        GMimeDataWrapper * wrapper;
                        GMimeStream * stream;

                        /* make a partial */
                        message = messages[i];
                        wrapper = g_mime_part_get_content_object (GMIME_PART(message->mime_part));
                        stream = g_mime_data_wrapper_get_stream ((GMimeDataWrapper*)wrapper);
                        partial = pan_fake_partial (message, stream, message_id, i+1, qty);
                        g_mime_stream_reset (stream);

                        g_mime_stream_unref (stream);
                        g_object_unref (wrapper);
                        g_object_unref (message);

                        unref[i] = partial;
                  }

                  partials[i] = partial;
            }

            /* merge the message/partials together */
            retval = g_mime_message_partial_reconstruct_message (partials, qty);

            /* cleanup */
            for (i=0; i<qty; ++i)
                  g_object_unref (unref[i]);
      }

      /* pick out yenc and uuenc data in text/plain messages */
      if (retval != NULL)
            handle_inline_encoded_data (&retval->mime_part);

      /* cleanup */
      return retval;
}

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

void
pan_g_mime_message_mark_read (GMimeMessage * message)
{
      const char * xref;
      debug_enter ("pan_g_mime_message_mark_read");

      g_return_if_fail (GMIME_IS_MESSAGE (message));

      xref = g_mime_message_get_header (message, HEADER_XREF);

      if (is_nonempty_string (xref))
      {
            GString * token = g_string_new (NULL);
            Server * server = serverlist_get_active_server ();
            const char * message_id_str = g_mime_message_get_message_id (message);
            const PString message_id = pstring_shallow (message_id_str, -1);

            /* skip servername */
            skip_next_token (xref, ' ', &xref);

            /* walk through the xrefs group:number group2:number2*/
            while ((get_next_token_g_str (xref, ' ', &xref, token)))
            {
                  /* parse out the group and number values */
                  char * delim = strchr (token->str, ':');
                  if (delim != NULL)
                  {
                        /* if the group is subscribed, mark the article as read */
                        const PString group_name = pstring_shallow (token->str, delim-token->str);
                        Group * group = server_get_named_group_in_type (server, &group_name, SERVER_GROUPS_SUBSCRIBED);
                        if (group != NULL)
                        {
                              if (group_ref_articles_if_loaded (group)) {
                                    Article * article = group_get_article_by_message_id (group, &message_id);
                                    if (article != NULL)
                                          articles_set_read_simple (&article, 1, TRUE);
                                    group_unref_articles (group, NULL);
                              }
                              else if (group_is_subscribed (group))
                                    group_mark_article_read (group, strtoul (delim+1, NULL, 10), TRUE);

                        }
                  }
            }

            g_string_free (token, TRUE);
      }

      debug_exit ("pan_g_mime_message_mark_read");
}

/**
 * Loops through the message's Newsgroups: header
 * until we find a group that is already loaded.
 * That group is returned.
 */
Group*
pan_g_mime_message_get_group (GMimeMessage * message)
{
      Group * retval = NULL;
      Server * server;

      g_return_val_if_fail (message!=NULL, NULL);

            server = serverlist_get_active_server ();
      if (server != NULL)
      {
            const char * newsgroups = g_mime_message_get_header (message, HEADER_NEWSGROUPS);
            if (is_nonempty_string (newsgroups))
            {
                  PString group_name;

                  for (;;)
                  {
                        int group_name_len;
                        const char * group_name_str;
                        const char * march = newsgroups;

                        if (!get_next_token_run (march, ',', &march, &group_name_str, &group_name_len))
                              break;

                        group_name = pstring_shallow (group_name_str, group_name_len);
                        retval = server_get_named_group (server, &group_name);
                        if (retval != NULL)
                              break;
                  }
            }
      }

      return retval;
}

/**
 * Retrieve the charset from a mime message
 */

static void
get_charset_partfunc (GMimeObject * obj, gpointer charset_gpointer)
{
      GMimePart * part;
      const GMimeContentType * type;
      const char ** charset;

      if (!GMIME_IS_PART (obj))
            return;

      part = GMIME_PART (obj);
      type = g_mime_object_get_content_type (GMIME_OBJECT (part));
      charset = (const char **) charset_gpointer;
      if (g_mime_content_type_is_type (type, "text", "*"))
            *charset = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "charset");
}

const char *
pan_g_mime_message_get_charset (GMimeMessage * message)
{
      const char * retval = NULL;
      g_return_val_if_fail (message!=NULL, NULL);

      g_mime_message_foreach_part (message, get_charset_partfunc, &retval);

      return retval;
}

Generated by  Doxygen 1.6.0   Back to index