Merge with OUT-285-MsgConverter
authorThomas
Mon, 17 Jul 2017 15:13:06 +0200
changeset 1748e5eac8b478de
parent 1747 302100eaa722
parent 1745 acdbd00c0066
child 1750 bf933fc8d4e9
child 1751 20cecd2804c6
Merge with OUT-285-MsgConverter
     1.1 --- a/MsgConverter.cs	Mon Jul 17 14:01:39 2017 +0200
     1.2 +++ b/MsgConverter.cs	Mon Jul 17 15:13:06 2017 +0200
     1.3 @@ -16,36 +16,20 @@
     1.4      /// </summary>
     1.5      internal static class MsgConverter
     1.6      {
     1.7 -        private static readonly object LockToken = new object();
     1.8 -        private static IConverterSession _session;
     1.9 -
    1.10          public static Guid CLSID_IConverterSession = new Guid("{4e3a7680-b77a-11d0-9da5-00c04fd65685}");
    1.11  
    1.12 -        public static Guid IID_IConverterSession = new Guid("{4b401570-b77b-11d0-9da5-00c04fd65685}");
    1.13 +        public static IConverterSession CreateConverterSession()
    1.14 +        {
    1.15 +            Type converter = Type.GetTypeFromCLSID(CLSID_IConverterSession);
    1.16 +            object obj = Activator.CreateInstance(converter);
    1.17 +            var session = (IConverterSession)obj;
    1.18 +            session.SetTextWrapping(false, 0); // We do not want to wrap any texts...
    1.19  
    1.20 -        public static IConverterSession Session
    1.21 -        {
    1.22 -            get
    1.23 -            {
    1.24 -                if (_session == null)
    1.25 -                {
    1.26 -                    Type converter = Type.GetTypeFromCLSID(CLSID_IConverterSession);
    1.27 -                    object obj = Activator.CreateInstance(converter);
    1.28 -                    _session = (IConverterSession)obj;
    1.29 -                    _session.SetTextWrapping(false, 0); // We do not want to wrap any texts...
    1.30 -                }
    1.31 -                return _session;
    1.32 -            }
    1.33 +            return session;
    1.34          }
    1.35  
    1.36          public static byte[] TryConvertEmbeddedItemToOpenFormat(Outlook.Attachment attachment, ref string fileName, ref string mimeType)
    1.37          {
    1.38 -            bool passUnconvertedData = false;
    1.39 -            byte[] bytes = new byte[0];
    1.40 -            string tempFile;
    1.41 -            object asMail = null;
    1.42 -            Outlook.MailItem omi = null;
    1.43 -
    1.44              // Convert an attached embedded item (e. G. "forward as attachment") to an
    1.45              // open standard byte stream if possible.
    1.46              // TODO: Possible improvements:
    1.47 @@ -56,88 +40,89 @@
    1.48  
    1.49              Debug.Assert(attachment.Type == Outlook.OlAttachmentType.olEmbeddeditem, "wrong attachment type" + attachment.Type);
    1.50  
    1.51 -            tempFile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "pep" + Guid.NewGuid().ToString() + ".msg");
    1.52 +            var tempFile = Path.Combine(Path.GetTempPath(), "pep" + Guid.NewGuid().ToString() + ".msg");
    1.53  
    1.54              try
    1.55              {
    1.56                  attachment.SaveAsFile(tempFile);
    1.57 -                asMail = Globals.ThisAddIn.Application.Session.OpenSharedItem(tempFile);
    1.58 -                omi = (Outlook.MailItem)asMail;
    1.59  
    1.60 -                var myStream = new MemoryStreamComMinimalWrapper();
    1.61 -                //var mapiObject = omi.MAPIOBJECT;
    1.62 +                object asMail = Globals.ThisAddIn.Application.Session.OpenSharedItem(tempFile);
    1.63                  try
    1.64                  {
    1.65 -                    //IntPtr mapiObjectPtr = Marshal.GetIUnknownForObject(mapiObject);
    1.66 -
    1.67 +                    var omi = (Outlook.MailItem)asMail;
    1.68                      try
    1.69                      {
    1.70 -                        // TODO: Possibly check whether the original message had any MIME headers
    1.71 -                        // using the PR_TRANSPORT_MESSAGE_HEADERS property, and call 
    1.72 -                        // Session.SetSaveFormat(MimeSaveType.SAVE_RFC_1521) or Session.SetSaveFormat(MimeSaveType.Save_RFC822)
    1.73 -                        // depending on whether the original message was a MIME message or not.
    1.74 -
    1.75 +                        var myStream = new MemoryStreamComMinimalWrapper();
    1.76                          IStream iStream = myStream;
    1.77  
    1.78 -                        var hr = Session.MAPIToMIMEStm((IMessage)omi.MAPIOBJECT, iStream, CCSF.SMTP | CCSF.EightBITHEADERS);
    1.79 +                        // As this MAPIOBJECT is internally used by outlook, we should not free it,
    1.80 +                        // to prevent problems with internal Outlook handling (the reference we get
    1.81 +                        // might be a shared reference...)
    1.82 +                        object mapiObject = omi.MAPIOBJECT;
    1.83 +                        if (mapiObject == null)
    1.84 +                        {
    1.85 +                            Debug.Print("No MAPI Object found, using unconverted attachment data.");
    1.86 +                            return File.ReadAllBytes(tempFile);
    1.87 +                        }
    1.88  
    1.89 -                        if (hr != 0)
    1.90 -                            throw new COMException("COM exception", hr);
    1.91 +                        var mapiMessage = (IMessage)mapiObject;
    1.92 +
    1.93 +                        var session = CreateConverterSession();
    1.94 +                        try
    1.95 +                        {
    1.96 +                            // TODO: Possibly check whether the original message had any MIME headers
    1.97 +                            // using the PR_TRANSPORT_MESSAGE_HEADERS property, and call 
    1.98 +                            // Session.SetSaveFormat(MimeSaveType.SAVE_RFC_1521) or Session.SetSaveFormat(MimeSaveType.Save_RFC822)
    1.99 +                            // depending on whether the original message was a MIME message or not.
   1.100 +                            var hr = session.MAPIToMIMEStm(mapiMessage, iStream, CCSF.SMTP | CCSF.EightBITHEADERS);
   1.101 +
   1.102 +                            Marshal.ThrowExceptionForHR(hr);
   1.103 +                        }
   1.104 +                        finally
   1.105 +                        {
   1.106 +                            // This com object is locally scoped, noone else has a reference, so we release it to clean up ressources.
   1.107 +                            Marshal.FinalReleaseComObject(session);
   1.108 +                        }
   1.109 +
   1.110 +                        var bytes = myStream.ToArray();
   1.111 +
   1.112 +                        fileName = Path.ChangeExtension(fileName ?? attachment.FileName, ".eml");
   1.113 +                        mimeType = "message/rfc822"; // This will only help with gpg/mime or s/mime, where the mime type can be retained.
   1.114 +                        return bytes;
   1.115                      }
   1.116                      finally
   1.117                      {
   1.118 -                        //Marshal.Release(mapiObjectPtr);
   1.119 +                        // This com objects are locally scoped, and noone else has a reference,
   1.120 +                        // so we release them to clean up ressources.
   1.121 +                        Marshal.FinalReleaseComObject(omi);
   1.122                      }
   1.123                  }
   1.124                  finally
   1.125                  {
   1.126 -                    //Marshal.ReleaseComObject(mapiObject);
   1.127 +                    // This com objects are locally scoped, and noone else has a reference,
   1.128 +                    // so we release them to clean up ressources.
   1.129 +                    Marshal.FinalReleaseComObject(asMail);
   1.130                  }
   1.131 -
   1.132 -                bytes = myStream.ToArray();
   1.133 -
   1.134 -                fileName = Path.ChangeExtension(fileName ?? attachment.FileName, ".eml");
   1.135 -                mimeType = "message/rfc822"; // This will only help with gpg/mime or s/mime, where the mime type can be retained.
   1.136              }
   1.137              catch (Exception ex)
   1.138              {
   1.139 -                passUnconvertedData = true;
   1.140                  Debug.Print(ex.ToString());
   1.141 +
   1.142 +                Debug.Print("Using unconverted attachment data.");
   1.143 +                return File.ReadAllBytes(tempFile);
   1.144              }
   1.145              finally
   1.146              {
   1.147 -                // Release COM objects
   1.148 -                if (asMail != null)
   1.149 -                {
   1.150 -                    Marshal.FinalReleaseComObject(asMail);
   1.151 -                    asMail = null;
   1.152 -                }
   1.153 -
   1.154 -                if (omi != null)
   1.155 -                {
   1.156 -                    Marshal.FinalReleaseComObject(omi);
   1.157 -                    omi = null;
   1.158 -                }
   1.159 -
   1.160 -                // Pass data through if failure
   1.161 -                if (passUnconvertedData)
   1.162 -                {
   1.163 -                    Debug.Print("Using unconverted attachment data.");
   1.164 -                    bytes = File.ReadAllBytes(tempFile);
   1.165 -                }
   1.166 -
   1.167                  // Delete temp file
   1.168                  try
   1.169                  {
   1.170 -                    System.IO.File.Delete(tempFile);
   1.171 +                    File.Delete(tempFile);
   1.172                  }
   1.173                  catch (Exception ex)
   1.174                  {
   1.175                      Debug.Print(ex.ToString());
   1.176                  }
   1.177              }
   1.178 -
   1.179 -            return (bytes);
   1.180          }
   1.181  
   1.182          /// <summary>
   1.183 @@ -307,6 +292,13 @@
   1.184  
   1.185          }
   1.186  
   1.187 +        /// <summary>
   1.188 +        /// Minimal hackish declaration of the IConverterSession interface, which allows us to call
   1.189 +        /// the methods via COM. We need to keep the VTable order, thus the declaration of methods
   1.190 +        /// we do not use.
   1.191 +        /// </summary>
   1.192 +        /// <seealso cref="https://msdn.microsoft.com/de-de/library/office/ff960417.aspx"/>
   1.193 +        /// <seealso cref="https://msdn.microsoft.com/DE-DE/library/office/ff960231.aspx"/>
   1.194          [ComImport]
   1.195          [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   1.196          [Guid("4b401570-b77b-11d0-9da5-00c04fd65685")]
   1.197 @@ -331,8 +323,8 @@
   1.198              [PreserveSig]
   1.199              int Placeholder1();
   1.200  
   1.201 -            // Converts a MIME stream to a MAPI mail object - it is unclear wheterh
   1.202 -            // we will be able 
   1.203 +            // Converts a MIME stream to a MAPI mail object - it is unclear whether
   1.204 +            // we will be able to make use of this method one day...
   1.205              [PreserveSig]
   1.206              int MIMEToMAPI(
   1.207                  [In, MarshalAs(UnmanagedType.Interface)] IStream pstm, // LPStream