[pan2/testing] a little broken ...



commit 0220ec7dc64ddc74447ce477c024976840bcb7af
Author: Heinrich MÃller <henmull src gnome org>
Date:   Mon Dec 12 14:29:15 2011 +0100

    a little broken ...

 configure.in                   |   16 +-
 pan/gui/body-pane.cc           |   35 +-
 pan/gui/body-pane.h            |    4 +-
 pan/usenet-utils/mime-utils.cc | 2045 ++++++++++++++++++++--------------------
 pan/usenet-utils/mime-utils.h  |    2 +-
 5 files changed, 1063 insertions(+), 1039 deletions(-)
---
diff --git a/configure.in b/configure.in
index 5ce2b3c..a89b89a 100644
--- a/configure.in
+++ b/configure.in
@@ -122,7 +122,7 @@ fi
 
 dnl Check for libnotify if user-enabled for popup notifications
 AC_ARG_ENABLE([libnotify],
-AC_HELP_STRING([--enable-libnotify],[enable libnotify support]),,[enable_libnotify=yes])
+AC_HELP_STRING([--enable-libnotify],[enable libnotify support]),[enable_libnotify=$enableval],[enable_libnotify=yes])
 if test "x$enable_libnotify" = "xyes" ; then
   PKG_CHECK_MODULES([LIBNOTIFY],[libnotify >= $LIBNOTIFY_REQUIRED],[HAVE_LIBNOTIFY="yes"],[HAVE_LIBNOTIFY="no"])
   AC_SUBST([LIBNOTIFY_CFLAGS])
@@ -134,7 +134,7 @@ fi
 
 dnl check for libgsasl for secure authentication
 AC_ARG_ENABLE([libgsasl],
-AC_HELP_STRING([--enable-libgsasl],[enable libgsasl support]),,[enable_libgsasl=yes])
+AC_HELP_STRING([--enable-libgsasl],[enable libgsasl support]),[enable_libgsasl=$enableval],[enable_libgsasl=yes])
 if test "x$enable_libgsasl" = "xyes" ; then
   PKG_CHECK_MODULES([LIBGSASL],[libgsasl >= $LIBGSASL_REQUIRED],[HAVE_SASL="yes"],[HAVE_SASL="no"])
   AC_SUBST([LIBGSASL_CFLAGS])
@@ -145,9 +145,9 @@ if test "x$enable_libgsasl" = "xyes" ; then
 fi
 
 dnl Check for gnome-keyring if user-enabled for popup notifications
-AC_ARG_ENABLE([gnome-keyring],
-AC_HELP_STRING([--enable-gnome-keyring],[enable gnome-keyring support]),,[enable_gnome-keyring=yes])
-if test "x$enable-gnome-keyring" = "xyes" ; then
+AC_ARG_ENABLE([gkr],
+AC_HELP_STRING([--enable-gkr],[enable gnome-keyring support]),[enable_gkr=$enableval],[enable_gkr=yes])
+if test "x$enable_gkr" = "xyes" ; then
   PKG_CHECK_MODULES([LIBGNOME_KEYRING_1],[gnome-keyring-1 >= $LIBGKR_REQUIRED],[HAVE_GKR="yes"],[HAVE_GKR="no"])
   AC_SUBST([LIBGNOME_KEYRING_1_CFLAGS])
   AC_SUBST([LIBGNOME_KEYRING_1_LIBS])
@@ -158,10 +158,10 @@ fi
 
 dnl Check for gpgme for message encryption / signing
 AC_ARG_ENABLE([gpgme],
-AC_HELP_STRING([--enable-gpgme],[enable gpgme support]),,[enable_gpgme=no])
-if test "x$enable-gpgme" = "xyes" ; then
+AC_HELP_STRING([--enable-gpgme],[enable gpgme support]),[enable_gpgme=$enableval],[enable_gpgme=yes])
+if test "x$enable_gpgme" = "xyes" ; then
   AM_PATH_GPGME([$LIBGPGME_REQUIRED],[HAVE_GPGME="yes"],[HAVE_GPGME="no"])
-  if test "x$HAVE_GKR" = "xyes"; then
+  if test "x$HAVE_GPGME" = "xyes"; then
     AC_DEFINE([HAVE_GPGME],[1],[gpgme for message encryption / signing])
   fi
 fi
diff --git a/pan/gui/body-pane.cc b/pan/gui/body-pane.cc
index 56ef715..bd0a79e 100644
--- a/pan/gui/body-pane.cc
+++ b/pan/gui/body-pane.cc
@@ -918,7 +918,7 @@ namespace
 
 #ifdef HAVE_GPGME
 bool
-BodyPane ::get_gpgsig_from_gmime_part (GMimePart * part)
+BodyPane ::get_gpgsig_from_gmime_part (GMimeObject* parent, GMimeObject* base, GMimePart * part)
 {
   GMimeDataWrapper * wrapper (g_mime_part_get_content_object (part));
   GMimeStream * mem_stream (g_mime_stream_mem_new ());
@@ -926,7 +926,9 @@ BodyPane ::get_gpgsig_from_gmime_part (GMimePart * part)
   {
     g_mime_data_wrapper_write_to_stream (wrapper, mem_stream);
     g_mime_stream_reset(mem_stream);
-    gpg_decrypt_and_verify(_signer_info, _gpgerr, mem_stream);
+    gpg_decrypt_and_verify(_signer_info, _gpgerr, mem_stream,
+                           g_mime_multipart_index_of(GMIME_MULTIPART(parent),base),
+                           parent);
     return true;
   }
   return false;
@@ -934,7 +936,7 @@ BodyPane ::get_gpgsig_from_gmime_part (GMimePart * part)
 #endif
 
 void
-BodyPane :: append_part (GMimeObject * obj, GtkAllocation * widget_size)
+BodyPane :: append_part (GMimeObject * parent, GMimeObject * obj, GtkAllocation * widget_size)
 {
   bool is_done (false);
 
@@ -1017,9 +1019,10 @@ BodyPane :: append_part (GMimeObject * obj, GtkAllocation * widget_size)
     is_done = true;
 #ifdef HAVE_GPGME
     /* verify signature */
+
     if (g_mime_content_type_is_type (type, "*", "pgp-signature"))
     {
-      bool res = get_gpgsig_from_gmime_part(part);
+      bool res = get_gpgsig_from_gmime_part(parent, obj, part);
       std::cerr<<"1023\n";
       if (res) update_sig_valid(_gpgerr.verify_ok);
     }
@@ -1040,13 +1043,13 @@ BodyPane :: append_part (GMimeObject * obj, GtkAllocation * widget_size)
 }
 
 void
-BodyPane :: foreach_part_cb (GMimeObject* /*parent*/, GMimeObject* o, gpointer self)
+BodyPane :: foreach_part_cb (GMimeObject* parent, GMimeObject* o, gpointer self)
 {
   BodyPane * pane = static_cast<BodyPane*>(self);
   GtkWidget * w (pane->_text);
   GtkAllocation aloc;
   gtk_widget_get_allocation(w, &aloc);
-  pane->append_part (o, &aloc);
+  pane->append_part (parent, o, &aloc);
 }
 
 
@@ -1141,15 +1144,6 @@ BodyPane :: set_text_from_message (GMimeMessage * message)
         w = std::max (w, l);
       }
     }
-
-      // obsolete in favor of the certificate icon tooltip (bodypane)
-//    const StringView gpg (g_mime_object_get_header ((GMimeObject *) message, "X-GPG-Signed"));
-//    if (!gpg.empty())
-//    {
-//      char buf[256];
-//      l = add_header_line (s, message, _("GPG-Signed message signature "), "X-GPG-Signed", fallback_charset);
-//      w = std::max (w, l);
-//    }
   }
 
   s.resize (s.size()-1); // remove trailing linefeed
@@ -1237,7 +1231,7 @@ BodyPane :: set_text_from_message (GMimeMessage * message)
     gtk_text_buffer_get_start_iter  (_buffer, &iter);
     gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(_text), &iter, 0.0, true, 0.0, 0.0);
   }
-  std::cerr<<"1240\n";
+  std::cerr<<"1234\n";
 
 }
 
