Concatenate body parts.
authorWerner Koch <wk@gnupg.org>
Wed, 9 Jan 2008 13:25:15 +0000 (13:25 +0000)
committerWerner Koch <wk@gnupg.org>
Wed, 9 Jan 2008 13:25:15 +0000 (13:25 +0000)
NEWS
TODO
doc/gpgol.texi
src/ChangeLog
src/mapihelp.cpp
src/message.cpp
src/mimeparser.c
src/user-events.cpp

diff --git a/NEWS b/NEWS
index d1fa020..b4708ea 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ Noteworthy changes for version 0.10.4
 
  * Sign and encrypt works now.
 
+ * Texts with embedded attachments are now concatenated.
+
 
 Noteworthy changes for version 0.10.3 (2007-12-10)
 ==================================================
diff --git a/TODO b/TODO
index aca8fd1..88ce59a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -22,5 +22,5 @@
 * We should not write a decrypted file without user consent.  A
   possible solution in attach-file-events.c is to keep track of
   presented file names and decrypt them only on OpenSzFile.  Need to
-  get some documentaion first.
+  find some documentation first.
 
index 0f8117f..64e8d40 100644 (file)
@@ -641,12 +641,6 @@ does not exist, is is first searched below @code{HKLM} and then it
 defaults to @code{bin/kleopatra.exe} (FIXME: The final name will be just
 @code{kleopatra.exe}).
 
-In case the UI server requires the socket name as an argument, the
-placeholder @code{$s} may be used to indicate this.  Due to this feature
-it is required that all verbatim dollar signs are doubled.  If the
-actual program name contains spaces the program name needs to be enclosed
-in quotes.
-
 @item HKCU\Software\GNU\GpgOL:enableDebug
 Setting this key to the string @code{1} enables a few extra features in
 the UI, useful only for debugging.  Setting it to values larger than 1
index 68fd8a1..bbe29ae 100644 (file)
@@ -1,3 +1,16 @@
+2008-01-09  Werner Koch  <wk@g10code.com>
+
+       * mimeparser.c (finish_saved_body): New.
+       (finish_attachment): Keep the body attachment open.
+       (mime_decrypt, mime_verify): Close the saved body data.
+       (t2body): Continue body attachments.
+
+       * message.cpp (message_verify): Save changes.
+
+       * mapihelp.cpp (mapi_change_message_class): Handle case of
+       PGP/MIME signed with IPM.Note.  Save only of really needed, use
+       FORCE_SAVE and keep it open for read and write.
+
 2008-01-07  Marcus Brinkmann  <marcus@g10code.de>
 
        * engine-assuan.c (replace_dollar_s): Remove obsolete function.
index af87d00..df8ad04 100644 (file)
@@ -477,6 +477,7 @@ mapi_change_message_class (LPMESSAGE message)
   SPropValue prop;
   LPSPropValue propval = NULL;
   char *newvalue = NULL;
+  int need_save = 0;
 
   if (!message)
     return 0; /* No message: Nop. */
@@ -496,7 +497,7 @@ mapi_change_message_class (LPMESSAGE message)
       if (!strcmp (s, "IPM.Note"))
         {
           /* Most message today are of this type.  However a PGP/MIME
-             encrypted message although has this class here.  We need
+             encrypted message also has this class here.  We need
              to see whether we can detect such a mail right here and
              change the message class accordingly. */
           char *ct, *proto;
@@ -516,7 +517,16 @@ mapi_change_message_class (LPMESSAGE message)
               
                   if (!strcmp (ct, "multipart/encrypted")
                       && !strcmp (proto, "application/pgp-encrypted"))
-                    newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
+                    {
+                      newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
+                    }
+                  else if (!strcmp (ct, "multipart/signed")
+                           && !strcmp (proto, "application/pgp-signature"))
+                    {
+                      /* Sometimes we receive a PGP/MIME signed
+                         message with a class IPM.Note.  */
+                      newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
+                    }
                   xfree (proto);
                 }
               else if (!strcmp (ct, "text/plain"))
