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