373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| /*
 | |
|   KeePass Password Safe - The Open-Source Password Manager
 | |
|   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
 | |
| 
 | |
|   This program is free software; you can redistribute it and/or modify
 | |
|   it under the terms of the GNU General Public License as published by
 | |
|   the Free Software Foundation; either version 2 of the License, or
 | |
|   (at your option) any later version.
 | |
| 
 | |
|   This program is distributed in the hope that it will be useful,
 | |
|   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|   GNU General Public License for more details.
 | |
| 
 | |
|   You should have received a copy of the GNU General Public License
 | |
|   along with this program; if not, write to the Free Software
 | |
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | |
| */
 | |
| 
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Diagnostics;
 | |
| using System.Text;
 | |
| using System.Text.RegularExpressions;
 | |
| using System.Xml;
 | |
| using System.Xml.XPath;
 | |
| 
 | |
| using KeePassLib.Collections;
 | |
| using KeePassLib.Delegates;
 | |
| using KeePassLib.Interfaces;
 | |
| using KeePassLib.Security;
 | |
| using KeePassLib.Serialization;
 | |
| using KeePassLib.Utility;
 | |
| 
 | |
| namespace KeePassLib
 | |
| {
 | |
|     public sealed partial class PwGroup
 | |
|     {
 | |
|         private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added
 | |
|         public const string SearchContextUuid = "Uuid";
 | |
|         public const string SearchContextParentGroup = "Parent Group";
 | |
|         public const string SearchContextTags = "Tags";
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Search this group and all subgroups for entries.
 | |
|         /// </summary>
 | |
|         /// <param name="sp">Specifies the search method.</param>
 | |
|         /// <param name="listStorage">Entry list in which the search results will
 | |
|         /// be stored.</param>
 | |
|         public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage)
 | |
|         {
 | |
|             SearchEntries(sp, listStorage, null);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Search this group and all subgroups for entries.
 | |
|         /// </summary>
 | |
|         /// <param name="sp">Specifies the search method.</param>
 | |
|         /// <param name="listStorage">Entry list in which the search results will
 | |
|         /// be stored.</param>
 | |
|         /// <param name="slStatus">Optional status reporting object.</param>
 | |
|         public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
 | |
|             IStatusLogger slStatus)
 | |
|         {
 | |
|             SearchEntries(sp, listStorage, null, slStatus);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Search this group and all subgroups for entries.
 | |
|         /// </summary>
 | |
|         /// <param name="sp">Specifies the search method.</param>
 | |
|         /// <param name="listStorage">Entry list in which the search results will
 | |
|         /// be stored.</param>
 | |
|         /// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param>
 | |
|         public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
 | |
|             IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
 | |
|             IStatusLogger slStatus)
 | |
|         {
 | |
| 
 | |
|             if (sp == null)
 | |
|             {
 | |
|                 Debug.Assert(false);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (listStorage == null)
 | |
|             {
 | |
|                 Debug.Assert(false);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             ulong uCurEntries = 0, uTotalEntries = 0;
 | |
| 
 | |
|             List<string> lTerms = StrUtil.SplitSearchTerms(sp.SearchString);
 | |
|             if ((lTerms.Count <= 1) || sp.RegularExpression)
 | |
|             {
 | |
|                 if (slStatus != null) uTotalEntries = GetEntriesCount(true);
 | |
|                 SearchEntriesSingle(sp, listStorage, resultContexts, slStatus, ref uCurEntries,
 | |
|                     uTotalEntries);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Search longer strings first (for improved performance)
 | |
|             lTerms.Sort(StrUtil.CompareLengthGt);
 | |
| 
 | |
|             string strFullSearch = sp.SearchString; // Backup
 | |
| 
 | |
|             PwGroup pg = this;
 | |
|             for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm)
 | |
|             {
 | |
|                 // Update counters for a better state guess
 | |
|                 if (slStatus != null)
 | |
|                 {
 | |
|                     ulong uRemRounds = (ulong) (lTerms.Count - iTerm);
 | |
|                     uTotalEntries = uCurEntries + (uRemRounds *
 | |
|                                                    pg.GetEntriesCount(true));
 | |
|                 }
 | |
| 
 | |
|                 PwGroup pgNew = new PwGroup();
 | |
| 
 | |
|                 sp.SearchString = lTerms[iTerm];
 | |
| 
 | |
|                 bool bNegate = false;
 | |
|                 if (sp.SearchString.StartsWith("-"))
 | |
|                 {
 | |
|                     sp.SearchString = sp.SearchString.Substring(1);
 | |
|                     bNegate = (sp.SearchString.Length > 0);
 | |
|                 }
 | |
| 
 | |
|                 if (!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
 | |
|                     ref uCurEntries, uTotalEntries))
 | |
|                 {
 | |
|                     pg = null;
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 if (bNegate)
 | |
|                 {
 | |
|                     PwObjectList<PwEntry> lCand = pg.GetEntries(true);
 | |
| 
 | |
|                     pg = new PwGroup();
 | |
|                     foreach (PwEntry peCand in lCand)
 | |
|                     {
 | |
|                         if (pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand);
 | |
|                     }
 | |
|                 }
 | |
|                 else pg = pgNew;
 | |
|             }
 | |
| 
 | |
|             if (pg != null) listStorage.Add(pg.Entries);
 | |
|             sp.SearchString = strFullSearch; // Restore
 | |
|         }
 | |
| 
 | |
|         private bool SearchEntriesSingle(SearchParameters spIn,
 | |
|             PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
 | |
|             IStatusLogger slStatus,
 | |
|             ref ulong uCurEntries, ulong uTotalEntries)
 | |
|         {
 | |
|             SearchParameters sp = spIn.Clone();
 | |
|             if (sp.SearchString == null)
 | |
|             {
 | |
|                 Debug.Assert(false);
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             sp.SearchString = sp.SearchString.Trim();
 | |
| 
 | |
|             bool bTitle = sp.SearchInTitles;
 | |
|             bool bUserName = sp.SearchInUserNames;
 | |
|             bool bPassword = sp.SearchInPasswords;
 | |
|             bool bUrl = sp.SearchInUrls;
 | |
|             bool bNotes = sp.SearchInNotes;
 | |
|             bool bOther = sp.SearchInOther;
 | |
|             bool bUuids = sp.SearchInUuids;
 | |
|             bool bGroupName = sp.SearchInGroupNames;
 | |
|             bool bTags = sp.SearchInTags;
 | |
|             bool bExcludeExpired = sp.ExcludeExpired;
 | |
|             bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled;
 | |
| 
 | |
|             DateTime dtNow = DateTime.Now;
 | |
| 
 | |
|             Regex rx = null;
 | |
|             if (sp.RegularExpression)
 | |
|             {
 | |
|                 RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled
 | |
|                 if ((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) ||
 | |
| #if !KeePassUAP
 | |
|                     (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) ||
 | |
| #endif
 | |
|                     (sp.ComparisonMode == StringComparison.OrdinalIgnoreCase))
 | |
|                 {
 | |
|                     ro |= RegexOptions.IgnoreCase;
 | |
|                 }
 | |
| 
 | |
|                 rx = new Regex(sp.SearchString, ro);
 | |
|             }
 | |
| 
 | |
|             ulong uLocalCurEntries = uCurEntries;
 | |
| 
 | |
|             EntryHandler eh = null;
 | |
|             if (sp.SearchString.Length <= 0) // Report all
 | |
|             {
 | |
|                 eh = delegate(PwEntry pe)
 | |
|                 {
 | |
|                     if (slStatus != null)
 | |
|                     {
 | |
|                         if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
 | |
|                                                            100UL) / uTotalEntries))) return false;
 | |
|                         ++uLocalCurEntries;
 | |
|                     }
 | |
| 
 | |
|                     if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
 | |
|                         return true; // Skip
 | |
|                     if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
 | |
|                         return true; // Skip
 | |
| 
 | |
|                     listStorage.Add(pe);
 | |
|                     return true;
 | |
|                 };
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 eh = delegate(PwEntry pe)
 | |
|                 {
 | |
|                     if (slStatus != null)
 | |
|                     {
 | |
|                         if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
 | |
|                                                            100UL) / uTotalEntries))) return false;
 | |