@@ -1273,7 +1267,7 @@ BodyPane:: on_tooltip_query(GtkWidget  *widget,
   GPGDecErr& err = pane->_gpgerr;
   GPGSignersInfo& info = pane->_signer_info;
 
-  g_return_val_if_fail(err.dec_ok, false);
+//  g_return_val_if_fail(err.dec_ok, false);
   g_return_val_if_fail(err.err == GPG_ERR_NO_ERROR || err.err == GPG_ERR_NO_DATA, false);
 
   if (err.no_sigs) return false;
@@ -1281,9 +1275,6 @@ BodyPane:: on_tooltip_query(GtkWidget  *widget,
   if (!err.v_res->signatures) return false;
   if (!err.v_res->signatures->fpr) return false;
 
-// get uid from fingerprint
-//  GPGSignersInfo info = get_uids_from_fingerprint(err->v_res->signatures->fpr);
-
   EvolutionDateMaker ed;
 
   char buf[2048];
@@ -1312,6 +1303,8 @@ BodyPane :: update_sig_valid(int i)
 
   gtk_image_clear(GTK_IMAGE(_sig_icon));
 
+  i = 1;
+
   switch (i)
   {
     case 0:
@@ -1350,7 +1343,7 @@ BodyPane :: set_article (const Article& a)
       val = 0;
   }
 #ifdef HAVE_GPGME
-  std::cerr<<"1354\n";
+  std::cerr<<"1344\n";
   update_sig_valid(val);
 #endif
   refresh ();
diff --git a/pan/gui/body-pane.h b/pan/gui/body-pane.h
index 17c111b..2120f50 100644
--- a/pan/gui/body-pane.h
+++ b/pan/gui/body-pane.h
@@ -92,7 +92,7 @@ namespace pan
       bool read_more_or_less (bool more);
       char* body_to_utf8 (GMimePart*);
       void set_text_from_message (GMimeMessage*);
-      void append_part (GMimeObject*, GtkAllocation*);
+      void append_part (GMimeObject*, GMimeObject*, GtkAllocation*);
       static gboolean expander_activated_idle (gpointer self);
       static void expander_activated_cb (GtkExpander*, gpointer self);
       static void foreach_part_cb (GMimeObject*, GMimeObject*, gpointer self);
@@ -114,7 +114,7 @@ namespace pan
 #ifdef HAVE_GPGME
       GPGDecErr _gpgerr;
       GPGSignersInfo _signer_info;
-      bool get_gpgsig_from_gmime_part (GMimePart * part);
+      bool get_gpgsig_from_gmime_part (GMimeObject* parent, GMimeObject* base, GMimePart * part);
 #endif
 
     private:
diff --git a/pan/usenet-utils/mime-utils.cc b/pan/usenet-utils/mime-utils.cc
index aeb9853..d888773 100644
--- a/pan/usenet-utils/mime-utils.cc
+++ b/pan/usenet-utils/mime-utils.cc
@@ -478,1209 +478,1240 @@ namespace pan
     return true;
   }
 
-#ifdef HAVE_GPGME
-  GMimeStream* gpg_decrypt_and_verify (GPGSignersInfo& signer_info, GPGDecErr& info, GMimeStream* s, const char* body)
+  void apply_source_and_maybe_filter (TempPart * part, GMimeStream * s)
   {
 
-    GMimeStream* decrypted = g_mime_stream_mem_new ();
-
-    ssize_t stream_len = g_mime_stream_length(s);
-
-    char* streambuf = new char[stream_len];
-
-    g_mime_stream_read(s, streambuf, stream_len);
-    g_object_unref(s);
-
-    info.err = GPG_ERR_NO_ERROR;
-
-    gpgme_data_t gpg_buf, gpg_out_buf, body_data;
-    gpgme_key_t key;
-
-    StringView v(streambuf);
-    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
-
-    if (body) gpgme_data_new_from_mem (&body_data, body, strlen(body), 0);
-
-    gpgme_strerror(gpgme_data_new (&gpg_out_buf));
-
-    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_NONE);
-
-    info.err = gpgme_op_decrypt_verify (gpg_ctx, gpg_buf, gpg_out_buf);
+    if (!part->stream) {
+      part->stream = g_mime_stream_mem_new ();
+      if (part->type != ENC_PLAIN && part->type != ENC_GPG) {
+        part->filter_stream = g_mime_stream_filter_new (part->stream);
+        switch (part->type)
+        {
+          case ENC_UU:
+            part->filter = g_mime_filter_basic_new (GMIME_CONTENT_ENCODING_UUENCODE, FALSE);
+            break;
 
-    // no data to decrypt, check for signature validity anyway....
-    if (gpgme_err_code(info.err) == GPG_ERR_NO_DATA || gpgme_err_code(info.err) == GPG_ERR_NO_ERROR)
-      info.dec_ok = true;
-    else
-      return decrypted;
+          case ENC_YENC:
+            part->filter = g_mime_filter_yenc_new (FALSE);
+            break;
+        }
 
-    if (gpgme_err_code(info.err) == GPG_ERR_NO_DATA && body) // verify attached sigs, too
-    {
-      info.err = gpgme_op_verify (gpg_ctx, gpg_buf, body_data, 0);
+        g_mime_stream_filter_add (GMIME_STREAM_FILTER(part->filter_stream),
+                                  part->filter);
+      }
     }
 
-    info.dec_res  = gpgme_op_decrypt_result (gpg_ctx);
-    info.v_res    = gpgme_op_verify_result  (gpg_ctx);
+    g_mime_stream_write_to_stream (s, (part->type == ENC_PLAIN || part->type == ENC_GPG) ?
+				   part->stream : part->filter_stream);
+    g_object_unref (s);
+  }
 
-    if (info.v_res->signatures)
-    {
-      info.no_sigs = false;
-      if (gpgme_err_code(info.v_res->signatures->status) == GPG_ERR_NO_ERROR)
-        info.verify_ok = true;
-    }
-    else
-      info.no_sigs = true;
+  struct sep_state
+  {
+    temp_parts_t master_list;
+    temp_parts_t current_list;
+    TempPart *uu_temp;
+    std::string gpg_verified;
+#ifdef HAVE_GPGME
+    GPGDecErr gpgerr;
+    GPGSignersInfo signer_info;
+#endif
+    sep_state():uu_temp(NULL), gpg_verified("") {};
+  };
 
-    delete streambuf;
+  bool
+  separate_encoded_parts (GMimeStream  * istream, sep_state &state)
+  {
+    temp_parts_t& master(state.master_list);
+    temp_parts_t& appendme(state.current_list);
+    TempPart * cur = NULL;
+    EncType type = ENC_PLAIN;
+    GByteArray * line;
+    gboolean yenc_looking_for_part_line = FALSE;
+    gboolean gpg_looking_for_line = FALSE;
+    gboolean gpg_is_signed = FALSE;
+    gboolean gpg_signed_end_found = FALSE;
+    gint64 linestart_pos = 0;
+    gint64 sub_begin = 0;
+    gint64 signed_msg_start = 0;
+    gint64 signed_msg_end = 0;
+    guint line_len;
+    bool found = false;
 
-    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
+    /* sanity clause */
+    pan_return_val_if_fail (istream!=NULL,false);
 
-    ssize_t ret(0);
-    char buffer[4096]={0};
-    gint64 len(0);
+    sub_begin = 0;
+    line = g_byte_array_sized_new (4096);
 
-    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
+    while ((line_len = stream_readln (istream, line, &linestart_pos)))
     {
-      len += g_mime_stream_write(decrypted, buffer, ret);
-    }
+      char * line_str = (char*) line->data;
+      char * pch = strchr (line_str, '\n');
+      if (pch != NULL) {
+        pch[1] = '\0';
+        line_len = pch - line_str;
+      }
 
-    g_mime_stream_flush (decrypted);
-    g_mime_stream_reset(decrypted);
+      if (gpg_is_signed_begin_line(line_str))
+      {
+        gpg_is_signed = true;
+        signed_msg_start = linestart_pos;
+      }
+      if (gpg_is_signed_end_line(line_str))
+      {
+        gboolean gpg_signed_end_found = true;
+        signed_msg_end = linestart_pos+line_len;
+      }
 
-    gpgme_data_release(gpg_buf);
-    gpgme_data_release(gpg_out_buf);
+      switch (type)
+      {
+        case ENC_PLAIN:
+        {
+          const StringView line_pstr (line_str, line_len);
 
-    GPGSignersInfo si;
+          if (uu_is_beginning_line (line_pstr))
+          {
+            gulong mode = 0ul;
+            char * filename = NULL;
 
-    if (!info.no_sigs)
-    {
-      if (info.v_res->signatures->fpr)
-        gpgme_get_key (gpg_ctx, info.v_res->signatures->fpr, &key, 0);
-      if (key)
-        fill_signer_info(si, key);
-    }
+            found=true;
+            // flush the current entry
+            if (cur != NULL) {
+              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
+              apply_source_and_maybe_filter (cur, s);
 
-    signer_info = si;
-    info.err = GPG_ERR_NO_ERROR;
+              if ( append_if_not_present (master, cur) )
+                append_if_not_present (appendme, cur);
+              cur = NULL;
+            }
 
-    return decrypted;
-  }
+            // start a new section (or, if filename matches, continue an existing one)
+            sub_begin = linestart_pos;
+            uu_parse_begin_line (line_pstr, &filename, &mode);
+            cur = find_filename_part (master, filename);
+            if (cur)
+              g_free (filename);
+            else
+              cur = new TempPart (type=ENC_UU, filename);
+            state.uu_temp = cur;
+          }
+          else if (yenc_is_beginning_line (line_str))
+          {
+            found = true;
+            // flush the current entry
+            if (cur != NULL) {
+              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
+              apply_source_and_maybe_filter (cur, s);
 
-  void
-  mime_part_set_content (GMimePart *part, const char *str)
-  {
-    GMimeDataWrapper *content;
-    GMimeStream *stream;
+              if ( append_if_not_present (master, cur) )
+                append_if_not_present (appendme, cur);
+              cur = NULL;
+            }
+            sub_begin = linestart_pos;
 
-    stream = g_mime_stream_mem_new_with_buffer (str, strlen (str));
-    content = g_mime_data_wrapper_new_with_stream (stream, GMIME_CONTENT_ENCODING_DEFAULT);
-    g_object_unref (stream);
+            // start a new section (or, if filename matches, continue an existing one)
+            char * fname;
+            int line_len, attach_size, part;
+            yenc_parse_begin_line (line_str, &fname, &line_len, &attach_size, &part);
+            cur = find_filename_part (master, fname);
+            if (cur) {
+              g_free (fname);
+              g_mime_filter_yenc_set_state (GMIME_FILTER_YENC (cur->filter),
+                                            GMIME_YDECODE_STATE_INIT);
+            }
+            else
+            {
+              cur = new TempPart (type=ENC_YENC, fname);
+              cur->y_line_len = line_len;
+              cur->y_attach_size = attach_size;
+              cur->y_part = part;
+              yenc_looking_for_part_line = cur->y_part!=0;
+            }
+          }
+          else if (gpg_is_beginning_line (line_str))
+          {
 
-    g_mime_part_set_content_object (part, content);
-    g_object_unref (content);
-  }
+            found = true;
+            cur = new TempPart (type = ENC_GPG);
+            gpg_looking_for_line = true;
+            sub_begin = linestart_pos;
+          }
+          else if (state.uu_temp != NULL && is_uu_line(line_str, line_len) )
+          {
+            // continue an incomplete uu decode
+            found = true;
+            // flush the current entry
+            if (cur != NULL) {
+              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
+              apply_source_and_maybe_filter (cur, s);
 
-  GMimeMessage*
-  message_add_signed_part (const std::string& uid, const std::string& body_str, GMimeMessage* body, GPGEncErr& fail)
-  {
-    if (uid.empty()) return 0;
+              if ( append_if_not_present (master, cur) )
+                append_if_not_present (appendme, cur);
+              cur = NULL;
+            }
+            sub_begin = linestart_pos;
+            cur = state.uu_temp;
+            ++cur->valid_lines;
+            type = ENC_UU;
+          }
+          else if (cur == NULL)
+          {
+            sub_begin = linestart_pos;
 
-    GMimeMultipart *mp = g_mime_multipart_new_with_subtype ("mixed");
-    g_mime_multipart_set_boundary(mp, NULL);
-    g_mime_multipart_add(mp,g_mime_message_get_mime_part(body));
+            cur = new TempPart (type = ENC_PLAIN);
+          }
+          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);
+            apply_source_and_maybe_filter (cur, stream);
+            if( append_if_not_present (master, cur) )
+              append_if_not_present (appendme, cur);
 
-    gpgme_data_t gpg_buf, gpg_out_buf;
-    gpgme_key_t mykey(0), key;
+            cur = NULL;
+            type = ENC_PLAIN;
+            state.uu_temp = NULL;
+          }
+          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);
+                apply_source_and_maybe_filter (cur, stream);
+            }
 
