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 24 #if !KeePassUAP 25 using System.Drawing; 26 #endif 27 28 using KeePassLib.Collections; 29 using KeePassLib.Interfaces; 30 using KeePassLib.Security; 31 using KeePassLib.Utility; 32 33 namespace KeePassLib 34 { 35 /// <summary> 36 /// A class representing a password entry. A password entry consists of several 37 /// fields like title, user name, password, etc. Each password entry has a 38 /// unique ID (UUID). 39 /// </summary> 40 public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable<PwEntry> 41 { 42 private PwUuid m_uuid = PwUuid.Zero; 43 private PwGroup m_pParentGroup = null; 44 private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; 45 private PwUuid m_puPrevParentGroup = PwUuid.Zero; 46 47 private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary(); 48 private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary(); 49 private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig(); 50 private PwObjectList<PwEntry> m_lHistory = new PwObjectList<PwEntry>(); 51 52 private PwIcon m_pwIcon = PwIcon.Key; 53 private PwUuid m_puCustomIcon = PwUuid.Zero; 54 55 private Color m_clrForeground = Color.Empty; 56 private Color m_clrBackground = Color.Empty; 57 58 private DateTime m_tCreation = PwDefs.DtDefaultNow; 59 private DateTime m_tLastMod = PwDefs.DtDefaultNow; 60 private DateTime m_tLastAccess = PwDefs.DtDefaultNow; 61 private DateTime m_tExpire = PwDefs.DtDefaultNow; 62 private bool m_bExpires = false; 63 private ulong m_uUsageCount = 0; 64 65 private string m_strOverrideUrl = string.Empty; 66 private bool m_bQualityCheck = true; 67 68 private List<string> m_lTags = new List<string>(); 69 70 private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); 71 72 /// <summary> 73 /// UUID of this entry. 74 /// </summary> 75 public PwUuid Uuid 76 { 77 get { return m_uuid; } 78 set 79 { 80 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 81 m_uuid = value; 82 } 83 } 84 85 /// <summary> 86 /// Reference to a group which contains the current entry. 87 /// </summary> 88 public PwGroup ParentGroup 89 { 90 get { return m_pParentGroup; } 91 92 // Plugins: use <c>PwGroup.AddEntry</c> instead. 93 internal set { m_pParentGroup = value; } 94 } 95 96 /// <summary> 97 /// The date/time when the location of the object was last changed. 98 /// </summary> 99 public DateTime LocationChanged 100 { 101 get { return m_tParentGroupLastMod; } 102 set { m_tParentGroupLastMod = value; } 103 } 104 105 public PwUuid PreviousParentGroup 106 { 107 get { return m_puPrevParentGroup; } 108 set 109 { 110 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 111 m_puPrevParentGroup = value; 112 } 113 } 114 115 /// <summary> 116 /// Get or set all entry strings. 117 /// </summary> 118 public ProtectedStringDictionary Strings 119 { 120 get { return m_dStrings; } 121 set 122 { 123 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 124 m_dStrings = value; 125 } 126 } 127 128 /// <summary> 129 /// Get or set all entry binaries. 130 /// </summary> 131 public ProtectedBinaryDictionary Binaries 132 { 133 get { return m_dBinaries; } 134 set 135 { 136 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 137 m_dBinaries = value; 138 } 139 } 140 141 /// <summary> 142 /// Get or set all auto-type window/keystroke sequence associations. 143 /// </summary> 144 public AutoTypeConfig AutoType 145 { 146 get { return m_cfgAutoType; } 147 set 148 { 149 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 150 m_cfgAutoType = value; 151 } 152 } 153 154 /// <summary> 155 /// Get all previous versions of this entry (backups). 156 /// </summary> 157 public PwObjectList<PwEntry> History 158 { 159 get { return m_lHistory; } 160 set 161 { 162 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 163 m_lHistory = value; 164 } 165 } 166 167 /// <summary> 168 /// Image ID specifying the icon that will be used for this entry. 169 /// </summary> 170 public PwIcon IconId 171 { 172 get { return m_pwIcon; } 173 set { m_pwIcon = value; } 174 } 175 176 /// <summary> 177 /// Get the custom icon ID. This value is 0, if no custom icon is 178 /// being used (i.e. the icon specified by the <c>IconID</c> property 179 /// should be displayed). 180 /// </summary> 181 public PwUuid CustomIconUuid 182 { 183 get { return m_puCustomIcon; } 184 set 185 { 186 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 187 m_puCustomIcon = value; 188 } 189 } 190 191 /// <summary> 192 /// Get or set the foreground color of this entry. 193 /// </summary> 194 public Color ForegroundColor 195 { 196 get { return m_clrForeground; } 197 set { m_clrForeground = value; } 198 } 199 200 /// <summary> 201 /// Get or set the background color of this entry. 202 /// </summary> 203 public Color BackgroundColor 204 { 205 get { return m_clrBackground; } 206 set { m_clrBackground = value; } 207 } 208 209 /// <summary> 210 /// The date/time when this entry was created. 211 /// </summary> 212 public DateTime CreationTime 213 { 214 get { return m_tCreation; } 215 set { m_tCreation = value; } 216 } 217 218 /// <summary> 219 /// The date/time when this entry was last modified. 220 /// </summary> 221 public DateTime LastModificationTime 222 { 223 get { return m_tLastMod; } 224 set { m_tLastMod = value; } 225 } 226 227 /// <summary> 228 /// The date/time when this entry was last accessed (read). 229 /// </summary> 230 public DateTime LastAccessTime 231 { 232 get { return m_tLastAccess; } 233 set { m_tLastAccess = value; } 234 } 235 236 /// <summary> 237 /// The date/time when this entry expires. Use the <c>Expires</c> property 238 /// to specify if the entry does actually expire or not. 239 /// </summary> 240 public DateTime ExpiryTime 241 { 242 get { return m_tExpire; } 243 set { m_tExpire = value; } 244 } 245 246 /// <summary> 247 /// Specifies whether the entry expires or not. 248 /// </summary> 249 public bool Expires 250 { 251 get { return m_bExpires; } 252 set { m_bExpires = value; } 253 } 254 255 /// <summary> 256 /// Get or set the usage count of the entry. To increase the usage 257 /// count by one, use the <c>Touch</c> function. 258 /// </summary> 259 public ulong UsageCount 260 { 261 get { return m_uUsageCount; } 262 set { m_uUsageCount = value; } 263 } 264 265 /// <summary> 266 /// Entry-specific override URL. 267 /// </summary> 268 public string OverrideUrl 269 { 270 get { return m_strOverrideUrl; } 271 set 272 { 273 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 274 m_strOverrideUrl = value; 275 } 276 } 277 278 public bool QualityCheck 279 { 280 get { return m_bQualityCheck; } 281 set { m_bQualityCheck = value; } 282 } 283 284 /// <summary> 285 /// List of tags associated with this entry. 286 /// </summary> 287 public List<string> Tags 288 { 289 get { StrUtil.NormalizeTags(m_lTags); return m_lTags; } 290 set 291 { 292 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 293 m_lTags = value; 294 } 295 } 296 297 /// <summary> 298 /// Custom data container that can be used by plugins to store 299 /// own data in KeePass entries. 300 /// The data is stored in the encrypted part of encrypted 301 /// database files. 302 /// Use unique names for your items, e.g. "PluginName_ItemName". 303 /// </summary> 304 public StringDictionaryEx CustomData 305 { 306 get { return m_dCustomData; } 307 internal set 308 { 309 if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } 310 m_dCustomData = value; 311 } 312 } 313 314 public static EventHandler<ObjectTouchedEventArgs> EntryTouched; 315 public EventHandler<ObjectTouchedEventArgs> Touched; 316 317 /// <summary> 318 /// Construct a new, empty password entry. Member variables will be initialized 319 /// to their default values. 320 /// </summary> 321 /// <param name="bCreateNewUuid">If <c>true</c>, a new UUID will be created 322 /// for this entry. If <c>false</c>, the UUID is zero and you must set it 323 /// manually later.</param> 324 /// <param name="bSetTimes">If <c>true</c>, the creation, last modification 325 /// and last access times will be set to the current system time.</param> PwEntry(bool bCreateNewUuid, bool bSetTimes)326 public PwEntry(bool bCreateNewUuid, bool bSetTimes) 327 { 328 if(bCreateNewUuid) m_uuid = new PwUuid(true); 329 330 if(bSetTimes) 331 { 332 DateTime dtNow = DateTime.UtcNow; 333 m_tCreation = dtNow; 334 m_tLastMod = dtNow; 335 m_tLastAccess = dtNow; 336 m_tParentGroupLastMod = dtNow; 337 } 338 } 339 340 /// <summary> 341 /// Construct a new, empty password entry. Member variables will be initialized 342 /// to their default values. 343 /// </summary> 344 /// <param name="pwParentGroup">Reference to the containing group, this 345 /// parameter may be <c>null</c> and set later manually.</param> 346 /// <param name="bCreateNewUuid">If <c>true</c>, a new UUID will be created 347 /// for this entry. If <c>false</c>, the UUID is zero and you must set it 348 /// manually later.</param> 349 /// <param name="bSetTimes">If <c>true</c>, the creation, last modification 350 /// and last access times will be set to the current system time.</param> 351 [Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")] PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes)352 public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes) 353 { 354 m_pParentGroup = pwParentGroup; 355 356 if(bCreateNewUuid) m_uuid = new PwUuid(true); 357 358 if(bSetTimes) 359 { 360 DateTime dtNow = DateTime.UtcNow; 361 m_tCreation = dtNow; 362 m_tLastMod = dtNow; 363 m_tLastAccess = dtNow; 364 m_tParentGroupLastMod = dtNow; 365 } 366 } 367 368 #if DEBUG 369 // For display in debugger ToString()370 public override string ToString() 371 { 372 return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'"); 373 } 374 #endif 375 376 /// <summary> 377 /// Clone the current entry. The returned entry is an exact value copy 378 /// of the current entry (including UUID and parent group reference). 379 /// All mutable members are cloned. 380 /// </summary> 381 /// <returns>Exact value clone. All references to mutable values changed.</returns> CloneDeep()382 public PwEntry CloneDeep() 383 { 384 PwEntry peNew = new PwEntry(false, false); 385 386 peNew.m_uuid = m_uuid; // PwUuid is immutable 387 peNew.m_pParentGroup = m_pParentGroup; 388 peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; 389 peNew.m_puPrevParentGroup = m_puPrevParentGroup; 390 391 peNew.m_dStrings = m_dStrings.CloneDeep(); 392 peNew.m_dBinaries = m_dBinaries.CloneDeep(); 393 peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep(); 394 peNew.m_lHistory = m_lHistory.CloneDeep(); 395 396 peNew.m_pwIcon = m_pwIcon; 397 peNew.m_puCustomIcon = m_puCustomIcon; 398 399 peNew.m_clrForeground = m_clrForeground; 400 peNew.m_clrBackground = m_clrBackground; 401 402 peNew.m_tCreation = m_tCreation; 403 peNew.m_tLastMod = m_tLastMod; 404 peNew.m_tLastAccess = m_tLastAccess; 405 peNew.m_tExpire = m_tExpire; 406 peNew.m_bExpires = m_bExpires; 407 peNew.m_uUsageCount = m_uUsageCount; 408 409 peNew.m_strOverrideUrl = m_strOverrideUrl; 410 peNew.m_bQualityCheck = m_bQualityCheck; 411 412 peNew.m_lTags.AddRange(m_lTags); 413 414 peNew.m_dCustomData = m_dCustomData.CloneDeep(); 415 416 return peNew; 417 } 418 CloneStructure()419 public PwEntry CloneStructure() 420 { 421 PwEntry peNew = new PwEntry(false, false); 422 423 peNew.m_uuid = m_uuid; // PwUuid is immutable 424 peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; 425 // Do not assign m_pParentGroup 426 427 return peNew; 428 } 429 BuildCmpOpt(bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup)430 private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup, 431 bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, 432 bool bIgnoreThisLastBackup) 433 { 434 PwCompareOptions pwOpt = PwCompareOptions.None; 435 if(bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup; 436 if(bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod; 437 if(bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess; 438 if(bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory; 439 if(bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup; 440 return pwOpt; 441 } 442 443 [Obsolete] EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup)444 public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, 445 bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) 446 { 447 return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, 448 bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), 449 MemProtCmpMode.None); 450 } 451 452 [Obsolete] EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, MemProtCmpMode mpCmpStr)453 public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, 454 bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, 455 MemProtCmpMode mpCmpStr) 456 { 457 return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, 458 bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr); 459 } 460 EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, MemProtCmpMode mpCmpStr)461 public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, 462 MemProtCmpMode mpCmpStr) 463 { 464 if(pe == null) { Debug.Assert(false); return false; } 465 466 bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != 467 PwCompareOptions.None); 468 bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != 469 PwCompareOptions.None); 470 bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != 471 PwCompareOptions.None); 472 473 if(!m_uuid.Equals(pe.m_uuid)) return false; 474 if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) 475 { 476 if(m_pParentGroup != pe.m_pParentGroup) return false; 477 if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) 478 return false; 479 if(!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup)) 480 return false; 481 } 482 483 if(!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr)) 484 return false; 485 if(!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false; 486 487 if(!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false; 488 489 if((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) 490 { 491 bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != 492 PwCompareOptions.None); 493 494 if(!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount)) 495 return false; 496 if(bIgnoreLastBackup && (m_lHistory.UCount == 0)) 497 { 498 Debug.Assert(false); 499 return false; 500 } 501 if(bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount)) 502 return false; 503 504 PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; 505 if(bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd; 506 if(bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; 507 if(bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; 508 509 for(uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist) 510 { 511 if(!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt( 512 uHist), cmpSub, MemProtCmpMode.None)) 513 return false; 514 } 515 } 516 517 if(m_pwIcon != pe.m_pwIcon) return false; 518 if(!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false; 519 520 if(m_clrForeground != pe.m_clrForeground) return false; 521 if(m_clrBackground != pe.m_clrBackground) return false; 522 523 if(m_tCreation != pe.m_tCreation) return false; 524 if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false; 525 if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false; 526 if(m_tExpire != pe.m_tExpire) return false; 527 if(m_bExpires != pe.m_bExpires) return false; 528 if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; 529 530 if(m_strOverrideUrl != pe.m_strOverrideUrl) return false; 531 if(m_bQualityCheck != pe.m_bQualityCheck) return false; 532 533 // The Tags property normalizes 534 if(!MemUtil.ListsEqual<string>(this.Tags, pe.Tags)) return false; 535 536 if(!m_dCustomData.Equals(pe.m_dCustomData)) return false; 537 538 return true; 539 } 540 541 /// <summary> 542 /// Assign properties to the current entry based on a template entry. 543 /// </summary> 544 /// <param name="peTemplate">Template entry. Must not be <c>null</c>.</param> 545 /// <param name="bOnlyIfNewer">Only set the properties of the template entry 546 /// if it is newer than the current one.</param> 547 /// <param name="bIncludeHistory">If <c>true</c>, the history will be 548 /// copied, too.</param> 549 /// <param name="bAssignLocationChanged">If <c>true</c>, the 550 /// <c>LocationChanged</c> property is copied, otherwise not.</param> AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged)551 public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, 552 bool bIncludeHistory, bool bAssignLocationChanged) 553 { 554 if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } 555 556 if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, 557 m_tLastMod, true) < 0)) 558 return; 559 560 // Template UUID should be the same as the current one 561 Debug.Assert(m_uuid.Equals(peTemplate.m_uuid)); 562 m_uuid = peTemplate.m_uuid; 563 564 if(bAssignLocationChanged) 565 { 566 m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; 567 m_puPrevParentGroup = peTemplate.m_puPrevParentGroup; 568 } 569 570 m_dStrings = peTemplate.m_dStrings.CloneDeep(); 571 m_dBinaries = peTemplate.m_dBinaries.CloneDeep(); 572 m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep(); 573 if(bIncludeHistory) 574 m_lHistory = peTemplate.m_lHistory.CloneDeep(); 575 576 m_pwIcon = peTemplate.m_pwIcon; 577 m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable 578 579 m_clrForeground = peTemplate.m_clrForeground; 580 m_clrBackground = peTemplate.m_clrBackground; 581 582 m_tCreation = peTemplate.m_tCreation; 583 m_tLastMod = peTemplate.m_tLastMod; 584 m_tLastAccess = peTemplate.m_tLastAccess; 585 m_tExpire = peTemplate.m_tExpire; 586 m_bExpires = peTemplate.m_bExpires; 587 m_uUsageCount = peTemplate.m_uUsageCount; 588 589 m_strOverrideUrl = peTemplate.m_strOverrideUrl; 590 m_bQualityCheck = peTemplate.m_bQualityCheck; 591 592 m_lTags = new List<string>(peTemplate.m_lTags); 593 594 m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); 595 } 596 597 /// <summary> 598 /// Touch the entry. This function updates the internal last access 599 /// time. If the <paramref name="bModified" /> parameter is <c>true</c>, 600 /// the last modification time gets updated, too. 601 /// </summary> 602 /// <param name="bModified">Modify last modification time.</param> Touch(bool bModified)603 public void Touch(bool bModified) 604 { 605 Touch(bModified, true); 606 } 607 608 /// <summary> 609 /// Touch the entry. This function updates the internal last access 610 /// time. If the <paramref name="bModified" /> parameter is <c>true</c>, 611 /// the last modification time gets updated, too. 612 /// </summary> 613 /// <param name="bModified">Modify last modification time.</param> 614 /// <param name="bTouchParents">If <c>true</c>, all parent objects 615 /// get touched, too.</param> Touch(bool bModified, bool bTouchParents)616 public void Touch(bool bModified, bool bTouchParents) 617 { 618 m_tLastAccess = DateTime.UtcNow; 619 ++m_uUsageCount; 620 621 if(bModified) m_tLastMod = m_tLastAccess; 622 623 if(this.Touched != null) 624 this.Touched(this, new ObjectTouchedEventArgs(this, 625 bModified, bTouchParents)); 626 if(PwEntry.EntryTouched != null) 627 PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this, 628 bModified, bTouchParents)); 629 630 if(bTouchParents && (m_pParentGroup != null)) 631 m_pParentGroup.Touch(bModified, true); 632 } 633 634 /// <summary> 635 /// Create a backup of this entry. The backup item doesn't contain any 636 /// history items. 637 /// </summary> 638 [Obsolete] CreateBackup()639 public void CreateBackup() 640 { 641 CreateBackup(null); 642 } 643 644 /// <summary> 645 /// Create a backup of this entry. The backup item doesn't contain any 646 /// history items. 647 /// <param name="pwHistMntcSettings">If this parameter isn't <c>null</c>, 648 /// the history list is maintained automatically (i.e. old backups are 649 /// deleted if there are too many or the history size is too large). 650 /// This parameter may be <c>null</c> (no maintenance then).</param> 651 /// </summary> CreateBackup(PwDatabase pwHistMntcSettings)652 public void CreateBackup(PwDatabase pwHistMntcSettings) 653 { 654 PwEntry peCopy = CloneDeep(); 655 peCopy.m_lHistory.Clear(); 656 657 m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry 658 659 if(pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); 660 } 661 662 /// <summary> 663 /// Restore an entry snapshot from backups. 664 /// </summary> 665 /// <param name="uBackupIndex">Index of the backup item, to which 666 /// should be reverted.</param> 667 [Obsolete] RestoreFromBackup(uint uBackupIndex)668 public void RestoreFromBackup(uint uBackupIndex) 669 { 670 RestoreFromBackup(uBackupIndex, null); 671 } 672 673 /// <summary> 674 /// Restore an entry snapshot from backups. 675 /// </summary> 676 /// <param name="uBackupIndex">Index of the backup item, to which 677 /// should be reverted.</param> 678 /// <param name="pwHistMntcSettings">If this parameter isn't <c>null</c>, 679 /// the history list is maintained automatically (i.e. old backups are 680 /// deleted if there are too many or the history size is too large). 681 /// This parameter may be <c>null</c> (no maintenance then).</param> RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)682 public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) 683 { 684 if(uBackupIndex >= m_lHistory.UCount) 685 { 686 Debug.Assert(false); 687 throw new ArgumentOutOfRangeException("uBackupIndex"); 688 } 689 690 PwEntry pe = m_lHistory.GetAt(uBackupIndex); 691 if(pe == null) { Debug.Assert(false); throw new InvalidOperationException(); } 692 693 CreateBackup(pwHistMntcSettings); // Backup current data before restoring 694 AssignProperties(pe, false, false, false); 695 } 696 HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, bool bIgnoreLastAccess)697 public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, 698 bool bIgnoreLastAccess) 699 { 700 if(peData == null) { Debug.Assert(false); return false; } 701 702 PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | 703 PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd); 704 if(bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; 705 if(bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; 706 707 foreach(PwEntry pe in m_lHistory) 708 { 709 if(pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; 710 } 711 712 return false; 713 } 714 715 /// <summary> 716 /// Delete old history entries if there are too many or the 717 /// history size is too large. 718 /// <returns>If one or more history entries have been deleted, 719 /// <c>true</c> is returned. Otherwise <c>false</c>.</returns> 720 /// </summary> MaintainBackups(PwDatabase pwSettings)721 public bool MaintainBackups(PwDatabase pwSettings) 722 { 723 if(pwSettings == null) { Debug.Assert(false); return false; } 724 725 // Fix UUIDs of history entries; should not be necessary 726 PwUuid pu = m_uuid; 727 foreach(PwEntry pe in m_lHistory) 728 { 729 if(!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; } 730 } 731 732 bool bDeleted = false; 733 734 int nMaxItems = pwSettings.HistoryMaxItems; 735 if(nMaxItems >= 0) 736 { 737 while(m_lHistory.UCount > (uint)nMaxItems) 738 { 739 RemoveOldestBackup(); 740 bDeleted = true; 741 } 742 } 743 744 long lMaxSize = pwSettings.HistoryMaxSize; 745 if(lMaxSize >= 0) 746 { 747 while(true) 748 { 749 ulong uHistSize = 0; 750 foreach(PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); } 751 752 if(uHistSize > (ulong)lMaxSize) 753 { 754 RemoveOldestBackup(); 755 bDeleted = true; 756 } 757 else break; 758 } 759 } 760 761 return bDeleted; 762 } 763 RemoveOldestBackup()764 private void RemoveOldestBackup() 765 { 766 DateTime dtMin = TimeUtil.SafeMaxValueUtc; 767 uint idxRemove = uint.MaxValue; 768 769 for(uint u = 0; u < m_lHistory.UCount; ++u) 770 { 771 PwEntry pe = m_lHistory.GetAt(u); 772 if(TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0) 773 { 774 idxRemove = u; 775 dtMin = pe.LastModificationTime; 776 } 777 } 778 779 if(idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove); 780 } 781 782 // Cf. AutoType.GetEnabledText GetAutoTypeEnabled()783 public bool GetAutoTypeEnabled() 784 { 785 if(!m_cfgAutoType.Enabled) return false; 786 787 if(m_pParentGroup != null) 788 return m_pParentGroup.GetAutoTypeEnabledInherited(); 789 790 return PwGroup.DefaultAutoTypeEnabled; 791 } 792 GetAutoTypeSequence()793 public string GetAutoTypeSequence() 794 { 795 string strSeq = m_cfgAutoType.DefaultSequence; 796 797 PwGroup pg = m_pParentGroup; 798 while(pg != null) 799 { 800 if(strSeq.Length != 0) break; 801 802 strSeq = pg.DefaultAutoTypeSequence; 803 pg = pg.ParentGroup; 804 } 805 806 if(strSeq.Length != 0) return strSeq; 807 808 if(PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan; 809 return PwDefs.DefaultAutoTypeSequence; 810 } 811 GetSearchingEnabled()812 public bool GetSearchingEnabled() 813 { 814 if(m_pParentGroup != null) 815 return m_pParentGroup.GetSearchingEnabledInherited(); 816 817 return PwGroup.DefaultSearchingEnabled; 818 } 819 820 /// <summary> 821 /// Approximate the total size (in process memory) of this entry 822 /// in bytes (including strings, binaries and history entries). 823 /// </summary> 824 /// <returns>Size in bytes.</returns> GetSize()825 public ulong GetSize() 826 { 827 // This method assumes 64-bit pointers/references and Unicode 828 // strings (i.e. 2 bytes per character) 829 830 ulong cb = 276; // Number of bytes; approx. fixed length data 831 ulong cc = 0; // Number of characters 832 833 cb += (ulong)m_dStrings.UCount * 40; 834 foreach(KeyValuePair<string, ProtectedString> kvpStr in m_dStrings) 835 cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length; 836 837 cb += (ulong)m_dBinaries.UCount * 65; 838 foreach(KeyValuePair<string, ProtectedBinary> kvpBin in m_dBinaries) 839 { 840 cc += (ulong)kvpBin.Key.Length; 841 cb += (ulong)kvpBin.Value.Length; 842 } 843 844 cc += (ulong)m_cfgAutoType.DefaultSequence.Length; 845 cb += (ulong)m_cfgAutoType.AssociationsCount * 24; 846 foreach(AutoTypeAssociation a in m_cfgAutoType.Associations) 847 cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length; 848 849 cb += (ulong)m_lHistory.UCount * 8; 850 foreach(PwEntry peHistory in m_lHistory) 851 cb += peHistory.GetSize(); 852 853 cc += (ulong)m_strOverrideUrl.Length; 854 855 cb += (ulong)m_lTags.Count * 8; 856 foreach(string strTag in m_lTags) 857 cc += (ulong)strTag.Length; 858 859 cb += (ulong)m_dCustomData.Count * 16; 860 foreach(KeyValuePair<string, string> kvp in m_dCustomData) 861 cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; 862 863 return (cb + (cc << 1)); 864 } 865 HasTag(string strTag)866 public bool HasTag(string strTag) 867 { 868 if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } 869 870 // this.Tags normalizes 871 return this.Tags.Contains(StrUtil.NormalizeTag(strTag)); 872 } 873 AddTag(string strTag)874 public bool AddTag(string strTag) 875 { 876 if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } 877 878 strTag = StrUtil.NormalizeTag(strTag); 879 if(this.Tags.Contains(strTag)) return false; // this.Tags normalizes 880 881 m_lTags.Add(strTag); 882 return true; 883 } 884 RemoveTag(string strTag)885 public bool RemoveTag(string strTag) 886 { 887 if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } 888 889 // this.Tags normalizes 890 return this.Tags.Remove(StrUtil.NormalizeTag(strTag)); 891 } 892 GetTagsInherited()893 internal List<string> GetTagsInherited() 894 { 895 List<string> l = ((m_pParentGroup != null) ? 896 m_pParentGroup.GetTagsInherited(false) : new List<string>()); 897 l.AddRange(this.Tags); 898 StrUtil.NormalizeTags(l); 899 return l; 900 } 901 IsContainedIn(PwGroup pgContainer)902 public bool IsContainedIn(PwGroup pgContainer) 903 { 904 PwGroup pgCur = m_pParentGroup; 905 while(pgCur != null) 906 { 907 if(pgCur == pgContainer) return true; 908 909 pgCur = pgCur.ParentGroup; 910 } 911 912 return false; 913 } 914 SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids)915 public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids) 916 { 917 this.Uuid = pwNewUuid; 918 919 if(bAlsoChangeHistoryUuids) 920 { 921 foreach(PwEntry peHist in m_lHistory) 922 peHist.Uuid = pwNewUuid; 923 } 924 } 925 SetCreatedNow()926 public void SetCreatedNow() 927 { 928 DateTime dt = DateTime.UtcNow; 929 930 m_tCreation = dt; 931 m_tLastAccess = dt; 932 } 933 Duplicate()934 public PwEntry Duplicate() 935 { 936 PwEntry pe = CloneDeep(); 937 938 pe.SetUuid(new PwUuid(true), true); 939 pe.SetCreatedNow(); 940 941 return pe; 942 } 943 } 944 945 public sealed class PwEntryComparer : IComparer<PwEntry> 946 { 947 private string m_strFieldName; 948 private bool m_bCaseInsensitive; 949 private bool m_bCompareNaturally; 950 PwEntryComparer(string strFieldName, bool bCaseInsensitive, bool bCompareNaturally)951 public PwEntryComparer(string strFieldName, bool bCaseInsensitive, 952 bool bCompareNaturally) 953 { 954 if(strFieldName == null) throw new ArgumentNullException("strFieldName"); 955 956 m_strFieldName = strFieldName; 957 m_bCaseInsensitive = bCaseInsensitive; 958 m_bCompareNaturally = bCompareNaturally; 959 } 960 Compare(PwEntry a, PwEntry b)961 public int Compare(PwEntry a, PwEntry b) 962 { 963 string strA = a.Strings.ReadSafe(m_strFieldName); 964 string strB = b.Strings.ReadSafe(m_strFieldName); 965 966 if(m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB); 967 968 return string.Compare(strA, strB, m_bCaseInsensitive); 969 } 970 } 971 } 972