@@ -617,7 +627,10 @@ mapi_change_message_class (LPMESSAGE message)
       /* We use our Sig-Status property to mark messages which passed
          this function.  This helps us to avoid later tests.  */
       if (!mapi_has_sig_status (message))
-        mapi_set_sig_status (message, "#");
+        {
+          mapi_set_sig_status (message, "#");
+          need_save = 1;
+        }
     }
   else
     {
@@ -631,14 +644,18 @@ mapi_change_message_class (LPMESSAGE message)
                      SRCNAME, __func__, hr);
           return 0;
         }
+      need_save = 1;
     }
 
-  hr = message->SaveChanges (KEEP_OPEN_READONLY);
-  if (hr)
+  if (need_save)
     {
-      log_error ("%s:%s: SaveChanges() failed: hr=%#lx\n",
-                 SRCNAME, __func__, hr); 
-      return 0;
+      hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+      if (hr)
+        {
+          log_error ("%s:%s: SaveChanges() failed: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          return 0;
+        }
     }
 
   return 1;
index b5acce5..f136d98 100644 (file)
@@ -64,8 +64,10 @@ message_incoming_handler (LPMESSAGE message,msgtype_t msgtype, HWND hwnd)
          code it won't have an unknown msgtype _and_ no sig status
          flag.  Thus we look at the message class now and change it if
          required.  It won't get displayed correctly right away but a
-         latter decrypt command or when viewd a second time all has
-         been set.  */
+         latter decrypt command or when viewed a second time all has
+         been set.  Note that we should have similar code for some
+         message classes in GpgolUserEvents:OnSelectionChange; but
+         tehre are a couiple of problems.  */
       if (!mapi_has_sig_status (message))
         {
           log_debug ("%s:%s: message class not yet checked - doing now\n",
@@ -443,6 +445,7 @@ pgp_mime_from_clearsigned (LPSTREAM input, size_t *outputlen)
 int
 message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
 {
+  HRESULT hr;
   mapi_attach_item_t *table = NULL;
   int moss_idx = -1;
   int i;
@@ -463,10 +466,14 @@ message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
     case MSGTYPE_GPGOL_PGP_MESSAGE:
+      log_debug ("%s:%s: message of type %d not expected",
+                 SRCNAME, __func__, msgtype);
       return -1; /* Should not be called for such a message.  */
     case MSGTYPE_UNKNOWN:
     case MSGTYPE_SMIME:
     case MSGTYPE_GPGOL:
+      log_debug ("%s:%s: message of type %d ignored",
+                 SRCNAME, __func__, msgtype);
       return 0; /* Nothing to do.  */
     }
   
@@ -553,6 +560,12 @@ message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
   else
     mapi_set_sig_status (message, "! Good signature");
 
+  hr = message->SaveChanges (KEEP_OPEN_READWRITE);
+  if (hr)
+    log_error_w32 (hr, "%s:%s: SaveChanges failed",
+                   SRCNAME, __func__); 
+
+
   mapi_release_attach_table (table);
   return 0;
 }
index c03fc0f..14494db 100644 (file)
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-/*
-   Fixme: Explain how the this parser works and how it fits into the
-   whole picture.
-*/
-   
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
@@ -116,6 +111,7 @@ struct mime_context
   int is_qp_encoded;      /* Current part is QP encoded. */
   int is_base64_encoded;  /* Current part is base 64 encoded. */
   int is_utf8;            /* Current part has charset utf-8. */
+  int is_body;            /* The current part belongs to the body.  */
   protocol_t protocol;    /* The detected crypto protocol.  */
 
   int part_counter;       /* Counts the number of processed parts. */
@@ -134,7 +130,14 @@ struct mime_context
   LPSTREAM outstream;     /* NULL or a stream to write a part to. */
   LPATTACH mapi_attach;   /* The attachment object we are writing.  */
   symenc_t symenc;        /* NULL or the context used to protect
-                             attachments. */
+                             an attachment. */
+  struct {
+    LPSTREAM outstream;   /* Saved stream used to continue a body
+                             part. */
+    LPATTACH mapi_attach; /* Saved attachment used to continue a body part.  */
+    symenc_t symenc;      /* Saved encryption context used to continue
+                             a body part.  */
+  } body_saved;
   int any_attachments_created;  /* True if we created a new atatchment.  */
 
   b64_state_t base64;     /* The state of the Base-64 decoder.  */
@@ -320,7 +323,7 @@ start_attachment (mime_context_t ctx, int is_body)
           goto leave;
         }
     }
