PEPMessage.cs
author Thomas
Mon, 06 Apr 2020 10:11:47 +0200
changeset 3097 3d0bf28073c0
parent 3075 cb4f823815a9
permissions -rw-r--r--
Merge with OUT-445

The Update now button itself is implemented. Other functionality regarding this feature is tracked in other tickets.
     1 using MimeKit;
     2 using pEpCOMServerAdapterLib;
     3 using System;
     4 using System.Collections.Generic;
     5 using System.ComponentModel;
     6 using System.IO;
     7 using System.Linq;
     8 using System.Text;
     9 using Outlook = Microsoft.Office.Interop.Outlook;
    10 
    11 namespace pEp
    12 {
    13     /// <summary>
    14     /// Class for a completely in-memory message based on the pEp engine TextMessage.
    15     /// </summary>
    16     internal class PEPMessage : INotifyPropertyChanged,
    17                                 IEquatable<PEPMessage>,
    18                                 Interfaces.ICopy<PEPMessage>,
    19                                 Interfaces.IReset
    20     {
    21         /// <summary>
    22         /// Event raised when a property is changed on a component.
    23         /// </summary>
    24         public event PropertyChangedEventHandler PropertyChanged;
    25 
    26         // pEp named MAPI properties
    27         public const string PR_ENC_STATUS_NAME              = "X-EncStatus";
    28         public const string PR_KEY_LIST_NAME                = "X-KeyList";
    29         public const string PR_PEP_AUTO_CONSUME_NAME        = "pEp-auto-consume";
    30         public const string PR_PEP_AUTO_CONSUME_NAME_OLD    = "X-pEp-auto-consume"; // legacy - see OUT-522
    31         public const string PR_PEP_KEY_IMPORT_NAME          = "pEp-key-import";
    32         public const string PR_PEP_KEY_IMPORT_NAME_OLD      = "X-pEp-key-import"; // legacy - see OUT-522
    33         public const string PR_PEP_FORCE_PROTECTION_NAME    = "X-pEp-force-protection";
    34         public const string PR_PEP_NEVER_UNSECURE_NAME      = "X-pEp-Never-Unsecure";
    35         public const string PR_PEP_PROTOCOL_VERSION_NAME    = "X-pEp-Version";
    36 
    37         public const string PR_PEP_NEVER_UNSECURE_VALUE     = "yes";
    38 
    39         public static readonly MapiProperty.MapiProp PidNameEncStatus          = new MapiProperty.MapiProp(PR_ENC_STATUS_NAME,              MapiProperty.PS_INTERNET_HEADERS);
    40         public static readonly MapiProperty.MapiProp PidNameKeyList            = new MapiProperty.MapiProp(PR_KEY_LIST_NAME,                MapiProperty.PS_INTERNET_HEADERS);
    41         public static readonly MapiProperty.MapiProp PidNamePEPAutoConsume     = new MapiProperty.MapiProp(PR_PEP_AUTO_CONSUME_NAME,        MapiProperty.PS_INTERNET_HEADERS);
    42         public static readonly MapiProperty.MapiProp PidNamePEPAutoConsumeOld  = new MapiProperty.MapiProp(PR_PEP_AUTO_CONSUME_NAME_OLD,    MapiProperty.PS_INTERNET_HEADERS);
    43         public static readonly MapiProperty.MapiProp PidNamePEPForceProtection = new MapiProperty.MapiProp(PR_PEP_FORCE_PROTECTION_NAME,    MapiProperty.PS_INTERNET_HEADERS);
    44         public static readonly MapiProperty.MapiProp PidNamePEPNeverUnsecure   = new MapiProperty.MapiProp(PR_PEP_NEVER_UNSECURE_NAME,      MapiProperty.PS_INTERNET_HEADERS);
    45         public static readonly MapiProperty.MapiProp PidNamePEPProtocolVersion = new MapiProperty.MapiProp(PR_PEP_PROTOCOL_VERSION_NAME,    MapiProperty.PS_INTERNET_HEADERS);
    46         public static readonly MapiProperty.MapiProp PidNamePEPKeyImport       = new MapiProperty.MapiProp(PR_PEP_KEY_IMPORT_NAME,          MapiProperty.PS_INTERNET_HEADERS);
    47         public static readonly MapiProperty.MapiProp PidNamePEPKeyImportOld    = new MapiProperty.MapiProp(PR_PEP_KEY_IMPORT_NAME_OLD,      MapiProperty.PS_INTERNET_HEADERS);
    48 
    49         public const string PEP_HIDDEN_SYNC_MESSAGE_BODY                       = "This message is part of p≡p's concept to synchronize. \n\nYou can safely ignore it. It will be deleted automatically.";
    50         public const string PEP_HIDDEN_SYNC_MESSAGE_SUBJECT                    = "p≡p synchronization message - please ignore";
    51 
    52         public const string PEPTUNNEL_MAIL_ADDRESS                             = "@pep.foundation";
    53 
    54         private static readonly object                      mutexMirror        = new object();
    55         private static readonly Dictionary<string, string>  mirrorCache        = new Dictionary<string, string>();
    56 
    57         private List<StringPair>    _AdditionalHeaders;
    58         private List<PEPAttachment> _Attachments;
    59         private string              _AutoConsume;
    60         private List<PEPIdentity>   _Bcc;
    61         private List<PEPIdentity>   _Cc;
    62         private string              _ConversationId;
    63         private string              _ConversationIndex;
    64         private string              _ConversationTopic;
    65         private pEpMsgDirection     _Direction;
    66         private bool                _EnableProtection;
    67         private string              _ForceProtectionId;
    68         private bool                _ForceUnencrypted;
    69         private PEPIdentity         _From;
    70         private string              _Id;
    71         private string              _KeyImport;
    72         private string              _KeyList;
    73         private List<string>        _Keywords;
    74         private string              _LongMsg;
    75         private string              _LongMsgFormattedHtml;
    76         private string              _LongMsgFormattedRtf;
    77         private bool                _NeverUnsecure;
    78         private string              _PEPProtocolVersion;
    79         private pEpRating           _Rating;
    80         private DateTime?           _ReceivedOn;
    81         private List<PEPIdentity>   _ReplyTo;
    82         private DateTime?           _SentOn;
    83         private string              _ShortMsg;
    84         private List<PEPIdentity>   _To;
    85 
    86         /**************************************************************
    87          * 
    88          * Constructors
    89          * 
    90          *************************************************************/
    91 
    92         /// <summary>
    93         /// Default constructor.
    94         /// </summary>
    95         public PEPMessage()
    96         {
    97             this.Reset();
    98         }
    99 
   100         /**************************************************************
   101          * 
   102          * Property Accessors
   103          * 
   104          *************************************************************/
   105 
   106         #region Property Accessors
   107 
   108         /// <summary>
   109         /// Gets the list of additional headers for this message.
   110         /// Those are headers that are of no direct use for pEp for Outlook and
   111         /// will just get passed from the Outlook mail item to the engine (via
   112         /// optional fields)
   113         /// </summary>
   114         public List<StringPair> AdditionalHeaders
   115         {
   116             get { return this._AdditionalHeaders; }
   117         }
   118 
   119         /// <summary>
   120         /// Gets the list of attachements for this message.
   121         ///          MailItem : Corresponds to property 'Attachments' which contains 'Attachment'
   122         /// CryptableMailItem : not exposed
   123         ///       TextMessage : Corresponds to property 'Attachments' which contains 'Blob'
   124         /// </summary>
   125         public List<PEPAttachment> Attachments
   126         {
   127             get { return (this._Attachments); }
   128         }
   129 
   130         /// <summary>
   131         /// Gets or sets whether this message should be automatically consumed by the pEp engine.
   132         /// Note: While this is stored as a header field, it is OK to apply to any MailItem and outgoing messages.
   133         /// It's intended function is only to add to outgoing messages generated by the engine.
   134         ///          MailItem : Corresponds to MAPI Property PidNamePEPAutoConsume (Header field)
   135         /// CryptableMailItem : not supported
   136         ///       TextMessage : Corresponds with the key PR_PEP_AUTO_CONSUME_NAME within 'opt_fields' array.
   137         /// </summary>
   138         public string AutoConsume
   139         {
   140             get { return (this._AutoConsume); }
   141             set
   142             {
   143                 this._AutoConsume = value;
   144                 this.RaisePropertyChangedEvent(nameof(this.AutoConsume));
   145             }
   146         }
   147 
   148         /// <summary>
   149         /// Gets the list of identities to be blind carbon copied on the message.
   150         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
   151         /// CryptableMailItem : not exposed directly (part of Recipients)
   152         ///       TextMessage : Corresponds to property 'Bcc'
   153         /// </summary>
   154         public List<PEPIdentity> Bcc
   155         {
   156             get { return (this._Bcc); }
   157         }
   158 
   159         /// <summary>
   160         /// Gets the list of identities to be carbon copied on the message.
   161         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
   162         /// CryptableMailItem : not exposed directly (part of Recipients)
   163         ///       TextMessage : Corresponds to property 'Cc'
   164         /// </summary>
   165         public List<PEPIdentity> Cc
   166         {
   167             get { return (this._Cc); }
   168         }
   169 
   170         /// <summary>
   171         /// Gets or sets the conversation ID of the message.
   172         /// Outlook should normally manage this itself.
   173         ///          MailItem : Corresponds to property 'ConversationID' (Underlying MAPI property PidTagConversationId)
   174         /// CryptableMailItem : Corresponds to property 'ConversationID'
   175         ///       TextMessage : not supported
   176         /// </summary>
   177         public string ConversationId
   178         {
   179             get { return (this._ConversationId); }
   180             set
   181             {
   182                 this._ConversationId = value;
   183                 this.RaisePropertyChangedEvent(nameof(this.ConversationId));
   184             }
   185         }
   186 
   187         /// <summary>
   188         /// Gets or sets the conversation index of the message.
   189         /// Outlook should normally manage this itself.
   190         ///          MailItem : Corresponds to property 'ConversationIndex' (Underlying MAPI property PidTagConversationIndex)
   191         /// CryptableMailItem : Corresponds to property 'ConversationIndex'
   192         ///       TextMessage : not supported
   193         /// </summary>
   194         public string ConversationIndex
   195         {
   196             get { return (this._ConversationIndex); }
   197             set
   198             {
   199                 this._ConversationIndex = value;
   200                 this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
   201             }
   202         }
   203 
   204         /// <summary>
   205         /// Gets or sets the conversation index of the message.
   206         /// Outlook should normally manage this itself.
   207         ///          MailItem : Corresponds to property 'ConversationTopic' (Underlying MAPI property PidTagConversationTopic)
   208         /// CryptableMailItem : Corresponds to property 'ConversationTopic'
   209         ///       TextMessage : not supported
   210         /// </summary>
   211         public string ConversationTopic
   212         {
   213             get { return (this._ConversationTopic); }
   214             set
   215             {
   216                 this._ConversationTopic = value;
   217                 this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
   218             }
   219         }
   220 
   221         /// <summary>
   222         /// Gets the date and time when the message was either sent or received.
   223         /// This corresponds with the SentOn and ReceivedOn properties defined separately.
   224         /// </summary>
   225         public DateTime? DateTimeSentOrReceived
   226         {
   227             get
   228             {
   229                 if (this._Direction == pEpMsgDirection.pEpDirIncoming)
   230                 {
   231                     return (this._ReceivedOn);
   232                 }
   233                 else
   234                 {
   235                     return (this._SentOn);
   236                 }
   237             }
   238         }
   239 
   240         /// <summary>
   241         /// Gets or sets the direction (incoming or outgoing) of the message.
   242         ///          MailItem : not supported (calculated from various properties)
   243         /// CryptableMailItem : Corresponds 1-to-1 with property 'IsIncoming'
   244         ///       TextMessage : Corresponds to property 'Dir'
   245         /// </summary>
   246         public pEpMsgDirection Direction
   247         {
   248             get { return (this._Direction); }
   249             set
   250             {
   251                 this._Direction = value;
   252                 this.RaisePropertyChangedEvent(nameof(this.Direction));
   253             }
   254         }
   255 
   256         /// <summary>
   257         /// Gets or sets the enable protection status.
   258         ///          MailItem : Corresponds to UserProperty USER_PROPERTY_KEY_ENABLE_PROTECTION
   259         /// CryptableMailItem : Corresponds to property 'EnableProtection'
   260         ///       TextMessage : not supported
   261         /// </summary>
   262         public bool EnableProtection
   263         {
   264             get { return (this._EnableProtection); }
   265             set
   266             {
   267                 this._EnableProtection = value;
   268                 this.RaisePropertyChangedEvent(nameof(this.EnableProtection));
   269             }
   270         }
   271 
   272         /// <summary>
   273         /// Gets or sets the message's force protection id (if any).
   274         ///          MailItem : Corresponds to MAPI Property PidNamePEPForceProtection (Header field)
   275         /// CryptableMailItem : not supported
   276         ///       TextMessage : Corresponds with the key PR_PEP_FORCE_PROTECTION_NAME within 'opt_fields' array.
   277         /// </summary>
   278         public string ForceProtectionId
   279         {
   280             get { return (this._ForceProtectionId); }
   281             set
   282             {
   283                 this._ForceProtectionId = value;
   284                 this.RaisePropertyChangedEvent(nameof(this.ForceProtectionId));
   285             }
   286         }
   287 
   288         /// <summary>
   289         /// Gets or sets the force unencrypted status.
   290         ///          MailItem : Corresponds to UserProperty USER_PROPERTY_KEY_FORCE_UNENCRYPTED
   291         /// CryptableMailItem : Corresponds to property 'ForceUnencryptedBool'
   292         ///       TextMessage : not supported
   293         /// </summary>
   294         public bool ForceUnencrypted
   295         {
   296             get { return (this._ForceUnencrypted); }
   297             set
   298             {
   299                 this._ForceUnencrypted = value;
   300                 this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
   301             }
   302         }
   303 
   304         /// <summary>
   305         /// Gets or sets the from identity of the message.
   306         /// Warning: this value can be null.
   307         ///          MailItem : not supported (calculated from various properties)
   308         /// CryptableMailItem : Corresponds to property 'From'
   309         ///       TextMessage : Corresponds to property 'From'
   310         /// </summary>
   311         public PEPIdentity From
   312         {
   313             get { return (this._From); }
   314             set
   315             {
   316                 this._From = value;
   317                 this.RaisePropertyChangedEvent(nameof(this.From));
   318             }
   319         }
   320 
   321         /// <summary>
   322         /// Gets or sets the ID of the message.
   323         /// Warning: this value can be null.
   324         ///          MailItem : Corresponds to MAPI property PidTagInternetMessageId
   325         /// CryptableMailItem : not supported
   326         ///       TextMessage : Corresponds to property 'Id'
   327         /// </summary>
   328         public string Id
   329         {
   330             get { return (this._Id); }
   331             set
   332             {
   333                 this._Id = value;
   334                 this.RaisePropertyChangedEvent(nameof(this.Id));
   335             }
   336         }
   337 
   338         /// <summary>
   339         /// Convenience method to get whether this message is auto consumable.
   340         /// </summary>
   341         public bool IsAutoConsume
   342         {
   343             get { return (string.IsNullOrEmpty(this._AutoConsume) == false); }
   344         }
   345 
   346         /// <summary>
   347         /// Gets whether this message is considered secure.
   348         /// This will forward the call to the static method of the same purpose.
   349         /// </summary>
   350         public bool IsSecure
   351         {
   352             get { return (PEPMessage.GetIsSecure(this)); }
   353         }
   354 
   355         /// <summary>
   356         /// Gets whether this message is secured using PGP/MIME format.
   357         /// This will forward the call to the static method of the same purpose.
   358         /// </summary>
   359         public bool IsPGPMIMEEncrypted
   360         {
   361             get { return (PEPMessage.GetIsPGPMIMEEncrypted(this)); }
   362         }
   363 
   364         /// <summary>
   365         /// Gets or sets the partner fingerprint of a key import message.
   366         /// </summary>
   367         public string KeyImport
   368         {
   369             get { return (this._KeyImport); }
   370             set
   371             {
   372                 this._KeyImport = value;
   373                 this.RaisePropertyChangedEvent(nameof(this.KeyImport));
   374             }
   375         }
   376 
   377         /// <summary>
   378         /// Gets or sets the list of keys associated with this message.
   379         /// Commonly this contains the list of decryption keys.
   380         /// Warning: Since this is stored as a header field, care must be taken not to apply this to a MailItem on an 
   381         /// untrusted server or on an outgoing message.
   382         ///          MailItem : Corresponds to MAPI Property PidNameKeyList (Header field)
   383         /// CryptableMailItem : not supported
   384         ///       TextMessage : Corresponds with the key PR_KEY_LIST_NAME within 'opt_fields' array. 
   385         ///                     This is also an out parameter of the decrypt function.
   386         /// </summary>
   387         public string KeyList
   388         {
   389             get { return (this._KeyList); }
   390             set
   391             {
   392                 this._KeyList = value;
   393                 this.RaisePropertyChangedEvent(nameof(this.KeyList));
   394             }
   395         }
   396 
   397         /// <summary>
   398         /// Gets the list of keywords associated with the message.
   399         ///          MailItem : Corresponds to property 'Categories'
   400         /// CryptableMailItem : not supported
   401         ///       TextMessage : Corresponds to property 'Keywords'
   402         /// </summary>
   403         public List<string> Keywords
   404         {
   405             get { return (this._Keywords); }
   406         }
   407 
   408         /// <summary>
   409         /// Gets or sets the plain text long-form (body) of the message.
   410         /// Warning: this value can be null.
   411         ///          MailItem : Corresponds to property 'Body' (also BodyFormat)
   412         /// CryptableMailItem : not exposed (processed internally)
   413         ///       TextMessage : Corresponds to property 'LongMsg'
   414         /// </summary>
   415         public string LongMsg
   416         {
   417             get { return (this._LongMsg); }
   418             set
   419             {
   420                 this._LongMsg = value;
   421                 this.RaisePropertyChangedEvent(nameof(this.LongMsg));
   422             }
   423         }
   424 
   425         /// <summary>
   426         /// Gets or sets the HTML formatted long-form (body) of the message.
   427         /// Warning: this value can be null.
   428         ///          MailItem : Corresponds to property 'HTMLBody' (also BodyFormat)
   429         /// CryptableMailItem : not exposed (processed internally)
   430         ///       TextMessage : Corresponds to property 'LongMsgFormatted'
   431         /// </summary>
   432         public string LongMsgFormattedHtml
   433         {
   434             get { return (this._LongMsgFormattedHtml); }
   435             set
   436             {
   437                 this._LongMsgFormattedHtml = value;
   438                 this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedHtml));
   439             }
   440         }
   441 
   442         /// <summary>
   443         /// Gets or sets the RTF formatted long-form (body) of the message.
   444         /// Warning: this value can be null, it is only supported when creating from a MailItem. It is
   445         /// not applied to a MailItem during .ApplyTo as it's unsupported by the engine.
   446         ///          MailItem : Corresponds to property 'RTFBody' (also BodyFormat)
   447         /// CryptableMailItem : not exposed (processed internally)
   448         ///       TextMessage : not supported
   449         /// </summary>
   450         public string LongMsgFormattedRtf
   451         {
   452             get { return (this._LongMsgFormattedRtf); }
   453             set
   454             {
   455                 this._LongMsgFormattedRtf = value;
   456                 this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedRtf));
   457             }
   458         }
   459 
   460         /// <summary>
   461         /// Gets or sets whether this message is marked to never be unsecure.
   462         /// Note: While this is stored as a header field, it is OK to apply to any MailItem and outgoing messages.
   463         /// It's intended function is to flag a message to keep it encrypted.
   464         ///          MailItem : Corresponds to MAPI Property PidNamePEPNeverUnsecure (Header field)
   465         /// CryptableMailItem : Corresponds to property 'NeverUnsecure'
   466         ///       TextMessage : not supported
   467         /// </summary>
   468         public bool NeverUnsecure
   469         {
   470             get { return (this._NeverUnsecure); }
   471             set
   472             {
   473                 this._NeverUnsecure = value;
   474                 this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
   475             }
   476         }
   477 
   478         /// <summary>
   479         /// Gets or sets the pEp protocol (or 'format') version of this message.
   480         /// This is not to be confused with pEp engine version which can be different.
   481         /// This is commonly set after decryption.
   482         /// Note: While this is stored as a header field, it is OK to apply to any MailItem and outgoing messages.
   483         /// It's intended function is just to show the version of pEp last used to process the message.
   484         ///          MailItem : Corresponds to MAPI Property PidNamePEPProtocolVersion (Header field)
   485         /// CryptableMailItem : not supported
   486         ///       TextMessage : Corresponds with the key PR_PEP_PROTOCOL_VERSION_NAME within 'opt_fields' array. 
   487         /// </summary>
   488         public string PEPProtocolVersion
   489         {
   490             get { return (this._PEPProtocolVersion); }
   491             set
   492             {
   493                 this._PEPProtocolVersion = value;
   494                 this.RaisePropertyChangedEvent(nameof(this.PEPProtocolVersion));
   495             }
   496         }
   497 
   498         /// <summary>
   499         /// Gets or sets the pEp rating of the message.
   500         /// This should corresponds primarily with the decryption rating.
   501         /// Warning: Since this is stored as a header field, care must be taken not to apply this to a MailItem on an 
   502         /// untrusted server or on an outgoing message.
   503         ///          MailItem : Corresponds to MAPI Property PidNameEncStatus (Header field)
   504         /// CryptableMailItem : not exposed (processed internally)
   505         ///       TextMessage : Corresponds with the key PR_ENC_STATUS_NAME within 'opt_fields' array
   506         /// </summary>
   507         public pEpRating Rating
   508         {
   509             get { return (this._Rating); }
   510             set
   511             {
   512                 this._Rating = value;
   513                 this.RaisePropertyChangedEvent(nameof(this.Rating));
   514             }
   515         }
   516 
   517         /// <summary>
   518         /// Gets or sets the date and time when the message was received.
   519         ///          MailItem : Corresponds to property 'ReceivedTime'
   520         /// CryptableMailItem : not supported
   521         ///       TextMessage : Corresponds to property 'recv'
   522         /// </summary>
   523         public DateTime? ReceivedOn
   524         {
   525             get { return (this._ReceivedOn); }
   526             set
   527             {
   528                 this._ReceivedOn = value;
   529                 this.RaisePropertyChangedEvent(nameof(this.ReceivedOn));
   530             }
   531         }
   532 
   533         /// <summary>
   534         /// Gets the list of identities in the Reply-To field.
   535         ///          MailItem : Corresponds to property 'ReplyRecipients' 
   536         ///       TextMessage : Corresponds to property 'ReplyTo'
   537         /// </summary>
   538         public List<PEPIdentity> ReplyTo
   539         {
   540             get { return (this._ReplyTo); }
   541         }
   542 
   543         /// <summary>
   544         /// Gets or sets the date and time when the message was sent.
   545         ///          MailItem : Corresponds to property 'SentOn'
   546         /// CryptableMailItem : not supported
   547         ///       TextMessage : Corresponds to property 'sent'
   548         /// </summary>
   549         public DateTime? SentOn
   550         {
   551             get { return (this._SentOn); }
   552             set
   553             {
   554                 this._SentOn = value;
   555                 this.RaisePropertyChangedEvent(nameof(this.SentOn));
   556             }
   557         }
   558 
   559         /// <summary>
   560         /// Gets or sets the short-form (subject) of the message.
   561         /// Warning: this value can be null.
   562         ///          MailItem : Corresponds to property 'Subject'
   563         /// CryptableMailItem : not supported
   564         ///       TextMessage : Corresponds to property 'Shortmsg'
   565         /// </summary>
   566         public string ShortMsg
   567         {
   568             get { return (this._ShortMsg); }
   569             set
   570             {
   571                 this._ShortMsg = value;
   572                 this.RaisePropertyChangedEvent(nameof(this.ShortMsg));
   573             }
   574         }
   575 
   576         /// <summary>
   577         /// Gets the list of identities to receive the message.
   578         ///          MailItem : Component of property 'Recipients' which contains 'Recipient'
   579         /// CryptableMailItem : not exposed directly (part of Recipients)
   580         ///       TextMessage : Corresponds to property 'To'
   581         /// </summary>
   582         public List<PEPIdentity> To
   583         {
   584             get { return (this._To); }
   585         }
   586 
   587         /// <summary>
   588         /// Gets the total number of all recipients in the message (Bcc, Cc &amp; To).
   589         /// </summary>
   590         public int RecipientCount
   591         {
   592             get { return (this._Bcc.Count + this._Cc.Count + this._To.Count); }
   593         }
   594 
   595         /// <summary>
   596         /// Gets a list of all recipients in the message (Bcc, Cc &amp; To).
   597         /// Each recipient in the returned list is a copy.
   598         /// </summary>
   599         public PEPIdentity[] Recipients
   600         {
   601             get
   602             {
   603                 List<PEPIdentity> recipients = new List<PEPIdentity>();
   604 
   605                 // Bcc
   606                 for (int i = 0; i < this._Bcc.Count; i++)
   607                 {
   608                     recipients.Add(this._Bcc[i].Copy());
   609                 }
   610 
   611                 // Cc
   612                 for (int i = 0; i < this._Cc.Count; i++)
   613                 {
   614                     recipients.Add(this._Cc[i].Copy());
   615                 }
   616 
   617                 // To
   618                 for (int i = 0; i < this._To.Count; i++)
   619                 {
   620                     recipients.Add(this._To[i].Copy());
   621                 }
   622 
   623                 return (recipients.ToArray());
   624             }
   625         }
   626 
   627         #endregion
   628 
   629         /**************************************************************
   630          * 
   631          * Methods
   632          * 
   633          *************************************************************/
   634 
   635         /// <summary>
   636         /// Raises the property changed event, if possible, with the given arguments.
   637         /// </summary>
   638         /// <param name="propertyName">The name of the property that changed.</param>
   639         private void RaisePropertyChangedEvent(string propertyName)
   640         {
   641             this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   642         }
   643 
   644         /// <summary>
   645         /// Gets the outgoing rating of this message.
   646         /// <param name="ignoreOptions">Ignore user settings like ForceProtection or
   647         /// ForceUnencrypted and return always calculated engine rating.</param>
   648         /// </summary>
   649         public pEpRating GetOutgoingRating(bool ignoreOptions = false,
   650                                            bool previewOnly = false)
   651         {
   652             pEpRating rating = pEpRating.pEpRatingUndefined;
   653 
   654 #if READER_RELEASE_MODE
   655             // If reader mode, always unencrypted
   656             rating = pEpRating.pEpRatingUnencrypted;
   657 #else
   658             // If message has BCC recipients, always return unencrypted
   659             if (this.Bcc?.Count > 0)
   660             {
   661                 return pEpRating.pEpRatingUnencrypted;
   662             }
   663 
   664             if (ignoreOptions == false)
   665             {
   666                 /* If message is forcefully unencrypted, return Unencrypted.
   667                  * If message is forcefully encrypted, return Reliable.
   668                  */
   669                 if (this.ForceUnencrypted)
   670                 {
   671                     return pEpRating.pEpRatingUnencrypted;
   672                 }
   673                 else if (string.IsNullOrEmpty(this.ForceProtectionId) == false)
   674                 {
   675                     return pEpRating.pEpRatingReliable;
   676                 }
   677             }
   678 
   679             // If we have no rating at this point, calculate it
   680             PEPMessage workingMessage = this.Copy(true);
   681             workingMessage.FlattenAllRecipientIdentities();
   682             workingMessage.Direction = pEpMsgDirection.pEpDirOutgoing;
   683 
   684             try
   685             {
   686                 if (previewOnly)
   687                 {
   688                     rating = ThisAddIn.PEPEngine.OutgoingMessageRatingPreview(workingMessage.ToCOMType());
   689                 }
   690                 else
   691                 {
   692                     rating = ThisAddIn.PEPEngine.OutgoingMessageRating(workingMessage.ToCOMType());
   693                 }
   694             }
   695             catch (Exception ex)
   696             {
   697                 rating = pEpRating.pEpRatingUndefined;
   698                 Log.Error("GetOutgoingRating: Error getting outgoing rating from engine. " + ex.ToString());
   699             }
   700 
   701 #endif
   702             return rating;
   703         }
   704 
   705         /// <summary>
   706         /// Returns this pEp message as a new pEp engine TextMessage struct.
   707         /// Warning: Any identity members (groups) are lost, call FlattenAllRecipientIdentities() before this method.
   708         /// </summary>
   709         /// <param name="alwaysAddOptFields">Whether to always add optional fields (default is false). When false, 
   710         /// optional fields will not be added if they are undefined/null/empty in the PEPMessage. This is to prevent 
   711         /// against adding optional fields when they have never been set.
   712         /// </param>
   713         /// <returns>A pEp engine identity.</returns>
   714         public TextMessage ToCOMType(bool alwaysAddOptFields = false)
   715         {
   716             long recvSec;
   717             long sentSec;
   718             TimeSpan span;
   719             List<Blob> attachments = new List<Blob>();
   720             List<pEpIdentity> bcc = new List<pEpIdentity>();
   721             List<pEpIdentity> cc = new List<pEpIdentity>();
   722             List<pEpIdentity> to = new List<pEpIdentity>();
   723             List<pEpIdentity> replyTo = new List<pEpIdentity>();
   724             List<StringPair> optionalFields = new List<StringPair>();
   725             StringPair field;
   726             TextMessage result = new TextMessage();
   727 
   728             // Convert attachments
   729             for (int i = 0; i < this._Attachments.Count; i++)
   730             {
   731                 attachments.Add(this._Attachments[i].ToCOMType());
   732             }
   733 
   734             // Convert Bcc
   735             for (int i = 0; i < this._Bcc.Count; i++)
   736             {
   737                 bcc.Add(this._Bcc[i].ToCOMType());
   738             }
   739 
   740             // Convert Cc
   741             for (int i = 0; i < this._Cc.Count; i++)
   742             {
   743                 cc.Add(this._Cc[i].ToCOMType());
   744             }
   745 
   746             // Convert To
   747             for (int i = 0; i < this._To.Count; i++)
   748             {
   749                 to.Add(this._To[i].ToCOMType());
   750             }
   751 
   752             // Create optional fields
   753             if ((alwaysAddOptFields) ||
   754                 (string.IsNullOrEmpty(this._AutoConsume) == false))
   755             {
   756                 field = new StringPair
   757                 {
   758                     Name = PEPMessage.PR_PEP_AUTO_CONSUME_NAME,
   759                     Value = this._AutoConsume
   760                 };
   761                 optionalFields.Add(field);
   762             }
   763 
   764             if ((alwaysAddOptFields) ||
   765                 (string.IsNullOrEmpty(this._ForceProtectionId) == false))
   766             {
   767                 field = new StringPair
   768                 {
   769                     Name = PEPMessage.PR_PEP_FORCE_PROTECTION_NAME,
   770                     Value = this._ForceProtectionId
   771                 };
   772                 optionalFields.Add(field);
   773             }
   774 
   775             if ((alwaysAddOptFields) ||
   776                 (string.IsNullOrEmpty(this._KeyImport) == false))
   777             {
   778                 field = new StringPair
   779                 {
   780                     Name = PEPMessage.PR_PEP_KEY_IMPORT_NAME,
   781                     Value = this._KeyImport
   782                 };
   783                 optionalFields.Add(field);
   784             }
   785 
   786             if ((alwaysAddOptFields) ||
   787                 (string.IsNullOrEmpty(this._KeyList) == false))
   788             {
   789                 field = new StringPair
   790                 {
   791                     Name = PEPMessage.PR_KEY_LIST_NAME,
   792                     Value = this._KeyList
   793                 };
   794                 optionalFields.Add(field);
   795             }
   796 
   797             if ((alwaysAddOptFields) ||
   798                 (this._NeverUnsecure == true))
   799             {
   800                 field = new StringPair
   801                 {
   802                     Name = PEPMessage.PR_PEP_NEVER_UNSECURE_NAME,
   803                     Value = PEPMessage.PR_PEP_NEVER_UNSECURE_VALUE
   804                 };
   805                 optionalFields.Add(field);
   806             }
   807 
   808             if ((alwaysAddOptFields) ||
   809                 (string.IsNullOrEmpty(this._PEPProtocolVersion) == false))
   810             {
   811                 field = new StringPair
   812                 {
   813                     Name = PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME,
   814                     Value = this._PEPProtocolVersion
   815                 };
   816                 optionalFields.Add(field);
   817             }
   818 
   819             if ((alwaysAddOptFields) ||
   820                 (this._Rating != pEpRating.pEpRatingUndefined))
   821             {
   822                 field = new StringPair
   823                 {
   824                     Name = PEPMessage.PR_ENC_STATUS_NAME,
   825                     Value = this._Rating.ToEngineString()
   826                 };
   827                 optionalFields.Add(field);
   828             }
   829 
   830             // Add additionalHeaders
   831             foreach (var additionalHeader in this._AdditionalHeaders)
   832             {
   833                 optionalFields.Add(additionalHeader);
   834             }
   835 
   836             // ReceivedOn
   837             recvSec = -1;
   838             if (this._ReceivedOn != null)
   839             {
   840                 DateTime receivedOnUtc;
   841                 try
   842                 {
   843                     receivedOnUtc = TimeZoneInfo.ConvertTimeToUtc((DateTime)this._ReceivedOn);
   844                 }
   845                 catch (Exception ex)
   846                 {
   847                     Log.Verbose("PEPMessage.ToCOMType: Error converting received time to UTC. " + ex.ToString());
   848                     receivedOnUtc = (DateTime)this._ReceivedOn;
   849                 }
   850                 span = receivedOnUtc - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
   851                 recvSec = (long)span.TotalSeconds;
   852             }
   853 
   854             // Convert ReplyTo
   855             for (int i = 0; i < this._ReplyTo.Count; i++)
   856             {
   857                 replyTo.Add(this.ReplyTo[i].ToCOMType());
   858             }
   859 
   860             // SentOn
   861             sentSec = -1;
   862             if (this._SentOn != null)
   863             {
   864                 DateTime sentOnUtc;
   865                 try
   866                 {
   867                     sentOnUtc = TimeZoneInfo.ConvertTimeToUtc((DateTime)this._SentOn);
   868                 }
   869                 catch (Exception ex)
   870                 {
   871                     Log.Verbose("PEPMessage.ToCOMType: Error converting sent time to UTC. " + ex.ToString());
   872                     sentOnUtc = (DateTime)this._SentOn;
   873                 }
   874                 span = sentOnUtc - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
   875                 sentSec = (long)span.TotalSeconds;
   876             }
   877 
   878             /* Note: Skip the following properties which are not supported in TextMessage
   879              *   • ConversationId
   880              *   • ConversationIndex
   881              *   • ConversationTopic
   882              *   • EnableProtection
   883              *   • ForceUnencrypted
   884              *   • LongMsgFormattedRtf
   885              * 
   886              * Also not the following are handled as optional fields
   887              *   • Rating
   888              *   • ForceProtection
   889              *   • NeverUnsecure
   890              *   • KeyImport
   891              *   • KeyList
   892              *   • PEPProtocolVersion
   893              * 
   894              * This also skips a number of TextMessage properties currently unsupported in PEPMessage.
   895              */
   896             result.Attachments = attachments.ToArray();
   897             result.Bcc = bcc.ToArray();
   898             result.Cc = cc.ToArray();
   899             result.Dir = this._Direction;
   900             result.From = (this._From != null ? this._From.ToCOMType() : result.From);
   901             result.Id = this._Id;
   902             result.Keywords = this._Keywords.ToArray();
   903             result.LongMsg = this._LongMsg;
   904             result.LongMsgFormatted = this._LongMsgFormattedHtml;
   905             result.OptFields = optionalFields.ToArray();
   906             result.Recv = ((recvSec >= 0) ? recvSec : result.Recv);
   907             result.ReplyTo = replyTo.ToArray();
   908             result.Sent = ((sentSec >= 0) ? sentSec : result.Sent);
   909             result.ShortMsg = this._ShortMsg;
   910             result.To = to.ToArray();
   911 
   912             return (result);
   913         }
   914 
   915         public static bool SaveAsEML(PEPMessage pEpMessage, string filePath)
   916         {
   917             bool result = false;
   918 
   919             if (PEPMessage.ToMIMEMessage(pEpMessage, out MimeMessage message) == Globals.ReturnStatus.Success)
   920             {
   921                 try
   922                 {
   923                     message.WriteTo(filePath);
   924                     result = true;
   925                 }
   926                 catch (Exception ex)
   927                 {
   928                     filePath = null;
   929                     Log.Error("SaveAsEml: Error saving mail item to EML file. " + ex.ToString());
   930                 }
   931             }
   932 
   933             return result;
   934         }
   935 
   936         /// <summary>
   937         /// Converts a PEPMessage into a MimeKit.MimeMessage.
   938         /// </summary>
   939         /// <param name="pEpMessage">The PEPMessage to convert.</param>
   940         /// <param name="mimeMessage">The created MimeMessage.</param>
   941         /// <returns>The status of this method.</returns>
   942         public static Globals.ReturnStatus ToMIMEMessage(PEPMessage pEpMessage, out MimeMessage mimeMessage)
   943         {
   944             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
   945             MimeMessage message = new MimeMessage();
   946             BodyBuilder bodyBuilder = new BodyBuilder();
   947 
   948             // Convert TextMessage to MimeMessage
   949             // Add attachments to body builder
   950             for (int i = 0; i < pEpMessage.Attachments?.Count; i++)
   951             {
   952                 try
   953                 {
   954                     PEPAttachment currentAttachment = pEpMessage.Attachments[i];
   955                     MimePart messageAttachment = bodyBuilder.Attachments.Add(currentAttachment.FileName, currentAttachment.Data) as MimePart;
   956 
   957                     // In case of inline attachments, add content id
   958                     if (string.IsNullOrEmpty(currentAttachment.ContentId) == false)
   959                     {
   960                         messageAttachment.ContentId = currentAttachment.ContentId;
   961                         messageAttachment.ContentDisposition = new ContentDisposition(ContentDisposition.Inline);
   962                     }
   963                 }
   964                 catch (Exception ex)
   965                 {
   966                     status = Globals.ReturnStatus.Failure;
   967                     Log.Error("ToMIMEMessage: Error parsing attachment. " + ex.ToString());
   968                 }
   969             }
   970 
   971             // Add body to body builder
   972             bodyBuilder.TextBody = pEpMessage.LongMsg;
   973             bodyBuilder.HtmlBody = pEpMessage.LongMsgFormattedHtml;
   974 
   975             // Add attachments and body to message
   976             message.Body = bodyBuilder.ToMessageBody();
   977 
   978             // Process recipients
   979             // BCC
   980             for (int i = 0; i < pEpMessage.Bcc?.Count; i++)
   981             {
   982                 try
   983                 {
   984                     if (InternetAddress.TryParse(pEpMessage.Bcc[i].Address, out InternetAddress address))
   985                     {
   986                         address.Name = pEpMessage.Bcc[i].UserName;
   987                         message.Bcc.Add(address);
   988                     }
   989                 }
   990                 catch (Exception ex)
   991                 {
   992                     status = Globals.ReturnStatus.Failure;
   993                     Log.Error("ToMIMEMessage: Error parsing BCC identity. " + ex.ToString());
   994                 }
   995             }
   996 
   997             // CC
   998             for (int i = 0; i < pEpMessage.Cc?.Count; i++)
   999             {
  1000                 try
  1001                 {
  1002                     if (InternetAddress.TryParse(pEpMessage.Cc[i].Address, out InternetAddress address))
  1003                     {
  1004                         address.Name = pEpMessage.Cc[i].UserName;
  1005                         message.Cc.Add(address);
  1006                     }
  1007                 }
  1008                 catch (Exception ex)
  1009                 {
  1010                     status = Globals.ReturnStatus.Failure;
  1011                     Log.Error("ToMIMEMessage: Error parsing CC identity. " + ex.ToString());
  1012                 }
  1013             }
  1014 
  1015             // To
  1016             for (int i = 0; i < pEpMessage.To?.Count; i++)
  1017             {
  1018                 try
  1019                 {
  1020                     if (InternetAddress.TryParse(pEpMessage.To[i].Address, out InternetAddress address))
  1021                     {
  1022                         address.Name = pEpMessage.To[i].UserName;
  1023                         message.To.Add(address);
  1024                     }
  1025                 }
  1026                 catch (Exception ex)
  1027                 {
  1028                     status = Globals.ReturnStatus.Failure;
  1029                     Log.Error("ToMIMEMessage: Error parsing To identity. " + ex.ToString());
  1030                 }
  1031             }
  1032 
  1033             // From
  1034             try
  1035             {
  1036                 if (InternetAddress.TryParse(pEpMessage.From.Address, out InternetAddress from))
  1037                 {
  1038                     from.Name = pEpMessage.From.UserName;
  1039                     message.From.Add(from);
  1040                 }
  1041             }
  1042             catch (Exception ex)
  1043             {
  1044                 status = Globals.ReturnStatus.Failure;
  1045                 Log.Error("ToMIMEMessage: Error parsing From identity. " + ex.ToString());
  1046             }
  1047 
  1048             // Message ID
  1049             if (string.IsNullOrEmpty(pEpMessage.Id) == false)
  1050             {
  1051                 message.MessageId = pEpMessage.Id;
  1052             }
  1053 
  1054             // ReplyTo
  1055             for (int i = 0; i < pEpMessage.ReplyTo?.Count; i++)
  1056             {
  1057                 try
  1058                 {
  1059                     if (InternetAddress.TryParse(pEpMessage.ReplyTo[i].Address, out InternetAddress address))
  1060                     {
  1061                         address.Name = pEpMessage.ReplyTo[i].UserName;
  1062                         message.ReplyTo.Add(address);
  1063                     }
  1064                 }
  1065                 catch (Exception ex)
  1066                 {
  1067                     status = Globals.ReturnStatus.Failure;
  1068                     Log.Error("ToMIMEMessage: Error parsing ReplyTo identity. " + ex.ToString());
  1069                 }
  1070             }
  1071 
  1072             // Subject
  1073             if (string.IsNullOrEmpty(pEpMessage.ShortMsg) == false)
  1074             {
  1075                 message.Subject = pEpMessage.ShortMsg;
  1076             }
  1077 
  1078             // Add pEp header fields
  1079             if (string.IsNullOrEmpty(pEpMessage.AutoConsume) == false)
  1080             {
  1081                 try
  1082                 {
  1083                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_PEP_AUTO_CONSUME_NAME, pEpMessage.AutoConsume);
  1084                     message.Headers.Add(header);
  1085                 }
  1086                 catch (Exception ex)
  1087                 {
  1088                     status = Globals.ReturnStatus.Failure;
  1089                     Log.Error("ToMIMEMessage: Error adding AutoConsume header. " + ex.ToString());
  1090                 }
  1091             }
  1092 
  1093             if (string.IsNullOrEmpty(pEpMessage.ForceProtectionId) == false)
  1094             {
  1095                 try
  1096                 {
  1097                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_PEP_FORCE_PROTECTION_NAME, pEpMessage.ForceProtectionId);
  1098                     message.Headers.Add(header);
  1099                 }
  1100                 catch (Exception ex)
  1101                 {
  1102                     status = Globals.ReturnStatus.Failure;
  1103                     Log.Error("ToMIMEMessage: Error adding ForceProtection header. " + ex.ToString());
  1104                 }
  1105             }
  1106 
  1107             if (string.IsNullOrEmpty(pEpMessage.KeyImport) == false)
  1108             {
  1109                 try
  1110                 {
  1111                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_PEP_KEY_IMPORT_NAME, pEpMessage.KeyImport);
  1112                     message.Headers.Add(header);
  1113                 }
  1114                 catch (Exception ex)
  1115                 {
  1116                     status = Globals.ReturnStatus.Failure;
  1117                     Log.Error("ToMIMEMessage: Error adding KeyImportFpr header. " + ex.ToString());
  1118                 }
  1119             }
  1120 
  1121             if (string.IsNullOrEmpty(pEpMessage.KeyList) == false)
  1122             {
  1123                 try
  1124                 {
  1125                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_KEY_LIST_NAME, pEpMessage.KeyList);
  1126                     message.Headers.Add(header);
  1127                 }
  1128                 catch (Exception ex)
  1129                 {
  1130                     status = Globals.ReturnStatus.Failure;
  1131                     Log.Error("ToMIMEMessage: Error adding KeyList header. " + ex.ToString());
  1132                 }
  1133             }
  1134 
  1135             if (pEpMessage.NeverUnsecure)
  1136             {
  1137                 try
  1138                 {
  1139                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_PEP_NEVER_UNSECURE_NAME, PEPMessage.PR_PEP_NEVER_UNSECURE_VALUE);
  1140                     message.Headers.Add(header);
  1141                 }
  1142                 catch (Exception ex)
  1143                 {
  1144                     status = Globals.ReturnStatus.Failure;
  1145                     Log.Error("ToMIMEMessage: Error adding NeverUnsecure header. " + ex.ToString());
  1146                 }
  1147             }
  1148 
  1149             if (string.IsNullOrEmpty(pEpMessage.PEPProtocolVersion) == false)
  1150             {
  1151                 try
  1152                 {
  1153                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME, pEpMessage.PEPProtocolVersion);
  1154                     message.Headers.Add(header);
  1155                 }
  1156                 catch (Exception ex)
  1157                 {
  1158                     status = Globals.ReturnStatus.Failure;
  1159                     Log.Error("ToMIMEMessage: Error adding AutConsume header. " + ex.ToString());
  1160                 }
  1161             }
  1162 
  1163             if (pEpMessage.Rating != pEpRating.pEpRatingUndefined)
  1164             {
  1165                 try
  1166                 {
  1167                     Header header = new Header(Encoding.Unicode, PEPMessage.PR_ENC_STATUS_NAME, pEpMessage.Rating.ToEngineString());
  1168                     message.Headers.Add(header);
  1169                 }
  1170                 catch (Exception ex)
  1171                 {
  1172                     status = Globals.ReturnStatus.Failure;
  1173                     Log.Error("ToMIMEMessage: Error adding AutConsume header. " + ex.ToString());
  1174                 }
  1175             }
  1176 
  1177             // Additional headers
  1178             foreach (var additionalHeader in pEpMessage.AdditionalHeaders)
  1179             {
  1180                 message.Headers?.Add(new Header(additionalHeader.Name, additionalHeader.Value));
  1181             }
  1182 
  1183             // Message date
  1184             try
  1185             {
  1186                 if (pEpMessage.ReceivedOn != null)
  1187                 {
  1188                     message.Date = new DateTimeOffset((DateTime)pEpMessage.ReceivedOn);
  1189                 }
  1190                 else if (pEpMessage.SentOn != null)
  1191                 {
  1192                     message.Date = new DateTimeOffset((DateTime)pEpMessage.SentOn);
  1193                 }
  1194             }
  1195             catch (Exception ex)
  1196             {
  1197                 status = Globals.ReturnStatus.Failure;
  1198                 Log.Error("ToMIMEMessage: Error setting message date. " + ex.ToString());
  1199             }
  1200 
  1201             // Return MimeMessage
  1202             mimeMessage = message;
  1203 
  1204             return status;
  1205         }
  1206 
  1207         /// <summary>
  1208         /// Serves as a hash function for a particular type.
  1209         /// </summary>
  1210         /// <returns>A hash code for the current object.</returns>
  1211         public override int GetHashCode()
  1212         {
  1213             return base.GetHashCode();
  1214         }
  1215 
  1216         /// <summary>
  1217         /// Indicates whether the current object is equal to another object of the same type.
  1218         /// </summary>
  1219         /// <param name="obj">The object to check equality with.</param>
  1220         /// <returns>True if both objects are considered equal, otherwise false.</returns>
  1221         public override bool Equals(object obj)
  1222         {
  1223             if ((obj == null) ||
  1224                 !(obj is PEPMessage))
  1225             {
  1226                 return (false);
  1227             }
  1228 
  1229             return (this.Equals((PEPMessage)obj));
  1230         }
  1231 
  1232         /// <summary>
  1233         /// Indicates whether the current object is equal to another object of the same type.
  1234         /// </summary>
  1235         /// <param name="obj">The object to check equality with.</param>
  1236         /// <returns>True if both objects are considered equal, otherwise false.</returns>
  1237         public bool Equals(PEPMessage obj)
  1238         {
  1239             if (obj == null)
  1240             {
  1241                 return (false);
  1242             }
  1243 
  1244             if (Comparisons.Equals(this.AdditionalHeaders, obj.AdditionalHeaders) &&
  1245                 Comparisons.Equals(this.Attachments, obj.Attachments) &&
  1246                 Comparisons.Equals(this.Bcc, obj.Bcc) &&
  1247                 Comparisons.Equals(this.Cc, obj.Cc) &&
  1248                 Comparisons.Equals(this.ConversationId, obj.ConversationId) &&
  1249                 Comparisons.Equals(this.ConversationIndex, obj.ConversationIndex) &&
  1250                 Comparisons.Equals(this.ConversationTopic, obj.ConversationTopic) &&
  1251                 Comparisons.Equals(this.Direction, obj.Direction) &&
  1252                 Comparisons.Equals(this.EnableProtection, obj.EnableProtection) &&
  1253                 Comparisons.Equals(this.ForceProtectionId, obj.ForceProtectionId) &&
  1254                 Comparisons.Equals(this.ForceUnencrypted, obj.ForceUnencrypted) &&
  1255                 Comparisons.Equals(this.From, obj.From) &&
  1256                 Comparisons.Equals(this.Id, obj.Id) &&
  1257                 Comparisons.Equals(this.KeyImport, obj.KeyImport) &&
  1258                 Comparisons.Equals(this.KeyList, obj.KeyList) &&
  1259                 Comparisons.Equals(this.Keywords, obj.Keywords) &&
  1260                 Comparisons.Equals(this.LongMsg, obj.LongMsg) &&
  1261                 Comparisons.Equals(this.LongMsgFormattedHtml, obj.LongMsgFormattedHtml) &&
  1262                 Comparisons.Equals(this.LongMsgFormattedRtf, obj.LongMsgFormattedRtf) &&
  1263                 Comparisons.Equals(this.NeverUnsecure, obj.NeverUnsecure) &&
  1264                 Comparisons.Equals(this.PEPProtocolVersion, obj.PEPProtocolVersion) &&
  1265                 Comparisons.Equals(this.Rating, obj.Rating) &&
  1266                 Comparisons.Equals(this.ReceivedOn, obj.ReceivedOn) &&
  1267                 Comparisons.Equals(this.ReplyTo, obj.ReplyTo) &&
  1268                 Comparisons.Equals(this.SentOn, obj.SentOn) &&
  1269                 Comparisons.Equals(this.ShortMsg, obj.ShortMsg) &&
  1270                 Comparisons.Equals(this.To, obj.To))
  1271             {
  1272                 return (true);
  1273             }
  1274 
  1275             return (false);
  1276         }
  1277 
  1278         /// <summary>
  1279         /// Indicates whether the content of the current PEPMessage is equal to another one's.
  1280         /// </summary>
  1281         /// <param name="obj">The object to check equality with.</param>
  1282         /// <returns>True if both objects' content is considered equal, otherwise false.</returns>
  1283         public bool EqualsByContent(PEPMessage obj)
  1284         {
  1285             if (obj == null)
  1286             {
  1287                 return false;
  1288             }
  1289 
  1290             if (Comparisons.Equals(this.ConversationTopic, obj.ConversationTopic) &&
  1291                 Comparisons.Equals(this.Keywords, obj.Keywords) &&
  1292                 Comparisons.Equals(this.LongMsg, obj.LongMsg) &&
  1293                 Comparisons.Equals(this.LongMsgFormattedHtml, obj.LongMsgFormattedHtml) &&
  1294                 Comparisons.Equals(this.LongMsgFormattedRtf, obj.LongMsgFormattedRtf) &&
  1295                 Comparisons.Equals(this.ShortMsg, obj.ShortMsg))
  1296             {
  1297                 return true;
  1298             }
  1299             else
  1300             {
  1301                 return false;
  1302             }
  1303         }
  1304 
  1305         /// <summary>
  1306         /// Gets a deep copy of the object and all its data.
  1307         /// </summary>
  1308         /// <returns>The deep copy of the object.</returns>
  1309         public PEPMessage Copy()
  1310         {
  1311             return (this.Copy(false));
  1312         }
  1313 
  1314         /// <summary>
  1315         /// Gets a copy of the PEPMessage with or without data.
  1316         /// </summary>
  1317         /// <param name="createWithoutContent">Whether to include content such as text body, attachments 
  1318         /// and optional properties.</param>
  1319         /// <returns>The copy of the PEPMessage.</returns>
  1320         public PEPMessage Copy(bool createWithoutContent = false)
  1321         {
  1322             PEPMessage copy = new PEPMessage();
  1323 
  1324             // Additional headers
  1325             copy.AdditionalHeaders.Clear();
  1326             foreach (var additionalHeader in this._AdditionalHeaders)
  1327             {
  1328                 copy.AdditionalHeaders.Add(additionalHeader);
  1329             }
  1330 
  1331             // Attachments
  1332             copy.Attachments.Clear();
  1333             if (createWithoutContent == false)
  1334             {
  1335                 for (int i = 0; i < this._Attachments.Count; i++)
  1336                 {
  1337                     copy.Attachments.Add(this._Attachments[i].Copy());
  1338                 }
  1339             }
  1340 
  1341             copy.AutoConsume = (this._AutoConsume == null ? null : string.Copy(this._AutoConsume));
  1342 
  1343             // Bcc
  1344             copy.Bcc.Clear();
  1345             for (int i = 0; i < this._Bcc.Count; i++)
  1346             {
  1347                 copy.Bcc.Add(this._Bcc[i].Copy());
  1348             }
  1349 
  1350             // Cc
  1351             copy.Cc.Clear();
  1352             for (int i = 0; i < this._Cc.Count; i++)
  1353             {
  1354                 copy.Cc.Add(this._Cc[i].Copy());
  1355             }
  1356 
  1357             copy.ConversationId = (this._ConversationId == null ? null : string.Copy(this._ConversationId));
  1358             copy.ConversationIndex = (this._ConversationIndex == null ? null : string.Copy(this._ConversationIndex));
  1359             copy.ConversationTopic = (this._ConversationTopic == null ? null : string.Copy(this._ConversationTopic));
  1360             copy.Direction = this._Direction;
  1361             copy.EnableProtection = this._EnableProtection;
  1362             copy.ForceProtectionId = (this._ForceProtectionId == null ? null : string.Copy(this._ForceProtectionId));
  1363             copy.ForceUnencrypted = this._ForceUnencrypted;
  1364             copy.From = this._From?.Copy();
  1365             copy.Id = (this._Id == null ? null : string.Copy(this._Id));
  1366             copy.KeyImport = (this._KeyImport == null ? null : string.Copy(this._KeyImport));
  1367             copy.KeyList = (this._KeyList == null ? null : string.Copy(this._KeyList));
  1368 
  1369             // Keywords
  1370             copy.Keywords.Clear();
  1371             for (int i = 0; i < this._Keywords.Count; i++)
  1372             {
  1373                 copy.Keywords.Add(this._Keywords[i]);
  1374             }
  1375 
  1376             // Body
  1377             if (createWithoutContent == false)
  1378             {
  1379                 copy.LongMsg = (this._LongMsg == null ? null : string.Copy(this._LongMsg));
  1380                 copy.LongMsgFormattedHtml = (this._LongMsgFormattedHtml == null ? null : string.Copy(this._LongMsgFormattedHtml));
  1381                 copy.LongMsgFormattedRtf = (this._LongMsgFormattedRtf == null ? null : string.Copy(this._LongMsgFormattedRtf));
  1382             }
  1383             else
  1384             {
  1385                 copy.LongMsg = null;
  1386                 copy.LongMsgFormattedHtml = null;
  1387                 copy.LongMsgFormattedRtf = null;
  1388             }
  1389 
  1390             copy.NeverUnsecure = this._NeverUnsecure;
  1391             copy.PEPProtocolVersion = (this._PEPProtocolVersion == null ? null : string.Copy(this._PEPProtocolVersion));
  1392             copy.Rating = this._Rating;
  1393 
  1394             // ReceivedOn
  1395             if (this._ReceivedOn != null)
  1396             {
  1397                 copy.ReceivedOn = new DateTime(((DateTime)this._ReceivedOn).Ticks);
  1398             }
  1399             else
  1400             {
  1401                 copy.ReceivedOn = null;
  1402             }
  1403 
  1404             // ReplyTo
  1405             copy.ReplyTo.Clear();
  1406             for (int i = 0; i < this._ReplyTo.Count; i++)
  1407             {
  1408                 copy.ReplyTo.Add(this._ReplyTo[i].Copy());
  1409             }
  1410 
  1411             // SentOn
  1412             if (this._SentOn != null)
  1413             {
  1414                 copy.SentOn = new DateTime(((DateTime)this._SentOn).Ticks);
  1415             }
  1416             else
  1417             {
  1418                 copy.SentOn = null;
  1419             }
  1420 
  1421             copy.ShortMsg = (this._ShortMsg == null ? null : string.Copy(this._ShortMsg));
  1422 
  1423             // To
  1424             copy.To.Clear();
  1425             for (int i = 0; i < this._To.Count; i++)
  1426             {
  1427                 copy.To.Add(this._To[i].Copy());
  1428             }
  1429 
  1430             return (copy);
  1431         }
  1432 
  1433         /// <summary>
  1434         /// Resets the object to its default state/values.
  1435         /// </summary>
  1436         public void Reset()
  1437         {
  1438             this._AdditionalHeaders = new List<StringPair>();
  1439             this._Attachments = new List<PEPAttachment>();
  1440             this._AutoConsume = null;
  1441             this._Bcc = new List<PEPIdentity>();
  1442             this._Cc = new List<PEPIdentity>();
  1443             this._ConversationId = null;
  1444             this._ConversationIndex = null;
  1445             this._ConversationTopic = null;
  1446             this._Direction = pEpMsgDirection.pEpDirIncoming;
  1447             this._EnableProtection = false;
  1448             this._ForceProtectionId = null;
  1449             this._ForceUnencrypted = false;
  1450             this._From = null;
  1451             this._Id = null;
  1452             this._KeyImport = null;
  1453             this._KeyList = null;
  1454             this._Keywords = new List<string>();
  1455             this._LongMsg = null;
  1456             this._LongMsgFormattedHtml = null;
  1457             this._LongMsgFormattedRtf = null;
  1458             this._NeverUnsecure = false;
  1459             this._PEPProtocolVersion = null;
  1460             this._Rating = pEpRating.pEpRatingUndefined;
  1461             this._ReceivedOn = null;
  1462             this._ReplyTo = new List<PEPIdentity>();
  1463             this._SentOn = null;
  1464             this._ShortMsg = null;
  1465             this._To = new List<PEPIdentity>();
  1466 
  1467             this.RaisePropertyChangedEvent(nameof(this.Attachments));
  1468             this.RaisePropertyChangedEvent(nameof(this.AutoConsume));
  1469             this.RaisePropertyChangedEvent(nameof(this.Bcc));
  1470             this.RaisePropertyChangedEvent(nameof(this.Cc));
  1471             this.RaisePropertyChangedEvent(nameof(this.ConversationId));
  1472             this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
  1473             this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
  1474             this.RaisePropertyChangedEvent(nameof(this.Direction));
  1475             this.RaisePropertyChangedEvent(nameof(this.EnableProtection));
  1476             this.RaisePropertyChangedEvent(nameof(this.ForceProtectionId));
  1477             this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
  1478             this.RaisePropertyChangedEvent(nameof(this.From));
  1479             this.RaisePropertyChangedEvent(nameof(this.Id));
  1480             this.RaisePropertyChangedEvent(nameof(this.KeyImport));
  1481             this.RaisePropertyChangedEvent(nameof(this.KeyList));
  1482             this.RaisePropertyChangedEvent(nameof(this.Keywords));
  1483             this.RaisePropertyChangedEvent(nameof(this.LongMsg));
  1484             this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedHtml));
  1485             this.RaisePropertyChangedEvent(nameof(this.LongMsgFormattedRtf));
  1486             this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
  1487             this.RaisePropertyChangedEvent(nameof(this.PEPProtocolVersion));
  1488             this.RaisePropertyChangedEvent(nameof(this.Rating));
  1489             this.RaisePropertyChangedEvent(nameof(this.ReceivedOn));
  1490             this.RaisePropertyChangedEvent(nameof(this.ReplyTo));
  1491             this.RaisePropertyChangedEvent(nameof(this.SentOn));
  1492             this.RaisePropertyChangedEvent(nameof(this.ShortMsg));
  1493             this.RaisePropertyChangedEvent(nameof(this.To));
  1494         }
  1495 
  1496         /// <summary>
  1497         /// Copies main properties that are unsupported by the engine from the given message into this message.
  1498         /// This is commonly used to restore information that would otherwise be lost in conversions to/from 
  1499         /// TextMessage during engine processing.
  1500         /// Key properties that should be set separately for encryption concerns (such as LongMsgFormattedRtf) are ignored.
  1501         /// </summary>
  1502         /// <param name="msg">The message to copy over properties from.</param>
  1503         public void SetNonEnginePropertiesFrom(PEPMessage msg)
  1504         {
  1505             /* Include the following properties:
  1506              *   • AutoConsume
  1507              *   • ConversationId
  1508              *   • ConversationIndex
  1509              *   • ConversationTopic
  1510              *   • EnableProtection
  1511              *   • ForceProtection
  1512              *   • ForceUnencrypted
  1513              *   • NeverUnsecure
  1514              * 
  1515              * Also note:
  1516              *              Rating: This is handled by the decrypt function of the engine only. It should not be
  1517              *                      added back separately so is skipped.
  1518              *             KeyList: This is handled by the decrypt function of the engine only. It should not be
  1519              *                      added back separately so is skipped.
  1520              * LongMsgFormattedRtf: This is completely unsupported by the engine but otherwise should be encrypted.
  1521              *                      Due to this, it's completely skipped.
  1522              *  PEPProtocolVersion: This is handled in both the decrypt and encrypt functions of the engine.
  1523              *                      It should almost always be set within the TextMessage there isn't needed to add back.
  1524              */
  1525 
  1526             if (msg != null)
  1527             {
  1528                 this._AutoConsume = this._AutoConsume ?? msg.AutoConsume;
  1529                 this._ConversationId = msg.ConversationId;
  1530                 this._ConversationIndex = msg.ConversationIndex;
  1531                 this._ConversationTopic = msg.ConversationTopic;
  1532                 this._EnableProtection = msg.EnableProtection;
  1533                 this._ForceProtectionId = msg.ForceProtectionId;
  1534                 this._ForceUnencrypted = msg.ForceUnencrypted;
  1535                 this._NeverUnsecure = msg.NeverUnsecure;
  1536 
  1537                 this.RaisePropertyChangedEvent(nameof(this.AutoConsume));
  1538                 this.RaisePropertyChangedEvent(nameof(this.ConversationId));
  1539                 this.RaisePropertyChangedEvent(nameof(this.ConversationIndex));
  1540                 this.RaisePropertyChangedEvent(nameof(this.ConversationTopic));
  1541                 this.RaisePropertyChangedEvent(nameof(this.ForceProtectionId));
  1542                 this.RaisePropertyChangedEvent(nameof(this.EnableProtection));
  1543                 this.RaisePropertyChangedEvent(nameof(this.ForceUnencrypted));
  1544                 this.RaisePropertyChangedEvent(nameof(this.NeverUnsecure));
  1545             }
  1546 
  1547             return;
  1548         }
  1549 
  1550         /// <summary>
  1551         /// Gets the outgoing pEp rating of the given message based on the recipients force unencrypted status.
  1552         /// The IsForceUnencrypted property of each recipient must have already been set in the message.
  1553         /// This property is copied from any associated Outlook contact's force unencrypted user property.
  1554         /// </summary>
  1555         /// <param name="message">The message to get the rating for.</param>
  1556         /// <returns>The rating: undefined, reliable, unencrypted or unencrypted_for_some.
  1557         /// Will return undefined by default or if no recipients exist.</returns>
  1558         public pEpRating GetOutgoingRatingByRecipients()
  1559         {
  1560             // TODO: Check whether this method should be part of the engine?
  1561             int unencryptedCount = 0;
  1562             pEpRating rating = pEpRating.pEpRatingUndefined;
  1563             List<PEPIdentity> forceUnencryptedList = new List<PEPIdentity>();
  1564             PEPIdentity[] recipients;
  1565             PEPMessage workingMessage;
  1566 
  1567             workingMessage = this.Copy(true);
  1568             workingMessage.FlattenAllRecipientIdentities();
  1569 
  1570             recipients = workingMessage.Recipients;
  1571 
  1572             if (recipients.Length > 0)
  1573             {
  1574                 // Calculate for all recipients
  1575                 for (int i = 0; i < recipients.Length; i++)
  1576                 {
  1577                     if (recipients[i].IsForceUnencryptedBool)
  1578                     {
  1579                         unencryptedCount++;
  1580                     }
  1581                 }
  1582 
  1583                 // Final rating determination
  1584                 if (unencryptedCount == recipients.Length)
  1585                 {
  1586                     rating = pEpRating.pEpRatingUnencrypted;
  1587                 }
  1588                 else if (unencryptedCount == 0)
  1589                 {
  1590                     // Assume encrypted (will use engine rating in the end)
  1591                     rating = pEpRating.pEpRatingReliable;
  1592                 }
  1593                 else if (unencryptedCount < recipients.Length)
  1594                 {
  1595                     rating = pEpRating.pEpRatingUnencryptedForSome;
  1596                 }
  1597                 else
  1598                 {
  1599                     // Should never get here
  1600                     rating = pEpRating.pEpRatingUndefined;
  1601                 }
  1602             }
  1603 
  1604             return (rating);
  1605         }
  1606 
  1607         /// <summary>
  1608         /// Returns a property from a given MimeKitLite HeaderList.
  1609         /// </summary>
  1610         /// <param name="headerList">The header list to search.</param>
  1611         /// <param name="property">The property to get.</param>
  1612         /// <returns>The property value or null if not found.</returns>
  1613         public static object GetPropertyFromHeaderList(HeaderList headerList, string property)
  1614         {
  1615             return headerList?.SingleOrDefault(a => (a.Field?.Trim()?.Equals(property, StringComparison.OrdinalIgnoreCase) == true))?.Value?.Trim();
  1616         }
  1617 
  1618         /// <summary>
  1619         /// Gets a string containing the list of all To, Cc, and Bcc recipients.
  1620         /// </summary>
  1621         /// <returns>The list of recipients as a string.</returns>
  1622         public string GetRecipientsList()
  1623         {
  1624             string recipientsList = null;
  1625 
  1626             this.To?.ForEach((to) =>
  1627             {
  1628                 recipientsList += to.Address + ", ";
  1629             });
  1630 
  1631             this.Cc?.ForEach((to) =>
  1632             {
  1633                 recipientsList += to.Address + ", ";
  1634             });
  1635 
  1636             this.Bcc?.ForEach((to) =>
  1637             {
  1638                 recipientsList += to.Address + ", ";
  1639             });
  1640 
  1641             recipientsList = recipientsList?.TrimEnd(new char[] { ' ', ',' });
  1642 
  1643             return recipientsList;
  1644         }
  1645 
  1646         /// <summary>
  1647         /// Applies this pEp message's data to the given Outlook item.
  1648         /// </summary>
  1649         /// <param name="omi">The Outlook mail item to apply this pEp message's data to.</param>
  1650         /// <param name="setInternalHeaderFields">Whether to set internal header fields (Stored as MAPI properites) such as 
  1651         /// Rating and KeyList. Warning: Never apply these to outgoing messages -- used only for mirrors or internal MailItems.
  1652         /// Note that some header fields will be applied regardless such as NeverUnsecure or PEPProtocolVersion. 
  1653         /// That is because these are not considered internal only and don't contain sensitive information.</param>
  1654         /// <param name="setSender">Whether to set the sender manually. This is only needed when creating a sent message. 
  1655         /// Important: Setting this to true for outgoing messages can cause problems with Exchange accounts.</param>
  1656         /// <param name="setRecipients">Whether to set the message recipients manually. This should normally be set to true
  1657         /// for all common messages and makes only sense to be omitted in special cases like a FPP reply message.</param>
  1658         /// <param name="convertToPGPMIMEAttachment">Whether to convert the message into a PGP/MIME attachment if needed.
  1659         /// The default behaviour is to convert if the message is PGP/MIME encrypted, which is needed for all outgoing messages
  1660         /// in order for Outlook to send the message correctly. For special cases like when using EncryptForSelf, we might not 
  1661         /// want ot do that, so the parameter can be set to false.</param>
  1662         /// <returns>The status of the method.</returns>
  1663         public Globals.ReturnStatus ApplyTo(Outlook.MailItem omi,
  1664                                            bool setInternalHeaderFields,
  1665                                            bool setSender,
  1666                                            bool setRecipients = true,
  1667                                            bool? convertToPGPMIMEAttachment = null)
  1668         {
  1669             bool fromRecipientRemoved = false;
  1670             bool isPGPMIMEMsg = convertToPGPMIMEAttachment ?? this.IsPGPMIMEEncrypted;
  1671             Outlook.Attachments attachments = null;
  1672             Outlook.Recipient newRecipient = null;
  1673             Outlook.Recipients recipients = null;
  1674             Outlook.Account currAccount = null;
  1675             Outlook.Account sendUsingAccount = null;
  1676             Outlook.Accounts accounts = null;
  1677             Outlook.Recipient fromRecipient = null;
  1678             Outlook.NameSpace ns = null;
  1679             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  1680 
  1681             try
  1682             {
  1683                 ns = Globals.ThisAddIn.Application.Session;
  1684 
  1685                 if (setRecipients)
  1686                 {
  1687                     // Remove all existing recipients
  1688                     recipients = omi.Recipients;
  1689                     while (recipients.Count > 0)
  1690                     {
  1691                         recipients.Remove(1);
  1692                     }
  1693 
  1694                     // Set recipients
  1695                     for (int i = 0; i < this._Bcc.Count; i++)
  1696                     {
  1697                         if (string.IsNullOrWhiteSpace(this._Bcc[i].Address) == false)
  1698                         {
  1699                             // Add by address
  1700                             newRecipient = recipients.Add(this._Bcc[i].Address);
  1701                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olBCC;
  1702 
  1703                             // Marshal.ReleaseComObject(newRecipient);
  1704                             newRecipient = null;
  1705                         }
  1706                         else if (string.IsNullOrWhiteSpace(this._Bcc[i].UserName) == false)
  1707                         {
  1708                             // Add by user name (required for distribution lists)
  1709                             newRecipient = recipients.Add(this._Bcc[i].UserName);
  1710                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olBCC;
  1711 
  1712                             // Marshal.ReleaseComObject(newRecipient);
  1713                             newRecipient = null;
  1714                         }
  1715                     }
  1716 
  1717                     for (int i = 0; i < this._Cc.Count; i++)
  1718                     {
  1719                         if (string.IsNullOrWhiteSpace(this._Cc[i].Address) == false)
  1720                         {
  1721                             // Add by address
  1722                             newRecipient = recipients.Add(this._Cc[i].Address);
  1723                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olCC;
  1724 
  1725                             // Marshal.ReleaseComObject(newRecipient);
  1726                             newRecipient = null;
  1727                         }
  1728                         else if (string.IsNullOrWhiteSpace(this._Cc[i].UserName) == false)
  1729                         {
  1730                             // Add by user name (required for distribution lists)
  1731                             newRecipient = recipients.Add(this._Cc[i].UserName);
  1732                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olCC;
  1733 
  1734                             // Marshal.ReleaseComObject(newRecipient);
  1735                             newRecipient = null;
  1736                         }
  1737                     }
  1738 
  1739                     for (int i = 0; i < this._To.Count; i++)
  1740                     {
  1741                         if (string.IsNullOrWhiteSpace(this._To[i].Address) == false)
  1742                         {
  1743                             // Add by address
  1744                             newRecipient = recipients.Add(this._To[i].Address);
  1745                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1746 
  1747                             // Marshal.ReleaseComObject(newRecipient);
  1748                             newRecipient = null;
  1749                         }
  1750                         else if (string.IsNullOrWhiteSpace(this._To[i].UserName) == false)
  1751                         {
  1752                             // Add by user name (required for distribution lists)
  1753                             newRecipient = recipients.Add(this._To[i].UserName);
  1754                             newRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1755 
  1756                             // Marshal.ReleaseComObject(newRecipient);
  1757                             newRecipient = null;
  1758                         }
  1759                     }
  1760                 }
  1761 
  1762                 /* Add the pEp From identity as its own recipient. This will be removed later.
  1763                  * However, here it simplifies getting the Sender/From AddressEntry, which will be set later.
  1764                  * 
  1765                  * Note: Outlook will not allow the Sender to be set for received mail items.
  1766                  * If you try to set the sender in this situation, it will always throw an error.
  1767                  * To mitigate this, the Sender should only be set to the MailItem if one is not already existing that already
  1768                  * matches the Sender we would otherwise try to set. This is considered good enough as determining
  1769                  * if a mail item is received is not completely reliable.
  1770                  */
  1771                 if (setSender)
  1772                 {
  1773                     if ((this._From != null) &&
  1774                         ((omi.Sender == null) ||
  1775                          ((omi.Sender != null) &&
  1776                           (this._From != null) &&
  1777                           (this._From.EqualsByAddress(omi.Sender.Address) == false))))
  1778                     {
  1779                         if (string.IsNullOrWhiteSpace(this._From.Address) == false)
  1780                         {
  1781                             // Add by address
  1782                             fromRecipient = recipients.Add(this._From.Address);
  1783                             fromRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1784                         }
  1785                         else if (string.IsNullOrWhiteSpace(this._From.UserName) == false)
  1786                         {
  1787                             // Add by user name (required for distribution lists)
  1788                             fromRecipient = recipients.Add(this._From.UserName);
  1789                             fromRecipient.Type = (int)Outlook.OlMailRecipientType.olTo;
  1790                         }
  1791                     }
  1792 
  1793                     try
  1794                     {
  1795                         recipients.ResolveAll();
  1796                     }
  1797                     catch { }
  1798 
  1799                     /* Set sender account
  1800                      * Note that if this fails and the account is null, it will eventually use the default account.
  1801                      * If the send using account is already populated, this usually cannot be re-set. (Exception for unsaved drafts)
  1802                      */
  1803                     sendUsingAccount = omi.SendUsingAccount;
  1804 
  1805                     if ((this._From != null) &&
  1806                         (this._From.Address != null))
  1807                     {
  1808                         accounts = ns.Accounts;
  1809 
  1810                         // Note: Index starts at 1
  1811                         for (int i = 1; i <= accounts.Count; i++)
  1812                         {
  1813                             currAccount = accounts[i];
  1814                             var sts = PEPIdentity.GetOwnIdentity(currAccount, out PEPIdentity ident);
  1815 
  1816                             if ((ident != null) &&
  1817                                 (ident.EqualsByAddress(this._From)))
  1818                             {
  1819                                 /* Try to set the SendUsingAccount
  1820                                  * This will fail if the mail item is already marked as sent or the SendUsingAccount is not null, etc...
  1821                                  * If it fails, Outlook will in the end just use the default account.
  1822                                  * This property should ideally be set before a mail item is saved.
  1823                                  */
  1824                                 try
  1825                                 {
  1826                                     omi.SendUsingAccount = currAccount;
  1827                                 }
  1828                                 catch { }
  1829 
  1830                                 // Marshal.ReleaseComObject(currAccount);
  1831                                 currAccount = null;
  1832 
  1833                                 break;
  1834                             }
  1835 
  1836                             // Marshal.ReleaseComObject(currAccount);
  1837                             currAccount = null;
  1838                         }
  1839                     }
  1840 
  1841                     /* Set Sender
  1842                      * Outlook takes the Sender property to display the respective name in the Sent folder.
  1843                      * This Sender property is based on the name, the email address and the EntryID MAPI properties. As the EntryID can't be accessed
  1844                      * directly with MAPI methods, we have to set these properties indirectly through the Sender.
  1845                      */
  1846                     if (fromRecipient != null)
  1847                     {
  1848                         try
  1849                         {
  1850                             omi.Sender = fromRecipient.AddressEntry;
  1851                         }
  1852                         catch
  1853                         {
  1854                             Log.Warning("PEPMessage.ApplyTo: Failed to set the Sender.");
  1855                         }
  1856 
  1857                         // Remove the From recipient from the Recipients collection by searching for a match by EntryID
  1858                         for (int i = 1; i <= recipients.Count; i++)
  1859                         {
  1860                             newRecipient = recipients[i];
  1861                             if (newRecipient != null)
  1862                             {
  1863                                 if ((string.IsNullOrEmpty(newRecipient.EntryID) == false) &&
  1864                                     (string.IsNullOrEmpty(fromRecipient.EntryID) == false) &&
  1865                                     (newRecipient.EntryID == fromRecipient.EntryID))
  1866                                 {
  1867                                     try
  1868                                     {
  1869                                         recipients.Remove(i);
  1870                                         fromRecipientRemoved = true;
  1871                                     }
  1872                                     catch
  1873                                     {
  1874                                         Log.Warning("PEPMessage.ApplyTo: Error removing 'From' recipient from Recipients collection.");
  1875                                     }
  1876                                     break;
  1877                                 }
  1878 
  1879                                 // Marshal.ReleaseComObject(newRecipient);
  1880                                 newRecipient = null;
  1881                             }
  1882                         }
  1883 
  1884                         // Fallback solution in case there is no EntryID match
  1885                         if (fromRecipientRemoved == false)
  1886                         {
  1887                             try
  1888                             {
  1889                                 recipients.Remove(recipients.Count);
  1890                                 Log.Warning("PEPMessage.ApplyTo: No EntryID match for 'From' recipient in Recipients collection. Removed using fallback solution.");
  1891                             }
  1892                             catch
  1893                             {
  1894                                 Log.Error("PEPMessage.ApplyTo: No EntryID match for 'From' recipient in Recipients collection. Fallback solution also failed.");
  1895                             }
  1896                         }
  1897 
  1898                         // Set Sender's name and email properties via MAPIProperty (won't be created automatically when assigníng Sender)
  1899                         try
  1900                         {
  1901                             MapiHelper.SetProperty(omi, MapiProperty.PidTagSenderName, this._From.UserName);
  1902                             MapiHelper.SetProperty(omi, MapiProperty.PidTagSenderEmailAddress, this._From.Address);
  1903                         }
  1904                         catch (Exception ex)
  1905                         {
  1906                             Log.Warning("PEPMessage.ApplyTo: Unable to set sender's name or email address via MAPIProperty. " + ex.Message);
  1907                         }
  1908 
  1909                         // Marshal.ReleaseComObject(fromRecipient);
  1910                         fromRecipient = null;
  1911                     }
  1912                 }
  1913                 else
  1914                 {
  1915                     try
  1916                     {
  1917                         recipients?.ResolveAll();
  1918                     }
  1919                     catch { }
  1920                 }
  1921 
  1922                 // ReplyTo recipients
  1923                 recipients = omi.ReplyRecipients;
  1924                 while (recipients?.Count > 0)
  1925                 {
  1926                     recipients.Remove(1);
  1927                 }
  1928 
  1929                 // Tunnel the auto-consume header through this field
  1930                 if (string.IsNullOrEmpty(this.AutoConsume) == false)
  1931                 {
  1932                     this.ReplyTo.Add(new PEPIdentity(PEPMessage.PR_PEP_AUTO_CONSUME_NAME + PEPMessage.PEPTUNNEL_MAIL_ADDRESS));
  1933                 }
  1934 
  1935                 for (int i = 0; i < this._ReplyTo.Count; i++)
  1936                 {
  1937                     if (string.IsNullOrWhiteSpace(this._ReplyTo[i].Address) == false)
  1938                     {
  1939                         // Add by address
  1940                         newRecipient = recipients.Add(this._ReplyTo[i].Address);
  1941                         newRecipient = null;
  1942                     }
  1943                     else if (string.IsNullOrWhiteSpace(this._ReplyTo[i].UserName) == false)
  1944                     {
  1945                         // Add by user name (required for distribution lists)
  1946                         newRecipient = recipients.Add(this._ReplyTo[i].UserName);
  1947                         newRecipient = null;
  1948                     }
  1949                 }
  1950 
  1951                 /* Set the encoding to UTF-8
  1952                  * See: https://msdn.microsoft.com/en-us/library/office/ff860730.aspx
  1953                  * All PEPMessages should be UTF especially those coming from the engine.
  1954                  * 
  1955                  * It is important that the encoding is set BEFORE the body and other text of the 
  1956                  * mail item. This is necessary to avoid character encoding issues where
  1957                  * Outlook will not respect the UTF encoding of strings set to the body/subject/etc.
  1958                  * It will instead try to use the strings with the encoding of the codepage.
  1959                  */
  1960                 MAPIProperties encodingProperties = new MAPIProperties
  1961                 {
  1962                     { MapiProperty.PidTagInternetCodepage, 65001 },
  1963                     { MapiProperty.PidTagMessageCodepage, 65001 }
  1964                 };
  1965                 MapiHelper.SetProperties(omi, encodingProperties);
  1966 
  1967                 /* Set the body
  1968                  * PGP/MIME format must be handled specially.
  1969                  * In addition, RTF format is ignored. Only HTML is used the same as the pEp engine.
  1970                  */
  1971                 if (isPGPMIMEMsg)
  1972                 {
  1973                     /* Clear any old body content, these cannot exist for PGP/MIME encrypted messages.
  1974                      * Note that setting the RTFBody to null or an empty byte array throws an exception.
  1975                      * Therefore, it is sent to a single zero byte to be constant.
  1976                      * The RTFBody is ignored when sending the actual message.
  1977                      * Note also that the body properties throw errors when set using MAPI properties,
  1978                      * so we have to set them directly.
  1979                      */
  1980                     omi.Body = null;
  1981                     omi.HTMLBody = null;
  1982                     omi.RTFBody = new byte[] { 0x00 };
  1983                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatPlain;
  1984 
  1985                     // Now, set the mail mime type to S/MIME multipart/signed, this is used later to add a special attachment
  1986                     MapiHelper.SetProperty(omi, MapiProperty.PidTagMessageClass, MapiPropertyValue.PidTagMessageClassSMIMEMultipartSigned);
  1987                 }
  1988                 else if ((string.IsNullOrWhiteSpace(this._LongMsgFormattedHtml)) ||
  1989                           (Globals.ThisAddIn.OutlookOptions.ReadAsPlain))
  1990                 {
  1991                     omi.Body = this._LongMsg;
  1992                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatPlain;
  1993                 }
  1994                 else
  1995                 {
  1996                     omi.HTMLBody = this._LongMsgFormattedHtml;
  1997                     omi.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
  1998                 }
  1999 
  2000                 // Remove any previous attachments
  2001                 attachments = omi.Attachments;
  2002                 while (attachments.Count > 0)
  2003                 {
  2004                     /* OUT-193: We cannot use attachments.Remove() here, as it
  2005                      * seems to be buggy in Outlook 2013. Let's hope that
  2006                      * using Attachment.Delete() has no bad side effects on
  2007                      * other Outlook versions.
  2008                      * OUT-292: The Delete() method can fail under rare circumstances.
  2009                      * In this case, just log the error and break. 
  2010                      * NOTE: We can NOT use a for loop here, as this leads to unforeseeable 
  2011                      * issues with actual attachments (supposedly due to race conditions on 
  2012                      * multithreaded decryption operations).
  2013                      */
  2014                     try
  2015                     {
  2016                         attachments[attachments.Count].Delete();
  2017                     }
  2018                     catch (Exception ex)
  2019                     {
  2020                         Log.Error("Applyto: Error deleting attachment. " + ex.ToString());
  2021                         break;
  2022                     }
  2023                 }
  2024 
  2025                 if (isPGPMIMEMsg)
  2026                 {
  2027                     PEPAttachment pgpMIME = this.ConvertToPGPMIMEAttachment();
  2028 
  2029                     if (pgpMIME != null)
  2030                     {
  2031                         try
  2032                         {
  2033                             pgpMIME.AddTo(attachments);
  2034                         }
  2035                         catch (Exception ex)
  2036                         {
  2037                             Log.Verbose("Failed to add PGP/MIME attachment. " + ex.ToString());
  2038 
  2039                             /* If adding the attachment fails, check if it is because of file size limitations
  2040                              * and throw dedicated exception in that case (needed for user feedback).
  2041                              */
  2042                             var pgpgMIMESizeInKB = pgpMIME.Data.Length / 1000; // Get size in kB
  2043                             if (pgpgMIMESizeInKB > omi.GetMaxMailSize())
  2044                             {
  2045                                 throw new AttachmentSizeException(string.Format("Failed to add PGP/MIME attachment due to size restrictions. Allowed size: {0} kB. Actual size: {1} kB.", omi.GetMaxMailSize(), pgpgMIMESizeInKB));
  2046                             }
  2047                             else
  2048                             {
  2049                                 throw;
  2050                             }
  2051                         }
  2052                     }
  2053                     else
  2054                     {
  2055                         throw new Exception("Failed to create and add PGP/MIME attachment.");
  2056                     }
  2057                 }
  2058                 else
  2059                 {
  2060                     for (int i = 0; i < this._Attachments.Count; i++)
  2061                     {
  2062                         try
  2063                         {
  2064                             this._Attachments[i].AddTo(attachments, ("attachment" + i.ToString()));
  2065                         }
  2066                         catch (Exception ex)
  2067                         {
  2068                             Log.Error("ApplyTo: Error adding attachment. " + ex.ToString());
  2069                         }
  2070                     }
  2071                 }
  2072 
  2073                 // Gather properties to set via MAPI
  2074                 MAPIProperties propertiesToSet = new MAPIProperties();
  2075 
  2076                 // Add subject
  2077                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ShortMsg), this._ShortMsg);
  2078 
  2079                 // Add id
  2080                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.Id), this._Id);
  2081 
  2082                 /* Conversation information
  2083                  * Note: PidTagConversationId cannot be set even through the MAPI accessor.
  2084                  * This is by design since this property is computed automatically from other properties.
  2085                  * See: https://msdn.microsoft.com/en-us/library/ee204279.aspx
  2086                  */
  2087                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ConversationIndex), MapiHelper.StringToPtypBinary(this._ConversationIndex));
  2088                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ConversationTopic), this._ConversationTopic ?? string.Empty);
  2089                 propertiesToSet.Add(MapiProperty.PidTagConversationIndexTracking, true);
  2090 
  2091                 /* Add internal properties
  2092                  * Note: there is no need to check for defaults as it's all handled within the SetInterpretedProperty method.
  2093                  * Also, .Save() must never be called here as the .ApplyTo() method can be used on MailItems that need to change properties before save.
  2094                  */
  2095                 if (setInternalHeaderFields)
  2096                 {
  2097                     // Note: ignore return status
  2098                     propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.KeyList), this._KeyList);
  2099 
  2100                     // Only store rating once and never change it
  2101                     if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.Rating, out object storedRating) &&
  2102                         ((storedRating as pEpRating?) == pEpRating.pEpRatingUndefined))
  2103                     {
  2104                         propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.Rating), this._Rating);
  2105                     }
  2106                 }
  2107 
  2108                 // Note: ignore return status
  2109                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.AutoConsume), this._AutoConsume);
  2110                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ForceProtectionId), this._ForceProtectionId);
  2111                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.KeyImport), this._KeyImport);
  2112                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.NeverUnsecure), this._NeverUnsecure);
  2113                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.PEPProtocolVersion), this._PEPProtocolVersion);
  2114 
  2115                 // Cancel ReadReceiptRequested and OriginatorDeliveryReportRequested for automatic mails
  2116                 if (string.IsNullOrEmpty(this.AutoConsume) == false)
  2117                 {
  2118                     try
  2119                     {
  2120                         omi.ReadReceiptRequested = false;
  2121                         omi.OriginatorDeliveryReportRequested = false;
  2122                     }
  2123                     catch (Exception ex)
  2124                     {
  2125                         Log.Error("PEPMessage.ApplyTo: Error cancelling ReadReceiptRequested and OriginatorDeliveryReportRequested: " + ex.ToString());
  2126                     }
  2127                 }
  2128 
  2129                 // Set all MAPI properties at once and report errors if any
  2130                 Dictionary<string, object> result = MapiHelper.SetProperties(omi, propertiesToSet);
  2131 
  2132                 // Log errors if any
  2133                 if (result != null)
  2134                 {
  2135                     foreach (var error in result)
  2136                     {
  2137                         if (error.Value != null)
  2138                         {
  2139                             Log.Error("PEPMessage.ApplyTo: Error setting MAPI property {0}: {1}", error.Key, (error.Value as int?)?.ToString("X2") ?? "<null>");
  2140                         }
  2141                     }
  2142                 }
  2143             }
  2144             catch (Exception ex)
  2145             {
  2146                 status = Globals.ReturnStatus.Failure;
  2147                 Log.Error("PEPMessage.ApplyTo: Failure occured, " + ex.ToString());
  2148                 throw;
  2149             }
  2150             finally
  2151             {
  2152                 accounts = null;
  2153                 attachments = null;
  2154                 newRecipient = null;
  2155                 currAccount = null;
  2156                 recipients = null;
  2157                 sendUsingAccount = null;
  2158                 fromRecipient = null;
  2159                 ns = null;
  2160             }
  2161 
  2162             return status;
  2163         }
  2164 
  2165         /// <summary>
  2166         /// Converts this message into a single PGP/MIME attachment.
  2167         /// This is necessary in order for Outlook and MAPI to correctly transport PGP/MIME messages.
  2168         /// The attachment will report itself as S/MIME to MAPI, S/MIME attachments are passed-through as unmodified text.
  2169         /// Since we can insert unmodified text, this allows us to build a correct PGP/MIME message from existing attachments.
  2170         /// Note: To prevent overencoding of the message during transport, we have ot make sure to always use "CRLF" line ending
  2171         ///       in the message instead of "LF". Tests showed that using the latter can lead to Exchange encoding the whole message
  2172         ///       again as quoted-printable and adding CRLF in addition to the present LF line endings.
  2173         /// Warning: This can return null if a failure occured.
  2174         /// </summary>
  2175         /// <returns>This message converted to a single PGP/MIME attachment, otherwise null.</returns>
  2176         private PEPAttachment ConvertToPGPMIMEAttachment()
  2177         {
  2178             string boundary;
  2179             byte[] bytes;
  2180             bool versionInfoFound = false;
  2181             bool contentFound = false;
  2182             PEPAttachment newAttachment = null;
  2183             List<byte> data = new List<byte>();
  2184 
  2185             if (this._Attachments.Count == 2)
  2186             {
  2187                 boundary = "----=_NextPart_" + Guid.NewGuid().ToString().Replace('-', '_');
  2188 
  2189                 /* See below for an example PGP/MIME formatted message.
  2190                  * this is from RFC 3156.  
  2191                  *    
  2192                  *  Content-Type: multipart/encrypted; boundary=foo;
  2193                  *     protocol="application/pgp-encrypted"
  2194                  * 
  2195                  *  --foo
  2196                  *  Content-Type: application/pgp-encrypted
  2197                  * 
  2198                  *  Version: 1
  2199                  * 
  2200                  *  --foo
  2201                  *  Content-Type: application/octet-stream
  2202                  * 
  2203                  *  -----BEGIN PGP MESSAGE-----
  2204                  *  Version: 2.6.2
  2205                  * 
  2206                  *  hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj
  2207                  *  eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ
  2208                  *  g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA
  2209                  *  AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3
  2210                  *  1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8=
  2211                  *  =zzaA
  2212                  *  -----END PGP MESSAGE-----
  2213                  * 
  2214                  *  --foo--
  2215                  */
  2216 
  2217                 // Add internet headers (these are not added by default for S/MIME messages which allows them to be customized)
  2218                 bytes = Encoding.UTF8.GetBytes("MIME-Version: 1.0\r\n" +
  2219                                                "Content-Type: multipart/encrypted;\r\n" +
  2220                                                "\tprotocol=\"application/pgp-encrypted\";\r\n" +
  2221                                                "\tboundary=\"" + boundary + "\"\r\n");
  2222                 data.AddRange(bytes);
  2223 
  2224                 // Add the version identification attachment
  2225                 bytes = Encoding.UTF8.GetBytes("\r\n--" + boundary +
  2226                                                "\nContent-Type: application/pgp-encrypted\r\n" +
  2227                                                "Content-Description: PGP/MIME version identification\r\n\r\n");
  2228                 data.AddRange(bytes);
  2229 
  2230                 foreach (PEPAttachment attach in this._Attachments)
  2231                 {
  2232                     if (attach.IsPGPMIMEVersionInfoFormat)
  2233                     {
  2234                         /* Replace all "LF" by "CRLF" (see note above)
  2235                          * 10 is the decimal value for "LF", 13 for "CR"
  2236                          * See https://en.wikipedia.org/wiki/Newline#Representation
  2237                          */
  2238                         List<byte> versionInfo = new List<byte>();
  2239                         for (int i = 0; i < attach.Data.Length; i++)
  2240                         {
  2241                             if (attach.Data[i] == 10)
  2242                             {
  2243                                 versionInfo.Add(13);
  2244                             }
  2245 
  2246                             versionInfo.Add(attach.Data[i]);
  2247                         }
  2248 
  2249                         data.AddRange(versionInfo);
  2250                         versionInfoFound = true;
  2251                         break;
  2252                     }
  2253                 }
  2254 
  2255                 // Add the content attachment
  2256                 bytes = Encoding.UTF8.GetBytes("\r\n\r\n--" + boundary +
  2257                                                "\r\nContent-Type: application/octet-stream;\r\n" +
  2258                                                "\tname=\"msg.asc\"\r\n\r\n");
  2259                 data.AddRange(bytes);
  2260 
  2261                 foreach (PEPAttachment attach in this._Attachments)
  2262                 {
  2263                     if (attach.IsPGPMIMEContentFormat)
  2264                     {
  2265                         /* Replace all "LF" by "CRLF" (see note above)
  2266                          * 10 is the decimal value for "LF", 13 for "CR"
  2267                          * See https://en.wikipedia.org/wiki/Newline#Representation
  2268                          */
  2269                         List<byte> mimeContent = new List<byte>();
  2270                         for (int i = 0; i < attach.Data.Length; i++)
  2271                         {
  2272                             if (attach.Data[i] == 10)
  2273                             {
  2274                                 mimeContent.Add(13);
  2275                             }
  2276 
  2277                             mimeContent.Add(attach.Data[i]);
  2278                         }
  2279 
  2280                         data.AddRange(mimeContent);
  2281                         contentFound = true;
  2282                         break;
  2283                     }
  2284                 }
  2285 
  2286                 bytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
  2287                 data.AddRange(bytes);
  2288 
  2289                 // Create the new attachment
  2290                 if (versionInfoFound && contentFound)
  2291                 {
  2292                     newAttachment = new PEPAttachment()
  2293                     {
  2294                         Data = data.ToArray(),
  2295                         MimeType = "multipart/signed",
  2296                         Tag = MapiPropertyValue.PidTagAttachTagMIME
  2297                     };
  2298                 }
  2299             }
  2300 
  2301             return (newAttachment);
  2302         }
  2303 
  2304         /// <summary>
  2305         /// Recursivley converts all "Bcc", "Cc", and "To" identities into a 'flat' list of any members.
  2306         /// This will remove groups (hierarchy) and convert a group into it's members.
  2307         /// </summary>
  2308         public void FlattenAllRecipientIdentities()
  2309         {
  2310             this._Bcc = PEPIdentity.ToFlatList(this._Bcc);
  2311             this._Cc = PEPIdentity.ToFlatList(this._Cc);
  2312             this._To = PEPIdentity.ToFlatList(this._To);
  2313 
  2314             return;
  2315         }
  2316 
  2317         /// <summary>
  2318         /// Add a disclaimer to the message if needed.
  2319         /// Note: This should only be applied to outgoing messages. As the method doesn't 
  2320         /// save the mail item after adding a disclaimer, it's the caller's responsibility
  2321         /// to save if necessary.
  2322         /// </summary>
  2323         public void AddDisclaimer()
  2324         {
  2325             // We only add disclaimers to outgoing messages
  2326             if (this.Direction == pEpMsgDirection.pEpDirOutgoing)
  2327             {
  2328                 try
  2329                 {
  2330                     // Get settings for From account
  2331                     var accountSettings = PEPSettings.GetAccountSettings(this.From?.Address);
  2332 
  2333                     /* Add disclaimer if needed:
  2334                      *      - if set to add to all messages
  2335                      *      - if set to add to encrypted messages and outgoing rating is
  2336                      *        at least reliable.
  2337                      */
  2338                     if ((accountSettings?.AddDisclaimer == PEPSettings.Disclaimer.AllMessages) ||
  2339                         ((accountSettings?.AddDisclaimer == PEPSettings.Disclaimer.OnlyEncryptedMessages) && this.GetOutgoingRating() >= pEpRating.pEpRatingReliable))
  2340                     {
  2341                         // Add to plain text
  2342                         if (string.IsNullOrEmpty(this._LongMsg) == false)
  2343                         {
  2344                             this._LongMsg += "\n\n" + accountSettings.DisclaimerText;
  2345                         }
  2346 
  2347                         // Add to html
  2348                         if (string.IsNullOrEmpty(this._LongMsgFormattedHtml) == false)
  2349                         {
  2350                             this._LongMsgFormattedHtml += "<br><br><p>" +
  2351                                                           accountSettings.DisclaimerText +
  2352                                                           "</p>";
  2353                         }
  2354 
  2355                         // Add to Rtf
  2356                         if (string.IsNullOrEmpty(this._LongMsgFormattedRtf) == false)
  2357                         {
  2358                             System.Windows.Forms.RichTextBox rtb = new System.Windows.Forms.RichTextBox
  2359                             {
  2360                                 Text = this._LongMsgFormattedRtf
  2361                             };
  2362                             rtb.AppendText("\n\n" + accountSettings.DisclaimerText);
  2363                             this._LongMsgFormattedRtf = rtb.ToString();
  2364                         }
  2365                     }
  2366                 }
  2367                 catch (Exception ex)
  2368                 {
  2369                     Log.Error("AddDisclaimer: Error adding disclaimer to message. " + ex.ToString());
  2370                 }
  2371             }
  2372             else
  2373             {
  2374                 Log.Verbose("AddDisclaimer: Skipped. Message is incoming.");
  2375             }
  2376         }
  2377 
  2378         /**************************************************************
  2379          * 
  2380          * Static Methods
  2381          * 
  2382          *************************************************************/
  2383 
  2384         public static pEpRating CalculateRating(PEPMessage message)
  2385         {
  2386             pEpRating rating = pEpRating.pEpRatingTrustedAndAnonymized;
  2387             List<PEPIdentity> recipients = new List<PEPIdentity>
  2388             {
  2389                 message.From
  2390             };
  2391 
  2392             message.To?.ForEach(identity =>
  2393             {
  2394                 recipients.Add(identity);
  2395             });
  2396 
  2397             message.Cc?.ForEach(identity =>
  2398             {
  2399                 recipients.Add(identity);
  2400             });
  2401 
  2402             message.Bcc?.ForEach(identity =>
  2403             {
  2404                 recipients.Add(identity);
  2405             });
  2406 
  2407             recipients.ForEach(identity =>
  2408             {
  2409                 // Skip own identities
  2410                 if (PEPSettings.GetIsOwnAccount(identity) == false)
  2411                 {
  2412                     try
  2413                     {
  2414                         pEpIdentity _identity = identity.ToCOMType();
  2415                         _identity = ThisAddIn.PEPEngine.UpdateIdentity(_identity);
  2416                         pEpRating identityRating = ThisAddIn.PEPEngine.RatingFromCommType(_identity.CommType);
  2417 
  2418                         if (identityRating < rating)
  2419                         {
  2420                             rating = identityRating;
  2421                         }
  2422                     }
  2423                     catch (Exception ex)
  2424                     {
  2425                         Log.Error("CalculateRating: Error updating identity. " + ex.ToString());
  2426                     }
  2427                 }
  2428             });
  2429 
  2430             return rating;
  2431         }
  2432 
  2433         /// <summary>
  2434         /// Constructs a new message from the given pEp engine TextMessage.
  2435         /// The output will never be null.
  2436         /// </summary>
  2437         /// <param name="msg">The TextMessage to construct from.</param>
  2438         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  2439         /// <returns>The status of the method.</returns>
  2440         /// <remarks>In some contexts which call this message, a partial conversion of the
  2441         /// <see cref="TextMessage"/> is acceptable, non-critical, and better than failure (e. G. displaying
  2442         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  2443         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  2444         /// in the more critical contexts. Callers of that message need to be aware of this
  2445         /// and check the result if appropriate.</remarks>
  2446         public static Globals.ReturnStatus Create(TextMessage msg,
  2447                                                   out PEPMessage createdMessage)
  2448         {
  2449             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  2450             PEPMessage newMessage = new PEPMessage();
  2451 
  2452             try
  2453             {
  2454                 /* Note: Skip the following properties which are not supported in the TextMessage
  2455                  *   • ConversationID
  2456                  *   • ConversationIndex
  2457                  *   • ConversationTopic
  2458                  *   • EnableProtection
  2459                  *   • ForceUnencrypted
  2460                  *   • LongMsgFormattedRtf
  2461                  * 
  2462                  * Also note the following are handled as optional fields
  2463                  *   • AutoConsume
  2464                  *   • ForceProtection
  2465                  *   • KeyList
  2466                  *   • NeverUnsecure
  2467                  *   • PEPProtocolVersion
  2468                  *   • Rating
  2469                  * 
  2470                  * This also skips a number of TextMessage properties currently unsupported in PEPMessage.
  2471                  */
  2472 
  2473                 // Attachments
  2474                 newMessage.Attachments.Clear();
  2475                 if (msg.Attachments != null)
  2476                 {
  2477                     for (int i = 0; i < msg.Attachments.Length; i++)
  2478                     {
  2479                         newMessage.Attachments.Add(new PEPAttachment((Blob)msg.Attachments.GetValue(i)));
  2480                     }
  2481                 }
  2482 
  2483                 // Bcc
  2484                 newMessage.Bcc.Clear();
  2485                 if (msg.Bcc != null)
  2486                 {
  2487                     for (int i = 0; i < msg.Bcc.Length; i++)
  2488                     {
  2489                         newMessage.Bcc.Add(new PEPIdentity((pEpIdentity)msg.Bcc.GetValue(i)));
  2490                     }
  2491                 }
  2492 
  2493                 // Cc
  2494                 newMessage.Cc.Clear();
  2495                 if (msg.Cc != null)
  2496                 {
  2497                     for (int i = 0; i < msg.Cc.Length; i++)
  2498                     {
  2499                         newMessage.Cc.Add(new PEPIdentity((pEpIdentity)msg.Cc.GetValue(i)));
  2500                     }
  2501                 }
  2502 
  2503                 newMessage.Direction = msg.Dir;
  2504                 newMessage.From = new PEPIdentity(msg.From);
  2505                 newMessage.Id = msg.Id;
  2506 
  2507                 // Keywords
  2508                 newMessage.Keywords.Clear();
  2509                 if (msg.Keywords != null)
  2510                 {
  2511                     for (int i = 0; i < msg.Keywords.Length; i++)
  2512                     {
  2513                         newMessage.Keywords.Add((string)msg.Keywords.GetValue(i));
  2514                     }
  2515                 }
  2516 
  2517                 newMessage.LongMsg = msg.LongMsg;
  2518                 newMessage.LongMsgFormattedHtml = msg.LongMsgFormatted;
  2519 
  2520                 // Optional properties
  2521                 if (msg.OptFields != null)
  2522                 {
  2523                     foreach (StringPair optField in msg.OptFields)
  2524                     {
  2525                         if (optField.Name != null)
  2526                         {
  2527                             switch (optField.Name)
  2528                             {
  2529                                 case PEPMessage.PR_PEP_AUTO_CONSUME_NAME:
  2530                                 case PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD:
  2531                                     {
  2532                                         newMessage.AutoConsume = optField.Value;
  2533                                         break;
  2534                                     }
  2535                                 case PEPMessage.PR_PEP_FORCE_PROTECTION_NAME:
  2536                                     {
  2537                                         newMessage.ForceProtectionId = optField.Value;
  2538                                         break;
  2539                                     }
  2540                                 case PEPMessage.PR_PEP_KEY_IMPORT_NAME:
  2541                                 case PEPMessage.PR_PEP_KEY_IMPORT_NAME_OLD:
  2542                                     {
  2543                                         newMessage.KeyImport = optField.Value;
  2544                                         break;
  2545                                     }
  2546                                 case PEPMessage.PR_KEY_LIST_NAME:
  2547                                     {
  2548                                         newMessage.KeyList = optField.Value;
  2549                                         break;
  2550                                     }
  2551                                 case PEPMessage.PR_PEP_NEVER_UNSECURE_NAME:
  2552                                     {
  2553                                         // If it exists it's true, value doesn't matter
  2554                                         newMessage.NeverUnsecure = true;
  2555                                         break;
  2556                                     }
  2557                                 case PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME:
  2558                                     {
  2559                                         newMessage.PEPProtocolVersion = optField.Value;
  2560                                         break;
  2561                                     }
  2562                                 case PEPMessage.PR_ENC_STATUS_NAME:
  2563                                     {
  2564                                         try
  2565                                         {
  2566                                             newMessage.Rating = AdapterExtensions.ParseRatingString(optField.Value);
  2567                                         }
  2568                                         catch
  2569                                         {
  2570                                             newMessage.Rating = pEpRating.pEpRatingUndefined;
  2571                                         }
  2572                                         break;
  2573                                     }
  2574                             }
  2575                         }
  2576                     }
  2577                 }
  2578 
  2579                 // ReceivedOn
  2580                 if (msg.Recv > 0)
  2581                 {
  2582                     DateTime receivedOnUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(msg.Recv);
  2583                     try
  2584                     {
  2585                         newMessage.ReceivedOn = TimeZoneInfo.ConvertTimeFromUtc(receivedOnUtc, TimeZoneInfo.Local);
  2586                     }
  2587                     catch (Exception ex)
  2588                     {
  2589                         Log.Error("PEPMessage.Create: Error converting received time to local time. " + ex.ToString());
  2590                         newMessage.ReceivedOn = new DateTime(1970, 1, 1).AddSeconds(msg.Recv);
  2591                     }
  2592                 }
  2593                 else
  2594                 {
  2595                     newMessage.ReceivedOn = null;
  2596                 }
  2597 
  2598                 // ReplyTo
  2599                 newMessage.ReplyTo.Clear();
  2600                 if (msg.ReplyTo != null)
  2601                 {
  2602                     for (int i = 0; i < msg.ReplyTo.Length; i++)
  2603                     {
  2604                         newMessage.ReplyTo.Add(new PEPIdentity((pEpIdentity)msg.ReplyTo.GetValue(i)));
  2605                     }
  2606                 }
  2607 
  2608                 // SentOn
  2609                 if (msg.Sent > 0)
  2610                 {
  2611                     DateTime sentOnUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(msg.Sent);
  2612                     try
  2613                     {
  2614                         newMessage.SentOn = TimeZoneInfo.ConvertTimeFromUtc(sentOnUtc, TimeZoneInfo.Local);
  2615                     }
  2616                     catch (Exception ex)
  2617                     {
  2618                         Log.Error("PEPMessage.Create: Error converting sent time to local time. " + ex.ToString());
  2619                         newMessage.SentOn = new DateTime(1970, 1, 1).AddSeconds(msg.Sent);
  2620                     }
  2621                 }
  2622                 else
  2623                 {
  2624                     newMessage.SentOn = null;
  2625                 }
  2626 
  2627                 newMessage.ShortMsg = msg.ShortMsg;
  2628 
  2629                 // To
  2630                 newMessage.To.Clear();
  2631                 if (msg.To != null)
  2632                 {
  2633                     for (int i = 0; i < msg.To.Length; i++)
  2634                     {
  2635                         newMessage.To.Add(new PEPIdentity((pEpIdentity)msg.To.GetValue(i)));
  2636                     }
  2637                 }
  2638             }
  2639             catch (Exception ex)
  2640             {
  2641                 status = Globals.ReturnStatus.Failure;
  2642                 Log.Error("PEPMessage.Create: Failure occured, " + ex.ToString());
  2643             }
  2644 
  2645             createdMessage = newMessage;
  2646             return (status);
  2647         }
  2648 
  2649         /// <summary>
  2650         /// Contructs a new message from the given outlook mail item.
  2651         /// The output will never be null.
  2652         /// </summary>
  2653         /// <param name="omi">The outlook mail item to create the message from.</param>
  2654         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  2655         /// <param name="onlyRecipientsAndDirection">Whether to only set recipients and direction. Only set to
  2656         /// true in special cases, e.g. if the message is only needed to calculate the outgoing rating.</param>
  2657         /// <param name="createContacts">Whether or not to create contacts from the mail items's recipients during
  2658         /// processing of the message. Only set to false in special cases like for outgoing rating.</param>
  2659         /// <returns>The status of the method.</returns>
  2660         /// <remarks>In some contexts which call this message, a partial conversion of the
  2661         /// <see cref="TextMessage"/> is acceptable, non-critical, and better than failure (e. G. displaying
  2662         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  2663         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  2664         /// in the more critical contexts. Callers of that message need to be aware of this
  2665         /// and check the result if appropriate.</remarks>
  2666         public static Globals.ReturnStatus Create(Outlook.MailItem omi,
  2667                                                   out PEPMessage createdMessage,
  2668                                                   bool onlyRecipientsAndDirection = false,
  2669                                                   bool createContacts = true)
  2670         {
  2671             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
  2672             Globals.ReturnStatus sts = Globals.ReturnStatus.Failure;
  2673             PEPMessage newMessage = new PEPMessage();
  2674             Outlook.Recipient recipient = null;
  2675             Outlook.Recipients recipients = null;
  2676             Outlook.Attachment attachment = null;
  2677             Outlook.Attachments attachments = null;
  2678 
  2679             try
  2680             {
  2681                 Log.Verbose("PEPMessage.Create: Creating new PEPMessage from OMI started.");
  2682 
  2683                 // Direction
  2684                 newMessage.Direction = omi.GetIsIncoming() ? pEpMsgDirection.pEpDirIncoming : pEpMsgDirection.pEpDirOutgoing;
  2685 
  2686                 // Calculate recipients
  2687                 newMessage.Bcc.Clear();
  2688                 newMessage.Cc.Clear();
  2689                 newMessage.To.Clear();
  2690                 recipients = omi.Recipients;
  2691                 for (int i = 1; i <= recipients.Count; i++)
  2692                 {
  2693                     recipient = recipients[i];
  2694 
  2695                     switch ((Outlook.OlMailRecipientType)recipient.Type)
  2696                     {
  2697                         case Outlook.OlMailRecipientType.olBCC:
  2698                             {
  2699                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident);
  2700                                 if (sts == Globals.ReturnStatus.Success)
  2701                                 {
  2702                                     newMessage.Bcc.Add(ident);
  2703                                 }
  2704                                 else
  2705                                 {
  2706                                     // Update the status, only the first failure type is recorded
  2707                                     if (status == Globals.ReturnStatus.Success)
  2708                                     {
  2709                                         status = sts;
  2710                                     }
  2711 
  2712                                     Log.Error("PEPMessage.Create: Failure creating 'Bcc' identity.");
  2713                                 }
  2714 
  2715                                 break;
  2716                             }
  2717                         case Outlook.OlMailRecipientType.olCC:
  2718                             {
  2719                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident, (createContacts && (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)));
  2720                                 if (sts == Globals.ReturnStatus.Success)
  2721                                 {
  2722                                     newMessage.Cc.Add(ident);
  2723                                 }
  2724                                 else
  2725                                 {
  2726                                     // Update the status, only the first failure type is recorded
  2727                                     if (status == Globals.ReturnStatus.Success)
  2728                                     {
  2729                                         status = sts;
  2730                                     }
  2731 
  2732                                     Log.Error("PEPMessage.Create: Failure creating 'Cc' identity.");
  2733                                 }
  2734 
  2735                                 break;
  2736                             }
  2737                         case Outlook.OlMailRecipientType.olTo:
  2738                             {
  2739                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident, (createContacts && (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)));
  2740                                 if (sts == Globals.ReturnStatus.Success)
  2741                                 {
  2742                                     newMessage.To.Add(ident);
  2743                                 }
  2744                                 else
  2745                                 {
  2746                                     // Update the status, only the first failure type is recorded
  2747                                     if (status == Globals.ReturnStatus.Success)
  2748                                     {
  2749                                         status = sts;
  2750                                     }
  2751 
  2752                                     Log.Error("PEPMessage.Create: Failure creating 'To' identity.");
  2753                                 }
  2754 
  2755                                 break;
  2756                             }
  2757                     }
  2758 
  2759                     recipient = null;
  2760                 }
  2761 
  2762                 recipients = omi.ReplyRecipients;
  2763                 for (int i = 1; i <= recipients.Count; i++)
  2764                 {
  2765                     recipient = recipients[i];
  2766                     sts = PEPIdentity.Create(recipient, out PEPIdentity ident);
  2767                     if (sts == Globals.ReturnStatus.Success)
  2768                     {
  2769                         newMessage.ReplyTo.Add(ident);
  2770                     }
  2771                     else
  2772                     {
  2773                         // Update the status, only the first failure type is recorded
  2774                         if (status == Globals.ReturnStatus.Success)
  2775                         {
  2776                             status = sts;
  2777                         }
  2778 
  2779                         Log.Error("PEPMessage.Create: Failure creating 'ReplyTo' identity.");
  2780                     }
  2781                 }
  2782 
  2783                 Log.Verbose("PEPMessage.Create: Recipients calculated, calculating main properties.");
  2784 
  2785                 if (onlyRecipientsAndDirection == false)
  2786                 {
  2787                     // From
  2788                     sts = PEPIdentity.GetFromIdentity(omi, out PEPIdentity ident);
  2789                     if (sts == Globals.ReturnStatus.Success)
  2790                     {
  2791                         newMessage.From = ident;
  2792                     }
  2793                     else
  2794                     {
  2795                         // Update the status, only the first failure type is recorded
  2796                         if (status == Globals.ReturnStatus.Success)
  2797                         {
  2798                             status = sts;
  2799                         }
  2800 
  2801                         Log.Error("PEPMessage.Create: Failure creating 'From' identity.");
  2802                     }
  2803                     Log.Verbose("PEPMessage.Create: From identity calculated.");
  2804 
  2805                     // Get all mail item properties
  2806                     MAPIProperties mapiProperties = omi.GetCommonMessageProperties();
  2807 
  2808                     /* Set retrieved values in newly created PEPMessage.
  2809                      * If a value couldn't be retrieved correctly, use properties
  2810                      * of the Outlook Mail Item as fallback (if possible).
  2811                      */
  2812                     object value = null;
  2813 
  2814                     // Id
  2815                     if (mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Id), out value) == true)
  2816                     {
  2817                         newMessage.Id = value as string;
  2818                     }
  2819 
  2820                     // Subject
  2821                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ShortMsg), out value) == true) &&
  2822                         (value is string shortMsg))
  2823                     {
  2824                         newMessage.ShortMsg = shortMsg;
  2825                     }
  2826                     else
  2827                     {
  2828                         newMessage.ShortMsg = omi.Subject;
  2829                     }
  2830 
  2831                     /* Date & Times
  2832                      * 
  2833                      * Note: The mail item date can be invalid:
  2834                      * For incoming this commonly occurs when creating a mirror -- it's created without a received time.
  2835                      * For outgoing this commonly occurs for unsent mail.
  2836                      * In these cases, the invalid date and time will be returned as 1/1/4501 at 12:00AM
  2837                      * This situation is filtered here and the date is set as null.
  2838                      */
  2839 
  2840                     // ReceivedOn
  2841                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ReceivedOn), out value) == true) &&
  2842                         (value is DateTime receivedOn) &&
  2843                         (receivedOn.Year < 4000))
  2844                     {
  2845                         newMessage.ReceivedOn = receivedOn.ToLocalTime();
  2846                     }
  2847                     else
  2848                     {
  2849                         DateTime? receivedTime = omi.ReceivedTime;
  2850 
  2851                         if (receivedTime?.Year > 4000)
  2852                         {
  2853                             receivedTime = null;
  2854                         }
  2855 
  2856                         newMessage.ReceivedOn = receivedTime;
  2857                     }
  2858 
  2859                     // SentOn
  2860                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.SentOn), out value) == true) &&
  2861                         (value is DateTime sentOn) &&
  2862                         (sentOn.Year < 4000))
  2863                     {
  2864                         newMessage.SentOn = sentOn.ToLocalTime();
  2865                     }
  2866                     else
  2867                     {
  2868                         DateTime? sentTime = omi.SentOn;
  2869 
  2870                         if (sentTime?.Year > 4000)
  2871                         {
  2872                             sentTime = null;
  2873                         }
  2874 
  2875                         newMessage.SentOn = sentTime;
  2876                     }
  2877 
  2878                     // Conversation id
  2879                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationId), out value) == true) &&
  2880                         (value is byte[] conversationId))
  2881                     {
  2882                         newMessage.ConversationId = MapiHelper.PtypBinaryToString(conversationId);
  2883                     }
  2884                     else
  2885                     {
  2886                         newMessage.ConversationId = omi.ConversationID;
  2887                     }
  2888 
  2889                     // Conversation index
  2890                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationIndex), out value) == true) &&
  2891                         (value is byte[] conversationIndex))
  2892                     {
  2893                         newMessage.ConversationIndex = MapiHelper.PtypBinaryToString(conversationIndex);
  2894                     }
  2895                     else
  2896                     {
  2897                         newMessage.ConversationIndex = omi.ConversationIndex;
  2898                     }
  2899 
  2900                     // Conversation topic
  2901                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationTopic), out value) == true) &&
  2902                         (value is string conversationTopic))
  2903                     {
  2904                         newMessage.ConversationTopic = conversationTopic;
  2905                     }
  2906                     else
  2907                     {
  2908                         newMessage.ConversationTopic = omi.ConversationTopic;
  2909                     }
  2910 
  2911                     // Keywords
  2912                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Keywords), out value) == true) &&
  2913                         (value is string keywords))
  2914                     {
  2915                         string[] keywordsArray = keywords.Split(Globals.ListDelimiter);
  2916 
  2917                         if (keywordsArray != null)
  2918                         {
  2919                             foreach (var keyword in keywordsArray)
  2920                             {
  2921                                 newMessage.Keywords.Add(keyword);
  2922                             }
  2923                         }
  2924                     }
  2925 
  2926                     // Get transport headers
  2927                     HeaderList transportHeaders = null;
  2928                     if ((mapiProperties?.TryGetValue(MapiProperty.PidTagTransportMessageHeaders, out value) == true) &&
  2929                         (value is string messageHeaders) &&
  2930                         (string.IsNullOrEmpty(messageHeaders) == false))
  2931                     {
  2932                         // Parse the headers using MimeKit
  2933                         MemoryStream stream = null;
  2934                         try
  2935                         {
  2936                             stream = new MemoryStream(Encoding.UTF8.GetBytes(messageHeaders));
  2937                             MimeParser mimeParser = new MimeParser(stream);
  2938                             transportHeaders = mimeParser.ParseHeaders();
  2939                         }
  2940                         catch (Exception ex)
  2941                         {
  2942                             transportHeaders = null;
  2943                             Log.Error("GetParsedTransportMessageHeaders: Failed to parse MIME headers, " + ex.ToString());
  2944                         }
  2945                         finally
  2946                         {
  2947                             if (stream != null)
  2948                             {
  2949                                 stream.Close();
  2950                                 stream = null;
  2951                             }
  2952                         }
  2953                     }
  2954 
  2955                     // pEp protocol version
  2956                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.PEPProtocolVersion), out value) == true) &&
  2957                         (value is string pEpProtocolVersion))
  2958                     {
  2959                         newMessage.PEPProtocolVersion = pEpProtocolVersion;
  2960                     }
  2961                     else
  2962                     {
  2963                         newMessage.PEPProtocolVersion = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME) as string;
  2964                     }
  2965 
  2966                     // AutoConsume
  2967                     if (((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.AutoConsume), out value) == true) ||
  2968                          (mapiProperties?.TryGetValue(PEPMessage.PidNamePEPAutoConsumeOld, out value) == true)) &&
  2969                          (value is string autoConsume))
  2970                     {
  2971                         newMessage.AutoConsume = autoConsume;
  2972                     }
  2973                     else
  2974                     {
  2975                         newMessage.AutoConsume = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME) as string ??
  2976                                                  PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME) as string;
  2977 
  2978                         // Check backup solution with ReplyTo tunnel if needed
  2979                         if (string.IsNullOrEmpty(newMessage.AutoConsume) &&
  2980                             (newMessage.ReplyTo?.Count > 0))
  2981                         {
  2982                             for (int i = 0; i < newMessage.ReplyTo.Count; i++)
  2983                             {
  2984                                 string address = newMessage.ReplyTo[i].Address?.Trim();
  2985 
  2986                                 if ((address?.EndsWith(PEPMessage.PEPTUNNEL_MAIL_ADDRESS) == true) &&
  2987                                     (address.Split('@')?.GetValue(0) is string tunneledValue) &&
  2988                                     (tunneledValue.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase)))
  2989                                 {
  2990                                     newMessage.AutoConsume = tunneledValue;
  2991                                     break;
  2992                                 }
  2993                             }
  2994                         }
  2995                     }
  2996 
  2997                     // Rating
  2998                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Rating), out value) == true) &&
  2999                         (value is string rating))
  3000                     {
  3001                         newMessage.Rating = AdapterExtensions.ParseRatingString(rating);
  3002                     }
  3003                     else
  3004                     {
  3005                         newMessage.Rating = AdapterExtensions.ParseRatingString(PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_ENC_STATUS_NAME) as string);
  3006                     }
  3007 
  3008                     // Key import
  3009                     if (((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.KeyImport), out value) == true) ||
  3010                          (mapiProperties?.TryGetValue(PEPMessage.PidNamePEPKeyImportOld, out value) == true)) &&
  3011                         (value is string keyImport))
  3012                     {
  3013                         newMessage.KeyImport = keyImport;
  3014                     }
  3015                     else
  3016                     {
  3017                         newMessage.KeyImport = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_KEY_IMPORT_NAME) as string ??
  3018                                                PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD) as string;
  3019 
  3020                         // Check backup solution with ReplyTo tunnel if needed
  3021                         if (string.IsNullOrEmpty(newMessage.KeyImport) &&
  3022                             (newMessage.ReplyTo?.Count > 0))
  3023                         {
  3024                             for (int i = 0; i < newMessage.ReplyTo.Count; i++)
  3025                             {
  3026                                 string address = newMessage.ReplyTo[i].Address?.Trim();
  3027 
  3028                                 // At the moment, there are only two possible tunneled values: autoconsume and the fpr
  3029                                 if ((address?.EndsWith(PEPMessage.PEPTUNNEL_MAIL_ADDRESS) == true) &&
  3030                                     (address.Split('@')?.GetValue(0) is string tunneledValue) &&
  3031                                     (tunneledValue.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase) == false))
  3032                                 {
  3033                                     newMessage.KeyImport = tunneledValue;
  3034                                     break;
  3035                                 }
  3036                             }
  3037                         }
  3038                     }
  3039 
  3040                     // Key list
  3041                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.KeyList), out value) == true) &&
  3042                         (value is string keyList))
  3043                     {
  3044                         newMessage.KeyList = keyList;
  3045                     }
  3046                     else
  3047                     {
  3048                         newMessage.KeyList = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_KEY_LIST_NAME) as string;
  3049                     }
  3050 
  3051                     // Force protection
  3052                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ForceProtectionId), out value) == true) &&
  3053                         (value is string forceProtectionId))
  3054                     {
  3055                         newMessage.ForceProtectionId = forceProtectionId;
  3056                     }
  3057                     else
  3058                     {
  3059                         newMessage.ForceProtectionId = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_FORCE_PROTECTION_NAME) as string;
  3060                     }
  3061 
  3062                     // Never unsecure
  3063                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.NeverUnsecure), out value) == true) &&
  3064                         (value is bool neverUnsecure))
  3065                     {
  3066                         newMessage.NeverUnsecure = neverUnsecure;
  3067                     }
  3068                     else
  3069                     {
  3070                         newMessage.NeverUnsecure = (PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_NEVER_UNSECURE_NAME) != null);
  3071                     }
  3072 
  3073                     // User properties for outgoing messages
  3074                     if (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)
  3075                     {
  3076                         // Enable protection
  3077                         if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.EnableProtection, out value) &&
  3078                             (value is bool enableProtection))
  3079                         {
  3080                             newMessage.EnableProtection = enableProtection;
  3081                         }
  3082 
  3083                         // Force unencrypted
  3084                         if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.ForceUnencrypted, out value) &&
  3085                             (value is bool forceUnencrypted))
  3086                         {
  3087                             newMessage.ForceUnencrypted = forceUnencrypted;
  3088                         }
  3089                     }
  3090 
  3091                     // Add additional headers
  3092                     string autocryptHeaderKey = "Autocrypt";
  3093                     try
  3094                     {
  3095                         Header ac = transportHeaders?.FirstOrDefault(a => (a.Field?.Trim()?.Equals(autocryptHeaderKey, StringComparison.OrdinalIgnoreCase) == true));
  3096                         if (ac != null)
  3097                         {
  3098                             StringPair autocryptHeader = new StringPair
  3099                             {
  3100                                 Name = ac.Field.Trim(),
  3101                                 Value = ac.Value.Trim()
  3102                             };
  3103                             newMessage.AdditionalHeaders.Add(autocryptHeader);
  3104                         }
  3105                     }
  3106                     catch (Exception ex)
  3107                     {
  3108                         Log.Error("PEPMessage.Create: Error adding additional headers. " + ex.ToString());
  3109                     }
  3110 
  3111                     // Calculate text body and attachments
  3112                     // Body
  3113                     if (omi.Body != null)
  3114                     {
  3115                         newMessage.LongMsg = omi.Body.Replace("\r\n", "\n");
  3116 
  3117                         // Save as RTF
  3118                         try
  3119                         {
  3120                             byte[] rtfBody = omi.RTFBody;
  3121 
  3122                             if ((rtfBody != null) &&
  3123                                 (rtfBody.Length > 0))
  3124                             {
  3125                                 newMessage.LongMsgFormattedRtf = Encoding.ASCII.GetString(rtfBody, 0, rtfBody.Length);
  3126                             }
  3127                         }
  3128                         catch
  3129                         {
  3130                             Log.Warning("PEPMessage.Create: Unable to read RTF body in MailItem.");
  3131                         }
  3132 
  3133                         // Force any rich text into HTML
  3134                         // WARNING: This is technically a modifcation of the original MailItem
  3135                         // It should be further investigated if this can be removed in the future.
  3136                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatRichText)
  3137                         {
  3138                             omi.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
  3139                         }
  3140 
  3141                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatHTML)
  3142                         {
  3143                             newMessage.LongMsgFormattedHtml = omi.HTMLBody;
  3144                         }
  3145                     }
  3146 
  3147                     Log.Verbose("PEPMessage.Create: Body calculated.");
  3148 
  3149                     // Attachments
  3150                     newMessage.Attachments.Clear();
  3151                     attachments = omi.Attachments;
  3152                     for (int i = 1; i <= attachments.Count; i++)
  3153                     {
  3154                         attachment = attachments[i];
  3155 
  3156                         try
  3157                         {
  3158                             var newAttachment = new PEPAttachment(attachment, newMessage.LongMsgFormattedHtml ?? newMessage.LongMsgFormattedRtf ?? newMessage.LongMsg);
  3159                             if (newAttachment?.Data != null)
  3160                             {
  3161                                 newMessage.Attachments.Add(newAttachment);
  3162                             }
  3163                         }
  3164                         catch { }
  3165 
  3166                         attachment = null;
  3167                     }
  3168                 }
  3169 
  3170                 status = Globals.ReturnStatus.Success;
  3171                 Log.Verbose("PEPMessage.Create: New PEPMessage created from OMI.");
  3172             }
  3173             catch (Exception ex)
  3174             {
  3175                 status = Globals.ReturnStatus.Failure;
  3176                 Log.Error("PEPMessage.Create: failure occured, " + ex.ToString());
  3177             }
  3178             finally
  3179             {
  3180                 attachment = null;
  3181                 attachments = null;
  3182                 recipient = null;
  3183                 recipients = null;
  3184             }
  3185 
  3186             createdMessage = newMessage;
  3187             Log.Verbose("PEPMessage.Create: ReturnStatus=" + status.ToString());
  3188 
  3189             return status;
  3190         }
  3191 
  3192         /// <summary>
  3193         /// Creates a PEPMessage from a MimeMessage.
  3194         /// </summary>
  3195         /// <param name="mimeMessage">The MimeMessage to create a PEPMessage from.</param>
  3196         /// <param name="createdMessage">The created PEPMessage.</param>
  3197         /// <returns>The creation status.</returns>
  3198         public static Globals.ReturnStatus Create(MimeMessage mimeMessage,
  3199                                                   out PEPMessage createdMessage)
  3200         {
  3201             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  3202             PEPMessage pEpMessage = new PEPMessage();
  3203 
  3204             try
  3205             {
  3206                 // Attachments
  3207                 if (mimeMessage.Attachments != null)
  3208                 {
  3209                     foreach (var attachment in mimeMessage.Attachments)
  3210                     {
  3211                         // Cast attachment to MimePart and check if casting was successful
  3212                         if (attachment is MimePart currentAttachment)
  3213                         {
  3214                             PEPAttachment pEpAttachment = new PEPAttachment
  3215                             {
  3216                                 // Assign content type
  3217                                 MimeType = currentAttachment.ContentType?.MimeType,
  3218                                 FileName = currentAttachment.FileName
  3219                             };
  3220 
  3221                             // Decode content object and copy to data property
  3222                             using (var memStream = new MemoryStream())
  3223                             {
  3224                                 currentAttachment.Content?.DecodeTo(memStream);
  3225                                 pEpAttachment.Data = memStream.ToArray();
  3226                             }
  3227 
  3228                             // Add attachment to PEPMessage.Attachments collection
  3229                             pEpMessage.Attachments.Add(pEpAttachment);
  3230                         }
  3231                         else
  3232                         {
  3233                             Log.Error("PEPMessage.Create: Error casting attachment.");
  3234                         }
  3235                     }
  3236                 }
  3237 
  3238                 // Check for inline content
  3239                 foreach (var bodyPart in mimeMessage.BodyParts)
  3240                 {
  3241                     // If inline content is found, add it as attachment
  3242                     if (bodyPart.ContentDisposition?.Disposition == ContentDisposition.Inline)
  3243                     {
  3244                         var pEpAttachment = new PEPAttachment
  3245                         {
  3246                             ContentId = bodyPart.ContentId,
  3247                             MimeType = bodyPart.ContentType?.MimeType
  3248                         };
  3249 
  3250                         // Decode content object and copy to data property
  3251                         using (var memStream = new MemoryStream())
  3252                         {
  3253                             (bodyPart as MimePart)?.Content?.DecodeTo(memStream);
  3254                             pEpAttachment.Data = memStream.ToArray();
  3255                         }
  3256 
  3257                         // Add inline content as attachment
  3258                         pEpMessage.Attachments.Add(pEpAttachment);
  3259                     }
  3260                 }
  3261 
  3262                 // Body
  3263                 pEpMessage.LongMsg = mimeMessage.TextBody;
  3264                 pEpMessage.LongMsgFormattedHtml = mimeMessage.HtmlBody;
  3265 
  3266                 // Subject
  3267                 pEpMessage.ShortMsg = mimeMessage.Subject;
  3268 
  3269                 // Process recipients
  3270                 // BCC
  3271                 for (int i = 0; i < mimeMessage.Bcc?.Count; i++)
  3272                 {
  3273                     PEPIdentity ident = new PEPIdentity
  3274                     {
  3275                         Address = (mimeMessage.Bcc[i] as MailboxAddress)?.Address,
  3276                         UserName = (mimeMessage.Bcc[i] as MailboxAddress)?.Name
  3277                     };
  3278 
  3279                     pEpMessage.Bcc.Add(ident);
  3280                 }
  3281 
  3282                 // CC
  3283                 for (int i = 0; i < mimeMessage.Cc?.Count; i++)
  3284                 {
  3285                     PEPIdentity ident = new PEPIdentity
  3286                     {
  3287                         Address = (mimeMessage.Cc[i] as MailboxAddress)?.Address,
  3288                         UserName = (mimeMessage.Cc[i] as MailboxAddress)?.Name
  3289                     };
  3290 
  3291                     pEpMessage.Cc.Add(ident);
  3292                 }
  3293 
  3294                 // To
  3295                 for (int i = 0; i < mimeMessage.To?.Count; i++)
  3296                 {
  3297                     PEPIdentity ident = new PEPIdentity
  3298                     {
  3299                         Address = (mimeMessage.To[i] as MailboxAddress)?.Address,
  3300                         UserName = (mimeMessage.To[i] as MailboxAddress)?.Name
  3301                     };
  3302 
  3303                     pEpMessage.To.Add(ident);
  3304                 }
  3305 
  3306                 // From
  3307                 if (mimeMessage.Sender != null)
  3308                 {
  3309                     PEPIdentity ident = new PEPIdentity
  3310                     {
  3311                         Address = mimeMessage.Sender?.Address,
  3312                         UserName = mimeMessage.Sender?.Name
  3313                     };
  3314 
  3315                     pEpMessage.From = ident;
  3316                 }
  3317                 else if (mimeMessage.From?.Count == 1)
  3318                 {
  3319                     PEPIdentity ident = new PEPIdentity
  3320                     {
  3321                         Address = (mimeMessage.From[0] as MailboxAddress)?.Address,
  3322                         UserName = (mimeMessage.From[0] as MailboxAddress)?.Name
  3323                     };
  3324 
  3325                     pEpMessage.From = ident;
  3326                 }
  3327 
  3328                 if (mimeMessage.Headers != null)
  3329                 {
  3330                     // pEp header fields
  3331                     // Key List
  3332                     Header keyList = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_KEY_LIST_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3333                     if (keyList != null)
  3334                     {
  3335                         pEpMessage.KeyList = keyList.Value?.Trim();
  3336                     }
  3337 
  3338                     // Key Import
  3339                     Header keyImport = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_KEY_IMPORT_NAME, StringComparison.OrdinalIgnoreCase) == true)) ??
  3340                                        mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_KEY_IMPORT_NAME_OLD, StringComparison.OrdinalIgnoreCase) == true));
  3341                     if (keyImport != null)
  3342                     {
  3343                         pEpMessage.KeyImport = keyImport.Value?.Trim();
  3344                     }
  3345 
  3346                     // Auto consume
  3347                     Header autoConsume = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase) == true)) ??
  3348                                          mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD, StringComparison.OrdinalIgnoreCase) == true));
  3349                     if (autoConsume != null)
  3350                     {
  3351                         pEpMessage.AutoConsume = autoConsume.Value?.Trim();
  3352                     }
  3353 
  3354                     // Force protection
  3355                     Header forceProtection = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_FORCE_PROTECTION_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3356                     if (forceProtection != null)
  3357                     {
  3358                         pEpMessage.ForceProtectionId = forceProtection.Value?.Trim();
  3359                     }
  3360 
  3361                     // Never unsecure
  3362                     Header neverUnsecure = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_NEVER_UNSECURE_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3363                     if (neverUnsecure != null)
  3364                     {
  3365                         pEpMessage.NeverUnsecure = true;
  3366                     }
  3367 
  3368                     // pEp protocol version
  3369                     Header pEpVersion = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3370                     if (pEpVersion != null)
  3371                     {
  3372                         pEpMessage.PEPProtocolVersion = pEpVersion.Value?.Trim();
  3373                     }
  3374 
  3375                     // Additional headers
  3376                     string autocryptHeaderKey = "Autocrypt";
  3377                     try
  3378                     {
  3379                         Header ac = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(autocryptHeaderKey, StringComparison.OrdinalIgnoreCase) == true));
  3380                         if (ac != null)
  3381                         {
  3382                             StringPair autocryptHeader = new StringPair
  3383                             {
  3384                                 Name = ac.Field.Trim(),
  3385                                 Value = ac.Value.Trim()
  3386                             };
  3387                             pEpMessage.AdditionalHeaders.Add(autocryptHeader);
  3388                         }
  3389                     }
  3390                     catch (Exception ex)
  3391                     {
  3392                         Log.Error("PEPMessage.Create: Error adding additional headers. " + ex.ToString());
  3393                     }
  3394                 }
  3395 
  3396                 // Message date
  3397                 if (mimeMessage.Date != null)
  3398                 {
  3399                     pEpMessage.SentOn = mimeMessage.Date.DateTime;
  3400                 }
  3401             }
  3402             catch (Exception ex)
  3403             {
  3404                 status = Globals.ReturnStatus.Failure;
  3405                 Log.Error("Create: Error creating PEPMessage from MimeMessage. " + ex.ToString());
  3406             }
  3407 
  3408             createdMessage = pEpMessage;
  3409 
  3410             return status;
  3411         }
  3412 
  3413         /// <summary>
  3414         /// Creates a PEPMessage from a MIME string.
  3415         /// </summary>
  3416         /// <param name="message">The MIME message as a string.</param>
  3417         /// <param name="createdMessage">The created PEPMessage.</param>
  3418         /// <returns>True if a message could be created, otherwise false.</returns>
  3419         public static bool Create(string message, out PEPMessage createdMessage)
  3420         {
  3421             createdMessage = null;
  3422 
  3423             try
  3424             {
  3425                 TextMessage msg = ThisAddIn.PEPEngine.MIMEDecodeMessage(message);
  3426                 if (PEPMessage.Create(msg, out PEPMessage pEpMsg) == Globals.ReturnStatus.Success)
  3427                 {
  3428                     createdMessage = pEpMsg;
  3429                 }
  3430                 else
  3431                 {
  3432                     Log.Error("Create: Error creating PEPMessage from string.");
  3433                 }
  3434             }
  3435             catch (Exception ex)
  3436             {
  3437                 createdMessage = null;
  3438                 Log.Error("Create: Error creating PEPMessage from string. " + ex.ToString());
  3439             }
  3440 
  3441             return (createdMessage != null);
  3442         }
  3443 
  3444         /// <summary>
  3445         /// Returns the mirror of a given Outlook mail item based on its EntryId or MessageId
  3446         /// and the sender's user name.
  3447         /// </summary>
  3448         /// <param name="id">The entry id of the original encrypted mail item.</param>
  3449         /// <param name="userName">The user name of the original's sender.</param>
  3450         /// <returns>The mirror mail item if successful. Otherwise null.</returns>
  3451         public static Outlook.MailItem GetMirror(string id, string userName)
  3452         {
  3453             string mirrorID;
  3454             string origEntryID;
  3455             Outlook.MailItem mirrorItem = null;
  3456             Outlook.MailItem currMirror = null;
  3457             Outlook.NameSpace ns = Globals.ThisAddIn.Application.Session;
  3458             Outlook.Store pEpStore = Globals.ThisAddIn.PEPStoreRootFolder.Store;
  3459             Outlook.Folder targetFolder = null;
  3460             Outlook.Items targetFolderItems = null;
  3461 
  3462             if (string.IsNullOrEmpty(id) == false)
  3463             {
  3464                 // Try to find in cache
  3465                 try
  3466                 {
  3467                     lock (mutexMirror)
  3468                     {
  3469                         mirrorID = mirrorCache[id];
  3470                     }
  3471 
  3472                     // Note: If messageId was stored, this method returns null
  3473                     mirrorItem = (Outlook.MailItem)ns.GetItemFromID(mirrorID, pEpStore.StoreID);
  3474                 }
  3475                 catch
  3476                 {
  3477                     mirrorItem = null;
  3478                 }
  3479 
  3480                 // Try to find same EntryID
  3481                 if (mirrorItem == null)
  3482                 {
  3483                     try
  3484                     {
  3485                         // Note: If messageId was stored, this method returns null
  3486                         mirrorItem = (Outlook.MailItem)ns.GetItemFromID(id, pEpStore.StoreID);
  3487                     }
  3488                     catch
  3489                     {
  3490                         mirrorItem = null;
  3491                     }
  3492                 }
  3493 
  3494                 // Try to find by user property
  3495                 if (mirrorItem == null)
  3496                 {
  3497                     Outlook.UserDefinedProperties up = null;
  3498 
  3499                     try
  3500                     {
  3501                         targetFolder = PEPMessage.GetMirrorFolder(userName);
  3502                         targetFolderItems = targetFolder.Items;
  3503 
  3504                         /* Add user property to folder if not there already:
  3505                          * "If you are trying to use the Find or Restrict methods with user-defined fields,
  3506                          * the fields must be defined in the folder, otherwise an error will occur."
  3507                          * See: https://msdn.microsoft.com/en-us/library/office/ff869662.aspx
  3508                         */
  3509                         up = targetFolder.UserDefinedProperties as Outlook.UserDefinedProperties;
  3510                         if (up.Find(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID) == null)
  3511                         {
  3512                             up.Add(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, Outlook.OlUserPropertyType.olText);
  3513                         }
  3514 
  3515                         string filter = string.Format("[{0}] = '{1}'", MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, id);
  3516                         mirrorItem = targetFolderItems.Find(filter) as Outlook.MailItem;
  3517 
  3518                         if (mirrorItem != null)
  3519                         {
  3520                             lock (mutexMirror)
  3521                             {
  3522                                 mirrorCache[id] = mirrorItem.EntryID;
  3523                             }
  3524                         }
  3525                     }
  3526                     catch (Exception ex)
  3527                     {
  3528                         mirrorItem = null;
  3529                         Log.Error("GetMirror: Error searching for mirror omi. " + ex.ToString());
  3530                     }
  3531                     finally
  3532                     {
  3533                         up = null;
  3534                     }
  3535                 }
  3536 
  3537                 // Nothing worked, search each item by user property (slow)
  3538                 if (mirrorItem == null)
  3539                 {
  3540                     targetFolder = PEPMessage.GetMirrorFolder(userName);
  3541                     targetFolderItems = targetFolder.Items;
  3542 
  3543                     // Note: index starts at 1
  3544                     for (int i = 1; i <= targetFolderItems.Count; i++)
  3545                     {
  3546                         currMirror = null;
  3547 
  3548                         try
  3549                         {
  3550                             currMirror = (Outlook.MailItem)targetFolderItems[i];
  3551                             origEntryID = (string)currMirror.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, "");
  3552 
  3553                             if (origEntryID == id)
  3554                             {
  3555                                 lock (mutexMirror)
  3556                                 {
  3557                                     mirrorCache[id] = currMirror.EntryID;
  3558                                 }
  3559 
  3560                                 mirrorItem = currMirror;
  3561                                 break;
  3562                             }
  3563                         }
  3564                         catch { }
  3565 
  3566                         currMirror = null;
  3567                     }
  3568                 }
  3569             }
  3570             else
  3571             {
  3572                 Log.Error("MsgProcessor.GetMirror: EntryId is null or empty.");
  3573             }
  3574 
  3575             /* Release objects
  3576              * Note: Do not release mirror or currMirror (which may point to mirror).
  3577              * These are returned for external use.
  3578              */
  3579             ns = null;
  3580             pEpStore = null;
  3581             targetFolder = null;
  3582             targetFolderItems = null;
  3583 
  3584             return mirrorItem;
  3585         }
  3586 
  3587         /// <summary>
  3588         /// Gets the mirror folder based on a user name.
  3589         /// </summary>
  3590         /// <param name="userName">The user name.</param>
  3591         /// <returns>The Outlook folder for this user name.</returns>
  3592         public static Outlook.Folder GetMirrorFolder(string userName)
  3593         {
  3594             string[] specialChars;
  3595             Outlook.Folder folder;
  3596             Outlook.Folders folders;
  3597             StringBuilder strBuilder = new StringBuilder();
  3598 
  3599             try
  3600             {
  3601                 folders = Globals.ThisAddIn.PEPStoreRootFolder.Folders;
  3602             }
  3603             catch (Exception ex)
  3604             {
  3605                 Log.Error("GetMirrorFolder: Error getting PEPStoreRootFolder. " + ex.ToString());
  3606                 folders = null;
  3607                 return null;
  3608             }
  3609 
  3610             if (userName != null)
  3611             {
  3612                 /* Remove special characters from folder name.
  3613                  * See: https://msdn.microsoft.com/en-us/library/aa493942(v=exchg.80).aspx
  3614                  */
  3615                 specialChars = new string[] { "[", "]", "/", "\\", "&", "~", "?", "*", "|", "<", ">", "\"", ";", ":", "+" };
  3616                 if (userName != null)
  3617                 {
  3618                     strBuilder.Append(userName);
  3619                     for (int i = 0; i < specialChars.Length; i++)
  3620                     {
  3621                         strBuilder.Replace(specialChars[i], "");
  3622                     }
  3623                     userName = strBuilder.ToString();
  3624                 }
  3625 
  3626                 // Use unknown if invalid
  3627                 if (string.IsNullOrWhiteSpace(userName))
  3628                 {
  3629                     userName = MailItemExtensions.UNKNOWN_SENDER;
  3630                 }
  3631             }
  3632             else
  3633             {
  3634                 userName = MailItemExtensions.UNKNOWN_SENDER;
  3635             }
  3636 
  3637             try
  3638             {
  3639                 folder = (Outlook.Folder)folders[userName];
  3640                 Log.Verbose("GetMirrorFolder: Using existing folder " + userName);
  3641             }
  3642             catch
  3643             {
  3644                 folder = (Outlook.Folder)folders.Add(userName);
  3645                 Log.Verbose("GetMirrorFolder: Creating new folder + " + userName);
  3646             }
  3647 
  3648             folders = null;
  3649 
  3650             return folder;
  3651         }
  3652 
  3653         /// <summary>
  3654         /// Determines if the given text is PGP text based on starting text sequence.
  3655         /// The starting sequence must be "-----BEGIN PGP MESSAGE-----"
  3656         /// </summary>
  3657         /// <param name="text">The text to test if it is PGP text.</param>
  3658         /// <returns>True if the given text is PGP text, otherwise false.</returns>
  3659         public static bool IsPGPText(string text)
  3660         {
  3661             if (string.IsNullOrEmpty(text) == false)
  3662             {
  3663                 string pgp_text = text.Trim();
  3664                 return (pgp_text.StartsWith("-----BEGIN PGP MESSAGE-----"));
  3665             }
  3666             else
  3667             {
  3668                 return (false);
  3669             }
  3670         }
  3671 
  3672         /// <summary>
  3673         /// Determines if the given message is considered secure.
  3674         /// Currently, only PGP encrypted messages will be detected.
  3675         /// </summary>
  3676         /// <param name="msg">The message to check security for.</param>
  3677         /// <returns>True if the given message is encrypted, otherwise false.</returns>
  3678         public static bool GetIsSecure(PEPMessage msg)
  3679         {
  3680             if (msg != null)
  3681             {
  3682                 // Partitioned or inline PGP format
  3683                 if (msg.LongMsg != null && PEPMessage.IsPGPText(msg.LongMsg))
  3684                 {
  3685                     return (true);
  3686                 }
  3687 
  3688                 // PGP/MIME format
  3689                 if (PEPMessage.GetIsPGPMIMEEncrypted(msg))
  3690                 {
  3691                     return (true);
  3692                 }
  3693             }
  3694 
  3695             return (false);
  3696         }
  3697 
  3698         /// <summary>
  3699         /// Determines if the given message is encrypted in PGP/MIME format.
  3700         /// </summary>
  3701         /// <param name="msg">The message to check encryption for.</param>
  3702         /// <returns>True if the given message is PGP/MIME encrypted, otherwise false.</returns>
  3703         public static bool GetIsPGPMIMEEncrypted(PEPMessage msg)
  3704         {
  3705             bool result = false;
  3706             bool versionInfoFound = false;
  3707             bool contentFound = false;
  3708 
  3709             if (msg != null)
  3710             {
  3711                 // Require only two attachments (version identification & encrypted content)
  3712                 // However, allow the attachments to be in any order
  3713                 if (msg.Attachments.Count == 2)
  3714                 {
  3715                     foreach (PEPAttachment attach in msg.Attachments)
  3716                     {
  3717                         if (attach.IsPGPMIMEVersionInfoFormat)
  3718                         {
  3719                             versionInfoFound = true;
  3720                         }
  3721                         else if (attach.IsPGPMIMEContentFormat)
  3722                         {
  3723                             contentFound = true;
  3724                         }
  3725                     }
  3726 
  3727                     if (versionInfoFound && contentFound)
  3728                     {
  3729                         result = true;
  3730                     }
  3731                 }
  3732             }
  3733 
  3734             return (result);
  3735         }
  3736     }
  3737 
  3738     /// <summary>
  3739     /// Custom exception for when the PGP/MIME attachment exceeds the server's
  3740     /// allowable limit.
  3741     /// </summary>
  3742     [Serializable]
  3743     internal class AttachmentSizeException : Exception
  3744     {
  3745         public AttachmentSizeException()
  3746         {
  3747         }
  3748 
  3749         public AttachmentSizeException(string message)
  3750             : base(message)
  3751         {
  3752         }
  3753 
  3754         public AttachmentSizeException(string message, Exception inner)
  3755             : base(message, inner)
  3756         {
  3757         }
  3758     }
  3759 }