PEPMessage.cs
author Thomas
Tue, 20 Sep 2016 12:49:18 +0200
changeset 1299 523bb6ad5ccc
parent 1294 5a1fdc8f3334
child 1303 5b20f1bee571
permissions -rw-r--r--
Add setSender parameter to ApplyTo() method and only use it in CreateNewSentMail()
     1 using Microsoft.Win32;
     2 using pEpCOMServerAdapterLib;
     3 using System;
     4 using System.Collections.Generic;
     5 using System.ComponentModel;
     6 using System.Runtime.InteropServices;
     7 using System.Text;
     8 using Outlook = Microsoft.Office.Interop.Outlook;
     9 
    10 namespace pEp
    11 {
    12     /// <summary>
    13     /// Class for a completely in-memory message based on the pEp engine text_message.
    14     /// </summary>
    15     internal class PEPMessage : INotifyPropertyChanged,
    16                                 IEquatable<PEPMessage>,
    17                                 Interfaces.ICopy<PEPMessage>,
    18                                 Interfaces.IReset
    19     {
    20         /// <summary>
    21         /// Event raised when a property is changed on a component.
    22         /// </summary>
    23         public event PropertyChangedEventHandler PropertyChanged;
    24 
    25         // pEp named MAPI properties
    26         public const string PR_X_ENC_STATUS_NAME           = "X-EncStatus";
    27         public const string PR_X_KEY_LIST_NAME             = "X-KeyList";
    28         public const string PR_X_PEP_NEVER_UNSECURE_NAME   = "X-pEp-Never-Unsecure";
    29         public const string PR_X_PEP_PROTOCOL_VERSION_NAME = "X-pEp-Version";
    30 
    31         public static readonly MAPIProperty.MAPIProp XPidNameEncStatus          = new MAPIProperty.MAPIProp(PR_X_ENC_STATUS_NAME,           MAPIProperty.PS_INTERNET_HEADERS);
    32         public static readonly MAPIProperty.MAPIProp XPidNameKeyList            = new MAPIProperty.MAPIProp(PR_X_KEY_LIST_NAME,             MAPIProperty.PS_INTERNET_HEADERS);
    33         public static readonly MAPIProperty.MAPIProp XPidNamePEPNeverUnsecure   = new MAPIProperty.MAPIProp(PR_X_PEP_NEVER_UNSECURE_NAME,   MAPIProperty.PS_INTERNET_HEADERS);
    34         public static readonly MAPIProperty.MAPIProp XPidNamePEPProtocolVersion = new MAPIProperty.MAPIProp(PR_X_PEP_PROTOCOL_VERSION_NAME, MAPIProperty.PS_INTERNET_HEADERS);
    35 
    36         private List<PEPAttachment> _Attachments;
    37         private List<PEPIdentity>   _BCC;
    38         private List<PEPIdentity>   _CC;
    39         private _pEp_color          _Rating;
    40         private string              _ConversationID;
    41         private string              _ConversationIndex;
    42         private string              _ConversationTopic;
    43         private _pEp_msg_direction  _Direction;
    44         private bool                _ForceUnencrypted;
    45         private PEPIdentity         _From;
    46         private string              _ID;
    47         private string              _KeyList;
    48         private List<string>        _Keywords;
    49         private string              _LongMsg;
    50         private string              _LongMsgFormattedHTML;
    51         private string              _LongMsgFormattedRTF;
    52         private bool                _NeverUnsecure;
    53         private string              _PEPProtocolVersion;
    54         private DateTime?           _ReceivedOn;
    55         private DateTime?           _SentOn;
    56         private string              _ShortMsg;
    57         private List<PEPIdentity>   _To;
    58 
    59         /**************************************************************
    60          * 
    61          * Constructors
    62          * 
    63          *************************************************************/
    64 
    65         /// <summary>
    66         /// Default constructor.
    67         /// </summary>
    68         public PEPMessage()
    69         {
    70             this.Reset();
    71         }
    72 
    73         /**************************************************************
    74          * 
    75          * Property Accessors
    76          * 
    77          *************************************************************/
    78 
    79         #region Property Accessors
    80 
    81         /// <summary>
    82         /// Gets the list of attachements for this message.
    83         ///          MailItem : Corresponds to property 'Attachments' which contains 'Attachment'
    84         /// CryptableMailItem : not exposed
    85         ///      text_message : Corresponds to property 'attachments' which contains 'blob'
    86         /// </summary>
    87         public List<PEPAttachment> Attachments
    88         {
    89             get { return (this._Attachments); }
    90         }
    91 
    92         /// <summary>
    93         /// Gets the list of identities to be blind carbon copied on the message.
    94         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
    95         /// CryptableMailItem : not exposed directly (part of Recipients)
    96         ///      text_message : Corresponds to property 'bcc'
    97         /// </summary>
    98         public List<PEPIdentity> BCC
    99         {
   100             get { return (this._BCC); }
   101         }
   102 
   103         /// <summary>
   104         /// Gets the list of identities to be carbon copied on the message.
   105         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
   106         /// CryptableMailItem : not exposed directly (part of Recipients)
   107         ///      text_message : Corresponds to property 'cc'
   108         /// </summary>
   109         public List<PEPIdentity> CC
   110         {
   111             get { return (this._CC); }
   112         }
   113 
   114         /// <summary>
   115         /// Gets or sets the pEp rating of the message.
   116         /// This should corresponds primarily with the decryption rating.
   117         /// Warning: Since this is stored as a header field, care must be taken not to apply this to a MailItem on an 
   118         /// untrusted server or on an outgoing message.
   119         ///          MailItem : Corresponds to MAPI Property PR_X_ENC_STATUS (Header field)
   120         /// CryptableMailItem : not exposed (processed internally)
   121         ///      text_message : Corresponds with the key PR_X_ENC_STATUS_NAME within 'opt_fields' array
   122         /// </summary>
   123         public _pEp_color Rating
   124         {
   125             get { return (this._Rating); }
   126             set
   127             {
   128                 this._Rating = value;
   129                 this.RaisePropertyChangedEvent(nameof(this.Rating));
   130             }
   131         }
   132 
   133         /// <summary>
   134         /// Gets or sets the conversation ID of the message.
   135         /// Outlook should normally manage this itself.
   136         ///          MailItem : Corresponds to property 'ConversationID' (Underlying MAPI property PidTagConversationId)
   137         /// CryptableMailItem : Corresponds to property 'ConversationID'
   138         ///      text_message : not supported
   139         /// </summary>
   140         public string ConversationID
   141         {
   142             get { return (this._ConversationID); }
   143             set
   144             {
   145                 this._ConversationID = value;
   146                 this.RaisePropertyChangedEvent(nameof(this.ConversationID));
   147             }
   148         }
   149 
   150         /// <summary>
   151         /// Gets or sets the conversation index of the message.
   152         /// Outlook should normally manage this itself.
   153         ///          MailItem : Corresponds to property 'ConversationIndex' (Underlying MAPI property PidTagConversationIndex)
   154         /// CryptableMailItem : Corresponds to property 'ConversationIndex'
   155         ///      text_message : not supported
   156         /// </summary>
   157         public string ConversationIndex
   158         {
   159             get { return (this._ConversationIndex); }
   160             set
   161             {
   162                 this._ConversationIndex = value;
   163                 this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
   164             }
   165         }
   166 
   167         /// <summary>
   168         /// Gets or sets the conversation index of the message.
   169         /// Outlook should normally manage this itself.
   170         ///          MailItem : Corresponds to property 'ConversationTopic' (Underlying MAPI property PidTagConversationTopic)
   171         /// CryptableMailItem : Corresponds to property 'ConversationTopic'
   172         ///      text_message : not supported
   173         /// </summary>
   174         public string ConversationTopic
   175         {
   176             get { return (this._ConversationTopic); }
   177             set
   178             {
   179                 this._ConversationTopic = value;
   180                 this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
   181             }
   182         }
   183 
   184         /// <summary>
   185         /// Gets the date and time when the message was either sent or received.
   186         /// This corresponds with the SentOn and ReceivedOn properties defined separately.
   187         /// </summary>
   188         public DateTime? DateTimeSentOrReceived
   189         {
   190             get
   191             {
   192                 if (this._Direction == _pEp_msg_direction.pEp_dir_incoming)
   193                 {
   194                     return (this._ReceivedOn);
   195                 }
   196                 else
   197                 {
   198                     return (this._SentOn);
   199                 }
   200             }
   201         }
   202 
   203         /// <summary>
   204         /// Gets or sets the direction (incoming or outgoing) of the message.
   205         ///          MailItem : not supported (calculated from various properties)
   206         /// CryptableMailItem : Corresponds 1-to-1 with property 'IsIncoming'
   207         ///      text_message : Corresponds to property 'dir'
   208         /// </summary>
   209         public _pEp_msg_direction Direction
   210         {
   211             get { return (this._Direction); }
   212             set
   213             {
   214                 this._Direction = value;
   215                 this.RaisePropertyChangedEvent(nameof(this.Direction));
   216             }
   217         }
   218 
   219         /// <summary>
   220         /// Gets or sets the force unencrypted status.
   221         ///          MailItem : Corresponds to UserProperty USER_PROPERTY_KEY_FORCE_UNENCRYPTED
   222         /// CryptableMailItem : Corresponds to property 'ForceUnencryptedBool'
   223         ///      text_message : not supported
   224         /// </summary>
   225         public bool ForceUnencrypted
   226         {
   227             get { return (this._ForceUnencrypted); }
   228             set
   229             {
   230                 this._ForceUnencrypted = value;
   231                 this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
   232             }
   233         }
   234 
   235         /// <summary>
   236         /// Gets or sets the from identity of the message.
   237         /// Warning: this value can be null.
   238         ///          MailItem : not supported (calculated from various properties)
   239         /// CryptableMailItem : Corresponds to property 'From'
   240         ///      text_message : Corresponds to property 'from'
   241         /// </summary>
   242         public PEPIdentity From
   243         {
   244             get { return (this._From); }
   245             set
   246             {
   247                 this._From = value;
   248                 this.RaisePropertyChangedEvent(nameof(this.From));
   249             }
   250         }
   251 
   252         /// <summary>
   253         /// Gets or sets the ID of the message.
   254         /// Warning: this value can be null.
   255         ///          MailItem : Corresponds to MAPI property PidTagInternetMessageId
   256         /// CryptableMailItem : not supported
   257         ///      text_message : Corresponds to property 'id'
   258         /// </summary>
   259         public string ID
   260         {
   261             get { return (this._ID); }
   262             set
   263             {
   264                 this._ID = value;
   265                 this.RaisePropertyChangedEvent(nameof(this.ID));
   266             }
   267         }
   268 
   269         /// <summary>
   270         /// Gets whether this message is encrypted.
   271         /// This will forward the call to the static method of the same purpose.
   272         /// </summary>
   273         public bool IsEncrypted
   274         {
   275             get { return (PEPMessage.GetIsEncrypted(this)); }
   276         }
   277 
   278         /// <summary>
   279         /// Gets whether this message is secured using PGP/MIME format.
   280         /// This will forward the call to the static method of the same purpose.
   281         /// </summary>
   282         public bool IsPGPMIMEEncrypted
   283         {
   284             get { return (PEPMessage.GetIsPGPMIMEEncrypted(this)); }
   285         }
   286 
   287         /// <summary>
   288         /// Gets or sets the list of keys associated with this message.
   289         /// Commonly this contains the list of decryption keys.
   290         /// Warning: Since this is stored as a header field, care must be taken not to apply this to a MailItem on an 
   291         /// untrusted server or on an outgoing message.
   292         ///          MailItem : Corresponds to MAPI Property PR_X_KEY_LIST (Header field)
   293         /// CryptableMailItem : not supported
   294         ///      text_message : Corresponds with the key PR_X_KEY_LIST_NAME within 'opt_fields' array. 
   295         ///                     This is also an out parameter of the decrypt function.
   296         /// </summary>
   297         public string KeyList
   298         {
   299             get { return (this._KeyList); }
   300             set
   301             {
   302                 this._KeyList = value;
   303                 this.RaisePropertyChangedEvent(nameof(this.KeyList));
   304             }
   305         }
   306 
   307         /// <summary>
   308         /// Gets the list of keywords associated with the message.
   309         ///          MailItem : Corresponds to property 'Categories'
   310         /// CryptableMailItem : not supported
   311         ///      text_message : Corresponds to property 'keywords'
   312         /// </summary>
   313         public List<string> Keywords
   314         {
   315             get { return (this._Keywords); }
   316         }
   317 
   318         /// <summary>
   319         /// Gets or sets the plain text long-form (body) of the message.
   320         /// Warning: this value can be null.
   321         ///          MailItem : Corresponds to property 'Body' (also BodyFormat)
   322         /// CryptableMailItem : not exposed (processed internally)
   323         ///      text_message : Corresponds to property 'longmsg'
   324         /// </summary>
   325         public string LongMsg
   326         {
   327             get { return (this._LongMsg); }
   328             set
   329             {
   330                 this._LongMsg = value;
   331                 this.RaisePropertyChangedEvent(nameof(this.LongMsg));
   332             }
   333         }
   334 
   335         /// <summary>
   336         /// Gets or sets the HTML formatted long-form (body) of the message.
   337         /// Warning: this value can be null.
   338         ///          MailItem : Corresponds to property 'HTMLBody' (also BodyFormat)
   339         /// CryptableMailItem : not exposed (processed internally)
   340         ///      text_message : Corresponds to property 'longmsg_formatted'
   341         /// </summary>
   342         public string LongMsgFormattedHTML
   343         {
   344             get { return (this._LongMsgFormattedHTML); }
   345             set
   346             {
   347                 this._LongMsgFormattedHTML = value;
   348                 this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedHTML));
   349             }
   350         }
   351 
   352         /// <summary>
   353         /// Gets or sets the RTF formatted long-form (body) of the message.
   354         /// Warning: this value can be null, it is only supported when creating from a MailItem. It is
   355         /// not applied to a MailItem during .ApplyTo as it's unsupported by the engine.
   356         ///          MailItem : Corresponds to property 'RTFBody' (also BodyFormat)
   357         /// CryptableMailItem : not exposed (processed internally)
   358         ///      text_message : not supported
   359         /// </summary>
   360         public string LongMsgFormattedRTF
   361         {
   362             get { return (this._LongMsgFormattedRTF); }
   363             set
   364             {
   365                 this._LongMsgFormattedRTF = value;
   366                 this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedRTF));
   367             }
   368         }
   369 
   370         /// <summary>
   371         /// Gets or sets whether this message is marked to never be unsecure.
   372         /// Note: While this is stored as a header field, it is OK to apply to any MailItem and outgoing messages.
   373         /// It's intended function is to flag a message to keep it encrypted.
   374         ///          MailItem : Corresponds to MAPI Property PR_X_PEP_NEVER_UNSECURE (Header field)
   375         /// CryptableMailItem : Corresponds to property 'NeverUnsecure'
   376         ///      text_message : not supported
   377         /// </summary>
   378         public bool NeverUnsecure
   379         {
   380             get { return (this._NeverUnsecure); }
   381             set
   382             {
   383                 this._NeverUnsecure = value;
   384                 this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
   385             }
   386         }
   387 
   388         /// <summary>
   389         /// Gets or sets the pEp protocol (or 'format') version of this message.
   390         /// This is not to be confused with pEp engine version which can be different.
   391         /// This is commonly set after decryption.
   392         /// Note: While this is stored as a header field, it is OK to apply to any MailItem and outgoing messages.
   393         /// It's intended function is just to show the version of pEp last used to process the message.
   394         ///          MailItem : Corresponds to MAPI Property PR_X_PEP_PROTOCOL_VERSION (Header field)
   395         /// CryptableMailItem : not supported
   396         ///      text_message : Corresponds with the key PR_X_PEP_PROTOCOL_VERSION_NAME within 'opt_fields' array. 
   397         /// </summary>
   398         public string PEPProtocolVersion
   399         {
   400             get { return (this._PEPProtocolVersion); }
   401             set
   402             {
   403                 this._PEPProtocolVersion = value;
   404                 this.RaisePropertyChangedEvent(nameof(this.PEPProtocolVersion));
   405             }
   406         }
   407 
   408         /// <summary>
   409         /// Gets or sets the date and time when the message was received.
   410         ///          MailItem : Corresponds to property 'ReceivedTime'
   411         /// CryptableMailItem : not supported
   412         ///      text_message : Corresponds to property 'recv'
   413         /// </summary>
   414         public DateTime? ReceivedOn
   415         {
   416             get { return (this._ReceivedOn); }
   417             set
   418             {
   419                 this._ReceivedOn = value;
   420                 this.RaisePropertyChangedEvent(nameof(this.ReceivedOn));
   421             }
   422         }
   423 
   424         /// <summary>
   425         /// Gets or sets the date and time when the message was sent.
   426         ///          MailItem : Corresponds to property 'SentOn'
   427         /// CryptableMailItem : not supported
   428         ///      text_message : Corresponds to property 'sent'
   429         /// </summary>
   430         public DateTime? SentOn
   431         {
   432             get { return (this._SentOn); }
   433             set
   434             {
   435                 this._SentOn = value;
   436                 this.RaisePropertyChangedEvent(nameof(this.SentOn));
   437             }
   438         }
   439 
   440         /// <summary>
   441         /// Gets or sets the short-form (subject) of the message.
   442         /// Warning: this value can be null.
   443         ///          MailItem : Corresponds to property 'Subject'
   444         /// CryptableMailItem : not supported
   445         ///      text_message : Corresponds to property 'shortmsg'
   446         /// </summary>
   447         public string ShortMsg
   448         {
   449             get { return (this._ShortMsg); }
   450             set
   451             {
   452                 this._ShortMsg = value;
   453                 this.RaisePropertyChangedEvent(nameof(this.ShortMsg));
   454             }
   455         }
   456 
   457         /// <summary>
   458         /// Gets the list of identities to receive the message.
   459         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
   460         /// CryptableMailItem : not exposed directly (part of Recipients)
   461         ///      text_message : Corresponds to property 'to'
   462         /// </summary>
   463         public List<PEPIdentity> To
   464         {
   465             get { return (this._To); }
   466         }
   467 
   468         /// <summary>
   469         /// Gets the total number of all recipients in the message (BCC, CC &amp; To).
   470         /// </summary>
   471         public int RecipientCount
   472         {
   473             get { return (this._BCC.Count + this._CC.Count + this._To.Count); }
   474         }
   475 
   476         /// <summary>
   477         /// Gets a list of all recipients in the message (BCC, CC &amp; To).
   478         /// Each recipient in the returned list is a copy.
   479         /// </summary>
   480         public PEPIdentity[] Recipients
   481         {
   482             get
   483             {
   484                 List<PEPIdentity> recipients = new List<PEPIdentity>();
   485 
   486                 // BCC
   487                 for (int i = 0; i < this._BCC.Count; i++)
   488                 {
   489                     recipients.Add(this._BCC[i].Copy());
   490                 }
   491 
   492                 // CC
   493                 for (int i = 0; i < this._CC.Count; i++)
   494                 {
   495                     recipients.Add(this._CC[i].Copy());
   496                 }
   497 
   498                 // To
   499                 for (int i = 0; i < this._To.Count; i++)
   500                 {
   501                     recipients.Add(this._To[i].Copy());
   502                 }
   503 
   504                 return (recipients.ToArray());
   505             }
   506         }
   507 
   508         #endregion
   509 
   510         /**************************************************************
   511          * 
   512          * Methods
   513          * 
   514          *************************************************************/
   515 
   516         /// <summary>
   517         /// Raises the property changed event, if possible, with the given arguments.
   518         /// </summary>
   519         /// <param name="propertyName">The name of the property that changed.</param>
   520         private void RaisePropertyChangedEvent(string propertyName)
   521         {
   522             this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   523             return;
   524         }
   525 
   526         /// <summary>
   527         /// Returns this pEp message as a new pEp engine text_message struct.
   528         /// Warning: Any identity members (groups) are lost, call FlattenAllRecipientIdentities() before this method.
   529         /// </summary>
   530         /// <param name="alwaysAddOptFields">Whether to always add optional fields (default is false). When false, 
   531         /// optional fields will not be added if they are undefined/null/empty in the PEPMessage. This is to prevent 
   532         /// against adding optional fields when they have never been set.
   533         /// </param>
   534         /// <returns>A pEp engine identity.</returns>
   535         public text_message ToCOMType(bool alwaysAddOptFields = false)
   536         {
   537             long recvSec;
   538             long sentSec;
   539             TimeSpan span;
   540             List<blob> attachments = new List<blob>();
   541             List<pEp_identity_s> bcc = new List<pEp_identity_s>();
   542             List<pEp_identity_s> cc = new List<pEp_identity_s>();
   543             List<pEp_identity_s> to = new List<pEp_identity_s>();
   544             List<opt_field> optionalFields = new List<opt_field>();
   545             opt_field field;
   546             text_message result = new text_message();
   547 
   548             // Convert attachments
   549             for (int i = 0; i < this._Attachments.Count; i++)
   550             {
   551                 attachments.Add(this._Attachments[i].ToCOMType());
   552             }
   553 
   554             // Convert BCC
   555             for (int i = 0; i < this._BCC.Count; i++)
   556             {
   557                 bcc.Add(this._BCC[i].ToCOMType());
   558             }
   559 
   560             // Convert CC
   561             for (int i = 0; i < this._CC.Count; i++)
   562             {
   563                 cc.Add(this._CC[i].ToCOMType());
   564             }
   565 
   566             // Convert To
   567             for (int i = 0; i < this._To.Count; i++)
   568             {
   569                 to.Add(this._To[i].ToCOMType());
   570             }
   571 
   572             // Create optional fields
   573             if ((alwaysAddOptFields) ||
   574                 (this._Rating != _pEp_color.pEp_rating_undefined))
   575             {
   576                 field = new opt_field();
   577                 field.name = PEPMessage.PR_X_ENC_STATUS_NAME;
   578                 field.value = this._Rating.ToString().Remove(0, 11); // Remove the 'pEp_rating_' text
   579                 optionalFields.Add(field);
   580             }
   581 
   582             if ((alwaysAddOptFields) ||
   583                 (string.IsNullOrEmpty(this._KeyList) == false))
   584             {
   585                 field = new opt_field();
   586                 field.name = PEPMessage.PR_X_KEY_LIST_NAME;
   587                 field.value = this._KeyList;
   588                 optionalFields.Add(field);
   589             }
   590 
   591             if ((alwaysAddOptFields) ||
   592                 (this._NeverUnsecure == true))
   593             {
   594                 field = new opt_field();
   595                 field.name = PEPMessage.PR_X_PEP_NEVER_UNSECURE_NAME;
   596                 field.value = "1";
   597                 optionalFields.Add(field);
   598             }
   599 
   600             if ((alwaysAddOptFields) ||
   601                 (string.IsNullOrEmpty(this._PEPProtocolVersion) == false))
   602             {
   603                 field = new opt_field();
   604                 field.name = PEPMessage.PR_X_PEP_PROTOCOL_VERSION_NAME;
   605                 field.value = this._PEPProtocolVersion;
   606                 optionalFields.Add(field);
   607             }
   608 
   609             // ReceivedOn
   610             recvSec = -1;
   611             if (this._ReceivedOn != null)
   612             {
   613                 span = (DateTime)this._ReceivedOn - new DateTime(1970, 1, 1);
   614                 recvSec = (long)span.TotalSeconds;
   615             }
   616 
   617             // SentOn
   618             sentSec = -1;
   619             if (this._SentOn != null)
   620             {
   621                 span = (DateTime)this._SentOn - new DateTime(1970, 1, 1);
   622                 sentSec = (long)span.TotalSeconds;
   623             }
   624 
   625             /* Note: Skip the following properties which are not supported in text_message
   626              *   • ConversationID
   627              *   • ConversationIndex
   628              *   • ConversationTopic
   629              *   • ForceUnencrypted
   630              *   • LongMsgFormattedRTF
   631              * 
   632              * Also not the following are handled as optional fields
   633              *   • Rating
   634              *   • NeverUnsecure
   635              *   • KeyList
   636              *   • PEPProtocolVersion
   637              * 
   638              * This also skips a number of text_message properties currently unsupported in PEPMessage.
   639              */
   640             result.attachments = attachments.ToArray();
   641             result.bcc = bcc.ToArray();
   642             result.cc = cc.ToArray();
   643             result.dir = this._Direction;
   644             result.from = (this._From != null ? this._From.ToCOMType() : result.from);
   645             result.id = this._ID;
   646             result.keywords = this._Keywords.ToArray();
   647             result.longmsg = this._LongMsg;
   648             result.longmsg_formatted = this._LongMsgFormattedHTML;
   649             result.opt_fields = optionalFields.ToArray();
   650             result.recv = ((recvSec >= 0) ? recvSec : result.recv);
   651             result.sent = ((sentSec >= 0) ? sentSec : result.sent);
   652             result.shortmsg = this._ShortMsg;
   653             result.to = to.ToArray();
   654 
   655             return (result);
   656         }
   657 
   658         /// <summary>
   659         /// Serves as a hash function for a particular type.
   660         /// </summary>
   661         /// <returns>A hash code for the current object.</returns>
   662         public override int GetHashCode()
   663         {
   664             return base.GetHashCode();
   665         }
   666 
   667         /// <summary>
   668         /// Indicates whether the current object is equal to another object of the same type.
   669         /// </summary>
   670         /// <param name="obj">The object to check equality with.</param>
   671         /// <returns>True if both objects are considered equal, otherwise false.</returns>
   672         public override bool Equals(object obj)
   673         {
   674             if ((obj == null) ||
   675                 !(obj is PEPMessage))
   676             {
   677                 return (false);
   678             }
   679 
   680             return (this.Equals((PEPMessage)obj));
   681         }
   682 
   683         /// <summary>
   684         /// Indicates whether the current object is equal to another object of the same type.
   685         /// </summary>
   686         /// <param name="obj">The object to check equality with.</param>
   687         /// <returns>True if both objects are considered equal, otherwise false.</returns>
   688         public bool Equals(PEPMessage obj)
   689         {
   690             if (obj == null)
   691             {
   692                 return (false);
   693             }
   694 
   695             if (Comparisons.Equals(this.Attachments, obj.Attachments) &&
   696                 Comparisons.Equals(this.BCC, obj.BCC) &&
   697                 Comparisons.Equals(this.CC, obj.CC) &&
   698                 Comparisons.Equals(this.Rating, obj.Rating) &&
   699                 Comparisons.Equals(this.ConversationID, obj.ConversationID) &&
   700                 Comparisons.Equals(this.ConversationIndex, obj.ConversationIndex) &&
   701                 Comparisons.Equals(this.ConversationTopic, obj.ConversationTopic) &&
   702                 Comparisons.Equals(this.Direction, obj.Direction) &&
   703                 Comparisons.Equals(this.ForceUnencrypted, obj.ForceUnencrypted) &&
   704                 Comparisons.Equals(this.From, obj.From) &&
   705                 Comparisons.Equals(this.ID, obj.ID) &&
   706                 Comparisons.Equals(this.KeyList, obj.KeyList) &&
   707                 Comparisons.Equals(this.Keywords, obj.Keywords) &&
   708                 Comparisons.Equals(this.LongMsg, obj.LongMsg) &&
   709                 Comparisons.Equals(this.LongMsgFormattedHTML, obj.LongMsgFormattedHTML) &&
   710                 Comparisons.Equals(this.LongMsgFormattedRTF, obj.LongMsgFormattedRTF) &&
   711                 Comparisons.Equals(this.NeverUnsecure, obj.NeverUnsecure) &&
   712                 Comparisons.Equals(this.PEPProtocolVersion, obj.PEPProtocolVersion) &&
   713                 Comparisons.Equals(this.ReceivedOn, obj.ReceivedOn) &&
   714                 Comparisons.Equals(this.SentOn, obj.SentOn) &&
   715                 Comparisons.Equals(this.ShortMsg, obj.ShortMsg) &&
   716                 Comparisons.Equals(this.To, obj.To))
   717             {
   718                 return (true);
   719             }
   720 
   721             return (false);
   722         }
   723 
   724         /// <summary>
   725         /// Gets a deep copy of the object and all its data.
   726         /// </summary>
   727         /// <returns>The deep copy of the object.</returns>
   728         public PEPMessage Copy()
   729         {
   730             return (this.Copy(false));
   731         }
   732 
   733         /// <summary>
   734         /// Gets a copy of the PEPMessage with or without data.
   735         /// </summary>
   736         /// <param name="createWithoutContent">Whether to include content such as text body, attachments 
   737         /// and optional properties.</param>
   738         /// <returns>The copy of the PEPMessage.</returns>
   739         public PEPMessage Copy(bool createWithoutContent = false)
   740         {
   741             PEPMessage copy = new PEPMessage();
   742 
   743             // Attachments
   744             copy.Attachments.Clear();
   745             if (createWithoutContent == false)
   746             {
   747                 for (int i = 0; i < this._Attachments.Count; i++)
   748                 {
   749                     copy.Attachments.Add(this._Attachments[i].Copy());
   750                 }
   751             }
   752 
   753             // BCC
   754             copy.BCC.Clear();
   755             for (int i = 0; i < this._BCC.Count; i++)
   756             {
   757                 copy.BCC.Add(this._BCC[i].Copy());
   758             }
   759 
   760             // CC
   761             copy.CC.Clear();
   762             for (int i = 0; i < this._CC.Count; i++)
   763             {
   764                 copy.CC.Add(this._CC[i].Copy());
   765             }
   766 
   767             copy.Rating = this._Rating;
   768             copy.ConversationID = (this._ConversationID == null ? null : string.Copy(this._ConversationID));
   769             copy.ConversationIndex = (this._ConversationIndex == null ? null : string.Copy(this._ConversationIndex));
   770             copy.ConversationTopic = (this._ConversationTopic == null ? null : string.Copy(this._ConversationTopic));
   771             copy.Direction = this._Direction;
   772             copy.ForceUnencrypted = this._ForceUnencrypted;
   773             copy.From = (this._From == null ? null : this._From.Copy());
   774             copy.ID = (this._ID == null ? null : string.Copy(this._ID));
   775             copy.KeyList = (this._KeyList == null ? null : string.Copy(this._KeyList));
   776 
   777             // Keywords
   778             copy.Keywords.Clear();
   779             for (int i = 0; i < this._Keywords.Count; i++)
   780             {
   781                 copy.Keywords.Add(this._Keywords[i]);
   782             }
   783 
   784             // Body
   785             if (createWithoutContent == false)
   786             {
   787                 copy.LongMsg = (this._LongMsg == null ? null : string.Copy(this._LongMsg));
   788                 copy.LongMsgFormattedHTML = (this._LongMsgFormattedHTML == null ? null : string.Copy(this._LongMsgFormattedHTML));
   789                 copy.LongMsgFormattedRTF = (this._LongMsgFormattedRTF == null ? null : string.Copy(this._LongMsgFormattedRTF));
   790             }
   791             else
   792             {
   793                 copy.LongMsg = null;
   794                 copy.LongMsgFormattedHTML = null;
   795                 copy.LongMsgFormattedRTF = null;
   796             }
   797 
   798             copy.NeverUnsecure = this._NeverUnsecure;
   799             copy.PEPProtocolVersion = (this._PEPProtocolVersion == null ? null : string.Copy(this._PEPProtocolVersion));
   800 
   801             // ReceivedOn
   802             if (this._ReceivedOn != null)
   803             {
   804                 copy.ReceivedOn = new DateTime(((DateTime)this._ReceivedOn).Ticks);
   805             }
   806             else
   807             {
   808                 copy.ReceivedOn = null;
   809             }
   810 
   811             // SentOn
   812             if (this._SentOn != null)
   813             {
   814                 copy.SentOn = new DateTime(((DateTime)this._SentOn).Ticks);
   815             }
   816             else
   817             {
   818                 copy.SentOn = null;
   819             }
   820 
   821             copy.ShortMsg = (this._ShortMsg == null ? null : string.Copy(this._ShortMsg));
   822 
   823             // To
   824             copy.To.Clear();
   825             for (int i = 0; i < this._To.Count; i++)
   826             {
   827                 copy.To.Add(this._To[i].Copy());
   828             }
   829 
   830             return (copy);
   831         }
   832 
   833         /// <summary>
   834         /// Resets the object to its default state/values.
   835         /// </summary>
   836         public void Reset()
   837         {
   838             this._Attachments = new List<PEPAttachment>();
   839             this._BCC = new List<PEPIdentity>();
   840             this._CC = new List<PEPIdentity>();
   841             this._Rating = _pEp_color.pEp_rating_undefined;
   842             this._ConversationID = null;
   843             this._ConversationIndex = null;
   844             this._ConversationTopic = null;
   845             this._Direction = _pEp_msg_direction.pEp_dir_incoming;
   846             this._ForceUnencrypted = false;
   847             this._From = null;
   848             this._ID = null;
   849             this._KeyList = null;
   850             this._Keywords = new List<string>();
   851             this._LongMsg = null;
   852             this._LongMsgFormattedHTML = null;
   853             this._LongMsgFormattedRTF = null;
   854             this._NeverUnsecure = false;
   855             this._PEPProtocolVersion = null;
   856             this._ReceivedOn = null;
   857             this._SentOn = null;
   858             this._ShortMsg = null;
   859             this._To = new List<PEPIdentity>();
   860 
   861             this.RaisePropertyChangedEvent(nameof(this.Attachments));
   862             this.RaisePropertyChangedEvent(nameof(this.BCC));
   863             this.RaisePropertyChangedEvent(nameof(this.CC));
   864             this.RaisePropertyChangedEvent(nameof(this.Rating));
   865             this.RaisePropertyChangedEvent(nameof(this.ConversationID));
   866             this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
   867             this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
   868             this.RaisePropertyChangedEvent(nameof(this.Direction));
   869             this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
   870             this.RaisePropertyChangedEvent(nameof(this.From));
   871             this.RaisePropertyChangedEvent(nameof(this.ID));
   872             this.RaisePropertyChangedEvent(nameof(this.KeyList));
   873             this.RaisePropertyChangedEvent(nameof(this.Keywords));
   874             this.RaisePropertyChangedEvent(nameof(this.LongMsg));
   875             this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedHTML));
   876             this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedRTF));
   877             this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
   878             this.RaisePropertyChangedEvent(nameof(this.PEPProtocolVersion));
   879             this.RaisePropertyChangedEvent(nameof(this.ReceivedOn));
   880             this.RaisePropertyChangedEvent(nameof(this.SentOn));
   881             this.RaisePropertyChangedEvent(nameof(this.ShortMsg));
   882             this.RaisePropertyChangedEvent(nameof(this.To));
   883 
   884             return;
   885         }
   886 
   887         /// <summary>
   888         /// Copies main properties that are unsupported by the engine from the given message into this message.
   889         /// This is commonly used to restore information that would otherwise be lost in conversions to/from 
   890         /// text_message during engine processing.
   891         /// Key properties that should be set separately for encryption concerns (such as LongMsgFormattedRTF) are ignored.
   892         /// </summary>
   893         /// <param name="msg">The message to copy over properties from.</param>
   894         public void SetNonEnginePropertiesFrom(PEPMessage msg)
   895         {
   896             /* Include the following properties:
   897              *   • ConversationID
   898              *   • ConversationIndex
   899              *   • ConversationTopic
   900              *   • ForceUnencrypted
   901              *   • NeverUnsecure
   902              * 
   903              * Also note:
   904              *              Rating: This is handled by the decrypt function of the engine only. It should not be
   905              *                      added back separately so is skipped.
   906              *             KeyList: This is handled by the decrypt function of the engine only. It should not be
   907              *                      added back separately so is skipped.
   908              * LongMsgFormattedRTF: This is completely unsupported by the engine but otherwise should be encrypted.
   909              *                      Due to this, it's completely skipped.
   910              *  PEPProtocolVersion: This is handled in both the decrypt and encrypt functions of the engine.
   911              *                      It should almost always be set within the text_message there isn't needed to add back.
   912              */
   913 
   914             if (msg != null)
   915             {
   916                 this._ConversationID = msg.ConversationID;
   917                 this._ConversationIndex = msg.ConversationIndex;
   918                 this._ConversationTopic = msg.ConversationTopic;
   919                 this._ForceUnencrypted = msg.ForceUnencrypted;
   920                 this._NeverUnsecure = msg.NeverUnsecure;
   921 
   922                 this.RaisePropertyChangedEvent(nameof(this.ConversationID));
   923                 this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
   924                 this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
   925                 this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
   926                 this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
   927             }
   928 
   929             return;
   930         }
   931 
   932         /// <summary>
   933         /// Applies this pEp message's data to the given Outlook item.
   934         /// </summary>
   935         /// <param name="omi">The Outlook mail item to apply this pEp message's data to.</param>
   936         /// <param name="setInternalHeaderFields">Whether to set internal header fields (Stored as MAPI properites) such as 
   937         /// Rating and KeyList. Warning: Never apply these to outgoing messages -- used only for mirrors or internal MailItems.
   938         /// Note that some header fields will be applied regardless such as NeverUnsecure or PEPProtocolVersion. 
   939         /// That is because these are not considered internal only and don't contain sensitive information.</param>
   940         /// /// <param name="setSender">Whether to set the sender manually. This is only needed when creating a sent message. 
   941         /// Important: Setting this to true for outgoing messages can cause problems with Exchange accounts.</param>
   942         /// <returns>The status of the method.</returns>
   943         public Globals.ReturnStatus ApplyTo(Outlook.MailItem omi,
   944                                             bool setInternalHeaderFields = false,
   945                                             bool setSender = false)
   946         {
   947             byte[] bytes;
   948             Outlook.Attachments attachments = null;
   949             Outlook.Recipient newRecipient = null;
   950             Outlook.Recipients recipients = null;
   951             Outlook.Account currAccount = null;
   952             Outlook.Account sendUsingAccount = null;
   953             Outlook.Accounts accounts = null;
   954             Outlook.Recipient fromRecipient = null;
   955             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
   956             bool fromRecipientRemoved = false;
   957             bool isPGPMIMEMsg = this.IsPGPMIMEEncrypted;
   958 
   959             try
   960             {
   961                 // Remove all existing recipients
   962                 recipients = omi.Recipients;
   963                 while (recipients.Count > 0)
   964                 {
   965                     recipients.Remove(1);
   966                 }
   967 
   968                 // Set recipients
   969                 for (int i = 0; i < this._BCC.Count; i++)
   970                 {
   971                     if (string.IsNullOrWhiteSpace(this._BCC[i].Address) == false)
   972                     {
   973                         // Add by address
   974                         newRecipient = recipients.Add(this._BCC[i].Address);
   975                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olBCC;
   976 
   977                         Marshal.ReleaseComObject(newRecipient);
   978                         newRecipient = null;
   979                     }
   980                     else if (string.IsNullOrWhiteSpace(this._BCC[i].Username) == false)
   981                     {
   982                         // Add by username (required for distribution lists)
   983                         newRecipient = recipients.Add(this._BCC[i].Username);
   984                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olBCC;
   985 
   986                         Marshal.ReleaseComObject(newRecipient);
   987                         newRecipient = null;
   988                     }
   989                 }
   990 
   991                 for (int i = 0; i < this._CC.Count; i++)
   992                 {
   993                     if (string.IsNullOrWhiteSpace(this._CC[i].Address) == false)
   994                     {
   995                         // Add by address
   996                         newRecipient = recipients.Add(this._CC[i].Address);
   997                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olCC;
   998 
   999                         Marshal.ReleaseComObject(newRecipient);
  1000                         newRecipient = null;
  1001                     }
  1002                     else if (string.IsNullOrWhiteSpace(this._CC[i].Username) == false)
  1003                     {
  1004                         // Add by username (required for distribution lists)
  1005                         newRecipient = recipients.Add(this._CC[i].Username);
  1006                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olCC;
  1007 
  1008                         Marshal.ReleaseComObject(newRecipient);
  1009                         newRecipient = null;
  1010                     }
  1011                 }
  1012 
  1013                 for (int i = 0; i < this._To.Count; i++)
  1014                 {
  1015                     if (string.IsNullOrWhiteSpace(this._To[i].Address) == false)
  1016                     {
  1017                         // Add by address
  1018                         newRecipient = recipients.Add(this._To[i].Address);
  1019                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1020 
  1021                         Marshal.ReleaseComObject(newRecipient);
  1022                         newRecipient = null;
  1023                     }
  1024                     else if (string.IsNullOrWhiteSpace(this._To[i].Username) == false)
  1025                     {
  1026                         // Add by username (required for distribution lists)
  1027                         newRecipient = recipients.Add(this._To[i].Username);
  1028                         newRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1029 
  1030                         Marshal.ReleaseComObject(newRecipient);
  1031                         newRecipient = null;
  1032                     }
  1033                 }
  1034 
  1035                 /* Add the pEp From identity as its own recipient. This will be removed later.
  1036                  * However, here it simplifies getting the Sender/From AddressEntry, which will be set later.
  1037                  * 
  1038                  * Note: Outlook will not allow the Sender to be set for received mail items.
  1039                  * If you try to set the sender in this situation, it will always throw an error.
  1040                  * To mitigate this, the Sender should only be set to the MailItem if one is not already existing that already
  1041                  * matches the Sender we would otherwise try to set. This is considered good enough as determining
  1042                  * if a mail item is received is not completely reliable.
  1043                  */
  1044                 if (setSender)
  1045                 {
  1046                     if ((this._From != null) &&
  1047                         ((omi.Sender == null) ||
  1048                          ((omi.Sender != null) &&
  1049                           (this._From != null) &&
  1050                           (this._From.EqualsByAddress(omi.Sender.Address) == false))))
  1051                     {
  1052                         if (string.IsNullOrWhiteSpace(this._From.Address) == false)
  1053                         {
  1054                             // Add by address
  1055                             fromRecipient = recipients.Add(this._From.Address);
  1056                             fromRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1057                         }
  1058                         else if (string.IsNullOrWhiteSpace(this._From.Username) == false)
  1059                         {
  1060                             // Add by username (required for distribution lists)
  1061                             fromRecipient = recipients.Add(this._From.Username);
  1062                             fromRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1063                         }
  1064                     }
  1065 
  1066                     try
  1067                     {
  1068                         recipients.ResolveAll();
  1069                     }
  1070                     catch { }
  1071 
  1072                     /* Set sender account
  1073                      * Note that if fails, will be empty which eventually will use the default send account
  1074                      * If the send using account is already populated, this cannot be re-set.
  1075                      * So far this doesn't appear to be an issue as it occurs when applying unencrypted data to a mirror.
  1076                      * However, the mirror SendUsingAccount is already correct at this point and doesn't need to be set.
  1077                      */
  1078                     sendUsingAccount = omi.SendUsingAccount;
  1079 
  1080                     if ((this._From != null) &&
  1081                         (this._From.Address != null) &&
  1082                         (sendUsingAccount == null))
  1083                     {
  1084                         accounts = Globals.ThisAddIn.Application.Session.Accounts;
  1085 
  1086                         // Note: Index starts at 1
  1087                         for (int i = 1; i <= accounts.Count; i++)
  1088                         {
  1089                             currAccount = accounts[i];
  1090 
  1091                             if ((currAccount.SmtpAddress != null) &&
  1092                                 (currAccount.SmtpAddress.ToUpper() == this._From.Address.ToUpper()))
  1093                             {
  1094                                 /* Try to set the SendUsingAccount
  1095                                  * This will fail if the mail item is already marked as sent or the SendUsingAccount is not null, etc...
  1096                                  * If it fails, Outlook will in the end just use the default account.
  1097                                  * This property should ideally be set before a mail item is saved.
  1098                                  */
  1099                                 try
  1100                                 {
  1101                                     omi.SendUsingAccount = currAccount;
  1102                                 }
  1103                                 catch { }
  1104 
  1105                                 Marshal.ReleaseComObject(currAccount);
  1106                                 currAccount = null;
  1107 
  1108                                 break;
  1109                             }
  1110 
  1111                             Marshal.ReleaseComObject(currAccount);
  1112                             currAccount = null;
  1113                         }
  1114                     }
  1115 
  1116                     /* Set Sender
  1117                      * Outlook takes the Sender property to display the respective name in the Sent folder.
  1118                      * This Sender property is based on the name, the email address and the EntryID MAPI properties. As the EntryID can't be accessed
  1119                      * directly with MAPI methods, we have to set these properties indirectly through the Sender.
  1120                      */
  1121                     if (fromRecipient != null)
  1122                     {
  1123                         try
  1124                         {
  1125                             omi.Sender = fromRecipient.AddressEntry;
  1126                         }
  1127                         catch
  1128                         {
  1129                             Log.Warning("PEPMessage.ApplyTo: Failed to set the Sender.");
  1130                         }
  1131 
  1132                         // Remove the From recipient from the Recipients collection by searching for a match by EntryID
  1133                         for (int i = 1; i <= recipients.Count; i++)
  1134                         {
  1135                             newRecipient = recipients[i];
  1136                             if (newRecipient != null)
  1137                             {
  1138                                 if ((string.IsNullOrEmpty(newRecipient.EntryID) == false) &&
  1139                                     (string.IsNullOrEmpty(fromRecipient.EntryID) == false) &&
  1140                                     (newRecipient.EntryID == fromRecipient.EntryID))
  1141                                 {
  1142                                     try
  1143                                     {
  1144                                         recipients.Remove(i);
  1145                                         fromRecipientRemoved = true;
  1146                                     }
  1147                                     catch
  1148                                     {
  1149                                         Log.Warning("PEPMessage.ApplyTo: Error removing 'From' recipient from Recipients collection.");
  1150                                     }
  1151                                     break;
  1152                                 }
  1153 
  1154                                 Marshal.ReleaseComObject(newRecipient);
  1155                                 newRecipient = null;
  1156                             }
  1157                         }
  1158 
  1159                         // Fallback solution in case there is no EntryID match
  1160                         if (fromRecipientRemoved == false)
  1161                         {
  1162                             try
  1163                             {
  1164                                 recipients.Remove(recipients.Count);
  1165                                 Log.Warning("PEPMessage.ApplyTo: No EntryID match for 'From' recipient in Recipients collection. Removed using fallback solution.");
  1166                             }
  1167                             catch
  1168                             {
  1169                                 Log.Error("PEPMessage.ApplyTo: No EntryID match for 'From' recipient in Recipients collection. Fallback solution also failed.");
  1170                             }
  1171                         }
  1172 
  1173                         // Set Sender's name and email properties via MAPIProperty (won't be created automatically when assigníng Sender)
  1174                         try
  1175                         {
  1176                             MAPIHelper.SetProperty(omi, MAPIProperty.PidTagSenderName, this._From.Username);
  1177                             MAPIHelper.SetProperty(omi, MAPIProperty.PidTagSenderEmailAddress, this._From.Address);
  1178                         }
  1179                         catch (Exception ex)
  1180                         {
  1181                             Log.Warning("PEPMessage.ApplyTo: Unable to set sender's name or email address via MAPIProperty. " + ex.Message);
  1182                         }
  1183 
  1184                         Marshal.ReleaseComObject(fromRecipient);
  1185                         fromRecipient = null;
  1186                     }
  1187                 }
  1188                 else
  1189                 {
  1190                     try
  1191                     {
  1192                         recipients.ResolveAll();
  1193                     }
  1194                     catch { }
  1195                 }
  1196 
  1197                 /* Set the encoding to UTF-8
  1198                  * See: https://msdn.microsoft.com/en-us/library/office/ff860730.aspx
  1199                  * All PEPMessages should be UTF especially those coming from the engine.
  1200                  * 
  1201                  * It is important that the encoding is set BEFORE the body and other text of the 
  1202                  * mail item. This is necessary to avoid character encoding issues where
  1203                  * Outlook will not respect the UTF encoding of strings set to the body/subject/etc.
  1204                  * It will instead try to use the strings with the encoding of the codepage.
  1205                  */
  1206                 try
  1207                 {
  1208                     MAPIHelper.SetProperty(omi, MAPIProperty.PidTagInternetCodepage, 65001);
  1209                     MAPIHelper.SetProperty(omi, MAPIProperty.PidTagMessageCodepage, 65001);
  1210                 }
  1211                 catch
  1212                 {
  1213                     Log.Warning("PEPMessage.ApplyTo: Failed to set UTF-8 encoding.");
  1214                 }
  1215 
  1216                 // Set the subject
  1217                 omi.Subject = this._ShortMsg;
  1218 
  1219                 /* Set the body
  1220                  * PGP/MIME format must be handled specially.
  1221                  * In addition, RTF format is ignored. Only HTML is used the same as the pEp engine.
  1222                  */
  1223                 if (isPGPMIMEMsg)
  1224                 {
  1225                     /* Clear any old body content, these cannot exist for PGP/MIME encrypted messages.
  1226                      * Note that setting the RTFBody to null or an empty byte array throws an exception.
  1227                      * Therefore, it is sent to a single zero byte to be constant.
  1228                      * The RTFBody is ignored when sending the actual message.
  1229                      */
  1230                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatPlain;
  1231                     omi.Body = null;
  1232                     omi.HTMLBody = null;
  1233                     omi.RTFBody = new byte[] { 0x00 };
  1234 
  1235                     // Now, set the mail mime type to S/MIME multipart/signed, this is used later to add a special attachment
  1236                     try
  1237                     {
  1238                         MAPIHelper.SetProperty(omi, MAPIProperty.PidTagMessageClass, MAPIPropertyValue.PidTagMessageClassSMIMEMultipartSigned);
  1239                     }
  1240                     catch
  1241                     {
  1242                         status = Globals.ReturnStatus.Failure;
  1243                         throw new Exception("Failure to set message class for PGP/MIME message.");
  1244                     }
  1245                 }
  1246                 else if (string.IsNullOrWhiteSpace(this._LongMsgFormattedHTML))
  1247                 {
  1248                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatPlain;
  1249                     omi.Body = this._LongMsg;
  1250                 }
  1251                 else
  1252                 {
  1253                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
  1254                     omi.HTMLBody = this._LongMsgFormattedHTML;
  1255                 }
  1256 
  1257                 // Remove any previous attachments
  1258                 attachments = omi.Attachments;
  1259                 while (attachments.Count > 0)
  1260                 {
  1261                     attachments.Remove(1);
  1262                 }
  1263 
  1264                 if (isPGPMIMEMsg)
  1265                 {
  1266                     PEPAttachment pgpMIME = this.ConvertToPGPMIMEAttachment();
  1267 
  1268                     if (pgpMIME != null)
  1269                     {
  1270                         pgpMIME.AddTo(attachments);
  1271                     }
  1272                     else
  1273                     {
  1274                         throw new Exception("Failed to create and add PGP/MIME attachment.");
  1275                     }
  1276                 }
  1277                 else
  1278                 {
  1279                     for (int i = 0; i < this._Attachments.Count; i++)
  1280                     {
  1281                         this._Attachments[i].AddTo(attachments, ("attachment" + i.ToString()));
  1282                     }
  1283                 }
  1284 
  1285                 // ID
  1286                 try
  1287                 {
  1288                     MAPIHelper.SetProperty(omi, MAPIProperty.PidTagInternetMessageId, this._ID);
  1289                 }
  1290                 catch { }
  1291 
  1292                 // Conversation information
  1293                 try
  1294                 {
  1295                     /* Note: PidTagConversationId cannot be set even through the MAPI accessor.
  1296                      * This is by design since this property is computed automatically from other properties.
  1297                      * See: https://msdn.microsoft.com/en-us/library/ee204279.aspx
  1298                      */
  1299                     bytes = MAPIHelper.StringToPtypBinary(this._ConversationIndex);
  1300                     if ((bytes != null) &&
  1301                         (bytes.Length > 0))
  1302                     {
  1303                         MAPIHelper.SetProperty(omi, MAPIProperty.PidTagConversationIndex, bytes);
  1304                     }
  1305 
  1306                     MAPIHelper.SetProperty(omi, MAPIProperty.PidTagConversationIndexTracking, false);
  1307                     MAPIHelper.SetProperty(omi, MAPIProperty.PidTagConversationTopic, this._ConversationTopic);
  1308                 }
  1309                 catch (Exception ex1)
  1310                 {
  1311                     Log.Warning("PEPMessage.ApplyTo: Failure setting conversation information. " + ex1.ToString());
  1312                 }
  1313 
  1314                 /* Add internal properties
  1315                  * Note: there is no need to check for defaults as it's all handled within the SetInterpretedProperty method.
  1316                  * Also, .Save() must never be called here as the .ApplyTo() method can be used on MailItems that need to change properties before save.
  1317                  */
  1318                 if (setInternalHeaderFields)
  1319                 {
  1320                     // Rating, ignore return status
  1321                     CryptableMailItem.SetInterpretedProperty(omi,
  1322                                                              PEPMessage.XPidNameEncStatus.DASLName,
  1323                                                              this._Rating);
  1324 
  1325                     // KeyList, ignore return status
  1326                     CryptableMailItem.SetInterpretedProperty(omi,
  1327                                                              PEPMessage.XPidNameKeyList.DASLName,
  1328                                                              this._KeyList);
  1329                 }
  1330 
  1331                 // NeverUnsecure, ignore return status
  1332                 CryptableMailItem.SetInterpretedProperty(omi,
  1333                                                          PEPMessage.XPidNamePEPNeverUnsecure.DASLName,
  1334                                                          this._NeverUnsecure);
  1335 
  1336                 // PEPProtocolVersion, ignore return status
  1337                 CryptableMailItem.SetInterpretedProperty(omi,
  1338                                                          PEPMessage.XPidNamePEPProtocolVersion.DASLName,
  1339                                                          this._PEPProtocolVersion);
  1340             }
  1341             catch (Exception ex)
  1342             {
  1343                 status = Globals.ReturnStatus.Failure;
  1344                 Log.Error("PEPMessage.ApplyTo: Failure occured, " + ex.ToString());
  1345             }
  1346             finally
  1347             {
  1348                 if (attachments != null)
  1349                 {
  1350                     Marshal.ReleaseComObject(attachments);
  1351                     attachments = null;
  1352                 }
  1353 
  1354                 if (newRecipient != null)
  1355                 {
  1356                     Marshal.ReleaseComObject(newRecipient);
  1357                     newRecipient = null;
  1358                 }
  1359 
  1360                 if (recipients != null)
  1361                 {
  1362                     Marshal.ReleaseComObject(recipients);
  1363                     recipients = null;
  1364                 }
  1365 
  1366                 if (currAccount != null)
  1367                 {
  1368                     Marshal.ReleaseComObject(currAccount);
  1369                     currAccount = null;
  1370                 }
  1371 
  1372                 if (sendUsingAccount != null)
  1373                 {
  1374                     Marshal.ReleaseComObject(sendUsingAccount);
  1375                     sendUsingAccount = null;
  1376                 }
  1377 
  1378                 if (accounts != null)
  1379                 {
  1380                     Marshal.ReleaseComObject(accounts);
  1381                     accounts = null;
  1382                 }
  1383 
  1384                 if (fromRecipient != null)
  1385                 {
  1386                     Marshal.ReleaseComObject(fromRecipient);
  1387                     fromRecipient = null;
  1388                 }
  1389             }
  1390 
  1391             return (status);
  1392         }
  1393 
  1394         /// <summary>
  1395         /// Converts this message into a single PGP/MIME attachment.
  1396         /// This is necessary in order for Outlook and MAPI to correctly transport PGP/MIME messages.
  1397         /// The attachment will report itself as S/MIME to MAPI, S/MIME attachments are passed-through as unmodified text.
  1398         /// Since we can insert unmodified text, this allows us to build a correct PGP/MIME message from existing attachments.
  1399         /// Warning: This can return null if a failure occured.
  1400         /// </summary>
  1401         /// <returns>This message converted to a single PGP/MIME attachment, otherwise null.</returns>
  1402         private PEPAttachment ConvertToPGPMIMEAttachment()
  1403         {
  1404             string boundary;
  1405             byte[] bytes;
  1406             bool versionInfoFound = false;
  1407             bool contentFound = false;
  1408             PEPAttachment newAttachment = null;
  1409             List<byte> data = new List<byte>();
  1410 
  1411             if (this._Attachments.Count == 2)
  1412             {
  1413                 boundary = "----=_NextPart_" + Guid.NewGuid().ToString().Replace('-', '_');
  1414 
  1415                 /* See below for an example PGP/MIME formatted message.
  1416                  * this is from RFC 3156.  
  1417                  *    
  1418                  *  Content-Type: multipart/encrypted; boundary=foo;
  1419                  *     protocol="application/pgp-encrypted"
  1420                  * 
  1421                  *  --foo
  1422                  *  Content-Type: application/pgp-encrypted
  1423                  * 
  1424                  *  Version: 1
  1425                  * 
  1426                  *  --foo
  1427                  *  Content-Type: application/octet-stream
  1428                  * 
  1429                  *  -----BEGIN PGP MESSAGE-----
  1430                  *  Version: 2.6.2
  1431                  * 
  1432                  *  hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj
  1433                  *  eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ
  1434                  *  g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA
  1435                  *  AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3
  1436                  *  1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8=
  1437                  *  =zzaA
  1438                  *  -----END PGP MESSAGE-----
  1439                  * 
  1440                  *  --foo--
  1441                  */
  1442 
  1443                 // Add internet headers (these are not added by default for S/MIME messages which allows them to be customized)
  1444                 bytes = Encoding.UTF8.GetBytes("MIME-Version: 1.0" + Environment.NewLine +
  1445                                                "Content-Type: multipart/encrypted;" + Environment.NewLine +
  1446                                                "\tprotocol=\"application/pgp-encrypted\";" + Environment.NewLine +
  1447                                                "\tboundary=\"" + boundary + "\"" + Environment.NewLine);
  1448                 data.AddRange(bytes);
  1449 
  1450                 // Add the version identification attachment
  1451                 bytes = Encoding.UTF8.GetBytes(Environment.NewLine +
  1452                                                "--" + boundary + Environment.NewLine +
  1453                                                "Content-Type: application/pgp-encrypted" + Environment.NewLine +
  1454                                                "Content-Description: PGP/MIME version identification" + Environment.NewLine +
  1455                                                Environment.NewLine);
  1456                 data.AddRange(bytes);
  1457 
  1458                 foreach (PEPAttachment attach in this._Attachments)
  1459                 {
  1460                     if (PEPMessage.IsPGPMIMEVersionInfoFormat(attach))
  1461                     {
  1462                         data.AddRange(attach.Data);
  1463                         versionInfoFound = true;
  1464                         break;
  1465                     }
  1466                 }
  1467 
  1468                 // Add the content attachment
  1469                 bytes = Encoding.UTF8.GetBytes(Environment.NewLine +
  1470                                                Environment.NewLine +
  1471                                                "--" + boundary + Environment.NewLine +
  1472                                                "Content-Type: application/octet-stream;" + Environment.NewLine +
  1473                                                "\tname=\"msg.asc\"" + Environment.NewLine +
  1474                                                Environment.NewLine);
  1475                 data.AddRange(bytes);
  1476 
  1477                 foreach (PEPAttachment attach in this._Attachments)
  1478                 {
  1479                     if (PEPMessage.IsPGPMIMEContentFormat(attach))
  1480                     {
  1481                         data.AddRange(attach.Data);
  1482                         contentFound = true;
  1483                         break;
  1484                     }
  1485                 }
  1486 
  1487                 bytes = Encoding.UTF8.GetBytes(Environment.NewLine +
  1488                                                "--" + boundary + "--" + Environment.NewLine);
  1489                 data.AddRange(bytes);
  1490 
  1491                 // Create the new attachment
  1492                 if (versionInfoFound && contentFound)
  1493                 {
  1494                     newAttachment = new PEPAttachment()
  1495                     {
  1496                         Data = data.ToArray(),
  1497                         MIMEType = "multipart/signed",
  1498                         Tag = MAPIPropertyValue.PidTagAttachTagMIME
  1499                     };
  1500                 }
  1501             }
  1502 
  1503             return (newAttachment);
  1504         }
  1505 
  1506         /// <summary>
  1507         /// Recursivley converts all "BCC", "CC", and "To" identities into a 'flat' list of any members.
  1508         /// This will remove groups (hierarchy) and convert a group into it's members.
  1509         /// </summary>
  1510         public void FlattenAllRecipientIdentities()
  1511         {
  1512             this._BCC = PEPIdentity.ToFlatList(this._BCC);
  1513             this._CC = PEPIdentity.ToFlatList(this._CC);
  1514             this._To = PEPIdentity.ToFlatList(this._To);
  1515 
  1516             return;
  1517         }
  1518 
  1519         /// <summary>
  1520         /// Processes all content IDs in the image attachments as well as HTML body.
  1521         /// This will convert the CID's to the image filename only.
  1522         /// </summary>
  1523         public void NormalizeContentIDs()
  1524         {
  1525             int currImgTagIndex;
  1526             int currImgTagStopIndex;
  1527             int currImgTagCIDIndex;
  1528             int currImgTagCIDStopIndex;
  1529             int tempIndex;
  1530             string currCID;
  1531             string newCID;
  1532             string beginning;
  1533             string ending;
  1534             string currFileName;
  1535             string workingHTML;
  1536             Dictionary<string, string> modifiedCIDs = new Dictionary<string, string>();
  1537 
  1538             // Modify CIDs for all attachments and store modified CIDs in the dictionary
  1539             for (int i = 0; i < this._Attachments.Count; i++)
  1540             {
  1541                 currCID = this._Attachments[i].ContentID;
  1542                 currFileName = this._Attachments[i].FileName;
  1543 
  1544                 if (string.IsNullOrEmpty(currCID))
  1545                 {
  1546                     /* Just use the file name (even if file name is null or empty)
  1547                      * It's doesn't matter as it shouldn't be replaced in HTML.
  1548                      * This situation should mean either the attachment isn't an embedded image, or
  1549                      * the filename is already used as the CID in the HTML.
  1550                      */
  1551                     newCID = currFileName;
  1552                 }
  1553                 else
  1554                 {
  1555                     if (string.IsNullOrEmpty(currFileName))
  1556                     {
  1557                         // A name must exist for HTML so create one
  1558                         newCID = "attachment" + i.ToString();
  1559                     }
  1560                     else
  1561                     {
  1562                         newCID = currFileName;
  1563                     }
  1564 
  1565                     modifiedCIDs.Add(currCID, newCID);
  1566                 }
  1567 
  1568                 this._Attachments[i].ContentID = newCID;
  1569             }
  1570 
  1571             /* Process the HTML body replacing all modified CIDs
  1572              * Since no HTML document library is provided in standard C#, text operations are done.
  1573              * This should work ok, but there are some potential issue with malformed HTML that might render correctly.
  1574              * Only " is supported instead of ' as well.
  1575              * This shouldn't be an issue since the method is primarily run on mail items generated internally.
  1576              */
  1577             workingHTML = this._LongMsgFormattedHTML;
  1578             if (string.IsNullOrEmpty(workingHTML) == false)
  1579             {
  1580                 tempIndex = workingHTML.IndexOf("<img", 0);
  1581                 currImgTagIndex = (tempIndex > -1 ? (tempIndex + 4) : -1); // MUST start index after "<img"
  1582 
  1583                 while (currImgTagIndex > -1)
  1584                 {
  1585                     try
  1586                     {
  1587                         tempIndex = workingHTML.IndexOf("src=\"cid:", currImgTagIndex);
  1588                         currImgTagCIDIndex = (tempIndex > -1 ? (tempIndex + 9) : -1);
  1589                         currImgTagCIDStopIndex = workingHTML.IndexOf("\"", currImgTagCIDIndex);
  1590                         currImgTagStopIndex = workingHTML.IndexOf(">", currImgTagIndex);
  1591                     }
  1592                     catch
  1593                     {
  1594                         Log.Warning("NormalizeContentIDs: Incorrect index detected when calculating CID position, skipping current img.");
  1595 
  1596                         // Likely System.ArgumentOutOfRangeException from incorrect index
  1597                         // Just invalidate all locations and will not change this <img>
  1598                         currImgTagCIDIndex = -1;
  1599                         currImgTagCIDStopIndex = -1;
  1600                         currImgTagStopIndex = -1;
  1601                     }
  1602 
  1603                     // Validate relative index positions
  1604                     if ((currImgTagCIDIndex < currImgTagStopIndex) &&
  1605                         (currImgTagCIDStopIndex < currImgTagStopIndex) &&
  1606                         ((currImgTagCIDStopIndex - currImgTagCIDIndex) >= 1))
  1607                     {
  1608                         // Split the HTML at the CID and modify if necessary
  1609                         try
  1610                         {
  1611                             beginning = workingHTML.Substring(0, currImgTagCIDIndex);
  1612                             ending = workingHTML.Substring(currImgTagCIDStopIndex);
  1613                             currCID = workingHTML.Substring(currImgTagCIDIndex, (currImgTagCIDStopIndex - currImgTagCIDIndex));
  1614                         }
  1615                         catch
  1616                         {
  1617                             Log.Warning("NormalizeContentIDs: Error splitting HTML at CID, skipping current img.");
  1618 
  1619                             beginning = null;
  1620                             ending = null;
  1621                             currCID = null;
  1622                         }
  1623 
  1624                         // Lookup the new CID
  1625                         newCID = null;
  1626                         try
  1627                         {
  1628                             if (currCID != null)
  1629                             {
  1630                                 newCID = modifiedCIDs[currCID];
  1631                             }
  1632                         }
  1633                         catch
  1634                         {
  1635                             newCID = null;
  1636                         }
  1637 
  1638                         // Replace
  1639                         if ((beginning != null) &&
  1640                             (string.IsNullOrEmpty(newCID) == false) &&
  1641                             (ending != null))
  1642                         {
  1643                             workingHTML = beginning + newCID + ending;
  1644                         }
  1645                     }
  1646 
  1647                     try
  1648                     {
  1649                         tempIndex = workingHTML.IndexOf("<img", currImgTagIndex);
  1650                         currImgTagIndex = (tempIndex > -1 ? (tempIndex + 4) : -1); // MUST start index after "<img"
  1651                     }
  1652                     catch
  1653                     {
  1654                         Log.Warning("NormalizeContentIDs: Incorrect index detected, stopping calculation.");
  1655 
  1656                         // Likely System.ArgumentOutOfRangeException from incorrect index
  1657                         // Stop processing the HTML and use whatever is processed up to this point
  1658                         currImgTagIndex = -1;
  1659                     }
  1660                 }
  1661             }
  1662             this._LongMsgFormattedHTML = workingHTML;
  1663 
  1664             return;
  1665         }
  1666 
  1667         /**************************************************************
  1668          * 
  1669          * Static Methods
  1670          * 
  1671          *************************************************************/
  1672 
  1673         /// <summary>
  1674         /// Constructs a new message from the given pEp engine text_message.
  1675         /// The output will never be null.
  1676         /// </summary>
  1677         /// <param name="msg">The text_message to construct from.</param>
  1678         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  1679         /// <returns>The status of the method.</returns>
  1680         /// <remarks>In some contexts which call this message, a partial conversion of the
  1681         /// <paramref name="text_message"/> is acceptable, non-critical, and better than failure (e. G. displaying
  1682         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  1683         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  1684         /// in the more critical contexts. Callers of that message need to be aware of this
  1685         /// and check the result if appropriate.</remarks>
  1686         public static Globals.ReturnStatus Create(text_message msg,
  1687                                                   out PEPMessage createdMessage)
  1688         {
  1689             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  1690             PEPMessage newMessage = new PEPMessage();
  1691 
  1692             try
  1693             {
  1694                 /* Note: Skip the following properties which are not supported in the text_message
  1695                  *   • ConversationID
  1696                  *   • ConversationIndex
  1697                  *   • ConversationTopic
  1698                  *   • ForceUnencrypted
  1699                  *   • LongMsgFormattedRTF
  1700                  * 
  1701                  * Also not the following are handled as optional fields
  1702                  *   • Rating
  1703                  *   • KeyList
  1704                  *   • NeverUnsecure
  1705                  *   • PEPProtocolVersion
  1706                  * 
  1707                  * This also skips a number of text_message properties currently unsupported in PEPMessage.
  1708                  */
  1709 
  1710                 // Attachments
  1711                 newMessage.Attachments.Clear();
  1712                 if (msg.attachments != null)
  1713                 {
  1714                     for (int i = 0; i < msg.attachments.Length; i++)
  1715                     {
  1716                         newMessage.Attachments.Add(new PEPAttachment((blob)msg.attachments.GetValue(i)));
  1717                     }
  1718                 }
  1719 
  1720                 // BCC
  1721                 newMessage.BCC.Clear();
  1722                 if (msg.bcc != null)
  1723                 {
  1724                     for (int i = 0; i < msg.bcc.Length; i++)
  1725                     {
  1726                         newMessage.BCC.Add(new PEPIdentity((pEp_identity_s)msg.bcc.GetValue(i)));
  1727                     }
  1728                 }
  1729 
  1730                 // CC
  1731                 newMessage.CC.Clear();
  1732                 if (msg.cc != null)
  1733                 {
  1734                     for (int i = 0; i < msg.cc.Length; i++)
  1735                     {
  1736                         newMessage.CC.Add(new PEPIdentity((pEp_identity_s)msg.cc.GetValue(i)));
  1737                     }
  1738                 }
  1739 
  1740                 newMessage.Direction = msg.dir;
  1741                 newMessage.From = new PEPIdentity(msg.from);
  1742                 newMessage.ID = msg.id;
  1743 
  1744                 // Keywords
  1745                 newMessage.Keywords.Clear();
  1746                 if (msg.keywords != null)
  1747                 {
  1748                     for (int i = 0; i < msg.keywords.Length; i++)
  1749                     {
  1750                         newMessage.Keywords.Add((string)msg.keywords.GetValue(i));
  1751                     }
  1752                 }
  1753 
  1754                 newMessage.LongMsg = msg.longmsg;
  1755                 newMessage.LongMsgFormattedHTML = msg.longmsg_formatted;
  1756 
  1757                 // Optional properties
  1758                 if (msg.opt_fields != null)
  1759                 {
  1760                     for (int i = 0; i < msg.opt_fields.Length; i++)
  1761                     {
  1762                         if (msg.opt_fields[i].name != null)
  1763                         {
  1764                             switch (msg.opt_fields[i].name)
  1765                             {
  1766                                 case (PEPMessage.PR_X_ENC_STATUS_NAME):
  1767                                     {
  1768                                         try
  1769                                         {
  1770                                             // Note: ignore character case when parsing
  1771                                             newMessage.Rating = (_pEp_color)Enum.Parse(typeof(_pEp_color), ("pEp_rating_" + msg.opt_fields[i].value), true);
  1772                                         }
  1773                                         catch
  1774                                         {
  1775                                             newMessage.Rating = _pEp_color.pEp_rating_undefined;
  1776                                         }
  1777                                         break;
  1778                                     }
  1779                                 case (PEPMessage.PR_X_KEY_LIST_NAME):
  1780                                     {
  1781                                         newMessage.KeyList = msg.opt_fields[i].value;
  1782                                         break;
  1783                                     }
  1784                                 case (PEPMessage.PR_X_PEP_NEVER_UNSECURE_NAME):
  1785                                     {
  1786                                         // If it exists it's true, value doesn't matter
  1787                                         newMessage.NeverUnsecure = true;
  1788                                         break;
  1789                                     }
  1790                                 case (PEPMessage.PR_X_PEP_PROTOCOL_VERSION_NAME):
  1791                                     {
  1792                                         newMessage.PEPProtocolVersion = msg.opt_fields[i].value;
  1793                                         break;
  1794                                     }
  1795                             }
  1796                         }
  1797                     }
  1798                 }
  1799 
  1800                 // ReceivedOn
  1801                 if (msg.recv > 0)
  1802                 {
  1803                     newMessage.ReceivedOn = new DateTime(1970, 1, 1).AddSeconds(msg.recv);
  1804                 }
  1805                 else
  1806                 {
  1807                     newMessage.ReceivedOn = null;
  1808                 }
  1809 
  1810                 // SentOn
  1811                 if (msg.sent > 0)
  1812                 {
  1813                     newMessage.SentOn = new DateTime(1970, 1, 1).AddSeconds(msg.sent);
  1814                 }
  1815                 else
  1816                 {
  1817                     newMessage.SentOn = null;
  1818                 }
  1819 
  1820                 newMessage.ShortMsg = msg.shortmsg;
  1821 
  1822                 // To
  1823                 newMessage.To.Clear();
  1824                 if (msg.to != null)
  1825                 {
  1826                     for (int i = 0; i < msg.to.Length; i++)
  1827                     {
  1828                         newMessage.To.Add(new PEPIdentity((pEp_identity_s)msg.to.GetValue(i)));
  1829                     }
  1830                 }
  1831             }
  1832             catch (Exception ex)
  1833             {
  1834                 status = Globals.ReturnStatus.Failure;
  1835                 Log.Error("PEPMessage.Create: Failure occured, " + ex.ToString());
  1836             }
  1837 
  1838             createdMessage = newMessage;
  1839             return (status);
  1840         }
  1841 
  1842         /// <summary>
  1843         /// Contructs a new message from the given outlook mail item.
  1844         /// The output will never be null.
  1845         /// </summary>
  1846         /// <param name="omi">The outlook mail item to create the message from.</param>
  1847         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  1848         /// <param name="createWithoutContent">Whether to include content such as text body, attachments 
  1849         /// and optional properties.</param>
  1850         /// <returns>The status of the method.</returns>
  1851         /// <remarks>In some contexts which call this message, a partial conversion of the
  1852         /// <paramref name="text_message"/> is acceptable, non-critical, and better than failure (e. G. displaying
  1853         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  1854         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  1855         /// in the more critical contexts. Callers of that message need to be aware of this
  1856         /// and check the result if appropriate.</remarks>
  1857         public static Globals.ReturnStatus Create(Outlook.MailItem omi,
  1858                                                   out PEPMessage createdMessage,
  1859                                                   bool createWithoutContent = false)
  1860         {
  1861             byte[] rtfBody;
  1862             string delim;
  1863             string[] keywords;
  1864             object propValue;
  1865             DateTime? receivedOn = null;
  1866             DateTime? sentOn = null;
  1867             PEPIdentity ident;
  1868             Outlook.Attachment currAttachment = null;
  1869             Outlook.Attachments attachments = null;
  1870             Outlook.Recipient currRecipient = null;
  1871             Outlook.Recipients recipients = null;
  1872             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  1873             Globals.ReturnStatus sts;
  1874             PEPMessage newMessage = new PEPMessage();
  1875 
  1876             try
  1877             {
  1878                 Log.Verbose("PEPMessage.Create: Creating new PEPMessage from OMI started, calculating recipients.");
  1879 
  1880                 /* Note: Skip the following properties which are not supported in the MailItem
  1881                  *   • Direction
  1882                  */
  1883 
  1884                 // Calculate recipients
  1885                 newMessage.BCC.Clear();
  1886                 newMessage.CC.Clear();
  1887                 newMessage.To.Clear();
  1888                 recipients = omi.Recipients;
  1889                 for (int i = 1; i <= recipients.Count; i++)
  1890                 {
  1891                     currRecipient = recipients[i];
  1892 
  1893                     switch ((Outlook.OlMailRecipientType)currRecipient.Type)
  1894                     {
  1895                         case Outlook.OlMailRecipientType.olBCC:
  1896                             {
  1897                                 sts = PEPIdentity.Create(currRecipient, out ident);
  1898                                 if (sts == Globals.ReturnStatus.Success)
  1899                                 {
  1900                                     newMessage.BCC.Add(ident);
  1901                                 }
  1902                                 else
  1903                                 {
  1904                                     // Update the status, only the first failure type is recorded
  1905                                     if (status == Globals.ReturnStatus.Success)
  1906                                     {
  1907                                         status = sts;
  1908                                     }
  1909 
  1910                                     Log.Error("PEPMessage.Create: Failure creating 'BCC' identity.");
  1911                                 }
  1912 
  1913                                 break;
  1914                             }
  1915                         case Outlook.OlMailRecipientType.olCC:
  1916                             {
  1917                                 sts = PEPIdentity.Create(currRecipient, out ident);
  1918                                 if (sts == Globals.ReturnStatus.Success)
  1919                                 {
  1920                                     newMessage.CC.Add(ident);
  1921                                 }
  1922                                 else
  1923                                 {
  1924                                     // Update the status, only the first failure type is recorded
  1925                                     if (status == Globals.ReturnStatus.Success)
  1926                                     {
  1927                                         status = sts;
  1928                                     }
  1929 
  1930                                     Log.Error("PEPMessage.Create: Failure creating 'CC' identity.");
  1931                                 }
  1932 
  1933                                 break;
  1934                             }
  1935                         case Outlook.OlMailRecipientType.olTo:
  1936                             {
  1937                                 sts = PEPIdentity.Create(currRecipient, out ident);
  1938                                 if (sts == Globals.ReturnStatus.Success)
  1939                                 {
  1940                                     newMessage.To.Add(ident);
  1941                                 }
  1942                                 else
  1943                                 {
  1944                                     // Update the status, only the first failure type is recorded
  1945                                     if (status == Globals.ReturnStatus.Success)
  1946                                     {
  1947                                         status = sts;
  1948                                     }
  1949 
  1950                                     Log.Error("PEPMessage.Create: Failure creating 'To' identity.");
  1951                                 }
  1952 
  1953                                 break;
  1954                             }
  1955                     }
  1956 
  1957                     Marshal.ReleaseComObject(currRecipient);
  1958                     currRecipient = null;
  1959                 }
  1960 
  1961                 Log.Verbose("PEPMessage.Create: Recipients calculated, calculating main properties.");
  1962 
  1963                 newMessage.Direction = CryptableMailItem.GetIsIncoming(omi) ? _pEp_msg_direction.pEp_dir_incoming : _pEp_msg_direction.pEp_dir_outgoing;
  1964 
  1965                 // From
  1966                 sts = PEPIdentity.GetFromIdentity(omi, out ident);
  1967                 if (sts == Globals.ReturnStatus.Success)
  1968                 {
  1969                     newMessage.From = ident;
  1970                 }
  1971                 else
  1972                 {
  1973                     // Update the status, only the first failure type is recorded
  1974                     if (status == Globals.ReturnStatus.Success)
  1975                     {
  1976                         status = sts;
  1977                     }
  1978 
  1979                     Log.Error("PEPMessage.Create: Failure creating 'From' identity.");
  1980                 }
  1981 
  1982                 newMessage.ID = (string)MAPIHelper.GetProperty(omi, MAPIProperty.PidTagInternetMessageId, "");
  1983                 newMessage.ShortMsg = omi.Subject;
  1984 
  1985                 /* Date & Times
  1986                  * 
  1987                  * Note: The mail item date can be invalid:
  1988                  * For incoming this commonly occurs when creating a mirror -- it's created without a received time.
  1989                  * For outgoing this commonly occurs for unsent mail.
  1990                  * In these cases, the invalid date and time will be returned as 1/1/4501 at 12:00AM
  1991                  * This situation is filtered here and the date is set as null.
  1992                  */
  1993                 receivedOn = omi.ReceivedTime;
  1994                 if ((receivedOn != null) &&
  1995                     (((DateTime)receivedOn).Year > 4000)) // ~2000 years from now, no issue
  1996                 {
  1997                     receivedOn = null;
  1998                 }
  1999                 newMessage.ReceivedOn = receivedOn;
  2000 
  2001                 sentOn = omi.SentOn;
  2002                 if ((sentOn != null) &&
  2003                     (((DateTime)sentOn).Year > 4000)) // ~2000 years from now, no issue
  2004                 {
  2005                     sentOn = null;
  2006                 }
  2007                 newMessage.SentOn = sentOn;
  2008 
  2009                 // Conversation information
  2010                 newMessage.ConversationID = omi.ConversationID;
  2011                 newMessage.ConversationIndex = omi.ConversationIndex;
  2012                 newMessage.ConversationTopic = omi.ConversationTopic;
  2013 
  2014                 // ForceUnencrypted
  2015                 CryptableMailItem.GetInterpretedProperty(omi,
  2016                                                          CryptableMailItem.USER_PROPERTY_KEY_FORCE_UNENCRYPTED,
  2017                                                          out propValue);
  2018                 newMessage.ForceUnencrypted = (bool)propValue;
  2019 
  2020                 // Rating
  2021                 CryptableMailItem.GetInterpretedProperty(omi,
  2022                                                          PEPMessage.XPidNameEncStatus.DASLName,
  2023                                                          out propValue);
  2024                 newMessage.Rating = (_pEp_color)propValue;
  2025 
  2026                 // KeyList
  2027                 CryptableMailItem.GetInterpretedProperty(omi,
  2028                                                          PEPMessage.XPidNameKeyList.DASLName,
  2029                                                          out propValue);
  2030                 newMessage.KeyList = (string)propValue;
  2031 
  2032                 // NeverUnsecure
  2033                 CryptableMailItem.GetInterpretedProperty(omi,
  2034                                                          PEPMessage.XPidNamePEPNeverUnsecure.DASLName,
  2035                                                          out propValue);
  2036                 newMessage.NeverUnsecure = (bool)propValue;
  2037 
  2038                 // PEPProtocolVersion
  2039                 CryptableMailItem.GetInterpretedProperty(omi,
  2040                                                          PEPMessage.XPidNamePEPProtocolVersion.DASLName,
  2041                                                          out propValue);
  2042                 newMessage.PEPProtocolVersion = (string)propValue;
  2043 
  2044                 Log.Verbose("PEPMessage.Create: Main properties calculated, calculating body and attachments.");
  2045 
  2046                 // Calculate text body and attachments
  2047                 if (createWithoutContent == false)
  2048                 {
  2049                     // Body
  2050                     if (omi.Body != null)
  2051                     {
  2052                         newMessage.LongMsg = omi.Body.Replace("\r\n", "\n");
  2053 
  2054                         // Save as RTF
  2055                         try
  2056                         {
  2057                             rtfBody = omi.RTFBody;
  2058 
  2059                             if ((rtfBody != null) &&
  2060                                 (rtfBody.Length > 0))
  2061                             {
  2062                                 newMessage.LongMsgFormattedRTF = Encoding.ASCII.GetString(rtfBody, 0, rtfBody.Length);
  2063                             }
  2064                         }
  2065                         catch
  2066                         {
  2067                             Log.Warning("PEPMessage.Create: Unable to read RTF body in MailItem.");
  2068                         }
  2069 
  2070                         // Force any rich text into HTML
  2071                         // WARNING: This is technically a modifcation of the original MailItem
  2072                         // It should be further investigated if this can be removed in the future.
  2073                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatRichText)
  2074                         {
  2075                             omi.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
  2076                         }
  2077 
  2078                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatHTML)
  2079                         {
  2080                             newMessage.LongMsgFormattedHTML = omi.HTMLBody;
  2081                         }
  2082                     }
  2083 
  2084                     // Attachments
  2085                     newMessage.Attachments.Clear();
  2086                     attachments = omi.Attachments;
  2087                     for (int i = 1; i <= attachments.Count; i++)
  2088                     {
  2089                         currAttachment = attachments[i];
  2090 
  2091                         try
  2092                         {
  2093                             newMessage.Attachments.Add(new PEPAttachment(currAttachment));
  2094                         }
  2095                         catch { }
  2096 
  2097                         Marshal.ReleaseComObject(currAttachment);
  2098                         currAttachment = null;
  2099                     }
  2100 
  2101                     // Keywords
  2102                     if (omi.Categories != null)
  2103                     {
  2104                         try
  2105                         {
  2106                             newMessage.Keywords.Clear();
  2107 
  2108                             using (RegistryKey key1 = Registry.CurrentUser.OpenSubKey("Control Panel\\International"))
  2109                             {
  2110                                 delim = key1.GetValue("sList").ToString();
  2111                                 keywords = omi.Categories.Split(delim.ToCharArray());
  2112 
  2113                                 for (int i = 0; i < keywords.Length; i++)
  2114                                 {
  2115                                     newMessage.Keywords.Add(keywords[i]);
  2116                                 }
  2117                             }
  2118                         }
  2119                         catch
  2120                         {
  2121                             newMessage.Keywords.Clear();
  2122                             Log.Warning("PEPMessage.Create: Unable to set keywords.");
  2123                         }
  2124                     }
  2125                 }
  2126 
  2127                 Log.Verbose("PEPMessage.Create: New PEPMessage created from OMI.");
  2128             }
  2129             catch (Exception ex)
  2130             {
  2131                 status = Globals.ReturnStatus.Failure;
  2132                 Log.Error("PEPMessage.Create: failure occured, " + ex.ToString());
  2133             }
  2134             finally
  2135             {
  2136                 // Free resources
  2137                 if (currAttachment != null)
  2138                 {
  2139                     Marshal.ReleaseComObject(currAttachment);
  2140                     currAttachment = null;
  2141                 }
  2142 
  2143                 if (attachments != null)
  2144                 {
  2145                     Marshal.ReleaseComObject(attachments);
  2146                     attachments = null;
  2147                 }
  2148 
  2149                 if (currRecipient != null)
  2150                 {
  2151                     Marshal.ReleaseComObject(currRecipient);
  2152                     currRecipient = null;
  2153                 }
  2154 
  2155                 if (recipients != null)
  2156                 {
  2157                     Marshal.ReleaseComObject(recipients);
  2158                     recipients = null;
  2159                 }
  2160             }
  2161 
  2162             createdMessage = newMessage;
  2163 
  2164             Log.Verbose("PEPMessage.Create: ReturnStatus=" + status.ToString());
  2165             return (status);
  2166         }
  2167 
  2168         /// <summary>
  2169         /// Determines if the given text is PGP text based on starting text sequence.
  2170         /// The starting sequence must be "-----BEGIN PGP MESSAGE-----"
  2171         /// </summary>
  2172         /// <param name="text">The text to test if it is PGP text.</param>
  2173         /// <returns>True if the given text is PGP text, otherwise false.</returns>
  2174         public static bool IsPGPText(string text)
  2175         {
  2176             if (string.IsNullOrEmpty(text) == false)
  2177             {
  2178                 string pgp_text = text.Trim();
  2179                 return (pgp_text.StartsWith("-----BEGIN PGP MESSAGE-----"));
  2180             }
  2181             else
  2182             {
  2183                 return (false);
  2184             }
  2185         }
  2186 
  2187         /// <summary>
  2188         /// Determines if the given message is encrypted.
  2189         /// Currently, only PGP encrypted messages will be detected.
  2190         /// </summary>
  2191         /// <param name="omi">The outlook mail item to check encryption for.</param>
  2192         /// <returns>True if the given message is encrypted, otherwise false.</returns>
  2193         public static bool GetIsEncrypted(Outlook.MailItem omi)
  2194         {
  2195             if (omi != null)
  2196             {
  2197                 // Partitioned or inline PGP format
  2198                 if (omi.Body != null && PEPMessage.IsPGPText(omi.Body))
  2199                 {
  2200                     return (true);
  2201                 }
  2202 
  2203                 // PGP/MIME format
  2204                 if (PEPMessage.GetIsPGPMIMEEncrypted(omi))
  2205                 {
  2206                     return (true);
  2207                 }
  2208             }
  2209 
  2210             return (false);
  2211         }
  2212 
  2213         /// <summary>
  2214         /// Determines if the given message is encrypted in PGP/MIME format.
  2215         /// </summary>
  2216         /// <param name="msg">The message to check encryption for.</param>
  2217         /// <returns>True if the given message is PGP/MIME encrypted, otherwise false.</returns>
  2218         public static bool GetIsPGPMIMEEncrypted(Outlook.MailItem omi)
  2219         {
  2220             bool result = false;
  2221             bool versionInfoFound = false;
  2222             bool contentFound = false;
  2223             PEPAttachment attach;
  2224             Outlook.Attachment attachment = null;
  2225             Outlook.Attachments attachments = null;
  2226 
  2227             try
  2228             {
  2229                 if (omi != null)
  2230                 {
  2231                     attachments = omi.Attachments;
  2232 
  2233                     // Require only two attachments (version identification & encrypted content)
  2234                     // However, allow the attachments to be in any order
  2235                     if (attachments.Count == 2)
  2236                     {
  2237                         // Note: attachment index starts at 1
  2238                         for (int i = 1; i <= attachments.Count; i++)
  2239                         {
  2240                             attachment = attachments[i];
  2241                             attach = new PEPAttachment(attachment);
  2242 
  2243                             if (PEPMessage.IsPGPMIMEVersionInfoFormat(attach))
  2244                             {
  2245                                 versionInfoFound = true;
  2246                             }
  2247                             else if (PEPMessage.IsPGPMIMEContentFormat(attach))
  2248                             {
  2249                                 contentFound = true;
  2250                             }
  2251 
  2252                             Marshal.ReleaseComObject(attachment);
  2253                             attachment = null;
  2254                         }
  2255 
  2256                         if (versionInfoFound && contentFound)
  2257                         {
  2258                             result = true;
  2259                         }
  2260                     }
  2261                 }
  2262             }
  2263             catch { }
  2264             finally
  2265             {
  2266                 if (attachment != null)
  2267                 {
  2268                     Marshal.ReleaseComObject(attachment);
  2269                     attachment = null;
  2270                 }
  2271 
  2272                 if (attachments != null)
  2273                 {
  2274                     Marshal.ReleaseComObject(attachments);
  2275                     attachments = null;
  2276                 }
  2277             }
  2278 
  2279             return (result);
  2280         }
  2281 
  2282         /// <summary>
  2283         /// Determines if the given PEPMessage is encrypted.
  2284         /// Currently, only PGP encrypted messages will be detected.
  2285         /// </summary>
  2286         /// <param name="msg">The message to check encryption for.</param>
  2287         /// <returns>True if the given message is encrypted, otherwise false.</returns>
  2288         public static bool GetIsEncrypted(PEPMessage msg)
  2289         {
  2290             if (msg != null)
  2291             {
  2292                 // Partitioned or inline PGP format
  2293                 if (msg.LongMsg != null && PEPMessage.IsPGPText(msg.LongMsg))
  2294                 {
  2295                     return (true);
  2296                 }
  2297 
  2298                 // PGP/MIME format
  2299                 if (PEPMessage.GetIsPGPMIMEEncrypted(msg))
  2300                 {
  2301                     return (true);
  2302                 }
  2303             }
  2304 
  2305             return (false);
  2306         }
  2307 
  2308         /// <summary>
  2309         /// Determines if the given message is encrypted in PGP/MIME format.
  2310         /// </summary>
  2311         /// <param name="msg">The message to check encryption for.</param>
  2312         /// <returns>True if the given message is PGP/MIME encrypted, otherwise false.</returns>
  2313         public static bool GetIsPGPMIMEEncrypted(PEPMessage msg)
  2314         {
  2315             bool result = false;
  2316             bool versionInfoFound = false;
  2317             bool contentFound = false;
  2318 
  2319             if (msg != null)
  2320             {
  2321                 // Require only two attachments (version identification & encrypted content)
  2322                 // However, allow the attachments to be in any order
  2323                 if (msg.Attachments.Count == 2)
  2324                 {
  2325                     foreach (PEPAttachment attach in msg.Attachments)
  2326                     {
  2327                         if (PEPMessage.IsPGPMIMEVersionInfoFormat(attach))
  2328                         {
  2329                             versionInfoFound = true;
  2330                         }
  2331                         else if (PEPMessage.IsPGPMIMEContentFormat(attach))
  2332                         {
  2333                             contentFound = true;
  2334                         }
  2335                     }
  2336 
  2337                     if (versionInfoFound && contentFound)
  2338                     {
  2339                         result = true;
  2340                     }
  2341                 }
  2342             }
  2343 
  2344             return (result);
  2345         }
  2346 
  2347         /// <summary>
  2348         /// Determines if the given attachment is formatted like a PGP/MIME version identification.
  2349         /// </summary>
  2350         /// <param name="attachment">The attachment to check.</param>
  2351         /// <returns>True if the attachment is PGP/MIME version identifcation formatted, otherwise false.</returns>
  2352         private static bool IsPGPMIMEVersionInfoFormat(PEPAttachment attachment)
  2353         {
  2354             bool result = false;
  2355 
  2356             if ((attachment != null) &&
  2357                 (string.IsNullOrEmpty(attachment.MIMEType) == false) &&
  2358                 (string.Equals(attachment.MIMEType.Trim(), "application/pgp-encrypted", StringComparison.OrdinalIgnoreCase)))
  2359             {
  2360                 // Allow any data
  2361                 if ((attachment.Data != null) &&
  2362                     (attachment.Data.Length > 0))
  2363                 {
  2364                     result = true;
  2365                 }
  2366             }
  2367 
  2368             return (result);
  2369         }
  2370 
  2371         /// <summary>
  2372         /// Determines if the given attachment is formatted like PGP/MIME content.
  2373         /// </summary>
  2374         /// <param name="attachment">The attachment to check.</param>
  2375         /// <returns>True if the attachment is PGP/MIME content formatted, otherwise false.</returns>
  2376         private static bool IsPGPMIMEContentFormat(PEPAttachment attachment)
  2377         {
  2378             bool result = false;
  2379 
  2380             if ((attachment != null) &&
  2381                 (string.IsNullOrEmpty(attachment.MIMEType) == false) &&
  2382                 (string.Equals(attachment.MIMEType.Trim(), "application/octet-stream", StringComparison.OrdinalIgnoreCase)))
  2383             {
  2384                 // Require data to start with PGP text and be at least 100 bytes
  2385                 if ((attachment.Data != null) &&
  2386                     (attachment.Data.Length > 100))
  2387                 {
  2388                     // Check for PGP text after converting to string
  2389                     if (PEPMessage.IsPGPText(Encoding.ASCII.GetString(attachment.Data, 0, 100)))
  2390                     {
  2391                         result = true;
  2392                     }
  2393                 }
  2394             }
  2395 
  2396             return (result);
  2397         }
  2398     }
  2399 }