1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Text;
24 
25 using KeePassLib.Collections;
26 using KeePassLib.Delegates;
27 using KeePassLib.Interfaces;
28 using KeePassLib.Resources;
29 using KeePassLib.Utility;
30 
31 namespace KeePassLib
32 {
33 	/// <summary>
34 	/// A group containing subgroups and entries.
35 	/// </summary>
36 	public sealed partial class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<PwGroup>
37 	{
38 		public const bool DefaultAutoTypeEnabled = true;
39 		public const bool DefaultSearchingEnabled = true;
40 
41 		// In the tree view of Windows 10, the X coordinate is reset
42 		// to 0 after 256 nested nodes
43 		private const uint MaxDepth = 126; // Depth 126 = level 127 < 256/2
44 
45 		private PwUuid m_uuid = PwUuid.Zero;
46 		private PwGroup m_pParentGroup = null;
47 		private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
48 		private PwUuid m_puPrevParentGroup = PwUuid.Zero;
49 
50 		private PwObjectList<PwGroup> m_listGroups = new PwObjectList<PwGroup>();
51 		private PwObjectList<PwEntry> m_listEntries = new PwObjectList<PwEntry>();
52 
53 		private string m_strName = string.Empty;
54 		private string m_strNotes = string.Empty;
55 
56 		private PwIcon m_pwIcon = PwIcon.Folder;
57 		private PwUuid m_pwCustomIconID = PwUuid.Zero;
58 
59 		private DateTime m_tCreation = PwDefs.DtDefaultNow;
60 		private DateTime m_tLastMod = PwDefs.DtDefaultNow;
61 		private DateTime m_tLastAccess = PwDefs.DtDefaultNow;
62 		private DateTime m_tExpire = PwDefs.DtDefaultNow;
63 		private bool m_bExpires = false;
64 		private ulong m_uUsageCount = 0;
65 
66 		private bool m_bIsExpanded = true;
67 		private bool m_bVirtual = false;
68 
69 		private string m_strDefaultAutoTypeSequence = string.Empty;
70 
71 		private bool? m_bEnableAutoType = null;
72 		private bool? m_bEnableSearching = null;
73 
74 		private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero;
75 
76 		private List<string> m_lTags = new List<string>();
77 
78 		private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
79 
80 		/// <summary>
81 		/// UUID of this group.
82 		/// </summary>
83 		public PwUuid Uuid
84 		{
85 			get { return m_uuid; }
86 			set
87 			{
88 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
89 				m_uuid = value;
90 			}
91 		}
92 
93 		/// <summary>
94 		/// Reference to the group to which this group belongs. May be <c>null</c>.
95 		/// </summary>
96 		public PwGroup ParentGroup
97 		{
98 			get { return m_pParentGroup; }
99 
100 			// Plugins: use the PwGroup.AddGroup method instead.
101 			// Internal: check depth using CanAddGroup/CheckCanAddGroup.
102 			internal set { Debug.Assert(value != this); m_pParentGroup = value; }
103 		}
104 
105 		/// <summary>
106 		/// The date/time when the location of the object was last changed.
107 		/// </summary>
108 		public DateTime LocationChanged
109 		{
110 			get { return m_tParentGroupLastMod; }
111 			set { m_tParentGroupLastMod = value; }
112 		}
113 
114 		public PwUuid PreviousParentGroup
115 		{
116 			get { return m_puPrevParentGroup; }
117 			set
118 			{
119 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
120 				m_puPrevParentGroup = value;
121 			}
122 		}
123 
124 		/// <summary>
125 		/// The name of this group. Cannot be <c>null</c>.
126 		/// </summary>
127 		public string Name
128 		{
129 			get { return m_strName; }
130 			set
131 			{
132 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
133 				m_strName = value;
134 			}
135 		}
136 
137 		/// <summary>
138 		/// Comments about this group. Cannot be <c>null</c>.
139 		/// </summary>
140 		public string Notes
141 		{
142 			get { return m_strNotes; }
143 			set
144 			{
145 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
146 				m_strNotes = value;
147 			}
148 		}
149 
150 		/// <summary>
151 		/// Icon of the group.
152 		/// </summary>
153 		public PwIcon IconId
154 		{
155 			get { return m_pwIcon; }
156 			set { m_pwIcon = value; }
157 		}
158 
159 		/// <summary>
160 		/// Get the custom icon ID. This value is 0, if no custom icon is
161 		/// being used (i.e. the icon specified by the <c>IconID</c> property
162 		/// should be displayed).
163 		/// </summary>
164 		public PwUuid CustomIconUuid
165 		{
166 			get { return m_pwCustomIconID; }
167 			set
168 			{
169 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
170 				m_pwCustomIconID = value;
171 			}
172 		}
173 
174 		/// <summary>
175 		/// A flag that specifies if the group is shown as expanded or
176 		/// collapsed in the user interface.
177 		/// </summary>
178 		public bool IsExpanded
179 		{
180 			get { return m_bIsExpanded; }
181 			set { m_bIsExpanded = value; }
182 		}
183 
184 		/// <summary>
185 		/// The date/time when this group was created.
186 		/// </summary>
187 		public DateTime CreationTime
188 		{
189 			get { return m_tCreation; }
190 			set { m_tCreation = value; }
191 		}
192 
193 		/// <summary>
194 		/// The date/time when this group was last modified.
195 		/// </summary>
196 		public DateTime LastModificationTime
197 		{
198 			get { return m_tLastMod; }
199 			set { m_tLastMod = value; }
200 		}
201 
202 		/// <summary>
203 		/// The date/time when this group was last accessed (read).
204 		/// </summary>
205 		public DateTime LastAccessTime
206 		{
207 			get { return m_tLastAccess; }
208 			set { m_tLastAccess = value; }
209 		}
210 
211 		/// <summary>
212 		/// The date/time when this group expires.
213 		/// </summary>
214 		public DateTime ExpiryTime
215 		{
216 			get { return m_tExpire; }
217 			set { m_tExpire = value; }
218 		}
219 
220 		/// <summary>
221 		/// Flag that determines if the group expires.
222 		/// </summary>
223 		public bool Expires
224 		{
225 			get { return m_bExpires; }
226 			set { m_bExpires = value; }
227 		}
228 
229 		/// <summary>
230 		/// Get or set the usage count of the group. To increase the usage
231 		/// count by one, use the <c>Touch</c> function.
232 		/// </summary>
233 		public ulong UsageCount
234 		{
235 			get { return m_uUsageCount; }
236 			set { m_uUsageCount = value; }
237 		}
238 
239 		/// <summary>
240 		/// Get a list of subgroups in this group.
241 		/// </summary>
242 		public PwObjectList<PwGroup> Groups
243 		{
244 			get { return m_listGroups; }
245 		}
246 
247 		/// <summary>
248 		/// Get a list of entries in this group.
249 		/// </summary>
250 		public PwObjectList<PwEntry> Entries
251 		{
252 			get { return m_listEntries; }
253 		}
254 
255 		/// <summary>
256 		/// A flag specifying whether this group is virtual or not. Virtual
257 		/// groups can contain links to entries stored in other groups.
258 		/// Note that this flag has to be interpreted and set by the calling
259 		/// code; it won't prevent you from accessing and modifying the list
260 		/// of entries in this group in any way.
261 		/// </summary>
262 		public bool IsVirtual
263 		{
264 			get { return m_bVirtual; }
265 			set { m_bVirtual = value; }
266 		}
267 
268 		/// <summary>
269 		/// Default auto-type keystroke sequence for all entries in
270 		/// this group. This property can be an empty string, which
271 		/// means that the value should be inherited from the parent.
272 		/// </summary>
273 		public string DefaultAutoTypeSequence
274 		{
275 			get { return m_strDefaultAutoTypeSequence; }
276 			set
277 			{
278 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
279 				m_strDefaultAutoTypeSequence = value;
280 			}
281 		}
282 
283 		public bool? EnableAutoType
284 		{
285 			get { return m_bEnableAutoType; }
286 			set { m_bEnableAutoType = value; }
287 		}
288 
289 		public bool? EnableSearching
290 		{
291 			get { return m_bEnableSearching; }
292 			set { m_bEnableSearching = value; }
293 		}
294 
295 		public PwUuid LastTopVisibleEntry
296 		{
297 			get { return m_pwLastTopVisibleEntry; }
298 			set
299 			{
300 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
301 				m_pwLastTopVisibleEntry = value;
302 			}
303 		}
304 
305 		public List<string> Tags
306 		{
307 			get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
308 			set
309 			{
310 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
311 				m_lTags = value;
312 			}
313 		}
314 
315 		/// <summary>
316 		/// Custom data container that can be used by plugins to store
317 		/// own data in KeePass groups.
318 		/// The data is stored in the encrypted part of encrypted
319 		/// database files.
320 		/// Use unique names for your items, e.g. "PluginName_ItemName".
321 		/// </summary>
322 		public StringDictionaryEx CustomData
323 		{
324 			get { return m_dCustomData; }
325 			internal set
326 			{
327 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
328 				m_dCustomData = value;
329 			}
330 		}
331 
332 		public static EventHandler<ObjectTouchedEventArgs> GroupTouched;
333 		public EventHandler<ObjectTouchedEventArgs> Touched;
334 
335 		/// <summary>
336 		/// Construct a new, empty group.
337 		/// </summary>
PwGroup()338 		public PwGroup()
339 		{
340 		}
341 
342 		/// <summary>
343 		/// Construct a new, empty group.
344 		/// </summary>
345 		/// <param name="bCreateNewUuid">Create a new UUID for this group.</param>
346 		/// <param name="bSetTimes">Set creation, last access and last modification times to the current time.</param>
PwGroup(bool bCreateNewUuid, bool bSetTimes)347 		public PwGroup(bool bCreateNewUuid, bool bSetTimes)
348 		{
349 			if(bCreateNewUuid) m_uuid = new PwUuid(true);
350 
351 			if(bSetTimes)
352 			{
353 				DateTime dtNow = DateTime.UtcNow;
354 				m_tCreation = dtNow;
355 				m_tLastMod = dtNow;
356 				m_tLastAccess = dtNow;
357 				m_tParentGroupLastMod = dtNow;
358 			}
359 		}
360 
361 		/// <summary>
362 		/// Construct a new group.
363 		/// </summary>
364 		/// <param name="bCreateNewUuid">Create a new UUID for this group.</param>
365 		/// <param name="bSetTimes">Set creation, last access and last modification times to the current time.</param>
366 		/// <param name="strName">Name of the new group.</param>
367 		/// <param name="pwIcon">Icon of the new group.</param>
PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon)368 		public PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon)
369 		{
370 			if(bCreateNewUuid) m_uuid = new PwUuid(true);
371 
372 			if(bSetTimes)
373 			{
374 				DateTime dtNow = DateTime.UtcNow;
375 				m_tCreation = dtNow;
376 				m_tLastMod = dtNow;
377 				m_tLastAccess = dtNow;
378 				m_tParentGroupLastMod = dtNow;
379 			}
380 
381 			if(strName != null) m_strName = strName;
382 
383 			m_pwIcon = pwIcon;
384 		}
385 
386 #if DEBUG
387 		// For display in debugger
ToString()388 		public override string ToString()
389 		{
390 			return (@"PwGroup '" + m_strName + @"'");
391 		}
392 #endif
393 
394 		/// <summary>
395 		/// Deeply clone the current group. The returned group will be an exact
396 		/// value copy of the current object (including UUID, etc.).
397 		/// </summary>
398 		/// <returns>Exact value copy of the current <c>PwGroup</c> object.</returns>
CloneDeep()399 		public PwGroup CloneDeep()
400 		{
401 			PwGroup pg = new PwGroup(false, false);
402 
403 			pg.m_uuid = m_uuid; // PwUuid is immutable
404 
405 			pg.m_listGroups = m_listGroups.CloneDeep();
406 			pg.m_listEntries = m_listEntries.CloneDeep();
407 			pg.TakeOwnership(true, true, false);
408 
409 			pg.m_pParentGroup = m_pParentGroup;
410 			pg.m_tParentGroupLastMod = m_tParentGroupLastMod;
411 			pg.m_puPrevParentGroup = m_puPrevParentGroup;
412 
413 			pg.m_strName = m_strName;
414 			pg.m_strNotes = m_strNotes;
415 
416 			pg.m_pwIcon = m_pwIcon;
417 			pg.m_pwCustomIconID = m_pwCustomIconID;
418 
419 			pg.m_tCreation = m_tCreation;
420 			pg.m_tLastMod = m_tLastMod;
421 			pg.m_tLastAccess = m_tLastAccess;
422 			pg.m_tExpire = m_tExpire;
423 			pg.m_bExpires = m_bExpires;
424 			pg.m_uUsageCount = m_uUsageCount;
425 
426 			pg.m_bIsExpanded = m_bIsExpanded;
427 			pg.m_bVirtual = m_bVirtual;
428 
429 			pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence;
430 
431 			pg.m_bEnableAutoType = m_bEnableAutoType;
432 			pg.m_bEnableSearching = m_bEnableSearching;
433 
434 			pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry;
435 
436 			pg.m_lTags.AddRange(m_lTags);
437 
438 			pg.m_dCustomData = m_dCustomData.CloneDeep();
439 
440 			return pg;
441 		}
442 
CloneStructure()443 		public PwGroup CloneStructure()
444 		{
445 			PwGroup pg = new PwGroup(false, false);
446 
447 			pg.m_uuid = m_uuid; // PwUuid is immutable
448 			pg.m_tParentGroupLastMod = m_tParentGroupLastMod;
449 			// Do not assign m_pParentGroup
450 
451 			foreach(PwGroup pgSub in m_listGroups)
452 				pg.AddGroup(pgSub.CloneStructure(), true);
453 
454 			foreach(PwEntry peSub in m_listEntries)
455 				pg.AddEntry(peSub.CloneStructure(), true);
456 
457 			return pg;
458 		}
459 
EqualsGroup(PwGroup pg, PwCompareOptions pwOpt, MemProtCmpMode mpCmpStr)460 		public bool EqualsGroup(PwGroup pg, PwCompareOptions pwOpt,
461 			MemProtCmpMode mpCmpStr)
462 		{
463 			if(pg == null) { Debug.Assert(false); return false; }
464 
465 			bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) !=
466 				PwCompareOptions.None);
467 			bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) !=
468 				PwCompareOptions.None);
469 
470 			if(!m_uuid.Equals(pg.m_uuid)) return false;
471 			if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None)
472 			{
473 				if(m_pParentGroup != pg.m_pParentGroup) return false;
474 				if(!bIgnoreLastMod && (m_tParentGroupLastMod != pg.m_tParentGroupLastMod))
475 					return false;
476 				if(!m_puPrevParentGroup.Equals(pg.m_puPrevParentGroup))
477 					return false;
478 			}
479 
480 			if(m_strName != pg.m_strName) return false;
481 			if(m_strNotes != pg.m_strNotes) return false;
482 
483 			if(m_pwIcon != pg.m_pwIcon) return false;
484 			if(!m_pwCustomIconID.Equals(pg.m_pwCustomIconID)) return false;
485 
486 			if(m_tCreation != pg.m_tCreation) return false;
487 			if(!bIgnoreLastMod && (m_tLastMod != pg.m_tLastMod)) return false;
488 			if(!bIgnoreLastAccess && (m_tLastAccess != pg.m_tLastAccess)) return false;
489 			if(m_tExpire != pg.m_tExpire) return false;
490 			if(m_bExpires != pg.m_bExpires) return false;
491 			if(!bIgnoreLastAccess && (m_uUsageCount != pg.m_uUsageCount)) return false;
492 
493 			// if(m_bIsExpanded != pg.m_bIsExpanded) return false;
494 
495 			if(m_strDefaultAutoTypeSequence != pg.m_strDefaultAutoTypeSequence) return false;
496 
497 			if(m_bEnableAutoType.HasValue != pg.m_bEnableAutoType.HasValue) return false;
498 			if(m_bEnableAutoType.HasValue)
499 			{
500 				if(m_bEnableAutoType.Value != pg.m_bEnableAutoType.Value) return false;
501 			}
502 			if(m_bEnableSearching.HasValue != pg.m_bEnableSearching.HasValue) return false;
503 			if(m_bEnableSearching.HasValue)
504 			{
505 				if(m_bEnableSearching.Value != pg.m_bEnableSearching.Value) return false;
506 			}
507 
508 			if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false;
509 
510 			// The Tags property normalizes
511 			if(!MemUtil.ListsEqual<string>(this.Tags, pg.Tags)) return false;
512 
513 			if(!m_dCustomData.Equals(pg.m_dCustomData)) return false;
514 
515 			if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None)
516 			{
517 				if(m_listEntries.UCount != pg.m_listEntries.UCount) return false;
518 				for(uint u = 0; u < m_listEntries.UCount; ++u)
519 				{
520 					PwEntry peA = m_listEntries.GetAt(u);
521 					PwEntry peB = pg.m_listEntries.GetAt(u);
522 					if(!peA.EqualsEntry(peB, pwOpt, mpCmpStr)) return false;
523 				}
524 
525 				if(m_listGroups.UCount != pg.m_listGroups.UCount) return false;
526 				for(uint u = 0; u < m_listGroups.UCount; ++u)
527 				{
528 					PwGroup pgA = m_listGroups.GetAt(u);
529 					PwGroup pgB = pg.m_listGroups.GetAt(u);
530 					if(!pgA.EqualsGroup(pgB, pwOpt, mpCmpStr)) return false;
531 				}
532 			}
533 
534 			return true;
535 		}
536 
537 		/// <summary>
538 		/// Assign properties to the current group based on a template group.
539 		/// </summary>
540 		/// <param name="pgTemplate">Template group. Must not be <c>null</c>.</param>
541 		/// <param name="bOnlyIfNewer">Only set the properties of the template group
542 		/// if it is newer than the current one.</param>
543 		/// <param name="bAssignLocationChanged">If <c>true</c>, the
544 		/// <c>LocationChanged</c> property is copied, otherwise not.</param>
AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer, bool bAssignLocationChanged)545 		public void AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer,
546 			bool bAssignLocationChanged)
547 		{
548 			Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate");
549 
550 			if(bOnlyIfNewer && (TimeUtil.Compare(pgTemplate.m_tLastMod, m_tLastMod,
551 				true) < 0))
552 				return;
553 
554 			// Template UUID should be the same as the current one
555 			Debug.Assert(m_uuid.Equals(pgTemplate.m_uuid));
556 			m_uuid = pgTemplate.m_uuid;
557 
558 			if(bAssignLocationChanged)
559 			{
560 				m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod;
561 				m_puPrevParentGroup = pgTemplate.m_puPrevParentGroup;
562 			}
563 
564 			m_strName = pgTemplate.m_strName;
565 			m_strNotes = pgTemplate.m_strNotes;
566 
567 			m_pwIcon = pgTemplate.m_pwIcon;
568 			m_pwCustomIconID = pgTemplate.m_pwCustomIconID;
569 
570 			m_tCreation = pgTemplate.m_tCreation;
571 			m_tLastMod = pgTemplate.m_tLastMod;
572 			m_tLastAccess = pgTemplate.m_tLastAccess;
573 			m_tExpire = pgTemplate.m_tExpire;
574 			m_bExpires = pgTemplate.m_bExpires;
575 			m_uUsageCount = pgTemplate.m_uUsageCount;
576 
577 			m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence;
578 
579 			m_bEnableAutoType = pgTemplate.m_bEnableAutoType;
580 			m_bEnableSearching = pgTemplate.m_bEnableSearching;
581 
582 			m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry;
583 
584 			m_lTags = new List<string>(pgTemplate.m_lTags);
585 
586 			m_dCustomData = pgTemplate.m_dCustomData.CloneDeep();
587 		}
588 
589 		/// <summary>
590 		/// Touch the group. This function updates the internal last access
591 		/// time. If the <paramref name="bModified" /> parameter is <c>true</c>,
592 		/// the last modification time gets updated, too.
593 		/// </summary>
594 		/// <param name="bModified">Modify last modification time.</param>
Touch(bool bModified)595 		public void Touch(bool bModified)
596 		{
597 			Touch(bModified, true);
598 		}
599 
600 		/// <summary>
601 		/// Touch the group. This function updates the internal last access
602 		/// time. If the <paramref name="bModified" /> parameter is <c>true</c>,
603 		/// the last modification time gets updated, too.
604 		/// </summary>
605 		/// <param name="bModified">Modify last modification time.</param>
606 		/// <param name="bTouchParents">If <c>true</c>, all parent objects
607 		/// get touched, too.</param>
Touch(bool bModified, bool bTouchParents)608 		public void Touch(bool bModified, bool bTouchParents)
609 		{
610 			m_tLastAccess = DateTime.UtcNow;
611 			++m_uUsageCount;
612 
613 			if(bModified) m_tLastMod = m_tLastAccess;
614 
615 			if(this.Touched != null)
616 				this.Touched(this, new ObjectTouchedEventArgs(this,
617 					bModified, bTouchParents));
618 			if(PwGroup.GroupTouched != null)
619 				PwGroup.GroupTouched(this, new ObjectTouchedEventArgs(this,
620 					bModified, bTouchParents));
621 
622 			if(bTouchParents && (m_pParentGroup != null))
623 				m_pParentGroup.Touch(bModified, true);
624 		}
625 
626 		/// <summary>
627 		/// Get number of groups and entries in the current group. This function
628 		/// can also traverse through all subgroups and accumulate their counts
629 		/// (recursive mode).
630 		/// </summary>
631 		/// <param name="bRecursive">If this parameter is <c>true</c>, all
632 		/// subgroups and entries in subgroups will be counted and added to
633 		/// the returned value. If it is <c>false</c>, only the number of
634 		/// subgroups and entries of the current group is returned.</param>
635 		/// <param name="uNumGroups">Number of subgroups.</param>
636 		/// <param name="uNumEntries">Number of entries.</param>
GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries)637 		public void GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries)
638 		{
639 			if(bRecursive)
640 			{
641 				uint uTotalGroups = m_listGroups.UCount;
642 				uint uTotalEntries = m_listEntries.UCount;
643 				uint uSubGroupCount, uSubEntryCount;
644 
645 				foreach(PwGroup pg in m_listGroups)
646 				{
647 					pg.GetCounts(true, out uSubGroupCount, out uSubEntryCount);
648 
649 					uTotalGroups += uSubGroupCount;
650 					uTotalEntries += uSubEntryCount;
651 				}
652 
653 				uNumGroups = uTotalGroups;
654 				uNumEntries = uTotalEntries;
655 			}
656 			else // !bRecursive
657 			{
658 				uNumGroups = m_listGroups.UCount;
659 				uNumEntries = m_listEntries.UCount;
660 			}
661 		}
662 
GetEntriesCount(bool bRecursive)663 		public uint GetEntriesCount(bool bRecursive)
664 		{
665 			uint uGroups, uEntries;
666 			GetCounts(bRecursive, out uGroups, out uEntries);
667 			return uEntries;
668 		}
669 
670 		/// <summary>
671 		/// Traverse the group/entry tree in the current group. Various traversal
672 		/// methods are available.
673 		/// </summary>
674 		/// <param name="tm">Specifies the traversal method.</param>
675 		/// <param name="groupHandler">Function that performs an action on
676 		/// the currently visited group (see <c>GroupHandler</c> for more).
677 		/// This parameter may be <c>null</c>, in this case the tree is traversed but
678 		/// you don't get notifications for each visited group.</param>
679 		/// <param name="entryHandler">Function that performs an action on
680 		/// the currently visited entry (see <c>EntryHandler</c> for more).
681 		/// This parameter may be <c>null</c>.</param>
682 		/// <returns>Returns <c>true</c> if all entries and groups have been
683 		/// traversed. If the traversal has been canceled by one of the two
684 		/// handlers, the return value is <c>false</c>.</returns>
TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler)685 		public bool TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler)
686 		{
687 			bool bRet = false;
688 
689 			switch(tm)
690 			{
691 				case TraversalMethod.None:
692 					bRet = true;
693 					break;
694 				case TraversalMethod.PreOrder:
695 					bRet = PreOrderTraverseTree(groupHandler, entryHandler);
696 					break;
697 				default:
698 					Debug.Assert(false);
699 					break;
700 			}
701 
702 			return bRet;
703 		}
704 
PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler)705 		private bool PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler)
706 		{
707 			if(entryHandler != null)
708 			{
709 				foreach(PwEntry pe in m_listEntries)
710 				{
711 					if(!entryHandler(pe)) return false;
712 				}
713 			}
714 
715 			foreach(PwGroup pg in m_listGroups)
716 			{
717 				if(groupHandler != null)
718 				{
719 					if(!groupHandler(pg)) return false;
720 				}
721 
722 				if(!pg.PreOrderTraverseTree(groupHandler, entryHandler))
723 					return false;
724 			}
725 
726 			return true;
727 		}
728 
729 		/// <summary>
730 		/// Pack all groups into one flat linked list of references (recursively).
731 		/// </summary>
732 		/// <returns>Flat list of all groups.</returns>
GetFlatGroupList()733 		public LinkedList<PwGroup> GetFlatGroupList()
734 		{
735 			LinkedList<PwGroup> list = new LinkedList<PwGroup>();
736 
737 			foreach(PwGroup pg in m_listGroups)
738 			{
739 				list.AddLast(pg);
740 
741 				if(pg.Groups.UCount != 0)
742 					LinearizeGroupRecursive(list, pg, 1);
743 			}
744 
745 			return list;
746 		}
747 
LinearizeGroupRecursive(LinkedList<PwGroup> list, PwGroup pg, ushort uLevel)748 		private void LinearizeGroupRecursive(LinkedList<PwGroup> list, PwGroup pg, ushort uLevel)
749 		{
750 			Debug.Assert(pg != null); if(pg == null) return;
751 
752 			foreach(PwGroup pwg in pg.Groups)
753 			{
754 				list.AddLast(pwg);
755 
756 				if(pwg.Groups.UCount != 0)
757 					LinearizeGroupRecursive(list, pwg, (ushort)(uLevel + 1));
758 			}
759 		}
760 
761 		/// <summary>
762 		/// Pack all entries into one flat linked list of references. Temporary
763 		/// group IDs are assigned automatically.
764 		/// </summary>
765 		/// <param name="flatGroupList">A flat group list created by
766 		/// <c>GetFlatGroupList</c>.</param>
767 		/// <returns>Flat list of all entries.</returns>
GetFlatEntryList(LinkedList<PwGroup> flatGroupList)768 		public static LinkedList<PwEntry> GetFlatEntryList(LinkedList<PwGroup> flatGroupList)
769 		{
770 			Debug.Assert(flatGroupList != null); if(flatGroupList == null) return null;
771 
772 			LinkedList<PwEntry> list = new LinkedList<PwEntry>();
773 			foreach(PwGroup pg in flatGroupList)
774 			{
775 				foreach(PwEntry pe in pg.Entries)
776 					list.AddLast(pe);
777 			}
778 
779 			return list;
780 		}
781 
782 		/// <summary>
783 		/// Enable protection of a specific string field type.
784 		/// </summary>
785 		/// <param name="strFieldName">Name of the string field to protect or unprotect.</param>
786 		/// <param name="bEnable">Enable protection or not.</param>
787 		/// <returns>Returns <c>true</c>, if the operation completed successfully,
788 		/// otherwise <c>false</c>.</returns>
EnableStringFieldProtection(string strFieldName, bool bEnable)789 		public bool EnableStringFieldProtection(string strFieldName, bool bEnable)
790 		{
791 			Debug.Assert(strFieldName != null);
792 
793 			EntryHandler eh = delegate(PwEntry pe)
794 			{
795 				// Enable protection of current string
796 				pe.Strings.EnableProtection(strFieldName, bEnable);
797 
798 				// Do the same for all history items
799 				foreach(PwEntry peHistory in pe.History)
800 				{
801 					peHistory.Strings.EnableProtection(strFieldName, bEnable);
802 				}
803 
804 				return true;
805 			};
806 
807 			return PreOrderTraverseTree(null, eh);
808 		}
809 
GetTagsInherited(bool bNormalize)810 		internal List<string> GetTagsInherited(bool bNormalize)
811 		{
812 			List<string> l = new List<string>();
813 
814 			PwGroup pg = this;
815 			while(pg != null)
816 			{
817 				l.AddRange(pg.Tags);
818 				pg = pg.m_pParentGroup;
819 			}
820 
821 			if(bNormalize) StrUtil.NormalizeTags(l);
822 			return l;
823 		}
824 
BuildEntryTagsList()825 		public List<string> BuildEntryTagsList()
826 		{
827 			return BuildEntryTagsList(false, false);
828 		}
829 
BuildEntryTagsList(bool bSort)830 		public List<string> BuildEntryTagsList(bool bSort)
831 		{
832 			return BuildEntryTagsList(bSort, false);
833 		}
834 
BuildEntryTagsList(bool bSort, bool bGroupTags)835 		internal List<string> BuildEntryTagsList(bool bSort, bool bGroupTags)
836 		{
837 			Dictionary<string, bool> d = new Dictionary<string, bool>();
838 
839 			GroupHandler gh = null;
840 			if(bGroupTags)
841 			{
842 				gh = delegate(PwGroup pg)
843 				{
844 					foreach(string strTag in pg.Tags) d[strTag] = true;
845 					return true;
846 				};
847 			}
848 
849 			EntryHandler eh = delegate(PwEntry pe)
850 			{
851 				foreach(string strTag in pe.Tags) d[strTag] = true;
852 				return true;
853 			};
854 
855 			if(gh != null) gh(this);
856 			TraverseTree(TraversalMethod.PreOrder, gh, eh);
857 
858 			List<string> l = new List<string>(d.Keys);
859 			if(bSort) l.Sort(StrUtil.CompareNaturally);
860 
861 			return l;
862 		}
863 
864 #if !KeePassLibSD
BuildEntryTagsDict(bool bSort)865 		public IDictionary<string, uint> BuildEntryTagsDict(bool bSort)
866 		{
867 			Debug.Assert(!bSort); // Obsolete
868 
869 			IDictionary<string, uint> d;
870 			if(!bSort) d = new Dictionary<string, uint>();
871 			else d = new SortedDictionary<string, uint>();
872 
873 			GroupHandler gh = delegate(PwGroup pg)
874 			{
875 				foreach(string strTag in pg.Tags)
876 				{
877 					// For groups without entries
878 					if(!d.ContainsKey(strTag)) d[strTag] = 0;
879 				}
880 
881 				return true;
882 			};
883 
884 			EntryHandler eh = delegate(PwEntry pe)
885 			{
886 				foreach(string strTag in pe.GetTagsInherited())
887 				{
888 					uint u;
889 					d.TryGetValue(strTag, out u);
890 					d[strTag] = u + 1;
891 				}
892 
893 				return true;
894 			};
895 
896 			gh(this);
897 			TraverseTree(TraversalMethod.PreOrder, gh, eh);
898 
899 			return d;
900 		}
901 #endif
902 
FindEntriesByTag(string strTag, PwObjectList<PwEntry> listStorage, bool bSearchRecursive)903 		public void FindEntriesByTag(string strTag, PwObjectList<PwEntry> listStorage,
904 			bool bSearchRecursive)
905 		{
906 			if(strTag == null) throw new ArgumentNullException("strTag");
907 
908 			strTag = StrUtil.NormalizeTag(strTag);
909 			if(string.IsNullOrEmpty(strTag)) return;
910 
911 			EntryHandler eh = delegate(PwEntry pe)
912 			{
913 				foreach(string strEntryTag in pe.GetTagsInherited())
914 				{
915 					if(strEntryTag == strTag)
916 					{
917 						listStorage.Add(pe);
918 						break;
919 					}
920 				}
921 
922 				return true;
923 			};
924 
925 			if(bSearchRecursive)
926 				TraverseTree(TraversalMethod.PreOrder, null, eh);
927 			else
928 			{
929 				foreach(PwEntry pe in m_listEntries) eh(pe);
930 			}
931 		}
932 
933 		/// <summary>
934 		/// Find a group.
935 		/// </summary>
936 		/// <param name="uuid">UUID identifying the group the caller is looking for.</param>
937 		/// <param name="bSearchRecursive">If <c>true</c>, the search is recursive.</param>
938 		/// <returns>Returns reference to found group, otherwise <c>null</c>.</returns>
FindGroup(PwUuid uuid, bool bSearchRecursive)939 		public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive)
940 		{
941 			// Do not assert on PwUuid.Zero
942 			if(m_uuid.Equals(uuid)) return this;
943 
944 			if(bSearchRecursive)
945 			{
946 				PwGroup pgRec;
947 				foreach(PwGroup pg in m_listGroups)
948 				{
949 					pgRec = pg.FindGroup(uuid, true);
950 					if(pgRec != null) return pgRec;
951 				}
952 			}
953 			else // Not recursive
954 			{
955 				foreach(PwGroup pg in m_listGroups)
956 				{
957 					if(pg.m_uuid.Equals(uuid))
958 						return pg;
959 				}
960 			}
961 
962 			return null;
963 		}
964 
965 		/// <summary>
966 		/// Find an object.
967 		/// </summary>
968 		/// <param name="uuid">UUID of the object to find.</param>
969 		/// <param name="bRecursive">Specifies whether to search recursively.</param>
970 		/// <param name="bEntries">If <c>null</c>, groups and entries are
971 		/// searched. If <c>true</c>, only entries are searched. If <c>false</c>,
972 		/// only groups are searched.</param>
973 		/// <returns>Reference to the object, if found. Otherwise <c>null</c>.</returns>
FindObject(PwUuid uuid, bool bRecursive, bool? bEntries)974 		public IStructureItem FindObject(PwUuid uuid, bool bRecursive,
975 			bool? bEntries)
976 		{
977 			if(bEntries.HasValue)
978 			{
979 				if(bEntries.Value) return FindEntry(uuid, bRecursive);
980 				else return FindGroup(uuid, bRecursive);
981 			}
982 
983 			PwGroup pg = FindGroup(uuid, bRecursive);
984 			if(pg != null) return pg;
985 			return FindEntry(uuid, bRecursive);
986 		}
987 
988 		/// <summary>
989 		/// Try to find a subgroup and create it, if it doesn't exist yet.
990 		/// </summary>
991 		/// <param name="strName">Name of the subgroup.</param>
992 		/// <param name="bCreateIfNotFound">If the group isn't found: create it.</param>
993 		/// <returns>Returns a reference to the requested group or <c>null</c> if
994 		/// it doesn't exist and shouldn't be created.</returns>
FindCreateGroup(string strName, bool bCreateIfNotFound)995 		public PwGroup FindCreateGroup(string strName, bool bCreateIfNotFound)
996 		{
997 			Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName");
998 
999 			foreach(PwGroup pg in m_listGroups)
1000 			{
1001 				if(pg.Name == strName) return pg;
1002 			}
1003 
1004 			if(!bCreateIfNotFound) return null;
1005 
1006 			PwGroup pgNew = new PwGroup(true, true, strName, PwIcon.Folder);
1007 			AddGroup(pgNew, true);
1008 			return pgNew;
1009 		}
1010 
1011 		/// <summary>
1012 		/// Find an entry.
1013 		/// </summary>
1014 		/// <param name="uuid">UUID identifying the entry the caller is looking for.</param>
1015 		/// <param name="bSearchRecursive">If <c>true</c>, the search is recursive.</param>
1016 		/// <returns>Returns reference to found entry, otherwise <c>null</c>.</returns>
FindEntry(PwUuid uuid, bool bSearchRecursive)1017 		public PwEntry FindEntry(PwUuid uuid, bool bSearchRecursive)
1018 		{
1019 			foreach(PwEntry pe in m_listEntries)
1020 			{
1021 				if(pe.Uuid.Equals(uuid)) return pe;
1022 			}
1023 
1024 			if(bSearchRecursive)
1025 			{
1026 				PwEntry peSub;
1027 				foreach(PwGroup pg in m_listGroups)
1028 				{
1029 					peSub = pg.FindEntry(uuid, true);
1030 					if(peSub != null) return peSub;
1031 				}
1032 			}
1033 
1034 			return null;
1035 		}
1036 
1037 		/// <summary>
1038 		/// Get the full path of a group.
1039 		/// </summary>
1040 		/// <returns>Full path of the group.</returns>
GetFullPath()1041 		public string GetFullPath()
1042 		{
1043 			return GetFullPath(".", false);
1044 		}
1045 
1046 		/// <summary>
1047 		/// Get the full path of a group.
1048 		/// </summary>
1049 		/// <param name="strSeparator">String that separates the group
1050 		/// names.</param>
1051 		/// <param name="bIncludeTopMostGroup">Specifies whether the returned
1052 		/// path starts with the topmost group.</param>
1053 		/// <returns>Full path of the group.</returns>
GetFullPath(string strSeparator, bool bIncludeTopMostGroup)1054 		public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup)
1055 		{
1056 			Debug.Assert(strSeparator != null);
1057 			if(strSeparator == null) throw new ArgumentNullException("strSeparator");
1058 
1059 			string strPath = m_strName;
1060 
1061 			PwGroup pg = m_pParentGroup;
1062 			while(pg != null)
1063 			{
1064 				if(!bIncludeTopMostGroup && (pg.m_pParentGroup == null))
1065 					break;
1066 
1067 				strPath = pg.Name + strSeparator + strPath;
1068 
1069 				pg = pg.m_pParentGroup;
1070 			}
1071 
1072 			return strPath;
1073 		}
1074 
1075 		/// <summary>
1076 		/// Assign new UUIDs to groups and entries.
1077 		/// </summary>
1078 		/// <param name="bNewGroups">Create new UUIDs for subgroups.</param>
1079 		/// <param name="bNewEntries">Create new UUIDs for entries.</param>
1080 		/// <param name="bRecursive">Recursive tree traversal.</param>
CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive)1081 		public void CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive)
1082 		{
1083 			if(bNewGroups)
1084 			{
1085 				foreach(PwGroup pg in m_listGroups)
1086 					pg.Uuid = new PwUuid(true);
1087 			}
1088 
1089 			if(bNewEntries)
1090 			{
1091 				foreach(PwEntry pe in m_listEntries)
1092 					pe.SetUuid(new PwUuid(true), true);
1093 			}
1094 
1095 			if(bRecursive)
1096 			{
1097 				foreach(PwGroup pg in m_listGroups)
1098 					pg.CreateNewItemUuids(bNewGroups, bNewEntries, true);
1099 			}
1100 		}
1101 
TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive)1102 		public void TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive)
1103 		{
1104 			if(bTakeSubGroups)
1105 			{
1106 				foreach(PwGroup pg in m_listGroups)
1107 					pg.ParentGroup = this;
1108 			}
1109 
1110 			if(bTakeEntries)
1111 			{
1112 				foreach(PwEntry pe in m_listEntries)
1113 					pe.ParentGroup = this;
1114 			}
1115 
1116 			if(bRecursive)
1117 			{
1118 				foreach(PwGroup pg in m_listGroups)
1119 					pg.TakeOwnership(bTakeSubGroups, bTakeEntries, true);
1120 			}
1121 		}
1122 
1123 #if !KeePassLibSD
1124 		/// <summary>
1125 		/// Find/create a subtree of groups.
1126 		/// </summary>
1127 		/// <param name="strTree">Tree string.</param>
1128 		/// <param name="vSeparators">Separators that delimit groups in the
1129 		/// <c>strTree</c> parameter.</param>
FindCreateSubTree(string strTree, char[] vSeparators)1130 		public PwGroup FindCreateSubTree(string strTree, char[] vSeparators)
1131 		{
1132 			return FindCreateSubTree(strTree, vSeparators, true);
1133 		}
1134 
FindCreateSubTree(string strTree, char[] vSeparators, bool bAllowCreate)1135 		public PwGroup FindCreateSubTree(string strTree, char[] vSeparators,
1136 			bool bAllowCreate)
1137 		{
1138 			if(vSeparators == null) { Debug.Assert(false); vSeparators = new char[0]; }
1139 
1140 			string[] v = new string[vSeparators.Length];
1141 			for(int i = 0; i < vSeparators.Length; ++i)
1142 				v[i] = new string(vSeparators[i], 1);
1143 
1144 			return FindCreateSubTree(strTree, v, bAllowCreate);
1145 		}
1146 
FindCreateSubTree(string strTree, string[] vSeparators, bool bAllowCreate)1147 		public PwGroup FindCreateSubTree(string strTree, string[] vSeparators,
1148 			bool bAllowCreate)
1149 		{
1150 			Debug.Assert(strTree != null); if(strTree == null) return this;
1151 			if(strTree.Length == 0) return this;
1152 
1153 			string[] vGroups = strTree.Split(vSeparators, StringSplitOptions.None);
1154 			if((vGroups == null) || (vGroups.Length == 0)) return this;
1155 
1156 			PwGroup pgContainer = this;
1157 			for(int nGroup = 0; nGroup < vGroups.Length; ++nGroup)
1158 			{
1159 				if(string.IsNullOrEmpty(vGroups[nGroup])) continue;
1160 
1161 				bool bFound = false;
1162 				foreach(PwGroup pg in pgContainer.Groups)
1163 				{
1164 					if(pg.Name == vGroups[nGroup])
1165 					{
1166 						pgContainer = pg;
1167 						bFound = true;
1168 						break;
1169 					}
1170 				}
1171 
1172 				if(!bFound)
1173 				{
1174 					if(!bAllowCreate) return null;
1175 
1176 					PwGroup pg = new PwGroup(true, true, vGroups[nGroup], PwIcon.Folder);
1177 					pgContainer.AddGroup(pg, true);
1178 					pgContainer = pg;
1179 				}
1180 			}
1181 
1182 			return pgContainer;
1183 		}
1184 #endif
1185 
1186 		/// <summary>
1187 		/// Get the depth of this group (i.e. the number of ancestors).
1188 		/// </summary>
1189 		/// <returns>Depth of this group.</returns>
GetDepth()1190 		public uint GetDepth()
1191 		{
1192 			PwGroup pg = m_pParentGroup;
1193 			uint d = 0;
1194 
1195 			while(pg != null)
1196 			{
1197 				pg = pg.m_pParentGroup;
1198 				++d;
1199 			}
1200 
1201 			return d;
1202 		}
1203 
GetHeight()1204 		private uint GetHeight()
1205 		{
1206 			if(m_listGroups.UCount == 0) return 0;
1207 
1208 			uint h = 0;
1209 			foreach(PwGroup pgSub in m_listGroups)
1210 			{
1211 				h = Math.Max(h, pgSub.GetHeight());
1212 			}
1213 
1214 			return (h + 1);
1215 		}
1216 
GetAutoTypeSequenceInherited()1217 		public string GetAutoTypeSequenceInherited()
1218 		{
1219 			if(m_strDefaultAutoTypeSequence.Length > 0)
1220 				return m_strDefaultAutoTypeSequence;
1221 
1222 			if(m_pParentGroup != null)
1223 				return m_pParentGroup.GetAutoTypeSequenceInherited();
1224 
1225 			return string.Empty;
1226 		}
1227 
GetAutoTypeEnabledInherited()1228 		public bool GetAutoTypeEnabledInherited()
1229 		{
1230 			if(m_bEnableAutoType.HasValue) return m_bEnableAutoType.Value;
1231 
1232 			if(m_pParentGroup != null)
1233 				return m_pParentGroup.GetAutoTypeEnabledInherited();
1234 
1235 			return DefaultAutoTypeEnabled;
1236 		}
1237 
GetSearchingEnabledInherited()1238 		public bool GetSearchingEnabledInherited()
1239 		{
1240 			if(m_bEnableSearching.HasValue) return m_bEnableSearching.Value;
1241 
1242 			if(m_pParentGroup != null)
1243 				return m_pParentGroup.GetSearchingEnabledInherited();
1244 
1245 			return DefaultSearchingEnabled;
1246 		}
1247 
1248 		/// <summary>
1249 		/// Get a list of subgroups (not including this one).
1250 		/// </summary>
1251 		/// <param name="bRecursive">If <c>true</c>, subgroups are added
1252 		/// recursively, i.e. all child groups are returned, too.</param>
1253 		/// <returns>List of subgroups. If <paramref name="bRecursive" /> is
1254 		/// <c>true</c>, it is guaranteed that subsubgroups appear after
1255 		/// subgroups.</returns>
GetGroups(bool bRecursive)1256 		public PwObjectList<PwGroup> GetGroups(bool bRecursive)
1257 		{
1258 			if(!bRecursive) return m_listGroups;
1259 
1260 			PwObjectList<PwGroup> list = m_listGroups.CloneShallow();
1261 			foreach(PwGroup pgSub in m_listGroups)
1262 			{
1263 				list.Add(pgSub.GetGroups(true));
1264 			}
1265 
1266 			return list;
1267 		}
1268 
GetEntries(bool bIncludeSubGroupEntries)1269 		public PwObjectList<PwEntry> GetEntries(bool bIncludeSubGroupEntries)
1270 		{
1271 			PwObjectList<PwEntry> l = new PwObjectList<PwEntry>();
1272 
1273 			GroupHandler gh = delegate(PwGroup pg)
1274 			{
1275 				l.Add(pg.Entries);
1276 				return true;
1277 			};
1278 
1279 			gh(this);
1280 			if(bIncludeSubGroupEntries)
1281 				PreOrderTraverseTree(gh, null);
1282 
1283 			Debug.Assert(l.UCount == GetEntriesCount(bIncludeSubGroupEntries));
1284 			return l;
1285 		}
1286 
1287 		/// <summary>
1288 		/// Get objects contained in this group.
1289 		/// </summary>
1290 		/// <param name="bRecursive">Specifies whether to search recursively.</param>
1291 		/// <param name="bEntries">If <c>null</c>, the returned list contains
1292 		/// groups and entries. If <c>true</c>, the returned list contains only
1293 		/// entries. If <c>false</c>, the returned list contains only groups.</param>
1294 		/// <returns>List of objects.</returns>
GetObjects(bool bRecursive, bool? bEntries)1295 		public List<IStructureItem> GetObjects(bool bRecursive, bool? bEntries)
1296 		{
1297 			List<IStructureItem> list = new List<IStructureItem>();
1298 
1299 			if(!bEntries.HasValue || !bEntries.Value)
1300 			{
1301 				PwObjectList<PwGroup> lGroups = GetGroups(bRecursive);
1302 				foreach(PwGroup pg in lGroups) list.Add(pg);
1303 			}
1304 
1305 			if(!bEntries.HasValue || bEntries.Value)
1306 			{
1307 				PwObjectList<PwEntry> lEntries = GetEntries(bRecursive);
1308 				foreach(PwEntry pe in lEntries) list.Add(pe);
1309 			}
1310 
1311 			return list;
1312 		}
1313 
IsContainedIn(PwGroup pgContainer)1314 		public bool IsContainedIn(PwGroup pgContainer)
1315 		{
1316 			PwGroup pgCur = m_pParentGroup;
1317 			while(pgCur != null)
1318 			{
1319 				if(pgCur == pgContainer) return true;
1320 
1321 				pgCur = pgCur.m_pParentGroup;
1322 			}
1323 
1324 			return false;
1325 		}
1326 
1327 		/// <summary>
1328 		/// Add a subgroup to this group.
1329 		/// </summary>
1330 		/// <param name="subGroup">Group to be added. Must not be <c>null</c>.</param>
1331 		/// <param name="bTakeOwnership">If this parameter is <c>true</c>, the
1332 		/// parent group reference of the subgroup will be set to the current
1333 		/// group (i.e. the current group takes ownership of the subgroup).</param>
AddGroup(PwGroup subGroup, bool bTakeOwnership)1334 		public void AddGroup(PwGroup subGroup, bool bTakeOwnership)
1335 		{
1336 			AddGroup(subGroup, bTakeOwnership, false);
1337 		}
1338 
1339 		/// <summary>
1340 		/// Add a subgroup to this group.
1341 		/// </summary>
1342 		/// <param name="subGroup">Group to be added. Must not be <c>null</c>.</param>
1343 		/// <param name="bTakeOwnership">If this parameter is <c>true</c>, the
1344 		/// parent group reference of the subgroup will be set to the current
1345 		/// group (i.e. the current group takes ownership of the subgroup).</param>
1346 		/// <param name="bUpdateLocationChangedOfSub">If <c>true</c>, the
1347 		/// <c>LocationChanged</c> property of the subgroup is updated.</param>
AddGroup(PwGroup subGroup, bool bTakeOwnership, bool bUpdateLocationChangedOfSub)1348 		public void AddGroup(PwGroup subGroup, bool bTakeOwnership,
1349 			bool bUpdateLocationChangedOfSub)
1350 		{
1351 			if(subGroup == null) throw new ArgumentNullException("subGroup");
1352 
1353 			CheckCanAddGroup(subGroup);
1354 			m_listGroups.Add(subGroup);
1355 
1356 			if(bTakeOwnership) subGroup.ParentGroup = this;
1357 
1358 			if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.UtcNow;
1359 		}
1360 
CanAddGroup(PwGroup pgSub)1361 		internal bool CanAddGroup(PwGroup pgSub)
1362 		{
1363 			if(pgSub == null) { Debug.Assert(false); return false; }
1364 
1365 			uint dCur = GetDepth(), hSub = pgSub.GetHeight();
1366 			return ((dCur + hSub + 1) <= MaxDepth);
1367 		}
1368 
CheckCanAddGroup(PwGroup pgSub)1369 		internal void CheckCanAddGroup(PwGroup pgSub)
1370 		{
1371 			if(!CanAddGroup(pgSub))
1372 			{
1373 				Debug.Assert(false);
1374 				throw new InvalidOperationException(KLRes.StructsTooDeep);
1375 			}
1376 		}
1377 
1378 		/// <summary>
1379 		/// Add an entry to this group.
1380 		/// </summary>
1381 		/// <param name="pe">Entry to be added. Must not be <c>null</c>.</param>
1382 		/// <param name="bTakeOwnership">If this parameter is <c>true</c>, the
1383 		/// parent group reference of the entry will be set to the current
1384 		/// group (i.e. the current group takes ownership of the entry).</param>
AddEntry(PwEntry pe, bool bTakeOwnership)1385 		public void AddEntry(PwEntry pe, bool bTakeOwnership)
1386 		{
1387 			AddEntry(pe, bTakeOwnership, false);
1388 		}
1389 
1390 		/// <summary>
1391 		/// Add an entry to this group.
1392 		/// </summary>
1393 		/// <param name="pe">Entry to be added. Must not be <c>null</c>.</param>
1394 		/// <param name="bTakeOwnership">If this parameter is <c>true</c>, the
1395 		/// parent group reference of the entry will be set to the current
1396 		/// group (i.e. the current group takes ownership of the entry).</param>
1397 		/// <param name="bUpdateLocationChangedOfEntry">If <c>true</c>, the
1398 		/// <c>LocationChanged</c> property of the entry is updated.</param>
AddEntry(PwEntry pe, bool bTakeOwnership, bool bUpdateLocationChangedOfEntry)1399 		public void AddEntry(PwEntry pe, bool bTakeOwnership,
1400 			bool bUpdateLocationChangedOfEntry)
1401 		{
1402 			if(pe == null) throw new ArgumentNullException("pe");
1403 
1404 			m_listEntries.Add(pe);
1405 
1406 			// Do not remove the entry from its previous parent group,
1407 			// only assign it to the new one
1408 			if(bTakeOwnership) pe.ParentGroup = this;
1409 
1410 			if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.UtcNow;
1411 		}
1412 
SortSubGroups(bool bRecursive)1413 		public void SortSubGroups(bool bRecursive)
1414 		{
1415 			m_listGroups.Sort(new PwGroupComparer());
1416 
1417 			if(bRecursive)
1418 			{
1419 				foreach(PwGroup pgSub in m_listGroups)
1420 					pgSub.SortSubGroups(true);
1421 			}
1422 		}
1423 
DeleteAllObjects(PwDatabase pdContext)1424 		public void DeleteAllObjects(PwDatabase pdContext)
1425 		{
1426 			DateTime dtNow = DateTime.UtcNow;
1427 
1428 			foreach(PwEntry pe in m_listEntries)
1429 			{
1430 				PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow);
1431 				pdContext.DeletedObjects.Add(pdo);
1432 			}
1433 			m_listEntries.Clear();
1434 
1435 			foreach(PwGroup pg in m_listGroups)
1436 			{
1437 				pg.DeleteAllObjects(pdContext);
1438 
1439 				PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, dtNow);
1440 				pdContext.DeletedObjects.Add(pdo);
1441 			}
1442 			m_listGroups.Clear();
1443 		}
1444 
GetTopSearchSkippedGroups()1445 		internal List<PwGroup> GetTopSearchSkippedGroups()
1446 		{
1447 			List<PwGroup> l = new List<PwGroup>();
1448 
1449 			if(!GetSearchingEnabledInherited()) l.Add(this);
1450 			else GetTopSearchSkippedGroupsRec(l);
1451 
1452 			return l;
1453 		}
1454 
GetTopSearchSkippedGroupsRec(List<PwGroup> l)1455 		private void GetTopSearchSkippedGroupsRec(List<PwGroup> l)
1456 		{
1457 			if(m_bEnableSearching.HasValue && !m_bEnableSearching.Value)
1458 			{
1459 				l.Add(this);
1460 				return;
1461 			}
1462 			else { Debug.Assert(GetSearchingEnabledInherited()); }
1463 
1464 			foreach(PwGroup pgSub in m_listGroups)
1465 				pgSub.GetTopSearchSkippedGroupsRec(l);
1466 		}
1467 
SetCreatedNow(bool bRecursive)1468 		public void SetCreatedNow(bool bRecursive)
1469 		{
1470 			DateTime dt = DateTime.UtcNow;
1471 
1472 			m_tCreation = dt;
1473 			m_tLastAccess = dt;
1474 
1475 			if(!bRecursive) return;
1476 
1477 			GroupHandler gh = delegate(PwGroup pg)
1478 			{
1479 				pg.m_tCreation = dt;
1480 				pg.m_tLastAccess = dt;
1481 				return true;
1482 			};
1483 
1484 			EntryHandler eh = delegate(PwEntry pe)
1485 			{
1486 				pe.CreationTime = dt;
1487 				pe.LastAccessTime = dt;
1488 				return true;
1489 			};
1490 
1491 			TraverseTree(TraversalMethod.PreOrder, gh, eh);
1492 		}
1493 
Duplicate()1494 		public PwGroup Duplicate()
1495 		{
1496 			PwGroup pg = CloneDeep();
1497 
1498 			pg.Uuid = new PwUuid(true);
1499 			pg.CreateNewItemUuids(true, true, true);
1500 
1501 			pg.SetCreatedNow(true);
1502 
1503 			return pg;
1504 		}
1505 
CollectEntryStrings(GFunc<PwEntry, string> f, bool bSort)1506 		internal string[] CollectEntryStrings(GFunc<PwEntry, string> f, bool bSort)
1507 		{
1508 			if(f == null) { Debug.Assert(false); return new string[0]; }
1509 
1510 			Dictionary<string, bool> d = new Dictionary<string, bool>();
1511 
1512 			EntryHandler eh = delegate(PwEntry pe)
1513 			{
1514 				string str = f(pe);
1515 				if(str != null) d[str] = true;
1516 
1517 				return true;
1518 			};
1519 			TraverseTree(TraversalMethod.PreOrder, null, eh);
1520 
1521 			string[] v = new string[d.Count];
1522 			if(d.Count != 0)
1523 			{
1524 				d.Keys.CopyTo(v, 0);
1525 				if(bSort) Array.Sort<string>(v, StrUtil.CaseIgnoreComparer);
1526 			}
1527 
1528 			return v;
1529 		}
1530 
GetAutoTypeSequences(bool bWithStd)1531 		internal string[] GetAutoTypeSequences(bool bWithStd)
1532 		{
1533 			try
1534 			{
1535 				Dictionary<string, bool> d = new Dictionary<string, bool>();
1536 
1537 				Action<string> fAdd = delegate(string str)
1538 				{
1539 					if(!string.IsNullOrEmpty(str)) d[str] = true;
1540 				};
1541 
1542 				if(bWithStd)
1543 				{
1544 					fAdd(PwDefs.DefaultAutoTypeSequence);
1545 					fAdd(PwDefs.DefaultAutoTypeSequenceTan);
1546 				}
1547 
1548 				GroupHandler gh = delegate(PwGroup pg)
1549 				{
1550 					fAdd(pg.DefaultAutoTypeSequence);
1551 					return true;
1552 				};
1553 
1554 				EntryHandler eh = delegate(PwEntry pe)
1555 				{
1556 					AutoTypeConfig c = pe.AutoType;
1557 
1558 					fAdd(c.DefaultSequence);
1559 					foreach(AutoTypeAssociation a in c.Associations)
1560 					{
1561 						fAdd(a.Sequence);
1562 					}
1563 
1564 					return true;
1565 				};
1566 
1567 				gh(this);
1568 				TraverseTree(TraversalMethod.PreOrder, gh, eh);
1569 
1570 				string[] v = new string[d.Count];
1571 				if(d.Count != 0)
1572 				{
1573 					d.Keys.CopyTo(v, 0);
1574 					Array.Sort<string>(v, StrUtil.CaseIgnoreComparer);
1575 				}
1576 
1577 				return v;
1578 			}
1579 			catch(Exception) { Debug.Assert(false); }
1580 
1581 			return new string[0];
1582 		}
1583 	}
1584 
1585 	public sealed class PwGroupComparer : IComparer<PwGroup>
1586 	{
PwGroupComparer()1587 		public PwGroupComparer()
1588 		{
1589 		}
1590 
Compare(PwGroup a, PwGroup b)1591 		public int Compare(PwGroup a, PwGroup b)
1592 		{
1593 			return StrUtil.CompareNaturally(a.Name, b.Name);
1594 		}
1595 	}
1596 }
1597