PEPMessage.cs
author Thomas
Thu, 02 Jul 2020 09:13:27 +0200
changeset 3177 72be9d974fb7
parent 3120 5aa2a0c25d97
permissions -rw-r--r--
Adjust Sync settings to new specs in Adapter regarding start/stop sync
     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                 int index = attachments.Count;
  2003                 while (index > 0)
  2004                 {
  2005                     /* OUT-193: We cannot use attachments.Remove() here, as it
  2006                      * seems to be buggy in Outlook 2013. Let's hope that
  2007                      * using Attachment.Delete() has no bad side effects on
  2008                      * other Outlook versions.
  2009                      * OUT-292: The Delete() method can fail under rare circumstances.
  2010                      * In this case, just log the error and break. 
  2011                      * NOTE: We can NOT use a for loop here, as this leads to unforeseeable 
  2012                      * issues with actual attachments (supposedly due to race conditions on 
  2013                      * multithreaded decryption operations).
  2014                      */
  2015                     try
  2016                     {
  2017                         /* OUT-712: Embedded images aren't shown if we apply the round-trip through the engine
  2018                          * and apply them again to the mail item. Therefore, only remove those attachments
  2019                          * that don't exist in the PEPMessage. If an attachment that is to be attached from the
  2020                          * PEPMessage already exists in the Outlook mail item, don't remove it from the mail item
  2021                          * but do remove it from the PEPMessage to avoid attaching it again.
  2022                          */
  2023                         if ((isPGPMIMEMsg == false) &&
  2024                             (this._Attachments.FindIndex(a => a.EqualsByContent(new PEPAttachment(attachments[index]))) is int i) &&
  2025                             (i >= 0))
  2026                         {
  2027                             this._Attachments.RemoveAt(i);
  2028                         }
  2029                         else
  2030                         {
  2031                             attachments[index].Delete();
  2032                         }
  2033                         index--;
  2034                     }
  2035                     catch (Exception ex)
  2036                     {
  2037                         Log.Error("Applyto: Error deleting attachment. " + ex.ToString());
  2038                         break;
  2039                     }
  2040                 }
  2041 
  2042                 if (isPGPMIMEMsg)
  2043                 {
  2044                     PEPAttachment pgpMIME = this.ConvertToPGPMIMEAttachment();
  2045 
  2046                     if (pgpMIME != null)
  2047                     {
  2048                         try
  2049                         {
  2050                             pgpMIME.AddTo(attachments);
  2051                         }
  2052                         catch (Exception ex)
  2053                         {
  2054                             Log.Verbose("Failed to add PGP/MIME attachment. " + ex.ToString());
  2055 
  2056                             /* If adding the attachment fails, check if it is because of file size limitations
  2057                              * and throw dedicated exception in that case (needed for user feedback).
  2058                              */
  2059                             var pgpgMIMESizeInKB = pgpMIME.Data.Length / 1000; // Get size in kB
  2060                             if (pgpgMIMESizeInKB > omi.GetMaxMailSize())
  2061                             {
  2062                                 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));
  2063                             }
  2064                             else
  2065                             {
  2066                                 throw;
  2067                             }
  2068                         }
  2069                     }
  2070                     else
  2071                     {
  2072                         throw new Exception("Failed to create and add PGP/MIME attachment.");
  2073                     }
  2074                 }
  2075                 else
  2076                 {
  2077                     for (int i = 0; i < this._Attachments.Count; i++)
  2078                     {
  2079                         try
  2080                         {
  2081                             this._Attachments[i].AddTo(attachments, ("attachment" + i.ToString()));
  2082                         }
  2083                         catch (Exception ex)
  2084                         {
  2085                             Log.Error("ApplyTo: Error adding attachment. " + ex.ToString());
  2086                         }
  2087                     }
  2088                 }
  2089 
  2090                 // Gather properties to set via MAPI
  2091                 MAPIProperties propertiesToSet = new MAPIProperties();
  2092 
  2093                 // Add subject
  2094                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ShortMsg), this._ShortMsg);
  2095 
  2096                 // Add id
  2097                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.Id), this._Id);
  2098 
  2099                 /* Conversation information
  2100                  * Note: PidTagConversationId cannot be set even through the MAPI accessor.
  2101                  * This is by design since this property is computed automatically from other properties.
  2102                  * See: https://msdn.microsoft.com/en-us/library/ee204279.aspx
  2103                  */
  2104                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ConversationIndex), MapiHelper.StringToPtypBinary(this._ConversationIndex));
  2105                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ConversationTopic), this._ConversationTopic ?? string.Empty);
  2106                 propertiesToSet.Add(MapiProperty.PidTagConversationIndexTracking, true);
  2107 
  2108                 /* Add internal properties
  2109                  * Note: there is no need to check for defaults as it's all handled within the SetInterpretedProperty method.
  2110                  * Also, .Save() must never be called here as the .ApplyTo() method can be used on MailItems that need to change properties before save.
  2111                  */
  2112                 if (setInternalHeaderFields)
  2113                 {
  2114                     // Note: ignore return status
  2115                     propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.KeyList), this._KeyList);
  2116 
  2117                     // Only store rating once and never change it
  2118                     if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.Rating, out object storedRating) &&
  2119                         ((storedRating as pEpRating?) == pEpRating.pEpRatingUndefined))
  2120                     {
  2121                         propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.Rating), this._Rating);
  2122                     }
  2123                 }
  2124 
  2125                 // Note: ignore return status
  2126                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.AutoConsume), this._AutoConsume);
  2127                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.ForceProtectionId), this._ForceProtectionId);
  2128                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.KeyImport), this._KeyImport);
  2129                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.NeverUnsecure), this._NeverUnsecure);
  2130                 propertiesToSet.AddPEPMessageProperty(nameof(PEPMessage.PEPProtocolVersion), this._PEPProtocolVersion);
  2131 
  2132                 // Cancel ReadReceiptRequested and OriginatorDeliveryReportRequested for automatic mails
  2133                 if (string.IsNullOrEmpty(this.AutoConsume) == false)
  2134                 {
  2135                     try
  2136                     {
  2137                         omi.ReadReceiptRequested = false;
  2138                         omi.OriginatorDeliveryReportRequested = false;
  2139                     }
  2140                     catch (Exception ex)
  2141                     {
  2142                         Log.Error("PEPMessage.ApplyTo: Error cancelling ReadReceiptRequested and OriginatorDeliveryReportRequested: " + ex.ToString());
  2143                     }
  2144                 }
  2145 
  2146                 // Set all MAPI properties at once and report errors if any
  2147                 Dictionary<string, object> result = MapiHelper.SetProperties(omi, propertiesToSet);
  2148 
  2149                 // Log errors if any
  2150                 if (result != null)
  2151                 {
  2152                     foreach (var error in result)
  2153                     {
  2154                         if (error.Value != null)
  2155                         {
  2156                             Log.Error("PEPMessage.ApplyTo: Error setting MAPI property {0}: {1}", error.Key, (error.Value as int?)?.ToString("X2") ?? "<null>");
  2157                         }
  2158                     }
  2159                 }
  2160             }
  2161             catch (Exception ex)
  2162             {
  2163                 status = Globals.ReturnStatus.Failure;
  2164                 Log.Error("PEPMessage.ApplyTo: Failure occured, " + ex.ToString());
  2165                 throw;
  2166             }
  2167             finally
  2168             {
  2169                 accounts = null;
  2170                 attachments = null;
  2171                 newRecipient = null;
  2172                 currAccount = null;
  2173                 recipients = null;
  2174                 sendUsingAccount = null;
  2175                 fromRecipient = null;
  2176                 ns = null;
  2177             }
  2178 
  2179             return status;
  2180         }
  2181 
  2182         /// <summary>
  2183         /// Converts this message into a single PGP/MIME attachment.
  2184         /// This is necessary in order for Outlook and MAPI to correctly transport PGP/MIME messages.
  2185         /// The attachment will report itself as S/MIME to MAPI, S/MIME attachments are passed-through as unmodified text.
  2186         /// Since we can insert unmodified text, this allows us to build a correct PGP/MIME message from existing attachments.
  2187         /// Note: To prevent overencoding of the message during transport, we have ot make sure to always use "CRLF" line ending
  2188         ///       in the message instead of "LF". Tests showed that using the latter can lead to Exchange encoding the whole message
  2189         ///       again as quoted-printable and adding CRLF in addition to the present LF line endings.
  2190         /// Warning: This can return null if a failure occured.
  2191         /// </summary>
  2192         /// <returns>This message converted to a single PGP/MIME attachment, otherwise null.</returns>
  2193         private PEPAttachment ConvertToPGPMIMEAttachment()
  2194         {
  2195             string boundary;
  2196             byte[] bytes;
  2197             bool versionInfoFound = false;
  2198             bool contentFound = false;
  2199             PEPAttachment newAttachment = null;
  2200             List<byte> data = new List<byte>();
  2201 
  2202             if (this._Attachments.Count == 2)
  2203             {
  2204                 boundary = "----=_NextPart_" + Guid.NewGuid().ToString().Replace('-', '_');
  2205 
  2206                 /* See below for an example PGP/MIME formatted message.
  2207                  * this is from RFC 3156.  
  2208                  *    
  2209                  *  Content-Type: multipart/encrypted; boundary=foo;
  2210                  *     protocol="application/pgp-encrypted"
  2211                  * 
  2212                  *  --foo
  2213                  *  Content-Type: application/pgp-encrypted
  2214                  * 
  2215                  *  Version: 1
  2216                  * 
  2217                  *  --foo
  2218                  *  Content-Type: application/octet-stream
  2219                  * 
  2220                  *  -----BEGIN PGP MESSAGE-----
  2221                  *  Version: 2.6.2
  2222                  * 
  2223                  *  hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj
  2224                  *  eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ
  2225                  *  g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA
  2226                  *  AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3
  2227                  *  1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8=
  2228                  *  =zzaA
  2229                  *  -----END PGP MESSAGE-----
  2230                  * 
  2231                  *  --foo--
  2232                  */
  2233 
  2234                 // Add internet headers (these are not added by default for S/MIME messages which allows them to be customized)
  2235                 bytes = Encoding.UTF8.GetBytes("MIME-Version: 1.0\r\n" +
  2236                                                "Content-Type: multipart/encrypted;\r\n" +
  2237                                                "\tprotocol=\"application/pgp-encrypted\";\r\n" +
  2238                                                "\tboundary=\"" + boundary + "\"\r\n");
  2239                 data.AddRange(bytes);
  2240 
  2241                 // Add the version identification attachment
  2242                 bytes = Encoding.UTF8.GetBytes("\r\n--" + boundary +
  2243                                                "\nContent-Type: application/pgp-encrypted\r\n" +
  2244                                                "Content-Description: PGP/MIME version identification\r\n\r\n");
  2245                 data.AddRange(bytes);
  2246 
  2247                 foreach (PEPAttachment attach in this._Attachments)
  2248                 {
  2249                     if (attach.IsPGPMIMEVersionInfoFormat)
  2250                     {
  2251                         /* Replace all "LF" by "CRLF" (see note above)
  2252                          * 10 is the decimal value for "LF", 13 for "CR"
  2253                          * See https://en.wikipedia.org/wiki/Newline#Representation
  2254                          */
  2255                         List<byte> versionInfo = new List<byte>();
  2256                         for (int i = 0; i < attach.Data.Length; i++)
  2257                         {
  2258                             if (attach.Data[i] == 10)
  2259                             {
  2260                                 versionInfo.Add(13);
  2261                             }
  2262 
  2263                             versionInfo.Add(attach.Data[i]);
  2264                         }
  2265 
  2266                         data.AddRange(versionInfo);
  2267                         versionInfoFound = true;
  2268                         break;
  2269                     }
  2270                 }
  2271 
  2272                 // Add the content attachment
  2273                 bytes = Encoding.UTF8.GetBytes("\r\n\r\n--" + boundary +
  2274                                                "\r\nContent-Type: application/octet-stream;\r\n" +
  2275                                                "\tname=\"msg.asc\"\r\n\r\n");
  2276                 data.AddRange(bytes);
  2277 
  2278                 foreach (PEPAttachment attach in this._Attachments)
  2279                 {
  2280                     if (attach.IsPGPMIMEContentFormat)
  2281                     {
  2282                         /* Replace all "LF" by "CRLF" (see note above)
  2283                          * 10 is the decimal value for "LF", 13 for "CR"
  2284                          * See https://en.wikipedia.org/wiki/Newline#Representation
  2285                          */
  2286                         List<byte> mimeContent = new List<byte>();
  2287                         for (int i = 0; i < attach.Data.Length; i++)
  2288                         {
  2289                             if (attach.Data[i] == 10)
  2290                             {
  2291                                 mimeContent.Add(13);
  2292                             }
  2293 
  2294                             mimeContent.Add(attach.Data[i]);
  2295                         }
  2296 
  2297                         data.AddRange(mimeContent);
  2298                         contentFound = true;
  2299                         break;
  2300                     }
  2301                 }
  2302 
  2303                 bytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
  2304                 data.AddRange(bytes);
  2305 
  2306                 // Create the new attachment
  2307                 if (versionInfoFound && contentFound)
  2308                 {
  2309                     newAttachment = new PEPAttachment()
  2310                     {
  2311                         Data = data.ToArray(),
  2312                         MimeType = "multipart/signed",
  2313                         Tag = MapiPropertyValue.PidTagAttachTagMIME
  2314                     };
  2315                 }
  2316             }
  2317 
  2318             return (newAttachment);
  2319         }
  2320 
  2321         /// <summary>
  2322         /// Recursivley converts all "Bcc", "Cc", and "To" identities into a 'flat' list of any members.
  2323         /// This will remove groups (hierarchy) and convert a group into it's members.
  2324         /// </summary>
  2325         public void FlattenAllRecipientIdentities()
  2326         {
  2327             this._Bcc = PEPIdentity.ToFlatList(this._Bcc);
  2328             this._Cc = PEPIdentity.ToFlatList(this._Cc);
  2329             this._To = PEPIdentity.ToFlatList(this._To);
  2330 
  2331             return;
  2332         }
  2333 
  2334         /// <summary>
  2335         /// Add a disclaimer to the message if needed.
  2336         /// Note: This should only be applied to outgoing messages. As the method doesn't 
  2337         /// save the mail item after adding a disclaimer, it's the caller's responsibility
  2338         /// to save if necessary.
  2339         /// </summary>
  2340         public void AddDisclaimer()
  2341         {
  2342             // We only add disclaimers to outgoing messages
  2343             if (this.Direction == pEpMsgDirection.pEpDirOutgoing)
  2344             {
  2345                 try
  2346                 {
  2347                     // Get settings for From account
  2348                     var accountSettings = PEPSettings.GetAccountSettings(this.From?.Address);
  2349 
  2350                     /* Add disclaimer if needed:
  2351                      *      - if set to add to all messages
  2352                      *      - if set to add to encrypted messages and outgoing rating is
  2353                      *        at least reliable.
  2354                      */
  2355                     if ((accountSettings?.AddDisclaimer == PEPSettings.Disclaimer.AllMessages) ||
  2356                         ((accountSettings?.AddDisclaimer == PEPSettings.Disclaimer.OnlyEncryptedMessages) && this.GetOutgoingRating() >= pEpRating.pEpRatingReliable))
  2357                     {
  2358                         // Add to plain text
  2359                         if (string.IsNullOrEmpty(this._LongMsg) == false)
  2360                         {
  2361                             this._LongMsg += "\n\n" + accountSettings.DisclaimerText;
  2362                         }
  2363 
  2364                         // Add to html
  2365                         if (string.IsNullOrEmpty(this._LongMsgFormattedHtml) == false)
  2366                         {
  2367                             this._LongMsgFormattedHtml += "<br><br><p>" +
  2368                                                           accountSettings.DisclaimerText +
  2369                                                           "</p>";
  2370                         }
  2371 
  2372                         // Add to Rtf
  2373                         if (string.IsNullOrEmpty(this._LongMsgFormattedRtf) == false)
  2374                         {
  2375                             System.Windows.Forms.RichTextBox rtb = new System.Windows.Forms.RichTextBox
  2376                             {
  2377                                 Text = this._LongMsgFormattedRtf
  2378                             };
  2379                             rtb.AppendText("\n\n" + accountSettings.DisclaimerText);
  2380                             this._LongMsgFormattedRtf = rtb.ToString();
  2381                         }
  2382                     }
  2383                 }
  2384                 catch (Exception ex)
  2385                 {
  2386                     Log.Error("AddDisclaimer: Error adding disclaimer to message. " + ex.ToString());
  2387                 }
  2388             }
  2389             else
  2390             {
  2391                 Log.Verbose("AddDisclaimer: Skipped. Message is incoming.");
  2392             }
  2393         }
  2394 
  2395         /**************************************************************
  2396          * 
  2397          * Static Methods
  2398          * 
  2399          *************************************************************/
  2400 
  2401         public static pEpRating CalculateRating(PEPMessage message)
  2402         {
  2403             pEpRating rating = pEpRating.pEpRatingTrustedAndAnonymized;
  2404             List<PEPIdentity> recipients = new List<PEPIdentity>
  2405             {
  2406                 message.From
  2407             };
  2408 
  2409             message.To?.ForEach(identity =>
  2410             {
  2411                 recipients.Add(identity);
  2412             });
  2413 
  2414             message.Cc?.ForEach(identity =>
  2415             {
  2416                 recipients.Add(identity);
  2417             });
  2418 
  2419             message.Bcc?.ForEach(identity =>
  2420             {
  2421                 recipients.Add(identity);
  2422             });
  2423 
  2424             recipients.ForEach(identity =>
  2425             {
  2426                 // Skip own identities
  2427                 if (PEPSettings.GetIsOwnAccount(identity) == false)
  2428                 {
  2429                     try
  2430                     {
  2431                         pEpIdentity _identity = identity.ToCOMType();
  2432                         _identity = ThisAddIn.PEPEngine.UpdateIdentity(_identity);
  2433                         pEpRating identityRating = ThisAddIn.PEPEngine.RatingFromCommType(_identity.CommType);
  2434 
  2435                         if (identityRating < rating)
  2436                         {
  2437                             rating = identityRating;
  2438                         }
  2439                     }
  2440                     catch (Exception ex)
  2441                     {
  2442                         Log.Error("CalculateRating: Error updating identity. " + ex.ToString());
  2443                     }
  2444                 }
  2445             });
  2446 
  2447             return rating;
  2448         }
  2449 
  2450         /// <summary>
  2451         /// Constructs a new message from the given pEp engine TextMessage.
  2452         /// The output will never be null.
  2453         /// </summary>
  2454         /// <param name="msg">The TextMessage to construct from.</param>
  2455         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  2456         /// <returns>The status of the method.</returns>
  2457         /// <remarks>In some contexts which call this message, a partial conversion of the
  2458         /// <see cref="TextMessage"/> is acceptable, non-critical, and better than failure (e. G. displaying
  2459         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  2460         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  2461         /// in the more critical contexts. Callers of that message need to be aware of this
  2462         /// and check the result if appropriate.</remarks>
  2463         public static Globals.ReturnStatus Create(TextMessage msg,
  2464                                                   out PEPMessage createdMessage)
  2465         {
  2466             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  2467             PEPMessage newMessage = new PEPMessage();
  2468 
  2469             try
  2470             {
  2471                 /* Note: Skip the following properties which are not supported in the TextMessage
  2472                  *   • ConversationID
  2473                  *   • ConversationIndex
  2474                  *   • ConversationTopic
  2475                  *   • EnableProtection
  2476                  *   • ForceUnencrypted
  2477                  *   • LongMsgFormattedRtf
  2478                  * 
  2479                  * Also note the following are handled as optional fields
  2480                  *   • AutoConsume
  2481                  *   • ForceProtection
  2482                  *   • KeyList
  2483                  *   • NeverUnsecure
  2484                  *   • PEPProtocolVersion
  2485                  *   • Rating
  2486                  * 
  2487                  * This also skips a number of TextMessage properties currently unsupported in PEPMessage.
  2488                  */
  2489 
  2490                 // Attachments
  2491                 newMessage.Attachments.Clear();
  2492                 if (msg.Attachments != null)
  2493                 {
  2494                     for (int i = 0; i < msg.Attachments.Length; i++)
  2495                     {
  2496                         newMessage.Attachments.Add(new PEPAttachment((Blob)msg.Attachments.GetValue(i)));
  2497                     }
  2498                 }
  2499 
  2500                 // Bcc
  2501                 newMessage.Bcc.Clear();
  2502                 if (msg.Bcc != null)
  2503                 {
  2504                     for (int i = 0; i < msg.Bcc.Length; i++)
  2505                     {
  2506                         newMessage.Bcc.Add(new PEPIdentity((pEpIdentity)msg.Bcc.GetValue(i)));
  2507                     }
  2508                 }
  2509 
  2510                 // Cc
  2511                 newMessage.Cc.Clear();
  2512                 if (msg.Cc != null)
  2513                 {
  2514                     for (int i = 0; i < msg.Cc.Length; i++)
  2515                     {
  2516                         newMessage.Cc.Add(new PEPIdentity((pEpIdentity)msg.Cc.GetValue(i)));
  2517                     }
  2518                 }
  2519 
  2520                 newMessage.Direction = msg.Dir;
  2521                 newMessage.From = new PEPIdentity(msg.From);
  2522                 newMessage.Id = msg.Id;
  2523 
  2524                 // Keywords
  2525                 newMessage.Keywords.Clear();
  2526                 if (msg.Keywords != null)
  2527                 {
  2528                     for (int i = 0; i < msg.Keywords.Length; i++)
  2529                     {
  2530                         newMessage.Keywords.Add((string)msg.Keywords.GetValue(i));
  2531                     }
  2532                 }
  2533 
  2534                 newMessage.LongMsg = msg.LongMsg;
  2535                 newMessage.LongMsgFormattedHtml = msg.LongMsgFormatted;
  2536 
  2537                 // Optional properties
  2538                 if (msg.OptFields != null)
  2539                 {
  2540                     foreach (StringPair optField in msg.OptFields)
  2541                     {
  2542                         if (optField.Name != null)
  2543                         {
  2544                             switch (optField.Name)
  2545                             {
  2546                                 case PEPMessage.PR_PEP_AUTO_CONSUME_NAME:
  2547                                 case PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD:
  2548                                     {
  2549                                         newMessage.AutoConsume = optField.Value;
  2550                                         break;
  2551                                     }
  2552                                 case PEPMessage.PR_PEP_FORCE_PROTECTION_NAME:
  2553                                     {
  2554                                         newMessage.ForceProtectionId = optField.Value;
  2555                                         break;
  2556                                     }
  2557                                 case PEPMessage.PR_PEP_KEY_IMPORT_NAME:
  2558                                 case PEPMessage.PR_PEP_KEY_IMPORT_NAME_OLD:
  2559                                     {
  2560                                         newMessage.KeyImport = optField.Value;
  2561                                         break;
  2562                                     }
  2563                                 case PEPMessage.PR_KEY_LIST_NAME:
  2564                                     {
  2565                                         newMessage.KeyList = optField.Value;
  2566                                         break;
  2567                                     }
  2568                                 case PEPMessage.PR_PEP_NEVER_UNSECURE_NAME:
  2569                                     {
  2570                                         // If it exists it's true, value doesn't matter
  2571                                         newMessage.NeverUnsecure = true;
  2572                                         break;
  2573                                     }
  2574                                 case PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME:
  2575                                     {
  2576                                         newMessage.PEPProtocolVersion = optField.Value;
  2577                                         break;
  2578                                     }
  2579                                 case PEPMessage.PR_ENC_STATUS_NAME:
  2580                                     {
  2581                                         try
  2582                                         {
  2583                                             newMessage.Rating = AdapterExtensions.ParseRatingString(optField.Value);
  2584                                         }
  2585                                         catch
  2586                                         {
  2587                                             newMessage.Rating = pEpRating.pEpRatingUndefined;
  2588                                         }
  2589                                         break;
  2590                                     }
  2591                             }
  2592                         }
  2593                     }
  2594                 }
  2595 
  2596                 // ReceivedOn
  2597                 if (msg.Recv > 0)
  2598                 {
  2599                     DateTime receivedOnUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(msg.Recv);
  2600                     try
  2601                     {
  2602                         newMessage.ReceivedOn = TimeZoneInfo.ConvertTimeFromUtc(receivedOnUtc, TimeZoneInfo.Local);
  2603                     }
  2604                     catch (Exception ex)
  2605                     {
  2606                         Log.Error("PEPMessage.Create: Error converting received time to local time. " + ex.ToString());
  2607                         newMessage.ReceivedOn = new DateTime(1970, 1, 1).AddSeconds(msg.Recv);
  2608                     }
  2609                 }
  2610                 else
  2611                 {
  2612                     newMessage.ReceivedOn = null;
  2613                 }
  2614 
  2615                 // ReplyTo
  2616                 newMessage.ReplyTo.Clear();
  2617                 if (msg.ReplyTo != null)
  2618                 {
  2619                     for (int i = 0; i < msg.ReplyTo.Length; i++)
  2620                     {
  2621                         newMessage.ReplyTo.Add(new PEPIdentity((pEpIdentity)msg.ReplyTo.GetValue(i)));
  2622                     }
  2623                 }
  2624 
  2625                 // SentOn
  2626                 if (msg.Sent > 0)
  2627                 {
  2628                     DateTime sentOnUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(msg.Sent);
  2629                     try
  2630                     {
  2631                         newMessage.SentOn = TimeZoneInfo.ConvertTimeFromUtc(sentOnUtc, TimeZoneInfo.Local);
  2632                     }
  2633                     catch (Exception ex)
  2634                     {
  2635                         Log.Error("PEPMessage.Create: Error converting sent time to local time. " + ex.ToString());
  2636                         newMessage.SentOn = new DateTime(1970, 1, 1).AddSeconds(msg.Sent);
  2637                     }
  2638                 }
  2639                 else
  2640                 {
  2641                     newMessage.SentOn = null;
  2642                 }
  2643 
  2644                 newMessage.ShortMsg = msg.ShortMsg;
  2645 
  2646                 // To
  2647                 newMessage.To.Clear();
  2648                 if (msg.To != null)
  2649                 {
  2650                     for (int i = 0; i < msg.To.Length; i++)
  2651                     {
  2652                         newMessage.To.Add(new PEPIdentity((pEpIdentity)msg.To.GetValue(i)));
  2653                     }
  2654                 }
  2655             }
  2656             catch (Exception ex)
  2657             {
  2658                 status = Globals.ReturnStatus.Failure;
  2659                 Log.Error("PEPMessage.Create: Failure occured, " + ex.ToString());
  2660             }
  2661 
  2662             createdMessage = newMessage;
  2663             return (status);
  2664         }
  2665 
  2666         /// <summary>
  2667         /// Contructs a new message from the given outlook mail item.
  2668         /// The output will never be null.
  2669         /// </summary>
  2670         /// <param name="omi">The outlook mail item to create the message from.</param>
  2671         /// <param name="createdMessage">The output newly created message (will never be null).</param>
  2672         /// <param name="onlyRecipientsAndDirection">Whether to only set recipients and direction. Only set to
  2673         /// true in special cases, e.g. if the message is only needed to calculate the outgoing rating.</param>
  2674         /// <param name="createContacts">Whether or not to create contacts from the mail items's recipients during
  2675         /// processing of the message. Only set to false in special cases like for outgoing rating.</param>
  2676         /// <returns>The status of the method.</returns>
  2677         /// <remarks>In some contexts which call this message, a partial conversion of the
  2678         /// <see cref="TextMessage"/> is acceptable, non-critical, and better than failure (e. G. displaying
  2679         /// a preview to the user). Thus, this method will always create a <see cref="PEPMessage"/>
  2680         /// which is "best effort", and return the <see cref="Globals.ReturnStatus"/> which can be evaluated
  2681         /// in the more critical contexts. Callers of that message need to be aware of this
  2682         /// and check the result if appropriate.</remarks>
  2683         public static Globals.ReturnStatus Create(Outlook.MailItem omi,
  2684                                                   out PEPMessage createdMessage,
  2685                                                   bool onlyRecipientsAndDirection = false,
  2686                                                   bool createContacts = true)
  2687         {
  2688             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
  2689             Globals.ReturnStatus sts = Globals.ReturnStatus.Failure;
  2690             PEPMessage newMessage = new PEPMessage();
  2691             Outlook.Recipient recipient = null;
  2692             Outlook.Recipients recipients = null;
  2693             Outlook.Attachment attachment = null;
  2694             Outlook.Attachments attachments = null;
  2695 
  2696             try
  2697             {
  2698                 Log.Verbose("PEPMessage.Create: Creating new PEPMessage from OMI started.");
  2699 
  2700                 // Direction
  2701                 newMessage.Direction = omi.GetIsIncoming() ? pEpMsgDirection.pEpDirIncoming : pEpMsgDirection.pEpDirOutgoing;
  2702 
  2703                 // Calculate recipients
  2704                 newMessage.Bcc.Clear();
  2705                 newMessage.Cc.Clear();
  2706                 newMessage.To.Clear();
  2707                 recipients = omi.Recipients;
  2708                 for (int i = 1; i <= recipients.Count; i++)
  2709                 {
  2710                     recipient = recipients[i];
  2711 
  2712                     switch ((Outlook.OlMailRecipientType)recipient.Type)
  2713                     {
  2714                         case Outlook.OlMailRecipientType.olBCC:
  2715                             {
  2716                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident);
  2717                                 if (sts == Globals.ReturnStatus.Success)
  2718                                 {
  2719                                     newMessage.Bcc.Add(ident);
  2720                                 }
  2721                                 else
  2722                                 {
  2723                                     // Update the status, only the first failure type is recorded
  2724                                     if (status == Globals.ReturnStatus.Success)
  2725                                     {
  2726                                         status = sts;
  2727                                     }
  2728 
  2729                                     Log.Error("PEPMessage.Create: Failure creating 'Bcc' identity.");
  2730                                 }
  2731 
  2732                                 break;
  2733                             }
  2734                         case Outlook.OlMailRecipientType.olCC:
  2735                             {
  2736                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident, (createContacts && (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)));
  2737                                 if (sts == Globals.ReturnStatus.Success)
  2738                                 {
  2739                                     newMessage.Cc.Add(ident);
  2740                                 }
  2741                                 else
  2742                                 {
  2743                                     // Update the status, only the first failure type is recorded
  2744                                     if (status == Globals.ReturnStatus.Success)
  2745                                     {
  2746                                         status = sts;
  2747                                     }
  2748 
  2749                                     Log.Error("PEPMessage.Create: Failure creating 'Cc' identity.");
  2750                                 }
  2751 
  2752                                 break;
  2753                             }
  2754                         case Outlook.OlMailRecipientType.olTo:
  2755                             {
  2756                                 sts = PEPIdentity.Create(recipient, out PEPIdentity ident, (createContacts && (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)));
  2757                                 if (sts == Globals.ReturnStatus.Success)
  2758                                 {
  2759                                     newMessage.To.Add(ident);
  2760                                 }
  2761                                 else
  2762                                 {
  2763                                     // Update the status, only the first failure type is recorded
  2764                                     if (status == Globals.ReturnStatus.Success)
  2765                                     {
  2766                                         status = sts;
  2767                                     }
  2768 
  2769                                     Log.Error("PEPMessage.Create: Failure creating 'To' identity.");
  2770                                 }
  2771 
  2772                                 break;
  2773                             }
  2774                     }
  2775 
  2776                     recipient = null;
  2777                 }
  2778 
  2779                 recipients = omi.ReplyRecipients;
  2780                 for (int i = 1; i <= recipients.Count; i++)
  2781                 {
  2782                     recipient = recipients[i];
  2783                     sts = PEPIdentity.Create(recipient, out PEPIdentity ident);
  2784                     if (sts == Globals.ReturnStatus.Success)
  2785                     {
  2786                         newMessage.ReplyTo.Add(ident);
  2787                     }
  2788                     else
  2789                     {
  2790                         // Update the status, only the first failure type is recorded
  2791                         if (status == Globals.ReturnStatus.Success)
  2792                         {
  2793                             status = sts;
  2794                         }
  2795 
  2796                         Log.Error("PEPMessage.Create: Failure creating 'ReplyTo' identity.");
  2797                     }
  2798                 }
  2799 
  2800                 Log.Verbose("PEPMessage.Create: Recipients calculated, calculating main properties.");
  2801 
  2802                 if (onlyRecipientsAndDirection == false)
  2803                 {
  2804                     // From
  2805                     sts = PEPIdentity.GetFromIdentity(omi, out PEPIdentity ident);
  2806                     if (sts == Globals.ReturnStatus.Success)
  2807                     {
  2808                         newMessage.From = ident;
  2809                     }
  2810                     else
  2811                     {
  2812                         // Update the status, only the first failure type is recorded
  2813                         if (status == Globals.ReturnStatus.Success)
  2814                         {
  2815                             status = sts;
  2816                         }
  2817 
  2818                         Log.Error("PEPMessage.Create: Failure creating 'From' identity.");
  2819                     }
  2820                     Log.Verbose("PEPMessage.Create: From identity calculated.");
  2821 
  2822                     // Get all mail item properties
  2823                     MAPIProperties mapiProperties = omi.GetCommonMessageProperties();
  2824 
  2825                     /* Set retrieved values in newly created PEPMessage.
  2826                      * If a value couldn't be retrieved correctly, use properties
  2827                      * of the Outlook Mail Item as fallback (if possible).
  2828                      */
  2829                     object value = null;
  2830 
  2831                     // Id
  2832                     if (mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Id), out value) == true)
  2833                     {
  2834                         newMessage.Id = value as string;
  2835                     }
  2836 
  2837                     // Subject
  2838                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ShortMsg), out value) == true) &&
  2839                         (value is string shortMsg))
  2840                     {
  2841                         newMessage.ShortMsg = shortMsg;
  2842                     }
  2843                     else
  2844                     {
  2845                         newMessage.ShortMsg = omi.Subject;
  2846                     }
  2847 
  2848                     /* Date & Times
  2849                      * 
  2850                      * Note: The mail item date can be invalid:
  2851                      * For incoming this commonly occurs when creating a mirror -- it's created without a received time.
  2852                      * For outgoing this commonly occurs for unsent mail.
  2853                      * In these cases, the invalid date and time will be returned as 1/1/4501 at 12:00AM
  2854                      * This situation is filtered here and the date is set as null.
  2855                      */
  2856 
  2857                     // ReceivedOn
  2858                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ReceivedOn), out value) == true) &&
  2859                         (value is DateTime receivedOn) &&
  2860                         (receivedOn.Year < 4000))
  2861                     {
  2862                         newMessage.ReceivedOn = receivedOn.ToLocalTime();
  2863                     }
  2864                     else
  2865                     {
  2866                         DateTime? receivedTime = omi.ReceivedTime;
  2867 
  2868                         if (receivedTime?.Year > 4000)
  2869                         {
  2870                             receivedTime = null;
  2871                         }
  2872 
  2873                         newMessage.ReceivedOn = receivedTime;
  2874                     }
  2875 
  2876                     // SentOn
  2877                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.SentOn), out value) == true) &&
  2878                         (value is DateTime sentOn) &&
  2879                         (sentOn.Year < 4000))
  2880                     {
  2881                         newMessage.SentOn = sentOn.ToLocalTime();
  2882                     }
  2883                     else
  2884                     {
  2885                         DateTime? sentTime = omi.SentOn;
  2886 
  2887                         if (sentTime?.Year > 4000)
  2888                         {
  2889                             sentTime = null;
  2890                         }
  2891 
  2892                         newMessage.SentOn = sentTime;
  2893                     }
  2894 
  2895                     // Conversation id
  2896                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationId), out value) == true) &&
  2897                         (value is byte[] conversationId))
  2898                     {
  2899                         newMessage.ConversationId = MapiHelper.PtypBinaryToString(conversationId);
  2900                     }
  2901                     else
  2902                     {
  2903                         newMessage.ConversationId = omi.ConversationID;
  2904                     }
  2905 
  2906                     // Conversation index
  2907                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationIndex), out value) == true) &&
  2908                         (value is byte[] conversationIndex))
  2909                     {
  2910                         newMessage.ConversationIndex = MapiHelper.PtypBinaryToString(conversationIndex);
  2911                     }
  2912                     else
  2913                     {
  2914                         newMessage.ConversationIndex = omi.ConversationIndex;
  2915                     }
  2916 
  2917                     // Conversation topic
  2918                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ConversationTopic), out value) == true) &&
  2919                         (value is string conversationTopic))
  2920                     {
  2921                         newMessage.ConversationTopic = conversationTopic;
  2922                     }
  2923                     else
  2924                     {
  2925                         newMessage.ConversationTopic = omi.ConversationTopic;
  2926                     }
  2927 
  2928                     // Keywords
  2929                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Keywords), out value) == true) &&
  2930                         (value is string keywords))
  2931                     {
  2932                         string[] keywordsArray = keywords.Split(Globals.ListDelimiter);
  2933 
  2934                         if (keywordsArray != null)
  2935                         {
  2936                             foreach (var keyword in keywordsArray)
  2937                             {
  2938                                 newMessage.Keywords.Add(keyword);
  2939                             }
  2940                         }
  2941                     }
  2942 
  2943                     // Get transport headers
  2944                     HeaderList transportHeaders = null;
  2945                     if ((mapiProperties?.TryGetValue(MapiProperty.PidTagTransportMessageHeaders, out value) == true) &&
  2946                         (value is string messageHeaders) &&
  2947                         (string.IsNullOrEmpty(messageHeaders) == false))
  2948                     {
  2949                         // Parse the headers using MimeKit
  2950                         MemoryStream stream = null;
  2951                         try
  2952                         {
  2953                             stream = new MemoryStream(Encoding.UTF8.GetBytes(messageHeaders));
  2954                             MimeParser mimeParser = new MimeParser(stream);
  2955                             transportHeaders = mimeParser.ParseHeaders();
  2956                         }
  2957                         catch (Exception ex)
  2958                         {
  2959                             transportHeaders = null;
  2960                             Log.Error("GetParsedTransportMessageHeaders: Failed to parse MIME headers, " + ex.ToString());
  2961                         }
  2962                         finally
  2963                         {
  2964                             if (stream != null)
  2965                             {
  2966                                 stream.Close();
  2967                                 stream = null;
  2968                             }
  2969                         }
  2970                     }
  2971 
  2972                     // pEp protocol version
  2973                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.PEPProtocolVersion), out value) == true) &&
  2974                         (value is string pEpProtocolVersion))
  2975                     {
  2976                         newMessage.PEPProtocolVersion = pEpProtocolVersion;
  2977                     }
  2978                     else
  2979                     {
  2980                         newMessage.PEPProtocolVersion = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME) as string;
  2981                     }
  2982 
  2983                     // AutoConsume
  2984                     if (((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.AutoConsume), out value) == true) ||
  2985                          (mapiProperties?.TryGetValue(PEPMessage.PidNamePEPAutoConsumeOld, out value) == true)) &&
  2986                          (value is string autoConsume))
  2987                     {
  2988                         newMessage.AutoConsume = autoConsume;
  2989                     }
  2990                     else
  2991                     {
  2992                         newMessage.AutoConsume = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME) as string ??
  2993                                                  PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME) as string;
  2994 
  2995                         // Check backup solution with ReplyTo tunnel if needed
  2996                         if (string.IsNullOrEmpty(newMessage.AutoConsume) &&
  2997                             (newMessage.ReplyTo?.Count > 0))
  2998                         {
  2999                             for (int i = 0; i < newMessage.ReplyTo.Count; i++)
  3000                             {
  3001                                 string address = newMessage.ReplyTo[i].Address?.Trim();
  3002 
  3003                                 if ((address?.EndsWith(PEPMessage.PEPTUNNEL_MAIL_ADDRESS) == true) &&
  3004                                     (address.Split('@')?.GetValue(0) is string tunneledValue) &&
  3005                                     (tunneledValue.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase)))
  3006                                 {
  3007                                     newMessage.AutoConsume = tunneledValue;
  3008                                     break;
  3009                                 }
  3010                             }
  3011                         }
  3012                     }
  3013 
  3014                     // Rating
  3015                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.Rating), out value) == true) &&
  3016                         (value is string rating))
  3017                     {
  3018                         newMessage.Rating = AdapterExtensions.ParseRatingString(rating);
  3019                     }
  3020                     else
  3021                     {
  3022                         newMessage.Rating = AdapterExtensions.ParseRatingString(PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_ENC_STATUS_NAME) as string);
  3023                     }
  3024 
  3025                     // Key import
  3026                     if (((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.KeyImport), out value) == true) ||
  3027                          (mapiProperties?.TryGetValue(PEPMessage.PidNamePEPKeyImportOld, out value) == true)) &&
  3028                         (value is string keyImport))
  3029                     {
  3030                         newMessage.KeyImport = keyImport;
  3031                     }
  3032                     else
  3033                     {
  3034                         newMessage.KeyImport = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_KEY_IMPORT_NAME) as string ??
  3035                                                PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD) as string;
  3036 
  3037                         // Check backup solution with ReplyTo tunnel if needed
  3038                         if (string.IsNullOrEmpty(newMessage.KeyImport) &&
  3039                             (newMessage.ReplyTo?.Count > 0))
  3040                         {
  3041                             for (int i = 0; i < newMessage.ReplyTo.Count; i++)
  3042                             {
  3043                                 string address = newMessage.ReplyTo[i].Address?.Trim();
  3044 
  3045                                 // At the moment, there are only two possible tunneled values: autoconsume and the fpr
  3046                                 if ((address?.EndsWith(PEPMessage.PEPTUNNEL_MAIL_ADDRESS) == true) &&
  3047                                     (address.Split('@')?.GetValue(0) is string tunneledValue) &&
  3048                                     (tunneledValue.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase) == false))
  3049                                 {
  3050                                     newMessage.KeyImport = tunneledValue;
  3051                                     break;
  3052                                 }
  3053                             }
  3054                         }
  3055                     }
  3056 
  3057                     // Key list
  3058                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.KeyList), out value) == true) &&
  3059                         (value is string keyList))
  3060                     {
  3061                         newMessage.KeyList = keyList;
  3062                     }
  3063                     else
  3064                     {
  3065                         newMessage.KeyList = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_KEY_LIST_NAME) as string;
  3066                     }
  3067 
  3068                     // Force protection
  3069                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.ForceProtectionId), out value) == true) &&
  3070                         (value is string forceProtectionId))
  3071                     {
  3072                         newMessage.ForceProtectionId = forceProtectionId;
  3073                     }
  3074                     else
  3075                     {
  3076                         newMessage.ForceProtectionId = PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_FORCE_PROTECTION_NAME) as string;
  3077                     }
  3078 
  3079                     // Never unsecure
  3080                     if ((mapiProperties?.TryGetPEPMessagePropertyValue(nameof(PEPMessage.NeverUnsecure), out value) == true) &&
  3081                         (value is bool neverUnsecure))
  3082                     {
  3083                         newMessage.NeverUnsecure = neverUnsecure;
  3084                     }
  3085                     else
  3086                     {
  3087                         newMessage.NeverUnsecure = (PEPMessage.GetPropertyFromHeaderList(transportHeaders, PEPMessage.PR_PEP_NEVER_UNSECURE_NAME) != null);
  3088                     }
  3089 
  3090                     // User properties for outgoing messages
  3091                     if (newMessage.Direction == pEpMsgDirection.pEpDirOutgoing)
  3092                     {
  3093                         // Enable protection
  3094                         if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.EnableProtection, out value) &&
  3095                             (value is bool enableProtection))
  3096                         {
  3097                             newMessage.EnableProtection = enableProtection;
  3098                         }
  3099 
  3100                         // Force unencrypted
  3101                         if (omi.GetPEPProperty(MailItemExtensions.PEPProperty.ForceUnencrypted, out value) &&
  3102                             (value is bool forceUnencrypted))
  3103                         {
  3104                             newMessage.ForceUnencrypted = forceUnencrypted;
  3105                         }
  3106                     }
  3107 
  3108                     // Add additional headers
  3109                     string autocryptHeaderKey = "Autocrypt";
  3110                     try
  3111                     {
  3112                         Header ac = transportHeaders?.FirstOrDefault(a => (a.Field?.Trim()?.Equals(autocryptHeaderKey, StringComparison.OrdinalIgnoreCase) == true));
  3113                         if (ac != null)
  3114                         {
  3115                             StringPair autocryptHeader = new StringPair
  3116                             {
  3117                                 Name = ac.Field.Trim(),
  3118                                 Value = ac.Value.Trim()
  3119                             };
  3120                             newMessage.AdditionalHeaders.Add(autocryptHeader);
  3121                         }
  3122                     }
  3123                     catch (Exception ex)
  3124                     {
  3125                         Log.Error("PEPMessage.Create: Error adding additional headers. " + ex.ToString());
  3126                     }
  3127 
  3128                     // Calculate text body and attachments
  3129                     // Body
  3130                     if (omi.Body != null)
  3131                     {
  3132                         newMessage.LongMsg = omi.Body.Replace("\r\n", "\n");
  3133 
  3134                         // Save as RTF
  3135                         try
  3136                         {
  3137                             byte[] rtfBody = omi.RTFBody;
  3138 
  3139                             if ((rtfBody != null) &&
  3140                                 (rtfBody.Length > 0))
  3141                             {
  3142                                 newMessage.LongMsgFormattedRtf = Encoding.ASCII.GetString(rtfBody, 0, rtfBody.Length);
  3143                             }
  3144                         }
  3145                         catch
  3146                         {
  3147                             Log.Warning("PEPMessage.Create: Unable to read RTF body in MailItem.");
  3148                         }
  3149 
  3150                         // Force any rich text into HTML
  3151                         // WARNING: This is technically a modifcation of the original MailItem
  3152                         // It should be further investigated if this can be removed in the future.
  3153                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatRichText)
  3154                         {
  3155                             omi.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
  3156                         }
  3157 
  3158                         if (omi.BodyFormat == Outlook.OlBodyFormat.olFormatHTML)
  3159                         {
  3160                             newMessage.LongMsgFormattedHtml = omi.HTMLBody;
  3161                         }
  3162                     }
  3163 
  3164                     Log.Verbose("PEPMessage.Create: Body calculated.");
  3165 
  3166                     // Attachments
  3167                     newMessage.Attachments.Clear();
  3168                     attachments = omi.Attachments;
  3169                     for (int i = 1; i <= attachments.Count; i++)
  3170                     {
  3171                         attachment = attachments[i];
  3172 
  3173                         try
  3174                         {
  3175                             var newAttachment = new PEPAttachment(attachment, newMessage.LongMsgFormattedHtml ?? newMessage.LongMsgFormattedRtf ?? newMessage.LongMsg);
  3176                             if (newAttachment?.Data != null)
  3177                             {
  3178                                 newMessage.Attachments.Add(newAttachment);
  3179                             }
  3180                         }
  3181                         catch { }
  3182 
  3183                         attachment = null;
  3184                     }
  3185                 }
  3186 
  3187                 status = Globals.ReturnStatus.Success;
  3188                 Log.Verbose("PEPMessage.Create: New PEPMessage created from OMI.");
  3189             }
  3190             catch (Exception ex)
  3191             {
  3192                 status = Globals.ReturnStatus.Failure;
  3193                 Log.Error("PEPMessage.Create: failure occured, " + ex.ToString());
  3194             }
  3195             finally
  3196             {
  3197                 attachment = null;
  3198                 attachments = null;
  3199                 recipient = null;
  3200                 recipients = null;
  3201             }
  3202 
  3203             createdMessage = newMessage;
  3204             Log.Verbose("PEPMessage.Create: ReturnStatus=" + status.ToString());
  3205 
  3206             return status;
  3207         }
  3208 
  3209         /// <summary>
  3210         /// Creates a PEPMessage from a MimeMessage.
  3211         /// </summary>
  3212         /// <param name="mimeMessage">The MimeMessage to create a PEPMessage from.</param>
  3213         /// <param name="createdMessage">The created PEPMessage.</param>
  3214         /// <returns>The creation status.</returns>
  3215         public static Globals.ReturnStatus Create(MimeMessage mimeMessage,
  3216                                                   out PEPMessage createdMessage)
  3217         {
  3218             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  3219             PEPMessage pEpMessage = new PEPMessage();
  3220 
  3221             try
  3222             {
  3223                 // Attachments
  3224                 if (mimeMessage.Attachments != null)
  3225                 {
  3226                     foreach (var attachment in mimeMessage.Attachments)
  3227                     {
  3228                         // Cast attachment to MimePart and check if casting was successful
  3229                         if (attachment is MimePart currentAttachment)
  3230                         {
  3231                             PEPAttachment pEpAttachment = new PEPAttachment
  3232                             {
  3233                                 // Assign content type
  3234                                 MimeType = currentAttachment.ContentType?.MimeType,
  3235                                 FileName = currentAttachment.FileName
  3236                             };
  3237 
  3238                             // Decode content object and copy to data property
  3239                             using (var memStream = new MemoryStream())
  3240                             {
  3241                                 currentAttachment.Content?.DecodeTo(memStream);
  3242                                 pEpAttachment.Data = memStream.ToArray();
  3243                             }
  3244 
  3245                             // Add attachment to PEPMessage.Attachments collection
  3246                             pEpMessage.Attachments.Add(pEpAttachment);
  3247                         }
  3248                         else
  3249                         {
  3250                             Log.Error("PEPMessage.Create: Error casting attachment.");
  3251                         }
  3252                     }
  3253                 }
  3254 
  3255                 // Check for inline content
  3256                 foreach (var bodyPart in mimeMessage.BodyParts)
  3257                 {
  3258                     // If inline content is found, add it as attachment
  3259                     if (bodyPart.ContentDisposition?.Disposition == ContentDisposition.Inline)
  3260                     {
  3261                         var pEpAttachment = new PEPAttachment
  3262                         {
  3263                             ContentId = bodyPart.ContentId,
  3264                             MimeType = bodyPart.ContentType?.MimeType
  3265                         };
  3266 
  3267                         // Decode content object and copy to data property
  3268                         using (var memStream = new MemoryStream())
  3269                         {
  3270                             (bodyPart as MimePart)?.Content?.DecodeTo(memStream);
  3271                             pEpAttachment.Data = memStream.ToArray();
  3272                         }
  3273 
  3274                         // Add inline content as attachment
  3275                         pEpMessage.Attachments.Add(pEpAttachment);
  3276                     }
  3277                 }
  3278 
  3279                 // Body
  3280                 pEpMessage.LongMsg = mimeMessage.TextBody;
  3281                 pEpMessage.LongMsgFormattedHtml = mimeMessage.HtmlBody;
  3282 
  3283                 // Subject
  3284                 pEpMessage.ShortMsg = mimeMessage.Subject;
  3285 
  3286                 // Process recipients
  3287                 // BCC
  3288                 for (int i = 0; i < mimeMessage.Bcc?.Count; i++)
  3289                 {
  3290                     PEPIdentity ident = new PEPIdentity
  3291                     {
  3292                         Address = (mimeMessage.Bcc[i] as MailboxAddress)?.Address,
  3293                         UserName = (mimeMessage.Bcc[i] as MailboxAddress)?.Name
  3294                     };
  3295 
  3296                     pEpMessage.Bcc.Add(ident);
  3297                 }
  3298 
  3299                 // CC
  3300                 for (int i = 0; i < mimeMessage.Cc?.Count; i++)
  3301                 {
  3302                     PEPIdentity ident = new PEPIdentity
  3303                     {
  3304                         Address = (mimeMessage.Cc[i] as MailboxAddress)?.Address,
  3305                         UserName = (mimeMessage.Cc[i] as MailboxAddress)?.Name
  3306                     };
  3307 
  3308                     pEpMessage.Cc.Add(ident);
  3309                 }
  3310 
  3311                 // To
  3312                 for (int i = 0; i < mimeMessage.To?.Count; i++)
  3313                 {
  3314                     PEPIdentity ident = new PEPIdentity
  3315                     {
  3316                         Address = (mimeMessage.To[i] as MailboxAddress)?.Address,
  3317                         UserName = (mimeMessage.To[i] as MailboxAddress)?.Name
  3318                     };
  3319 
  3320                     pEpMessage.To.Add(ident);
  3321                 }
  3322 
  3323                 // From
  3324                 if (mimeMessage.Sender != null)
  3325                 {
  3326                     PEPIdentity ident = new PEPIdentity
  3327                     {
  3328                         Address = mimeMessage.Sender?.Address,
  3329                         UserName = mimeMessage.Sender?.Name
  3330                     };
  3331 
  3332                     pEpMessage.From = ident;
  3333                 }
  3334                 else if (mimeMessage.From?.Count == 1)
  3335                 {
  3336                     PEPIdentity ident = new PEPIdentity
  3337                     {
  3338                         Address = (mimeMessage.From[0] as MailboxAddress)?.Address,
  3339                         UserName = (mimeMessage.From[0] as MailboxAddress)?.Name
  3340                     };
  3341 
  3342                     pEpMessage.From = ident;
  3343                 }
  3344 
  3345                 if (mimeMessage.Headers != null)
  3346                 {
  3347                     // pEp header fields
  3348                     // Key List
  3349                     Header keyList = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_KEY_LIST_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3350                     if (keyList != null)
  3351                     {
  3352                         pEpMessage.KeyList = keyList.Value?.Trim();
  3353                     }
  3354 
  3355                     // Key Import
  3356                     Header keyImport = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_KEY_IMPORT_NAME, StringComparison.OrdinalIgnoreCase) == true)) ??
  3357                                        mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_KEY_IMPORT_NAME_OLD, StringComparison.OrdinalIgnoreCase) == true));
  3358                     if (keyImport != null)
  3359                     {
  3360                         pEpMessage.KeyImport = keyImport.Value?.Trim();
  3361                     }
  3362 
  3363                     // Auto consume
  3364                     Header autoConsume = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME, StringComparison.OrdinalIgnoreCase) == true)) ??
  3365                                          mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_AUTO_CONSUME_NAME_OLD, StringComparison.OrdinalIgnoreCase) == true));
  3366                     if (autoConsume != null)
  3367                     {
  3368                         pEpMessage.AutoConsume = autoConsume.Value?.Trim();
  3369                     }
  3370 
  3371                     // Force protection
  3372                     Header forceProtection = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_FORCE_PROTECTION_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3373                     if (forceProtection != null)
  3374                     {
  3375                         pEpMessage.ForceProtectionId = forceProtection.Value?.Trim();
  3376                     }
  3377 
  3378                     // Never unsecure
  3379                     Header neverUnsecure = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_NEVER_UNSECURE_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3380                     if (neverUnsecure != null)
  3381                     {
  3382                         pEpMessage.NeverUnsecure = true;
  3383                     }
  3384 
  3385                     // pEp protocol version
  3386                     Header pEpVersion = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(PEPMessage.PR_PEP_PROTOCOL_VERSION_NAME, StringComparison.OrdinalIgnoreCase) == true));
  3387                     if (pEpVersion != null)
  3388                     {
  3389                         pEpMessage.PEPProtocolVersion = pEpVersion.Value?.Trim();
  3390                     }
  3391 
  3392                     // Additional headers
  3393                     string autocryptHeaderKey = "Autocrypt";
  3394                     try
  3395                     {
  3396                         Header ac = mimeMessage.Headers.FirstOrDefault(a => (a.Field?.Trim()?.Equals(autocryptHeaderKey, StringComparison.OrdinalIgnoreCase) == true));
  3397                         if (ac != null)
  3398                         {
  3399                             StringPair autocryptHeader = new StringPair
  3400                             {
  3401                                 Name = ac.Field.Trim(),
  3402                                 Value = ac.Value.Trim()
  3403                             };
  3404                             pEpMessage.AdditionalHeaders.Add(autocryptHeader);
  3405                         }
  3406                     }
  3407                     catch (Exception ex)
  3408                     {
  3409                         Log.Error("PEPMessage.Create: Error adding additional headers. " + ex.ToString());
  3410                     }
  3411                 }
  3412 
  3413                 // Message date
  3414                 if (mimeMessage.Date != null)
  3415                 {
  3416                     pEpMessage.SentOn = mimeMessage.Date.DateTime;
  3417                 }
  3418             }
  3419             catch (Exception ex)
  3420             {
  3421                 status = Globals.ReturnStatus.Failure;
  3422                 Log.Error("Create: Error creating PEPMessage from MimeMessage. " + ex.ToString());
  3423             }
  3424 
  3425             createdMessage = pEpMessage;
  3426 
  3427             return status;
  3428         }
  3429 
  3430         /// <summary>
  3431         /// Creates a PEPMessage from a MIME string.
  3432         /// </summary>
  3433         /// <param name="message">The MIME message as a string.</param>
  3434         /// <param name="createdMessage">The created PEPMessage.</param>
  3435         /// <returns>True if a message could be created, otherwise false.</returns>
  3436         public static bool Create(string message, out PEPMessage createdMessage)
  3437         {
  3438             createdMessage = null;
  3439 
  3440             try
  3441             {
  3442                 TextMessage msg = ThisAddIn.PEPEngine.MIMEDecodeMessage(message);
  3443                 if (PEPMessage.Create(msg, out PEPMessage pEpMsg) == Globals.ReturnStatus.Success)
  3444                 {
  3445                     createdMessage = pEpMsg;
  3446                 }
  3447                 else
  3448                 {
  3449                     Log.Error("Create: Error creating PEPMessage from string.");
  3450                 }
  3451             }
  3452             catch (Exception ex)
  3453             {
  3454                 createdMessage = null;
  3455                 Log.Error("Create: Error creating PEPMessage from string. " + ex.ToString());
  3456             }
  3457 
  3458             return (createdMessage != null);
  3459         }
  3460 
  3461         /// <summary>
  3462         /// Returns the mirror of a given Outlook mail item based on its EntryId or MessageId
  3463         /// and the sender's user name.
  3464         /// </summary>
  3465         /// <param name="id">The entry id of the original encrypted mail item.</param>
  3466         /// <param name="userName">The user name of the original's sender.</param>
  3467         /// <returns>The mirror mail item if successful. Otherwise null.</returns>
  3468         public static Outlook.MailItem GetMirror(string id, string userName)
  3469         {
  3470             string mirrorID;
  3471             string origEntryID;
  3472             Outlook.MailItem mirrorItem = null;
  3473             Outlook.MailItem currMirror = null;
  3474             Outlook.NameSpace ns = Globals.ThisAddIn.Application.Session;
  3475             Outlook.Store pEpStore = Globals.ThisAddIn.PEPStoreRootFolder.Store;
  3476             Outlook.Folder targetFolder = null;
  3477             Outlook.Items targetFolderItems = null;
  3478 
  3479             if (string.IsNullOrEmpty(id) == false)
  3480             {
  3481                 // Try to find in cache
  3482                 try
  3483                 {
  3484                     lock (mutexMirror)
  3485                     {
  3486                         mirrorID = mirrorCache[id];
  3487                     }
  3488 
  3489                     // Note: If messageId was stored, this method returns null
  3490                     mirrorItem = (Outlook.MailItem)ns.GetItemFromID(mirrorID, pEpStore.StoreID);
  3491                 }
  3492                 catch
  3493                 {
  3494                     mirrorItem = null;
  3495                 }
  3496 
  3497                 // Try to find same EntryID
  3498                 if (mirrorItem == null)
  3499                 {
  3500                     try
  3501                     {
  3502                         // Note: If messageId was stored, this method returns null
  3503                         mirrorItem = (Outlook.MailItem)ns.GetItemFromID(id, pEpStore.StoreID);
  3504                     }
  3505                     catch
  3506                     {
  3507                         mirrorItem = null;
  3508                     }
  3509                 }
  3510 
  3511                 // Try to find by user property
  3512                 if (mirrorItem == null)
  3513                 {
  3514                     Outlook.UserDefinedProperties up = null;
  3515 
  3516                     try
  3517                     {
  3518                         targetFolder = PEPMessage.GetMirrorFolder(userName);
  3519                         targetFolderItems = targetFolder.Items;
  3520 
  3521                         /* Add user property to folder if not there already:
  3522                          * "If you are trying to use the Find or Restrict methods with user-defined fields,
  3523                          * the fields must be defined in the folder, otherwise an error will occur."
  3524                          * See: https://msdn.microsoft.com/en-us/library/office/ff869662.aspx
  3525                         */
  3526                         up = targetFolder.UserDefinedProperties as Outlook.UserDefinedProperties;
  3527                         if (up.Find(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID) == null)
  3528                         {
  3529                             up.Add(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, Outlook.OlUserPropertyType.olText);
  3530                         }
  3531 
  3532                         string filter = string.Format("[{0}] = '{1}'", MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, id);
  3533                         mirrorItem = targetFolderItems.Find(filter) as Outlook.MailItem;
  3534 
  3535                         if (mirrorItem != null)
  3536                         {
  3537                             lock (mutexMirror)
  3538                             {
  3539                                 mirrorCache[id] = mirrorItem.EntryID;
  3540                             }
  3541                         }
  3542                     }
  3543                     catch (Exception ex)
  3544                     {
  3545                         mirrorItem = null;
  3546                         Log.Error("GetMirror: Error searching for mirror omi. " + ex.ToString());
  3547                     }
  3548                     finally
  3549                     {
  3550                         up = null;
  3551                     }
  3552                 }
  3553 
  3554                 // Nothing worked, search each item by user property (slow)
  3555                 if (mirrorItem == null)
  3556                 {
  3557                     targetFolder = PEPMessage.GetMirrorFolder(userName);
  3558                     targetFolderItems = targetFolder.Items;
  3559 
  3560                     // Note: index starts at 1
  3561                     for (int i = 1; i <= targetFolderItems.Count; i++)
  3562                     {
  3563                         currMirror = null;
  3564 
  3565                         try
  3566                         {
  3567                             currMirror = (Outlook.MailItem)targetFolderItems[i];
  3568                             origEntryID = (string)currMirror.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID, "");
  3569 
  3570                             if (origEntryID == id)
  3571                             {
  3572                                 lock (mutexMirror)
  3573                                 {
  3574                                     mirrorCache[id] = currMirror.EntryID;
  3575                                 }
  3576 
  3577                                 mirrorItem = currMirror;
  3578                                 break;
  3579                             }
  3580                         }
  3581                         catch { }
  3582 
  3583                         currMirror = null;
  3584                     }
  3585                 }
  3586             }
  3587             else
  3588             {
  3589                 Log.Error("MsgProcessor.GetMirror: EntryId is null or empty.");
  3590             }
  3591 
  3592             /* Release objects
  3593              * Note: Do not release mirror or currMirror (which may point to mirror).
  3594              * These are returned for external use.
  3595              */
  3596             ns = null;
  3597             pEpStore = null;
  3598             targetFolder = null;
  3599             targetFolderItems = null;
  3600 
  3601             return mirrorItem;
  3602         }
  3603 
  3604         /// <summary>
  3605         /// Gets the mirror folder based on a user name.
  3606         /// </summary>
  3607         /// <param name="userName">The user name.</param>
  3608         /// <returns>The Outlook folder for this user name.</returns>
  3609         public static Outlook.Folder GetMirrorFolder(string userName)
  3610         {
  3611             string[] specialChars;
  3612             Outlook.Folder folder;
  3613             Outlook.Folders folders;
  3614             StringBuilder strBuilder = new StringBuilder();
  3615 
  3616             try
  3617             {
  3618                 folders = Globals.ThisAddIn.PEPStoreRootFolder.Folders;
  3619             }
  3620             catch (Exception ex)
  3621             {
  3622                 Log.Error("GetMirrorFolder: Error getting PEPStoreRootFolder. " + ex.ToString());
  3623                 folders = null;
  3624                 return null;
  3625             }
  3626 
  3627             if (userName != null)
  3628             {
  3629                 /* Remove special characters from folder name.
  3630                  * See: https://msdn.microsoft.com/en-us/library/aa493942(v=exchg.80).aspx
  3631                  */
  3632                 specialChars = new string[] { "[", "]", "/", "\\", "&", "~", "?", "*", "|", "<", ">", "\"", ";", ":", "+" };
  3633                 if (userName != null)
  3634                 {
  3635                     strBuilder.Append(userName);
  3636                     for (int i = 0; i < specialChars.Length; i++)
  3637                     {
  3638                         strBuilder.Replace(specialChars[i], "");
  3639                     }
  3640                     userName = strBuilder.ToString();
  3641                 }
  3642 
  3643                 // Use unknown if invalid
  3644                 if (string.IsNullOrWhiteSpace(userName))
  3645                 {
  3646                     userName = MailItemExtensions.UNKNOWN_SENDER;
  3647                 }
  3648             }
  3649             else
  3650             {
  3651                 userName = MailItemExtensions.UNKNOWN_SENDER;
  3652             }
  3653 
  3654             try
  3655             {
  3656                 folder = (Outlook.Folder)folders[userName];
  3657                 Log.Verbose("GetMirrorFolder: Using existing folder " + userName);
  3658             }
  3659             catch
  3660             {
  3661                 folder = (Outlook.Folder)folders.Add(userName);
  3662                 Log.Verbose("GetMirrorFolder: Creating new folder + " + userName);
  3663             }
  3664 
  3665             folders = null;
  3666 
  3667             return folder;
  3668         }
  3669 
  3670         /// <summary>
  3671         /// Determines if the given text is PGP text based on starting text sequence.
  3672         /// The starting sequence must be "-----BEGIN PGP MESSAGE-----"
  3673         /// </summary>
  3674         /// <param name="text">The text to test if it is PGP text.</param>
  3675         /// <returns>True if the given text is PGP text, otherwise false.</returns>
  3676         public static bool IsPGPText(string text)
  3677         {
  3678             if (string.IsNullOrEmpty(text) == false)
  3679             {
  3680                 string pgp_text = text.Trim();
  3681                 return (pgp_text.StartsWith("-----BEGIN PGP MESSAGE-----"));
  3682             }
  3683             else
  3684             {
  3685                 return (false);
  3686             }
  3687         }
  3688 
  3689         /// <summary>
  3690         /// Determines if the given message is considered secure.
  3691         /// Currently, only PGP encrypted messages will be detected.
  3692         /// </summary>
  3693         /// <param name="msg">The message to check security for.</param>
  3694         /// <returns>True if the given message is encrypted, otherwise false.</returns>
  3695         public static bool GetIsSecure(PEPMessage msg)
  3696         {
  3697             if (msg != null)
  3698             {
  3699                 // Partitioned or inline PGP format
  3700                 if (msg.LongMsg != null && PEPMessage.IsPGPText(msg.LongMsg))
  3701                 {
  3702                     return (true);
  3703                 }
  3704 
  3705                 // PGP/MIME format
  3706                 if (PEPMessage.GetIsPGPMIMEEncrypted(msg))
  3707                 {
  3708                     return (true);
  3709                 }
  3710             }
  3711 
  3712             return (false);
  3713         }
  3714 
  3715         /// <summary>
  3716         /// Determines if the given message is encrypted in PGP/MIME format.
  3717         /// </summary>
  3718         /// <param name="msg">The message to check encryption for.</param>
  3719         /// <returns>True if the given message is PGP/MIME encrypted, otherwise false.</returns>
  3720         public static bool GetIsPGPMIMEEncrypted(PEPMessage msg)
  3721         {
  3722             bool result = false;
  3723             bool versionInfoFound = false;
  3724             bool contentFound = false;
  3725 
  3726             if (msg != null)
  3727             {
  3728                 // Require only two attachments (version identification & encrypted content)
  3729                 // However, allow the attachments to be in any order
  3730                 if (msg.Attachments.Count == 2)
  3731                 {
  3732                     foreach (PEPAttachment attach in msg.Attachments)
  3733                     {
  3734                         if (attach.IsPGPMIMEVersionInfoFormat)
  3735                         {
  3736                             versionInfoFound = true;
  3737                         }
  3738                         else if (attach.IsPGPMIMEContentFormat)
  3739                         {
  3740                             contentFound = true;
  3741                         }
  3742                     }
  3743 
  3744                     if (versionInfoFound && contentFound)
  3745                     {
  3746                         result = true;
  3747                     }
  3748                 }
  3749             }
  3750 
  3751             return (result);
  3752         }
  3753     }
  3754 
  3755     /// <summary>
  3756     /// Custom exception for when the PGP/MIME attachment exceeds the server's
  3757     /// allowable limit.
  3758     /// </summary>
  3759     [Serializable]
  3760     internal class AttachmentSizeException : Exception
  3761     {
  3762         public AttachmentSizeException()
  3763         {
  3764         }
  3765 
  3766         public AttachmentSizeException(string message)
  3767             : base(message)
  3768         {
  3769         }
  3770 
  3771         public AttachmentSizeException(string message, Exception inner)
  3772             : base(message, inner)
  3773         {
  3774         }
  3775     }
  3776 }