-  
+  ctx->is_body = is_body;
 
   /* We need to insert a short filename .  Without it, the _displayed_
      list of attachments won't get updated although the attachment has
@@ -469,7 +472,13 @@ finish_attachment (mime_context_t ctx, int cancel)
   log_debug ("%s:%s: for ctx=%p cancel=%d", SRCNAME, __func__, ctx, cancel);
 #endif
 
-  if (ctx->outstream)
+  if (ctx->outstream && ctx->is_body && !ctx->body_saved.outstream)
+    {
+      ctx->body_saved.outstream = ctx->outstream;
+      ctx->outstream = NULL;
+      retval = 0;
+    }
+  else if (ctx->outstream)
     {
       IStream_Commit (ctx->outstream, 0);
       IStream_Release (ctx->outstream);
@@ -489,20 +498,81 @@ finish_attachment (mime_context_t ctx, int cancel)
             retval = 0; /* Success.  */
         }
     }
-  if (ctx->mapi_attach)
+
+  if (ctx->mapi_attach && ctx->is_body && !ctx->body_saved.mapi_attach)
+    {
+      ctx->body_saved.mapi_attach = ctx->mapi_attach;
+      ctx->mapi_attach = NULL;
+    }
+  else if (ctx->mapi_attach)
     {
       IAttach_Release (ctx->mapi_attach);
       ctx->mapi_attach = NULL;
     }
-  if (ctx->symenc)
+
+  if (ctx->symenc && ctx->is_body && !ctx->body_saved.symenc)
+    {
+      ctx->body_saved.symenc = ctx->symenc;
+      ctx->symenc = NULL;
+    }
+  else if (ctx->symenc)
     {
       symenc_close (ctx->symenc);
       ctx->symenc = NULL;
     }
+
+  ctx->is_body = 0;
+  
   return retval;
 }
 
 
+/* Finish the saved body part.  This is required because we delay the
+   finishing of body parts.  */
+static int 
+finish_saved_body (mime_context_t ctx, int cancel)
+{
+  HRESULT hr;
+  int retval = -1;
+
+  if (ctx->body_saved.outstream)
+    {
+      IStream_Commit (ctx->body_saved.outstream, 0);
+      IStream_Release (ctx->body_saved.outstream);
+      ctx->body_saved.outstream = NULL;
+
+      if (cancel)
+        retval = 0;
+      else if (ctx->body_saved.mapi_attach)
+        {
+          hr = IAttach_SaveChanges (ctx->body_saved.mapi_attach, 0);
+          if (hr)
+            {
+              log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+            }
+          else
+            retval = 0; /* Success.  */
+        }
+    }
+
+  if (ctx->body_saved.mapi_attach)
+    {
+      IAttach_Release (ctx->body_saved.mapi_attach);
+      ctx->body_saved.mapi_attach = NULL;
+    }
+
+  if (ctx->symenc)
+    {
+      symenc_close (ctx->body_saved.symenc);
+      ctx->body_saved.symenc = NULL;
+    }
+
+  return retval;
+}
+
+
+
 /* Create the MIME info string.  This is a LF delimited string
    with one line per MIME part.  Each line is formatted this way:
    LEVEL:ENCINFO:SIGINFO:CT:CHARSET:FILENAME
@@ -566,7 +636,7 @@ finish_message (LPMESSAGE message, gpg_error_t err, int protect_mode,
   SPropValue prop;
 
   /* If this was an encrypted message we save the session marker in a
-     specila property so that we now that we already decrypted that
+     speciat property so that we now that we already decrypted that
      message within this session.  This is pretty useful when
      scrolling through messages and preview decryption has been
      enabled.  */
