DecryptionStack.cs
author Thomas
Fri, 24 Aug 2018 16:20:59 +0200
changeset 2347 2842144ab3ac
parent 2324 14b4d780a124
child 2399 37b53db9900e
child 2425 9e1f0bf414e3
permissions -rw-r--r--
Update version to 1.0.114
     1 ´╗┐using System;
     2 using System.Collections.Generic;
     3 using Outlook = Microsoft.Office.Interop.Outlook;
     4 
     5 namespace pEp
     6 {
     7     internal class DecryptionStack : Stack<string>, IDisposable
     8     {
     9         private const int MAX_DECRYPTION_COUNT                       = 5;       // Maximum amount of concurrently decrypted items
    10         private const int DECRYPTION_INTERVAL                        = 500;     // in ms
    11 
    12         private System.Windows.Forms.Timer decryptionTimer           = null;
    13         private int decryptionCounter                                = 0;
    14         private object mutexDecryptionCounter                        = new object();
    15         private object mutexDecryptionStack                          = new object();
    16 
    17         /// <summary>
    18         /// List to store already copied items to prevent unnecessary processing
    19         /// </summary>
    20         private List<string> copiedItemsList = new List<string>();
    21 
    22         /// <summary>
    23         /// Default constructor.
    24         /// </summary>
    25         public DecryptionStack()
    26         {
    27             this.decryptionTimer = new System.Windows.Forms.Timer();
    28             this.decryptionTimer.Interval = DECRYPTION_INTERVAL;
    29             this.decryptionTimer.Tick += this.DecryptionTimer_Tick;
    30         }
    31 
    32         /// <summary>
    33         /// Implementing IDisposable
    34         /// </summary>
    35         public void Dispose()
    36         {
    37             this.decryptionTimer.Dispose();
    38         }
    39 
    40         /// <summary>
    41         /// Increments the decryption counter if maximum has not yet been reached.
    42         /// </summary>
    43         /// <returns>True if counter was encrypted, false if maximum has already been reached.</returns>
    44         private bool IncrementDecryptionCounter()
    45         {
    46             bool incremented = false;
    47 
    48             lock (mutexDecryptionCounter)
    49             {
    50                 if (this.decryptionCounter < MAX_DECRYPTION_COUNT)
    51                 {
    52                     this.decryptionCounter++;
    53                     incremented = true;
    54                     Log.Verbose("DecryptionStack.IncrementDecryptionCounter: counter incremented to " + decryptionCounter);
    55                 }
    56             }
    57 
    58             return incremented;
    59         }
    60 
    61         /// <summary>
    62         /// Decrements the decryption counter if it is bigger than 0.
    63         /// </summary>
    64         public void DecrementDecryptionCounter()
    65         {
    66             lock (mutexDecryptionCounter)
    67             {
    68                 if (this.decryptionCounter > 0)
    69                 {
    70                     this.decryptionCounter--;
    71                     Log.Verbose("DecryptionStack.DecrementDecryptionCounter: counter decremented to " + decryptionCounter);
    72                 }
    73             }
    74         }
    75 
    76         /// <summary>
    77         /// Resets the decryption counter to 0.
    78         /// </summary>
    79         private void ResetDecryptionCounter()
    80         {
    81             lock (mutexDecryptionCounter)
    82             {
    83                 this.decryptionCounter = 0;
    84                 Log.Verbose("DecryptionStack.ResetDecryptionCounter: counter reset");
    85             }
    86         }
    87 
    88         /// <summary>
    89         /// Locks the decryption stack and tries to get its first element.
    90         /// Also checks if the following element(s) are duplicates of the one
    91         /// returned and removes them in this case.
    92         /// </summary>
    93         /// <param name="entryId">The retrieved entryId or an empty string if none was retrieved.</param>
    94         /// <returns>Tue if the operation retrieved successfully an entryId. Otherwise false.</returns>
    95         private bool TryPop(out string entryId)
    96         {
    97             entryId = null;
    98 
    99             lock (mutexDecryptionStack)
   100             {
   101                 try
   102                 {
   103                     if (this.Count > 0)
   104                     {
   105                         entryId = this.Pop();
   106                         Log.Verbose("DecryptionStack.TryPop: retrieved entryId " + entryId);
   107                     }
   108                 }
   109                 catch (Exception ex)
   110                 {
   111                     entryId = string.Empty;
   112                     Log.Error("TryPopDecryptionStack: Error getting first element from stack. " + ex.ToString());
   113                 }
   114 
   115                 // If an entryId could be retrieved, check following item(s)
   116                 if (string.IsNullOrEmpty(entryId) == false)
   117                 {
   118                     do
   119                     {
   120                         if (this.Count > 0)
   121                         {
   122                             string nextEntryId = null;
   123 
   124                             try
   125                             {
   126                                 nextEntryId = this.Peek();
   127                             }
   128                             catch (Exception ex)
   129                             {
   130                                 Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
   131                             }
   132 
   133                             // If next item equals the retrieved entryId, remove it
   134                             if (string.Equals(entryId, nextEntryId))
   135                             {
   136                                 try
   137                                 {
   138                                     this.Pop();
   139                                 }
   140                                 catch (Exception ex)
   141                                 {
   142                                     Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
   143                                     break;
   144                                 }
   145                             }
   146                             else
   147                             {
   148                                 break;
   149                             }
   150                         }
   151                         else
   152                         {
   153                             break;
   154                         }
   155 
   156                     } while (true);
   157                 }
   158             }
   159 
   160             return (string.IsNullOrEmpty(entryId) == false);
   161         }
   162 
   163         /// <summary>
   164         /// Locks the decryption stack and tries to push the given entryId.
   165         /// The item is not pushed if the last entryId is the same.
   166         /// </summary>
   167         /// <param name="entryId">The entryId to push to the stack.</param>
   168         /// <returns>True if item was pushed, otherwise false.</returns>
   169         public bool TryPush(string entryId)
   170         {
   171             bool pushed = false;
   172             string currentId = null;
   173 
   174             if (string.IsNullOrEmpty(entryId))
   175             {
   176                 return false;
   177             }
   178 
   179             // Push item to stack if not already on top of it
   180             lock (mutexDecryptionStack)
   181             {
   182                 if (this.Count > 0)
   183                 {
   184                     try
   185                     {
   186                         currentId = this.Peek();
   187                     }
   188                     catch (Exception ex)
   189                     {
   190                         Log.Error("DecryptionStack.TryPush: Error peeking item. " + ex.ToString());
   191                     }
   192                 }
   193 
   194                 if (entryId?.Equals(currentId) == false)
   195                 {
   196                     this.Push(entryId);
   197                     pushed = true;
   198                     Log.Verbose("DecryptionStack.TryPush: pushed entryId " + entryId);
   199                 }
   200             }
   201 
   202             // If item was pushed, enable timer and start decryption
   203             if (pushed &&
   204                 this.decryptionTimer.Enabled == false)
   205             {
   206                 this.decryptionTimer.Start();
   207                 this.DecryptionTimer_Tick(null, null);
   208                 Log.Verbose("DecryptionStack.TryPush: decryptionTimer started");
   209             }
   210 
   211             return pushed;
   212         }
   213 
   214         /// <summary>
   215         /// Event handler for when the decryption timer has elapsed.
   216         /// </summary>
   217         private void DecryptionTimer_Tick(object sender, EventArgs e)
   218         {
   219             Outlook.MailItem omi = null;
   220             Outlook.MailItem omiCopy = null;
   221             string entryId = null;
   222             CryptableMailItem cmi = null;
   223 
   224             // Try to increment the decryption counter
   225             if (this.IncrementDecryptionCounter() == false)
   226             {
   227                 Log.Verbose("DecryptionStack.DecryptionTimer_Tick: decryption queue full. Items in queue: " + this.Count);
   228                 return;
   229             }
   230 
   231             // Pop item from the decryption stack
   232             if (this.TryPop(out entryId))
   233             {
   234                 // Get mail item and set process flag to true
   235                 try
   236                 {
   237                     omi = Globals.ThisAddIn.Application.Session.GetItemFromID(entryId);
   238                 }
   239                 catch (Exception ex)
   240                 {
   241                     omi = null;
   242                     Log.Error("DecryptionTimer_Tick: Error getting item from ID. " + ex.ToString());
   243                 }
   244 
   245                 bool isSMIMEEnabled = omi.GetIsSMIMEEnabled();
   246                 if ((omi != null) &&
   247                     (omi.GetIsPEPEnabled() || omi.GetIsDecryptAlwaysEnabled()) &&
   248                     (isSMIMEEnabled == false))
   249                 {
   250                     /* In Outlook 2010, mails may arrive with download state OlHeaderOnly, which means we have no attachments at this point.
   251                      * Setting the MarkForDownload can lead to Outlook crashing. Therefore, the workaround here is to create a copy of
   252                      * the mail item, which makes Outlook download the full item.
   253                      */
   254                     if ((Globals.OutlookVersion == Globals.Version.Outlook2010) &&
   255                         (omi.GetIsAutoConsume()) &&
   256                         (omi.DownloadState == Outlook.OlDownloadState.olHeaderOnly) &&
   257                         (omi.GetIsInIMAPStore()) &&
   258                         (copiedItemsList.Contains(omi.EntryID) == false))
   259                     {
   260                         try
   261                         {
   262                             omiCopy = omi.Copy();
   263                             omiCopy.UnRead = false;
   264                             omiCopy.SetPEPProperty(MailItemExtensions.PEPProperty.AutoConsume, "yes");
   265                             copiedItemsList.Add(omi.EntryID);
   266                             copiedItemsList.Add(omiCopy.EntryID);
   267                             omiCopy.Save();
   268                             Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Copy OMI: " + omi.EntryID + " to: " + omiCopy.EntryID);
   269                         }
   270                         catch (Exception ex)
   271                         {
   272                             omiCopy = null;
   273                             Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during copying mailitem: " + ex.ToString());
   274                         }
   275                     }
   276 
   277                     /* Forcefully protected messages need a special treatment as it might be needed to open a
   278                      * handshake dialog, which has to happen from the main thread. The constructors of those 
   279                      * dialogs cannot be called from the background worker. 
   280                      */
   281                     if (omi.GetIsForcefullyProtected())
   282                     {
   283                         Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Force Protection Protocol Message found. Processing...");
   284 
   285 
   286                         FPPMessage fppMessage = new FPPMessage(omiCopy ?? omi);
   287 
   288                         try
   289                         {
   290                             omiCopy?.Delete();
   291                         }
   292                         catch (Exception ex)
   293                         {
   294                             Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during deleting copied FPP mailitem: " + ex.ToString());
   295                         }
   296 
   297                         fppMessage.ProcessIncoming();
   298                         if (omi.GetIsAutoConsume())
   299                         {
   300                             omi.UnRead = false;
   301                         }
   302                     }
   303                     else
   304                     {
   305                         // Process item
   306                         cmi = new CryptableMailItem(omiCopy ?? omi, null, true);
   307 
   308                         /* Note: Make sure to dispose the MailItem after processing (pass true).
   309                          * This is needed as decryption is asynchronous but the MailItem reference still needs to be released.
   310                          * Do NOT use omi or cmi references after this point!
   311                          */
   312                         cmi.StartProcessing(true);
   313                         Log.Verbose("DecryptionStack.DecryptionTimer_Tick: started decryption of item with entryId " + omi?.EntryID);
   314                     }
   315 
   316                     omi = null;
   317                     omiCopy = null;
   318                 }
   319                 else
   320                 {
   321                     // If we don't process, decrement the decryption counter again
   322                     this.DecrementDecryptionCounter();
   323                     Log.Verbose("DecryptionStack.DecryptionTimer_Tick: item will not be processed");
   324 
   325                     // Set S/MIME icons if necessary
   326                     if ((isSMIMEEnabled) &&
   327                         (omi.SetEncryptionIcons()))
   328                     {
   329                         try
   330                         {
   331                             omi.Save();
   332                         }
   333                         catch (Exception ex)
   334                         {
   335                             Log.Error("DecryptionTimer_Tick: Error saving mail item after applying encryption icon. " + ex.ToString());
   336                         }
   337                     }
   338                 }
   339             }
   340             else
   341             {
   342                 // If no more item is on the stack, stop the timer and set decryption counter to 0
   343                 if (this.Count <= 0)
   344                 {
   345                     this.ResetDecryptionCounter();
   346                     this.decryptionTimer.Stop();
   347                     Log.Verbose("DecryptionStack.DecryptionTimer_Tick: counter reset and timer stopped");
   348                 }
   349             }
   350         }
   351     }
   352 }