/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2013 Dominik Reichl 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.RegularExpressions; using KeePassLib.Collections; using KeePassLib.Delegates; using KeePassLib.Interfaces; using KeePassLib.Security; using KeePassLib.Utility; namespace KeePassLib { /// /// A group containing several password entries. /// public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable { 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"; public const bool DefaultAutoTypeEnabled = true; public const bool DefaultSearchingEnabled = true; private PwObjectList m_listGroups = new PwObjectList(); private PwObjectList m_listEntries = new PwObjectList(); private PwGroup m_pParentGroup = null; private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; private string m_tParentGroupLastModLazy; private PwUuid m_uuid = PwUuid.Zero; private string m_strName = string.Empty; private string m_strNotes = string.Empty; private PwIcon m_pwIcon = PwIcon.Folder; private PwUuid m_pwCustomIconID = PwUuid.Zero; private DateTime m_tCreation = PwDefs.DtDefaultNow; private DateTime m_tLastMod = PwDefs.DtDefaultNow; private DateTime m_tLastAccess = PwDefs.DtDefaultNow; private DateTime m_tExpire = PwDefs.DtDefaultNow; private string m_tCreationLazy; private string m_tLastModLazy; private string m_tLastAccessLazy; private string m_tExpireLazy; private bool m_bExpires = false; private ulong m_uUsageCount = 0; private bool m_bIsExpanded = true; private bool m_bVirtual = false; private string m_strDefaultAutoTypeSequence = string.Empty; private bool? m_bEnableAutoType = null; private bool? m_bEnableSearching = null; private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; /// /// UUID of this group. /// public PwUuid Uuid { get { return m_uuid; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_uuid = value; } } /// /// The name of this group. Cannot be null. /// public string Name { get { return m_strName; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_strName = value; } } /// /// Comments about this group. Cannot be null. /// public string Notes { get { return m_strNotes; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_strNotes = value; } } /// /// Icon of the group. /// public PwIcon IconId { get { return m_pwIcon; } set { m_pwIcon = value; } } /// /// Get the custom icon ID. This value is 0, if no custom icon is /// being used (i.e. the icon specified by the IconID property /// should be displayed). /// public PwUuid CustomIconUuid { get { return m_pwCustomIconID; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_pwCustomIconID = value; } } /// /// Reference to the group to which this group belongs. May be null. /// public PwGroup ParentGroup { get { return m_pParentGroup; } /// Plugins: use PwGroup.AddGroup instead. internal set { Debug.Assert(value != this); m_pParentGroup = value; } } /// /// The date/time when the location of the object was last changed. /// public DateTime LocationChanged { get { return GetLazyTime(ref m_tParentGroupLastModLazy, ref m_tParentGroupLastMod); } set { m_tParentGroupLastMod = value; m_tParentGroupLastModLazy = null; } } public void SetLazyLocationChanged(string xmlDateTime) { m_tParentGroupLastModLazy = xmlDateTime; } /// /// A flag that specifies if the group is shown as expanded or /// collapsed in the user interface. /// public bool IsExpanded { get { return m_bIsExpanded; } set { m_bIsExpanded = value; } } /// /// The date/time when this group was created. /// public DateTime CreationTime { get { return GetLazyTime(ref m_tCreationLazy, ref m_tCreation); } set { m_tCreation = value; m_tCreationLazy = null; } } public void SetLazyCreationTime(string xmlDateTime) { m_tCreationLazy = xmlDateTime; } /// /// The date/time when this group was last accessed (read). /// public DateTime LastAccessTime { get { return GetLazyTime(ref m_tLastAccessLazy, ref m_tLastAccess); } set { m_tLastAccess = value; m_tLastAccessLazy = null; } } public void SetLazyLastAccessTime(string xmlDateTime) { m_tLastAccessLazy = xmlDateTime; } /// /// The date/time when this group was last modified. /// public DateTime LastModificationTime { get { return GetLazyTime(ref m_tLastModLazy, ref m_tLastMod); } set { m_tLastMod = value; m_tLastModLazy = null; } } public void SetLazyLastModificationTime(string xmlDateTime) { m_tLastModLazy = xmlDateTime; } /// /// The date/time when this group expires. Use the Expires property /// to specify if the group does actually expire or not. /// public DateTime ExpiryTime { get { return GetLazyTime(ref m_tExpireLazy, ref m_tExpire); } set { m_tExpire = value; m_tExpireLazy = null; } } public void SetLazyExpiryTime(string xmlDateTime) { m_tExpireLazy = xmlDateTime; } /// /// Flag that determines if the group expires. /// public bool Expires { get { return m_bExpires; } set { m_bExpires = value; } } /// /// Get or set the usage count of the group. To increase the usage /// count by one, use the Touch function. /// public ulong UsageCount { get { return m_uUsageCount; } set { m_uUsageCount = value; } } /// /// Get a list of subgroups in this group. /// public PwObjectList Groups { get { return m_listGroups; } } /// /// Get a list of entries in this group. /// public PwObjectList Entries { get { return m_listEntries; } } /// /// A flag specifying whether this group is virtual or not. Virtual /// groups can contain links to entries stored in other groups. /// Note that this flag has to be interpreted and set by the calling /// code; it won't prevent you from accessing and modifying the list /// of entries in this group in any way. /// public bool IsVirtual { get { return m_bVirtual; } set { m_bVirtual = value; } } /// /// Default auto-type keystroke sequence for all entries in /// this group. This property can be an empty string, which /// means that the value should be inherited from the parent. /// public string DefaultAutoTypeSequence { get { return m_strDefaultAutoTypeSequence; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_strDefaultAutoTypeSequence = value; } } public bool? EnableAutoType { get { return m_bEnableAutoType; } set { m_bEnableAutoType = value; } } public bool? EnableSearching { get { return m_bEnableSearching; } set { m_bEnableSearching = value; } } public PwUuid LastTopVisibleEntry { get { return m_pwLastTopVisibleEntry; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_pwLastTopVisibleEntry = value; } } public static EventHandler GroupTouched; public EventHandler Touched; /// /// Construct a new, empty group. /// public PwGroup() { } /// /// Construct a new, empty group. /// /// Create a new UUID for this group. /// Set creation, last access and last modification times to the current time. public PwGroup(bool bCreateNewUuid, bool bSetTimes) { if(bCreateNewUuid) m_uuid = new PwUuid(true); if(bSetTimes) { m_tCreation = m_tLastMod = m_tLastAccess = m_tParentGroupLastMod = DateTime.Now; } } /// /// Construct a new group. /// /// Create a new UUID for this group. /// Set creation, last access and last modification times to the current time. /// Name of the new group. /// Icon of the new group. public PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon) { if(bCreateNewUuid) m_uuid = new PwUuid(true); if(bSetTimes) { m_tCreation = m_tLastMod = m_tLastAccess = m_tParentGroupLastMod = DateTime.Now; } if(strName != null) m_strName = strName; m_pwIcon = pwIcon; } /// /// Deeply clone the current group. The returned group will be an exact /// value copy of the current object (including UUID, etc.). /// /// Exact value copy of the current PwGroup object. public PwGroup CloneDeep() { PwGroup pg = new PwGroup(false, false); pg.m_uuid = m_uuid; // PwUuid is immutable pg.m_listGroups = m_listGroups.CloneDeep(); pg.m_listEntries = m_listEntries.CloneDeep(); pg.m_pParentGroup = m_pParentGroup; pg.m_tParentGroupLastMod = m_tParentGroupLastMod; pg.m_tParentGroupLastModLazy = m_tParentGroupLastModLazy; pg.m_strName = m_strName; pg.m_strNotes = m_strNotes; pg.m_pwIcon = m_pwIcon; pg.m_pwCustomIconID = m_pwCustomIconID; pg.m_tCreation = m_tCreation; pg.m_tExpire = m_tExpire; pg.m_tLastAccess = m_tLastAccess; pg.m_tLastMod = m_tLastMod; pg.m_bExpires = m_bExpires; pg.m_uUsageCount = m_uUsageCount; pg.m_tCreationLazy = m_tCreationLazy; pg.m_tLastModLazy = m_tLastModLazy; pg.m_tLastAccessLazy = m_tLastAccessLazy; pg.m_tExpireLazy = m_tExpireLazy; pg.m_bIsExpanded = m_bIsExpanded; pg.m_bVirtual = m_bVirtual; pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence; pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; return pg; } public PwGroup CloneStructure() { PwGroup pg = new PwGroup(false, false); pg.m_uuid = m_uuid; // PwUuid is immutable pg.m_tParentGroupLastMod = m_tParentGroupLastMod; // Do not assign m_pParentGroup foreach(PwGroup pgSub in m_listGroups) pg.AddGroup(pgSub.CloneStructure(), true); foreach(PwEntry peSub in m_listEntries) pg.AddEntry(peSub.CloneStructure(), true); return pg; } /// /// Assign properties to the current group based on a template group. /// /// Template group. Must not be null. /// Only set the properties of the template group /// if it is newer than the current one. /// If true, the /// LocationChanged property is copied, otherwise not. public void AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer, bool bAssignLocationChanged) { Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate"); if(bOnlyIfNewer && (TimeUtil.Compare(pgTemplate.LastModificationTime,LastModificationTime, true) < 0)) return; // Template UUID should be the same as the current one Debug.Assert(m_uuid.Equals(pgTemplate.m_uuid)); m_uuid = pgTemplate.m_uuid; if(bAssignLocationChanged) m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod; m_strName = pgTemplate.m_strName; m_strNotes = pgTemplate.m_strNotes; m_pwIcon = pgTemplate.m_pwIcon; m_pwCustomIconID = pgTemplate.m_pwCustomIconID; m_tCreation = pgTemplate.CreationTime; m_tLastMod = pgTemplate.LastModificationTime; m_tLastAccess = pgTemplate.LastAccessTime; m_tExpire = pgTemplate.ExpiryTime; m_bExpires = pgTemplate.m_bExpires; m_uUsageCount = pgTemplate.m_uUsageCount; m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence; m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; } /// /// Touch the group. This function updates the internal last access /// time. If the parameter is true, /// the last modification time gets updated, too. /// /// Modify last modification time. public void Touch(bool bModified) { Touch(bModified, true); } /// /// Touch the group. This function updates the internal last access /// time. If the parameter is true, /// the last modification time gets updated, too. /// /// Modify last modification time. /// If true, all parent objects /// get touched, too. public void Touch(bool bModified, bool bTouchParents) { m_tLastAccess = DateTime.Now; ++m_uUsageCount; if(bModified) m_tLastMod = m_tLastAccess; if(this.Touched != null) this.Touched(this, new ObjectTouchedEventArgs(this, bModified, bTouchParents)); if(PwGroup.GroupTouched != null) PwGroup.GroupTouched(this, new ObjectTouchedEventArgs(this, bModified, bTouchParents)); if(bTouchParents && (m_pParentGroup != null)) m_pParentGroup.Touch(bModified, true); } /// /// Get number of groups and entries in the current group. This function /// can also traverse through all subgroups and accumulate their counts /// (recursive mode). /// /// If this parameter is true, all /// subgroups and entries in subgroups will be counted and added to /// the returned value. If it is false, only the number of /// subgroups and entries of the current group is returned. /// Number of subgroups. /// Number of entries. public void GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries) { if(bRecursive) { uint uTotalGroups = m_listGroups.UCount; uint uTotalEntries = m_listEntries.UCount; uint uSubGroupCount, uSubEntryCount; foreach(PwGroup pg in m_listGroups) { pg.GetCounts(true, out uSubGroupCount, out uSubEntryCount); uTotalGroups += uSubGroupCount; uTotalEntries += uSubEntryCount; } uNumGroups = uTotalGroups; uNumEntries = uTotalEntries; } else // !bRecursive { uNumGroups = m_listGroups.UCount; uNumEntries = m_listEntries.UCount; } } public uint GetEntriesCount(bool bRecursive) { uint uGroups, uEntries; GetCounts(bRecursive, out uGroups, out uEntries); return uEntries; } /// /// Traverse the group/entry tree in the current group. Various traversal /// methods are available. /// /// Specifies the traversal method. /// Function that performs an action on /// the currently visited group (see GroupHandler for more). /// This parameter may be null, in this case the tree is traversed but /// you don't get notifications for each visited group. /// Function that performs an action on /// the currently visited entry (see EntryHandler for more). /// This parameter may be null. /// Returns true if all entries and groups have been /// traversed. If the traversal has been canceled by one of the two /// handlers, the return value is false. public bool TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler) { bool bRet = false; switch(tm) { case TraversalMethod.None: bRet = true; break; case TraversalMethod.PreOrder: bRet = PreOrderTraverseTree(groupHandler, entryHandler); break; default: Debug.Assert(false); break; } return bRet; } private bool PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler) { if(entryHandler != null) { foreach(PwEntry pe in m_listEntries) { if(!entryHandler(pe)) return false; } } if(groupHandler != null) { foreach(PwGroup pg in m_listGroups) { if(!groupHandler(pg)) return false; pg.PreOrderTraverseTree(groupHandler, entryHandler); } } else // groupHandler == null { foreach(PwGroup pg in m_listGroups) { pg.PreOrderTraverseTree(null, entryHandler); } } return true; } /// /// Pack all groups into one flat linked list of references (recursively). /// Temporary IDs (TemporaryID field) and levels (TemporaryLevel) /// are assigned automatically. /// /// Flat list of all groups. public LinkedList GetFlatGroupList() { LinkedList list = new LinkedList(); foreach(PwGroup pg in m_listGroups) { list.AddLast(pg); if(pg.Groups.UCount != 0) LinearizeGroupRecursive(list, pg, 1); } return list; } private void LinearizeGroupRecursive(LinkedList list, PwGroup pg, ushort uLevel) { Debug.Assert(pg != null); if(pg == null) return; foreach(PwGroup pwg in pg.Groups) { list.AddLast(pwg); if(pwg.Groups.UCount != 0) LinearizeGroupRecursive(list, pwg, (ushort)(uLevel + 1)); } } /// /// Pack all entries into one flat linked list of references. Temporary /// group IDs are assigned automatically. /// /// A flat group list created by /// GetFlatGroupList. /// Flat list of all entries. public static LinkedList GetFlatEntryList(LinkedList flatGroupList) { Debug.Assert(flatGroupList != null); if(flatGroupList == null) return null; LinkedList list = new LinkedList(); foreach(PwGroup pg in flatGroupList) { foreach(PwEntry pe in pg.Entries) list.AddLast(pe); } return list; } /// /// Enable protection of a specific string field type. /// /// Name of the string field to protect or unprotect. /// Enable protection or not. /// Returns true, if the operation completed successfully, /// otherwise false. public bool EnableStringFieldProtection(string strFieldName, bool bEnable) { Debug.Assert(strFieldName != null); EntryHandler eh = delegate(PwEntry pe) { // Enable protection of current string pe.Strings.EnableProtection(strFieldName, bEnable); // Do the same for all history items foreach(PwEntry peHistory in pe.History) { peHistory.Strings.EnableProtection(strFieldName, bEnable); } return true; }; return PreOrderTraverseTree(null, eh); } /// /// Search this group and all subgroups for entries. /// /// Specifies the search method. /// Entry list in which the search results will /// be stored. public void SearchEntries(SearchParameters sp, PwObjectList listStorage) { SearchEntries(sp, listStorage, null); } /// /// Search this group and all subgroups for entries. /// /// Specifies the search method. /// Entry list in which the search results will /// be stored. public void SearchEntries(SearchParameters sp, PwObjectList listStorage, IStatusLogger slStatus) { SearchEntries(sp, listStorage, null, slStatus); } /// /// Search this group and all subgroups for entries. /// /// Specifies the search method. /// Entry list in which the search results will /// be stored. /// Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned public void SearchEntries(SearchParameters sp, PwObjectList listStorage, IDictionary> resultContexts, IStatusLogger slStatus) { if(sp == null) { Debug.Assert(false); return; } if(listStorage == null) { Debug.Assert(false); return; } ulong uCurEntries = 0, uTotalEntries = 0; List 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 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 listStorage, IDictionary> 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) { #if KeePassRT RegexOptions ro = RegexOptions.None; #else RegexOptions ro = RegexOptions.Compiled; #endif if((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || #if !KeePassRT (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 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, SearchContextUuid); 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 lResults, IDictionary> 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.Min(matchPos - (SearchContextStringMaxLength / 10), contextString.Length - SearchContextStringMaxLength); contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " …" : null); } resultContexts[pe.Uuid] = new KeyValuePair(contextFieldName, contextString); } } } public List BuildEntryTagsList() { return BuildEntryTagsList(false); } public List BuildEntryTagsList(bool bSort) { List vTags = new List(); EntryHandler eh = delegate(PwEntry pe) { foreach(string strTag in pe.Tags) { bool bFound = false; for(int i = 0; i < vTags.Count; ++i) { if(vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) { bFound = true; break; } } if(!bFound) vTags.Add(strTag); } return true; }; TraverseTree(TraversalMethod.PreOrder, null, eh); if(bSort) vTags.Sort(StrUtil.CaseIgnoreComparer); return vTags; } #if !KeePassLibSD public IDictionary BuildEntryTagsDict(bool bSort) { IDictionary d; if(!bSort) d = new Dictionary(StrUtil.CaseIgnoreComparer); else d = new SortedDictionary(StrUtil.CaseIgnoreComparer); EntryHandler eh = delegate(PwEntry pe) { foreach(string strTag in pe.Tags) { uint u; if(d.TryGetValue(strTag, out u)) d[strTag] = u + 1; else d[strTag] = 1; } return true; }; TraverseTree(TraversalMethod.PreOrder, null, eh); return d; } #endif public void FindEntriesByTag(string strTag, PwObjectList listStorage, bool bSearchRecursive) { if(strTag == null) throw new ArgumentNullException("strTag"); if(strTag.Length == 0) return; foreach(PwEntry pe in m_listEntries) { foreach(string strEntryTag in pe.Tags) { if(strEntryTag.Equals(strTag, StrUtil.CaseIgnoreCmp)) { listStorage.Add(pe); break; } } } if(bSearchRecursive) { foreach(PwGroup pg in m_listGroups) pg.FindEntriesByTag(strTag, listStorage, true); } } /// /// Find a group. /// /// UUID identifying the group the caller is looking for. /// If true, the search is recursive. /// Returns reference to found group, otherwise null. public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive) { // Do not assert on PwUuid.Zero if(m_uuid.Equals(uuid)) return this; if(bSearchRecursive) { PwGroup pgRec; foreach(PwGroup pg in m_listGroups) { pgRec = pg.FindGroup(uuid, true); if(pgRec != null) return pgRec; } } else // Not recursive { foreach(PwGroup pg in m_listGroups) { if(pg.m_uuid.Equals(uuid)) return pg; } } return null; } /// /// Find an object. /// /// UUID of the object to find. /// Specifies whether to search recursively. /// If null, groups and entries are /// searched. If true, only entries are searched. If false, /// only groups are searched. /// Reference to the object, if found. Otherwise null. public IStructureItem FindObject(PwUuid uuid, bool bRecursive, bool? bEntries) { if(bEntries.HasValue) { if(bEntries.Value) return FindEntry(uuid, bRecursive); else return FindGroup(uuid, bRecursive); } PwGroup pg = FindGroup(uuid, bRecursive); if(pg != null) return pg; return FindEntry(uuid, bRecursive); } /// /// Try to find a subgroup and create it, if it doesn't exist yet. /// /// Name of the subgroup. /// If the group isn't found: create it. /// Returns a reference to the requested group or null if /// it doesn't exist and shouldn't be created. public PwGroup FindCreateGroup(string strName, bool bCreateIfNotFound) { Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); foreach(PwGroup pg in m_listGroups) { if(pg.Name == strName) return pg; } if(!bCreateIfNotFound) return null; PwGroup pgNew = new PwGroup(true, true, strName, PwIcon.Folder); AddGroup(pgNew, true); return pgNew; } /// /// Find an entry. /// /// UUID identifying the entry the caller is looking for. /// If true, the search is recursive. /// Returns reference to found entry, otherwise null. public PwEntry FindEntry(PwUuid uuid, bool bSearchRecursive) { foreach(PwEntry pe in m_listEntries) { if(pe.Uuid.Equals(uuid)) return pe; } if(bSearchRecursive) { PwEntry peSub; foreach(PwGroup pg in m_listGroups) { peSub = pg.FindEntry(uuid, true); if(peSub != null) return peSub; } } return null; } /// /// Get the full path of a group. /// /// Full path of the group. public string GetFullPath() { return GetFullPath(".", false); } /// /// Get the full path of a group. /// /// String that separates the group /// names. /// Full path of the group. public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup) { Debug.Assert(strSeparator != null); if(strSeparator == null) throw new ArgumentNullException("strSeparator"); string strPath = m_strName; PwGroup pg = m_pParentGroup; while(pg != null) { if((!bIncludeTopMostGroup) && (pg.m_pParentGroup == null)) break; strPath = pg.Name + strSeparator + strPath; pg = pg.m_pParentGroup; } return strPath; } /// /// Assign new UUIDs to groups and entries. /// /// Create new UUIDs for subgroups. /// Create new UUIDs for entries. /// Recursive tree traversal. public void CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive) { if(bNewGroups) { foreach(PwGroup pg in m_listGroups) pg.Uuid = new PwUuid(true); } if(bNewEntries) { foreach(PwEntry pe in m_listEntries) pe.SetUuid(new PwUuid(true), true); } if(bRecursive) { foreach(PwGroup pg in m_listGroups) pg.CreateNewItemUuids(bNewGroups, bNewEntries, true); } } public void TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive) { if(bTakeSubGroups) { foreach(PwGroup pg in m_listGroups) pg.ParentGroup = this; } if(bTakeEntries) { foreach(PwEntry pe in m_listEntries) pe.ParentGroup = this; } if(bRecursive) { foreach(PwGroup pg in m_listGroups) pg.TakeOwnership(bTakeSubGroups, bTakeEntries, true); } } #if !KeePassLibSD /// /// Find/create a subtree of groups. /// /// Tree string. /// Separators that delimit groups in the /// strTree parameter. public PwGroup FindCreateSubTree(string strTree, char[] vSeparators) { return FindCreateSubTree(strTree, vSeparators, true); } public PwGroup FindCreateSubTree(string strTree, char[] vSeparators, bool bAllowCreate) { if(vSeparators == null) { Debug.Assert(false); vSeparators = new char[0]; } string[] v = new string[vSeparators.Length]; for(int i = 0; i < vSeparators.Length; ++i) v[i] = new string(vSeparators[i], 1); return FindCreateSubTree(strTree, v, bAllowCreate); } public PwGroup FindCreateSubTree(string strTree, string[] vSeparators, bool bAllowCreate) { Debug.Assert(strTree != null); if(strTree == null) return this; if(strTree.Length == 0) return this; string[] vGroups = strTree.Split(vSeparators, StringSplitOptions.None); if((vGroups == null) || (vGroups.Length == 0)) return this; PwGroup pgContainer = this; for(int nGroup = 0; nGroup < vGroups.Length; ++nGroup) { if(string.IsNullOrEmpty(vGroups[nGroup])) continue; bool bFound = false; foreach(PwGroup pg in pgContainer.Groups) { if(pg.Name == vGroups[nGroup]) { pgContainer = pg; bFound = true; break; } } if(!bFound) { if(!bAllowCreate) return null; PwGroup pg = new PwGroup(true, true, vGroups[nGroup], PwIcon.Folder); pgContainer.AddGroup(pg, true); pgContainer = pg; } } return pgContainer; } #endif /// /// Get the level of the group (i.e. the number of parent groups). /// /// Number of parent groups. public uint GetLevel() { PwGroup pg = m_pParentGroup; uint uLevel = 0; while(pg != null) { pg = pg.ParentGroup; ++uLevel; } return uLevel; } public string GetAutoTypeSequenceInherited() { if(m_strDefaultAutoTypeSequence.Length > 0) return m_strDefaultAutoTypeSequence; if(m_pParentGroup != null) return m_pParentGroup.GetAutoTypeSequenceInherited(); return string.Empty; } public bool GetAutoTypeEnabledInherited() { if(m_bEnableAutoType.HasValue) return m_bEnableAutoType.Value; if(m_pParentGroup != null) return m_pParentGroup.GetAutoTypeEnabledInherited(); return DefaultAutoTypeEnabled; } public bool GetSearchingEnabledInherited() { if(m_bEnableSearching.HasValue) return m_bEnableSearching.Value; if(m_pParentGroup != null) return m_pParentGroup.GetSearchingEnabledInherited(); return DefaultSearchingEnabled; } /// /// Get a list of subgroups (not including this one). /// /// If true, subgroups are added /// recursively, i.e. all child groups are returned, too. /// List of subgroups. If is /// true, it is guaranteed that subsubgroups appear after /// subgroups. public PwObjectList GetGroups(bool bRecursive) { if(bRecursive == false) return m_listGroups; PwObjectList list = m_listGroups.CloneShallow(); foreach(PwGroup pgSub in m_listGroups) { list.Add(pgSub.GetGroups(true)); } return list; } public PwObjectList GetEntries(bool bIncludeSubGroupEntries) { if(bIncludeSubGroupEntries == false) return m_listEntries; PwObjectList list = m_listEntries.CloneShallow(); foreach(PwGroup pgSub in m_listGroups) { list.Add(pgSub.GetEntries(true)); } return list; } /// /// Get objects contained in this group. /// /// Specifies whether to search recursively. /// If null, the returned list contains /// groups and entries. If true, the returned list contains only /// entries. If false, the returned list contains only groups. /// List of objects. public List GetObjects(bool bRecursive, bool? bEntries) { List list = new List(); if(!bEntries.HasValue || !bEntries.Value) { PwObjectList lGroups = GetGroups(bRecursive); foreach(PwGroup pg in lGroups) list.Add(pg); } if(!bEntries.HasValue || bEntries.Value) { PwObjectList lEntries = GetEntries(bRecursive); foreach(PwEntry pe in lEntries) list.Add(pe); } return list; } public bool IsContainedIn(PwGroup pgContainer) { PwGroup pgCur = m_pParentGroup; while(pgCur != null) { if(pgCur == pgContainer) return true; pgCur = pgCur.m_pParentGroup; } return false; } /// /// Add a subgroup to this group. /// /// Group to be added. Must not be null. /// If this parameter is true, the /// parent group reference of the subgroup will be set to the current /// group (i.e. the current group takes ownership of the subgroup). public void AddGroup(PwGroup subGroup, bool bTakeOwnership) { AddGroup(subGroup, bTakeOwnership, false); } /// /// Add a subgroup to this group. /// /// Group to be added. Must not be null. /// If this parameter is true, the /// parent group reference of the subgroup will be set to the current /// group (i.e. the current group takes ownership of the subgroup). /// If true, the /// LocationChanged property of the subgroup is updated. public void AddGroup(PwGroup subGroup, bool bTakeOwnership, bool bUpdateLocationChangedOfSub) { if(subGroup == null) throw new ArgumentNullException("subGroup"); m_listGroups.Add(subGroup); if(bTakeOwnership) subGroup.m_pParentGroup = this; if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.Now; } /// /// Add an entry to this group. /// /// Entry to be added. Must not be null. /// If this parameter is true, the /// parent group reference of the entry will be set to the current /// group (i.e. the current group takes ownership of the entry). public void AddEntry(PwEntry pe, bool bTakeOwnership) { AddEntry(pe, bTakeOwnership, false); } /// /// Add an entry to this group. /// /// Entry to be added. Must not be null. /// If this parameter is true, the /// parent group reference of the entry will be set to the current /// group (i.e. the current group takes ownership of the entry). /// If true, the /// LocationChanged property of the entry is updated. public void AddEntry(PwEntry pe, bool bTakeOwnership, bool bUpdateLocationChangedOfEntry) { if(pe == null) throw new ArgumentNullException("pe"); m_listEntries.Add(pe); // Do not remove the entry from its previous parent group, // only assign it to the new one if(bTakeOwnership) pe.ParentGroup = this; if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.Now; } public void SortSubGroups(bool bRecursive) { m_listGroups.Sort(new PwGroupComparer()); if(bRecursive) { foreach(PwGroup pgSub in m_listGroups) pgSub.SortSubGroups(true); } } public void DeleteAllObjects(PwDatabase pdContext) { DateTime dtNow = DateTime.Now; foreach(PwEntry pe in m_listEntries) { PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); pdContext.DeletedObjects.Add(pdo); } m_listEntries.Clear(); foreach(PwGroup pg in m_listGroups) { pg.DeleteAllObjects(pdContext); PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, dtNow); pdContext.DeletedObjects.Add(pdo); } m_listGroups.Clear(); } private DateTime GetLazyTime(ref string lazyTime, ref DateTime dateTime) { if (lazyTime != null) { dateTime = TimeUtil.DeserializeUtcOrDefault(lazyTime, m_tLastMod); lazyTime = null; } return dateTime; } } public sealed class PwGroupComparer : IComparer { public PwGroupComparer() { } public int Compare(PwGroup a, PwGroup b) { return StrUtil.CompareNaturally(a.Name, b.Name); } } }