@@ -630,7 +700,6 @@ t2body (mime_context_t ctx, rfc822parse_t msg)
   size_t off;
   char *p;
   int is_text = 0;
-  int is_body = 0;
   char *filename = NULL; 
   char *charset = NULL;
         
@@ -767,24 +836,62 @@ t2body (mime_context_t ctx, rfc822parse_t msg)
   /* If this is a text part, decide whether we treat it as our body. */
   if (is_text)
     {
+      ctx->collect_attachment = 1;
+
       /* If this is the first text part at all we will start to
          collect it and use it later as the regular body.  */
       if (!ctx->body_seen)
         {
           ctx->body_seen = 1;
-          ctx->collect_attachment = 1;
-          is_body = 1;
+          if (start_attachment (ctx, 1))
+            return -1;
+          assert (ctx->outstream);
+        }
+      else if (!ctx->body_saved.outstream || !ctx->body_saved.mapi_attach)
+        {
+          /* Oops: We expected to continue a body but the state is not
+             correct.  Create a plain attachment instead.  */
+          log_debug ("%s:%s: ctx=%p, no saved outstream or mapi_attach (%p,%p)",
+                     SRCNAME, __func__, ctx, 
+                     ctx->body_saved.outstream, ctx->body_saved.mapi_attach);
+          if (start_attachment (ctx, 0))
+            return -1;
+          assert (ctx->outstream);
+        }
+      else if (ctx->outstream || ctx->mapi_attach || ctx->symenc)
+        {
+          /* We expected to continue a body but the last attachment
+             has not been properly closed.  Create a plain attachment
+             instead.  */
+          log_debug ("%s:%s: ctx=%p, outstream, mapi_attach or symenc not "
+                     "closed (%p,%p,%p)",
+                     SRCNAME, __func__, ctx, 
+                     ctx->outstream, ctx->mapi_attach, ctx->symenc);
+          if (start_attachment (ctx, 0))
+            return -1;
+          assert (ctx->outstream);
+        }
+      else 
+        {
+          /* We already got one body and thus we can continue that
+             last attachment.  */
+#ifdef DEBUG_PARSER
+          log_debug ("%s:%s: continuing body part\n", SRCNAME, __func__);
+#endif
+          ctx->is_body = 1;
+          ctx->outstream = ctx->body_saved.outstream;
+          ctx->mapi_attach = ctx->body_saved.mapi_attach;
+          ctx->symenc = ctx->body_saved.symenc;
+          ctx->body_saved.outstream = NULL;
+          ctx->body_saved.mapi_attach = NULL;
+          ctx->body_saved.symenc = NULL;
         }
-      else if (!ctx->preview)
-        ctx->collect_attachment = 1;
     }
-
-
-  if (ctx->collect_attachment)
+  else if (ctx->collect_attachment)
     {
       /* Now that if we have an attachment prepare a new MAPI
          attachment.  */
-      if (start_attachment (ctx, is_body))
+      if (start_attachment (ctx, 0))
         return -1;
       assert (ctx->outstream);
     }