|                         ++uLocalCurEntries;
 | |
|                     }
 | |
| 
 | |
|                     if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
 | |
|                         return true; // Skip
 | |
|                     if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
 | |
|                         return true; // Skip
 | |
| 
 | |
|                     uint uInitialResults = listStorage.UCount;
 | |
| 
 | |
|                     foreach (KeyValuePair<string, ProtectedString> kvp in pe.Strings)
 | |
|                     {
 | |
|                         string strKey = kvp.Key;
 | |
| 
 | |
|                         if (strKey == PwDefs.TitleField)
 | |
|                         {
 | |
|                             if (bTitle)
 | |
|                                 SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                     rx, pe, listStorage, resultContexts, strKey);
 | |
|                         }
 | |
|                         else if (strKey == PwDefs.UserNameField)
 | |
|                         {
 | |
|                             if (bUserName)
 | |
|                                 SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                     rx, pe, listStorage, resultContexts, strKey);
 | |
|                         }
 | |
|                         else if (strKey == PwDefs.PasswordField)
 | |
|                         {
 | |
|                             if (bPassword)
 | |
|                                 SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                     rx, pe, listStorage, resultContexts, strKey);
 | |
|                         }
 | |
|                         else if (strKey == PwDefs.UrlField)
 | |
|                         {
 | |
|                             if (bUrl)
 | |
|                                 SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                     rx, pe, listStorage, resultContexts, strKey);
 | |
