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