@@ -1101,6 +1208,8 @@ mime_verify (protocol_t protocol, const char *message, size_t messagelen,
     {
       /* Cancel any left open attachment.  */
       finish_attachment (ctx, 1); 
+      /* Save the body atatchment. */
+      finish_saved_body (ctx, 0);
       rfc822parse_close (ctx->msg);
       gpgme_data_release (ctx->signed_data);
       gpgme_data_release (ctx->sig_data);
@@ -1114,6 +1223,7 @@ mime_verify (protocol_t protocol, const char *message, size_t messagelen,
           ctx->mimestruct = tmp;
         }
       symenc_close (ctx->symenc);
+      symenc_close (ctx->body_saved.symenc);
       xfree (ctx);
     }
   return err;
@@ -1169,7 +1279,7 @@ mime_decrypt (protocol_t protocol, LPSTREAM instream, LPMESSAGE mapi_message,
       char buffer[4096];
       
       /* For EOF detection we assume that Read returns no error and
-         thus nread will be 0.  The specs say that "Depending on the
+         thus NREAD will be 0.  The specs say that "Depending on the
          implementation, either S_FALSE or an error code could be
          returned when reading past the end of the stream"; thus we
          are not really sure whether our assumption is correct.  At
@@ -1215,6 +1325,8 @@ mime_decrypt (protocol_t protocol, LPSTREAM instream, LPMESSAGE mapi_message,
          started the body attachment (gpgol000.txt) - this one needs
          to be finished properly.  */
       finish_attachment (ctx, ctx->any_boundary? 1: 0);
+      /* Save the body attachment.  */
+      finish_saved_body (ctx, 0);
       rfc822parse_close (ctx->msg);
       if (ctx->signed_data)
         gpgme_data_release (ctx->signed_data);
@@ -1230,6 +1342,7 @@ mime_decrypt (protocol_t protocol, LPSTREAM instream, LPMESSAGE mapi_message,
           ctx->mimestruct = tmp;
         }
       symenc_close (ctx->symenc);
+      symenc_close (ctx->body_saved.symenc);
       xfree (ctx);
     }
   return err;
index ef6b8a5..48f819a 100644 (file)
@@ -1,5 +1,5 @@
 /* user-events.cpp - Subclass impl of IExchExtUserEvents
- *     Copyright (C) 2007 g10 Code GmbH
+ *     Copyright (C) 2007, 2008 g10 Code GmbH
  * 
  * This file is part of GpgOL.
  * 
 
 
 /* Wrapper around UlRelease with error checking. */
-/* FIXME: Duplicated code.  */
-#if 0
-static void 
-ul_release (LPVOID punk)
-{
-  ULONG res;
+// static void 
+// ul_release (LPVOID punk, const char *func, int lnr)
+// {
+//   ULONG res;
   
-  if (!punk)
-    return;
-  res = UlRelease (punk);
-//   log_debug ("%s UlRelease(%p) had %lu references\n", __func__, punk, res);
-}
-#endif
+//   if (!punk)
+//     return;
+//   res = UlRelease (punk);
+//   log_debug ("%s:%s:%d: UlRelease(%p) had %lu references\n", 
+//              SRCNAME, func, lnr, punk, res);
+// }
 
 
 
@@ -105,12 +103,39 @@ GpgolUserEvents::OnSelectionChange (LPEXCHEXTCALLBACK eecb)
   hr = eecb->GetSelectionCount (&count);
   if (SUCCEEDED (hr) && count > 0)
     {
+      /* Get the first selected item.  */
       hr = eecb->GetSelectionItem (0L, NULL, NULL, &objtype,
                                    msgclass, sizeof msgclass -1, NULL, 0L);
       if (SUCCEEDED(hr) && objtype == MAPI_MESSAGE)
         {
           log_debug ("%s:%s: message class: %s\n",
                      SRCNAME, __func__, msgclass);
+
+          /* If SMIME has been enabled and the current message is of
+             class SMIME or in the past processed by CryptoEx, we
+             change the message class. */ 
+          // Unfortunaltely we can't use this because:
+          // 1. GetSelectionItem is as usual heavily undocumented and
+          // we need to guess a bit to see how to get message from the
+          // EntryID (2nd and 3rd arg).  2.  There are reports that
+          // OL2007 crashes when changing the message here.
+//           if (opt.enable_smime 
+//               && (!strncmp (msgclass, "IPM.Note.SMIME", 14)
+//                   || !strncmp (msgclass, "IPM.Note.Secure.Cex", 19)))
+//             {
+//               LPMESSAGE message = NULL;
+//               LPMDB mdb = NULL;
+
+//               hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
+//               if (SUCCEEDED (hr) && !mapi_has_sig_status (message))
+//                 {
+//                   log_debug ("%s:%s: message class not yet checked"
+//                              " - doing now\n", SRCNAME, __func__);
+//                   mapi_change_message_class (message);
+//                 }
+//               ul_release (message, __func__, __LINE__);
+//               ul_release (mdb, __func__, __LINE__);
+//             }
         }
       else if (SUCCEEDED(hr) && objtype == MAPI_FOLDER)
         {