|                         }
 | |
|                         else if (strKey == PwDefs.NotesField)
 | |
|                         {
 | |
|                             if (bNotes)
 | |
|                                 SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                     rx, pe, listStorage, resultContexts, strKey);
 | |
|                         }
 | |
|                         else if (bOther)
 | |
|                             SearchEvalAdd(sp, kvp.Value.ReadString(),
 | |
|                                 rx, pe, listStorage, resultContexts, strKey);
 | |
| 
 | |
|                         // An entry can match only once => break if we have added it
 | |
|                         if (listStorage.UCount > uInitialResults) break;
 | |
|                     }
 | |
| 
 | |
|                     if (bUuids && (listStorage.UCount == uInitialResults))
 | |
|                         SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts,
 | |
|                             SearchContextTags);
 | |
| 
 | |
|                     if (bGroupName && (listStorage.UCount == uInitialResults) &&
 | |
|                         (pe.ParentGroup != null))
 | |
|                         SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts,
 | |
|                             SearchContextParentGroup);
 | |
| 
 | |
|                     if (bTags)
 | |
|                     {
 | |
|                         foreach (string strTag in pe.Tags)
 | |
|                         {
 | |
|                             if (listStorage.UCount != uInitialResults) break; // Match
 | |
| 
 | |
|                             SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags);
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     return true;
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             if (!PreOrderTraverseTree(null, eh)) return false;
 | |
|             uCurEntries = uLocalCurEntries;
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         private static void SearchEvalAdd(SearchParameters sp, string strDataField,
 | |
|             Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults,
 | |
|             IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts, string contextFieldName)
 | |
|         {
 | |
|             bool bMatch = false;
 | |
|             int matchPos;
 | |
|             if (rx == null)
 | |
|             {
 | |
|                 matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
 | |
|                 bMatch = matchPos >= 0;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 var match = rx.Match(strDataField);
 | |
|                 bMatch = match.Success;
 | |
|                 matchPos = match.Index;
 | |
|             }
 | |
| 
 | |
|             if (!bMatch && (sp.DataTransformationFn != null))
 | |
|             {
 | |
|                 string strCmp = sp.DataTransformationFn(strDataField, pe);
 | |
|                 if (!object.ReferenceEquals(strCmp, strDataField))
 | |
|                 {
 | |
|                     if (rx == null)
 | |
|                     {
 | |
|                         matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
 | |
|                         bMatch = matchPos >= 0;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         var match = rx.Match(strCmp);
 | |
|                         bMatch = match.Success;
 | |
|                         matchPos = match.Index;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (bMatch)
 | |
|             {
 | |
|                 lResults.Add(pe);
 | |
| 
 | |
|                 if (resultContexts != null)
 | |
|                 {
 | |
|                     // Trim the value if necessary
 | |
|                     var contextString = strDataField;
 | |
|                     if (contextString.Length > SearchContextStringMaxLength)
 | |
|                     {
 | |
|                         // Start 10% before actual data, and don't run over
 | |
|                         var startPos = Math.Max(0,
 | |
|                             Math.Min(matchPos - (SearchContextStringMaxLength / 10),
 | |
|                                 contextString.Length - SearchContextStringMaxLength));
 | |
|                         contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) +
 | |
|                                         ((startPos + SearchContextStringMaxLength < contextString.Length)
 | |
|                                             ? " …"
 | |
|                                             : null);
 | |
|                     }
 | |
| 
 | |
|                     resultContexts[pe.Uuid] = new KeyValuePair<string, string>(contextFieldName, contextString);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 | 