-    StringView v(body_str);
-    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
-    gpgme_data_new (&gpg_out_buf);
-  //  gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
-  //  gpgme_data_set_file_name(gpg_out_buf, "signature.asc");
+            sub_begin = -1;
+          }
+          else if (sub_begin == -1)
+          {
+            /* looks like they decided to start using uu lines again. */
+            ++cur->valid_lines;
+            sub_begin = linestart_pos;
+          }
+          else
+          {
+            ++cur->valid_lines;
+          }
+          break;
+        }
+        case ENC_YENC:
+        {
+          if (yenc_is_ending_line (line_str))
+          {
+            GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
+            apply_source_and_maybe_filter (cur, stream);
+            yenc_parse_end_line (line_str, &cur->y_size, NULL, &cur->y_pcrc, &cur->y_crc);
+            if( append_if_not_present (master, cur) )
+              append_if_not_present (appendme, cur);
 
-    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
-    while (!fail.err)
-    {
-      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
-      if (fail.err) break;
-      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
-    }
-    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return 0; }
+            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;
+            ++cur->valid_lines;
+          }
+          else
+          {
+            ++cur->valid_lines;
+          }
+          break;
+        }
+#ifdef HAVE_GPGME
+        case ENC_GPG:
+        {
+          if (gpg_is_ending_line(line_str))
+          {
+            GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
+            GMimeStream * dec = gpg_decrypt_and_verify(state.signer_info, state.gpgerr, stream);
+            if (state.gpgerr.verify_ok)
+              state.gpg_verified = "valid";
+            else if (!state.gpgerr.verify_ok && !state.gpgerr.no_sigs)
+              state.gpg_verified = "invalid";
 
-    gpgme_signers_clear(gpg_ctx);
-    gpgme_signers_add(gpg_ctx, mykey);
+            gpg_looking_for_line = false;
+            if (state.gpgerr.dec_ok)
+            {
+              apply_source_and_maybe_filter (cur, dec);
+              if( append_if_not_present (master, cur) )
+                append_if_not_present (appendme, cur);
+            }
+            cur = NULL;
+            type = ENC_PLAIN;
+            sub_begin = -1;
+          }
+          else if (gpg_looking_for_line)
+          {
+            ++cur->valid_lines;
+          }
+          break;
+        }
+#endif
+      }
+    }
 
-    fail.err      = gpgme_op_sign (gpg_ctx, gpg_buf, gpg_out_buf, GPGME_SIG_MODE_DETACH);
-    fail.sign_res = gpgme_op_sign_result (gpg_ctx);
-    if (fail.err) return 0;
+    /* close last entry */
+    if (cur != NULL)
+    {
+      if (sub_begin >= 0)
+      {
+        GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
+        apply_source_and_maybe_filter (cur, stream);
+      }
 
-    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
-    ssize_t ret;
-    std::stringstream ret_str;
-    char buffer[4096]={0};
+      /* just in case someone started with "yenc" or "begin 644 asf" in a text message to fuck with unwary newsreaders */
+      if (cur->valid_lines < 10u)
+        cur->type = ENC_PLAIN;
 
-    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
-    {
-      ret_str << buffer;
+      if( append_if_not_present (master, cur) )
+        append_if_not_present (appendme, cur);
+      cur = NULL;
+      type = ENC_PLAIN;
     }
 
-    GMimePart *sig = g_mime_part_new_with_type("multipart", "signed");
-    g_mime_object_set_content_type_parameter(GMIME_OBJECT(sig),"protocol","pgp-signature");
-    mime_part_set_content (sig, ret_str.str().c_str());
+    g_byte_array_free (line, TRUE);
+    return found;
+  }
 
-    gpgme_data_release(gpg_buf);
-    gpgme_data_release(gpg_out_buf);
+}
 
-    g_mime_multipart_add (GMIME_MULTIPART (mp), GMIME_OBJECT (sig));
+void
+mime :: 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" },
+		{ ".flac",  "audio",        "ogg" },
+		{ ".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" },
+		{ ".oga",   "audio",        "x-vorbis" },
+		{ ".ogg",   "audio",        "ogg" },
+		{ ".ogv",   "video",        "ogg" },
+		{ ".ogx",   "application",  "ogg" },
+		{ ".png",   "image",        "png" },
+		{ ".qt",    "video",        "quicktime" },
+		{ ".rar",   "application",  "x-rar" },
+		{ ".rv",    "video",        "vnd.rn-realvideo" },
+		{ ".scr",   "application",  "octet-stream" },
+		{ ".spx",   "audio",        "ogg" },
+		{ ".svg",   "image",        "svg+xml" },
+		{ ".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;
 
-    g_mime_message_set_mime_part(body,GMIME_OBJECT(mp));
-    g_object_unref(mp);
+	/* zero out the return values */
+	pan_return_if_fail (setme_type!=NULL);
+	pan_return_if_fail (setme_subtype!=NULL);
+	*setme_type = *setme_subtype = NULL;
 
-    fail.err = GPG_ERR_NO_ERROR;
+	/* sanity clause */
+	pan_return_if_fail (is_nonempty_string(filename));
 
-    return body;
-  }
+       	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;
+			}
+		}
+	}
 
-  std::string
-  gpg_encrypt(const std::string& uid, const std::string& body, GPGEncErr& fail)
-  {
-    fail.err = GPG_ERR_GENERAL;
+	if (*setme_type == NULL) {
+		*setme_type = "application";
+		*setme_subtype = "octet-stream";
+	}
+}
 
-    if (uid.empty()) return "";
+namespace
+{
+  void
+  ptr_array_insert (GPtrArray *array, guint index, gpointer object)
+  {
+    unsigned char *dest, *src;
+    guint n;
 
-    gpgme_data_t gpg_buf, gpg_out_buf;
-    gpgme_key_t mykey(0), key;
+    g_ptr_array_set_size (array, array->len + 1);
 
-    StringView v(body);
-    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
-    gpgme_data_new (&gpg_out_buf);
-    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
+    if (index != array->len) {
+      /* need to move items down */
+      dest = ((unsigned char *) array->pdata) + (sizeof (void *) * (index + 1));
+      src = ((unsigned char *) array->pdata) + (sizeof (void *) * index);
+      n = array->len - index - 1;
 
-    /* find key to uid */
-    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
-    while (!fail.err)
-    {
-      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
-      if (fail.err) break;
-      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
+      g_memmove (dest, src, (sizeof (void *) * n));
     }
-    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return std::string(""); }
 
-    gpgme_key_t enc_keys[] = { mykey, NULL};
+    array->pdata[index] = object;
+  }
 
-    fail.err     = gpgme_op_encrypt (gpg_ctx, enc_keys, GPGME_ENCRYPT_PREPARE, gpg_buf, gpg_out_buf);
-    fail.enc_res = gpgme_op_encrypt_result (gpg_ctx);
-    if (fail.err) return std::string("");
+  void handle_uu_and_yenc_in_text_plain_cb (GMimeObject *parent, GMimeObject *part, gpointer data)
+  {
 
-    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
-    ssize_t ret;
-    std::stringstream ret_str;
-    char buffer[4096]={0};
+    if (!part)
+      return;
 
-    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
-    {
-      ret_str << buffer;
-    }
-
-    gpgme_data_release(gpg_buf);
-    gpgme_data_release(gpg_out_buf);
-
-    fail.err = GPG_ERR_NO_ERROR;
-    return ret_str.str();
-  }
-
-  std::string
-  gpg_encrypt_and_sign(const std::string& uid, const std::string& body, GPGEncErr& fail)
-  {
-    fail.err = GPG_ERR_GENERAL;
+    // we assume that inlined yenc and uu are only in text/plain blocks
+    GMimeContentType * content_type = g_mime_object_get_content_type (part);
+    if (!g_mime_content_type_is_type (content_type, "text", "plain"))
+      return;
 
-    if (uid.empty()) return "";
+    // get this part's content
+    GMimeDataWrapper * content = g_mime_part_get_content_object (GMIME_PART (part));
+    if (!content)
+      return;
 
-    gpgme_data_t gpg_buf, gpg_out_buf;
-    gpgme_key_t mykey(0), key;
+    // wrap a buffer stream around it for faster reading -- it could be a file stream
+    GMimeStream * stream = g_mime_data_wrapper_get_stream (content);
+    g_mime_stream_reset (stream);
+    GMimeStream * istream = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_BLOCK_READ);
 
-    StringView v(body);
-    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
-    gpgme_data_new (&gpg_out_buf);
-    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
+    // break it into separate parts for text, uu, and yenc pieces.
+    sep_state &state(*(sep_state*)data);
+    temp_parts_t &parts(state.current_list);
+    bool split = separate_encoded_parts (istream, state);
+    g_mime_stream_reset (istream);
 
-    /* find key to uid */
-    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
-    while (!fail.err)
+    // split?
+    if(split)
     {
-      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
-      if (fail.err) break;
-      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
-    }
-    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return std::string(""); }
-
-    gpgme_signers_clear(gpg_ctx);
-    gpgme_signers_add(gpg_ctx, mykey);
-
-    gpgme_key_t enc_keys[] = { mykey, NULL};
+      //this part was completely folded into a previous part
+      //so delete it
+      if(parts.size()==0) {
+        GMimeMultipart *mp = GMIME_MULTIPART (parent);
+        int index = g_mime_multipart_index_of (mp,part);
+        if(index>0)
+          g_mime_multipart_remove_at (mp,index);
+        g_object_unref(part);
+      }
+      else
+      {
+        GMimeMultipart * multipart = g_mime_multipart_new_with_subtype ("mixed");
 
-    fail.err     = gpgme_op_encrypt_sign (gpg_ctx, enc_keys, GPGME_ENCRYPT_PREPARE, gpg_buf, gpg_out_buf);
-    fail.enc_res = gpgme_op_encrypt_result (gpg_ctx);
-    fail.sign_res= gpgme_op_sign_result (gpg_ctx);
-    if (fail.err) return std::string("");
+        const TempPart *tmp_part;
+        const char *filename;
+        GMimePart *subpart;
+        GMimeStream *subpart_stream;
+        foreach (temp_parts_t, parts, it)
+        {
+          // reset these for each part
+          const char * type = "text";
+          const char * subtype = "plain";
 
-    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
-    ssize_t ret;
-    std::stringstream ret_str;
-    char buffer[4096]={0};
+          tmp_part = *it;
+          filename = tmp_part->filename;
 
-    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
-    {
-      ret_str << buffer;
-    }
+          if (filename && *filename)
+            mime::guess_part_type_from_filename (filename, &type, &subtype);
 
-    gpgme_data_release(gpg_buf);
-    gpgme_data_release(gpg_out_buf);
+          subpart = g_mime_part_new_with_type (type, subtype);
+          if (filename && *filename)
+            g_mime_part_set_filename (subpart, filename);
 
-    fail.err = GPG_ERR_NO_ERROR;
-    return ret_str.str();
-  }
-#endif
+          subpart_stream = tmp_part->stream;
+          content = g_mime_data_wrapper_new_with_stream (subpart_stream, GMIME_CONTENT_ENCODING_DEFAULT);
+          g_mime_part_set_content_object (subpart, content);
+          g_mime_multipart_add (GMIME_MULTIPART (multipart), GMIME_OBJECT (subpart));
 
-  void apply_source_and_maybe_filter (TempPart * part, GMimeStream * s)
-  {
+          g_object_unref (content);
+          g_object_unref (subpart);
+        }
 
-    if (!part->stream) {
-      part->stream = g_mime_stream_mem_new ();
-      if (part->type != ENC_PLAIN && part->type != ENC_GPG) {
-        part->filter_stream = g_mime_stream_filter_new (part->stream);
-        switch (part->type)
+        // replace the old part with the new multipart
+        GMimeObject *newpart = GMIME_OBJECT(multipart);
+        if(parts.size()==1)
         {
-          case ENC_UU:
-            part->filter = g_mime_filter_basic_new (GMIME_CONTENT_ENCODING_UUENCODE, FALSE);
-            break;
-
-          case ENC_YENC:
-            part->filter = g_mime_filter_yenc_new (FALSE);
-            break;
+          //only one part so no need for multipart
+          newpart = g_mime_multipart_remove_at(multipart,0);
+          g_object_unref(multipart);
         }
+        if(GMIME_IS_MULTIPART(parent))
+        {
+          GMimeMultipart *mp = GMIME_MULTIPART (parent);
+          int index = g_mime_multipart_index_of (mp, part);
+          g_mime_multipart_remove_at (mp, index);
+          g_object_unref (part);
 
-        g_mime_stream_filter_add (GMIME_STREAM_FILTER(part->filter_stream),
-                                  part->filter);
+          //workaround gmime insert bug
+          //g_mime_multipart_insert (mp,index,newpart);
+          {
+            ptr_array_insert(mp->children, index, newpart);
+            g_object_ref(newpart);
+          }
+        }
+        else if(GMIME_IS_MESSAGE(parent))
+        {
+          g_mime_message_set_mime_part((GMimeMessage*)parent, newpart);
+        }
+        g_object_unref(newpart);
       }
     }
 
-    g_mime_stream_write_to_stream (s, (part->type == ENC_PLAIN || part->type == ENC_GPG) ?
-				   part->stream : part->filter_stream);
-    g_object_unref (s);
+    parts.clear();
+    g_object_unref (istream);
   }
