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