Wrappers/WatchedWindow.cs
author Thomas
Mon, 18 Nov 2019 20:26:40 +0100
branchsync
changeset 2867 67f0cdd1b684
parent 2857 d8bc3f810959
child 2898 85c082c01112
child 2943 24036835b358
permissions -rw-r--r--
Add missing using
     1 ´╗┐using pEp.UI;
     2 using pEp.UI.Models;
     3 using pEp.UI.Views;
     4 using pEpCOMServerAdapterLib;
     5 using System;
     6 using System.Collections.Generic;
     7 using System.ComponentModel;
     8 using System.Windows.Forms;
     9 using Outlook = Microsoft.Office.Interop.Outlook;
    10 
    11 namespace pEp
    12 {
    13     /// <summary>
    14     /// Stores an Outlook Window with connected events.
    15     /// </summary>
    16     public abstract class WatchedWindow : IDisposable
    17     {
    18         private CryptableMailItem               cryptableMailItem           = null;
    19         private bool                            displayMirrorRequested      = false;
    20         private PEPMessage                      handshakeMessage            = null;
    21         private bool                            isEnabled                   = true;
    22         private bool                            isStarted                   = false;
    23         private bool                            processingOngoing           = false;
    24         private bool                            refreshOngoing              = false;
    25         private bool                            repeatProcessing            = false;
    26         private int                             repeatCounter               = 0;
    27         private const int                       maxRepeatCount              = 5;
    28 
    29 
    30         internal static DialogWindow            HandshakeDialog             = null;
    31 
    32         public enum WindowType
    33         {
    34             Undefined,
    35             Inspector,
    36             Explorer
    37         }
    38 
    39         #region Properties
    40 
    41         /// <summary>
    42         /// The mail item that is connected to this Inspector/Explorer window.
    43         /// </summary>
    44         public Outlook.MailItem CurrentMailItem { get; set; } = null;
    45 
    46         /// <summary>
    47         /// Gets or sets whether to disable the Force Protection option.
    48         /// </summary>
    49         public bool DisableForceProtection { get; set; } = false;
    50 
    51         /// <summary>
    52         /// Gets or sets whether to send this message forcefully protected.
    53         /// </summary>
    54         public bool ForceProtection { get; set; } = false;
    55 
    56         /// <summary>
    57         /// Gets or sets whether to send this message forcefully unencrypted.
    58         /// </summary>
    59         public bool ForceUnencrypted { get; set; } = false;
    60 
    61         /// <summary>
    62         /// Gets or sets whether the message is a draft message.
    63         /// </summary>
    64         public bool IsDraft { get; set; } = false;
    65 
    66         /// <summary>
    67         /// Gets or sets whether to always store this message as if on an untrusted server.
    68         /// </summary>
    69         public bool NeverUnsecure { get; set; } = false;
    70 
    71         /// <summary>
    72         /// The privacy state of this form region.
    73         /// </summary>
    74         internal PrivacyState PrivacyState { get; private set; } = new PrivacyState();
    75 
    76         /// <summary>
    77         /// Gets the rating of this message.
    78         /// </summary>
    79         public pEpRating Rating { get; private set; } = pEpRating.pEpRatingUndefined;
    80 
    81         /// <summary>
    82         /// Sets the rating of this message and updates the UI.
    83         /// </summary>
    84         /// <param name="rating">The message rating.</param>
    85         public void SetRating(pEpRating rating)
    86         {
    87             this.Rating = rating;
    88             this.UpdatePrivacyStateAndUI();
    89         }
    90 
    91         /// <summary>
    92         /// The timer that schedules the updates of this window.
    93         /// </summary>
    94         public System.Windows.Forms.Timer TimerRefresh { get; set; } = new System.Windows.Forms.Timer();
    95 
    96         /// <summary>
    97         /// Gets the associated Explorer/Inspector window. To be overwritten in child class.
    98         /// </summary>
    99         public abstract dynamic Window { get; }
   100 
   101         /// <summary>
   102         /// Gets the window type of this window. To be overwritten in child class.
   103         /// </summary>
   104         public abstract WindowType Type { get; }
   105 
   106         #endregion
   107 
   108         #region Constructors / Destructors
   109 
   110         /// <summary>
   111         /// Destructor.
   112         /// </summary>
   113         ~WatchedWindow()
   114         {
   115             this.Dispose(true);
   116         }
   117         #endregion
   118 
   119         #region Methods
   120         /**************************************************************
   121          * 
   122          * Methods
   123          * 
   124          *************************************************************/
   125 
   126         /// <summary>
   127         /// Releases all resources and disconnects internal events.
   128         /// </summary>
   129         public void Dispose()
   130         {
   131             this.Dispose(true);
   132         }
   133 
   134         /// <summary>
   135         /// Clean up any resources being used.
   136         /// </summary>
   137         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
   138         protected virtual void Dispose(bool disposing)
   139         {
   140             if (disposing)
   141             {
   142                 // Disconnect cryptable mail item
   143                 if (this.cryptableMailItem != null)
   144                 {
   145                     try
   146                     {
   147                         this.cryptableMailItem.PropertyChanged -= MailItem_PropertyChanged;
   148                         this.cryptableMailItem.ProcessingCompleted -= MailItem_ProcessingCompleted;
   149                         this.cryptableMailItem.GetMirrorCompleted -= MailItem_GetMirrorCompleted;
   150                         this.cryptableMailItem.Open -= MailItem_Open;
   151                         this.cryptableMailItem.Send -= MailItem_Send;
   152                         this.cryptableMailItem.OriginallyEncryptedStatusUpdated -= CryptableMailItem_OriginallyEncryptedStatusUpdated;
   153                     }
   154                     catch { }
   155 
   156                     this.cryptableMailItem.Dispose();
   157                     this.cryptableMailItem = null;
   158                 }
   159 
   160                 // Disconnect other
   161                 this.SetIsEnabled(false);
   162 
   163                 // Set Outlook objects to null
   164                 this.CurrentMailItem = null;
   165 
   166                 // Dispose of timer
   167                 if (this.TimerRefresh != null)
   168                 {
   169                     this.TimerRefresh.Tick -= TimerRefresh_Tick;
   170                     this.TimerRefresh.Dispose();
   171                     this.TimerRefresh = null;
   172                 }
   173             }
   174         }
   175 
   176         /// <summary>
   177         /// Builds the latest state of the encryption status manager then shows the UI.
   178         /// </summary>
   179         public void BuildAndShowHandshakeDialog()
   180         {
   181             /* Resolve all recipients -- this ensures the identities list is correctly populated
   182              * 
   183              * Note: The PropertyChanged changed event must be disconnected before trying to resolve.
   184              * This is because the resolve process can modify the contents of the mail item which triggers an event.
   185              * The PropertyChanged event would then trigger a UI refresh cycle. However, because the GetManagerState itself 
   186              * is called within the UI refresh, an infinite loop could occur trying to resolve a recipient that
   187              * cannot be resolved (no address).
   188              */
   189             try
   190             {
   191                 this.ResolveAllRecipients();
   192             }
   193             catch (Exception ex)
   194             {
   195                 Log.Verbose("BuildAndShowHandshakeDialog: Error resolving recipients. " + ex.ToString());
   196             }
   197 
   198             // Build the dialog
   199             try
   200             {
   201                 // The own identity to build the dialog with
   202                 PEPIdentity myself = this.cryptableMailItem?.Myself;
   203 
   204                 // If message is a draft, create it directly from the Outlook mail item
   205                 if (this.IsDraft)
   206                 {
   207                     Log.Verbose("BuildAndShowHandshakeDialog: Creating PEPMessage from draft.");
   208 
   209                     if (PEPMessage.Create(this.CurrentMailItem, out this.handshakeMessage, true) == Globals.ReturnStatus.Success)
   210                     {
   211                         // If Force Protection is set, assign a random GUID
   212                         if ((this.ForceProtection) &&
   213                             (this.DisableForceProtection == false))
   214                         {
   215                             this.handshakeMessage.ForceProtectionId = Guid.NewGuid().ToString();
   216                         }
   217 
   218                         // If message is Force Unencrypted, assign it
   219                         this.handshakeMessage.ForceUnencrypted = this.ForceUnencrypted;
   220                     }
   221                     else
   222                     {
   223                         Log.Error("BuildAndShowHandshakeDialog: Error creating PEPMessage from draft.");
   224                         this.handshakeMessage = null;
   225                     }
   226                 }
   227                 else
   228                 {
   229                     // Create the message
   230                     if (PEPMessage.Create(this.CurrentMailItem, out this.handshakeMessage) != Globals.ReturnStatus.Success)
   231                     {
   232                         this.handshakeMessage = null;
   233                         Log.Error("BuildAndShowHandshakeDialog: Error creating PEPMessage from mirror.");
   234                     }
   235                 }
   236 
   237                 // Build dialog
   238                 if (this.handshakeMessage != null)
   239                 {
   240                     // Get myself identiy if we don't have it yet
   241                     if (myself == null)
   242                     {
   243                         myself = this.CurrentMailItem.GetMyselfIdentity();
   244                     }
   245 
   246                     /* Add recipients to the dialog:
   247                      *  - For incoming messages, add the From recipient as primary
   248                      *    recipient and all others as secondary recipients.
   249                      *  - For outgoing messages, add all To recipients as primary
   250                      *    recipients and all others as secondary recipients.
   251                      * Note: don't add own identities as secondary recipients.
   252                      */
   253                     List<PEPIdentity> primaryPartners = new List<PEPIdentity>();
   254                     List<PEPIdentity> secondaryPartners = new List<PEPIdentity>();
   255 
   256                     if (this.handshakeMessage.Direction == pEpMsgDirection.pEpDirIncoming)
   257                     {
   258                         primaryPartners.Add(this.handshakeMessage.From);
   259 
   260                         this.handshakeMessage.To?.ForEach(identity =>
   261                         {
   262                             if (PEPIdentity.GetIsOwnIdentity(identity.Address) == false)
   263                             {
   264                                 secondaryPartners.Add(identity);
   265                             }
   266                         });
   267                     }
   268                     else
   269                     {
   270                         this.handshakeMessage.To?.ForEach(identity =>
   271                         {
   272                             primaryPartners.Add(identity);
   273                         });
   274                     }
   275 
   276                     this.handshakeMessage.Cc?.ForEach(identity =>
   277                     {
   278                         if (PEPIdentity.GetIsOwnIdentity(identity.Address) == false)
   279                         {
   280                             secondaryPartners.Add(identity);
   281                         }
   282                     });
   283 
   284                     this.handshakeMessage.Bcc?.ForEach(identity =>
   285                     {
   286                         if (PEPIdentity.GetIsOwnIdentity(identity.Address) == false)
   287                         {
   288                             secondaryPartners.Add(identity);
   289                         }
   290                     });
   291 
   292                     // Do not open dialogs if no recipients are found
   293                     if (primaryPartners?.Count > 0 || secondaryPartners?.Count > 0)
   294                     {
   295                         if (WatchedWindow.HandshakeDialog != null)
   296                         {
   297                             WatchedWindow.HandshakeDialog?.Close();
   298                             WatchedWindow.HandshakeDialog = null;
   299                         }
   300 
   301                         WatchedWindow.HandshakeDialog = new DialogWindow(new Dialog(Dialog.Type.Handshake, myself, primaryPartners, secondaryPartners));
   302                         WatchedWindow.HandshakeDialog.Closed += HandshakeDialog_Closed;
   303                         WatchedWindow.HandshakeDialog.Show();
   304                     }
   305                     else
   306                     {
   307                         Log.Error("BuildAndShowHandshakeDialog: No recipients found. No dialog opened.");
   308                     }
   309                 }
   310                 else
   311                 {
   312                     throw new Exception("Could not build handshake dialog. Message is null.");
   313                 }
   314             }
   315             catch (Exception ex)
   316             {
   317                 Log.Error("BuildAndShowHandshakeDialog: Error creating handshake dialog. " + ex.ToString());
   318             }
   319         }
   320 
   321         /// <summary>
   322         /// Initializes the watched explorer or inspector window.
   323         /// </summary>
   324         /// <param name="isInlineResponse">Whether this window is an inline response.</param>
   325         protected void InitializeWindow(bool isInlineResponse = false)
   326         {
   327             bool enableFormRegion = false;
   328             bool cancelOpenEvent = false;
   329             bool isSecureAttachedMail = false;
   330             string messageId = null;
   331             PEPCache.CacheItem cacheItem = null;
   332 
   333             try
   334             {
   335                 // Check if draft
   336                 this.IsDraft = this.CurrentMailItem.GetIsDraft();
   337 
   338                 /* Set immediately a provisional rating in order to have a less flickery
   339                  * UI experience.
   340                  * The provisional rating is either a stored rating or, in case we have a
   341                  * reply message, the rating of the original (the item we reply to).
   342                  * This provisional rating will be replace with the real one once the full
   343                  * processing is complete.
   344                  */
   345                 pEpRating provisionalRating = pEpRating.pEpRatingUndefined;
   346 
   347                 // Try to get an original rating (in case of reply messages)
   348                 if (this.IsDraft)
   349                 {
   350                     if (this.CurrentMailItem?.Recipients?.Count > 0)
   351                     {
   352                         string originalRatingString = this.CurrentMailItem.GetUserProperty(CryptableMailItem.USER_PROPERTY_KEY_ORIGINAL_RATING) as string;
   353 
   354                         // If we have an original rating, parse it and set it.
   355                         if (string.IsNullOrEmpty(originalRatingString) == false)
   356                         {
   357                             provisionalRating = AdapterExtensions.ParseRatingString(originalRatingString);
   358                         }
   359                     }
   360                 }
   361                 else
   362                 {
   363                     // Try to get item from cache
   364                     cacheItem = PEPCache.GetItemFromCache(this.CurrentMailItem.EntryID);
   365 
   366                     if (cacheItem != null)
   367                     {
   368                         // Set rating from cache
   369                         provisionalRating = cacheItem?.Rating ?? pEpRating.pEpRatingUndefined;
   370                     }
   371                     else
   372                     {
   373                         // Get rating from db
   374                         provisionalRating = PEPDatabase.GetRating(this.CurrentMailItem.EntryID);
   375 
   376                         // If there is no rating in the db, use stored or default rating
   377                         if (provisionalRating == pEpRating.pEpRatingUndefined)
   378                         {
   379                             provisionalRating = this.CurrentMailItem.GetStoredRating() ?? pEpRating.pEpRatingUndefined;
   380                         }
   381                     }
   382                 }
   383 
   384                 // Only set rating if one has been retrieved
   385                 if (provisionalRating != pEpRating.pEpRatingUndefined)
   386                 {
   387                     this.SetRating(provisionalRating);
   388                     Log.Verbose("InitializeWindow: Provisional rating {0} shown.", Enum.GetName(typeof(pEpRating), provisionalRating));
   389                 }
   390 
   391                 // Do not process S/MIME messages
   392                 if (this.CurrentMailItem.GetIsSMIMEEnabled())
   393                 {
   394                     Log.Verbose("InitializeWindow: S/MIME message detected. Won't be processed.");
   395 
   396                     // Set unencrypted rating
   397                     this.SetRating(pEpRating.pEpRatingUnencrypted);
   398 
   399                     // Set icon(s) if necessary     
   400                     if ((IsDraft == false) &&
   401                         (this.CurrentMailItem.SetEncryptionIcons()))
   402                     {
   403                         try
   404                         {
   405                             this.CurrentMailItem.Save();
   406                         }
   407                         catch (Exception ex)
   408                         {
   409                             Log.Error("InitializeWindow: Error saving message after changing icon. " + ex.ToString());
   410                         }
   411                     }
   412 
   413                     return;
   414                 }
   415 
   416                 /* Check if item is attached mail. For performance reasons,
   417                  * only do the whole check if an item has been loaded to the
   418                  * cache of attached mails and if item might be secure.
   419                  */
   420                 if ((PEPAttachment.AttachedMailsCache.Count > 0) &&
   421                     (this.CurrentMailItem.Attachments?.Count == 2))
   422                 {
   423                     try
   424                     {
   425                         // Check if mail item is an attached mail
   426                         if (this.CurrentMailItem.GetIsAttachedMail(out messageId))
   427                         {
   428                             Outlook.MailItem mirror = null;
   429 
   430                             // Try to get the mirror
   431                             mirror = this.CurrentMailItem.GetMirror(messageId);
   432 
   433                             // If mirror was not found, decrypt and create mirror
   434                             if (mirror != null)
   435                             {
   436                                 isSecureAttachedMail = true;
   437                             }
   438                             else
   439                             {
   440                                 if ((PEPMessage.Create(this.CurrentMailItem, out PEPMessage pEpMessage) == Globals.ReturnStatus.Success) &&
   441                                     (pEpMessage.IsSecure))
   442                                 {
   443                                     MsgProcessor msgProcessor = new MsgProcessor();
   444                                     if (msgProcessor.Decrypt(pEpMessage, out PEPMessage decryptedMessage))
   445                                     {
   446                                         isSecureAttachedMail = true;
   447                                         mirror = this.CurrentMailItem.CreateMirrorOMI(messageId);
   448                                         decryptedMessage.ApplyTo(mirror, true, false);
   449                                         mirror?.Save();
   450                                     }
   451                                     else
   452                                     {
   453                                         Log.Error("InitializeWindow: Decryption of attached mail was not successful.");
   454                                     }
   455                                 }
   456                             }
   457 
   458                             // Check if attachment is being opened or if only the preview is needed
   459                             if (CryptableMailItem.PreviewAttachedMailId?.Equals(messageId) != true)
   460                             {
   461                                 // Display mirror and cancel opening of original
   462                                 cancelOpenEvent = true;
   463                                 mirror?.Display();
   464                             }
   465 
   466                             // Not needed anymore after this point
   467                             CryptableMailItem.PreviewAttachedMailId = null;
   468                         }
   469                     }
   470                     catch (Exception ex)
   471                     {
   472                         messageId = null;
   473                         Log.Error("InitializeWindow: Error checking for attached mail. " + ex.ToString());
   474                     }
   475                 }
   476 
   477                 // Check for item from cache and show it if possible
   478                 if ((this.IsDraft == false) &&
   479                     (cacheItem?.Mirror != null) &&
   480                     (this.CurrentMailItem.GetIsInSecureStore()))
   481                 {
   482                     WindowFormRegionCollection formRegions = null;
   483                     try
   484                     {
   485                         formRegions = Globals.FormRegions[this.Window];
   486                     }
   487                     catch (Exception ex)
   488                     {
   489                         Log.Error("InitializeWindow: Error getting form region collection. " + ex.ToString());
   490                     }
   491 
   492                     this.GetFormRegionPreviewUnencrypted()?.DisplayState?.SetMessage(cacheItem.Mirror);
   493                 }
   494 
   495                 this.cryptableMailItem = new CryptableMailItem(this.CurrentMailItem, provisionalRating)
   496                 {
   497                     // Set inline response property
   498                     IsInlineResponse = isInlineResponse
   499                 };
   500 
   501                 // Set properties for encrypted attached mail
   502                 if (isSecureAttachedMail)
   503                 {
   504                     this.cryptableMailItem.MessageId = messageId;
   505                     this.cryptableMailItem.IsSecureAttachedMail = true;
   506                 }
   507 
   508                 // Connect cryptable mail item events
   509                 if (this.cryptableMailItem != null)
   510                 {
   511                     // If we don't open the original, set property and return
   512                     if (cancelOpenEvent)
   513                     {
   514                         this.cryptableMailItem.Open += MailItem_Open;
   515                         this.cryptableMailItem.CancelOpen = true;
   516                         return;
   517                     }
   518                     else
   519                     {
   520                         try
   521                         {
   522                             this.cryptableMailItem.PropertyChanged += MailItem_PropertyChanged;
   523                             this.cryptableMailItem.ProcessingCompleted += MailItem_ProcessingCompleted;
   524                             this.cryptableMailItem.GetMirrorCompleted += MailItem_GetMirrorCompleted;
   525                             this.cryptableMailItem.Send += MailItem_Send;
   526 
   527                             if (this.cryptableMailItem.IsSecurelyStored)
   528                             {
   529                                 this.cryptableMailItem.Open += MailItem_Open;
   530                             }
   531                         }
   532                         catch (Exception ex)
   533                         {
   534                             Log.Error("InitializeWindow: Error occured. " + ex.ToString());
   535                         }
   536 
   537                         if (this.CurrentMailItem.GetIsPEPEnabled())
   538                         {
   539                             // If pEp is enabled, show the form region
   540                             enableFormRegion = true;
   541                         }
   542                         else
   543                         {
   544                             /* If pEp is disabled, process item and show form region in the following cases:
   545                              * 1. Incoming items: if decrypt always option is set
   546                              * 2. Outgoing items: if EnableProtection option is set
   547                              *                    Note: if this property isn't set, subscribe to originally 
   548                              *                          encrypted status update event handler in case the 
   549                              *                          current code runs before CryptableMailItem.MailItem_Reply
   550                              *                          or CryptableMailItem.MailItem_Forward.
   551                              */
   552                             if (this.IsDraft)
   553                             {
   554                                 enableFormRegion = true;
   555                                 this.cryptableMailItem.OriginallyEncryptedStatusUpdated += CryptableMailItem_OriginallyEncryptedStatusUpdated;
   556                             }
   557                             else
   558                             {
   559                                 enableFormRegion = this.CurrentMailItem.GetIsDecryptAlwaysEnabled();
   560                             }
   561                         }
   562                     }
   563                 }
   564 
   565                 // Update pEp enabled status
   566                 this.SetIsEnabled(enableFormRegion);
   567 
   568                 if (this.isEnabled)
   569                 {
   570                     // If forcefully protected, run dedicated decryption
   571                     if (this.CurrentMailItem.GetIsForcefullyProtected())
   572                     {
   573                         FPPMessage fppMessage = new FPPMessage(this.CurrentMailItem);
   574                         if ((fppMessage?.GetMessageType() != null) ||
   575                             (fppMessage?.CurrentMessage?.IsSecure == true))
   576                         {
   577                             fppMessage.ProcessIncoming(true);
   578                         }
   579                         else
   580                         {
   581                             this.TimerRefresh_Tick(null, new EventArgs());
   582                         }
   583                     }
   584                     else
   585                     {
   586                         // Call the timer tick method manually to refresh data with no delay
   587                         this.TimerRefresh_Tick(null, new EventArgs());
   588                     }
   589                 }
   590             }
   591             catch (Exception ex)
   592             {
   593                 Log.Error("InitializeWindow: Error. " + ex.ToString());
   594             }
   595         }
   596 
   597         /// <summary>
   598         /// Schedules for the pEp rating and UI (including displayed mirror) to be updated.
   599         /// This can be called many times with no issue as the update is only run every n milliseconds.
   600         /// </summary>
   601         public void RequestRatingAndUIUpdate()
   602         {
   603             if (this.isEnabled)
   604             {
   605                 this.TimerRefresh.Enabled = true;
   606             }
   607         }
   608 
   609         /// <summary>
   610         /// Immediately starts the update of UI rating based on the associated mail item.
   611         /// This will start decryption as necessary and also will update the displayed mirror.
   612         /// This method by-passes the refresh timer completely.
   613         /// WARNING: This method assumes the message is fully downloaded already.
   614         /// </summary>
   615         private void ImmediateRatingAndUIUpdate()
   616         {
   617             if ((this.isEnabled) &&
   618                 (this.cryptableMailItem != null))
   619             {
   620                 Log.Verbose("ImmediateRatingAndUIUpdate: Starting processing.");
   621 
   622                 // Start the rating calculation/decryption process
   623                 this.cryptableMailItem.StartProcessing(this.IsDraft);
   624                 this.processingOngoing = true;
   625 
   626                 // If we have a draft message, fetch directly a preview outgoing rating
   627                 if (this.IsDraft)
   628                 {
   629                     pEpRating rating = this.GetOutgoingRatingPreview();
   630 
   631                     if (rating != pEpRating.pEpRatingUndefined)
   632                     {
   633                         this.SetRating(rating);
   634                     }
   635                 }
   636             }
   637         }
   638 
   639         /// <summary>
   640         /// Gets a preview of the outgoing rating which is faster than the full version.
   641         /// Caveat: might show a wrong rating in case of missing keys.
   642         /// </summary>
   643         /// <returns>A preview of the outgoing rating.</returns>
   644         private pEpRating GetOutgoingRatingPreview()
   645         {
   646             pEpRating rating = pEpRating.pEpRatingUndefined;
   647 
   648             try
   649             {
   650                 rating = this.CurrentMailItem?.GetOutgoingRating(false, true) ?? pEpRating.pEpRatingUndefined;
   651             }
   652             catch (Exception ex)
   653             {
   654                 rating = pEpRating.pEpRatingUndefined;
   655                 Log.Error("ImmediateRatingAndUIUpdate: Error getting outgoing preview rating. " + ex.ToString());
   656             }
   657 
   658             return rating;
   659         }
   660 
   661         /// <summary>
   662         /// Updates the privacy state and the UI (ribbon and form region).
   663         /// </summary>
   664         public void UpdatePrivacyStateAndUI()
   665         {
   666             try
   667             {
   668                 this.PrivacyState = new PrivacyState(this.Rating,
   669                                                      this.ForceProtection,
   670                                                      this.ForceUnencrypted);
   671                 RibbonCustomizations.Invalidate();
   672                 this.GetFormRegionPrivacyStatus()?.UpdateFormRegion(this.PrivacyState);
   673             }
   674             catch (Exception ex)
   675             {
   676                 Log.Error("UpdateUI: Error occured. " + ex.ToString());
   677             }
   678         }
   679 
   680         /// <summary>
   681         /// Gathers all form regions for this WatchedWindow.
   682         /// </summary>
   683         /// <returns>The gathered form regions.</returns>
   684         internal WindowFormRegionCollection GetFormRegions()
   685         {
   686             /* We have to find out here if the window is an explorer or an inspector.
   687              * Calling Globals.FormRegions[] with an ambiguous window type leads to a crash.
   688              */
   689             Outlook.Explorer explorer;
   690             Outlook.Inspector inspector;
   691             WindowFormRegionCollection formRegions = null;
   692             try
   693             {
   694                 if (this.Window != null)
   695                 {
   696                     if (this.Type == WindowType.Explorer)
   697                     {
   698                         explorer = this.Window as Outlook.Explorer;
   699                         formRegions = Globals.FormRegions[explorer];
   700                     }
   701                     else if (this.Type == WindowType.Inspector)
   702                     {
   703                         inspector = this.Window as Outlook.Inspector;
   704                         formRegions = Globals.FormRegions[inspector];
   705                     }
   706                 }
   707                 else
   708                 {
   709                     Log.Warning("GetFormRegions: Could not get form regions. Current window is null.");
   710                 }
   711             }
   712             catch (Exception ex)
   713             {
   714                 formRegions = null;
   715                 Log.Error("GetFormRegions: Error getting form regions collection. " + ex.ToString());
   716             }
   717             finally
   718             {
   719                 explorer = null;
   720                 inspector = null;
   721             }
   722 
   723             return formRegions;
   724         }
   725 
   726         /// <summary>
   727         /// Gets the FormRegionPrivacyStatus of this WatchedWindow (if available).
   728         /// </summary>
   729         /// <returns>The FormRegionPrivacyStatus of this window or null.</returns>
   730         internal FormRegionPrivacyStatus GetFormRegionPrivacyStatus()
   731         {
   732             return this.GetFormRegions()?.FormRegionPrivacyStatus;
   733         }
   734 
   735         /// <summary>
   736         /// Gets the FormRegionPreviewUnencrypted of this WatchedWindow (if available).
   737         /// </summary>
   738         /// <returns>The FormRegionPreviewUnencrypted of this window or null.</returns>
   739         internal FormRegionPreviewUnencrypted GetFormRegionPreviewUnencrypted()
   740         {
   741             return this.GetFormRegions()?.FormRegionPreviewUnencrypted;
   742         }
   743 
   744         /// <summary>
   745         /// Resets the Enabled status of this form region and recalculates the rating if necessary.
   746         /// </summary>
   747         /// <param name="enable">Whether to enable the form region.</param>
   748         public void UpdateFormRegion(bool enable)
   749         {
   750             this.SetIsEnabled(enable);
   751 
   752             // Refresh the UI
   753             if (enable)
   754             {
   755                 this.ResolveAllRecipients();
   756                 this.RequestRatingAndUIUpdate();
   757             }
   758         }
   759 
   760         /// <summary>
   761         /// Workaround method to update the current inspector window. This is done by moving the mail item
   762         /// to a temporary folder first and then back to the current folder. Both folders CANNOT be the same.
   763         /// As a fallback, the mail item stays in the temporary folder if moving back to the current folder
   764         /// fails.
   765         /// </summary>
   766         private void UpdateInspector()
   767         {
   768             Outlook.Application application = null;
   769             Outlook.Folder currentFolder = null;
   770             Outlook.Folder tempFolder = null;
   771             Outlook.Inspector currentInspector = null;
   772             Outlook.Inspector newInspector = null;
   773             Outlook.MailItem tempMailItem = null;
   774             Outlook.Store store = null;
   775             Outlook.MailItem omi = this.CurrentMailItem;
   776 
   777             if (omi != null)
   778             {
   779                 try
   780                 {
   781                     application = omi.Application;
   782                     currentInspector = omi.GetInspector;
   783                     currentFolder = omi.Parent as Outlook.Folder;
   784 
   785                     if ((currentInspector != null) &&
   786                         (application != null) &&
   787                         (currentFolder != null))
   788                     {
   789                         var left = currentInspector.Left;
   790                         var top = currentInspector.Top;
   791                         var width = currentInspector.Width;
   792                         var height = currentInspector.Height;
   793                         var windowState = currentInspector.WindowState;
   794 
   795                         /* Check, if in trusted store. In that case, use the default drafts folder
   796                          * as temporary folder. If the store is untrusted, use the pEp drafts folder.
   797                          */
   798                         store = currentFolder.Store;
   799                         if (store?.GetIsSecureStorageEnabled() ?? false)
   800                         {
   801                             tempFolder = Globals.ThisAddIn.GetPEPStoreDraftsFolder();
   802                         }
   803                         else
   804                         {
   805                             tempFolder = store.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDrafts) as Outlook.Folder;
   806                         }
   807 
   808                         if (tempFolder != null)
   809                         {
   810                             tempMailItem = omi.Move(tempFolder) as Outlook.MailItem;
   811 
   812                             if (tempMailItem != null)
   813                             {
   814                                 try
   815                                 {
   816                                     omi = tempMailItem.Move(currentFolder) as Outlook.MailItem;
   817                                 }
   818                                 catch
   819                                 {
   820                                     omi = tempMailItem.Copy();
   821                                 }
   822 
   823                                 newInspector = application.Inspectors.Add(omi);
   824 
   825                                 if (windowState == Outlook.OlWindowState.olNormalWindow)
   826                                 {
   827                                     newInspector.Left = left;
   828                                     newInspector.Top = top;
   829                                     newInspector.Width = width;
   830                                     newInspector.Height = height;
   831                                 }
   832 
   833                                 newInspector.Display();
   834                                 newInspector.WindowState = windowState;
   835 
   836                                 repeatProcessing = false;
   837                             }
   838                         }
   839                         else
   840                         {
   841                             Log.Error("UpdateInspector: Cannot get temporary folder.");
   842                         }
   843                     }
   844                     else
   845                     {
   846                         Log.Verbose("UpdateInspector: Error retrieving inspector window or application.");
   847                     }
   848                 }
   849                 catch (Exception ex)
   850                 {
   851                     Log.Verbose("UpdateInspector: Error updating inspector window. " + ex.ToString());
   852                 }
   853                 finally
   854                 {
   855                     application = null;
   856                     currentInspector = null;
   857                     newInspector = null;
   858                     omi = null;
   859                     tempMailItem = null;
   860                 }
   861             }
   862         }
   863 
   864         /// <summary>
   865         /// Clears the associated unencrypted preview and displays the given note (if any).
   866         /// </summary>
   867         /// <param name="note">The note to display.</param>
   868         private void SetNote(string note = null)
   869         {
   870             WindowFormRegionCollection formRegions = Globals.FormRegions[Globals.ThisAddIn.Application.ActiveWindow()];
   871 
   872             if ((formRegions != null) &&
   873                 (string.IsNullOrEmpty(note) == false))
   874             {
   875                 if ((formRegions.FormRegionPreviewUnencrypted != null) &&
   876                     (formRegions.FormRegionPreviewUnencrypted.Visible))
   877                 {
   878                     formRegions.FormRegionPreviewUnencrypted.DisplayState.SetNote(note);
   879                 }
   880             }
   881         }
   882 
   883         /// <summary>
   884         /// Sets whether processing of the mail item is enabled.
   885         /// This should commonly be set from the .GetIsPEPEnabled() value.
   886         /// </summary>
   887         private void SetIsEnabled(bool enabled)
   888         {
   889             Globals.ReturnStatus sts;
   890             Outlook.Recipient currUser = null;
   891             Outlook.Account currAccount = null;
   892             Outlook.Account sendingAccount = null;
   893             Outlook.NameSpace ns = null;
   894 
   895             this.isEnabled = enabled;
   896 
   897             if (enabled)
   898             {
   899                 // Do not allow initialization more than once
   900                 if (this.isStarted == false)
   901                 {
   902                     /* It's possible for new draft MailItems to be created outside the context of an account.
   903                      * In this situation the SendUsingAccount will always be null which of course breaks several pEp operations.
   904                      * The operations themselves cannot make the assumption about what account information to use.
   905                      * Therefore, the situation is detected here and for draft mail items a null SendUsingAccount will be 
   906                      * set with the session's default account.
   907                      */
   908                     if (this.IsDraft)
   909                     {
   910                         try
   911                         {
   912                             sendingAccount = this.CurrentMailItem.SendUsingAccount;
   913                             if (sendingAccount == null)
   914                             {
   915                                 ns = Globals.ThisAddIn.Application.Session;
   916                                 currUser = ns.CurrentUser;
   917                                 sts = PEPIdentity.Create(currUser, out PEPIdentity currIdent);
   918 
   919                                 if (sts == Globals.ReturnStatus.Success)
   920                                 {
   921                                     sendingAccount = AccountExtensions.GetDefaultAccount(Outlook.OlDefaultFolders.olFolderDrafts);
   922                                     if (sendingAccount != null)
   923                                     {
   924                                         this.CurrentMailItem.SendUsingAccount = currAccount;
   925                                     }
   926                                 }
   927                             }
   928 
   929                             // Check if account is already registered in pEp list and add it if necessary
   930                             PEPSettings.PEPAccountSettings acctSettings = Globals.ThisAddIn.Settings.GetAccountSettings(sendingAccount?.SmtpAddress);
   931                             if (acctSettings == null)
   932                             {
   933                                 Log.Verbose("SetIsEnabled: New account detected. Creating pEp settings.");
   934 
   935                                 // Create pEp settings for the new account
   936                                 acctSettings = sendingAccount.CreatePEPSettings();
   937 
   938                                 // Add account to list
   939                                 Globals.ThisAddIn.Settings.AccountsAddedWhileRunningList.Add(acctSettings?.SmtpAddress);
   940                                 Log.Verbose("SetIsEnabled: New account registered in pEp settings.");
   941 
   942                                 // Generate key
   943                                 Globals.ThisAddIn.RegisterMyself(acctSettings);
   944                                 Log.Verbose("SetIsEnabled: Myself registered.");
   945 
   946                                 // Set rules and view filters
   947                                 Globals.ThisAddIn.SetRulesAndViewFilters(sendingAccount, Globals.ThisAddIn.Settings.HideInternalMessages);
   948                             }
   949                         }
   950                         catch (Exception ex)
   951                         {
   952                             Log.Error("SetIsEnabled: Error occured. " + ex.ToString());
   953                         }
   954                         finally
   955                         {
   956                             currAccount = null;
   957                             currUser = null;
   958                             ns = null;
   959                             sendingAccount = null;
   960                         }
   961                     }
   962 
   963                     // Connect refresh timer
   964                     this.TimerRefresh.Tick += TimerRefresh_Tick;
   965                     this.TimerRefresh.Start();
   966                     this.isStarted = true;
   967                 }
   968             }
   969             else
   970             {
   971                 // Stop and disconnect the refresh timer
   972                 if (this.TimerRefresh != null)
   973                 {
   974                     this.TimerRefresh.Stop();
   975                     this.TimerRefresh.Enabled = false;
   976                     this.TimerRefresh.Tick -= TimerRefresh_Tick;
   977                 }
   978 
   979                 // For draft messages, just set rating to unencrypted if pEp is not enabled
   980                 if (this.IsDraft)
   981                 {
   982                     this.SetRating(pEpRating.pEpRatingUnencrypted);
   983                 }
   984 
   985                 this.isStarted = false;
   986             }
   987         }
   988 
   989         /// <summary>
   990         /// Resolves all recipients of the Outlook mail item.
   991         /// </summary>
   992         private void ResolveAllRecipients()
   993         {
   994             if ((this.isEnabled) &&
   995                 (this.cryptableMailItem != null))
   996             {
   997                 /*
   998                  * Note: The PropertyChanged changed event must be disconnected before trying to resolve.
   999                  * This is because the resolve process can modify the contents of the mail item which triggers an event.
  1000                  * The PropertyChanged event would then trigger a UI refresh cycle. However, because the GetManagerState itself 
  1001                  * is called within the UI refresh, an infinite loop could occur trying to resolve a recipient that
  1002                  * cannot be resolved(no address).
  1003                  */
  1004                 try
  1005                 {
  1006                     this.cryptableMailItem.PropertyChanged -= MailItem_PropertyChanged;
  1007                     this.cryptableMailItem.ResolveAllRecipients();
  1008                     this.cryptableMailItem.PropertyChanged += MailItem_PropertyChanged;
  1009                 }
  1010                 catch (Exception ex)
  1011                 {
  1012                     Log.Error("Error resolving recipients. " + ex.ToString());
  1013                 }
  1014             }
  1015         }
  1016         #endregion
  1017 
  1018         #region Event Handling
  1019         /**************************************************************
  1020          * 
  1021          * Event Handling
  1022          * 
  1023          *************************************************************/
  1024 
  1025         /// <summary>
  1026         /// Event handler for when the processing is completed in the associated mail item.
  1027         /// This will then update the form region UI and the privacy status window as needed.
  1028         /// </summary>
  1029         private void MailItem_ProcessingCompleted(object sender, MsgProcessor.ProcessingCompletedEventArgs e)
  1030         {
  1031             Log.Verbose("MailItem_ProcessingComplete: Decryption completed.");
  1032 
  1033             try
  1034             {
  1035                 /* Make sure that the calculated result corresponds to the currently selected mail item
  1036                  * If both are null, it's probably a draft message.
  1037                  */
  1038                 if ((e.EntryId == null && this.CurrentMailItem?.EntryID == null) ||
  1039                     (e.EntryId.Equals(this.CurrentMailItem.EntryID)))
  1040                 {
  1041                     // Marshal code back to UI thread as necessary
  1042                     DialogWindow.UIThreadDispatcher.Invoke(new Action(() =>
  1043                     {
  1044                         if (this.isEnabled)
  1045                         {
  1046                             try
  1047                             {
  1048                                 this.SetRating(e.ProcessedRating);
  1049 
  1050                                 // Set MAPI properties if needed
  1051                                 if (e.PropertiesToSet?.Count > 0)
  1052                                 {
  1053                                     this.CurrentMailItem?.SetMAPIProperties(e.PropertiesToSet);
  1054                                 }
  1055                                 Log.Verbose("MailItem_ProcessingComplete: Status bar updated with rating " + Enum.GetName(typeof(pEpRating), e.ProcessedRating));
  1056                             }
  1057                             catch (Exception ex)
  1058                             {
  1059                                 Log.Verbose("MailItem_ProcessingComplete: Error. " + ex.ToString());
  1060                             }
  1061 
  1062                             if (repeatProcessing &&
  1063                                 (repeatCounter++ < maxRepeatCount))
  1064                             {
  1065                                 repeatProcessing = false;
  1066                                 this.RequestRatingAndUIUpdate();
  1067                             }
  1068                             else
  1069                             {
  1070                                 /* Check if the mail item is in Outbox and update the inspector window if possible.
  1071                                  * This is the case when a submitted, but not yet sent email is opened again when working 
  1072                                  * offline or without internet connection. Without this update, Outlook removes the message class 
  1073                                  * "IPM.Note.SMIME.MultipartSigned" at the next send event and the message gets invalid and can't be
  1074                                  * opened again (i.e. is lost).
  1075                                  */
  1076                                 try
  1077                                 {
  1078                                     if ((this.CurrentMailItem?.GetIsSubmitted() == true) &&
  1079                                         (this.IsDraft))
  1080                                     {
  1081                                         UpdateInspector();
  1082                                     }
  1083                                 }
  1084                                 catch (Exception ex)
  1085                                 {
  1086                                     Log.Verbose("MailItem_ProcessingComplete: Error while checking if mail item is in outbox or updating inspector. " + ex.Message);
  1087                                 }
  1088 
  1089                                 /* Create the unencrypted preview if the mail item is encrypted and
  1090                                  * it is in an encrypted (untrusted) store
  1091                                  * 
  1092                                  * This is done here because FormRegionPrivacyStatus has the cryptable mail item and
  1093                                  * it also is initialized after FormRegionPreviewUnencrypted.
  1094                                  */
  1095                                 try
  1096                                 {
  1097                                     if (this.cryptableMailItem.IsSecurelyStored || this.cryptableMailItem.IsSecureAttachedMail)
  1098                                     {
  1099                                         Log.Verbose("MailItem_ProcessingComplete: Starting mirror location.");
  1100                                         this.cryptableMailItem.StartGetMirror(this.cryptableMailItem.MessageId);
  1101                                     }
  1102                                     else
  1103                                     {
  1104                                         this.SetNote();
  1105                                     }
  1106                                 }
  1107                                 catch (Exception ex)
  1108                                 {
  1109                                     // Error is possible in some situations where the mail item was deleted or moved while decryption was ongoing.
  1110                                     // While rare, just log the issue and stop the process
  1111                                     Log.Warning("MailItem_ProcessingComplete: Failed to start mirror location, " + ex.ToString());
  1112                                 }
  1113 
  1114                                 /* OUT-470: Workaround until this is implemented in the engine:
  1115                                  * Only enable Force Protection if all recipients are grey
  1116                                  */
  1117                                 if (this.IsDraft)
  1118                                 {
  1119                                     bool disableForceProtection = false;
  1120                                     Outlook.Recipients recipients = null;
  1121                                     Outlook.Recipient recipient = null;
  1122                                     PEPIdentity pEpIdentity = null;
  1123 
  1124                                     try
  1125                                     {
  1126                                         recipients = this.CurrentMailItem?.Recipients;
  1127                                         if (recipients?.Count > 0)
  1128                                         {
  1129                                             for (int i = 1; i <= recipients.Count; i++)
  1130                                             {
  1131                                                 recipient = recipients[i];
  1132                                                 if ((PEPIdentity.Create(recipient, out pEpIdentity, false) == Globals.ReturnStatus.Success) &&
  1133                                                     ((PEPIdentity.GetIsOwnIdentity(pEpIdentity.Address) ||
  1134                                                      (ThisAddIn.PEPEngine.IdentityRating(pEpIdentity.ToCOMType()) >= pEpRating.pEpRatingReliable))))
  1135                                                 {
  1136                                                     disableForceProtection = true;
  1137                                                     Log.Verbose("MailItem_ProcessingComplete: Secure recipient found. Disabling force protection.");
  1138                                                     break;
  1139                                                 }
  1140                                             }
  1141                                         }
  1142                                         else
  1143                                         {
  1144                                             // If there aren't any recipients (anymore), reset values
  1145                                             this.DisableForceProtection = false;
  1146                                             this.ForceProtection = false;
  1147                                             this.NeverUnsecure = false;
  1148                                             this.ForceUnencrypted = false;
  1149                                         }
  1150                                     }
  1151                                     catch (Exception ex)
  1152                                     {
  1153                                         Log.Error("MailItem_ProcessingComplete: Error occured. " + ex.ToString());
  1154                                     }
  1155                                     finally
  1156                                     {
  1157                                         recipient = null;
  1158                                         recipients = null;
  1159                                     }
  1160 
  1161                                     this.DisableForceProtection = disableForceProtection;
  1162                                 }
  1163                             }
  1164                         }
  1165                     }));
  1166                 }
  1167                 else
  1168                 {
  1169                     Log.Verbose("MailItem_ProcessingComplete: Calculated rating doesn't correspond to current mail item. Skipping...");
  1170                 }
  1171             }
  1172             catch (Exception ex)
  1173             {
  1174                 Log.Error("MailItem_ProcessingComplete: Error setting UI state, " + ex.ToString());
  1175             }
  1176 
  1177             this.processingOngoing = false;
  1178         }
  1179 
  1180         /// <summary>
  1181         /// Event handler for when the get mirror locating process is complete for the associated mail item.
  1182         /// This will then update the unencrypted preview in the UI.
  1183         /// </summary>
  1184         private void MailItem_GetMirrorCompleted(object sender, CryptableMailItem.GetMirrorCompletedEventArgs e)
  1185         {
  1186             try
  1187             {
  1188                 // Marshal code back to UI thread as necessary
  1189                 DialogWindow.UIThreadDispatcher.Invoke(new Action(() =>
  1190                 {
  1191                     if (this.isEnabled)
  1192                     {
  1193                         // If the message was forcefully protected, there is no mirror and we show the actual message
  1194                         if ((e.Mirror == null) &&
  1195                             (this.CurrentMailItem?.GetIsForcefullyProtected() == true))
  1196                         {
  1197                             if (PEPMessage.Create(this.CurrentMailItem, out PEPMessage mirror) == Globals.ReturnStatus.Success)
  1198                             {
  1199                                 e.Mirror = mirror;
  1200                             }
  1201                         }
  1202 
  1203                         if ((e.Mirror == null) &&
  1204                             (this.cryptableMailItem.LastProcessedStatus == Globals.ReturnStatus.Failure))
  1205                         {
  1206                             this.SetNote(Properties.Resources.Message_OpenError);
  1207                             Log.Verbose("MailItem_GetMirrorComplete: Cannot display mirror, failure during decryption.");
  1208                         }
  1209                         else if ((e.Mirror == null) &&
  1210                                  (this.cryptableMailItem.LastProcessedStatus == Globals.ReturnStatus.FailureNoConnection))
  1211                         {
  1212                             this.SetNote(Properties.Resources.Message_DecryptionNoConnection);
  1213                             Log.Verbose("MailItem_GetMirrorComplete: Cannot display mirror, connection failure during decryption.");
  1214                         }
  1215                         else
  1216                         {
  1217                             FormRegionPreviewUnencrypted formRegionPreviewUnencrypted = this.GetFormRegionPreviewUnencrypted();
  1218 
  1219                             // If we have a FormRegionPreviewUnencrypted and it's visible, show preview
  1220                             if (formRegionPreviewUnencrypted?.Visible == true)
  1221                             {
  1222                                 formRegionPreviewUnencrypted.DisplayState.OriginalEntryId = this.CurrentMailItem?.EntryID;
  1223                                 formRegionPreviewUnencrypted.DisplayState.SetMessage(e.Mirror);
  1224 
  1225                                 Log.Verbose("MailItem_GetMirrorComplete: Mirror found and displayed.");
  1226                             }
  1227                             else
  1228                             {
  1229                                 Log.Error("MailItem_GetMirrorComplete: FormRegionPreviewUnencrypted is null or invisible.");
  1230                             }
  1231                         }
  1232 
  1233                         // Display the mirror if necessary
  1234                         if ((this.cryptableMailItem != null) &&
  1235                             (this.displayMirrorRequested))
  1236                         {
  1237                             this.cryptableMailItem.DisplayMirror();
  1238                             this.displayMirrorRequested = false;
  1239                         }
  1240                     }
  1241                 }));
  1242             }
  1243             catch (Exception ex)
  1244             {
  1245                 Log.Error("MailItem_GetMirrorComplete: Error displaying preview, " + ex.ToString());
  1246             }
  1247         }
  1248 
  1249         /// <summary>
  1250         /// Event handler for the mail item originally encrypted status updated.
  1251         /// This event is fired after the status has been updated with parent information following a
  1252         /// forward or reply mail item event (on a different, likely parent, mail item).
  1253         /// </summary>
  1254         protected void CryptableMailItem_OriginallyEncryptedStatusUpdated(object sender, EventArgs e)
  1255         {
  1256             // Process the mail item now that the cache is updated
  1257             if (this.cryptableMailItem.IsOriginallyEncrypted)
  1258             {
  1259                 this.cryptableMailItem.StartProcessing(this.IsDraft);
  1260             }
  1261         }
  1262 
  1263         /// <summary>
  1264         /// Event handler for when a mail item is being opened in an inspector.
  1265         /// See: https://msdn.microsoft.com/en-us/library/office/ff865989.aspx
  1266         /// </summary>
  1267         /// <param name="cancel">Whether to cancel the event: Value is False when the event occurs. 
  1268         /// If the event procedure sets this argument to True, the open operation is not completed 
  1269         /// and the inspector is not displayed.</param>
  1270         protected void MailItem_Open(ref bool cancel)
  1271         {
  1272             if ((this.isEnabled) &&
  1273                 (this.cryptableMailItem != null))
  1274             {
  1275                 // If cryptable mail item is not to be opened, cancel opening
  1276                 if (this.cryptableMailItem.CancelOpen)
  1277                 {
  1278                     cancel = true;
  1279                 }
  1280                 else if ((this.cryptableMailItem.IsSecurelyStored) &&
  1281                          (this.cryptableMailItem.IsIncoming || this.cryptableMailItem.IsOriginallyEncrypted || !this.cryptableMailItem.IsDraft))
  1282                 {
  1283                     // Try to open the mirror
  1284                     cancel = this.cryptableMailItem.DisplayMirror();
  1285                 }
  1286             }
  1287         }
  1288 
  1289         /// <summary>
  1290         /// Event handler for when a mail item is sent.
  1291         /// See: https://msdn.microsoft.com/en-us/library/office/ff865379.aspx
  1292         /// </summary>
  1293         /// <param name="cancel">Whether to cancel the event: Value is False when the event occurs. 
  1294         /// If the event procedure sets this argument to True, the send operation is not completed 
  1295         /// and the inspector is left open.</param>
  1296         protected void MailItem_Send(ref bool cancel)
  1297         {
  1298             DialogResult result;
  1299 
  1300             if (this.isEnabled)
  1301             {
  1302                 if ((this.cryptableMailItem.IsBeingProcessed) ||
  1303                     (this.processingOngoing))
  1304                 {
  1305                     /* If the mail item is still being processed, this means that the message might go out with
  1306                      * a different rating than the one that was shown in the UI when sending was initiated.
  1307                      * As a trade-off between security and user experience, we make at least sure that the
  1308                      * current outgoing rating preview (which is only wrong under rare circumstances) is not
  1309                      * smaller than the one shown in the UI. If this is the case, just abort sending.
  1310                      */
  1311                     pEpRating outgoingPreviewRating = this.GetOutgoingRatingPreview();
  1312                     if (this.Rating != outgoingPreviewRating)
  1313                     {
  1314                         if (this.Rating > outgoingPreviewRating)
  1315                         {
  1316                             Log.Verbose("MailItem_Send: Mail item still being processed.");
  1317                             cancel = true;
  1318                             return;
  1319                         }
  1320 
  1321                         this.SetRating(outgoingPreviewRating);
  1322                     }
  1323                 }
  1324 
  1325                 // Show warning message if needed
  1326                 if ((Globals.ThisAddIn.Settings.IsSecurityLossWarningEnabled) &&
  1327                     (this.Rating < pEpRating.pEpRatingUnreliable) &&
  1328                     (this.cryptableMailItem.IsOriginallyEncrypted))
  1329                 {
  1330 
  1331 #if READER_RELEASE_MODE
  1332                     FormReaderSplash warningMessage = new FormReaderSplash(true);
  1333                     result = warningMessage.ShowDialog();
  1334 
  1335                     if (result != DialogResult.OK)
  1336                     {
  1337                         // Cancel sending
  1338                         cancel = true;
  1339                     }
  1340 #else
  1341                     result = MessageBox.Show(Properties.Resources.Message_WarningSecurityLoss,
  1342                                              Properties.Resources.Message_TitleConfirmOperation,
  1343                                              MessageBoxButtons.YesNo,
  1344                                              MessageBoxIcon.Warning);
  1345 
  1346                     if (result == DialogResult.No)
  1347                     {
  1348                         // Cancel sending
  1349                         cancel = true;
  1350                     }
  1351 #endif
  1352                 }
  1353 
  1354                 if (cancel == false)
  1355                 {
  1356                     if ((this.Type == WindowType.Inspector) &&
  1357                         (this.cryptableMailItem.IsInlineResponse == false) &&
  1358                         (this.CurrentMailItem.GetProcessingState() == null))
  1359                     {
  1360                         IntPtr hWnd = NativeMethods.GetActiveWindow();
  1361                         NativeMethods.SetWindowPos(hWnd, NativeMethods.HWND_BOTTOM, 0, 0, 0, 0, NativeMethods.SetWindowPosFlags.DoNotActivate |
  1362                                                                                                 NativeMethods.SetWindowPosFlags.IgnoreMove |
  1363                                                                                                 NativeMethods.SetWindowPosFlags.DoNotReposition |
  1364                                                                                                 NativeMethods.SetWindowPosFlags.IgnoreResize |
  1365                                                                                                 NativeMethods.SetWindowPosFlags.HideWindow);
  1366                         this.CurrentMailItem.SetProcessingState(MailItemExtensions.ProcessingState.ProcessInBackground);
  1367                         Log.Verbose("MailItem_Send: Inspector window closed.");
  1368                     }
  1369 
  1370                     // Set pEp options if needed
  1371                     if ((this.ForceProtection) &&
  1372                         (this.DisableForceProtection == false))
  1373                     {
  1374                         this.CurrentMailItem?.SetPEPProperty(MailItemExtensions.PEPProperty.ForceProtection, Guid.NewGuid().ToString());
  1375                     }
  1376                     else if (this.ForceUnencrypted)
  1377                     {
  1378                         this.CurrentMailItem?.SetPEPProperty(MailItemExtensions.PEPProperty.ForceUnencrypted, true);
  1379                     }
  1380 
  1381                     if (this.NeverUnsecure)
  1382                     {
  1383                         this.CurrentMailItem?.SetPEPProperty(MailItemExtensions.PEPProperty.NeverUnsecure, true);
  1384                     }
  1385 
  1386                     // Reset pEp options
  1387                     this.ForceProtection = false;
  1388                     this.DisableForceProtection = false;
  1389                     this.ForceUnencrypted = false;
  1390                     this.NeverUnsecure = false;
  1391 
  1392                     // Stop and disconnect the refresh timer
  1393                     // This is necessary so an ongoing refresh doesn't try to access a mail item as it's being moved
  1394                     this.TimerRefresh.Stop();
  1395                     this.TimerRefresh.Enabled = false;
  1396                     this.TimerRefresh.Tick -= TimerRefresh_Tick;
  1397                 }
  1398             }
  1399         }
  1400 
  1401         /// <summary>
  1402         /// Event handler for when a mail item property is changed.
  1403         /// See: https://msdn.microsoft.com/en-us/library/office/ff866739.aspx
  1404         /// </summary>
  1405         /// <param name="propertyName">The name of the property that was changed.</param>
  1406         protected void MailItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
  1407         {
  1408             switch (e.PropertyName.ToUpperInvariant())
  1409             {
  1410                 case "BCC":
  1411                     {
  1412                         // Outlook always fires Bcc, Cc and To together so only "TO" is used
  1413                         break;
  1414                     }
  1415                 case "CC":
  1416                     {
  1417                         // Outlook always fires Bcc, Cc and To together so only "TO" is used
  1418                         break;
  1419                     }
  1420                 case "SENTONBEHALFOFNAME":
  1421                     {
  1422                         // Always fired with "SENDUSINGACCOUNT" so is ignored
  1423                         break;
  1424                     }
  1425                 case "SENDUSINGACCOUNT":
  1426                     {
  1427                         // Update pEp enabled status
  1428                         this.isStarted = false;
  1429                         this.SetIsEnabled((this.CurrentMailItem)?.GetEnableFormRegion() ?? false);
  1430 
  1431                         // Refresh the UI
  1432                         RibbonCustomizations.Invalidate();
  1433 
  1434                         break;
  1435                     }
  1436                 case "TO":
  1437                     {
  1438                         if (this.isEnabled)
  1439                         {
  1440                             this.RequestRatingAndUIUpdate();
  1441                         }
  1442                         break;
  1443                     }
  1444             }
  1445         }
  1446 
  1447         /// <summary>
  1448         /// Event handler for when a handshake dialog was closed.
  1449         /// </summary>
  1450         protected void HandshakeDialog_Closed(object sender, EventArgs e)
  1451         {
  1452             pEpRating newRating = this.Rating;
  1453 
  1454             // Get the updated rating
  1455             bool trustedServer = this.CurrentMailItem.GetIsInSecureStore();
  1456             if (trustedServer)
  1457             {
  1458                 string[] keyList = new string[] { };
  1459                 pEpDecryptFlags flags = pEpDecryptFlags.pEpDecryptFlagUntrustedServer;
  1460                 TextMessage msg = this.handshakeMessage.ToCOMType();
  1461                 newRating = ThisAddIn.PEPEngine.DecryptMessage(ref msg, out _, ref keyList, ref flags);
  1462             }
  1463             else
  1464             {
  1465                 newRating = AdapterExtensions.ReevaluateMessageRating(this.handshakeMessage, this.Rating);
  1466             }
  1467 
  1468             // If the new rating is different, update the message with the new rating
  1469             if (newRating != this.Rating)
  1470             {
  1471                 Outlook.MailItem mirror = null;
  1472 
  1473                 try
  1474                 {
  1475                     this.SetRating(newRating);
  1476 
  1477                     if (trustedServer)
  1478                     {
  1479                         mirror = this.CurrentMailItem.GetMirror();
  1480                         mirror.SetPEPProperty(MailItemExtensions.PEPProperty.Rating, newRating);
  1481                         mirror.Save();
  1482                     }
  1483                     else
  1484                     {
  1485                         this.CurrentMailItem.SetPEPProperty(MailItemExtensions.PEPProperty.Rating, newRating);
  1486                         this.CurrentMailItem.Save();
  1487                     }
  1488                 }
  1489                 catch (Exception ex)
  1490                 {
  1491                     Log.Error("HandshakeDialog_Closed: Error updating rating. " + ex.ToString());
  1492                 }
  1493                 finally
  1494                 {
  1495                     mirror = null;
  1496                 }
  1497             }
  1498 
  1499             // Recalculate all other windows
  1500             Globals.ThisAddIn.RecalculateAllWindows(this);
  1501 
  1502             if (WatchedWindow.HandshakeDialog != null)
  1503             {
  1504                 WatchedWindow.HandshakeDialog.Closed -= HandshakeDialog_Closed;
  1505                 WatchedWindow.HandshakeDialog = null;
  1506             }
  1507 
  1508             this.handshakeMessage = null;
  1509         }
  1510 
  1511         /// <summary>
  1512         /// Event handler called after the refresh timer has elapsed.
  1513         /// </summary>
  1514         private void TimerRefresh_Tick(object sender, EventArgs e)
  1515         {
  1516             bool tryAgain = false;
  1517 #pragma warning disable 219
  1518             bool markForDownload = false;
  1519 #pragma warning restore 219
  1520             this.TimerRefresh.Stop(); // Only once
  1521             Outlook.OlDownloadState dlState;
  1522 
  1523             /* The Refresh/UI_Update process:
  1524              * There are the following components:
  1525              *   1. TimerRefresh_Tick
  1526              *        This is the main timer tick event handler called any time
  1527              *        a refresh was requested (RequestRatingAndUIUpdate) and hasn't been run yet. 
  1528              *        A refresh is requested either at initialization or when a property changes
  1529              *        (such as MailItem_PropertyChanged). It is on a timer as many 
  1530              *        property change events could occur rapidy, but only one refresh should
  1531              *        occur for performance reasons.
  1532              * 
  1533              *   2. ImmediateRatingAndUIUpdate
  1534              *        This will re-calculate the mail item's rating.
  1535              *        This internally just calls CryptableMailItem.StartProcessing.
  1536              * 
  1537              *   3. MailItem_ProcessingCompleted
  1538              *        When processing of the mail item is complete, this even handler will be called.
  1539              *        This will update the UI with the latest rating then call StartGetMirror.
  1540              *        The CopyStateToUI method is used which means any open privacy status form will also
  1541              *        be updated.
  1542              * 
  1543              *   4. MailItem_GetMirrorComplete
  1544              *        This is the final step in updating the UI, after a mirror is located, it's contents
  1545              *        will be shown in the unencrypted preview.
  1546              * 
  1547              * The general calling sequence is as shown above 1->4 with each component calling the next.
  1548              * However, for methods that update the mail item directly, commonly only 2->4 is needed.
  1549              */
  1550 
  1551             // Ensure the tick method is not called more than once
  1552             if ((this.isEnabled) &&
  1553                 (refreshOngoing == false))
  1554             {
  1555                 this.refreshOngoing = true;
  1556 
  1557                 if ((this.cryptableMailItem != null) &&
  1558                     (this.cryptableMailItem.IsBeingProcessed == false))
  1559                 {
  1560                     // Attempt to get the download state
  1561                     try
  1562                     {
  1563                         dlState = this.cryptableMailItem.DownloadState;
  1564                     }
  1565                     catch (Exception ex)
  1566                     {
  1567                         Log.Warning("TimerRefresh_Tick: Get DownloadState failed, " + ex.ToString());
  1568 
  1569                         // Assume everything is downloaded, but try to download again as well
  1570                         dlState = Outlook.OlDownloadState.olFullItem;
  1571                         markForDownload = true;
  1572                     }
  1573 
  1574                     if (dlState == Outlook.OlDownloadState.olFullItem)
  1575                     {
  1576                         this.ImmediateRatingAndUIUpdate();
  1577                     }
  1578                     else
  1579                     {
  1580                         markForDownload = true;
  1581                     }
  1582 
  1583                     /* 12/16/2016: It could be verified via testing that setting the MarkForDownload property
  1584                      * can be a way to crash Outlook under certain circumstances (e.g. with IMAP on Outlook 2010 or on Windows 7). 
  1585                      * This then is not caught by a try/catch block and therefore has to be considered an Outlook bug.
  1586                      * It seems that in newer versions of Outlook/Windows, they fixed it, causing exceptions, if at all.
  1587                      * For now, the following is commented out to prevent a crash. If at any point we see that header-only
  1588                      * messages cause bigger issues, this decision has to be reevaluated.
  1589                      */
  1590                     //if (markForDownload)
  1591                     //{
  1592                     //    // Try to mark the message for full download
  1593                     //    try
  1594                     //    {
  1595                     //        this.cryptableMailItem.MarkForDownload = Outlook.OlRemoteStatus.olMarkedForDownload;
  1596                     //        tryAgain = true;
  1597                     //    }
  1598                     //    catch (Exception ex)
  1599                     //    {
  1600                     //        Log.Warning("TimerRefresh_Tick: MarkForDownload failed, " + ex.ToString());
  1601                     //    }
  1602                     //}
  1603                 }
  1604                 else
  1605                 {
  1606                     repeatProcessing = true;
  1607                 }
  1608 
  1609                 // Set the timer to refresh again later automatically
  1610                 if (tryAgain)
  1611                 {
  1612                     this.TimerRefresh.Interval = 100;
  1613                     this.TimerRefresh.Enabled = true;
  1614                 }
  1615 
  1616                 this.refreshOngoing = false;
  1617             }
  1618         }
  1619 
  1620         #endregion
  1621     }
  1622 }