+}
 
-  struct sep_state
-  {
-    temp_parts_t master_list;
-    temp_parts_t current_list;
-    TempPart *uu_temp;
-    std::string gpg_verified;
-#ifdef HAVE_GPGME
-    GPGDecErr gpgerr;
-    GPGSignersInfo signer_info;
-#endif
-    sep_state():uu_temp(NULL), gpg_verified("") {};
+namespace{
+  struct temp_p {
+    GMimeObject *parent,*part;
+
+    temp_p(GMimeObject *p, GMimeObject *par):parent(p),part(par) {};
   };
+  typedef std::vector<temp_p> temp_p_t;
 
-  bool
-  separate_encoded_parts (GMimeStream  * istream, sep_state &state)
+  void find_text_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
   {
-    temp_parts_t& master(state.master_list);
-    temp_parts_t& appendme(state.current_list);
-    TempPart * cur = NULL;
-    EncType type = ENC_PLAIN;
-    GByteArray * line;
-    gboolean yenc_looking_for_part_line = FALSE;
-    gboolean gpg_looking_for_line = FALSE;
-    gboolean gpg_is_signed = FALSE;
-    gboolean gpg_signed_end_found = FALSE;
-    gint64 linestart_pos = 0;
-    gint64 sub_begin = 0;
-    gint64 signed_msg_start = 0;
-    gint64 signed_msg_end = 0;
-    guint line_len;
-    bool found = false;
-
-    /* sanity clause */
-    pan_return_val_if_fail (istream!=NULL,false);
-
-    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;
-      }
-
-      if (gpg_is_signed_begin_line(line_str))
-      {
-        gpg_is_signed = true;
-        signed_msg_start = linestart_pos;
-      }
-      if (gpg_is_signed_end_line(line_str))
-      {
-        gboolean gpg_signed_end_found = true;
-        signed_msg_end = linestart_pos+line_len;
-      }
-
-      switch (type)
-      {
-        case ENC_PLAIN:
-        {
-          const StringView line_pstr (line_str, line_len);
-
-          if (uu_is_beginning_line (line_pstr))
-          {
-            gulong mode = 0ul;
-            char * filename = NULL;
-
-            found=true;
-            // flush the current entry
-            if (cur != NULL) {
-              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
-              apply_source_and_maybe_filter (cur, s);
-
-              if ( append_if_not_present (master, cur) )
-                append_if_not_present (appendme, cur);
-              cur = NULL;
-            }
+    if(!GMIME_IS_PART(part))
+      return;
 
-            // start a new section (or, if filename matches, continue an existing one)
-            sub_begin = linestart_pos;
-            uu_parse_begin_line (line_pstr, &filename, &mode);
-            cur = find_filename_part (master, filename);
-            if (cur)
-              g_free (filename);
-            else
-              cur = new TempPart (type=ENC_UU, filename);
-            state.uu_temp = cur;
-          }
-          else if (yenc_is_beginning_line (line_str))
-          {
-            found = true;
-            // flush the current entry
-            if (cur != NULL) {
-              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
-              apply_source_and_maybe_filter (cur, s);
+    temp_p_t *v( (temp_p_t *) data);
+    // we assume that inlined yenc and uu are only in text/plain blocks
+    GMimeContentType * content_type = g_mime_object_get_content_type (part);
+    if (!g_mime_content_type_is_type (content_type, "text", "plain"))
+      return;
+    v->push_back(temp_p(parent,part) );
+  }
 
-              if ( append_if_not_present (master, cur) )
-                append_if_not_present (appendme, cur);
-              cur = NULL;
-            }
-            sub_begin = linestart_pos;
+  void find_gpg_parts_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
+  {
+    if(!GMIME_IS_PART(part))
+      return;
 
-            // start a new section (or, if filename matches, continue an existing one)
-            char * fname;
-            int line_len, attach_size, part;
-            yenc_parse_begin_line (line_str, &fname, &line_len, &attach_size, &part);
-            cur = find_filename_part (master, fname);
-            if (cur) {
-              g_free (fname);
-              g_mime_filter_yenc_set_state (GMIME_FILTER_YENC (cur->filter),
-                                            GMIME_YDECODE_STATE_INIT);
-            }
-            else
-            {
-              cur = new TempPart (type=ENC_YENC, fname);
-              cur->y_line_len = line_len;
-              cur->y_attach_size = attach_size;
-              cur->y_part = part;
-              yenc_looking_for_part_line = cur->y_part!=0;
-            }
-          }
-          else if (gpg_is_beginning_line (line_str))
-          {
+    temp_p_t *v( (temp_p_t *) data);
 
-            found = true;
-            cur = new TempPart (type = ENC_GPG);
-            gpg_looking_for_line = true;
-            sub_begin = linestart_pos;
-          }
-          else if (state.uu_temp != NULL && is_uu_line(line_str, line_len) )
-          {
-            // continue an incomplete uu decode
-            found = true;
-            // flush the current entry
-            if (cur != NULL) {
-              GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
-              apply_source_and_maybe_filter (cur, s);
+    GMimeContentType * content_type = g_mime_object_get_content_type (part);
+    if (!(g_mime_content_type_is_type (content_type, "multipart", "encrypted") ||
+          g_mime_content_type_is_type (content_type, "text", "plain")))
+      return;
+    v->push_back(temp_p(parent,part) );
+  }
 
-              if ( append_if_not_present (master, cur) )
-                append_if_not_present (appendme, cur);
-              cur = NULL;
-            }
-            sub_begin = linestart_pos;
-            cur = state.uu_temp;
-            ++cur->valid_lines;
-            type = ENC_UU;
-          }
-          else if (cur == NULL)
-          {
-            sub_begin = linestart_pos;
+}
 
-            cur = new TempPart (type = ENC_PLAIN);
-          }
-          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);
-            apply_source_and_maybe_filter (cur, stream);
-            if( append_if_not_present (master, cur) )
-              append_if_not_present (appendme, cur);
+/***
+****
+***/
 
-            cur = NULL;
-            type = ENC_PLAIN;
-            state.uu_temp = NULL;
-          }
-          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);
-                apply_source_and_maybe_filter (cur, stream);
-            }
+#ifdef HAVE_GPGME
+GMimeMessage*
+mime :: construct_message (GMimeStream    ** istreams,
+                           int             qty,
+                           GPGSignersInfo & signer_info,
+                           GPGDecErr      & gpgerr)
+#else
+GMimeMessage*
+mime :: construct_message (GMimeStream    ** istreams,
+                           int             qty)
+#endif
+{
+  const char * message_id = "Foo <bar mum>";
+  GMimeMessage * retval = 0;
 
-            sub_begin = -1;
-          }
-          else if (sub_begin == -1)
-          {
-            /* looks like they decided to start using uu lines again. */
-            ++cur->valid_lines;
-            sub_begin = linestart_pos;
-          }
-          else
-          {
-            ++cur->valid_lines;
-          }
-          break;
-        }
-        case ENC_YENC:
-        {
-          if (yenc_is_ending_line (line_str))
-          {
-            GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
-            apply_source_and_maybe_filter (cur, stream);
-            yenc_parse_end_line (line_str, &cur->y_size, NULL, &cur->y_pcrc, &cur->y_crc);
-            if( append_if_not_present (master, cur) )
-              append_if_not_present (appendme, cur);
+  // sanity clause
+  pan_return_val_if_fail (is_nonempty_string(message_id), NULL);
+  pan_return_val_if_fail (istreams!=NULL, NULL);
+  pan_return_val_if_fail (qty>=1, NULL);
+  for (int i=0; i<qty; ++i)
+    pan_return_val_if_fail (GMIME_IS_STREAM(istreams[i]), NULL);
 
-            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;
-            ++cur->valid_lines;
-          }
-          else
-          {
-            ++cur->valid_lines;
-          }
-          break;
-        }
-#ifdef HAVE_GPGME
-        case ENC_GPG:
-        {
-          if (gpg_is_ending_line(line_str))
-          {
-            GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
-            GMimeStream * dec = gpg_decrypt_and_verify(state.signer_info, state.gpgerr, stream);
-            if (state.gpgerr.verify_ok)
-              state.gpg_verified = "valid";
-            else if (!state.gpgerr.verify_ok && !state.gpgerr.no_sigs)
-              state.gpg_verified = "invalid";
+  // build the GMimeMessages
+  GMimeParser * parser = g_mime_parser_new ();
+  GMimeMessage ** messages = g_new (GMimeMessage*, qty);
+  for (int 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);
 
-            gpg_looking_for_line = false;
-            if (state.gpgerr.dec_ok)
-            {
-              apply_source_and_maybe_filter (cur, dec);
-              if( append_if_not_present (master, cur) )
-                append_if_not_present (appendme, cur);
-            }
-            cur = NULL;
-            type = ENC_PLAIN;
-            sub_begin = -1;
-          }
-          else if (gpg_looking_for_line)
-          {
-            ++cur->valid_lines;
-          }
-          break;
-        }
-#endif
-      }
-    }
+  if (qty > 1) // fold multiparts together
+  {
+    GMimeMultipart * mp = g_mime_multipart_new ();
 
-    /* close last entry */
-    if (cur != NULL)
+    for (int i=0; i<qty; ++i)
     {
-      if (sub_begin >= 0)
-      {
-        GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
-        apply_source_and_maybe_filter (cur, stream);
-      }
+      g_mime_multipart_add(mp,g_mime_message_get_mime_part(messages[i]) );
+    }
 
-      /* just in case someone started with "yenc" or "begin 644 asf" in a text message to fuck with unwary newsreaders */
-      if (cur->valid_lines < 10u)
-        cur->type = ENC_PLAIN;
+    g_mime_message_set_mime_part(messages[0],GMIME_OBJECT(mp));
+    g_object_unref(mp);
+  }
 
