UI/FormRegionPrivacyStatus.cs
author Dean
Thu, 06 Oct 2016 19:44:58 +0200
changeset 1348 c394606d6f6c
parent 1341 2d01b4811b85
child 1349 77c31d9ef5a6
permissions -rw-r--r--
Change to .net standard capitalization rules in several places.
This now matches the adapter but has several differences from Outlook naming.
     1 ´╗┐using pEp.UI;
     2 using pEpCOMServerAdapterLib;
     3 using System;
     4 using System.Collections.Generic;
     5 using System.ComponentModel;
     6 using System.Diagnostics;
     7 using System.Runtime.InteropServices;
     8 using System.Windows;
     9 using System.Windows.Forms;
    10 using System.Windows.Media.Imaging;
    11 using Outlook = Microsoft.Office.Interop.Outlook;
    12 
    13 namespace pEp
    14 {
    15     /// <summary>
    16     /// Partial class for the privacy status form region that is displayed below every outlook message.
    17     /// </summary>
    18     internal partial class FormRegionPrivacyStatus
    19     {
    20         #region Form Region Factory
    21 
    22         [Microsoft.Office.Tools.Outlook.FormRegionMessageClass(Microsoft.Office.Tools.Outlook.FormRegionMessageClassAttribute.Note)]
    23         [Microsoft.Office.Tools.Outlook.FormRegionName("pEp.FormRegionPrivacyStatus")]
    24         public partial class FormRegionPrivacyStatusFactory
    25         {
    26             // Occurs before the form region is initialized.
    27             // To prevent the form region from appearing, set e.Cancel to true.
    28             // Use e.OutlookItem to get a reference to the current Outlook item.
    29             private void FormRegionPrivacyStatus_FormRegionInitializing(object sender, Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e)
    30             {
    31                 Outlook.MailItem omi;
    32 
    33                 try
    34                 {
    35                     omi = (Outlook.MailItem)e.OutlookItem;
    36                 }
    37                 catch
    38                 {
    39                     // Never load if it's not a MailItem
    40                     e.Cancel = true;
    41                     return;
    42                 }
    43 
    44                 // Do not load if pEp is disabled
    45                 e.Cancel = (omi.GetIsInPEPEnabledStore() == false);
    46 
    47                 //WindowFormRegionCollection formRegions;
    48 
    49                 /* There is a Microsoft bug at least in Outlook 2013 and Windows 8.1
    50                  * This bug causes multiple PrivacyStatus form regions to appear stacked on top of each other.
    51                  * To trigger this bug, on an unencrypted server, click reply to compose an in-line response.
    52                  * Then click to another tab such as People or Tasks. Then click back on the mail tab to view 
    53                  * the original email again. Two form regions will be visible: 
    54                  * (1) for the status during the in-line reply and 
    55                  * (2) for the status of only the received message.
    56                  * 
    57                  * To fix this bug, any existing form regions are found and closed when initializing a new privacy status
    58                  * form region.
    59                  */
    60                 /*
    61                 try
    62                 {
    63                     formRegions = Globals.FormRegions[Globals.ThisAddIn.Application.ActiveWindow()];
    64                     if (formRegions.FormRegionPrivacyStatus != null)
    65                     {
    66                         // Note: there seems to be no way to actually close the form region.
    67                         // Therefore, this is as close as possible to actually closing it.
    68                         // The actual form regions will be cleaned up as soon as another email is selected.
    69                         formRegions.FormRegionPrivacyStatus.OutlookFormRegion.Visible = false;
    70                     }
    71                 }
    72                 catch { }
    73                 */
    74 
    75                 return;
    76             }
    77         }
    78 
    79         #endregion
    80 
    81         /* Notes:
    82          * 
    83          * Use this.OutlookItem to get a reference to the current Outlook item.
    84          * Use this.OutlookFormRegion to get a reference to the form region.
    85          * 
    86          * UI State Managment:
    87          * 
    88          * The UI state is almost entirely set from the associated mail item data.
    89          * However, a separate state class is maintained to represent the UI as some separation is needed.
    90          * This logical separation MUST be maintained throughout the code.
    91          * Specific cases are noted where possible.
    92          * 
    93          * The separate privacy status manager form state is also managed here.
    94          * 
    95          */
    96 
    97         private CryptableMailItem       associatedMailItem     = null;
    98         private bool                    initialized            = false;
    99         private FormManagePrivacyStatus managerForm            = null;
   100         private bool                    refreshOngoing         = false;
   101         private bool                    displayMirrorRequested = false;
   102 
   103         /**************************************************************
   104          * 
   105          * Methods
   106          * 
   107          *************************************************************/
   108 
   109         /// <summary>
   110         /// Determines if this form region is running in an inspector window.
   111         /// If not true, it is assumed to be within an explorer window.
   112         /// </summary>
   113         /// <returns>True if an inspector window, false if within an explorer.</returns>
   114         private bool IsWithinInspector()
   115         {
   116             bool isWithinInspector = false;
   117             Outlook.Inspector insp = null;
   118 
   119             try
   120             {
   121                 /* There are two potential methods to determine if the item is within an inspector.
   122                  * (1) Use this.OutlookFormRegion.Parent which will contain either the Explorer or an Inspector
   123                  *     A cast could be tried to an Explorer of this parent and if it fails, it's assumed to be an inspector
   124                  * (2) Use this.OutlookFormRegion.Inspector which will contain the Inspector if it exists.
   125                  *     This will fail or return null if this form region is not running within it's own Inspector window.
   126                  */
   127                 insp = this.OutlookFormRegion.Inspector;
   128 
   129                 if (insp != null)
   130                 {
   131                     isWithinInspector = true;
   132                 }
   133             }
   134             catch
   135             {
   136                 isWithinInspector = false;
   137             }
   138             finally
   139             {
   140                 if (insp != null)
   141                 {
   142                     Marshal.ReleaseComObject(insp);
   143                     insp = null;
   144                 }
   145             }
   146 
   147             return (isWithinInspector);
   148         }
   149 
   150         /// <summary>
   151         /// Sets the cryptable mail item associated with this encryption status panel.
   152         /// The associated mail item can then be accessed through its local variable.
   153         /// </summary>
   154         private void SetAssociatedMailItem()
   155         {
   156             bool errorOccurred = false;
   157             Outlook.MailItem omi = null;
   158 
   159             // Null check
   160             if (!errorOccurred)
   161             {
   162                 try
   163                 {
   164                     if (this.OutlookItem == null)
   165                     {
   166                         errorOccurred = true;
   167                     }
   168                 }
   169                 catch
   170                 {
   171                     errorOccurred = true;
   172                 }
   173             }
   174 
   175             // Attempt to get and cast the outlook mail item
   176             if (!errorOccurred)
   177             {
   178                 try
   179                 {
   180                     omi = (Outlook.MailItem)this.OutlookItem;
   181                 }
   182                 catch
   183                 {
   184                     errorOccurred = true;
   185                 }
   186             }
   187 
   188             // Finally set the associated mail item
   189             if ((errorOccurred) ||
   190                 (omi == null))
   191             {
   192                 this.associatedMailItem = null;
   193             }
   194             else
   195             {
   196                 // Check if the associated mail item has already been set
   197                 if (this.associatedMailItem != null)
   198                 {
   199                     // Only re-set the mail item if the EntryID has changed
   200                     if (this.associatedMailItem.EntryID != omi.EntryID)
   201                     {
   202                         this.associatedMailItem = new CryptableMailItem(omi);
   203                     }
   204                 }
   205                 else
   206                 {
   207                     this.associatedMailItem = new CryptableMailItem(omi);
   208                 }
   209             }
   210 
   211             return;
   212         }
   213 
   214         /// <summary>
   215         /// Completes the handshake process when the identity of a partner was previously marked as mistrusted.
   216         /// </summary>
   217         /// <param name="myself">The personal identity to complete the handshake with.</param>
   218         /// <param name="partner">The identity of the partner to complete the handshake with.</param>
   219         private void DoHandshakeForMistrustedKey(PEPIdentity myself,
   220                                                  PEPIdentity partner)
   221         {
   222             DialogResult result;
   223 
   224             result = System.Windows.Forms.MessageBox.Show(this.ParentForm,
   225                                                           pEp.Properties.Resources.Message_WarningMistrustedKey,
   226                                                           pEp.Properties.Resources.Message_TitleConfirmOperation,
   227                                                           MessageBoxButtons.YesNo,
   228                                                           MessageBoxIcon.Warning);
   229 
   230             if (result == DialogResult.Yes)
   231             {
   232                 this.DoHandshake(myself, partner);
   233             }
   234 
   235             return;
   236         }
   237 
   238         /// <summary>
   239         /// Completes the handshake process where the identity of a partner is confirmed.
   240         /// </summary>
   241         /// <param name="myself">The personal identity to complete the handshake with.</param>
   242         /// <param name="partner">The identity of the partner to complete the handshake with.</param>
   243         private void DoHandshake(PEPIdentity myself,
   244                                  PEPIdentity partner)
   245         {
   246             DialogResult result;
   247             FormHandshake handshakeDialog;
   248 
   249             Log.Verbose("DoHandshake: Handshake started.");
   250 
   251             // Create and show handshake dialog
   252             handshakeDialog = new FormHandshake();
   253             handshakeDialog.StartPosition = FormStartPosition.CenterParent;
   254             handshakeDialog.FormControl.DisplayState = new FormControlHandshake.State(myself,
   255                                                                                       partner,
   256                                                                                       Globals.ThisAddIn.Settings.TrustwordsCulture);
   257 
   258             result = handshakeDialog.ShowDialog(this);
   259             this.ProcessDoHandshakeResult(result, partner);
   260 
   261             return;
   262         }
   263 
   264         /// <summary>
   265         /// Processes the result of the do handshake dialog after a user makes a selection.
   266         /// </summary>
   267         /// <param name="result">The result of the handshake dialog selection.</param>
   268         /// <param name="partner">The identity of the partner to handshake with.</param>
   269         private void ProcessDoHandshakeResult(DialogResult result,
   270                                               PEPIdentity partner)
   271         {
   272             pEpRating partnerIdentityRating;
   273             pEpColor partnerIdentityColor;
   274             pEpIdentity identityPartner = ThisAddIn.PEPEngine.UpdateIdentity(partner.ToCOMType());
   275 
   276             switch (result)
   277             {
   278                 case DialogResult.Yes:
   279                     {
   280                         // Get the rating/color to determine if key was previously mistrusted -- warning to user must be displayed earlier
   281                         try
   282                         {
   283                             partnerIdentityRating = ThisAddIn.PEPEngine.IdentityRating(identityPartner);
   284                         }
   285                         catch (COMException ex)
   286                         {
   287                             partnerIdentityRating = pEpRating.pEpRatingUndefined;
   288                             Log.Warning("ProcessDoHandshakeResult: Failed to get identity rating, " + ex.ToString());
   289                         }
   290                         partnerIdentityColor = partnerIdentityRating.ToColor();
   291 
   292                         // Process result
   293                         try
   294                         {
   295                             if (partnerIdentityColor == pEpColor.pEpColorRed)
   296                             {
   297                                 ThisAddIn.PEPEngine.KeyResetTrust(ref identityPartner);
   298                             }
   299 
   300                             identityPartner = ThisAddIn.PEPEngine.TrustPersonalKey(ref identityPartner);
   301                         }
   302                         catch (Exception ex)
   303                         {
   304                             Log.Error("ProcessDoHandshakeResult: Error occured while trying to set trust: " + ex.ToString());
   305                         }
   306 
   307                         this.ImmediateRatingAndUIUpdate();
   308                         this.UpdateManagePrivacyStatusForm();
   309 
   310                         break;
   311                     }
   312                 case DialogResult.No:
   313                     {
   314                         try
   315                         {
   316                             ThisAddIn.PEPEngine.KeyMistrusted(ref identityPartner);
   317                         }
   318                         catch (Exception ex)
   319                         {
   320                             Log.Error("ProcessDoHandshakeResult: Error occured while trying to mistrust key: " + ex.ToString());
   321                         }
   322 
   323                         this.ImmediateRatingAndUIUpdate();
   324                         this.UpdateManagePrivacyStatusForm();
   325 
   326                         break;
   327                     }
   328             }
   329 
   330             Log.Verbose("ProcessDoHandshakeResult: Handshake complete.");
   331 
   332             return;
   333         }
   334 
   335         /// <summary>
   336         /// Reverses any past handshake confirmation by unconfirming the given identity partner.
   337         /// </summary>
   338         /// <param name="partner">The identity of the partner to unconfirm.</param>
   339         private void UndoHandshake(PEPIdentity partner)
   340         {
   341             pEpIdentity identityPartner = ThisAddIn.PEPEngine.UpdateIdentity(partner.ToCOMType());
   342             ThisAddIn.PEPEngine.KeyResetTrust(ref identityPartner);
   343 
   344             this.ImmediateRatingAndUIUpdate();
   345             this.UpdateManagePrivacyStatusForm();
   346 
   347             return;
   348         }
   349 
   350         /// <summary>
   351         /// Builds a new manager form state using this encryption state/mail item current state.
   352         /// </summary>
   353         /// <returns>A new manager form state.</returns>
   354         private FormControlManagePrivacyStatus.State GetManagerState()
   355         {
   356             int currIndex = 0;
   357             bool isMyself;
   358             string trustwordsShort;
   359             string trustwordsFull;
   360             BitmapImage imageForceUnencOn;
   361             BitmapImage imageGreen;
   362             BitmapImage imageNoColor;
   363             BitmapImage imageRed;
   364             BitmapImage imageYellow;
   365             List<PEPIdentity> identities;
   366             PEPIdentity identity;
   367             PEPIdentity myIdentity;
   368             pEpRating partnerIdentityRating;
   369             pEpColor partnerIdentityColor;
   370             SelectionItem item;
   371             FormControlManagePrivacyStatus.State managerState = new FormControlManagePrivacyStatus.State();
   372 
   373             if (this.associatedMailItem != null)
   374             {
   375                 // Load all images from resources
   376                 imageForceUnencOn = new BitmapImage(new Uri("pack://application:,,,/pEp;component/Resources/ImageForceUnencOn.png", UriKind.RelativeOrAbsolute));
   377                 imageGreen = new BitmapImage(new Uri("pack://application:,,,/pEp;component/Resources/ImagePrivacyStatusGreen.png", UriKind.RelativeOrAbsolute));
   378                 imageNoColor = new BitmapImage(new Uri("pack://application:,,,/pEp;component/Resources/ImagePrivacyStatusNoColor.png", UriKind.RelativeOrAbsolute));
   379                 imageRed = new BitmapImage(new Uri("pack://application:,,,/pEp;component/Resources/ImagePrivacyStatusRed.png", UriKind.RelativeOrAbsolute));
   380                 imageYellow = new BitmapImage(new Uri("pack://application:,,,/pEp;component/Resources/ImagePrivacyStatusYellow.png", UriKind.RelativeOrAbsolute));
   381 
   382                 /* Resolve all recipients -- this ensures the identities list is correctly populated
   383                  *
   384                  * Note: The PropertyChanged changed event must be disconnected before trying to resolve.
   385                  * This is because the resolve process can modify the contents of the mail item which triggers an event.
   386                  * The PropertyChanged event would then trigger a UI refresh cycle. However, because the GetManagerState itself 
   387                  * is called within the UI refresh, an infinite loop could occur trying to resolve a recipient that 
   388                  * cannot be resolved (no address).
   389                  */
   390                 this.associatedMailItem.PropertyChanged -= MailItem_PropertyChanged;
   391                 this.associatedMailItem.ResolveAllRecipients();
   392                 this.associatedMailItem.PropertyChanged += MailItem_PropertyChanged;
   393 
   394                 managerState.Rating = this.FormControlPrivacyStatusChild.DisplayState.Rating;
   395                 managerState.IsIncoming = this.associatedMailItem.IsIncoming;
   396 
   397                 // Get identities
   398                 identities = this.associatedMailItem.Recipients;
   399                 myIdentity = this.associatedMailItem.Myself;
   400 
   401                 // Include the from identity for incoming messages at the beginning
   402                 identity = this.associatedMailItem.From;
   403                 if ((this.associatedMailItem.IsIncoming) &&
   404                     (identity != null))
   405                 {
   406                     identities.Insert(0, identity);
   407                 }
   408 
   409                 // Remove own identity in the MailItem from the list
   410                 if (myIdentity != null)
   411                 {
   412                     for (int i = (identities.Count - 1); i >= 0; i--)
   413                     {
   414                         if ((identities[i] != null) &&
   415                             (identities[i].EqualsByAddress(myIdentity)))
   416                         {
   417                             identities.RemoveAt(i);
   418                         }
   419                     }
   420                 }
   421 
   422                 // Add identities
   423                 identities = PEPIdentity.ToFlatList(identities);
   424                 managerState.Identities.Clear();
   425                 foreach (PEPIdentity ident in identities)
   426                 {
   427                     // Needed so index can be used within anonymous methods without getting into the 'outer variable trap' of currIndex
   428                     int index = currIndex;
   429 
   430                     if (ident.IsAddressValid)
   431                     {
   432                         // Update the partner identity to get a fingerprint, preserve IsForceUnencrypted
   433                         pEpIdentity partnerIdentity_s = ThisAddIn.PEPEngine.UpdateIdentity(ident.ToCOMType());
   434                         PEPIdentity partnerIdentity = new PEPIdentity(partnerIdentity_s);
   435                         partnerIdentity.IsForceUnencrypted = ident.IsForceUnencrypted;
   436 
   437                         try
   438                         {
   439                             partnerIdentityRating = ThisAddIn.PEPEngine.IdentityRating(partnerIdentity_s);
   440                         }
   441                         catch (COMException ex)
   442                         {
   443                             partnerIdentityRating = pEpRating.pEpRatingUndefined;
   444                             Log.Warning("GetManagerState: Failed to get identity rating, " + ex.ToString());
   445                         }
   446                         partnerIdentityColor = partnerIdentityRating.ToColor();
   447 
   448                         isMyself = PEPIdentity.GetIsOwnIdentity(partnerIdentity.Address);
   449 
   450                         // Calculate trustwords
   451                         Globals.ThisAddIn.CalcTrustwords(myIdentity,
   452                                                          partnerIdentity,
   453                                                          out trustwordsShort,
   454                                                          out trustwordsFull,
   455                                                          Globals.ThisAddIn.Settings.TrustwordsCulture?.TwoLetterISOLanguageName);
   456 
   457                         item = new SelectionItem();
   458                         item.TextLine1 = partnerIdentity.UserName;
   459                         item.TextLine2 = partnerIdentity.Address;
   460                         item.Tag = (string.IsNullOrWhiteSpace(partnerIdentity.Fingerprint) ? null : trustwordsShort);
   461 
   462                         // Don't show both the user name and address if they are the same
   463                         if ((item.TextLine1 != null) &&
   464                             (item.TextLine2 != null) &&
   465                             (item.TextLine1 == item.TextLine2))
   466                         {
   467                             item.IsTwoTextLinesVisible = false;
   468                         }
   469 
   470                         // Set image
   471                         if ((partnerIdentity.IsForceUnencryptedBool) &&
   472                             (managerState.IsIncoming == false))
   473                         {
   474                             item.ItemImage = imageForceUnencOn;
   475                         }
   476                         else
   477                         {
   478                             switch (partnerIdentityColor)
   479                             {
   480                                 case pEpColor.pEpColorGreen:
   481                                     item.ItemImage = imageGreen;
   482                                     break;
   483                                 case pEpColor.pEpColorYellow:
   484                                     item.ItemImage = imageYellow;
   485                                     break;
   486                                 case pEpColor.pEpColorRed:
   487                                     item.ItemImage = imageRed;
   488                                     break;
   489                                 case pEpColor.pEpColorNoColor:
   490                                     item.ItemImage = imageNoColor;
   491                                     break;
   492                                 default:
   493                                     item.ItemImage = null;
   494                                     break;
   495                             }
   496                         }
   497 
   498                         // Set button
   499                         if ((isMyself) ||
   500                             ((partnerIdentity.IsForceUnencryptedBool) &&
   501                              (managerState.IsIncoming == false)))
   502                         {
   503                             item.IsButtonVisible = false;
   504                             item.IsExpandable = false;
   505                         }
   506                         else
   507                         {
   508                             if (partnerIdentityColor == pEpColor.pEpColorGreen)
   509                             {
   510                                 // Undo handshake
   511                                 item.TextButton = pEp.Properties.Resources.PrivacyStatus_StopTrusting;
   512                                 item.IsButtonVisible = true;
   513                                 item.ButtonOnClick = (x, y) => { this.UndoHandshake(partnerIdentity.Copy()); };
   514 
   515                                 item.IsExpandable = false;
   516                             }
   517                             else if (partnerIdentityColor == pEpColor.pEpColorYellow)
   518                             {
   519                                 // Do handshake
   520                                 item.TextButton = pEp.Properties.Resources.PrivacyStatus_Handshake;
   521                                 item.IsButtonVisible = true;
   522                                 item.ButtonOnClick = (x, y) => { this.DoHandshake(myIdentity.Copy(), partnerIdentity.Copy()); };
   523 
   524                                 item.IsExpandable = true;
   525                                 item.ExpandedText = pEp.Properties.Resources.Handshake_ConfirmTrustwordsText + "\n\n" +
   526                                                     trustwordsShort;
   527 
   528                                 item.ExpandedButton1Text = pEp.Properties.Resources.Handshake_ConfirmText;
   529                                 item.ExpandedButton1OnClick = (x, y) => { this.ProcessDoHandshakeResult(DialogResult.Yes, partnerIdentity.Copy()); };
   530                                 item.ExpandedButton1Style = (Style)Globals.ResourceDict["StyleConfirmButton"];
   531 
   532                                 item.ExpandedButton2Text = pEp.Properties.Resources.Handshake_CancelText;
   533                                 item.ExpandedButton2OnClick = (x, y) =>
   534                                     {
   535                                         SelectionItem selItem;
   536                                         FormControlManagePrivacyStatus.State activeState;
   537 
   538                                         if (this.managerForm != null)
   539                                         {
   540                                             activeState = this.managerForm.FormControl.DisplayState;
   541 
   542                                             if ((index >= 0) &&
   543                                                 (index < activeState.Identities.Count))
   544                                             {
   545                                                 selItem = activeState.Identities[index];
   546                                                 selItem.TextButton = pEp.Properties.Resources.PrivacyStatus_Handshake;
   547                                                 selItem.IsExpanded = false;
   548                                                 selItem.IsExpandable = false;
   549                                             }
   550                                         }
   551                                     };
   552                                 item.ExpandedButton2Style = (Style)Globals.ResourceDict["StyleCancelButton"];
   553 
   554                                 item.ExpandedButton3Text = pEp.Properties.Resources.Handshake_WrongText;
   555                                 item.ExpandedButton3OnClick = (x, y) => { this.ProcessDoHandshakeResult(DialogResult.No, partnerIdentity.Copy()); };
   556                                 item.ExpandedButton3Style = (Style)Globals.ResourceDict["StyleWrongButton"];
   557                             }
   558                             else if (partnerIdentityColor == pEpColor.pEpColorRed)
   559                             {
   560                                 // Redo handshake with confirmation
   561                                 item.TextButton = pEp.Properties.Resources.PrivacyStatus_Handshake;
   562                                 item.IsButtonVisible = true;
   563                                 item.ButtonOnClick = (x, y) => { this.DoHandshakeForMistrustedKey(myIdentity.Copy(), partnerIdentity.Copy()); };
   564 
   565                                 item.IsExpandable = false;
   566                             }
   567                             else
   568                             {
   569                                 item.IsButtonVisible = false;
   570                                 item.IsExpandable = false;
   571                             }
   572                         }
   573                     }
   574                     else // Invalid identity
   575                     {
   576                         item = new SelectionItem();
   577                         item.TextLine1 = ident.UserName;
   578                         item.TextLine2 = ident.Address;
   579                         item.ItemImage = null;
   580                         item.IsTwoTextLinesVisible = true;
   581                         item.IsButtonVisible = false;
   582                     }
   583 
   584                     managerState.Identities.Add(item);
   585                     currIndex++;
   586                 }
   587 
   588                 // Attempt to select and expand the first identity requiring a handshake
   589                 for (int i = 0; i < managerState.Identities.Count; i++)
   590                 {
   591                     // Determine if it's a handshake identity by checking if it's expandable
   592                     if (managerState.Identities[i].IsExpandable)
   593                     {
   594                         managerState.Identities[i].TextButton = Properties.Resources.PrivacyStatus_HandshakeAdvanced;
   595                         managerState.Identities[i].IsExpanded = true;
   596                         managerState.SelectedIdentityIndex = i;
   597                         break;
   598                     }
   599                 }
   600             }
   601 
   602             return (managerState);
   603         }
   604 
   605         /// <summary>
   606         /// Builds the latest state of the encryption status manager then shows the UI.
   607         /// </summary>
   608         private void BuildAndShowManager()
   609         {
   610             DialogResult result;
   611             FormManagePrivacyStatus form;
   612 
   613             try
   614             {
   615                 // Show the form
   616                 form = new FormManagePrivacyStatus();
   617                 form.StartPosition = FormStartPosition.CenterParent;
   618                 form.FormClosed += ManagerForm_FormClosed;
   619                 form.FormControl.DisplayState = this.GetManagerState();
   620 
   621                 this.managerForm = form;
   622                 result = form.ShowDialog(this.ParentForm);
   623             }
   624             catch (Exception ex)
   625             {
   626                 Globals.StopAndSendCrashReport(ex);
   627             }
   628 
   629             return;
   630         }
   631 
   632         /// <summary>
   633         /// Schedules for the pEp rating and UI (including displayed mirror) to be updated.
   634         /// This can be called many times with no issue as the update is only run every n milliseconds.
   635         /// </summary>
   636         public void RequestRatingAndUIUpdate()
   637         {
   638             this.TimerRefresh.Enabled = true;
   639             return;
   640         }
   641 
   642         /// <summary>
   643         /// Immediately starts the update of UI rating based on the associated mail item.
   644         /// This will start decryption as necessary and also will update the displayed mirror.
   645         /// This method by-passes the refresh timer completely.
   646         /// WARNING: This method assumes the message is fully downloaded already.
   647         /// </summary>
   648         private void ImmediateRatingAndUIUpdate()
   649         {
   650             if (this.associatedMailItem != null)
   651             {
   652                 Log.Verbose("ImmediateRatingAndUIUpdate: Starting processing.");
   653 
   654                 // Start the rating calculation/decryption process
   655                 this.associatedMailItem.StartProcessing();
   656             }
   657 
   658             return;
   659         }
   660 
   661         /// <summary>
   662         /// Immediately update the manage privacy status form display state.
   663         /// This by default will completely rebuild the display state.
   664         /// </summary>
   665         /// <param name="onlyRating">True to update only the rating, false to rebuild the entire display state.</param>
   666         private void UpdateManagePrivacyStatusForm(bool onlyRating = false)
   667         {
   668             if (this.managerForm != null)
   669             {
   670                 if (onlyRating)
   671                 {
   672                     // Only update the message rating
   673                     this.managerForm.FormControl.DisplayState.Rating = this.FormControlPrivacyStatusChild.DisplayState.Rating;
   674                 }
   675                 else
   676                 {
   677                     // Rebuild the entire display state which will update any identity changes
   678                     this.managerForm.FormControl.DisplayState = this.GetManagerState();
   679                 }
   680             }
   681 
   682             return;
   683         }
   684 
   685         /// <summary>
   686         /// Clears the associated unencrypted preview and displays the given note (if any).
   687         /// </summary>
   688         /// <param name="note">The note to diplsay.</param>
   689         private void ClearPreview(string note = null)
   690         {
   691             WindowFormRegionCollection formRegions = Globals.FormRegions[Globals.ThisAddIn.Application.ActiveWindow()];
   692 
   693             if ((formRegions != null) &&
   694                 (formRegions.FormRegionPreviewUnencrypted != null) &&
   695                 (formRegions.FormRegionPreviewUnencrypted.Visible))
   696             {
   697                 formRegions.FormRegionPreviewUnencrypted.ClearMessage();
   698                 formRegions.FormRegionPreviewUnencrypted.SetNote(note);
   699             }
   700 
   701             return;
   702         }
   703 
   704         /**************************************************************
   705          * 
   706          * Event Handling
   707          * 
   708          *************************************************************/
   709 
   710         /// <summary>
   711         /// Event handler that is called when the form region is displayed.
   712         /// This is called each time the form region looses then regains visibility 
   713         /// (for example an other email is selected then back to this one).
   714         /// </summary>
   715         private void FormRegionPrivacyStatus_FormRegionShowing(object sender, System.EventArgs e)
   716         {
   717             bool setByCache = false;
   718             PEPIdentity currIdent;
   719             Globals.ReturnStatus sts;
   720             Outlook.MailItem omi = null;
   721             Outlook.Recipient currUser = null;
   722             Outlook.Account currAccount = null;
   723             Outlook.Account sendingAccount = null;
   724             Outlook.NameSpace ns = Globals.ThisAddIn.Application.Session;
   725 
   726             // Do not allow initialization more than once
   727             if (initialized == false)
   728             {
   729                 this.SetAssociatedMailItem();
   730 
   731                 /* It's possible for new draft MailItems to be created outside the context of an account.
   732                  * In this situation the SendUsingAccount will always be null which of course breaks several pEp operations.
   733                  * The operations themselves cannot make the assumption about what account information to use.
   734                  * Therefore, the situation is detected here and for draft mail items a null SendUsingAccount will be 
   735                  * set with the session's default account.
   736                  */
   737                 try
   738                 {
   739                     if ((this.OutlookItem is Outlook.MailItem) &&
   740                         ((Outlook.MailItem)this.OutlookItem).GetIsDraft())
   741                     {
   742                         omi = this.OutlookItem as Outlook.MailItem;
   743                         sendingAccount = omi.SendUsingAccount;
   744                         currUser = ns.CurrentUser;
   745 
   746                         if (sendingAccount == null)
   747                         {
   748                             sts = PEPIdentity.Create(currUser, out currIdent);
   749 
   750                             if (sts == Globals.ReturnStatus.Success)
   751                             {
   752                                 currAccount = PEPIdentity.GetOwnAccount(currIdent.Address);
   753                                 omi.SendUsingAccount = currAccount;
   754                             }
   755                         }
   756                     }
   757                 }
   758                 catch { }
   759                 finally
   760                 {
   761                     if (omi != null)
   762                     {
   763                         Marshal.ReleaseComObject(omi);
   764                         omi = null;
   765                     }
   766 
   767                     if (currUser != null)
   768                     {
   769                         Marshal.ReleaseComObject(currUser);
   770                         currUser = null;
   771                     }
   772 
   773                     if (currAccount != null)
   774                     {
   775                         Marshal.ReleaseComObject(currAccount);
   776                         currAccount = null;
   777                     }
   778 
   779                     if (sendingAccount != null)
   780                     {
   781                         Marshal.ReleaseComObject(sendingAccount);
   782                         sendingAccount = null;
   783                     }
   784 
   785                     if (ns != null)
   786                     {
   787                         Marshal.ReleaseComObject(ns);
   788                         ns = null;
   789                     }
   790                 }
   791 
   792                 // Set reader upgrade link visibility
   793                 if ((Globals.RELEASE_MODE == Globals.ReleaseMode.Reader) &&
   794                     (this.associatedMailItem != null) &&
   795                     (this.associatedMailItem.IsDraft))
   796                 {
   797                     this.FormControlPrivacyStatusChild.DisplayState.UpgradeLinkIsVisible = true;
   798                 }
   799                 else
   800                 {
   801                     this.FormControlPrivacyStatusChild.DisplayState.UpgradeLinkIsVisible = false;
   802                 }
   803 
   804                 if (this.associatedMailItem != null)
   805                 {
   806                     // Connect cryptable mail item events
   807                     try
   808                     {
   809                         this.associatedMailItem.PropertyChanged += MailItem_PropertyChanged;
   810                         this.associatedMailItem.ProcessingCompleted += MailItem_ProcessingCompleted;
   811                         this.associatedMailItem.GetMirrorCompleted += MailItem_GetMirrorCompleted;
   812                         this.associatedMailItem.Send += MailItem_Send;
   813 
   814                         if (this.associatedMailItem.IsSecurelyStored)
   815                         {
   816                             this.associatedMailItem.Open += MailItem_Open;
   817                         }
   818                     }
   819                     catch { }
   820 
   821                     /* For a MailItem being forwarded or replied to it's import to check against the encrypted conversation cache.
   822                      * This is normally done within CryptableMailItem_EncryptedConversationCacheUpdated; however,
   823                      * for trusted servers that use an in-line reponse, the EncryptedConversationCacheUpdated event occurs
   824                      * BEFORE the new mail item is created. This means it never sees the event.
   825                      * To get around this issue, when the form region is first shown, also check if the mail item
   826                      * exists in the encrypted conversation cache and should be marked as formerly encrypted.
   827                      * If this check passes, never event connect the CryptableMailItem_EncryptedConversationCacheUpdated event.
   828                      */
   829                     setByCache = this.associatedMailItem.SetIsOriginallyEncryptedByCache();
   830                 }
   831 
   832                 // Connect events
   833                 if (setByCache == false) { CryptableMailItem.EncryptedConversationCacheUpdated += CryptableMailItem_EncryptedConversationCacheUpdated; }
   834                 this.FormControlPrivacyStatusChild.PrivacyViewClick += FormControlPrivacyStatusChild_PrivacyViewClick;
   835 
   836                 this.TimerRefresh.Tick += TimerRefresh_Tick;
   837                 this.initialized = true;
   838             }
   839 
   840             // Call the timer tick method manually to refresh data with no delay
   841             this.TimerRefresh_Tick(null, new EventArgs());
   842 
   843             return;
   844         }
   845 
   846         /// <summary>
   847         /// Event handler for when the form region is closed.
   848         /// </summary>
   849         private void FormRegionPrivacyStatus_FormRegionClosed(object sender, System.EventArgs e)
   850         {
   851             // Disconnect cryptable mail item events
   852             if (this.associatedMailItem != null)
   853             {
   854                 try
   855                 {
   856                     this.associatedMailItem.PropertyChanged -= MailItem_PropertyChanged;
   857                     this.associatedMailItem.ProcessingCompleted -= MailItem_ProcessingCompleted;
   858                     this.associatedMailItem.GetMirrorCompleted -= MailItem_GetMirrorCompleted;
   859                     this.associatedMailItem.Open -= MailItem_Open;
   860                     this.associatedMailItem.Send -= MailItem_Send;
   861                 }
   862                 catch { }
   863             }
   864 
   865             // Disconnect events
   866             CryptableMailItem.EncryptedConversationCacheUpdated -= CryptableMailItem_EncryptedConversationCacheUpdated;
   867             this.FormControlPrivacyStatusChild.PrivacyViewClick -= FormControlPrivacyStatusChild_PrivacyViewClick;
   868 
   869             // Stop and disconnect the refresh timer
   870             this.TimerRefresh.Stop();
   871             this.TimerRefresh.Enabled = false;
   872             this.TimerRefresh.Tick -= TimerRefresh_Tick;
   873 
   874             return;
   875         }
   876 
   877         /// <summary>
   878         /// Event handler called after the refresh timer has elapsed.
   879         /// </summary>
   880         private void TimerRefresh_Tick(object sender, EventArgs e)
   881         {
   882             bool tryAgain = false;
   883             bool markForDownload = false;
   884             this.TimerRefresh.Enabled = false; // Only once
   885             Outlook.OlDownloadState dlState;
   886 
   887             /* The Refresh/UI_Update process is a little more complicated here than other forms.
   888              * There are the following components:
   889              *   1. TimerRefresh_Tick
   890              *        This is the main timer tick event handler called any time
   891              *        a refresh was requested (RequestRatingAndUIUpdate) and hasn't been run yet. 
   892              *        A refresh is requested either at initialization or when a property changes
   893              *        (such as MailItem_PropertyChanged). It is on a timer as many 
   894              *        property change events could occur rapidy, but only one refresh should
   895              *        occur for performance reasons.
   896              * 
   897              *   2. ImmediateRatingAndUIUpdate
   898              *        This will re-calculate the mail item's rating.
   899              *        This internally just calls CryptableMailItem.StartProcessing.
   900              * 
   901              *   3. MailItem_ProcessingCompleted
   902              *        When processing of the mail item is complete, this even handler will be called.
   903              *        This will update the UI with the latest rating then call StartGetMirror.
   904              *        The CopyStateToUI method is used which means any open privacy status form will also
   905              *        be updated.
   906              * 
   907              *   4. MailItem_GetMirrorComplete
   908              *        This is the final step in updating the UI, after a mirror is located, it's contents
   909              *        will be shown in the unencrypted preview.
   910              * 
   911              * The general calling sequence is as shown above 1->4 with each component calling the next.
   912              * However, for methods that update the mail item directly, commonly only 2->4 is needed.
   913              */
   914 
   915             // Ensure the tick method is not called more than once
   916             if (refreshOngoing == false)
   917             {
   918                 this.refreshOngoing = true;
   919 
   920                 if (this.associatedMailItem != null)
   921                 {
   922                     // Attempt to get the download state
   923                     try
   924                     {
   925                         dlState = this.associatedMailItem.DownloadState;
   926                     }
   927                     catch (Exception ex)
   928                     {
   929                         Log.Warning("TimerRefresh_Tick: Get DownloadState failed, " + ex.ToString());
   930 
   931                         // Assume everything is downloaded, but try to download again as well
   932                         dlState = Outlook.OlDownloadState.olFullItem;
   933                         markForDownload = true;
   934                     }
   935 
   936                     if (dlState == Outlook.OlDownloadState.olFullItem)
   937                     {
   938                         this.ImmediateRatingAndUIUpdate();
   939                     }
   940                     else
   941                     {
   942                         markForDownload = true;
   943                     }
   944 
   945                     if (markForDownload)
   946                     {
   947                         // Try to mark the message for full download
   948                         try
   949                         {
   950                             this.associatedMailItem.MarkForDownload = Outlook.OlRemoteStatus.olMarkedForDownload;
   951                             tryAgain = true;
   952                         }
   953                         catch (Exception ex)
   954                         {
   955                             Log.Warning("TimerRefresh_Tick: MarkForDownload failed, " + ex.ToString());
   956                         }
   957                     }
   958                 }
   959 
   960                 // Set the timer to refresh again later automatically
   961                 if (tryAgain)
   962                 {
   963                     this.TimerRefresh.Interval = 100;
   964                     this.TimerRefresh.Enabled = true;
   965                 }
   966 
   967                 this.refreshOngoing = false;
   968             }
   969 
   970             return;
   971         }
   972 
   973         /// <summary>
   974         /// Event handler for when the processing is completed in the associated mail item.
   975         /// This will then update the form region UI and the privacy status window as needed.
   976         /// </summary>
   977         private void MailItem_ProcessingCompleted(object sender, CryptableMailItem.ProcessingCompletedEventArgs e)
   978         {
   979             Log.Verbose("MailItem_ProcessingComplete: Decryption completed.");
   980 
   981             try
   982             {
   983                 // Marshal code back to UI thread as necessary
   984                 this.Invoke(new Action(() =>
   985                     {
   986                         this.FormControlPrivacyStatusChild.DisplayState.Rating = e.Rating;
   987                         this.UpdateManagePrivacyStatusForm(true); // Only update the rating
   988 
   989                         /* Create the unencrypted preview if the mail item is encrypted and
   990                          * it is in an encrypted (untrusted) store
   991                          * 
   992                          * This is done here because FormRegionPrivacyStatus has the cryptable mail item and
   993                          * it also is initialized after FormRegionPreviewUnencrypted.
   994                          */
   995                         try
   996                         {
   997                             if (this.associatedMailItem.IsSecurelyStored)
   998                             {
   999                                 Log.Verbose("MailItem_ProcessingComplete: Starting mirror location.");
  1000                                 this.associatedMailItem.StartGetMirror();
  1001                             }
  1002                             else
  1003                             {
  1004                                 this.ClearPreview();
  1005                             }
  1006                         }
  1007                         catch (Exception ex)
  1008                         {
  1009                             // Error is possible in some situations where the mail item was deleted or moved while decryption was ongoing.
  1010                             // While rare, just log the issue and stop the process
  1011                             Log.Warning("MailItem_ProcessingComplete: Failed to start mirror location, " + ex.ToString());
  1012                         }
  1013                     }));
  1014             }
  1015             catch (Exception ex)
  1016             {
  1017                 Log.Error("MailItem_ProcessingComplete: Error setting UI state, " + ex.ToString());
  1018             }
  1019 
  1020             return;
  1021         }
  1022 
  1023         /// <summary>
  1024         /// Event handler for when the get mirror locating process is complete for the associated mail item.
  1025         /// This will then update the unencrypted preview in the UI.
  1026         /// </summary>
  1027         private void MailItem_GetMirrorCompleted(object sender, CryptableMailItem.GetMirrorCompletedEventArgs e)
  1028         {
  1029             try
  1030             {
  1031                 // Marshal code back to UI thread as necessary
  1032                 this.Invoke(new Action(() =>
  1033                     {
  1034                         WindowFormRegionCollection formRegions = Globals.FormRegions[Globals.ThisAddIn.Application.ActiveWindow()];
  1035 
  1036                         if ((e.Mirror == null) &&
  1037                             (this.associatedMailItem.LastProcessingStatus == Globals.ReturnStatus.Failure))
  1038                         {
  1039                             this.ClearPreview(Properties.Resources.Message_OpenError);
  1040                             Log.Verbose("MailItem_GetMirrorComplete: Cannot display mirror, failure during decryption.");
  1041                         }
  1042                         else if ((e.Mirror == null) &&
  1043                                  (this.associatedMailItem.LastProcessingStatus == Globals.ReturnStatus.FailureNoConnection))
  1044                         {
  1045                             this.ClearPreview(Properties.Resources.Message_DecryptionNoConnection);
  1046                             Log.Verbose("MailItem_GetMirrorComplete: Cannot display mirror, connection failure during decryption.");
  1047                         }
  1048                         else
  1049                         {
  1050                             if ((formRegions != null) &&
  1051                                 (formRegions.FormRegionPreviewUnencrypted != null) &&
  1052                                 (formRegions.FormRegionPreviewUnencrypted.Visible))
  1053                             {
  1054                                 formRegions.FormRegionPreviewUnencrypted.SetMessage(e.Mirror);
  1055                                 Log.Verbose("MailItem_GetMirrorComplete: Mirror found and displayed.");
  1056                             }
  1057                         }
  1058 
  1059                         // Display the mirror if necessary
  1060                         if ((this.associatedMailItem != null) &&
  1061                             (this.displayMirrorRequested))
  1062                         {
  1063                             this.associatedMailItem.DisplayMirror();
  1064                             this.displayMirrorRequested = false;
  1065                         }
  1066                     }));
  1067             }
  1068             catch (Exception ex)
  1069             {
  1070                 Log.Error("MailItem_GetMirrorComplete: Error displaying preview, " + ex.ToString());
  1071             }
  1072 
  1073             return;
  1074         }
  1075 
  1076         /// <summary>
  1077         /// Event handler for the static mail item encrypted conversation cache updated.
  1078         /// This event is fired after the conversation cache has been updated with parent information following a
  1079         /// forward or reply mail item event (on a different, likely parent, mail item).
  1080         /// </summary>
  1081         private void CryptableMailItem_EncryptedConversationCacheUpdated(object sender, EventArgs e)
  1082         {
  1083             // Process the mail item now that the cache is updated
  1084             this.associatedMailItem.SetIsOriginallyEncryptedByCache();
  1085 
  1086             // Remove the event handler -- this can only be processed once because it's timing dependent
  1087             CryptableMailItem.EncryptedConversationCacheUpdated -= CryptableMailItem_EncryptedConversationCacheUpdated;
  1088 
  1089             return;
  1090         }
  1091 
  1092         /// <summary>
  1093         /// Event handler for when a mail item is being opened in an inspector.
  1094         /// See: https://msdn.microsoft.com/en-us/library/office/ff865989.aspx
  1095         /// </summary>
  1096         /// <param name="cancel">Whether to cancel the event: Value is False when the event occurs. 
  1097         /// If the event procedure sets this argument to True, the open operation is not completed 
  1098         /// and the inspector is not displayed.</param>
  1099         private void MailItem_Open(ref bool cancel)
  1100         {
  1101             bool result;
  1102 
  1103             if (this.associatedMailItem != null &&
  1104                 this.associatedMailItem.IsSecurelyStored)
  1105             {
  1106                 // Try to open the mirror
  1107                 result = this.associatedMailItem.DisplayMirror();
  1108 
  1109                 if (result == false)
  1110                 {
  1111                     // Set flag to open after decryption/mirror location
  1112                     this.displayMirrorRequested = true;
  1113                 }
  1114 
  1115                 // Always cancel opening the original
  1116                 cancel = true;
  1117             }
  1118 
  1119             return;
  1120         }
  1121 
  1122         /// <summary>
  1123         /// Event handler for when a mail item is sent.
  1124         /// See: https://msdn.microsoft.com/en-us/library/office/ff865379.aspx
  1125         /// </summary>
  1126         /// <param name="cancel">Whether to cancel the event: Value is False when the event occurs. 
  1127         /// If the event procedure sets this argument to True, the send operation is not completed 
  1128         /// and the inspector is left open.</param>
  1129         private void MailItem_Send(ref bool cancel)
  1130         {
  1131             DialogResult result;
  1132 
  1133             if ((Globals.ThisAddIn.Settings.IsSecurityLossWarningEnabled) &&
  1134                 (this.associatedMailItem.IsOriginallyEncrypted) &&
  1135                 (this.FormControlPrivacyStatusChild.DisplayState.Rating < pEpRating.pEpRatingUnreliable))
  1136             {
  1137 
  1138 #if READER_RELEASE_MODE
  1139                 FormReaderSplash warningMessage = new FormReaderSplash(true);
  1140                 result = warningMessage.ShowDialog();
  1141 
  1142                 if (result != DialogResult.OK)
  1143                 {
  1144                     // Cancel sending
  1145                     cancel = true;
  1146                 }
  1147 #else
  1148                 result = System.Windows.Forms.MessageBox.Show(this.ParentForm,
  1149                                                               pEp.Properties.Resources.Message_WarningSecurityLoss,
  1150                                                               pEp.Properties.Resources.Message_TitleConfirmOperation,
  1151                                                               MessageBoxButtons.YesNo,
  1152                                                               MessageBoxIcon.Warning);
  1153 
  1154                 if (result == DialogResult.No)
  1155                 {
  1156                     // Cancel sending
  1157                     cancel = true;
  1158                 }
  1159 #endif
  1160             }
  1161 
  1162             // Stop and disconnect the refresh timer
  1163             // This is necessary so an ongoing refresh doesn't try to access a mail item as it's being moved
  1164             if (cancel == false)
  1165             {
  1166                 this.TimerRefresh.Stop();
  1167                 this.TimerRefresh.Enabled = false;
  1168                 this.TimerRefresh.Tick -= TimerRefresh_Tick;
  1169             }
  1170 
  1171             return;
  1172         }
  1173 
  1174         /// <summary>
  1175         /// Event handler for when a mail item property is changed.
  1176         /// See: https://msdn.microsoft.com/en-us/library/office/ff866739.aspx
  1177         /// </summary>
  1178         /// <param name="propertyName">The name of the property that was changed.</param>
  1179         private void MailItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
  1180         {
  1181             switch (e.PropertyName.ToUpper())
  1182             {
  1183                 case "TO":
  1184                     this.RequestRatingAndUIUpdate();
  1185                     break;
  1186                     // Outlook bug: there are always both events, so one is enough
  1187                     //case "CC":
  1188                     //    this.RequestRatingAndUIUpdate();
  1189                     //    break;
  1190             }
  1191 
  1192             return;
  1193         }
  1194 
  1195         /// <summary>
  1196         /// Event handler for when the manager form is closed.
  1197         /// </summary>
  1198         private void ManagerForm_FormClosed(object sender, FormClosedEventArgs e)
  1199         {
  1200             // Remove the reference held in the FormRegionPrivacyStatus so it stops getting updated
  1201             if (this.managerForm != null)
  1202             {
  1203                 this.managerForm.FormClosed -= ManagerForm_FormClosed;
  1204                 this.managerForm = null;
  1205             }
  1206 
  1207             return;
  1208         }
  1209 
  1210         /// <summary>
  1211         /// Event handler for when the pEp website hyperlink is clicked.
  1212         /// </summary>
  1213         private void LinkLabelUpgrade_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  1214         {
  1215             try
  1216             {
  1217                 Process.Start(Globals.PEP_WEBSITE_UPGRADE_LINK);
  1218             }
  1219             catch (Exception ex)
  1220             {
  1221                 Log.Error("LinkLabelUpgrade_LinkClicked: Unable to open website link, " + ex.ToString());
  1222             }
  1223 
  1224             return;
  1225         }
  1226 
  1227         /// <summary>
  1228         /// Event handler for when the privacy view button is clicked within the form control.
  1229         /// </summary>
  1230         private void FormControlPrivacyStatusChild_PrivacyViewClick(object sender, System.Windows.RoutedEventArgs e)
  1231         {
  1232             this.RequestRatingAndUIUpdate();
  1233             this.BuildAndShowManager();
  1234             return;
  1235         }
  1236     }
  1237 }