PEPIdentity.cs
author Dean
Thu, 08 Sep 2016 12:31:27 +0200
changeset 1250 e219136cec9d
parent 1249 00cc05656f71
child 1251 4fe048bff67e
permissions -rw-r--r--
OUT-87: Complete the new blacklist implementation.
     1 ´╗┐using pEpCOMServerAdapterLib;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Runtime.InteropServices;
     6 using System.Text.RegularExpressions;
     7 using Outlook = Microsoft.Office.Interop.Outlook;
     8 
     9 namespace pEp
    10 {
    11     /// <summary>
    12     /// Class to wrap the pEp engine identity and expose additional functionality.
    13     /// </summary>
    14     internal class PEPIdentity : IEquatable<PEPIdentity>,
    15                                  Interfaces.ICopy<PEPIdentity>
    16     {
    17         private bool?             _IsForceUnencrypted;
    18         private bool              _IsSmartAddressEnabled;
    19         private List<PEPIdentity> _Members;
    20 
    21         private pEp_identity_s internalIdentity;
    22 
    23         /**************************************************************
    24          * 
    25          * Constructors
    26          * 
    27          *************************************************************/
    28 
    29         /// <summary>
    30         /// Default constructor.
    31         /// </summary>
    32         public PEPIdentity()
    33         {
    34             this.internalIdentity = new pEp_identity_s();
    35             this._IsForceUnencrypted = null;
    36             this._IsSmartAddressEnabled = true;
    37             this._Members = new List<PEPIdentity>();
    38         }
    39 
    40         /// <summary>
    41         /// Constructor to build with the given address.
    42         /// </summary>
    43         /// <param name="address">The address of the identity to build with.</param>
    44         public PEPIdentity(string address)
    45         {
    46             this.internalIdentity = new pEp_identity_s();
    47             this.internalIdentity.address = address;
    48             this._IsForceUnencrypted = null;
    49             this._IsSmartAddressEnabled = true;
    50             this._Members = new List<PEPIdentity>();
    51         }
    52 
    53         /// <summary>
    54         /// Constructor to build from a pEp engine identity.
    55         /// </summary>
    56         /// <param name="ident">The identity to build from.</param>
    57         public PEPIdentity(pEp_identity_s ident)
    58         {
    59             this.internalIdentity = ident;
    60             this._IsForceUnencrypted = null;
    61             this._IsSmartAddressEnabled = true;
    62             this._Members = new List<PEPIdentity>();
    63         }
    64 
    65         /**************************************************************
    66          * 
    67          * Property Accessors
    68          * 
    69          *************************************************************/
    70 
    71         /// <summary>
    72         /// Gets or sets the email address of the identity.
    73         /// Note: For get, the address used is the smart address.
    74         /// If smart address is enabled, and the set address is empty/null, the username is 
    75         /// considered the address if it's formatted as an address itself.
    76         /// </summary>
    77         public string Address
    78         {
    79             get
    80             {
    81                 // Make sure to use the smart address
    82                 return (this.GetSmartAddress());
    83             }
    84             set { this.internalIdentity.address = value; }
    85         }
    86 
    87         /// <summary>
    88         /// Gets or sets the communication type with the identity.
    89         /// </summary>
    90         public _pEp_comm_type CommunicationType
    91         {
    92             get { return (this.internalIdentity.comm_type); }
    93             set { this.internalIdentity.comm_type = value; }
    94         }
    95 
    96         /// <summary>
    97         /// Gets or sets the pEp engine fingerprint of the identity.
    98         /// </summary>
    99         public string Fingerprint
   100         {
   101             get { return (this.internalIdentity.fpr); }
   102             set { this.internalIdentity.fpr = value; }
   103         }
   104 
   105         /// <summary>
   106         /// Gets or sets the communication language with the identity.
   107         /// </summary>
   108         public string Language
   109         {
   110             get { return (this.internalIdentity.lang); }
   111             set { this.internalIdentity.lang = value; }
   112         }
   113 
   114         /// <summary>
   115         /// Gets or sets a unique user ID for the identity.
   116         /// </summary>
   117         public string UserID
   118         {
   119             get { return (this.internalIdentity.user_id); }
   120             set { this.internalIdentity.user_id = value; }
   121         }
   122 
   123         /// <summary>
   124         /// Gets or sets the user name of the identity.
   125         /// This is what the identity should be displayed as.
   126         /// </summary>
   127         public string Username
   128         {
   129             get { return (this.internalIdentity.username); }
   130             set { this.internalIdentity.username = value; }
   131         }
   132 
   133         /// <summary>
   134         /// Gets or sets the force unencrypted property of the contact associated 
   135         /// with this identity. The value can be null if there is no contact property.
   136         /// </summary>
   137         public bool? IsForceUnencrypted
   138         {
   139             get { return (this._IsForceUnencrypted); }
   140             set { this._IsForceUnencrypted = value; }
   141         }
   142 
   143         /// <summary>
   144         /// Gets or sets whether smart address processing is enabled.
   145         /// If smart address is enabled, and the set address is empty/null, the username is 
   146         /// considered the address if it's formatted as an address itself.
   147         /// </summary>
   148         public bool IsSmartAddressEnabled
   149         {
   150             get { return (this._IsSmartAddressEnabled); }
   151             set { this._IsSmartAddressEnabled = value; }
   152         }
   153 
   154         /// <summary>
   155         /// Gets the list of members in this identity if it is a group.
   156         /// Note: This is not supported by the pEp engine or the COM type.
   157         /// </summary>
   158         public List<PEPIdentity> Members
   159         {
   160             get { return (this._Members); }
   161         }
   162 
   163         ///////////////////////////////////////////////////////////
   164         // Calculated
   165         ///////////////////////////////////////////////////////////
   166 
   167         /// <summary>
   168         /// Gets whether the identity address is valid for the pEp engine and further use.
   169         /// This can only validate the basic format of the address (xxxx@xxxx.xxx).
   170         /// Note: The address used is the smart address.
   171         /// If smart address is enabled, and the set address is empty/null, the username is 
   172         /// considered the address if it's formatted as an address itself.
   173         /// </summary>
   174         public bool IsAddressValid
   175         {
   176             get
   177             {
   178                 // Make sure to use the smart address
   179                 return (PEPIdentity.GetIsAddressValid(this.GetSmartAddress()));
   180             }
   181         }
   182 
   183         /// <summary>
   184         /// Gets whether this identity is empty and contains no data.
   185         /// Note: CommunicationType, Language and IsSmartAddressEnabled are ignored.
   186         /// </summary>
   187         public bool IsEmpty
   188         {
   189             get
   190             {
   191                 bool isEmpty = true;
   192 
   193                 // Check address
   194                 if (isEmpty)
   195                 {
   196                     // Do not use the smart address
   197                     if (string.IsNullOrEmpty(this.internalIdentity.address) == false)
   198                     {
   199                         isEmpty = false;
   200                     }
   201                 }
   202 
   203                 // Skip CommunicationType
   204 
   205                 // Check Fingerprint
   206                 if (isEmpty)
   207                 {
   208                     if (string.IsNullOrEmpty(this.internalIdentity.fpr) == false)
   209                     {
   210                         isEmpty = false;
   211                     }
   212                 }
   213 
   214                 // Skip Language
   215 
   216                 // Check UserID
   217                 if (isEmpty)
   218                 {
   219                     if (string.IsNullOrEmpty(this.internalIdentity.user_id) == false)
   220                     {
   221                         isEmpty = false;
   222                     }
   223                 }
   224 
   225                 // Check Username
   226                 if (isEmpty)
   227                 {
   228                     if (string.IsNullOrEmpty(this.internalIdentity.username) == false)
   229                     {
   230                         isEmpty = false;
   231                     }
   232                 }
   233 
   234                 // Check IsForceUnencrypted
   235                 if (isEmpty)
   236                 {
   237                     if (this._IsForceUnencrypted != null)
   238                     {
   239                         isEmpty = false;
   240                     }
   241                 }
   242 
   243                 // Skip IsSmartAddressEnabled
   244 
   245                 // Check Members
   246                 if (isEmpty)
   247                 {
   248                     for (int i = 0; i < this._Members.Count; i++)
   249                     {
   250                         if (this._Members[i].IsEmpty == false)
   251                         {
   252                             isEmpty = false;
   253                             break;
   254                         }
   255                     }
   256                 }
   257 
   258                 return (isEmpty);
   259             }
   260         }
   261 
   262         /// <summary>
   263         /// Gets the true/false boolean value of the IsForceUnencrypted property 
   264         /// (null is considered false).
   265         /// </summary>
   266         public bool IsForceUnencryptedBool
   267         {
   268             get
   269             {
   270                 if ((this._IsForceUnencrypted == null) ||
   271                     ((bool)this._IsForceUnencrypted == false))
   272                 {
   273                     return (false);
   274                 }
   275                 else
   276                 {
   277                     return (true);
   278                 }
   279             }
   280         }
   281 
   282         /// <summary>
   283         /// Gets whether this identity is a group with individual members.
   284         /// </summary>
   285         public bool IsGroup
   286         {
   287             get { return (this._Members.Count > 0); }
   288         }
   289 
   290         /**************************************************************
   291          * 
   292          * Methods
   293          * 
   294          *************************************************************/
   295 
   296         /// <summary>
   297         /// Returns this pEp identity as a pEp engine identity.
   298         /// Copied data is used in the COM type.
   299         /// Note: This will not include any members of a group, 
   300         /// also the address used is the smart address.
   301         /// </summary>
   302         /// <returns>A pEp engine identity.</returns>
   303         public pEp_identity_s ToCOMType()
   304         {
   305             pEp_identity_s newIdent = new pEp_identity_s();
   306 
   307             newIdent.address = this.GetSmartAddress();
   308             newIdent.comm_type = this.internalIdentity.comm_type;
   309             newIdent.fpr = this.internalIdentity.fpr;
   310             newIdent.lang = this.internalIdentity.lang;
   311             newIdent.user_id = this.internalIdentity.user_id;
   312             newIdent.username = this.internalIdentity.username;
   313 
   314             return (newIdent);
   315         }
   316 
   317         /// <summary>
   318         /// Serves as a hash function for a particular type.
   319         /// </summary>
   320         /// <returns>A hash code for the current object.</returns>
   321         public override int GetHashCode()
   322         {
   323             return base.GetHashCode();
   324         }
   325 
   326         /// <summary>
   327         /// Indicates whether the current object is equal to another object of the same type.
   328         /// </summary>
   329         /// <param name="obj">The object to check equality with.</param>
   330         /// <returns>True if both objects are considered equal, otherwise false.</returns>
   331         public override bool Equals(object obj)
   332         {
   333             if ((obj == null) ||
   334                 !(obj is PEPIdentity))
   335             {
   336                 return (false);
   337             }
   338 
   339             return (this.Equals((PEPIdentity)obj));
   340         }
   341 
   342         /// <summary>
   343         /// Indicates whether the current object is equal to another object of the same type.
   344         /// </summary>
   345         /// <param name="obj">The object to check equality with.</param>
   346         /// <returns>True if both objects are considered equal, otherwise false.</returns>
   347         public bool Equals(PEPIdentity obj)
   348         {
   349             if (obj == null)
   350             {
   351                 return (false);
   352             }
   353 
   354             if (Comparisons.Equals(this.Address, obj.Address) &&
   355                 Comparisons.Equals(this.CommunicationType, obj.CommunicationType) &&
   356                 Comparisons.Equals(this.Fingerprint, obj.Fingerprint) &&
   357                 Comparisons.Equals(this.Language, obj.Language) &&
   358                 Comparisons.Equals(this.UserID, obj.UserID) &&
   359                 Comparisons.Equals(this.Username, obj.Username) &&
   360                 Comparisons.Equals(this.IsForceUnencrypted, obj.IsForceUnencrypted) &&
   361                 Comparisons.Equals(this.IsSmartAddressEnabled, obj.IsSmartAddressEnabled) &&
   362                 Comparisons.Equals(this.Members, obj.Members))
   363             {
   364                 return (true);
   365             }
   366 
   367             return (false);
   368         }
   369 
   370         /// <summary>
   371         /// Gets a deep copy of the object and all it's data.
   372         /// </summary>
   373         /// <returns>The deep copy of the object.</returns>
   374         public PEPIdentity Copy()
   375         {
   376             PEPIdentity copy = new PEPIdentity();
   377 
   378             copy.Address = this.internalIdentity.address;
   379             copy.CommunicationType = this.internalIdentity.comm_type;
   380             copy.Fingerprint = this.internalIdentity.fpr;
   381             copy.Language = this.internalIdentity.lang;
   382             copy.UserID = this.internalIdentity.user_id;
   383             copy.Username = this.internalIdentity.username;
   384 
   385             // IsForceUnencrypted
   386             if (this._IsForceUnencrypted != null)
   387             {
   388                 copy.IsForceUnencrypted = (bool)this.IsForceUnencrypted;
   389             }
   390             else
   391             {
   392                 copy.IsForceUnencrypted = null;
   393             }
   394 
   395             copy.IsSmartAddressEnabled = this.IsSmartAddressEnabled;
   396 
   397             // Members
   398             copy.Members.Clear();
   399             for (int i = 0; i < this._Members.Count; i++)
   400             {
   401                 copy.Members.Add(this._Members[i].Copy());
   402             }
   403 
   404             return (copy);
   405         }
   406 
   407         /// <summary>
   408         /// Indicates whether the current object is equal to another object of the same type.
   409         /// This compares identities based on address (case insensitive), any members are ignored.
   410         /// Note: The address used is the smart address.
   411         /// Note: If either (or both) address is null or empty, they are considered NOT equal.
   412         /// </summary>
   413         /// <param name="obj">The object to compare against.</param>
   414         /// <returns>True if the given address and this object's address are neither null nor empty and considered equal, otherwise false.</returns>
   415         public bool EqualsByAddress(PEPIdentity obj)
   416         {
   417             bool areEqual = false;
   418             string addr = this.GetSmartAddress();
   419 
   420             if ((obj != null) &&
   421                 (string.IsNullOrEmpty(obj.Address) == false) &&
   422                 (string.IsNullOrEmpty(addr) == false))
   423             {
   424                 areEqual = string.Equals(addr.Trim(), obj.Address.Trim(), StringComparison.OrdinalIgnoreCase);
   425             }
   426 
   427             return (areEqual);
   428         }
   429 
   430         /// <summary>
   431         /// Indicates whether the current object is equal to another object of the same type.
   432         /// This compares identities based on address (case insensitive), any members are ignored.
   433         /// Note: The address used is the smart address.
   434         /// Note: If either (or both) address is null or empty, they are considered NOT equal.
   435         /// </summary>
   436         /// <param name="address">The address to compare against.</param>
   437         /// <returns>True if the given address and this object's address are neither null nor empty and considered equal, otherwise false.</returns>
   438         public bool EqualsByAddress(string address)
   439         {
   440             bool areEqual = false;
   441             string addr = this.GetSmartAddress();
   442 
   443             if ((string.IsNullOrEmpty(address) == false) &&
   444                 (string.IsNullOrEmpty(addr) == false))
   445             {
   446                 areEqual = string.Equals(addr.Trim(), address.Trim(), StringComparison.OrdinalIgnoreCase);
   447             }
   448 
   449             return (areEqual);
   450         }
   451 
   452         /// <summary>
   453         /// Recursivley converts the given identity into a 'flat' list of any members.
   454         /// This will remove groups (hierarchy) and convert a group into it's members.
   455         /// </summary>
   456         /// <param name="identity">The identity to get the flat list for.</param>
   457         /// <returns>The flat list of pEp identities in the identity.</returns>
   458         public List<PEPIdentity> ToFlatList()
   459         {
   460             List<PEPIdentity> result = new List<PEPIdentity>();
   461             this.RecToFlatList(this, result);
   462             return (result);
   463         }
   464 
   465         /// <summary>
   466         /// Recursively adds members of any group in the given identity to the list.
   467         /// </summary>
   468         /// <param name="identity">The identity to process.</param>
   469         /// <param name="list">The list to add identities to.</param>
   470         private void RecToFlatList(PEPIdentity identity,
   471                                    List<PEPIdentity> list)
   472         {
   473             List<PEPIdentity> memberList;
   474 
   475             if (identity.IsGroup)
   476             {
   477                 for (int i = 0; i < identity.Members.Count; i++)
   478                 {
   479                     memberList = new List<PEPIdentity>();
   480                     this.RecToFlatList(identity.Members[i], memberList);
   481 
   482                     for (int j = 0; j < memberList.Count; j++)
   483                     {
   484                         list.Add(memberList[j]);
   485                     }
   486                 }
   487             }
   488             else
   489             {
   490                 list.Add(identity.Copy());
   491             }
   492 
   493             return;
   494         }
   495 
   496         /// <summary>
   497         /// Gets the calculated smart address for this identity.
   498         /// If smart address is enabled, and the set address is empty/null, the username may be returned as the address
   499         /// if it's formatted as an address.
   500         /// </summary>
   501         /// <returns>The calculated 'smart' adress for this identity.</returns>
   502         private string GetSmartAddress()
   503         {
   504             string addr = this.internalIdentity.address;
   505 
   506             // If smart address processing is enabled, and the internal address is null/empty,
   507             // check if the username is valid and return it as the address.
   508             if ((this._IsSmartAddressEnabled) &&
   509                 (string.IsNullOrWhiteSpace(this.internalIdentity.address)))
   510             {
   511                 if (PEPIdentity.GetIsAddressValid(this.internalIdentity.username))
   512                 {
   513                     addr = this.internalIdentity.username;
   514                 }
   515             }
   516 
   517             return (addr);
   518         }
   519 
   520         /// <summary>
   521         /// Returns a string that represents this identity.
   522         /// Note: The address used is the smart address.
   523         /// </summary>
   524         /// <returns>The string representing the identity which can be empty.</returns>
   525         public override string ToString()
   526         {
   527             string result = "";
   528             string usrname = this.internalIdentity.username;
   529             string addr = this.GetSmartAddress();
   530 
   531             if ((string.IsNullOrWhiteSpace(usrname) == true) &&
   532                 (string.IsNullOrWhiteSpace(addr) == true))
   533             {
   534                 // Do nothing, return empty
   535             }
   536             else if ((string.IsNullOrWhiteSpace(usrname) == true) &&
   537                      (string.IsNullOrWhiteSpace(addr) == false))
   538             {
   539                 result = addr.Trim();
   540             }
   541             else if ((string.IsNullOrWhiteSpace(usrname) == false) &&
   542                      (string.IsNullOrWhiteSpace(addr) == true))
   543             {
   544                 result = usrname.Trim();
   545             }
   546             else if ((string.IsNullOrWhiteSpace(usrname) == false) &&
   547                      (string.IsNullOrWhiteSpace(addr) == false))
   548             {
   549                 if (usrname.Trim() == addr.Trim())
   550                 {
   551                     result = usrname.Trim();
   552                 }
   553                 else
   554                 {
   555                     result = usrname.Trim() + " <" + addr.Trim() + ">";
   556                 }
   557             }
   558 
   559             return (result);
   560         }
   561 
   562         /// <summary>
   563         /// Returns a string that represents this identity specifically for display.
   564         /// This will use the username if it exists, otherwise address.
   565         /// </summary>
   566         /// <returns>The display string representing the identity which can be empty.</returns>
   567         public string ToDisplayString()
   568         {
   569             string result = "";
   570 
   571             if (string.IsNullOrWhiteSpace(this.internalIdentity.username) == false)
   572             {
   573                 result = this.internalIdentity.username.Trim();
   574             }
   575             // Do not use the smart address (no need since username is tried first)
   576             else if (string.IsNullOrWhiteSpace(this.internalIdentity.address) == false)
   577             {
   578                 result = this.internalIdentity.address.Trim();
   579             }
   580 
   581             return (result);
   582         }
   583 
   584         /**************************************************************
   585          * 
   586          * Static Methods
   587          * 
   588          *************************************************************/
   589 
   590         /// <summary>
   591         /// Parses the given string as a new PEPIdentity. This will never return null.
   592         /// The string should be of the format 'user name &lt;email@address.com&gt;'
   593         /// If the string does not have both &lt; and &gt; it will be read as an address if it is a valid address, 
   594         /// otherwise a username.
   595         /// </summary>
   596         /// <param name="str">The string to parse as a PEPIdentity.</param>
   597         /// <returns>The string parsed as a PEPIdentity, otherwise null if failure.</returns>
   598         public static PEPIdentity Parse(string str)
   599         {
   600             int addressStartSymbol;
   601             int addressEndSymbol;
   602             string workingStr;
   603             string userName;
   604             string address;
   605             string[] temp;
   606             PEPIdentity result = new PEPIdentity();
   607 
   608             if (string.IsNullOrWhiteSpace(str) == false)
   609             {
   610                 workingStr = str.Trim();
   611                 addressStartSymbol = workingStr.IndexOf("<");
   612                 addressEndSymbol = workingStr.IndexOf(">");
   613 
   614                 if ((addressStartSymbol < 0) &&
   615                     (addressEndSymbol < 0))
   616                 {
   617                     if (PEPIdentity.GetIsAddressValid(workingStr))
   618                     {
   619                         // Only address
   620                         result = new PEPIdentity();
   621                         result.Address = workingStr;
   622                     }
   623                     else
   624                     {
   625                         // Only user name
   626                         result = new PEPIdentity();
   627                         result.Username = workingStr;
   628                     }
   629                 }
   630                 else if ((addressStartSymbol >= 0) &&
   631                          (addressEndSymbol >= 0) &&
   632                          (addressEndSymbol > addressStartSymbol) &&
   633                          (workingStr.LastIndexOf("<") == addressStartSymbol) &&
   634                          (workingStr.LastIndexOf(">") == addressEndSymbol))
   635                 {
   636                     temp = workingStr.Split('<');
   637 
   638                     if (temp.Length == 2)
   639                     {
   640                         userName = temp[0].Trim();
   641                         address = temp[1].Replace(">", "").Trim();
   642 
   643                         // Require both the user name and address to exist
   644                         if ((string.IsNullOrWhiteSpace(userName) == false) &&
   645                             (PEPIdentity.GetIsAddressValid(address)))
   646                         {
   647                             result = new PEPIdentity();
   648                             result.Username = userName;
   649                             result.Address = address;
   650                         }
   651                     }
   652                 }
   653             }
   654 
   655             return (result);
   656         }
   657 
   658         /// <summary>
   659         /// Gets whether the given address string is considered valid based on its format.
   660         /// This can only validate the basic format of the address (xxxx@xxxx.xxx).
   661         /// </summary>
   662         /// <param name="address">The address string to determine if it's valid.</param>
   663         /// <returns>True if the address string's format is valid, otherwise false.</returns>
   664         public static bool GetIsAddressValid(string address)
   665         {
   666             bool isValid = false;
   667             string addrPattern = "(?s)(?i).*[@].*[.].*"; // SingleLine, IgnoreCase
   668 
   669             if ((string.IsNullOrWhiteSpace(address) == false) &&
   670                 (Regex.IsMatch(address, addrPattern)))
   671             {
   672                 isValid = true;
   673             }
   674 
   675             return (isValid);
   676         }
   677 
   678         /// <summary>
   679         /// Gets whether the given address represents an own personal identity (myself).
   680         /// This is done by comparing against the current session's account list.
   681         /// If a registered account's SMTP address, or current user address, matches the given address (case insensitive), it is a match.
   682         /// This means it should work for both Exchange formatted and normal address strings.
   683         /// </summary>
   684         /// <param name="address">The email address to check if it is an own identity.</param>
   685         /// <returns>True if the given address represents an own identity, otherwise false.</returns>
   686         public static bool GetIsOwnIdentity(string address)
   687         {
   688             bool isMyself;
   689             Outlook.Account account;
   690 
   691             account = PEPIdentity.GetOwnAccount(address);
   692             isMyself = (account != null ? true : false);
   693 
   694             if (account != null)
   695             {
   696                 Marshal.ReleaseComObject(account);
   697                 account = null;
   698             }
   699 
   700             return (isMyself);
   701         }
   702 
   703         /// <summary>
   704         /// Gets the personal identity using the given email address (can handle exchange encoded addresses).
   705         /// The account for the email address must already exist in Outlook.
   706         /// Warning: The result can be null.
   707         /// </summary>
   708         /// <param name="address">The email address to get the personal identity from.</param>
   709         /// <param name="ownIdentity">The output own identity (may be null).</param>
   710         /// <returns>The personal identity.</returns>
   711         public static Globals.ReturnStatus GetOwnIdentity(string address,
   712                                                           out PEPIdentity ownIdentity)
   713         {
   714             pEp_identity_s ident;
   715             PEPIdentity ownBaseIdentity = null;
   716             PEPIdentity own = null;
   717             Outlook.Account account = null;
   718             Outlook.AddressEntry currAddrEntry = null;
   719             Outlook.Recipient currUser = null;
   720             Outlook.ExchangeUser currExchUser = null;
   721             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
   722 
   723             try
   724             {
   725                 account = PEPIdentity.GetOwnAccount(address);
   726 
   727                 if (account != null)
   728                 {
   729                     currUser = account.CurrentUser;
   730 
   731                     ownBaseIdentity = new PEPIdentity();
   732                     ownBaseIdentity.Address = account.SmtpAddress;
   733 
   734                     // Try to get an exchange user (even if one doesn't exist)
   735                     try
   736                     {
   737                         currAddrEntry = currUser.AddressEntry;
   738 
   739                         if (currAddrEntry.Type == "EX")
   740                         {
   741                             currExchUser = currAddrEntry.GetExchangeUser();
   742                         }
   743                     }
   744                     catch { }
   745 
   746                     // Use Exchange information
   747                     if (currExchUser != null)
   748                     {
   749                         ownBaseIdentity.Username = currExchUser.Name;
   750                     }
   751                     // Use current user recipient information
   752                     else if (currUser != null)
   753                     {
   754                         ownBaseIdentity.Username = currUser.Name;
   755                     }
   756 
   757                     // Add user ID
   758                     ownBaseIdentity.UserID = Globals.ThisAddIn.GetUserID(ownBaseIdentity.Address, null);
   759                 }
   760 
   761                 // Update the identity in the pEp engine (and get fingerprint)
   762                 if (ownBaseIdentity != null)
   763                 {
   764                     try
   765                     {
   766                         ident = ThisAddIn.PEPEngine.myself(ownBaseIdentity.ToCOMType());
   767                         own = new PEPIdentity(ident);
   768                         status = Globals.ReturnStatus.Success;
   769                     }
   770                     catch
   771                     {
   772                         own = null;
   773                         status = Globals.ReturnStatus.Failure;
   774                     }
   775                 }
   776             }
   777             catch (Exception ex)
   778             {
   779                 status = Globals.ReturnStatus.Failure;
   780                 Log.Error("GetOwnIdentity: Failure occured, " + ex.ToString());
   781             }
   782             finally
   783             {
   784                 if (account != null)
   785                 {
   786                     Marshal.ReleaseComObject(account);
   787                     account = null;
   788                 }
   789 
   790                 if (currAddrEntry != null)
   791                 {
   792                     Marshal.ReleaseComObject(currAddrEntry);
   793                     currAddrEntry = null;
   794                 }
   795 
   796                 if (currUser != null)
   797                 {
   798                     Marshal.ReleaseComObject(currUser);
   799                     currUser = null;
   800                 }
   801 
   802                 if (currExchUser != null)
   803                 {
   804                     Marshal.ReleaseComObject(currExchUser);
   805                     currExchUser = null;
   806                 }
   807             }
   808 
   809             ownIdentity = own;
   810             return (status);
   811         }
   812 
   813         /// <summary>
   814         /// Gets the Outlook account in this session that corresponds with the given address.
   815         /// If a registered account's SMTP address, or current user address, matches the given address (case insensitive), it is a match.
   816         /// This means it should work for both Exchange formatted and normal address strings.
   817         /// Warning: If multiple accounts have the same address this will only return the first one.
   818         /// </summary>
   819         /// <param name="address">The email address to find the account for.</param>
   820         /// <returns>The own Outlook Acccount associated with given address, otherwise null.
   821         /// The caller has ownership of the returned account.</returns>
   822         public static Outlook.Account GetOwnAccount(string address)
   823         {
   824             bool accountFound = false;
   825             string workingAddress;
   826             string accountAddress;
   827             string currUserAddress;
   828             Outlook.Account account = null;
   829             Outlook.Accounts accounts = null;
   830             Outlook.NameSpace ns = null;
   831             Outlook.Recipient currUser = null;
   832 
   833             /* Note: For exchange accounts the given address may not be an actual address unless the caller correctly 
   834              * gets the exchange user then the exchange user's address.
   835              * However, this function can handle both situations by simply checking against both the account smtp address
   836              * and the account's current user address.
   837              * If an exchange encoded address is given, it should match against the current user which also will have an
   838              * exchange encoded address. Otherwise, it should match against the account smpt address.
   839              */
   840 
   841             try
   842             {
   843                 if (string.IsNullOrWhiteSpace(address) == false)
   844                 {
   845                     ns = Globals.ThisAddIn.Application.Session;
   846                     accounts = ns.Accounts;
   847                     workingAddress = address.Trim();
   848 
   849                     // Look in existing accounts
   850                     for (int i = 1; i <= accounts.Count; i++)
   851                     {
   852                         account = accounts[i];
   853                         currUser = account.CurrentUser;
   854                         accountAddress = (account.SmtpAddress != null ? account.SmtpAddress.Trim() : "");
   855                         currUserAddress = (currUser.Address != null ? currUser.Address.Trim() : "");
   856 
   857                         // Match by current user or account account
   858                         if ((string.IsNullOrEmpty(accountAddress) == false) &&
   859                             (string.IsNullOrEmpty(currUserAddress) == false) &&
   860                             ((string.Equals(workingAddress, currUserAddress, StringComparison.OrdinalIgnoreCase)) ||
   861                              (string.Equals(workingAddress, accountAddress, StringComparison.OrdinalIgnoreCase))))
   862                         {
   863                             accountFound = true;
   864                         }
   865 
   866                         if (currUser != null)
   867                         {
   868                             Marshal.ReleaseComObject(currUser);
   869                             currUser = null;
   870                         }
   871 
   872                         // Do not release the account if it was a match
   873                         if ((account != null) &&
   874                             (accountFound == false))
   875                         {
   876                             Marshal.ReleaseComObject(account);
   877                             account = null;
   878                         }
   879 
   880                         if (accountFound)
   881                         {
   882                             break;
   883                         }
   884                     }
   885                 }
   886             }
   887             catch (Exception ex)
   888             {
   889                 Log.Error("GetOwnAccount: Failure occured, " + ex.ToString());
   890             }
   891             finally
   892             {
   893                 // Do not release the account if it was a match
   894                 if ((account != null) &&
   895                     (accountFound == false))
   896                 {
   897                     Marshal.ReleaseComObject(account);
   898                     account = null;
   899                 }
   900 
   901                 if (accounts != null)
   902                 {
   903                     Marshal.ReleaseComObject(accounts);
   904                     accounts = null;
   905                 }
   906 
   907                 if (ns != null)
   908                 {
   909                     Marshal.ReleaseComObject(ns);
   910                     ns = null;
   911                 }
   912 
   913                 if (currUser != null)
   914                 {
   915                     Marshal.ReleaseComObject(currUser);
   916                     currUser = null;
   917                 }
   918             }
   919 
   920             return (account);
   921         }
   922 
   923         /// <summary>
   924         /// Gets the personal identity using the given cryptable mail item.
   925         /// An account for the personal identity does not need to exist in Outlook.
   926         /// Warning: The result can be null.
   927         /// </summary>
   928         /// <param name="omi">The mail item to get the personal identity from.</param>
   929         /// <param name="ownIdentity">The output own identity (may be null).</param>
   930         /// <returns>The status of the method.</returns>
   931         public static Globals.ReturnStatus GetOwnIdentity(Outlook.MailItem omi,
   932                                                           out PEPIdentity ownIdentity)
   933         {
   934             string entryID;
   935             string address = null;
   936             string username = null;
   937             pEp_identity_s ident;
   938             PEPIdentity own = null;
   939             PEPIdentity from = null;
   940             Outlook.Account account = null;
   941             Outlook.Accounts accounts = null;
   942             Outlook.NameSpace ns = null;
   943             Outlook.Recipient currUser = null;
   944             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
   945             Globals.ReturnStatus sts;
   946 
   947             try
   948             {
   949                 if (CryptableMailItem.GetIsIncoming(omi))
   950                 {
   951                     // Attempt to get address by entry ID
   952                     entryID = omi.ReceivedByEntryID;
   953                     ns = Globals.ThisAddIn.Application.Session;
   954                     accounts = ns.Accounts;
   955                     for (int i = 1; i <= accounts.Count; i++)
   956                     {
   957                         account = accounts[i];
   958                         currUser = account.CurrentUser;
   959 
   960                         if (currUser != null)
   961                         {
   962                             if (currUser.EntryID == entryID)
   963                             {
   964                                 address = account.SmtpAddress;
   965                                 break;
   966                             }
   967 
   968                             Marshal.ReleaseComObject(currUser);
   969                             currUser = null;
   970                         }
   971 
   972                         Marshal.ReleaseComObject(account);
   973                         account = null;
   974                     }
   975 
   976                     // Attempt to get the address from the MAPI property
   977                     if (string.IsNullOrWhiteSpace(address))
   978                     {
   979                         address = (string)MAPIHelper.GetProperty(omi, MAPIProperty.PidTagReceivedByEmailAddress);
   980                     }
   981 
   982                     sts = PEPIdentity.GetOwnIdentity(address, out own);
   983                     status = sts; // Can return the just calculated value, therefore set status
   984 
   985                     /* Try using the mail item received-by user property information directly.
   986                      * Only incoming messages can do this, outgoing 'myself' identites must exist after GetMyIdentity(address).
   987                      */
   988                     if ((own == null) ||
   989                         (sts != Globals.ReturnStatus.Success))
   990                     {
   991                         address = (string)CryptableMailItem.GetUserProperty(omi, MAPIProperty.PidTagReceivedByEmailAddress.DASLName);
   992                         username = (string)CryptableMailItem.GetUserProperty(omi, MAPIProperty.PidTagReceivedByName.DASLName);
   993 
   994                         if (string.IsNullOrEmpty(address) == false)
   995                         {
   996                             own = new PEPIdentity();
   997                             own.Address = address;
   998                             own.Username = username;
   999                             own.UserID = Globals.ThisAddIn.GetUserID(own.Address, null); // Not using 'myself ID'
  1000 
  1001                             // Update the identity in the pEp engine (and get fingerprint)
  1002                             try
  1003                             {
  1004                                 ident = ThisAddIn.PEPEngine.myself(own.ToCOMType());
  1005                                 own = new PEPIdentity(ident);
  1006                                 status = Globals.ReturnStatus.Success;
  1007                             }
  1008                             catch
  1009                             {
  1010                                 own = null;
  1011                                 status = Globals.ReturnStatus.Failure;
  1012                             }
  1013                         }
  1014                     }
  1015                 }
  1016                 else // Outgoing
  1017                 {
  1018                     sts = PEPIdentity.GetFromIdentity(omi, out from);
  1019 
  1020                     // Default
  1021                     if ((from == null) ||
  1022                         (from.IsEmpty) ||
  1023                         (sts != Globals.ReturnStatus.Success))
  1024                     {
  1025                         sts = PEPIdentity.GetDefaultFromIdentity(out from);
  1026                     }
  1027 
  1028                     if ((from != null) &&
  1029                         (sts == Globals.ReturnStatus.Success))
  1030                     {
  1031                         /* Note: while From could be used directly, call GetMyIdentity on the address to ensure
  1032                          * it is an active account in Outlook or registered if new.
  1033                          */
  1034                         status = PEPIdentity.GetOwnIdentity(from.Address, out own);
  1035                     }
  1036                     else
  1037                     {
  1038                         own = null;
  1039                         status = Globals.ReturnStatus.Failure;
  1040                     }
  1041                 }
  1042             }
  1043             catch (Exception ex)
  1044             {
  1045                 status = Globals.ReturnStatus.Failure;
  1046                 Log.Error("GetOwnIdentity: Failure occured, " + ex.ToString());
  1047             }
  1048             finally
  1049             {
  1050                 // Release objects
  1051                 if (account != null)
  1052                 {
  1053                     Marshal.ReleaseComObject(account);
  1054                     account = null;
  1055                 }
  1056 
  1057                 if (accounts != null)
  1058                 {
  1059                     Marshal.ReleaseComObject(accounts);
  1060                     accounts = null;
  1061                 }
  1062 
  1063                 if (ns != null)
  1064                 {
  1065                     Marshal.ReleaseComObject(ns);
  1066                     ns = null;
  1067                 }
  1068 
  1069                 if (currUser != null)
  1070                 {
  1071                     Marshal.ReleaseComObject(currUser);
  1072                     currUser = null;
  1073                 }
  1074             }
  1075 
  1076             ownIdentity = own;
  1077             return (status);
  1078         }
  1079 
  1080         /// <summary>
  1081         /// Gets the from/sender user name from the given outlook mail item.
  1082         /// Only the sender fields of the outlook mail item will be used (SendUsingAccount is ignored).
  1083         /// This should only be used for messages that have already been sent/received (not draft).
  1084         /// Warning: The result can be null.
  1085         /// </summary>
  1086         /// <param name="omi">The outlook mail item to get the from/sender name from.</param>
  1087         /// <param name="fromUsername">The output from/sender username (may be null).</param>
  1088         /// <returns>The status of the method.</returns>
  1089         public static Globals.ReturnStatus GetFromUsername(Outlook.MailItem omi,
  1090                                                            out string fromUsername)
  1091         {
  1092             string username = null;
  1093             Outlook.AddressEntry sender = null;
  1094             Outlook.ExchangeUser exchSender = null;
  1095             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
  1096 
  1097             // Note: see https://msdn.microsoft.com/en-us/library/office/ff184624.aspx
  1098             try
  1099             {
  1100                 try
  1101                 {
  1102                     sender = omi.Sender;
  1103                 }
  1104                 catch { }
  1105 
  1106                 // Try to handle exchange using an exchange user
  1107                 if ((status != Globals.ReturnStatus.Success) &&
  1108                     (omi.SenderEmailType == "EX") &&
  1109                     (sender != null))
  1110                 {
  1111                     // Common failures occur here when an Exchange server is unavailable but required.
  1112                     // ExchangeConnectionMode could be checked but a global try/catch is sufficient.
  1113                     try
  1114                     {
  1115                         if (sender.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry ||
  1116                             sender.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeRemoteUserAddressEntry)
  1117                         {
  1118                             exchSender = sender.GetExchangeUser();
  1119                             if (exchSender != null)
  1120                             {
  1121                                 username = exchSender.Name;
  1122                                 status = Globals.ReturnStatus.Success;
  1123                             }
  1124                             else
  1125                             {
  1126                                 // Failed to access Exchange sender information
  1127                                 // Other methods will still be tried, if they fail too null will be returned
  1128                             }
  1129                         }
  1130                         else
  1131                         {
  1132                             username = sender.Name;
  1133                             status = Globals.ReturnStatus.Success;
  1134                         }
  1135                     }
  1136                     catch
  1137                     {
  1138                         // Assume the failure was because there is no connection to exchange
  1139                         // Other methods will still be tried, if they fail too null will be returned
  1140                         status = Globals.ReturnStatus.FailureNoConnection;
  1141                     }
  1142                 }
  1143 
  1144                 // Try using the mail item properties (non-exchange)
  1145                 if ((status != Globals.ReturnStatus.Success) &&
  1146                     (omi.SenderEmailType != "EX") &&
  1147                     (omi.SenderEmailAddress != null))
  1148                 {
  1149                     username = omi.SenderName;
  1150                     status = Globals.ReturnStatus.Success;
  1151                 }
  1152             }
  1153             catch (Exception ex)
  1154             {
  1155                 status = Globals.ReturnStatus.Failure;
  1156                 Log.Error("GetFromUsername: Failure occured, " + ex.ToString());
  1157             }
  1158             finally
  1159             {
  1160                 // Release objects
  1161                 if (sender != null)
  1162                 {
  1163                     Marshal.ReleaseComObject(sender);
  1164                     sender = null;
  1165                 }
  1166 
  1167                 if (exchSender != null)
  1168                 {
  1169                     Marshal.ReleaseComObject(exchSender);
  1170                     exchSender = null;
  1171                 }
  1172             }
  1173 
  1174             fromUsername = username;
  1175             return (status);
  1176         }
  1177 
  1178         /// <summary>
  1179         /// Gets the from/sender identity from the given outlook mail item.
  1180         /// Warning: The result can be null.
  1181         /// </summary>
  1182         /// <param name="omi">The outlook mail item to get the from/sender identity from.</param>
  1183         /// <param name="fromIdentity">The output from/sender identity (may be null).</param>
  1184         /// <returns>The status of the method.</returns>
  1185         public static Globals.ReturnStatus GetFromIdentity(Outlook.MailItem omi,
  1186                                                            out PEPIdentity fromIdentity)
  1187         {
  1188             PEPIdentity from = null;
  1189             Outlook.ContactItem contact = null;
  1190             Outlook.AddressEntry sender = null;
  1191             Outlook.ExchangeUser exchSender = null;
  1192             Outlook.Account sendingAccount = null;
  1193             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
  1194 
  1195             // Note: see https://msdn.microsoft.com/en-us/library/office/ff184624.aspx
  1196             try
  1197             {
  1198                 if (CryptableMailItem.GetIsIncoming(omi))
  1199                 {
  1200                     // Get sender
  1201                     try
  1202                     {
  1203                         sender = omi.Sender;
  1204                     }
  1205                     catch { }
  1206 
  1207                     // Try to handle exchange using an exchange user
  1208                     if ((status != Globals.ReturnStatus.Success) &&
  1209                         (omi.SenderEmailType == "EX") &&
  1210                         (sender != null))
  1211                     {
  1212                         // Common failures occur here when an Exchange server is unavailable but required.
  1213                         // ExchangeConnectionMode could be checked but a global try/catch is sufficient.
  1214                         try
  1215                         {
  1216                             if (sender.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry ||
  1217                                 sender.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeRemoteUserAddressEntry)
  1218                             {
  1219                                 // Use the ExchangeUser object PrimarySMTPAddress
  1220                                 exchSender = sender.GetExchangeUser();
  1221                                 if (exchSender != null)
  1222                                 {
  1223                                     from = new PEPIdentity();
  1224                                     from.Address = exchSender.PrimarySmtpAddress;
  1225                                     from.Username = exchSender.Name;
  1226 
  1227                                     // Add the contact force unencrypted property
  1228                                     try
  1229                                     {
  1230                                         contact = exchSender.GetContact();
  1231 
  1232                                         if (contact != null)
  1233                                         {
  1234                                             from.IsForceUnencrypted = Globals.ThisAddIn.GetForceUnencrypted(contact);
  1235                                             Marshal.ReleaseComObject(contact);
  1236                                             contact = null;
  1237                                         }
  1238                                     }
  1239                                     catch { }
  1240 
  1241                                     // Add the UserID
  1242                                     from.UserID = Globals.ThisAddIn.GetUserID(from.Address, contact);
  1243 
  1244                                     status = Globals.ReturnStatus.Success;
  1245                                 }
  1246                                 else
  1247                                 {
  1248                                     // Failed to access Exchange sender information
  1249                                     // Other methods will still be tried, if they fail too null will be returned
  1250                                 }
  1251                             }
  1252                             else
  1253                             {
  1254                                 from = new PEPIdentity();
  1255                                 from.Address = (string)MAPIHelper.GetProperty(sender, MAPIProperty.PidTagSmtpAddress);
  1256                                 from.Username = sender.Name;
  1257 
  1258                                 // Add the contact force unencrypted property
  1259                                 try
  1260                                 {
  1261                                     contact = sender.GetContact();
  1262 
  1263                                     if (contact != null)
  1264                                     {
  1265                                         from.IsForceUnencrypted = Globals.ThisAddIn.GetForceUnencrypted(contact);
  1266                                         Marshal.ReleaseComObject(contact);
  1267                                         contact = null;
  1268                                     }
  1269                                 }
  1270                                 catch { }
  1271 
  1272                                 // Add the UserID
  1273                                 from.UserID = Globals.ThisAddIn.GetUserID(from.Address, contact);
  1274 
  1275                                 status = Globals.ReturnStatus.Success;
  1276                             }
  1277                         }
  1278                         catch
  1279                         {
  1280                             // Assume the failure was because there is no connection to exchange
  1281                             // Other methods will still be tried, if they fail too null will be returned
  1282                             status = Globals.ReturnStatus.FailureNoConnection;
  1283                         }
  1284                     }
  1285 
  1286                     // Try using the mail item properties (non-exchange)
  1287                     if ((status != Globals.ReturnStatus.Success) &&
  1288                         (omi.SenderEmailType != "EX") &&
  1289                         (omi.SenderEmailAddress != null))
  1290                     {
  1291                         from = new PEPIdentity();
  1292                         from.Address = omi.SenderEmailAddress;
  1293                         from.Username = omi.SenderName;
  1294 
  1295                         // Add the contact force unencrypted property
  1296                         try
  1297                         {
  1298                             contact = sender.GetContact();
  1299 
  1300                             if (contact != null)
  1301                             {
  1302                                 from.IsForceUnencrypted = Globals.ThisAddIn.GetForceUnencrypted(contact);
  1303                                 Marshal.ReleaseComObject(contact);
  1304                                 contact = null;
  1305                             }
  1306                         }
  1307                         catch { }
  1308 
  1309                         // Add the UserID
  1310                         from.UserID = Globals.ThisAddIn.GetUserID(from.Address, contact);
  1311 
  1312                         status = Globals.ReturnStatus.Success;
  1313                     }
  1314                 }
  1315                 else // Outgoing
  1316                 {
  1317                     sendingAccount = omi.SendUsingAccount;
  1318                     if (sendingAccount != null)
  1319                     {
  1320                         status = PEPIdentity.GetOwnIdentity(sendingAccount.SmtpAddress, out from);
  1321                     }
  1322                 }
  1323             }
  1324             catch (Exception ex)
  1325             {
  1326                 status = Globals.ReturnStatus.Failure;
  1327                 Log.Error("GetFromIdentity: Failure occured, " + ex.ToString());
  1328             }
  1329             finally
  1330             {
  1331                 // Release objects
  1332                 if (contact != null)
  1333                 {
  1334                     Marshal.ReleaseComObject(contact);
  1335                     contact = null;
  1336                 }
  1337 
  1338                 if (sender != null)
  1339                 {
  1340                     Marshal.ReleaseComObject(sender);
  1341                     sender = null;
  1342                 }
  1343 
  1344                 if (exchSender != null)
  1345                 {
  1346                     Marshal.ReleaseComObject(exchSender);
  1347                     exchSender = null;
  1348                 }
  1349 
  1350                 if (sendingAccount != null)
  1351                 {
  1352                     Marshal.ReleaseComObject(sendingAccount);
  1353                     sendingAccount = null;
  1354                 }
  1355             }
  1356 
  1357             fromIdentity = from;
  1358             return (status);
  1359         }
  1360 
  1361         /// <summary>
  1362         /// Gets the default from/sender identity using the default Outlook user.
  1363         /// Warning: The result can be null.
  1364         /// </summary>
  1365         /// <param name="fromIdentity">The output default from/sender identity (may be null).</param>
  1366         /// <returns>The status of the method.</returns>
  1367         public static Globals.ReturnStatus GetDefaultFromIdentity(out PEPIdentity fromIdentity)
  1368         {
  1369             PEPIdentity from = null;
  1370             Outlook.NameSpace ns = null;
  1371             Outlook.Recipient currUser = null;
  1372             Globals.ReturnStatus status = Globals.ReturnStatus.Failure;
  1373 
  1374             try
  1375             {
  1376                 ns = Globals.ThisAddIn.Application.Session;
  1377                 currUser = ns.CurrentUser;
  1378 
  1379                 if (currUser != null)
  1380                 {
  1381                     status = PEPIdentity.GetOwnIdentity(currUser.Address, out from);
  1382                 }
  1383             }
  1384             catch (Exception ex)
  1385             {
  1386                 status = Globals.ReturnStatus.Failure;
  1387                 Log.Error("GetDefaultFromIdentity: Failure occured, " + ex.ToString());
  1388             }
  1389             finally
  1390             {
  1391                 // Release objects
  1392                 if (ns != null)
  1393                 {
  1394                     Marshal.ReleaseComObject(ns);
  1395                     ns = null;
  1396                 }
  1397 
  1398                 if (currUser != null)
  1399                 {
  1400                     Marshal.ReleaseComObject(currUser);
  1401                     currUser = null;
  1402                 }
  1403             }
  1404 
  1405             fromIdentity = from;
  1406             return (status);
  1407         }
  1408 
  1409         /// <summary>
  1410         /// Creates a new pEp identity from the given outlook recipient.
  1411         /// The output will never be null.
  1412         /// Warning: The result can contain identity groups.
  1413         /// </summary>
  1414         /// <param name="recipient">The recipient to determine pEp identity for.</param>
  1415         /// <param name="identity">The output pEp identity of the recipient (will never be null).</param>
  1416         /// <returns>The status of the method.</returns>
  1417         public static Globals.ReturnStatus Create(Outlook.Recipient recipient,
  1418                                                   out PEPIdentity identity)
  1419         {
  1420             bool identityCreated = false;
  1421             bool? forceUnencryptedProperty = null;
  1422             string tempAddress;
  1423             string tempUserName;
  1424             PEPIdentity newIdent = new PEPIdentity();
  1425             PEPIdentity newIdent2;
  1426             PEPIdentity member;
  1427             Outlook.OlAddressEntryUserType? dlType = null;
  1428             Outlook.AddressEntry addressEntry = null;
  1429             Outlook.AddressEntry currAddressEntry = null;
  1430             Outlook.ContactItem contact = null;
  1431             Outlook.ContactItem currContact = null;
  1432             Outlook.MAPIFolder contactsFolder = null;
  1433             Outlook.Recipient currRecipient = null;
  1434             Outlook.NameSpace ns = null;
  1435             Outlook.Store currStore = null;
  1436             Outlook.Stores stores = null;
  1437             Outlook.ExchangeDistributionList exchDL = null;
  1438             Outlook.AddressEntries exchDLMembers = null;
  1439             Outlook.ExchangeUser exchUser = null;
  1440             Globals.ReturnStatus sts;
  1441             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
  1442 
  1443             ///////////////////////////////////////////////////////////
  1444             // Pre-process
  1445             ///////////////////////////////////////////////////////////
  1446 
  1447             try
  1448             {
  1449                 // Get the address entry of the recipient
  1450                 addressEntry = recipient.AddressEntry;
  1451                 if (addressEntry == null)
  1452                 {
  1453                     // Attempt to resolve if necessary
  1454                     recipient.Resolve();
  1455                     addressEntry = recipient.AddressEntry;
  1456                 }
  1457 
  1458                 /* Determine if the recipient is a distribution list/contact group.
  1459                  * This will be used to decide further processing as DL can cause a significant performance issue.
  1460                  * 
  1461                  * The following rules apply:
  1462                  *  (1) If recipient.AddressEntry is null after being resolved, it's not a DL
  1463                  *  (2) If recipient.AddressEntry.AddressEntryUserType is either Outlook DL or Exchange DL type, it's a DL
  1464                  *  (3) Anything not falling into the above rules is not considered a DL
  1465                  */
  1466                 try
  1467                 {
  1468                     if ((addressEntry != null) &&
  1469                         ((addressEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olOutlookDistributionListAddressEntry) ||
  1470                          (addressEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeDistributionListAddressEntry)))
  1471                     {
  1472                         dlType = addressEntry.AddressEntryUserType;
  1473                     }
  1474                 }
  1475                 catch
  1476                 {
  1477                     dlType = null;
  1478                 }
  1479 
  1480                 // Add the contact force unencrypted property (and get contact)
  1481                 try
  1482                 {
  1483                     if (addressEntry != null)
  1484                     {
  1485                         // Save contact for later UserID calculation
  1486                         contact = addressEntry.GetContact();
  1487                         if (contact != null)
  1488                         {
  1489                             forceUnencryptedProperty = Globals.ThisAddIn.GetForceUnencrypted(contact);
  1490                         }
  1491                     }
  1492                 }
  1493                 catch
  1494                 {
  1495                     forceUnencryptedProperty = null;
  1496                 }
  1497             }
  1498             catch { }
  1499 
  1500             ///////////////////////////////////////////////////////////
  1501             // Handle Outlook distribution lists
  1502             ///////////////////////////////////////////////////////////
  1503 
  1504             // Handle when the recipient is a local Outlook distribution list/contact group
  1505             if ((identityCreated == false) &&
  1506                 (dlType != null) &&
  1507                 (((Outlook.OlAddressEntryUserType)dlType) == Outlook.OlAddressEntryUserType.olOutlookDistributionListAddressEntry))
  1508             {
  1509                 try
  1510                 {
  1511                     ns = Globals.ThisAddIn.Application.Session;
  1512                     stores = ns.Stores;
  1513 
  1514                     // Note: index starts at 1
  1515                     for (int i = 1; i <= stores.Count; i++)
  1516                     {
  1517                         currStore = stores[i];
  1518 
  1519                         if (currStore.ExchangeStoreType == Outlook.OlExchangeStoreType.olNotExchange)
  1520                         {
  1521                             contactsFolder = currStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
  1522 
  1523                             foreach (Outlook.DistListItem currList in contactsFolder.Items.OfType<Outlook.DistListItem>())
  1524                             {
  1525                                 // If the recipient name is equal to a distribution list name, assume it is a contact group
  1526                                 if ((currList.DLName != null) &&
  1527                                     (currList.DLName == recipient.Name))
  1528                                 {
  1529                                     newIdent = new PEPIdentity();
  1530                                     newIdent.Address = null;
  1531                                     newIdent.Username = recipient.Name;
  1532                                     newIdent.IsForceUnencrypted = forceUnencryptedProperty;
  1533 
  1534                                     // Note: index starts at 1
  1535                                     for (int j = 1; j <= currList.MemberCount; j++)
  1536                                     {
  1537                                         currRecipient = currList.GetMember(j);
  1538 
  1539                                         // Recursively get the member, this should handle contact groups in contact groups
  1540                                         sts = PEPIdentity.Create(currRecipient, out member);
  1541                                         newIdent.Members.Add(member);
  1542 
  1543                                         // Free currRecipient
  1544                                         if (currRecipient != null)
  1545                                         {
  1546                                             Marshal.ReleaseComObject(currRecipient);
  1547                                             currRecipient = null;
  1548                                         }
  1549                                     }
  1550 
  1551                                     identityCreated = true;
  1552                                 }
  1553 
  1554                                 // Free currList
  1555                                 if (currList != null)
  1556                                 {
  1557                                     Marshal.ReleaseComObject(currList);
  1558                                 }
  1559 
  1560                                 // Stop searching
  1561                                 if (identityCreated)
  1562                                 {
  1563                                     break;
  1564                                 }
  1565                             }
  1566                         }
  1567 
  1568                         // Free resources
  1569                         if (currStore != null)
  1570                         {
  1571                             Marshal.ReleaseComObject(currStore);
  1572                             currStore = null;
  1573                         }
  1574 
  1575                         if (contactsFolder != null)
  1576                         {
  1577                             Marshal.ReleaseComObject(contactsFolder);
  1578                             contactsFolder = null;
  1579                         }
  1580                     }
  1581                 }
  1582                 catch
  1583                 {
  1584                     identityCreated = false;
  1585                 }
  1586                 finally
  1587                 {
  1588                     // Free resources
  1589                     if (ns != null)
  1590                     {
  1591                         Marshal.ReleaseComObject(ns);
  1592                         ns = null;
  1593                     }
  1594 
  1595                     if (currStore != null)
  1596                     {
  1597                         Marshal.ReleaseComObject(currStore);
  1598                         currStore = null;
  1599                     }
  1600 
  1601                     if (stores != null)
  1602                     {
  1603                         Marshal.ReleaseComObject(stores);
  1604                         stores = null;
  1605                     }
  1606 
  1607                     if (contactsFolder != null)
  1608                     {
  1609                         Marshal.ReleaseComObject(contactsFolder);
  1610                         contactsFolder = null;
  1611                     }
  1612 
  1613                     if (currRecipient != null)
  1614                     {
  1615                         Marshal.ReleaseComObject(currRecipient);
  1616                         currRecipient = null;
  1617                     }
  1618                 }
  1619             }
  1620 
  1621             ///////////////////////////////////////////////////////////
  1622             // Handle exchange distribution lists
  1623             ///////////////////////////////////////////////////////////
  1624 
  1625             // Handle when the recipient is an exchange distribution list
  1626             // See: https://msdn.microsoft.com/en-us/library/office/bb645998.aspx
  1627             if ((identityCreated == false) &&
  1628                 (addressEntry != null) &&
  1629                 (dlType != null) &&
  1630                 (((Outlook.OlAddressEntryUserType)dlType) == Outlook.OlAddressEntryUserType.olExchangeDistributionListAddressEntry))
  1631             {
  1632                 try
  1633                 {
  1634                     exchDL = addressEntry.GetExchangeDistributionList();
  1635                     exchDLMembers = exchDL.GetExchangeDistributionListMembers();
  1636 
  1637                     // Add group
  1638                     newIdent = new PEPIdentity();
  1639                     newIdent.Address = null;
  1640                     newIdent.Username = exchDL.Name;
  1641                     newIdent.UserID = null;
  1642 
  1643                     // Add members
  1644                     if (exchDLMembers != null)
  1645                     {
  1646                         for (int i = 1; i <= exchDLMembers.Count; i++)
  1647                         {
  1648                             try
  1649                             {
  1650                                 currAddressEntry = exchDLMembers[i];
  1651                                 exchUser = currAddressEntry.GetExchangeUser();
  1652 
  1653                                 if (exchUser != null)
  1654                                 {
  1655                                     newIdent2 = new PEPIdentity();
  1656                                     newIdent2.Address = exchUser.PrimarySmtpAddress;
  1657                                     newIdent2.Username = exchUser.Name;
  1658 
  1659                                     // Add the contact force unencrypted property
  1660                                     currContact = null;
  1661                                     try
  1662                                     {
  1663                                         currContact = exchUser.GetContact();
  1664 
  1665                                         if (currContact != null)
  1666                                         {
  1667                                             newIdent2.IsForceUnencrypted = Globals.ThisAddIn.GetForceUnencrypted(currContact);
  1668                                         }
  1669                                     }
  1670                                     catch
  1671                                     {
  1672                                         currContact = null;
  1673                                     }
  1674 
  1675                                     newIdent2.UserID = Globals.ThisAddIn.GetUserID(newIdent2.Address, currContact);
  1676                                     newIdent.Members.Add(newIdent2);
  1677                                 }
  1678                             }
  1679                             catch { }
  1680                             finally
  1681                             {
  1682                                 if (currAddressEntry != null)
  1683                                 {
  1684                                     Marshal.ReleaseComObject(currAddressEntry);
  1685                                     currAddressEntry = null;
  1686                                 }
  1687 
  1688                                 if (exchUser != null)
  1689                                 {
  1690                                     Marshal.ReleaseComObject(exchUser);
  1691                                     exchUser = null;
  1692                                 }
  1693 
  1694                                 if (currContact != null)
  1695                                 {
  1696                                     Marshal.ReleaseComObject(currContact);
  1697                                     currContact = null;
  1698                                 }
  1699                             }
  1700                         }
  1701                     }
  1702 
  1703                     identityCreated = true;
  1704                 }
  1705                 catch { }
  1706                 finally
  1707                 {
  1708                     if (exchDL != null)
  1709                     {
  1710                         Marshal.ReleaseComObject(exchDL);
  1711                         exchDL = null;
  1712                     }
  1713 
  1714                     if (exchDLMembers != null)
  1715                     {
  1716                         Marshal.ReleaseComObject(exchDLMembers);
  1717                         exchDLMembers = null;
  1718                     }
  1719 
  1720                     if (currAddressEntry != null)
  1721                     {
  1722                         Marshal.ReleaseComObject(currAddressEntry);
  1723                         currAddressEntry = null;
  1724                     }
  1725 
  1726                     if (exchUser != null)
  1727                     {
  1728                         Marshal.ReleaseComObject(exchUser);
  1729                         exchUser = null;
  1730                     }
  1731 
  1732                     if (currContact != null)
  1733                     {
  1734                         Marshal.ReleaseComObject(currContact);
  1735                         currContact = null;
  1736                     }
  1737                 }
  1738             }
  1739 
  1740             ///////////////////////////////////////////////////////////
  1741             // Handle standard recipients (not lists)
  1742             ///////////////////////////////////////////////////////////
  1743 
  1744             if (identityCreated == false)
  1745             {
  1746                 exchUser = null;
  1747                 tempAddress = null;
  1748                 tempUserName = null;
  1749                 newIdent = new PEPIdentity();
  1750                 newIdent.IsForceUnencrypted = forceUnencryptedProperty;
  1751 
  1752                 // Handle any exchange users first
  1753                 if ((addressEntry != null) &&
  1754                     (addressEntry.Type == "EX"))
  1755                 {
  1756                     try
  1757                     {
  1758                         exchUser = addressEntry.GetExchangeUser();
  1759                         tempAddress = exchUser.PrimarySmtpAddress;
  1760                         tempUserName = exchUser.Name;
  1761                     }
  1762                     catch { }
  1763                     finally
  1764                     {
  1765                         if (exchUser != null)
  1766                         {
  1767                             Marshal.ReleaseComObject(exchUser);
  1768                             exchUser = null;
  1769                         }
  1770                     }
  1771                 }
  1772 
  1773                 /* Check for an address from the recipient's attached SMTP address
  1774                  * This is the microsoft recommented method.
  1775                  * See: https://msdn.microsoft.com/en-us/library/office/ff868695.aspx
  1776                  */
  1777                 if (string.IsNullOrWhiteSpace(tempAddress))
  1778                 {
  1779                     try
  1780                     {
  1781                         tempAddress = (string)MAPIHelper.GetProperty(recipient, MAPIProperty.PidTagSmtpAddress);
  1782                     }
  1783                     catch { }
  1784                 }
  1785 
  1786                 // Get user name from the recipient
  1787                 if (string.IsNullOrWhiteSpace(tempUserName))
  1788                 {
  1789                     tempUserName = recipient.Name;
  1790                 }
  1791 
  1792                 // As a fallback, get information from the associated contact
  1793                 if (string.IsNullOrWhiteSpace(tempAddress))
  1794                 {
  1795                     try
  1796                     {
  1797                         tempAddress = contact.Email1Address;
  1798                         tempUserName = contact.Email1DisplayName;
  1799                     }
  1800                     catch { }
  1801                 }
  1802 
  1803                 // Add address if possible
  1804                 if (string.IsNullOrWhiteSpace(tempAddress) == false)
  1805                 {
  1806                     newIdent.Address = tempAddress.Trim();
  1807                 }
  1808 
  1809                 // Add username if possible
  1810                 if (string.IsNullOrWhiteSpace(tempUserName) == false)
  1811                 {
  1812                     newIdent.Username = tempUserName.Trim();
  1813                 }
  1814 
  1815                 // Get the unique user ID if possible
  1816                 if (string.IsNullOrEmpty(newIdent.Address) == false)
  1817                 {
  1818                     newIdent.UserID = Globals.ThisAddIn.GetUserID(newIdent.Address, contact);
  1819                 }
  1820 
  1821                 identityCreated = true;
  1822             }
  1823 
  1824             // Free resources
  1825             if (contact != null)
  1826             {
  1827                 Marshal.ReleaseComObject(contact);
  1828                 contact = null;
  1829             }
  1830 
  1831             if (addressEntry != null)
  1832             {
  1833                 Marshal.ReleaseComObject(addressEntry);
  1834                 addressEntry = null;
  1835             }
  1836 
  1837             identity = newIdent;
  1838             return (status);
  1839         }
  1840 
  1841         /// <summary>
  1842         /// Recursivley converts the given identities into a 'flat' list of any members.
  1843         /// This will remove groups (hierarchy) and convert a group into it's members.
  1844         /// The list will be build by calling each identity's .ToFlatList method.
  1845         /// </summary>
  1846         /// <param name="identities">The identities to get the flat list for.</param>
  1847         /// <returns>The flat list of pEp identities.</returns>
  1848         public static List<PEPIdentity> ToFlatList(List<PEPIdentity> identities)
  1849         {
  1850             List<PEPIdentity> result = new List<PEPIdentity>();
  1851             List<PEPIdentity> singleList;
  1852 
  1853             for (int i = 0; i < identities.Count; i++)
  1854             {
  1855                 singleList = identities[i].ToFlatList();
  1856 
  1857                 for (int j = 0; j < singleList.Count; j++)
  1858                 {
  1859                     result.Add(singleList[j]);
  1860                 }
  1861             }
  1862 
  1863             return (result);
  1864         }
  1865     }
  1866 }