-      if( append_if_not_present (master, cur) )
-        append_if_not_present (appendme, cur);
-      cur = NULL;
-      type = ENC_PLAIN;
-    }
+  retval = messages[0];
+  for (int i=1; i<qty; ++i)
+    g_object_unref (messages[i]);
 
-    g_byte_array_free (line, TRUE);
-    return found;
+  // pick out yenc and uuenc data in text/plain messages
+  temp_p_t partslist;
+  sep_state state;
+  if (retval != NULL)
+    g_mime_message_foreach(retval, find_text_cb, &partslist);
+  foreach(temp_p_t, partslist, it)
+  {
+    temp_p &data(*it);
+    handle_uu_and_yenc_in_text_plain_cb(data.parent, data.part, &state);
+    /* set gpg signature verify success/fail flag */
+#ifdef HAVE_GPGME
+    g_mime_object_set_header(GMIME_OBJECT(data.parent), "X-GPG-Signed", state.gpg_verified.c_str());
+    gpgerr = state.gpgerr;
+    signer_info = state.signer_info;
+#endif
+  }
+
+  // cleanup
+  foreach (temp_parts_t, state.master_list, it)
+  {
+    delete *it;
   }
+  g_free (messages);
 
+  return retval;
 }
 
-void
-mime :: 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" },
-		{ ".flac",  "audio",        "ogg" },
-		{ ".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" },
-		{ ".oga",   "audio",        "x-vorbis" },
-		{ ".ogg",   "audio",        "ogg" },
-		{ ".ogv",   "video",        "ogg" },
-		{ ".ogx",   "application",  "ogg" },
-		{ ".png",   "image",        "png" },
-		{ ".qt",    "video",        "quicktime" },
-		{ ".rar",   "application",  "x-rar" },
-		{ ".rv",    "video",        "vnd.rn-realvideo" },
-		{ ".scr",   "application",  "octet-stream" },
-		{ ".spx",   "audio",        "ogg" },
-		{ ".svg",   "image",        "svg+xml" },
-		{ ".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 */
-	pan_return_if_fail (setme_type!=NULL);
-	pan_return_if_fail (setme_subtype!=NULL);
-	*setme_type = *setme_subtype = NULL;
+/**
+ * Retrieve the charset from a mime message
+ */
 
-	/* sanity clause */
-	pan_return_if_fail (is_nonempty_string(filename));
+#if 0 // unused?
+static void
+get_charset_partfunc (GMimeObject * obj, gpointer charset_gpointer)
+{
+	GMimePart * part;
+	const GMimeContentType * type;
+	const char ** charset;
 
-       	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 (!GMIME_IS_PART (obj))
+		return;
 
-	if (*setme_type == NULL) {
-		*setme_type = "application";
-		*setme_subtype = "octet-stream";
-	}
+	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");
 }
 
-namespace
+const char *
+mime :: get_charset (GMimeMessage * message)
 {
-  void
-  ptr_array_insert (GPtrArray *array, guint index, gpointer object)
-  {
-    unsigned char *dest, *src;
-    guint n;
+	const char * retval = NULL;
+	pan_return_val_if_fail (message!=NULL, NULL);
 
-    g_ptr_array_set_size (array, array->len + 1);
+	g_mime_message_foreach_part (message, get_charset_partfunc, &retval);
 
-    if (index != array->len) {
-      /* need to move items down */
-      dest = ((unsigned char *) array->pdata) + (sizeof (void *) * (index + 1));
-      src = ((unsigned char *) array->pdata) + (sizeof (void *) * index);
-      n = array->len - index - 1;
+	return retval;
+}
+#endif
 
-      g_memmove (dest, src, (sizeof (void *) * n));
-    }
+/**
+***
+**/
 
-    array->pdata[index] = object;
-  }
+namespace
+{
+   enum StripFlags
+   {
+      STRIP_CASE                    = (1<<0),
+      STRIP_MULTIPART_NUMERATOR     = (1<<1),
+      STRIP_MULTIPART               = (1<<2)
+   };
 
-  void handle_uu_and_yenc_in_text_plain_cb (GMimeObject *parent, GMimeObject *part, gpointer data)
-  {
+   /**
+    * Normalizing a subject header involves:
+    *
+    * 1. tearing out the numerator from multipart indicators
+    *    (i.e., remove "21" from (21/42))
+    *    for threading.
+    * 2. convert it to lowercase so that, when sorting, we can
+    *    have case-insensitive sorting without having to use
+    *    a slower case-insensitive compare function.
+    * 3. strip out all the leading noise that breaks sorting.
+    *
+    * When we're threading articles, it's much faster to
+    * normalize the * subjects at the outset instead of
+    * normalizing them for each comparison.
+    */
+   void
+   normalize_subject (const StringView   & subject,
+                      StripFlags           strip,
+                      std::string        & setme)
+   {
+#if 0
+      static bool _keep_chars[UCHAR_MAX+1];
+      static bool _inited (false);
+      if (!_inited) {
+         _inited = true;
+         for (int i=0; i<=UCHAR_MAX; ++i) {
+            const unsigned char uch ((unsigned char)i);
+            _keep_chars[i] = isalnum(uch) || isdigit(uch) || isspace(uch);
+         }
+      }
+#endif
+      setme.reserve (subject.len + 1);
+      const unsigned char * in ((const unsigned char*) subject.begin());
+      const unsigned char * end ((const unsigned char*) subject.end());
+      const bool strip_case (strip & STRIP_CASE);
+      const bool strip_numerator (strip & STRIP_MULTIPART_NUMERATOR);
+      const bool strip_multipart (strip & STRIP_MULTIPART);
 
-    if (!part)
-      return;
+      // skip the leading noise
+      while (in!=end && isspace(*in))
+         ++in;
 
-    // we assume that inlined yenc and uu are only in text/plain blocks
-    GMimeContentType * content_type = g_mime_object_get_content_type (part);
-    if (!g_mime_content_type_is_type (content_type, "text", "plain"))
-      return;
+      while (in!=end)
+      {
+         // strip numerator out of multipart information
+         if ((strip_multipart||strip_numerator)
+             && (*in=='('||*in=='[')
+             && isdigit(in[1]))
+         {
+            const unsigned char * start (++in);
 
-    // get this part's content
-    GMimeDataWrapper * content = g_mime_part_get_content_object (GMIME_PART (part));
-    if (!content)
-      return;
+            if (strip_multipart || strip_numerator)
+            {
+               while (in!=end && isdigit(*in))
+                  ++in;
 
-    // wrap a buffer stream around it for faster reading -- it could be a file stream
-    GMimeStream * stream = g_mime_data_wrapper_get_stream (content);
-    g_mime_stream_reset (stream);
-    GMimeStream * istream = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_BLOCK_READ);
+               if (*in!='/' && *in!='|') // oops, not multipart information
+                  in = start;
 
-    // break it into separate parts for text, uu, and yenc pieces.
-    sep_state &state(*(sep_state*)data);
-    temp_parts_t &parts(state.current_list);
-    bool split = separate_encoded_parts (istream, state);
-    g_mime_stream_reset (istream);
+               else if (strip_multipart)
+               {
+                  for (++in; in!=end && *in!=']' && *in!=')'; ++in)
+                  {
+                     if (isalpha(*in)) { // oops, not multipart information
+                        in = ++start;
+                        break;
+                     }
+                  }
 
-    // split?
-    if(split)
-    {
-      //this part was completely folded into a previous part
-      //so delete it
-      if(parts.size()==0) {
-        GMimeMultipart *mp = GMIME_MULTIPART (parent);
-        int index = g_mime_multipart_index_of (mp,part);
-        if(index>0)
-          g_mime_multipart_remove_at (mp,index);
-        g_object_unref(part);
+                  if (in!=end && (*in==']' || *in==')'))
+                    ++in;
+               }
+            }
+            continue;
+         }
+
+#if 0
+         // strip out junk that breaks sorting
+         if (_keep_chars[*in])
+#endif
+            setme += (char) (strip_case ? tolower(*in) : *in);
+
+         ++in;
       }
-      else
-      {
-        GMimeMultipart * multipart = g_mime_multipart_new_with_subtype ("mixed");
+   }
+}
 
-        const TempPart *tmp_part;
-        const char *filename;
-        GMimePart *subpart;
-        GMimeStream *subpart_stream;
-        foreach (temp_parts_t, parts, it)
-        {
-          // reset these for each part
-          const char * type = "text";
-          const char * subtype = "plain";
+void
+mime :: remove_multipart_from_subject (const StringView    & subject,
+                                       std::string         & setme)
+{
+  normalize_subject (subject, STRIP_MULTIPART, setme);
+}
 
-          tmp_part = *it;
-          filename = tmp_part->filename;
+void
+mime :: remove_multipart_part_from_subject (const StringView    & subject,
+                                            std::string         & setme)
+{
+  normalize_subject (subject, STRIP_MULTIPART_NUMERATOR, setme);
+}
 
-          if (filename && *filename)
-            mime::guess_part_type_from_filename (filename, &type, &subtype);
+namespace
+{
+  GMimeObject *
+  handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html);
 
-          subpart = g_mime_part_new_with_type (type, subtype);
-          if (filename && *filename)
-            g_mime_part_set_filename (subpart, filename);
+  GMimeObject *
+  handle_multipart_alternative (GMimeMultipart *multipart, gboolean *is_html)
+  {
+    GMimeObject *mime_part, *text_part = NULL;
+    GMimeContentType *type;
+    int count = g_mime_multipart_get_count (multipart);
 
-          subpart_stream = tmp_part->stream;
-          content = g_mime_data_wrapper_new_with_stream (subpart_stream, GMIME_CONTENT_ENCODING_DEFAULT);
-          g_mime_part_set_content_object (subpart, content);
-          g_mime_multipart_add (GMIME_MULTIPART (multipart), GMIME_OBJECT (subpart));
+    for (int i = 0; i < count; ++i) {
+      mime_part = g_mime_multipart_get_part (multipart, i);
 
-          g_object_unref (content);
-          g_object_unref (subpart);
+      type = g_mime_object_get_content_type (mime_part);
+      if (g_mime_content_type_is_type (type, "text", "*")) {
+        if (!text_part || !g_ascii_strcasecmp (type->subtype, "plain")) {
+          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
+          text_part = mime_part;
         }
+      }
+    }
 
-        // replace the old part with the new multipart
-        GMimeObject *newpart = GMIME_OBJECT(multipart);
-        if(parts.size()==1)
-        {
-          //only one part so no need for multipart
-          newpart = g_mime_multipart_remove_at(multipart,0);
-          g_object_unref(multipart);
-        }
-        if(GMIME_IS_MULTIPART(parent))
-        {
-          GMimeMultipart *mp = GMIME_MULTIPART (parent);
-          int index = g_mime_multipart_index_of (mp, part);
-          g_mime_multipart_remove_at (mp, index);
-          g_object_unref (part);
+    return text_part;
+  }
 
-          //workaround gmime insert bug
-          //g_mime_multipart_insert (mp,index,newpart);
-          {
-            ptr_array_insert(mp->children, index, newpart);
-            g_object_ref(newpart);
-          }
+  GMimeObject *
+  handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html)
+  {
+    GMimeObject *mime_part, *text_part = NULL;
+    GMimeContentType *type, *first_type = NULL;
+    int count = g_mime_multipart_get_count (multipart);
+
+    for (int i = 0; i < count; ++i) {
+      mime_part = g_mime_multipart_get_part (multipart, i);
+
+      type = g_mime_object_get_content_type (mime_part);
+      if (GMIME_IS_MULTIPART (mime_part)) {
+        multipart = GMIME_MULTIPART (mime_part);
+        if (g_mime_content_type_is_type (type, "multipart", "alternative")) {
+          mime_part = handle_multipart_alternative (multipart, is_html);
+          if (mime_part)
+            return mime_part;
+        } else {
+          mime_part = handle_multipart_mixed (multipart, is_html);
+          if (mime_part && !text_part)
+            text_part = mime_part;
         }
-        else if(GMIME_IS_MESSAGE(parent))
-        {
-          g_mime_message_set_mime_part((GMimeMessage*)parent, newpart);
+      } else if (g_mime_content_type_is_type (type, "text", "*")) {
+        if (!g_ascii_strcasecmp (type->subtype, "plain")) {
+          /* we got what we came for */
+          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
+          return mime_part;
+        }
+
+        /* if we haven't yet found a text part or if it is a type we can
+         * understand and it is the first of that type, save it */
+        if (!text_part || (!g_ascii_strcasecmp (type->subtype, "plain") && (first_type &&
+                           g_ascii_strcasecmp (type->subtype, first_type->subtype) != 0))) {
+          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
+          text_part = mime_part;
+          first_type = type;
         }
-        g_object_unref(newpart);
       }
     }
 
-    parts.clear();
-    g_object_unref (istream);
+    return text_part;
   }
+
 }
+#define NEEDS_DECODING(encoding) ((encoding == GMIME_CONTENT_ENCODING_BASE64) ||   \
+                                  (encoding == GMIME_CONTENT_ENCODING_UUENCODE) || \
+                                  (encoding == GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE))
 
-namespace{
-  struct temp_p {
-    GMimeObject *parent,*part;
+namespace
+{
+  char *
+  pan_g_mime_part_get_content (GMimePart *mime_part, size_t *len)
+  {
+    char *retval = NULL;
 
-    temp_p(GMimeObject *p, GMimeObject *par):parent(p),part(par) {};
-  };
-  typedef std::vector<temp_p> temp_p_t;
+    g_return_val_if_fail (GMIME_IS_PART (mime_part), NULL);
 
-  void find_text_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
-  {
-    if(!GMIME_IS_PART(part))
-      return;
+    if (!mime_part->content || !mime_part->content->stream) {
+//      g_warning ("no content set on this mime part");  // dbg
+      return NULL;
+    }
 
-    temp_p_t *v( (temp_p_t *) data);
-    // we assume that inlined yenc and uu are only in text/plain blocks
-    GMimeContentType * content_type = g_mime_object_get_content_type (part);
-    if (!g_mime_content_type_is_type (content_type, "text", "plain"))
-      return;
-    v->push_back(temp_p(parent,part) );
+    GMimeDataWrapper *wrapper = g_mime_part_get_content_object(mime_part);
+    GMimeStream *stream = g_mime_stream_mem_new();
+    g_mime_data_wrapper_write_to_stream (wrapper, stream);
+    GByteArray *bytes = g_mime_stream_mem_get_byte_array((GMimeStreamMem*)stream);
+    *len = bytes->len + 1;
+    if (bytes->len)
+    {
+      retval = (char*)g_malloc0(bytes->len + 1);
+      memcpy(retval, bytes->data, bytes->len);
+    }
+    g_object_unref(stream);
+
+    return retval;
   }
+}
 
-  void find_gpg_parts_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
-  {
-    if(!GMIME_IS_PART(part))
-      return;
+char *pan::pan_g_mime_message_get_body (GMimeMessage *message, gboolean *is_html)
+{
+  GMimeObject *mime_part = NULL;
+  GMimeContentType *type;
+  GMimeMultipart *multipart;
+  char *body = NULL;
+  size_t len = 0;
 
-    temp_p_t *v( (temp_p_t *) data);
+  g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
+//  g_return_val_if_fail (is_html != NULL, NULL);
 
-    GMimeContentType * content_type = g_mime_object_get_content_type (part);
-    if (!(g_mime_content_type_is_type (content_type, "multipart", "encrypted") ||
-          g_mime_content_type_is_type (content_type, "text", "plain")))
-      return;
-    v->push_back(temp_p(parent,part) );
+  type = g_mime_object_get_content_type (message->mime_part);
+  if (GMIME_IS_MULTIPART (message->mime_part)) {
+    /* let's see if we can find a body in the multipart */
+    multipart = GMIME_MULTIPART (message->mime_part);
+    if (g_mime_content_type_is_type (type, "multipart", "alternative"))
+      mime_part = handle_multipart_alternative (multipart, is_html);
+    else
+      mime_part = handle_multipart_mixed (multipart, is_html);
+  } else if (g_mime_content_type_is_type (type, "text", "*")) {
+    /* this *has* to be the message body */
+    if (g_mime_content_type_is_type (type, "text", "html"))
+      *is_html = TRUE;
+    else
+      *is_html = FALSE;
+    mime_part = message->mime_part;
   }
 
+  if (mime_part != NULL) {
+    body = pan_g_mime_part_get_content (GMIME_PART (mime_part), &len);
+  }
+  return body;
 }
 
-/***
-****
-***/
+void pan::pan_g_mime_message_add_recipients_from_string (GMimeMessage *message, GMimeRecipientType type, const char *string)
+{
+  InternetAddressList *addrlist;
+  if ((addrlist = internet_address_list_parse_string (string))) {
+    for (int i = 0; i < internet_address_list_length (addrlist); ++i) {
+      InternetAddress *ia = internet_address_list_get_address (addrlist, i);
+      if (INTERNET_ADDRESS_IS_MAILBOX(ia))
+        g_mime_message_add_recipient (message, type, internet_address_get_name(ia), internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(ia)));
+    }
+  }
+}
 
-#ifdef HAVE_GPGME
-GMimeMessage*
-mime :: construct_message (GMimeStream    ** istreams,
-                           int             qty,
-                           GPGSignersInfo & signer_info,
-                           GPGDecErr      & gpgerr)
-#else
-GMimeMessage*
-mime :: construct_message (GMimeStream    ** istreams,
-                           int             qty)
-#endif
+/**
+* Works around a GMime bug that uses `Message-Id' rather than `Message-ID'
+*/
+void pan::pan_g_mime_message_set_message_id (GMimeMessage *msg, const char *mid)
 {
-  const char * message_id = "Foo <bar mum>";
-  GMimeMessage * retval = 0;
+    g_mime_object_append_header ((GMimeObject *) msg, "Message-ID", mid);
+    char * bracketed = g_strdup_printf ("<%s>", mid);
+    g_mime_header_list_set (GMIME_OBJECT(msg)->headers, "Message-ID", bracketed);
+    g_free (bracketed);
+}
+
+namespace pan
+
+{
+  #ifdef HAVE_GPGME
+  GMimeStream* gpg_decrypt_and_verify (GPGSignersInfo& signer_info, GPGDecErr& info,
+                                       GMimeStream* s, int index, GMimeObject* parent)
+  {
+
+    GMimeStream* decrypted = g_mime_stream_mem_new ();
+
+    ssize_t stream_len = g_mime_stream_length(s);
+
+    char* streambuf = new char[stream_len];
+
+    g_mime_stream_read(s, streambuf, stream_len);
+    g_object_unref(s);
 
-  // sanity clause
-  pan_return_val_if_fail (is_nonempty_string(message_id), NULL);
-  pan_return_val_if_fail (istreams!=NULL, NULL);
-  pan_return_val_if_fail (qty>=1, NULL);
-  for (int i=0; i<qty; ++i)
-    pan_return_val_if_fail (GMIME_IS_STREAM(istreams[i]), NULL);
+    info.err = GPG_ERR_NO_ERROR;
 
-  // build the GMimeMessages
-  GMimeParser * parser = g_mime_parser_new ();
-  GMimeMessage ** messages = g_new (GMimeMessage*, qty);
-  for (int 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);
+    gpgme_data_t gpg_buf, gpg_out_buf;
+    gpgme_key_t key;
 
-  if (qty > 1) // fold multiparts together
-  {
-    GMimeMultipart * mp = g_mime_multipart_new ();
+    StringView v(streambuf);
+    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
 
-    for (int i=0; i<qty; ++i)
-    {
-      g_mime_multipart_add(mp,g_mime_message_get_mime_part(messages[i]) );
-    }
+    gpgme_strerror(gpgme_data_new (&gpg_out_buf));
 
-    g_mime_message_set_mime_part(messages[0],GMIME_OBJECT(mp));
-    g_object_unref(mp);
-  }
+    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_NONE);
 
-  retval = messages[0];
-  for (int i=1; i<qty; ++i)
-    g_object_unref (messages[i]);
+    info.err = gpgme_op_decrypt_verify (gpg_ctx, gpg_buf, gpg_out_buf);
 
-  // pick out yenc and uuenc data in text/plain messages
-  temp_p_t partslist;
-  sep_state state;
-  if (retval != NULL)
-    g_mime_message_foreach(retval, find_text_cb, &partslist);
-  foreach(temp_p_t, partslist, it)
-  {
-    temp_p &data(*it);
-    handle_uu_and_yenc_in_text_plain_cb(data.parent, data.part, &state);
-    /* set gpg signature verify success/fail flag */
-#ifdef HAVE_GPGME
-    g_mime_object_set_header(GMIME_OBJECT(data.parent), "X-GPG-Signed", state.gpg_verified.c_str());
-    gpgerr = state.gpgerr;
-    signer_info = state.signer_info;
-#endif
-  }
+    // no data to decrypt, check for signature validity anyway....
+    if (gpgme_err_code(info.err) == GPG_ERR_NO_DATA || gpgme_err_code(info.err) == GPG_ERR_NO_ERROR)
+      info.dec_ok = true;
+    else
+      return decrypted;
 
-  // cleanup
-  foreach (temp_parts_t, state.master_list, it)
-  {
-    delete *it;
-  }
-  g_free (messages);
+    info.dec_res  = gpgme_op_decrypt_result (gpg_ctx);
+    info.v_res    = gpgme_op_verify_result  (gpg_ctx);
 
-  return retval;
-}
+    // verify attached sigs, too. that means, no data to decrypt and no signatures found, yet
+    if (gpgme_err_code(info.err) == GPG_ERR_NO_DATA &&
+        gpgme_err_code(info.v_res->signatures->status) == GPG_ERR_BAD_SIGNATURE && parent)
+    {
+      int count(0); int no(0);
+      size_t len;
+      char* body(0);
+      count = g_mime_multipart_get_count (GMIME_MULTIPART(parent));
+      no = index;
+      gpgme_data_t body_data;
+
+      for (int i=0;i<count;++i)
+      {
 
-/***
-****
-***/
+        if (i == no) continue;
+        GMimeObject* part_of = g_mime_multipart_get_part (GMIME_MULTIPART(parent), i);
+        if (part_of)
+        {
+          body = pan_g_mime_part_get_content(GMIME_PART(part_of),&len);
+          if (body)
+          {
+            gpgme_data_new_from_mem (&body_data, body, len, 0);
+            info.err  = gpgme_op_verify (gpg_ctx, gpg_buf, body_data, 0);
+            info.v_res= gpgme_op_verify_result  (gpg_ctx);
+            gpgme_data_release(body_data);
+            g_free(body);
+            if (info.err == GPG_ERR_NO_ERROR &&
+                gpgme_err_code(info.v_res->signatures->status) == GPG_ERR_NO_ERROR) break;
+          }
+        }
+      }
+    }
 
-/**
- * Retrieve the charset from a mime message
- */
+    if (info.v_res->signatures)
+    {
+      info.no_sigs = false;
+      if (gpgme_err_code(info.v_res->signatures->status) == GPG_ERR_NO_ERROR)
+        info.verify_ok = true;
+    }
+    else
+      info.no_sigs = true;
 
-#if 0 // unused?
-static void
-get_charset_partfunc (GMimeObject * obj, gpointer charset_gpointer)
-{
-	GMimePart * part;
-	const GMimeContentType * type;
-	const char ** charset;
+    delete streambuf;
 
-	if (!GMIME_IS_PART (obj))
-		return;
+    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
 
-	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");
-}
+    ssize_t ret(0);
+    char buffer[4096]={0};
+    gint64 len(0);
 
-const char *
-mime :: get_charset (GMimeMessage * message)
-{
-	const char * retval = NULL;
-	pan_return_val_if_fail (message!=NULL, NULL);
+    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
+    {
+      len += g_mime_stream_write(decrypted, buffer, ret);
+    }
 
-	g_mime_message_foreach_part (message, get_charset_partfunc, &retval);
+    g_mime_stream_flush (decrypted);
+    g_mime_stream_reset(decrypted);
 
-	return retval;
-}
-#endif
+    gpgme_data_release(gpg_buf);
+    gpgme_data_release(gpg_out_buf);
 
-/**
-***
-**/
+    GPGSignersInfo si;
 
-namespace
-{
-   enum StripFlags
-   {
-      STRIP_CASE                    = (1<<0),
-      STRIP_MULTIPART_NUMERATOR     = (1<<1),
-      STRIP_MULTIPART               = (1<<2)
-   };
+    if (!info.no_sigs)
+    {
+      if (info.v_res->signatures->fpr)
+        gpgme_get_key (gpg_ctx, info.v_res->signatures->fpr, &key, 0);
+      if (key)
+        fill_signer_info(si, key);
+    }
 
-   /**
-    * Normalizing a subject header involves:
-    *
-    * 1. tearing out the numerator from multipart indicators
-    *    (i.e., remove "21" from (21/42))
-    *    for threading.
-    * 2. convert it to lowercase so that, when sorting, we can
-    *    have case-insensitive sorting without having to use
-    *    a slower case-insensitive compare function.
-    * 3. strip out all the leading noise that breaks sorting.
-    *
-    * When we're threading articles, it's much faster to
-    * normalize the * subjects at the outset instead of
-    * normalizing them for each comparison.
-    */
-   void
-   normalize_subject (const StringView   & subject,
-                      StripFlags           strip,
-                      std::string        & setme)
-   {
-#if 0
-      static bool _keep_chars[UCHAR_MAX+1];
-      static bool _inited (false);
-      if (!_inited) {
-         _inited = true;
-         for (int i=0; i<=UCHAR_MAX; ++i) {
-            const unsigned char uch ((unsigned char)i);
-            _keep_chars[i] = isalnum(uch) || isdigit(uch) || isspace(uch);
-         }
-      }
-#endif
-      setme.reserve (subject.len + 1);
-      const unsigned char * in ((const unsigned char*) subject.begin());
-      const unsigned char * end ((const unsigned char*) subject.end());
-      const bool strip_case (strip & STRIP_CASE);
-      const bool strip_numerator (strip & STRIP_MULTIPART_NUMERATOR);
-      const bool strip_multipart (strip & STRIP_MULTIPART);
+    signer_info = si;
+    info.err = GPG_ERR_NO_ERROR;
 
-      // skip the leading noise
-      while (in!=end && isspace(*in))
-         ++in;
+    return decrypted;
+  }
 
-      while (in!=end)
-      {
-         // strip numerator out of multipart information
-         if ((strip_multipart||strip_numerator)
-             && (*in=='('||*in=='[')
-             && isdigit(in[1]))
-         {
-            const unsigned char * start (++in);
+  void
+  mime_part_set_content (GMimePart *part, const char *str)
+  {
+    GMimeDataWrapper *content;
+    GMimeStream *stream;
 
-            if (strip_multipart || strip_numerator)
-            {
-               while (in!=end && isdigit(*in))
-                  ++in;
+    stream = g_mime_stream_mem_new_with_buffer (str, strlen (str));
+    content = g_mime_data_wrapper_new_with_stream (stream, GMIME_CONTENT_ENCODING_DEFAULT);
+    g_object_unref (stream);
 
-               if (*in!='/' && *in!='|') // oops, not multipart information
-                  in = start;
+    g_mime_part_set_content_object (part, content);
+    g_object_unref (content);
+  }
 
-               else if (strip_multipart)
-               {
-                  for (++in; in!=end && *in!=']' && *in!=')'; ++in)
-                  {
-                     if (isalpha(*in)) { // oops, not multipart information
-                        in = ++start;
-                        break;
-                     }
-                  }
+  GMimeMessage*
+  message_add_signed_part (const std::string& uid, const std::string& body_str, GMimeMessage* body, GPGEncErr& fail)
+  {
+    if (uid.empty()) return 0;
 
-                  if (in!=end && (*in==']' || *in==')'))
-                    ++in;
-               }
-            }
-            continue;
-         }
+    GMimeMultipart *mp = g_mime_multipart_new_with_subtype ("mixed");
+    g_mime_multipart_set_boundary(mp, NULL);
+    g_mime_multipart_add(mp,g_mime_message_get_mime_part(body));
+
+    gpgme_data_t gpg_buf, gpg_out_buf;
+    gpgme_key_t mykey(0), key;
+
+    StringView v(body_str);
+    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
+    gpgme_data_new (&gpg_out_buf);
+  //  gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
+  //  gpgme_data_set_file_name(gpg_out_buf, "signature.asc");
+
+    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
+    while (!fail.err)
+    {
+      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
+      if (fail.err) break;
+      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
+    }
+    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return 0; }
+
+    gpgme_signers_clear(gpg_ctx);
+    gpgme_signers_add(gpg_ctx, mykey);
 
-#if 0
-         // strip out junk that breaks sorting
-         if (_keep_chars[*in])
-#endif
-            setme += (char) (strip_case ? tolower(*in) : *in);
+    fail.err      = gpgme_op_sign (gpg_ctx, gpg_buf, gpg_out_buf, GPGME_SIG_MODE_DETACH);
+    fail.sign_res = gpgme_op_sign_result (gpg_ctx);
+    if (fail.err) return 0;
 
-         ++in;
-      }
-   }
-}
+    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
+    ssize_t ret;
+    std::stringstream ret_str;
+    char buffer[4096]={0};
 
-void
-mime :: remove_multipart_from_subject (const StringView    & subject,
-                                       std::string         & setme)
-{
-  normalize_subject (subject, STRIP_MULTIPART, setme);
-}
+    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
+    {
+      ret_str << buffer;
+    }
 
-void
-mime :: remove_multipart_part_from_subject (const StringView    & subject,
-                                            std::string         & setme)
-{
-  normalize_subject (subject, STRIP_MULTIPART_NUMERATOR, setme);
-}
+    GMimePart *sig = g_mime_part_new_with_type("multipart", "signed");
+    g_mime_object_set_content_type_parameter(GMIME_OBJECT(sig),"protocol","pgp-signature");
+    mime_part_set_content (sig, ret_str.str().c_str());
 
-namespace
-{
-  GMimeObject *
-  handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html);
+    gpgme_data_release(gpg_buf);
+    gpgme_data_release(gpg_out_buf);
 
-  GMimeObject *
-  handle_multipart_alternative (GMimeMultipart *multipart, gboolean *is_html)
-  {
-    GMimeObject *mime_part, *text_part = NULL;
-    GMimeContentType *type;
-    int count = g_mime_multipart_get_count (multipart);
+    g_mime_multipart_add (GMIME_MULTIPART (mp), GMIME_OBJECT (sig));
 
-    for (int i = 0; i < count; ++i) {
-      mime_part = g_mime_multipart_get_part (multipart, i);
+    g_mime_message_set_mime_part(body,GMIME_OBJECT(mp));
+    g_object_unref(mp);
 
-      type = g_mime_object_get_content_type (mime_part);
-      if (g_mime_content_type_is_type (type, "text", "*")) {
-        if (!text_part || !g_ascii_strcasecmp (type->subtype, "plain")) {
-          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
-          text_part = mime_part;
-        }
-      }
-    }
+    fail.err = GPG_ERR_NO_ERROR;
 
-    return text_part;
+    return body;
   }
 
-  GMimeObject *
-  handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html)
+  std::string
+  gpg_encrypt(const std::string& uid, const std::string& body, GPGEncErr& fail)
   {
-    GMimeObject *mime_part, *text_part = NULL;
-    GMimeContentType *type, *first_type = NULL;
-    int count = g_mime_multipart_get_count (multipart);
+    fail.err = GPG_ERR_GENERAL;
 
-    for (int i = 0; i < count; ++i) {
-      mime_part = g_mime_multipart_get_part (multipart, i);
+    if (uid.empty()) return "";
 
-      type = g_mime_object_get_content_type (mime_part);
-      if (GMIME_IS_MULTIPART (mime_part)) {
-        multipart = GMIME_MULTIPART (mime_part);
-        if (g_mime_content_type_is_type (type, "multipart", "alternative")) {
-          mime_part = handle_multipart_alternative (multipart, is_html);
-          if (mime_part)
-            return mime_part;
-        } else {
-          mime_part = handle_multipart_mixed (multipart, is_html);
-          if (mime_part && !text_part)
-            text_part = mime_part;
-        }
-      } else if (g_mime_content_type_is_type (type, "text", "*")) {
-        if (!g_ascii_strcasecmp (type->subtype, "plain")) {
-          /* we got what we came for */
-          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
-          return mime_part;
-        }
+    gpgme_data_t gpg_buf, gpg_out_buf;
+    gpgme_key_t mykey(0), key;
 
-        /* if we haven't yet found a text part or if it is a type we can
-         * understand and it is the first of that type, save it */
-        if (!text_part || (!g_ascii_strcasecmp (type->subtype, "plain") && (first_type &&
-                           g_ascii_strcasecmp (type->subtype, first_type->subtype) != 0))) {
-          *is_html = !g_ascii_strcasecmp (type->subtype, "html");
-          text_part = mime_part;
-          first_type = type;
-        }
-      }
+    StringView v(body);
+    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
+    gpgme_data_new (&gpg_out_buf);
+    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
+
+    /* find key to uid */
+    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
+    while (!fail.err)
+    {
+      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
+      if (fail.err) break;
+      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
     }
+    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return std::string(""); }
 
-    return text_part;
-  }
+    gpgme_key_t enc_keys[] = { mykey, NULL};
 
-}
-#define NEEDS_DECODING(encoding) ((encoding == GMIME_CONTENT_ENCODING_BASE64) ||   \
-                                  (encoding == GMIME_CONTENT_ENCODING_UUENCODE) || \
-                                  (encoding == GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE))
+    fail.err     = gpgme_op_encrypt (gpg_ctx, enc_keys, GPGME_ENCRYPT_PREPARE, gpg_buf, gpg_out_buf);
+    fail.enc_res = gpgme_op_encrypt_result (gpg_ctx);
+    if (fail.err) return std::string("");
 
-namespace
-{
-  char *
-  pan_g_mime_part_get_content (GMimePart *mime_part, size_t *len)
+    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
+    ssize_t ret;
+    std::stringstream ret_str;
+    char buffer[4096]={0};
+
+    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
+    {
+      ret_str << buffer;
+    }
+
+    gpgme_data_release(gpg_buf);
+    gpgme_data_release(gpg_out_buf);
+
+    fail.err = GPG_ERR_NO_ERROR;
+    return ret_str.str();
+  }
+
+  std::string
+  gpg_encrypt_and_sign(const std::string& uid, const std::string& body, GPGEncErr& fail)
   {
-    char *retval = NULL;
+    fail.err = GPG_ERR_GENERAL;
 
-    g_return_val_if_fail (GMIME_IS_PART (mime_part), NULL);
+    if (uid.empty()) return "";
 
-    if (!mime_part->content || !mime_part->content->stream) {
-//      g_warning ("no content set on this mime part");  // dbg
-      return NULL;
-    }
+    gpgme_data_t gpg_buf, gpg_out_buf;
+    gpgme_key_t mykey(0), key;
 
-    GMimeDataWrapper *wrapper = g_mime_part_get_content_object(mime_part);
-    GMimeStream *stream = g_mime_stream_mem_new();
-    g_mime_data_wrapper_write_to_stream (wrapper, stream);
-    GByteArray *bytes = g_mime_stream_mem_get_byte_array((GMimeStreamMem*)stream);
-    *len = bytes->len + 1;
-    if (bytes->len)
+    StringView v(body);
+    gpgme_data_new_from_mem (&gpg_buf, v.str, v.len, 0);
+    gpgme_data_new (&gpg_out_buf);
+    gpgme_data_set_encoding (gpg_out_buf, GPGME_DATA_ENCODING_BASE64);
+
+    /* find key to uid */
+    fail.err = gpgme_op_keylist_start (gpg_ctx, 0, 0);
+    while (!fail.err)
     {
-      retval = (char*)g_malloc0(bytes->len + 1);
-      memcpy(retval, bytes->data, bytes->len);
+      fail.err = gpgme_op_keylist_next (gpg_ctx, &key);
+      if (fail.err) break;
+      if (strcmp(key->subkeys->keyid, uid.c_str()) == 0) { mykey = key; break; }
     }
-    g_object_unref(stream);
-
-    return retval;
-  }
-}
+    if (!mykey) { fail.err = GPG_ERR_NO_PUBKEY; return std::string(""); }
 
-char *pan::pan_g_mime_message_get_body (GMimeMessage *message, gboolean *is_html)
-{
-  GMimeObject *mime_part = NULL;
-  GMimeContentType *type;
-  GMimeMultipart *multipart;
-  char *body = NULL;
-  size_t len = 0;
+    gpgme_signers_clear(gpg_ctx);
+    gpgme_signers_add(gpg_ctx, mykey);
 
-  g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
-//  g_return_val_if_fail (is_html != NULL, NULL);
+    gpgme_key_t enc_keys[] = { mykey, NULL};
 
-  type = g_mime_object_get_content_type (message->mime_part);
-  if (GMIME_IS_MULTIPART (message->mime_part)) {
-    /* let's see if we can find a body in the multipart */
-    multipart = GMIME_MULTIPART (message->mime_part);
-    if (g_mime_content_type_is_type (type, "multipart", "alternative"))
-      mime_part = handle_multipart_alternative (multipart, is_html);
-    else
-      mime_part = handle_multipart_mixed (multipart, is_html);
-  } else if (g_mime_content_type_is_type (type, "text", "*")) {
-    /* this *has* to be the message body */
-    if (g_mime_content_type_is_type (type, "text", "html"))
-      *is_html = TRUE;
-    else
-      *is_html = FALSE;
-    mime_part = message->mime_part;
-  }
+    fail.err     = gpgme_op_encrypt_sign (gpg_ctx, enc_keys, GPGME_ENCRYPT_PREPARE, gpg_buf, gpg_out_buf);
+    fail.enc_res = gpgme_op_encrypt_result (gpg_ctx);
+    fail.sign_res= gpgme_op_sign_result (gpg_ctx);
+    if (fail.err) return std::string("");
 
-  if (mime_part != NULL) {
-    body = pan_g_mime_part_get_content (GMIME_PART (mime_part), &len);
-  }
-  return body;
-}
+    gpgme_data_seek (gpg_out_buf,SEEK_SET, 0);
+    ssize_t ret;
+    std::stringstream ret_str;
+    char buffer[4096]={0};
 
-void pan::pan_g_mime_message_add_recipients_from_string (GMimeMessage *message, GMimeRecipientType type, const char *string)
-{
-  InternetAddressList *addrlist;
-  if ((addrlist = internet_address_list_parse_string (string))) {
-    for (int i = 0; i < internet_address_list_length (addrlist); ++i) {
-      InternetAddress *ia = internet_address_list_get_address (addrlist, i);
-      if (INTERNET_ADDRESS_IS_MAILBOX(ia))
-        g_mime_message_add_recipient (message, type, internet_address_get_name(ia), internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(ia)));
+    while ((ret = gpgme_data_read (gpg_out_buf, buffer, sizeof(buffer))) > 0)
+    {
+      ret_str << buffer;
     }
-  }
-}
 
-/**
-* Works around a GMime bug that uses `Message-Id' rather than `Message-ID'
-*/
-void pan::pan_g_mime_message_set_message_id (GMimeMessage *msg, const char *mid)
-{
-    g_mime_object_append_header ((GMimeObject *) msg, "Message-ID", mid);
-    char * bracketed = g_strdup_printf ("<%s>", mid);
-    g_mime_header_list_set (GMIME_OBJECT(msg)->headers, "Message-ID", bracketed);
-    g_free (bracketed);
+    gpgme_data_release(gpg_buf);
+    gpgme_data_release(gpg_out_buf);
+
+    fail.err = GPG_ERR_NO_ERROR;
+    return ret_str.str();
+  }
+#endif
 }
 
 
diff --git a/pan/usenet-utils/mime-utils.h b/pan/usenet-utils/mime-utils.h
index 0047432..b43650d 100644
--- a/pan/usenet-utils/mime-utils.h
+++ b/pan/usenet-utils/mime-utils.h
@@ -76,7 +76,7 @@
 namespace pan
 {
 #ifdef HAVE_GPGME
-  GMimeStream* gpg_decrypt_and_verify (GPGSignersInfo& signer_info, GPGDecErr& info, GMimeStream* s, const char* body=0);
+  GMimeStream* gpg_decrypt_and_verify (GPGSignersInfo& signer_info, GPGDecErr& info, GMimeStream* s, int index=0, GMimeObject* parent=0);
   GMimeMessage* message_add_signed_part (const std::string& uid, const std::string& body_str, GMimeMessage* body, GPGEncErr& fail);
   std::string gpg_encrypt(const std::string& uid, const std::string& body, GPGEncErr& fail);
   std::string gpg_encrypt_and_sign(const std::string& uid, const std::string& body, GPGEncErr& fail);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]