merge changes from KeePass 2.48 with support for KDBX 4.1 and slightly improved merging behavior with respect to custom icons, closes https://github.com/PhilippC/keepass2android/issues/1670
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -33,8 +33,11 @@ namespace KeePassLib.Collections
|
||||
private Dictionary<int, ProtectedBinary> m_d =
|
||||
new Dictionary<int, ProtectedBinary>();
|
||||
|
||||
public ProtectedBinarySet()
|
||||
private readonly bool m_bDedupAdd;
|
||||
|
||||
public ProtectedBinarySet(bool bDedupAdd)
|
||||
{
|
||||
m_bDedupAdd = bDedupAdd;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
@@ -47,15 +50,10 @@ namespace KeePassLib.Collections
|
||||
return m_d.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_d.Clear();
|
||||
}
|
||||
|
||||
private int GetFreeID()
|
||||
{
|
||||
int i = m_d.Count;
|
||||
while(m_d.ContainsKey(i)) { ++i; }
|
||||
while (m_d.ContainsKey(i)) { ++i; }
|
||||
Debug.Assert(i == m_d.Count); // m_d.Count should be free
|
||||
return i;
|
||||
}
|
||||
@@ -63,7 +61,7 @@ namespace KeePassLib.Collections
|
||||
public ProtectedBinary Get(int iID)
|
||||
{
|
||||
ProtectedBinary pb;
|
||||
if(m_d.TryGetValue(iID, out pb)) return pb;
|
||||
if (m_d.TryGetValue(iID, out pb)) return pb;
|
||||
|
||||
// Debug.Assert(false); // No assert
|
||||
return null;
|
||||
@@ -71,12 +69,12 @@ namespace KeePassLib.Collections
|
||||
|
||||
public int Find(ProtectedBinary pb)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); return -1; }
|
||||
if (pb == null) { Debug.Assert(false); return -1; }
|
||||
|
||||
// Fast search by reference
|
||||
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
{
|
||||
if(object.ReferenceEquals(pb, kvp.Value))
|
||||
if (object.ReferenceEquals(pb, kvp.Value))
|
||||
{
|
||||
Debug.Assert(pb.Equals(kvp.Value));
|
||||
return kvp.Key;
|
||||
@@ -84,9 +82,9 @@ namespace KeePassLib.Collections
|
||||
}
|
||||
|
||||
// Slow search by content
|
||||
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
{
|
||||
if(pb.Equals(kvp.Value)) return kvp.Key;
|
||||
if (pb.Equals(kvp.Value)) return kvp.Key;
|
||||
}
|
||||
|
||||
// Debug.Assert(false); // No assert
|
||||
@@ -95,28 +93,26 @@ namespace KeePassLib.Collections
|
||||
|
||||
public void Set(int iID, ProtectedBinary pb)
|
||||
{
|
||||
if(iID < 0) { Debug.Assert(false); return; }
|
||||
if(pb == null) { Debug.Assert(false); return; }
|
||||
if (iID < 0) { Debug.Assert(false); return; }
|
||||
if (pb == null) { Debug.Assert(false); return; }
|
||||
|
||||
m_d[iID] = pb;
|
||||
}
|
||||
|
||||
public void Add(ProtectedBinary pb)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); return; }
|
||||
if (pb == null) { Debug.Assert(false); return; }
|
||||
|
||||
int i = Find(pb);
|
||||
if(i >= 0) return; // Exists already
|
||||
if (m_bDedupAdd && (Find(pb) >= 0)) return; // Exists already
|
||||
|
||||
i = GetFreeID();
|
||||
m_d[i] = pb;
|
||||
m_d[GetFreeID()] = pb;
|
||||
}
|
||||
|
||||
public void AddFrom(ProtectedBinaryDictionary d)
|
||||
{
|
||||
if(d == null) { Debug.Assert(false); return; }
|
||||
if (d == null) { Debug.Assert(false); return; }
|
||||
|
||||
foreach(KeyValuePair<string, ProtectedBinary> kvp in d)
|
||||
foreach (KeyValuePair<string, ProtectedBinary> kvp in d)
|
||||
{
|
||||
Add(kvp.Value);
|
||||
}
|
||||
@@ -124,16 +120,16 @@ namespace KeePassLib.Collections
|
||||
|
||||
public void AddFrom(PwGroup pg)
|
||||
{
|
||||
if(pg == null) { Debug.Assert(false); return; }
|
||||
if (pg == null) { Debug.Assert(false); return; }
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
if(pe == null) { Debug.Assert(false); return true; }
|
||||
if (pe == null) { Debug.Assert(false); return true; }
|
||||
|
||||
AddFrom(pe.Binaries);
|
||||
foreach(PwEntry peHistory in pe.History)
|
||||
foreach (PwEntry peHistory in pe.History)
|
||||
{
|
||||
if(peHistory == null) { Debug.Assert(false); continue; }
|
||||
if (peHistory == null) { Debug.Assert(false); continue; }
|
||||
AddFrom(peHistory.Binaries);
|
||||
}
|
||||
|
||||
@@ -148,9 +144,9 @@ namespace KeePassLib.Collections
|
||||
int n = m_d.Count;
|
||||
ProtectedBinary[] v = new ProtectedBinary[n];
|
||||
|
||||
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
|
||||
{
|
||||
if((kvp.Key < 0) || (kvp.Key >= n))
|
||||
if ((kvp.Key < 0) || (kvp.Key >= n))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new InvalidOperationException();
|
||||
@@ -159,9 +155,9 @@ namespace KeePassLib.Collections
|
||||
v[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
for(int i = 0; i < n; ++i)
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if(v[i] == null)
|
||||
if (v[i] == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new InvalidOperationException();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,8 +20,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using KeePassLib.Interfaces;
|
||||
|
||||
@@ -34,48 +34,74 @@ namespace KeePassLib.Collections
|
||||
public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>,
|
||||
IEnumerable<KeyValuePair<string, string>>, IEquatable<StringDictionaryEx>
|
||||
{
|
||||
private SortedDictionary<string, string> m_dict =
|
||||
private SortedDictionary<string, string> m_d =
|
||||
new SortedDictionary<string, string>();
|
||||
|
||||
// Non-null if and only if last mod. times should be remembered
|
||||
private Dictionary<string, DateTime> m_dLastMod = null;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return m_dict.Count; }
|
||||
get { return m_d.Count; }
|
||||
}
|
||||
|
||||
public StringDictionaryEx()
|
||||
{
|
||||
}
|
||||
|
||||
internal StringDictionaryEx(bool bRememberLastMod)
|
||||
{
|
||||
if (bRememberLastMod) m_dLastMod = new Dictionary<string, DateTime>();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_dict.GetEnumerator();
|
||||
return m_d.GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
return m_dict.GetEnumerator();
|
||||
return m_d.GetEnumerator();
|
||||
}
|
||||
|
||||
public StringDictionaryEx CloneDeep()
|
||||
{
|
||||
StringDictionaryEx sdNew = new StringDictionaryEx();
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in m_dict)
|
||||
sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable
|
||||
foreach (KeyValuePair<string, string> kvp in m_d)
|
||||
sdNew.m_d[kvp.Key] = kvp.Value;
|
||||
|
||||
if (m_dLastMod != null)
|
||||
sdNew.m_dLastMod = new Dictionary<string, DateTime>(m_dLastMod);
|
||||
|
||||
Debug.Assert(Equals(sdNew));
|
||||
return sdNew;
|
||||
}
|
||||
|
||||
public bool Equals(StringDictionaryEx sdOther)
|
||||
{
|
||||
if(sdOther == null) { Debug.Assert(false); return false; }
|
||||
if (sdOther == null) { Debug.Assert(false); return false; }
|
||||
|
||||
if(m_dict.Count != sdOther.m_dict.Count) return false;
|
||||
if (m_d.Count != sdOther.m_d.Count) return false;
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in sdOther.m_dict)
|
||||
foreach (KeyValuePair<string, string> kvp in sdOther.m_d)
|
||||
{
|
||||
string str = Get(kvp.Key);
|
||||
if((str == null) || (str != kvp.Value)) return false;
|
||||
if ((str == null) || (str != kvp.Value)) return false;
|
||||
}
|
||||
|
||||
int cLastModT = ((m_dLastMod != null) ? m_dLastMod.Count : -1);
|
||||
int cLastModO = ((sdOther.m_dLastMod != null) ? sdOther.m_dLastMod.Count : -1);
|
||||
if (cLastModT != cLastModO) return false;
|
||||
|
||||
if (m_dLastMod != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, DateTime> kvp in sdOther.m_dLastMod)
|
||||
{
|
||||
DateTime? odt = GetLastModificationTime(kvp.Key);
|
||||
if (!odt.HasValue) return false;
|
||||
if (odt.Value != kvp.Value) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -83,48 +109,62 @@ namespace KeePassLib.Collections
|
||||
|
||||
public string Get(string strName)
|
||||
{
|
||||
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
|
||||
string s;
|
||||
if(m_dict.TryGetValue(strName, out s)) return s;
|
||||
string str;
|
||||
m_d.TryGetValue(strName, out str);
|
||||
return str;
|
||||
}
|
||||
|
||||
internal DateTime? GetLastModificationTime(string strName)
|
||||
{
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
|
||||
if (m_dLastMod == null) return null;
|
||||
|
||||
DateTime dt;
|
||||
if (m_dLastMod.TryGetValue(strName, out dt)) return dt;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool Exists(string strName)
|
||||
{
|
||||
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
|
||||
return m_dict.ContainsKey(strName);
|
||||
return m_d.ContainsKey(strName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a string.
|
||||
/// </summary>
|
||||
/// <param name="strField">Identifier of the string field to modify.</param>
|
||||
/// <param name="strNewValue">New value. This parameter must not be <c>null</c>.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if one of the input
|
||||
/// parameters is <c>null</c>.</exception>
|
||||
public void Set(string strField, string strNewValue)
|
||||
public void Set(string strName, string strValue)
|
||||
{
|
||||
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); }
|
||||
if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); }
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
|
||||
|
||||
m_dict[strField] = strNewValue;
|
||||
m_d[strName] = strValue;
|
||||
|
||||
if (m_dLastMod != null) m_dLastMod[strName] = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a string.
|
||||
/// </summary>
|
||||
/// <param name="strField">Name of the string field to delete.</param>
|
||||
/// <returns>Returns <c>true</c> if the field has been successfully
|
||||
/// removed, otherwise the return value is <c>false</c>.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if the input
|
||||
/// parameter is <c>null</c>.</exception>
|
||||
public bool Remove(string strField)
|
||||
internal void Set(string strName, string strValue, DateTime? odtLastMod)
|
||||
{
|
||||
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); }
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
|
||||
|
||||
return m_dict.Remove(strField);
|
||||
m_d[strName] = strValue;
|
||||
|
||||
if (m_dLastMod != null)
|
||||
{
|
||||
if (odtLastMod.HasValue) m_dLastMod[strName] = odtLastMod.Value;
|
||||
else m_dLastMod.Remove(strName);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string strName)
|
||||
{
|
||||
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
|
||||
|
||||
if (m_dLastMod != null) m_dLastMod.Remove(strName);
|
||||
|
||||
return m_d.Remove(strName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,29 @@ using KeePassLib.Resources;
|
||||
namespace KeePassLib.Cryptography.Cipher
|
||||
{
|
||||
public sealed class ChaCha20Engine : ICipherEngine2
|
||||
{
|
||||
private PwUuid m_uuid = new PwUuid(new byte[] {
|
||||
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
|
||||
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A
|
||||
});
|
||||
{
|
||||
private static PwUuid m_uuid = null;
|
||||
|
||||
internal static PwUuid ChaCha20Uuid
|
||||
{
|
||||
get
|
||||
{
|
||||
PwUuid pu = m_uuid;
|
||||
if (pu == null)
|
||||
{
|
||||
pu = new PwUuid(new byte[] {
|
||||
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
|
||||
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A });
|
||||
m_uuid = pu;
|
||||
}
|
||||
|
||||
return pu;
|
||||
}
|
||||
}
|
||||
|
||||
public PwUuid CipherUuid
|
||||
{
|
||||
get { return m_uuid; }
|
||||
get { return ChaCha20Uuid; }
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,17 +21,22 @@ using System;
|
||||
|
||||
namespace KeePassLib.Interfaces
|
||||
{
|
||||
public interface IStructureItem : ITimeLogger // Provides LocationChanged
|
||||
{
|
||||
PwUuid Uuid
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public interface IStructureItem : ITimeLogger // Provides LocationChanged
|
||||
{
|
||||
PwUuid Uuid
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
PwGroup ParentGroup
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
PwGroup ParentGroup
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
PwUuid PreviousParentGroup
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@
|
||||
<Compile Include="IDatabaseFormat.cs" />
|
||||
<Compile Include="Keys\KcpKeyFile.Xml.cs" />
|
||||
<Compile Include="Kp2aLog.cs" />
|
||||
<Compile Include="PwGroup.Search.cs" />
|
||||
<Compile Include="Resources\Resource.designer.cs" />
|
||||
<Compile Include="Resources\KLRes.Generated.cs" />
|
||||
<Compile Include="Resources\KSRes.Generated.cs" />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2013 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,61 +18,106 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
#if !KeePassUAP
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
#endif
|
||||
|
||||
using KeePassLib.Utility;
|
||||
|
||||
|
||||
namespace KeePassLib
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Custom icon. <c>PwCustomIcon</c> objects are immutable.
|
||||
/// </summary>
|
||||
public sealed class PwCustomIcon
|
||||
{
|
||||
private PwUuid m_pwUuid;
|
||||
private byte[] m_pbImageDataPng;
|
||||
private Android.Graphics.Bitmap m_pCachedImage;
|
||||
// Recommended maximum sizes, not obligatory
|
||||
internal const int MaxWidth = 128;
|
||||
internal const int MaxHeight = 128;
|
||||
|
||||
private readonly PwUuid m_uuid;
|
||||
private readonly byte[] m_pbImageDataPng;
|
||||
|
||||
private string m_strName = string.Empty;
|
||||
private DateTime? m_odtLastMod = null;
|
||||
|
||||
private Dictionary<long, Android.Graphics.Bitmap> m_dImageCache = new Dictionary<long, Android.Graphics.Bitmap>();
|
||||
|
||||
public PwUuid Uuid
|
||||
{
|
||||
get { return m_pwUuid; }
|
||||
get { return m_uuid; }
|
||||
}
|
||||
|
||||
public byte[] ImageDataPng
|
||||
{
|
||||
get { return m_pbImageDataPng; }
|
||||
// When allowing 'set', do not copy the cache in 'Clone'
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return m_strName; }
|
||||
set
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
m_strName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? LastModificationTime
|
||||
{
|
||||
get { return m_odtLastMod; }
|
||||
set { m_odtLastMod = value; }
|
||||
}
|
||||
|
||||
[Obsolete("Use GetImage instead.")]
|
||||
public Android.Graphics.Bitmap Image
|
||||
{
|
||||
get { return m_pCachedImage; }
|
||||
get { return GetImage(); } // Backward compatibility
|
||||
}
|
||||
|
||||
public PwCustomIcon(PwUuid pu, byte[] pbImageDataPng)
|
||||
{
|
||||
if (pu == null) { Debug.Assert(false); throw new ArgumentNullException("pu"); }
|
||||
if (pu.Equals(PwUuid.Zero)) { Debug.Assert(false); throw new ArgumentOutOfRangeException("pu"); }
|
||||
if (pbImageDataPng == null) { Debug.Assert(false); throw new ArgumentNullException("pbImageDataPng"); }
|
||||
|
||||
m_uuid = pu;
|
||||
m_pbImageDataPng = pbImageDataPng;
|
||||
}
|
||||
|
||||
public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng)
|
||||
private static long GetKey(int w, int h)
|
||||
{
|
||||
Debug.Assert(pwUuid != null);
|
||||
if(pwUuid == null) throw new ArgumentNullException("pwUuid");
|
||||
Debug.Assert(!pwUuid.Equals(PwUuid.Zero));
|
||||
if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0");
|
||||
return (((long)w << 32) ^ (long)h);
|
||||
}
|
||||
|
||||
Debug.Assert(pbImageDataPng != null);
|
||||
if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng");
|
||||
/// <summary>
|
||||
/// Get the icon as an <c>Image</c> (original size).
|
||||
/// </summary>
|
||||
public Android.Graphics.Bitmap GetImage()
|
||||
{
|
||||
const long lKey = -1;
|
||||
|
||||
m_pwUuid = pwUuid;
|
||||
m_pbImageDataPng = pbImageDataPng;
|
||||
Android.Graphics.Bitmap img;
|
||||
if (m_dImageCache.TryGetValue(lKey, out img)) return img;
|
||||
|
||||
#if !KeePassLibSD
|
||||
// MemoryStream ms = new MemoryStream(m_pbImageDataPng, false);
|
||||
// m_pCachedImage = Image.FromStream(ms);
|
||||
// ms.Close();
|
||||
m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng);
|
||||
#else
|
||||
m_pCachedImage = null;
|
||||
#endif
|
||||
try { img = GfxUtil.LoadImage(m_pbImageDataPng); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
m_dImageCache[lKey] = img;
|
||||
return img;
|
||||
}
|
||||
|
||||
internal PwCustomIcon Clone()
|
||||
{
|
||||
PwCustomIcon ico = new PwCustomIcon(m_uuid, m_pbImageDataPng);
|
||||
|
||||
ico.m_strName = m_strName;
|
||||
ico.m_odtLastMod = m_odtLastMod;
|
||||
|
||||
ico.m_dImageCache = m_dImageCache; // Same image data
|
||||
|
||||
return ico;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace KeePassLib
|
||||
private int m_nHistoryMaxItems = DefaultHistoryMaxItems;
|
||||
private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes
|
||||
|
||||
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
|
||||
private StringDictionaryEx m_dCustomData = new StringDictionaryEx(true);
|
||||
private VariantDictionary m_dPublicCustomData = new VariantDictionary();
|
||||
|
||||
private byte[] m_pbHashOfFileOnDisk = null;
|
||||
@@ -690,9 +690,9 @@ namespace KeePassLib
|
||||
public void MergeIn(PwDatabase pdSource, PwMergeMethod mm,
|
||||
IStatusLogger slStatus)
|
||||
{
|
||||
if(pdSource == null) throw new ArgumentNullException("pdSource");
|
||||
if (pdSource == null) throw new ArgumentNullException("pdSource");
|
||||
|
||||
if(mm == PwMergeMethod.CreateNewUuids)
|
||||
if (mm == PwMergeMethod.CreateNewUuids)
|
||||
{
|
||||
pdSource.RootGroup.Uuid = new PwUuid(true);
|
||||
pdSource.RootGroup.CreateNewItemUuids(true, true, true);
|
||||
@@ -707,7 +707,7 @@ namespace KeePassLib
|
||||
PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup);
|
||||
PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup);
|
||||
|
||||
GroupHandler ghSrc = delegate(PwGroup pg)
|
||||
GroupHandler ghSrc = delegate (PwGroup pg)
|
||||
{
|
||||
// if(pg == pdSource.m_pgRootGroup) return true;
|
||||
|
||||
@@ -716,11 +716,11 @@ namespace KeePassLib
|
||||
// pool should not be modified)
|
||||
PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true);
|
||||
|
||||
if(pgLocal == null)
|
||||
if (pgLocal == null)
|
||||
{
|
||||
PwGroup pgSourceParent = pg.ParentGroup;
|
||||
PwGroup pgLocalContainer;
|
||||
if(pgSourceParent == null)
|
||||
if (pgSourceParent == null)
|
||||
{
|
||||
// pg is the root group of pdSource, and no corresponding
|
||||
// local group was found; create the group within the
|
||||
@@ -728,17 +728,23 @@ namespace KeePassLib
|
||||
Debug.Assert(pg == pdSource.m_pgRootGroup);
|
||||
pgLocalContainer = m_pgRootGroup;
|
||||
}
|
||||
else if(pgSourceParent == pdSource.m_pgRootGroup)
|
||||
else if (pgSourceParent == pdSource.m_pgRootGroup)
|
||||
pgLocalContainer = m_pgRootGroup;
|
||||
else
|
||||
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
|
||||
Debug.Assert(pgLocalContainer != null);
|
||||
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
|
||||
if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
|
||||
|
||||
PwGroup pgNew = new PwGroup(false, false);
|
||||
pgNew.Uuid = pg.Uuid;
|
||||
pgNew.AssignProperties(pg, false, true);
|
||||
|
||||
if (!pgLocalContainer.CanAddGroup(pgNew))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
pgLocalContainer = m_pgRootGroup;
|
||||
pgLocalContainer.CheckCanAddGroup(pgNew);
|
||||
}
|
||||
// pgLocalContainer.AddGroup(pgNew, true);
|
||||
InsertObjectAtBestPos<PwGroup>(pgLocalContainer.Groups, pgNew, ppSrc);
|
||||
pgNew.ParentGroup = pgLocalContainer;
|
||||
@@ -747,9 +753,9 @@ namespace KeePassLib
|
||||
{
|
||||
Debug.Assert(mm != PwMergeMethod.CreateNewUuids);
|
||||
|
||||
if(mm == PwMergeMethod.OverwriteExisting)
|
||||
if (mm == PwMergeMethod.OverwriteExisting)
|
||||
pgLocal.AssignProperties(pg, false, false);
|
||||
else if((mm == PwMergeMethod.OverwriteIfNewer) ||
|
||||
else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
|
||||
(mm == PwMergeMethod.Synchronize))
|
||||
{
|
||||
pgLocal.AssignProperties(pg, true, false);
|
||||
@@ -760,23 +766,23 @@ namespace KeePassLib
|
||||
return ((slStatus != null) ? slStatus.ContinueWork() : true);
|
||||
};
|
||||
|
||||
EntryHandler ehSrc = delegate(PwEntry pe)
|
||||
EntryHandler ehSrc = delegate (PwEntry pe)
|
||||
{
|
||||
// PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true);
|
||||
PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry);
|
||||
Debug.Assert(object.ReferenceEquals(peLocal,
|
||||
m_pgRootGroup.FindEntry(pe.Uuid, true)));
|
||||
|
||||
if(peLocal == null)
|
||||
if (peLocal == null)
|
||||
{
|
||||
PwGroup pgSourceParent = pe.ParentGroup;
|
||||
PwGroup pgLocalContainer;
|
||||
if(pgSourceParent == pdSource.m_pgRootGroup)
|
||||
if (pgSourceParent == pdSource.m_pgRootGroup)
|
||||
pgLocalContainer = m_pgRootGroup;
|
||||
else
|
||||
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
|
||||
Debug.Assert(pgLocalContainer != null);
|
||||
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
|
||||
if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
|
||||
|
||||
PwEntry peNew = new PwEntry(false, false);
|
||||
peNew.Uuid = pe.Uuid;
|
||||
@@ -796,19 +802,19 @@ namespace KeePassLib
|
||||
bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None);
|
||||
|
||||
bool bOrgBackup = !bEquals;
|
||||
if(mm != PwMergeMethod.OverwriteExisting)
|
||||
if (mm != PwMergeMethod.OverwriteExisting)
|
||||
bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0);
|
||||
bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true);
|
||||
if(bOrgBackup) peLocal.CreateBackup(null); // Maintain at end
|
||||
if (bOrgBackup) peLocal.CreateBackup(null); // Maintain at end
|
||||
|
||||
bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting);
|
||||
bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0);
|
||||
bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true);
|
||||
if(bSrcBackup) pe.CreateBackup(null); // Maintain at end
|
||||
if (bSrcBackup) pe.CreateBackup(null); // Maintain at end
|
||||
|
||||
if(mm == PwMergeMethod.OverwriteExisting)
|
||||
if (mm == PwMergeMethod.OverwriteExisting)
|
||||
peLocal.AssignProperties(pe, false, false, false);
|
||||
else if((mm == PwMergeMethod.OverwriteIfNewer) ||
|
||||
else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
|
||||
(mm == PwMergeMethod.Synchronize))
|
||||
{
|
||||
peLocal.AssignProperties(pe, true, false, false);
|
||||
@@ -822,13 +828,13 @@ namespace KeePassLib
|
||||
};
|
||||
|
||||
ghSrc(pdSource.RootGroup);
|
||||
if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc))
|
||||
if (!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
IStatusLogger slPrevStatus = m_slStatus;
|
||||
m_slStatus = slStatus;
|
||||
|
||||
if(mm == PwMergeMethod.Synchronize)
|
||||
if (mm == PwMergeMethod.Synchronize)
|
||||
{
|
||||
RelocateGroups(ppOrg, ppSrc);
|
||||
RelocateEntries(ppOrg, ppSrc);
|
||||
@@ -838,24 +844,24 @@ namespace KeePassLib
|
||||
MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc);
|
||||
ppOrg = null; // Pools are now invalid, because the location
|
||||
ppSrc = null; // changed times have been merged in
|
||||
|
||||
// Delete *after* relocating, because relocating might
|
||||
// empty some groups that are marked for deletion (and
|
||||
// objects that weren't relocated yet might prevent the
|
||||
// deletion)
|
||||
Dictionary<PwUuid, PwDeletedObject> dOrgDel = CreateDeletedObjectsPool();
|
||||
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel);
|
||||
ApplyDeletions(m_pgRootGroup, dOrgDel);
|
||||
|
||||
// The list and the dictionary should be kept in sync
|
||||
Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count);
|
||||
}
|
||||
|
||||
// Delete *after* relocating, because relocating might empty
|
||||
// some groups that are marked for deletion (and objects
|
||||
// that weren't relocated yet might prevent the deletion)
|
||||
Dictionary<PwUuid, PwDeletedObject> dDel = CreateDeletedObjectsPool();
|
||||
if (mm == PwMergeMethod.Synchronize)
|
||||
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dDel);
|
||||
ApplyDeletions(m_pgRootGroup, dDel);
|
||||
// The list and the dictionary should be kept in sync
|
||||
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
|
||||
|
||||
// Must be called *after* merging groups, because group UUIDs
|
||||
// are required for recycle bin and entry template UUIDs
|
||||
MergeInDbProperties(pdSource, mm);
|
||||
|
||||
MergeInCustomIcons(pdSource);
|
||||
MergeInCustomIcons(pdSource, dDel);
|
||||
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
|
||||
|
||||
MaintainBackups();
|
||||
|
||||
@@ -863,15 +869,79 @@ namespace KeePassLib
|
||||
m_slStatus = slPrevStatus;
|
||||
}
|
||||
|
||||
private void MergeInCustomIcons(PwDatabase pdSource)
|
||||
{
|
||||
foreach(PwCustomIcon pwci in pdSource.CustomIcons)
|
||||
{
|
||||
if(GetCustomIconIndex(pwci.Uuid) >= 0) continue;
|
||||
|
||||
m_vCustomIcons.Add(pwci); // PwCustomIcon is immutable
|
||||
m_bUINeedsIconUpdate = true;
|
||||
private void MergeInCustomIcons(PwDatabase pdSource,
|
||||
Dictionary<PwUuid, PwDeletedObject> dDel)
|
||||
{
|
||||
bool bIconsMod = false;
|
||||
|
||||
Dictionary<PwUuid, int> d = new Dictionary<PwUuid, int>();
|
||||
for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
|
||||
d[m_vCustomIcons[i].Uuid] = i;
|
||||
Debug.Assert(d.Count == m_vCustomIcons.Count); // UUIDs unique
|
||||
|
||||
foreach (PwCustomIcon ciS in pdSource.m_vCustomIcons)
|
||||
{
|
||||
int iT;
|
||||
if (d.TryGetValue(ciS.Uuid, out iT))
|
||||
{
|
||||
PwCustomIcon ciT = m_vCustomIcons[iT];
|
||||
|
||||
DateTime? odtT = ciT.LastModificationTime;
|
||||
DateTime? odtS = ciS.LastModificationTime;
|
||||
|
||||
if (odtT.HasValue && odtS.HasValue)
|
||||
{
|
||||
if (odtT.Value >= odtS.Value) continue;
|
||||
}
|
||||
else if (odtT.HasValue) continue;
|
||||
else if (!odtS.HasValue) continue; // Both no time
|
||||
|
||||
m_vCustomIcons[iT] = ciS.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
d[ciS.Uuid] = m_vCustomIcons.Count;
|
||||
m_vCustomIcons.Add(ciS.Clone());
|
||||
}
|
||||
|
||||
bIconsMod = true;
|
||||
}
|
||||
|
||||
List<PwDeletedObject> lObsoleteDel = new List<PwDeletedObject>();
|
||||
foreach (KeyValuePair<PwUuid, PwDeletedObject> kvpDel in dDel)
|
||||
{
|
||||
int iT;
|
||||
if (d.TryGetValue(kvpDel.Key, out iT))
|
||||
{
|
||||
PwCustomIcon ci = m_vCustomIcons[iT];
|
||||
if (ci == null) { Debug.Assert(false); continue; } // Dup. del. obj.?
|
||||
|
||||
DateTime? odt = ci.LastModificationTime;
|
||||
|
||||
if (odt.HasValue && (odt.Value > kvpDel.Value.DeletionTime))
|
||||
lObsoleteDel.Add(kvpDel.Value);
|
||||
else
|
||||
{
|
||||
m_vCustomIcons[iT] = null; // Preserve indices, removed below
|
||||
bIconsMod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
|
||||
m_vCustomIcons.RemoveAll(f);
|
||||
|
||||
foreach (PwDeletedObject pdo in lObsoleteDel)
|
||||
{
|
||||
// Prevent future deletion attempts
|
||||
if (!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); }
|
||||
if (!dDel.Remove(pdo.Uuid)) { Debug.Assert(false); }
|
||||
}
|
||||
|
||||
if (bIconsMod) m_bUINeedsIconUpdate = true;
|
||||
|
||||
FixCustomIconRefs();
|
||||
}
|
||||
|
||||
private Dictionary<PwUuid, PwDeletedObject> CreateDeletedObjectsPool()
|
||||
@@ -1212,7 +1282,9 @@ namespace KeePassLib
|
||||
PwObjectBlock<T> b = new PwObjectBlock<T>();
|
||||
|
||||
DateTime dtLoc;
|
||||
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc);
|
||||
PwUuid puPrevParent;
|
||||
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc,
|
||||
out puPrevParent);
|
||||
b.Add(t, dtLoc, pPool);
|
||||
|
||||
lBlocks.Add(b);
|
||||
@@ -1247,7 +1319,7 @@ namespace KeePassLib
|
||||
}
|
||||
if(idSrcNext == 0) break;
|
||||
|
||||
pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc);
|
||||
pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc, out puPrevParent);
|
||||
b.Add(tNext, dtLoc, pPool);
|
||||
|
||||
++u;
|
||||
@@ -1260,28 +1332,31 @@ namespace KeePassLib
|
||||
}
|
||||
|
||||
private static PwObjectPoolEx GetBestPool<T>(T t, PwObjectPoolEx ppOrg,
|
||||
PwObjectPoolEx ppSrc, out DateTime dtLoc)
|
||||
PwObjectPoolEx ppSrc, out DateTime dtLoc, out PwUuid puPrevParent)
|
||||
where T : class, ITimeLogger, IStructureItem, IDeepCloneable<T>
|
||||
{
|
||||
PwObjectPoolEx p = null;
|
||||
dtLoc = TimeUtil.SafeMinValueUtc;
|
||||
PwObjectPoolEx p = null;
|
||||
dtLoc = TimeUtil.SafeMinValueUtc;
|
||||
puPrevParent = PwUuid.Zero;
|
||||
|
||||
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
|
||||
if(ptOrg != null)
|
||||
{
|
||||
dtLoc = ptOrg.LocationChanged;
|
||||
p = ppOrg;
|
||||
}
|
||||
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
|
||||
if (ptOrg != null)
|
||||
{
|
||||
dtLoc = ptOrg.LocationChanged;
|
||||
puPrevParent = ptOrg.PreviousParentGroup;
|
||||
p = ppOrg;
|
||||
}
|
||||
|
||||
IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid);
|
||||
if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
|
||||
{
|
||||
dtLoc = ptSrc.LocationChanged;
|
||||
p = ppSrc;
|
||||
}
|
||||
IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid);
|
||||
if ((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
|
||||
{
|
||||
dtLoc = ptSrc.LocationChanged;
|
||||
puPrevParent = ptSrc.PreviousParentGroup;
|
||||
p = ppSrc;
|
||||
}
|
||||
|
||||
Debug.Assert(p != null);
|
||||
return p;
|
||||
Debug.Assert(p != null);
|
||||
return p;
|
||||
}
|
||||
|
||||
private static int FindLocationChangedPivot<T>(List<PwObjectBlock<T>> lBlocks,
|
||||
@@ -1307,30 +1382,40 @@ namespace KeePassLib
|
||||
return iPosMax;
|
||||
}
|
||||
|
||||
private static void MergeInLocationChanged(PwGroup pg,
|
||||
PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc)
|
||||
{
|
||||
GroupHandler gh = delegate(PwGroup pgSub)
|
||||
{
|
||||
DateTime dt;
|
||||
if(GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt) != null)
|
||||
pgSub.LocationChanged = dt;
|
||||
else { Debug.Assert(false); }
|
||||
return true;
|
||||
};
|
||||
private static void MergeInLocationChanged(PwGroup pg,
|
||||
PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc)
|
||||
{
|
||||
GroupHandler gh = delegate (PwGroup pgSub)
|
||||
{
|
||||
DateTime dt;
|
||||
PwUuid puPrevParent;
|
||||
if (GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt,
|
||||
out puPrevParent) != null)
|
||||
{
|
||||
pgSub.LocationChanged = dt;
|
||||
pgSub.PreviousParentGroup = puPrevParent;
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
return true;
|
||||
};
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
{
|
||||
DateTime dt;
|
||||
if(GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt) != null)
|
||||
pe.LocationChanged = dt;
|
||||
else { Debug.Assert(false); }
|
||||
return true;
|
||||
};
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
DateTime dt;
|
||||
PwUuid puPrevParent;
|
||||
if (GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt,
|
||||
out puPrevParent) != null)
|
||||
{
|
||||
pe.LocationChanged = dt;
|
||||
pe.PreviousParentGroup = puPrevParent;
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
return true;
|
||||
};
|
||||
|
||||
gh(pg);
|
||||
pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
}
|
||||
gh(pg);
|
||||
pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
}
|
||||
|
||||
private static void InsertObjectAtBestPos<T>(PwObjectList<T> lItems,
|
||||
T tNew, PwObjectPoolEx ppSrc)
|
||||
@@ -1445,12 +1530,18 @@ namespace KeePassLib
|
||||
foreach(KeyValuePair<string, string> kvp in pdSource.m_dCustomData)
|
||||
{
|
||||
if(bSourceNewer || !m_dCustomData.Exists(kvp.Key))
|
||||
m_dCustomData.Set(kvp.Key, kvp.Value);
|
||||
m_dCustomData.Set(kvp.Key, kvp.Value, null);
|
||||
}
|
||||
|
||||
VariantDictionary vdLocal = m_dPublicCustomData; // Backup
|
||||
m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone();
|
||||
if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge
|
||||
// 'Clone' duplicates deep values (e.g. byte arrays)
|
||||
VariantDictionary vdS = (VariantDictionary)pdSource.m_dPublicCustomData.Clone();
|
||||
if (bForce || bSourceNewer)
|
||||
vdS.CopyTo(m_dPublicCustomData);
|
||||
else
|
||||
{
|
||||
m_dPublicCustomData.CopyTo(vdS);
|
||||
m_dPublicCustomData = vdS;
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeEntryHistory(PwEntry pe, PwEntry peSource,
|
||||
@@ -1543,12 +1634,12 @@ namespace KeePassLib
|
||||
/// <returns>Index of the icon.</returns>
|
||||
public int GetCustomIconIndex(PwUuid pwIconId)
|
||||
{
|
||||
for(int i = 0; i < m_vCustomIcons.Count; ++i)
|
||||
{
|
||||
PwCustomIcon pwci = m_vCustomIcons[i];
|
||||
if(pwci.Uuid.Equals(pwIconId))
|
||||
return i;
|
||||
}
|
||||
for (int i = 0; i < m_vCustomIcons.Count; ++i)
|
||||
{
|
||||
PwCustomIcon pwci = m_vCustomIcons[i];
|
||||
if (pwci.Uuid.Equals(pwIconId))
|
||||
return i;
|
||||
}
|
||||
|
||||
// Debug.Assert(false); // Do not assert
|
||||
return -1;
|
||||
@@ -1558,15 +1649,15 @@ namespace KeePassLib
|
||||
{
|
||||
if(pbPngData == null) { Debug.Assert(false); return -1; }
|
||||
|
||||
for(int i = 0; i < m_vCustomIcons.Count; ++i)
|
||||
{
|
||||
PwCustomIcon pwci = m_vCustomIcons[i];
|
||||
byte[] pbEx = pwci.ImageDataPng;
|
||||
if(pbEx == null) { Debug.Assert(false); continue; }
|
||||
for (int i = 0; i < m_vCustomIcons.Count; ++i)
|
||||
{
|
||||
PwCustomIcon pwci = m_vCustomIcons[i];
|
||||
byte[] pbEx = pwci.ImageDataPng;
|
||||
if (pbEx == null) { Debug.Assert(false); continue; }
|
||||
|
||||
if(MemUtil.ArraysEqual(pbEx, pbPngData))
|
||||
return i;
|
||||
}
|
||||
if (MemUtil.ArraysEqual(pbEx, pbPngData))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -1585,68 +1676,64 @@ namespace KeePassLib
|
||||
else { Debug.Assert(false); return null; }
|
||||
}
|
||||
|
||||
public bool DeleteCustomIcons(List<PwUuid> vUuidsToDelete)
|
||||
{
|
||||
Debug.Assert(vUuidsToDelete != null);
|
||||
if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete");
|
||||
if(vUuidsToDelete.Count <= 0) return true;
|
||||
public bool DeleteCustomIcons(List<PwUuid> lUuids)
|
||||
{
|
||||
if (lUuids == null) { Debug.Assert(false); throw new ArgumentNullException("lUuids"); }
|
||||
if (lUuids.Count == 0) return false;
|
||||
|
||||
GroupHandler gh = delegate(PwGroup pg)
|
||||
{
|
||||
PwUuid uuidThis = pg.CustomIconUuid;
|
||||
if(uuidThis.Equals(PwUuid.Zero)) return true;
|
||||
Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
|
||||
foreach (PwUuid pu in lUuids) { dToDel[pu] = true; }
|
||||
|
||||
foreach(PwUuid uuidDelete in vUuidsToDelete)
|
||||
{
|
||||
if(uuidThis.Equals(uuidDelete))
|
||||
{
|
||||
pg.CustomIconUuid = PwUuid.Zero;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DateTime dt = DateTime.UtcNow;
|
||||
for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
|
||||
{
|
||||
PwUuid pu = m_vCustomIcons[i].Uuid;
|
||||
if (dToDel.ContainsKey(pu))
|
||||
{
|
||||
m_vCustomIcons[i] = null; // Removed below
|
||||
m_vDeletedObjects.Add(new PwDeletedObject(pu, dt));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
|
||||
m_vCustomIcons.RemoveAll(f);
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
{
|
||||
RemoveCustomIconUuid(pe, vUuidsToDelete);
|
||||
return true;
|
||||
};
|
||||
FixCustomIconRefs();
|
||||
return true;
|
||||
}
|
||||
|
||||
gh(m_pgRootGroup);
|
||||
if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return false;
|
||||
}
|
||||
private void FixCustomIconRefs()
|
||||
{
|
||||
Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>();
|
||||
foreach (PwCustomIcon ci in m_vCustomIcons) { d[ci.Uuid] = true; }
|
||||
|
||||
foreach(PwUuid pwUuid in vUuidsToDelete)
|
||||
{
|
||||
int nIndex = GetCustomIconIndex(pwUuid);
|
||||
if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex);
|
||||
}
|
||||
GroupHandler gh = delegate (PwGroup pg)
|
||||
{
|
||||
PwUuid pu = pg.CustomIconUuid;
|
||||
if (pu.Equals(PwUuid.Zero)) return true;
|
||||
if (!d.ContainsKey(pu)) pg.CustomIconUuid = PwUuid.Zero;
|
||||
return true;
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
FixCustomIconRefs(pe, d);
|
||||
return true;
|
||||
};
|
||||
|
||||
private static void RemoveCustomIconUuid(PwEntry pe, List<PwUuid> vToDelete)
|
||||
{
|
||||
PwUuid uuidThis = pe.CustomIconUuid;
|
||||
if(uuidThis.Equals(PwUuid.Zero)) return;
|
||||
gh(m_pgRootGroup);
|
||||
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
}
|
||||
|
||||
foreach(PwUuid uuidDelete in vToDelete)
|
||||
{
|
||||
if(uuidThis.Equals(uuidDelete))
|
||||
{
|
||||
pe.CustomIconUuid = PwUuid.Zero;
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void FixCustomIconRefs(PwEntry pe, Dictionary<PwUuid, bool> d)
|
||||
{
|
||||
PwUuid pu = pe.CustomIconUuid;
|
||||
if (pu.Equals(PwUuid.Zero)) return;
|
||||
if (!d.ContainsKey(pu)) pe.CustomIconUuid = PwUuid.Zero;
|
||||
|
||||
foreach (PwEntry peH in pe.History) FixCustomIconRefs(peH, d);
|
||||
}
|
||||
|
||||
foreach(PwEntry peHistory in pe.History)
|
||||
RemoveCustomIconUuid(peHistory, vToDelete);
|
||||
}
|
||||
|
||||
private int GetTotalObjectUuidCount()
|
||||
{
|
||||
@@ -1935,61 +2022,119 @@ namespace KeePassLib
|
||||
return uDeleted;
|
||||
}
|
||||
|
||||
|
||||
public uint DeleteUnusedCustomIcons()
|
||||
{
|
||||
List<PwUuid> lToDelete = new List<PwUuid>();
|
||||
foreach(PwCustomIcon pwci in m_vCustomIcons)
|
||||
lToDelete.Add(pwci.Uuid);
|
||||
Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
|
||||
foreach (PwCustomIcon ci in m_vCustomIcons) { dToDel[ci.Uuid] = true; }
|
||||
|
||||
GroupHandler gh = delegate(PwGroup pg)
|
||||
GroupHandler gh = delegate (PwGroup pg)
|
||||
{
|
||||
PwUuid pwUuid = pg.CustomIconUuid;
|
||||
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true;
|
||||
|
||||
for(int i = 0; i < lToDelete.Count; ++i)
|
||||
{
|
||||
if(lToDelete[i].Equals(pwUuid))
|
||||
{
|
||||
lToDelete.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PwUuid pu = pg.CustomIconUuid;
|
||||
if (!pu.Equals(PwUuid.Zero)) dToDel.Remove(pu);
|
||||
return true;
|
||||
};
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
PwUuid pwUuid = pe.CustomIconUuid;
|
||||
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true;
|
||||
|
||||
for(int i = 0; i < lToDelete.Count; ++i)
|
||||
{
|
||||
if(lToDelete[i].Equals(pwUuid))
|
||||
{
|
||||
lToDelete.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveCustomIconsFromDict(dToDel, pe);
|
||||
return true;
|
||||
};
|
||||
|
||||
gh(m_pgRootGroup);
|
||||
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
|
||||
uint uDeleted = 0;
|
||||
foreach(PwUuid pwDel in lToDelete)
|
||||
uint cDel = (uint)dToDel.Count;
|
||||
if (cDel != 0)
|
||||
{
|
||||
int nIndex = GetCustomIconIndex(pwDel);
|
||||
if(nIndex < 0) { Debug.Assert(false); continue; }
|
||||
|
||||
m_vCustomIcons.RemoveAt(nIndex);
|
||||
++uDeleted;
|
||||
DeleteCustomIcons(new List<PwUuid>(dToDel.Keys));
|
||||
m_bUINeedsIconUpdate = true;
|
||||
}
|
||||
|
||||
if(uDeleted > 0) m_bUINeedsIconUpdate = true;
|
||||
return uDeleted;
|
||||
return cDel;
|
||||
}
|
||||
|
||||
private static void RemoveCustomIconsFromDict(Dictionary<PwUuid, bool> d,
|
||||
PwEntry pe)
|
||||
{
|
||||
PwUuid pu = pe.CustomIconUuid;
|
||||
if (!pu.Equals(PwUuid.Zero)) d.Remove(pu);
|
||||
|
||||
foreach (PwEntry peH in pe.History) RemoveCustomIconsFromDict(d, peH);
|
||||
}
|
||||
|
||||
internal static void CopyCustomIcons(PwDatabase pdFrom, PwDatabase pdTo,
|
||||
PwGroup pgSelect, bool bResetIfUnknown)
|
||||
{
|
||||
if (pgSelect == null) { Debug.Assert(false); return; }
|
||||
|
||||
Dictionary<PwUuid, PwCustomIcon> dFrom = new Dictionary<PwUuid, PwCustomIcon>();
|
||||
if (pdFrom != null)
|
||||
{
|
||||
foreach (PwCustomIcon ci in pdFrom.m_vCustomIcons)
|
||||
dFrom[ci.Uuid] = ci;
|
||||
}
|
||||
|
||||
Dictionary<PwUuid, int> dTo = new Dictionary<PwUuid, int>();
|
||||
if (pdTo != null)
|
||||
{
|
||||
for (int i = pdTo.m_vCustomIcons.Count - 1; i >= 0; --i)
|
||||
dTo[pdTo.m_vCustomIcons[i].Uuid] = i;
|
||||
}
|
||||
|
||||
Func<PwUuid, bool> fEnsureIcon = delegate (PwUuid puIcon)
|
||||
{
|
||||
if (puIcon.Equals(PwUuid.Zero)) return true;
|
||||
if (pdTo == null) { Debug.Assert(false); return false; }
|
||||
|
||||
PwCustomIcon ciFrom;
|
||||
if (!dFrom.TryGetValue(puIcon, out ciFrom)) { Debug.Assert(false); return false; }
|
||||
|
||||
int iTo;
|
||||
if (dTo.TryGetValue(puIcon, out iTo))
|
||||
{
|
||||
PwCustomIcon ciTo = pdTo.m_vCustomIcons[iTo];
|
||||
|
||||
DateTime? odtFrom = ciFrom.LastModificationTime;
|
||||
DateTime? odtTo = ciTo.LastModificationTime;
|
||||
|
||||
if (odtFrom.HasValue && odtTo.HasValue)
|
||||
{
|
||||
if (odtFrom.Value <= odtTo.Value) return true;
|
||||
}
|
||||
else if (odtTo.HasValue) return true;
|
||||
else if (!odtFrom.HasValue) return true; // Both no time
|
||||
|
||||
pdTo.m_vCustomIcons[iTo] = ciFrom.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
dTo[puIcon] = pdTo.m_vCustomIcons.Count;
|
||||
pdTo.m_vCustomIcons.Add(ciFrom.Clone());
|
||||
}
|
||||
|
||||
pdTo.Modified = true;
|
||||
pdTo.UINeedsIconUpdate = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
GroupHandler gh = delegate (PwGroup pgCur)
|
||||
{
|
||||
bool bTo = fEnsureIcon(pgCur.CustomIconUuid);
|
||||
if (!bTo && bResetIfUnknown) pgCur.CustomIconUuid = PwUuid.Zero;
|
||||
return true;
|
||||
};
|
||||
|
||||
EntryHandler eh = delegate (PwEntry peCur)
|
||||
{
|
||||
bool bTo = fEnsureIcon(peCur.CustomIconUuid);
|
||||
if (!bTo && bResetIfUnknown) peCur.CustomIconUuid = PwUuid.Zero;
|
||||
return true;
|
||||
};
|
||||
|
||||
gh(pgSelect);
|
||||
pgSelect.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -228,6 +228,18 @@ namespace KeePassLib
|
||||
/// </summary>
|
||||
public sealed class SearchParameters
|
||||
{
|
||||
private string m_strName = string.Empty;
|
||||
[DefaultValue("")]
|
||||
public string Name
|
||||
{
|
||||
get { return m_strName; }
|
||||
set
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
m_strName = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string m_strText = string.Empty;
|
||||
[DefaultValue("")]
|
||||
public string SearchString
|
||||
@@ -235,17 +247,25 @@ namespace KeePassLib
|
||||
get { return m_strText; }
|
||||
set
|
||||
{
|
||||
if(value == null) throw new ArgumentNullException("value");
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
m_strText = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_bRegex = false;
|
||||
private PwSearchMode m_sm = PwSearchMode.Simple;
|
||||
public PwSearchMode SearchMode
|
||||
{
|
||||
get { return m_sm; }
|
||||
set { m_sm = value; }
|
||||
}
|
||||
|
||||
[DefaultValue(false)]
|
||||
[Obsolete]
|
||||
[XmlIgnore]
|
||||
public bool RegularExpression
|
||||
{
|
||||
get { return m_bRegex; }
|
||||
set { m_bRegex = value; }
|
||||
get { return (m_sm == PwSearchMode.Regular); }
|
||||
set { m_sm = (value ? PwSearchMode.Regular : PwSearchMode.Simple); }
|
||||
}
|
||||
|
||||
private bool m_bSearchInTitles = true;
|
||||
@@ -296,6 +316,22 @@ namespace KeePassLib
|
||||
set { m_bSearchInOther = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInStringNames = false;
|
||||
[DefaultValue(false)]
|
||||
public bool SearchInStringNames
|
||||
{
|
||||
get { return m_bSearchInStringNames; }
|
||||
set { m_bSearchInStringNames = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInTags = true;
|
||||
[DefaultValue(true)]
|
||||
public bool SearchInTags
|
||||
{
|
||||
get { return m_bSearchInTags; }
|
||||
set { m_bSearchInTags = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInUuids = false;
|
||||
[DefaultValue(false)]
|
||||
public bool SearchInUuids
|
||||
@@ -304,6 +340,14 @@ namespace KeePassLib
|
||||
set { m_bSearchInUuids = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInGroupPaths = false;
|
||||
[DefaultValue(false)]
|
||||
public bool SearchInGroupPaths
|
||||
{
|
||||
get { return m_bSearchInGroupPaths; }
|
||||
set { m_bSearchInGroupPaths = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInGroupNames = false;
|
||||
[DefaultValue(false)]
|
||||
public bool SearchInGroupNames
|
||||
@@ -312,12 +356,12 @@ namespace KeePassLib
|
||||
set { m_bSearchInGroupNames = value; }
|
||||
}
|
||||
|
||||
private bool m_bSearchInTags = true;
|
||||
[DefaultValue(true)]
|
||||
public bool SearchInTags
|
||||
private bool m_bSearchInHistory = false;
|
||||
[DefaultValue(false)]
|
||||
public bool SearchInHistory
|
||||
{
|
||||
get { return m_bSearchInTags; }
|
||||
set { m_bSearchInTags = value; }
|
||||
get { return m_bSearchInHistory; }
|
||||
set { m_bSearchInHistory = value; }
|
||||
}
|
||||
|
||||
#if KeePassUAP
|
||||
@@ -369,7 +413,7 @@ namespace KeePassLib
|
||||
get { return m_strDataTrf; }
|
||||
set
|
||||
{
|
||||
if(value == null) throw new ArgumentNullException("value");
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
m_strDataTrf = value;
|
||||
}
|
||||
}
|
||||
@@ -381,20 +425,24 @@ namespace KeePassLib
|
||||
{
|
||||
SearchParameters sp = new SearchParameters();
|
||||
|
||||
// sp.m_strText = string.Empty;
|
||||
// sp.m_bRegex = false;
|
||||
Debug.Assert(sp.m_strName.Length == 0);
|
||||
Debug.Assert(sp.m_strText.Length == 0);
|
||||
Debug.Assert(sp.m_sm == PwSearchMode.Simple);
|
||||
sp.m_bSearchInTitles = false;
|
||||
sp.m_bSearchInUserNames = false;
|
||||
// sp.m_bSearchInPasswords = false;
|
||||
Debug.Assert(!sp.m_bSearchInPasswords);
|
||||
sp.m_bSearchInUrls = false;
|
||||
sp.m_bSearchInNotes = false;
|
||||
sp.m_bSearchInOther = false;
|
||||
// sp.m_bSearchInUuids = false;
|
||||
// sp.SearchInGroupNames = false;
|
||||
Debug.Assert(!sp.m_bSearchInStringNames);
|
||||
sp.m_bSearchInTags = false;
|
||||
// sp.m_scType = StringComparison.InvariantCultureIgnoreCase;
|
||||
// sp.m_bExcludeExpired = false;
|
||||
// m_bRespectEntrySearchingDisabled = true;
|
||||
Debug.Assert(!sp.m_bSearchInUuids);
|
||||
Debug.Assert(!sp.m_bSearchInGroupPaths);
|
||||
Debug.Assert(!sp.m_bSearchInGroupNames);
|
||||
Debug.Assert(!sp.m_bSearchInHistory);
|
||||
// Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase);
|
||||
Debug.Assert(!sp.m_bExcludeExpired);
|
||||
Debug.Assert(sp.m_bRespectEntrySearchingDisabled);
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -42,14 +42,15 @@ namespace KeePassLib
|
||||
private PwUuid m_uuid = PwUuid.Zero;
|
||||
private PwGroup m_pParentGroup = null;
|
||||
private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
|
||||
private PwUuid m_puPrevParentGroup = PwUuid.Zero;
|
||||
|
||||
private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary();
|
||||
private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary();
|
||||
private AutoTypeConfig m_listAutoType = new AutoTypeConfig();
|
||||
private PwObjectList<PwEntry> m_listHistory = new PwObjectList<PwEntry>();
|
||||
private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary();
|
||||
private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary();
|
||||
private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig();
|
||||
private PwObjectList<PwEntry> m_lHistory = new PwObjectList<PwEntry>();
|
||||
|
||||
private PwIcon m_pwIcon = PwIcon.Key;
|
||||
private PwUuid m_pwCustomIconID = PwUuid.Zero;
|
||||
private PwUuid m_puCustomIcon = PwUuid.Zero;
|
||||
|
||||
private Color m_clrForeground = Color.Empty;
|
||||
private Color m_clrBackground = Color.Empty;
|
||||
@@ -62,20 +63,21 @@ namespace KeePassLib
|
||||
private ulong m_uUsageCount = 0;
|
||||
|
||||
private string m_strOverrideUrl = string.Empty;
|
||||
private bool m_bQualityCheck = true;
|
||||
|
||||
private List<string> m_vTags = new List<string>();
|
||||
private List<string> m_lTags = new List<string>();
|
||||
|
||||
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
|
||||
|
||||
/// <summary>
|
||||
/// UUID of this entry.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public PwUuid Uuid
|
||||
{
|
||||
get { return m_uuid; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_uuid = value;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +89,7 @@ namespace KeePassLib
|
||||
{
|
||||
get { return m_pParentGroup; }
|
||||
|
||||
/// Plugins: use <c>PwGroup.AddEntry</c> instead.
|
||||
// Plugins: use <c>PwGroup.AddEntry</c> instead.
|
||||
internal set { m_pParentGroup = value; }
|
||||
}
|
||||
|
||||
@@ -100,17 +102,26 @@ namespace KeePassLib
|
||||
set { m_tParentGroupLastMod = value; }
|
||||
}
|
||||
|
||||
public PwUuid PreviousParentGroup
|
||||
{
|
||||
get { return m_puPrevParentGroup; }
|
||||
set
|
||||
{
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_puPrevParentGroup = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set all entry strings.
|
||||
/// </summary>
|
||||
public ProtectedStringDictionary Strings
|
||||
{
|
||||
get { return m_listStrings; }
|
||||
get { return m_dStrings; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
m_listStrings = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_dStrings = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +130,11 @@ namespace KeePassLib
|
||||
/// </summary>
|
||||
public ProtectedBinaryDictionary Binaries
|
||||
{
|
||||
get { return m_listBinaries; }
|
||||
get { return m_dBinaries; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
m_listBinaries = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_dBinaries = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +143,11 @@ namespace KeePassLib
|
||||
/// </summary>
|
||||
public AutoTypeConfig AutoType
|
||||
{
|
||||
get { return m_listAutoType; }
|
||||
get { return m_cfgAutoType; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
m_listAutoType = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_cfgAutoType = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +156,11 @@ namespace KeePassLib
|
||||
/// </summary>
|
||||
public PwObjectList<PwEntry> History
|
||||
{
|
||||
get { return m_listHistory; }
|
||||
get { return m_lHistory; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
m_listHistory = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_lHistory = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,11 +180,11 @@ namespace KeePassLib
|
||||
/// </summary>
|
||||
public PwUuid CustomIconUuid
|
||||
{
|
||||
get { return m_pwCustomIconID; }
|
||||
get { return m_puCustomIcon; }
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
m_pwCustomIconID = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_puCustomIcon = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,28 +263,34 @@ namespace KeePassLib
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry-specific override URL. If this string is non-empty,
|
||||
/// Entry-specific override URL.
|
||||
/// </summary>
|
||||
public string OverrideUrl
|
||||
{
|
||||
get { return m_strOverrideUrl; }
|
||||
set
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_strOverrideUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool QualityCheck
|
||||
{
|
||||
get { return m_bQualityCheck; }
|
||||
set { m_bQualityCheck = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of tags associated with this entry.
|
||||
/// </summary>
|
||||
public List<string> Tags
|
||||
{
|
||||
get { return m_vTags; }
|
||||
get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
|
||||
set
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException("value");
|
||||
m_vTags = value;
|
||||
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
|
||||
m_lTags = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,19 +366,19 @@ namespace KeePassLib
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
// For display in debugger
|
||||
public override string ToString()
|
||||
{
|
||||
return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'");
|
||||
return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Clone the current entry. The returned entry is an exact value copy
|
||||
/// of the current entry (including UUID and parent group reference).
|
||||
/// All mutable members are cloned.
|
||||
/// </summary>
|
||||
/// <returns>Exact value clone. All references to mutable values changed.</returns>
|
||||
/// </summary>
|
||||
/// <returns>Exact value clone. All references to mutable values changed.</returns>
|
||||
public PwEntry CloneDeep()
|
||||
{
|
||||
PwEntry peNew = new PwEntry(false, false);
|
||||
@@ -369,14 +386,15 @@ namespace KeePassLib
|
||||
peNew.m_uuid = m_uuid; // PwUuid is immutable
|
||||
peNew.m_pParentGroup = m_pParentGroup;
|
||||
peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
|
||||
peNew.m_puPrevParentGroup = m_puPrevParentGroup;
|
||||
|
||||
peNew.m_listStrings = m_listStrings.CloneDeep();
|
||||
peNew.m_listBinaries = m_listBinaries.CloneDeep();
|
||||
peNew.m_listAutoType = m_listAutoType.CloneDeep();
|
||||
peNew.m_listHistory = m_listHistory.CloneDeep();
|
||||
peNew.m_dStrings = m_dStrings.CloneDeep();
|
||||
peNew.m_dBinaries = m_dBinaries.CloneDeep();
|
||||
peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep();
|
||||
peNew.m_lHistory = m_lHistory.CloneDeep();
|
||||
|
||||
peNew.m_pwIcon = m_pwIcon;
|
||||
peNew.m_pwCustomIconID = m_pwCustomIconID;
|
||||
peNew.m_puCustomIcon = m_puCustomIcon;
|
||||
|
||||
peNew.m_clrForeground = m_clrForeground;
|
||||
peNew.m_clrBackground = m_clrBackground;
|
||||
@@ -389,8 +407,9 @@ namespace KeePassLib
|
||||
peNew.m_uUsageCount = m_uUsageCount;
|
||||
|
||||
peNew.m_strOverrideUrl = m_strOverrideUrl;
|
||||
peNew.m_bQualityCheck = m_bQualityCheck;
|
||||
|
||||
peNew.m_vTags = new List<string>(m_vTags);
|
||||
peNew.m_lTags.AddRange(m_lTags);
|
||||
|
||||
peNew.m_dCustomData = m_dCustomData.CloneDeep();
|
||||
|
||||
@@ -457,27 +476,29 @@ namespace KeePassLib
|
||||
if (m_pParentGroup != pe.m_pParentGroup) return false;
|
||||
if (!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod))
|
||||
return false;
|
||||
if (!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr))
|
||||
if (!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr))
|
||||
return false;
|
||||
if (!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false;
|
||||
if (!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false;
|
||||
|
||||
if (!m_listAutoType.Equals(pe.m_listAutoType)) return false;
|
||||
if (!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false;
|
||||
|
||||
if ((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None)
|
||||
{
|
||||
bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) !=
|
||||
PwCompareOptions.None);
|
||||
|
||||
if (!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount))
|
||||
if (!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount))
|
||||
return false;
|
||||
if (bIgnoreLastBackup && (m_listHistory.UCount == 0))
|
||||
if (bIgnoreLastBackup && (m_lHistory.UCount == 0))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return false;
|
||||
}
|
||||
if (bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount))
|
||||
if (bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount))
|
||||
return false;
|
||||
|
||||
PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup;
|
||||
@@ -485,16 +506,16 @@ namespace KeePassLib
|
||||
if (bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod;
|
||||
if (bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess;
|
||||
|
||||
for (uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist)
|
||||
for (uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist)
|
||||
{
|
||||
if (!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt(
|
||||
if (!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt(
|
||||
uHist), cmpSub, MemProtCmpMode.None))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pwIcon != pe.m_pwIcon) return false;
|
||||
if (!m_pwCustomIconID.Equals(pe.m_pwCustomIconID)) return false;
|
||||
if (!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false;
|
||||
|
||||
if (m_clrForeground != pe.m_clrForeground) return false;
|
||||
if (m_clrBackground != pe.m_clrBackground) return false;
|
||||
@@ -507,12 +528,10 @@ namespace KeePassLib
|
||||
if (!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false;
|
||||
|
||||
if (m_strOverrideUrl != pe.m_strOverrideUrl) return false;
|
||||
if (m_bQualityCheck != pe.m_bQualityCheck) return false;
|
||||
|
||||
if (m_vTags.Count != pe.m_vTags.Count) return false;
|
||||
for (int iTag = 0; iTag < m_vTags.Count; ++iTag)
|
||||
{
|
||||
if (m_vTags[iTag] != pe.m_vTags[iTag]) return false;
|
||||
}
|
||||
// The Tags property normalizes
|
||||
if (!MemUtil.ListsEqual<string>(this.Tags, pe.Tags)) return false;
|
||||
|
||||
if (!m_dCustomData.Equals(pe.m_dCustomData)) return false;
|
||||
|
||||
@@ -543,16 +562,19 @@ namespace KeePassLib
|
||||
m_uuid = peTemplate.m_uuid;
|
||||
|
||||
if (bAssignLocationChanged)
|
||||
{
|
||||
m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
|
||||
m_puPrevParentGroup = peTemplate.m_puPrevParentGroup;
|
||||
}
|
||||
|
||||
m_listStrings = peTemplate.m_listStrings.CloneDeep();
|
||||
m_listBinaries = peTemplate.m_listBinaries.CloneDeep();
|
||||
m_listAutoType = peTemplate.m_listAutoType.CloneDeep();
|
||||
m_dStrings = peTemplate.m_dStrings.CloneDeep();
|
||||
m_dBinaries = peTemplate.m_dBinaries.CloneDeep();
|
||||
m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep();
|
||||
if (bIncludeHistory)
|
||||
m_listHistory = peTemplate.m_listHistory.CloneDeep();
|
||||
m_lHistory = peTemplate.m_lHistory.CloneDeep();
|
||||
|
||||
m_pwIcon = peTemplate.m_pwIcon;
|
||||
m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable
|
||||
m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable
|
||||
|
||||
m_clrForeground = peTemplate.m_clrForeground;
|
||||
m_clrBackground = peTemplate.m_clrBackground;
|
||||
@@ -565,8 +587,9 @@ namespace KeePassLib
|
||||
m_uUsageCount = peTemplate.m_uUsageCount;
|
||||
|
||||
m_strOverrideUrl = peTemplate.m_strOverrideUrl;
|
||||
m_bQualityCheck = peTemplate.m_bQualityCheck;
|
||||
|
||||
m_vTags = new List<string>(peTemplate.m_vTags);
|
||||
m_lTags = new List<string>(peTemplate.m_lTags);
|
||||
|
||||
m_dCustomData = peTemplate.m_dCustomData.CloneDeep();
|
||||
}
|
||||
@@ -629,9 +652,9 @@ namespace KeePassLib
|
||||
public void CreateBackup(PwDatabase pwHistMntcSettings)
|
||||
{
|
||||
PwEntry peCopy = CloneDeep();
|
||||
peCopy.History = new PwObjectList<PwEntry>(); // Remove history
|
||||
peCopy.m_lHistory.Clear();
|
||||
|
||||
m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry
|
||||
m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry
|
||||
|
||||
if (pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings);
|
||||
}
|
||||
@@ -658,12 +681,14 @@ namespace KeePassLib
|
||||
/// This parameter may be <c>null</c> (no maintenance then).</param>
|
||||
public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)
|
||||
{
|
||||
Debug.Assert(uBackupIndex < m_listHistory.UCount);
|
||||
if (uBackupIndex >= m_listHistory.UCount)
|
||||
if (uBackupIndex >= m_lHistory.UCount)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("uBackupIndex");
|
||||
}
|
||||
|
||||
PwEntry pe = m_listHistory.GetAt(uBackupIndex);
|
||||
Debug.Assert(pe != null); if (pe == null) throw new InvalidOperationException();
|
||||
PwEntry pe = m_lHistory.GetAt(uBackupIndex);
|
||||
if (pe == null) { Debug.Assert(false); throw new InvalidOperationException(); }
|
||||
|
||||
CreateBackup(pwHistMntcSettings); // Backup current data before restoring
|
||||
AssignProperties(pe, false, false, false);
|
||||
@@ -679,7 +704,7 @@ namespace KeePassLib
|
||||
if (bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod;
|
||||
if (bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess;
|
||||
|
||||
foreach (PwEntry pe in m_listHistory)
|
||||
foreach (PwEntry pe in m_lHistory)
|
||||
{
|
||||
if (pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true;
|
||||
}
|
||||
@@ -688,21 +713,28 @@ namespace KeePassLib
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete old history items if there are too many or the history
|
||||
/// size is too large.
|
||||
/// <returns>If one or more history items have been deleted, <c>true</c>
|
||||
/// is returned. Otherwise <c>false</c>.</returns>
|
||||
/// Delete old history entries if there are too many or the
|
||||
/// history size is too large.
|
||||
/// <returns>If one or more history entries have been deleted,
|
||||
/// <c>true</c> is returned. Otherwise <c>false</c>.</returns>
|
||||
/// </summary>
|
||||
public bool MaintainBackups(PwDatabase pwSettings)
|
||||
{
|
||||
if (pwSettings == null) { Debug.Assert(false); return false; }
|
||||
|
||||
// Fix UUIDs of history entries; should not be necessary
|
||||
PwUuid pu = m_uuid;
|
||||
foreach (PwEntry pe in m_lHistory)
|
||||
{
|
||||
if (!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; }
|
||||
}
|
||||
|
||||
bool bDeleted = false;
|
||||
|
||||
int nMaxItems = pwSettings.HistoryMaxItems;
|
||||
if (nMaxItems >= 0)
|
||||
{
|
||||
while (m_listHistory.UCount > (uint)nMaxItems)
|
||||
while (m_lHistory.UCount > (uint)nMaxItems)
|
||||
{
|
||||
RemoveOldestBackup();
|
||||
bDeleted = true;
|
||||
@@ -715,7 +747,7 @@ namespace KeePassLib
|
||||
while (true)
|
||||
{
|
||||
ulong uHistSize = 0;
|
||||
foreach (PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); }
|
||||
foreach (PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); }
|
||||
|
||||
if (uHistSize > (ulong)lMaxSize)
|
||||
{
|
||||
@@ -734,9 +766,9 @@ namespace KeePassLib
|
||||
DateTime dtMin = TimeUtil.SafeMaxValueUtc;
|
||||
uint idxRemove = uint.MaxValue;
|
||||
|
||||
for (uint u = 0; u < m_listHistory.UCount; ++u)
|
||||
for (uint u = 0; u < m_lHistory.UCount; ++u)
|
||||
{
|
||||
PwEntry pe = m_listHistory.GetAt(u);
|
||||
PwEntry pe = m_lHistory.GetAt(u);
|
||||
if (TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0)
|
||||
{
|
||||
idxRemove = u;
|
||||
@@ -744,12 +776,12 @@ namespace KeePassLib
|
||||
}
|
||||
}
|
||||
|
||||
if (idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove);
|
||||
if (idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove);
|
||||
}
|
||||
|
||||
public bool GetAutoTypeEnabled()
|
||||
{
|
||||
if (!m_listAutoType.Enabled) return false;
|
||||
if (!m_cfgAutoType.Enabled) return false;
|
||||
|
||||
if (m_pParentGroup != null)
|
||||
return m_pParentGroup.GetAutoTypeEnabledInherited();
|
||||
@@ -759,7 +791,7 @@ namespace KeePassLib
|
||||
|
||||
public string GetAutoTypeSequence()
|
||||
{
|
||||
string strSeq = m_listAutoType.DefaultSequence;
|
||||
string strSeq = m_cfgAutoType.DefaultSequence;
|
||||
|
||||
PwGroup pg = m_pParentGroup;
|
||||
while (pg != null)
|
||||
@@ -785,69 +817,67 @@ namespace KeePassLib
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Approximate the total size of this entry in bytes (including
|
||||
/// strings, binaries and history entries).
|
||||
/// Approximate the total size (in process memory) of this entry
|
||||
/// in bytes (including strings, binaries and history entries).
|
||||
/// </summary>
|
||||
/// <returns>Size in bytes.</returns>
|
||||
public ulong GetSize()
|
||||
{
|
||||
ulong uSize = 128; // Approx fixed length data
|
||||
// This method assumes 64-bit pointers/references and Unicode
|
||||
// strings (i.e. 2 bytes per character)
|
||||
|
||||
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_listStrings)
|
||||
ulong cb = 276; // Number of bytes; approx. fixed length data
|
||||
ulong cc = 0; // Number of characters
|
||||
|
||||
cb += (ulong)m_dStrings.UCount * 40;
|
||||
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_dStrings)
|
||||
cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length;
|
||||
|
||||
cb += (ulong)m_dBinaries.UCount * 65;
|
||||
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_dBinaries)
|
||||
{
|
||||
uSize += (ulong)kvpStr.Key.Length;
|
||||
uSize += (ulong)kvpStr.Value.Length;
|
||||
cc += (ulong)kvpBin.Key.Length;
|
||||
cb += (ulong)kvpBin.Value.Length;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_listBinaries)
|
||||
{
|
||||
uSize += (ulong)kvpBin.Key.Length;
|
||||
uSize += kvpBin.Value.Length;
|
||||
}
|
||||
cc += (ulong)m_cfgAutoType.DefaultSequence.Length;
|
||||
cb += (ulong)m_cfgAutoType.AssociationsCount * 24;
|
||||
foreach (AutoTypeAssociation a in m_cfgAutoType.Associations)
|
||||
cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length;
|
||||
|
||||
uSize += (ulong)m_listAutoType.DefaultSequence.Length;
|
||||
foreach (AutoTypeAssociation a in m_listAutoType.Associations)
|
||||
{
|
||||
uSize += (ulong)a.WindowName.Length;
|
||||
uSize += (ulong)a.Sequence.Length;
|
||||
}
|
||||
cb += (ulong)m_lHistory.UCount * 8;
|
||||
foreach (PwEntry peHistory in m_lHistory)
|
||||
cb += peHistory.GetSize();
|
||||
|
||||
foreach (PwEntry peHistory in m_listHistory)
|
||||
uSize += peHistory.GetSize();
|
||||
cc += (ulong)m_strOverrideUrl.Length;
|
||||
|
||||
uSize += (ulong)m_strOverrideUrl.Length;
|
||||
|
||||
foreach (string strTag in m_vTags)
|
||||
uSize += (ulong)strTag.Length;
|
||||
cb += (ulong)m_lTags.Count * 8;
|
||||
foreach (string strTag in m_lTags)
|
||||
cc += (ulong)strTag.Length;
|
||||
|
||||
cb += (ulong)m_dCustomData.Count * 16;
|
||||
foreach (KeyValuePair<string, string> kvp in m_dCustomData)
|
||||
uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
|
||||
cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
|
||||
|
||||
return uSize;
|
||||
return (cb + (cc << 1));
|
||||
}
|
||||
|
||||
public bool HasTag(string strTag)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
|
||||
|
||||
for (int i = 0; i < m_vTags.Count; ++i)
|
||||
{
|
||||
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// this.Tags normalizes
|
||||
return this.Tags.Contains(StrUtil.NormalizeTag(strTag));
|
||||
}
|
||||
|
||||
public bool AddTag(string strTag)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
|
||||
|
||||
for (int i = 0; i < m_vTags.Count; ++i)
|
||||
{
|
||||
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false;
|
||||
}
|
||||
strTag = StrUtil.NormalizeTag(strTag);
|
||||
if (this.Tags.Contains(strTag)) return false; // this.Tags normalizes
|
||||
|
||||
m_vTags.Add(strTag);
|
||||
m_lTags.Add(strTag);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -855,16 +885,17 @@ namespace KeePassLib
|
||||
{
|
||||
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
|
||||
|
||||
for (int i = 0; i < m_vTags.Count; ++i)
|
||||
{
|
||||
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp))
|
||||
{
|
||||
m_vTags.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// this.Tags normalizes
|
||||
return this.Tags.Remove(StrUtil.NormalizeTag(strTag));
|
||||
}
|
||||
|
||||
return false;
|
||||
internal List<string> GetTagsInherited()
|
||||
{
|
||||
List<string> l = ((m_pParentGroup != null) ?
|
||||
m_pParentGroup.GetTagsInherited(false) : new List<string>());
|
||||
l.AddRange(this.Tags);
|
||||
StrUtil.NormalizeTags(l);
|
||||
return l;
|
||||
}
|
||||
|
||||
public bool IsContainedIn(PwGroup pgContainer)
|
||||
@@ -886,10 +917,8 @@ namespace KeePassLib
|
||||
|
||||
if (bAlsoChangeHistoryUuids)
|
||||
{
|
||||
foreach (PwEntry peHist in m_listHistory)
|
||||
{
|
||||
foreach (PwEntry peHist in m_lHistory)
|
||||
peHist.Uuid = pwNewUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,4 +316,13 @@ namespace KeePassLib
|
||||
Cinnamon,
|
||||
Pantheon
|
||||
}
|
||||
|
||||
|
||||
public enum PwSearchMode
|
||||
{
|
||||
None = 0,
|
||||
Simple,
|
||||
Regular,
|
||||
XPath
|
||||
}
|
||||
}
|
||||
|
||||
372
src/KeePassLib2Android/PwGroup.Search.cs
Normal file
372
src/KeePassLib2Android/PwGroup.Search.cs
Normal file
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using System.Xml.XPath;
|
||||
|
||||
using KeePassLib.Collections;
|
||||
using KeePassLib.Delegates;
|
||||
using KeePassLib.Interfaces;
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace KeePassLib
|
||||
{
|
||||
public sealed partial class PwGroup
|
||||
{
|
||||
private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added
|
||||
public const string SearchContextUuid = "Uuid";
|
||||
public const string SearchContextParentGroup = "Parent Group";
|
||||
public const string SearchContextTags = "Tags";
|
||||
|
||||
/// <summary>
|
||||
/// Search this group and all subgroups for entries.
|
||||
/// </summary>
|
||||
/// <param name="sp">Specifies the search method.</param>
|
||||
/// <param name="listStorage">Entry list in which the search results will
|
||||
/// be stored.</param>
|
||||
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage)
|
||||
{
|
||||
SearchEntries(sp, listStorage, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search this group and all subgroups for entries.
|
||||
/// </summary>
|
||||
/// <param name="sp">Specifies the search method.</param>
|
||||
/// <param name="listStorage">Entry list in which the search results will
|
||||
/// be stored.</param>
|
||||
/// <param name="slStatus">Optional status reporting object.</param>
|
||||
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
|
||||
IStatusLogger slStatus)
|
||||
{
|
||||
SearchEntries(sp, listStorage, null, slStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search this group and all subgroups for entries.
|
||||
/// </summary>
|
||||
/// <param name="sp">Specifies the search method.</param>
|
||||
/// <param name="listStorage">Entry list in which the search results will
|
||||
/// be stored.</param>
|
||||
/// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param>
|
||||
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
|
||||
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
|
||||
IStatusLogger slStatus)
|
||||
{
|
||||
|
||||
if (sp == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (listStorage == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ulong uCurEntries = 0, uTotalEntries = 0;
|
||||
|
||||
List<string> lTerms = StrUtil.SplitSearchTerms(sp.SearchString);
|
||||
if ((lTerms.Count <= 1) || sp.RegularExpression)
|
||||
{
|
||||
if (slStatus != null) uTotalEntries = GetEntriesCount(true);
|
||||
SearchEntriesSingle(sp, listStorage, resultContexts, slStatus, ref uCurEntries,
|
||||
uTotalEntries);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search longer strings first (for improved performance)
|
||||
lTerms.Sort(StrUtil.CompareLengthGt);
|
||||
|
||||
string strFullSearch = sp.SearchString; // Backup
|
||||
|
||||
PwGroup pg = this;
|
||||
for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm)
|
||||
{
|
||||
// Update counters for a better state guess
|
||||
if (slStatus != null)
|
||||
{
|
||||
ulong uRemRounds = (ulong) (lTerms.Count - iTerm);
|
||||
uTotalEntries = uCurEntries + (uRemRounds *
|
||||
pg.GetEntriesCount(true));
|
||||
}
|
||||
|
||||
PwGroup pgNew = new PwGroup();
|
||||
|
||||
sp.SearchString = lTerms[iTerm];
|
||||
|
||||
bool bNegate = false;
|
||||
if (sp.SearchString.StartsWith("-"))
|
||||
{
|
||||
sp.SearchString = sp.SearchString.Substring(1);
|
||||
bNegate = (sp.SearchString.Length > 0);
|
||||
}
|
||||
|
||||
if (!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
|
||||
ref uCurEntries, uTotalEntries))
|
||||
{
|
||||
pg = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bNegate)
|
||||
{
|
||||
PwObjectList<PwEntry> lCand = pg.GetEntries(true);
|
||||
|
||||
pg = new PwGroup();
|
||||
foreach (PwEntry peCand in lCand)
|
||||
{
|
||||
if (pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand);
|
||||
}
|
||||
}
|
||||
else pg = pgNew;
|
||||
}
|
||||
|
||||
if (pg != null) listStorage.Add(pg.Entries);
|
||||
sp.SearchString = strFullSearch; // Restore
|
||||
}
|
||||
|
||||
private bool SearchEntriesSingle(SearchParameters spIn,
|
||||
PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
|
||||
IStatusLogger slStatus,
|
||||
ref ulong uCurEntries, ulong uTotalEntries)
|
||||
{
|
||||
SearchParameters sp = spIn.Clone();
|
||||
if (sp.SearchString == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
sp.SearchString = sp.SearchString.Trim();
|
||||
|
||||
bool bTitle = sp.SearchInTitles;
|
||||
bool bUserName = sp.SearchInUserNames;
|
||||
bool bPassword = sp.SearchInPasswords;
|
||||
bool bUrl = sp.SearchInUrls;
|
||||
bool bNotes = sp.SearchInNotes;
|
||||
bool bOther = sp.SearchInOther;
|
||||
bool bUuids = sp.SearchInUuids;
|
||||
bool bGroupName = sp.SearchInGroupNames;
|
||||
bool bTags = sp.SearchInTags;
|
||||
bool bExcludeExpired = sp.ExcludeExpired;
|
||||
bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled;
|
||||
|
||||
DateTime dtNow = DateTime.Now;
|
||||
|
||||
Regex rx = null;
|
||||
if (sp.RegularExpression)
|
||||
{
|
||||
RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled
|
||||
if ((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) ||
|
||||
#if !KeePassUAP
|
||||
(sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) ||
|
||||
#endif
|
||||
(sp.ComparisonMode == StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ro |= RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
rx = new Regex(sp.SearchString, ro);
|
||||
}
|
||||
|
||||
ulong uLocalCurEntries = uCurEntries;
|
||||
|
||||
EntryHandler eh = null;
|
||||
if (sp.SearchString.Length <= 0) // Report all
|
||||
{
|
||||
eh = delegate(PwEntry pe)
|
||||
{
|
||||
if (slStatus != null)
|
||||
{
|
||||
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
|
||||
100UL) / uTotalEntries))) return false;
|
||||
++uLocalCurEntries;
|
||||
}
|
||||
|
||||
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
|
||||
return true; // Skip
|
||||
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
|
||||
return true; // Skip
|
||||
|
||||
listStorage.Add(pe);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
eh = delegate(PwEntry pe)
|
||||
{
|
||||
if (slStatus != null)
|
||||
{
|
||||
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
|
||||
100UL) / uTotalEntries))) return false;
|
||||
++uLocalCurEntries;
|
||||
}
|
||||
|
||||
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
|
||||
return true; // Skip
|
||||
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
|
||||
return true; // Skip
|
||||
|
||||
uint uInitialResults = listStorage.UCount;
|
||||
|
||||
foreach (KeyValuePair<string, ProtectedString> kvp in pe.Strings)
|
||||
{
|
||||
string strKey = kvp.Key;
|
||||
|
||||
if (strKey == PwDefs.TitleField)
|
||||
{
|
||||
if (bTitle)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
}
|
||||
else if (strKey == PwDefs.UserNameField)
|
||||
{
|
||||
if (bUserName)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
}
|
||||
else if (strKey == PwDefs.PasswordField)
|
||||
{
|
||||
if (bPassword)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
}
|
||||
else if (strKey == PwDefs.UrlField)
|
||||
{
|
||||
if (bUrl)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
}
|
||||
else if (strKey == PwDefs.NotesField)
|
||||
{
|
||||
if (bNotes)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
}
|
||||
else if (bOther)
|
||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||
rx, pe, listStorage, resultContexts, strKey);
|
||||
|
||||
// An entry can match only once => break if we have added it
|
||||
if (listStorage.UCount > uInitialResults) break;
|
||||
}
|
||||
|
||||
if (bUuids && (listStorage.UCount == uInitialResults))
|
||||
SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts,
|
||||
SearchContextTags);
|
||||
|
||||
if (bGroupName && (listStorage.UCount == uInitialResults) &&
|
||||
(pe.ParentGroup != null))
|
||||
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts,
|
||||
SearchContextParentGroup);
|
||||
|
||||
if (bTags)
|
||||
{
|
||||
foreach (string strTag in pe.Tags)
|
||||
{
|
||||
if (listStorage.UCount != uInitialResults) break; // Match
|
||||
|
||||
SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
if (!PreOrderTraverseTree(null, eh)) return false;
|
||||
uCurEntries = uLocalCurEntries;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void SearchEvalAdd(SearchParameters sp, string strDataField,
|
||||
Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults,
|
||||
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts, string contextFieldName)
|
||||
{
|
||||
bool bMatch = false;
|
||||
int matchPos;
|
||||
if (rx == null)
|
||||
{
|
||||
matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
|
||||
bMatch = matchPos >= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = rx.Match(strDataField);
|
||||
bMatch = match.Success;
|
||||
matchPos = match.Index;
|
||||
}
|
||||
|
||||
if (!bMatch && (sp.DataTransformationFn != null))
|
||||
{
|
||||
string strCmp = sp.DataTransformationFn(strDataField, pe);
|
||||
if (!object.ReferenceEquals(strCmp, strDataField))
|
||||
{
|
||||
if (rx == null)
|
||||
{
|
||||
matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
|
||||
bMatch = matchPos >= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = rx.Match(strCmp);
|
||||
bMatch = match.Success;
|
||||
matchPos = match.Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
lResults.Add(pe);
|
||||
|
||||
if (resultContexts != null)
|
||||
{
|
||||
// Trim the value if necessary
|
||||
var contextString = strDataField;
|
||||
if (contextString.Length > SearchContextStringMaxLength)
|
||||
{
|
||||
// Start 10% before actual data, and don't run over
|
||||
var startPos = Math.Max(0,
|
||||
Math.Min(matchPos - (SearchContextStringMaxLength / 10),
|
||||
contextString.Length - SearchContextStringMaxLength));
|
||||
contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) +
|
||||
((startPos + SearchContextStringMaxLength < contextString.Length)
|
||||
? " …"
|
||||
: null);
|
||||
}
|
||||
|
||||
resultContexts[pe.Uuid] = new KeyValuePair<string, string>(contextFieldName, contextString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ namespace KeePassLib.Resources
|
||||
{
|
||||
string strTemp;
|
||||
|
||||
if(dictNew.TryGetValue(strName, out strTemp))
|
||||
if (dictNew.TryGetValue(strName, out strTemp))
|
||||
return strTemp;
|
||||
|
||||
return strDefault;
|
||||
@@ -24,8 +24,11 @@ namespace KeePassLib.Resources
|
||||
|
||||
public static void SetTranslatedStrings(Dictionary<string, string> dictNew)
|
||||
{
|
||||
if(dictNew == null) throw new ArgumentNullException("dictNew");
|
||||
if (dictNew == null) throw new ArgumentNullException("dictNew");
|
||||
|
||||
m_strAlgorithmUnknown = TryGetEx(dictNew, "AlgorithmUnknown", m_strAlgorithmUnknown);
|
||||
m_strCharSetInvalid = TryGetEx(dictNew, "CharSetInvalid", m_strCharSetInvalid);
|
||||
m_strCharSetTooFewChars = TryGetEx(dictNew, "CharSetTooFewChars", m_strCharSetTooFewChars);
|
||||
m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed);
|
||||
m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge);
|
||||
m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard);
|
||||
@@ -41,7 +44,7 @@ namespace KeePassLib.Resources
|
||||
m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq);
|
||||
m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq);
|
||||
m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning);
|
||||
m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed);
|
||||
m_strFileSaveFailed2 = TryGetEx(dictNew, "FileSaveFailed2", m_strFileSaveFailed2);
|
||||
m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid);
|
||||
m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher);
|
||||
m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression);
|
||||
@@ -55,12 +58,18 @@ namespace KeePassLib.Resources
|
||||
m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint);
|
||||
m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits);
|
||||
m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel);
|
||||
m_strKeyHashMismatch = TryGetEx(dictNew, "KeyHashMismatch", m_strKeyHashMismatch);
|
||||
m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid);
|
||||
m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat);
|
||||
m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive);
|
||||
m_strPathBackslash = TryGetEx(dictNew, "PathBackslash", m_strPathBackslash);
|
||||
m_strPatternInvalid = TryGetEx(dictNew, "PatternInvalid", m_strPatternInvalid);
|
||||
m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth);
|
||||
m_strPwGenFailed = TryGetEx(dictNew, "PwGenFailed", m_strPwGenFailed);
|
||||
m_strStructsTooDeep = TryGetEx(dictNew, "StructsTooDeep", m_strStructsTooDeep);
|
||||
m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout);
|
||||
m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs);
|
||||
m_strUnknownError = TryGetEx(dictNew, "UnknownError", m_strUnknownError);
|
||||
m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId);
|
||||
m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf);
|
||||
m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError);
|
||||
@@ -68,6 +77,9 @@ namespace KeePassLib.Resources
|
||||
}
|
||||
|
||||
private static readonly string[] m_vKeyNames = {
|
||||
"AlgorithmUnknown",
|
||||
"CharSetInvalid",
|
||||
"CharSetTooFewChars",
|
||||
"CryptoStreamFailed",
|
||||
"EncDataTooLarge",
|
||||
"ErrorInClipboard",
|
||||
@@ -83,7 +95,7 @@ namespace KeePassLib.Resources
|
||||
"FileNewVerOrPlgReq",
|
||||
"FileNewVerReq",
|
||||
"FileSaveCorruptionWarning",
|
||||
"FileSaveFailed",
|
||||
"FileSaveFailed2",
|
||||
"FileSigInvalid",
|
||||
"FileUnknownCipher",
|
||||
"FileUnknownCompression",
|
||||
@@ -97,12 +109,18 @@ namespace KeePassLib.Resources
|
||||
"KeePass1xHint",
|
||||
"KeyBits",
|
||||
"KeyFileDbSel",
|
||||
"KeyHashMismatch",
|
||||
"MasterSeedLengthInvalid",
|
||||
"OldFormat",
|
||||
"Passive",
|
||||
"PathBackslash",
|
||||
"PatternInvalid",
|
||||
"PreAuth",
|
||||
"PwGenFailed",
|
||||
"StructsTooDeep",
|
||||
"Timeout",
|
||||
"TryAgainSecs",
|
||||
"UnknownError",
|
||||
"UnknownHeaderId",
|
||||
"UnknownKdf",
|
||||
"UserAccountKeyError",
|
||||
@@ -114,6 +132,39 @@ namespace KeePassLib.Resources
|
||||
return m_vKeyNames;
|
||||
}
|
||||
|
||||
private static string m_strAlgorithmUnknown =
|
||||
@"The algorithm is unknown.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The algorithm is unknown.'.
|
||||
/// </summary>
|
||||
public static string AlgorithmUnknown
|
||||
{
|
||||
get { return m_strAlgorithmUnknown; }
|
||||
}
|
||||
|
||||
private static string m_strCharSetInvalid =
|
||||
@"The character set is invalid.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The character set is invalid.'.
|
||||
/// </summary>
|
||||
public static string CharSetInvalid
|
||||
{
|
||||
get { return m_strCharSetInvalid; }
|
||||
}
|
||||
|
||||
private static string m_strCharSetTooFewChars =
|
||||
@"There are too few characters in the character set.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'There are too few characters in the character set.'.
|
||||
/// </summary>
|
||||
public static string CharSetTooFewChars
|
||||
{
|
||||
get { return m_strCharSetTooFewChars; }
|
||||
}
|
||||
|
||||
private static string m_strCryptoStreamFailed =
|
||||
@"Failed to initialize encryption/decryption stream!";
|
||||
/// <summary>
|
||||
@@ -279,15 +330,15 @@ namespace KeePassLib.Resources
|
||||
get { return m_strFileSaveCorruptionWarning; }
|
||||
}
|
||||
|
||||
private static string m_strFileSaveFailed =
|
||||
@"Failed to save the current database to the specified location!";
|
||||
private static string m_strFileSaveFailed2 =
|
||||
@"Failed to save to the specified file!";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'Failed to save the current database to the specified location!'.
|
||||
/// 'Failed to save to the specified file!'.
|
||||
/// </summary>
|
||||
public static string FileSaveFailed
|
||||
public static string FileSaveFailed2
|
||||
{
|
||||
get { return m_strFileSaveFailed; }
|
||||
get { return m_strFileSaveFailed2; }
|
||||
}
|
||||
|
||||
private static string m_strFileSigInvalid =
|
||||
@@ -346,10 +397,10 @@ namespace KeePassLib.Resources
|
||||
}
|
||||
|
||||
private static string m_strFrameworkNotImplExcp =
|
||||
@"The .NET framework/runtime under which KeePass is currently running does not support this operation.";
|
||||
@"The .NET Framework/runtime under which KeePass is currently running does not support this operation.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'.
|
||||
/// 'The .NET Framework/runtime under which KeePass is currently running does not support this operation.'.
|
||||
/// </summary>
|
||||
public static string FrameworkNotImplExcp
|
||||
{
|
||||
@@ -368,10 +419,10 @@ namespace KeePassLib.Resources
|
||||
}
|
||||
|
||||
private static string m_strInvalidCompositeKey =
|
||||
@"The composite key is invalid!";
|
||||
@"The master key is invalid!";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The composite key is invalid!'.
|
||||
/// 'The master key is invalid!'.
|
||||
/// </summary>
|
||||
public static string InvalidCompositeKey
|
||||
{
|
||||
@@ -379,10 +430,10 @@ namespace KeePassLib.Resources
|
||||
}
|
||||
|
||||
private static string m_strInvalidCompositeKeyHint =
|
||||
@"Make sure the composite key is correct and try again.";
|
||||
@"Make sure that the master key is correct and try it again.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'Make sure the composite key is correct and try again.'.
|
||||
/// 'Make sure that the master key is correct and try it again.'.
|
||||
/// </summary>
|
||||
public static string InvalidCompositeKeyHint
|
||||
{
|
||||
@@ -433,6 +484,17 @@ namespace KeePassLib.Resources
|
||||
get { return m_strKeyFileDbSel; }
|
||||
}
|
||||
|
||||
private static string m_strKeyHashMismatch =
|
||||
@"The key and the hash do not match, i.e. the key or the hash is invalid.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The key and the hash do not match, i.e. the key or the hash is invalid.'.
|
||||
/// </summary>
|
||||
public static string KeyHashMismatch
|
||||
{
|
||||
get { return m_strKeyHashMismatch; }
|
||||
}
|
||||
|
||||
private static string m_strMasterSeedLengthInvalid =
|
||||
@"The length of the master key seed is invalid!";
|
||||
/// <summary>
|
||||
@@ -466,6 +528,28 @@ namespace KeePassLib.Resources
|
||||
get { return m_strPassive; }
|
||||
}
|
||||
|
||||
private static string m_strPathBackslash =
|
||||
@"The path contains a backslash. Such paths are not supported (for security reasons).";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The path contains a backslash. Such paths are not supported (for security reasons).'.
|
||||
/// </summary>
|
||||
public static string PathBackslash
|
||||
{
|
||||
get { return m_strPathBackslash; }
|
||||
}
|
||||
|
||||
private static string m_strPatternInvalid =
|
||||
@"The pattern is invalid.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'The pattern is invalid.'.
|
||||
/// </summary>
|
||||
public static string PatternInvalid
|
||||
{
|
||||
get { return m_strPatternInvalid; }
|
||||
}
|
||||
|
||||
private static string m_strPreAuth =
|
||||
@"Pre-authenticate";
|
||||
/// <summary>
|
||||
@@ -477,6 +561,28 @@ namespace KeePassLib.Resources
|
||||
get { return m_strPreAuth; }
|
||||
}
|
||||
|
||||
private static string m_strPwGenFailed =
|
||||
@"Failed to generate a password.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'Failed to generate a password.'.
|
||||
/// </summary>
|
||||
public static string PwGenFailed
|
||||
{
|
||||
get { return m_strPwGenFailed; }
|
||||
}
|
||||
|
||||
private static string m_strStructsTooDeep =
|
||||
@"Structures are nested too deeply.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'Structures are nested too deeply.'.
|
||||
/// </summary>
|
||||
public static string StructsTooDeep
|
||||
{
|
||||
get { return m_strStructsTooDeep; }
|
||||
}
|
||||
|
||||
private static string m_strTimeout =
|
||||
@"Timeout";
|
||||
/// <summary>
|
||||
@@ -499,6 +605,17 @@ namespace KeePassLib.Resources
|
||||
get { return m_strTryAgainSecs; }
|
||||
}
|
||||
|
||||
private static string m_strUnknownError =
|
||||
@"An unknown error occurred.";
|
||||
/// <summary>
|
||||
/// Look up a localized string similar to
|
||||
/// 'An unknown error occurred.'.
|
||||
/// </summary>
|
||||
public static string UnknownError
|
||||
{
|
||||
get { return m_strUnknownError; }
|
||||
}
|
||||
|
||||
private static string m_strUnknownHeaderId =
|
||||
@"Unknown header ID!";
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -33,11 +33,11 @@ using KeePassLibSD;
|
||||
namespace KeePassLib.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an in-memory encrypted string.
|
||||
/// A string that is protected in process memory.
|
||||
/// <c>ProtectedString</c> objects are immutable and thread-safe.
|
||||
/// </summary>
|
||||
#if (DEBUG && !KeePassLibSD)
|
||||
[DebuggerDisplay(@"{ReadString()}")]
|
||||
[DebuggerDisplay("{ReadString()}")]
|
||||
#endif
|
||||
public sealed class ProtectedString
|
||||
{
|
||||
@@ -48,11 +48,24 @@ namespace KeePassLib.Security
|
||||
private bool m_bIsProtected;
|
||||
|
||||
private static readonly ProtectedString m_psEmpty = new ProtectedString();
|
||||
/// <summary>
|
||||
/// Get an empty <c>ProtectedString</c> object, without protection.
|
||||
/// </summary>
|
||||
public static ProtectedString Empty
|
||||
{
|
||||
get { return m_psEmpty; }
|
||||
}
|
||||
|
||||
private static readonly ProtectedString m_psEmptyEx = new ProtectedString(
|
||||
true, new byte[0]);
|
||||
/// <summary>
|
||||
/// Get an empty <c>ProtectedString</c> object, with protection turned on.
|
||||
/// </summary>
|
||||
public static ProtectedString EmptyEx
|
||||
{
|
||||
get { return m_psEmptyEx; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flag specifying whether the <c>ProtectedString</c> object
|
||||
/// has turned on memory protection or not.
|
||||
@@ -66,8 +79,8 @@ namespace KeePassLib.Security
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null) return (pBin.Length == 0);
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if (p != null) return (p.Length == 0);
|
||||
|
||||
Debug.Assert(m_strPlainText != null);
|
||||
return (m_strPlainText.Length == 0);
|
||||
@@ -75,18 +88,21 @@ namespace KeePassLib.Security
|
||||
}
|
||||
|
||||
private int m_nCachedLength = -1;
|
||||
/// <summary>
|
||||
/// Length of the protected string, in characters.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_nCachedLength >= 0) return m_nCachedLength;
|
||||
if (m_nCachedLength >= 0) return m_nCachedLength;
|
||||
|
||||
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null)
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if (p != null)
|
||||
{
|
||||
byte[] pbPlain = pBin.ReadData();
|
||||
m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain);
|
||||
MemUtil.ZeroByteArray(pbPlain);
|
||||
byte[] pbPlain = p.ReadData();
|
||||
try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); }
|
||||
finally { MemUtil.ZeroByteArray(pbPlain); }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -140,53 +156,51 @@ namespace KeePassLib.Security
|
||||
/// to the value passed in the <c>XorredBuffer</c> object.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">Enable protection or not.</param>
|
||||
/// <param name="xbProtected"><c>XorredBuffer</c> object containing the
|
||||
/// <param name="xb"><c>XorredBuffer</c> object containing the
|
||||
/// string in UTF-8 representation. The UTF-8 string must not
|
||||
/// be <c>null</c>-terminated.</param>
|
||||
public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected)
|
||||
public ProtectedString(bool bEnableProtection, XorredBuffer xb)
|
||||
{
|
||||
Debug.Assert(xbProtected != null);
|
||||
if(xbProtected == null) throw new ArgumentNullException("xbProtected");
|
||||
if (xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
|
||||
|
||||
byte[] pb = xbProtected.ReadPlainText();
|
||||
Init(bEnableProtection, pb);
|
||||
|
||||
if(bEnableProtection) MemUtil.ZeroByteArray(pb);
|
||||
byte[] pb = xb.ReadPlainText();
|
||||
try { Init(bEnableProtection, pb); }
|
||||
finally { if (bEnableProtection) MemUtil.ZeroByteArray(pb); }
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, string str)
|
||||
{
|
||||
if(str == null) throw new ArgumentNullException("str");
|
||||
if (str == null) throw new ArgumentNullException("str");
|
||||
|
||||
m_bIsProtected = bEnableProtection;
|
||||
|
||||
// The string already is in memory and immutable,
|
||||
// As the string already is in memory and immutable,
|
||||
// protection would be useless
|
||||
m_strPlainText = str;
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, byte[] pbUtf8)
|
||||
{
|
||||
if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8");
|
||||
if (pbUtf8 == null) throw new ArgumentNullException("pbUtf8");
|
||||
|
||||
m_bIsProtected = bEnableProtection;
|
||||
|
||||
if(bEnableProtection)
|
||||
if (bEnableProtection)
|
||||
m_pbUtf8 = new ProtectedBinary(true, pbUtf8);
|
||||
else
|
||||
m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the protected string to a normal string object.
|
||||
/// Be careful with this function, the returned string object
|
||||
/// Convert the protected string to a standard string object.
|
||||
/// Be careful with this function, as the returned string object
|
||||
/// isn't protected anymore and stored in plain-text in the
|
||||
/// process memory.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text string. Is never <c>null</c>.</returns>
|
||||
public string ReadString()
|
||||
{
|
||||
if(m_strPlainText != null) return m_strPlainText;
|
||||
if (m_strPlainText != null) return m_strPlainText;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
string str = ((pb.Length == 0) ? string.Empty :
|
||||
@@ -194,82 +208,120 @@ namespace KeePassLib.Security
|
||||
// No need to clear pb
|
||||
|
||||
// As the text is now visible in process memory anyway,
|
||||
// there's no need to protect it anymore
|
||||
// there's no need to protect it anymore (strings are
|
||||
// immutable and thus cannot be overwritten)
|
||||
m_strPlainText = str;
|
||||
m_pbUtf8 = null; // Thread-safe order
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the string and return it as a char array.
|
||||
/// The returned array is not protected and should be cleared by
|
||||
/// the caller.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text char array.</returns>
|
||||
public char[] ReadChars()
|
||||
{
|
||||
if (m_strPlainText != null) return m_strPlainText.ToCharArray();
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
char[] v;
|
||||
try { v = StrUtil.Utf8.GetChars(pb); }
|
||||
finally { MemUtil.ZeroByteArray(pb); }
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the string and return a byte array that contains the
|
||||
/// string encoded using UTF-8. The returned string is not protected
|
||||
/// anymore!
|
||||
/// string encoded using UTF-8.
|
||||
/// The returned array is not protected and should be cleared by
|
||||
/// the caller.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text UTF-8 byte array.</returns>
|
||||
public byte[] ReadUtf8()
|
||||
{
|
||||
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null) return pBin.ReadData();
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if (p != null) return p.ReadData();
|
||||
|
||||
return StrUtil.Utf8.GetBytes(m_strPlainText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the protected string and return it protected with a sequence
|
||||
/// of bytes generated by a random stream.
|
||||
/// Get the string as an UTF-8 sequence xorred with bytes
|
||||
/// from a <c>CryptoRandomStream</c>.
|
||||
/// </summary>
|
||||
/// <param name="crsRandomSource">Random number source.</param>
|
||||
/// <returns>Protected string.</returns>
|
||||
public byte[] ReadXorredString(CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource");
|
||||
if (crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
|
||||
|
||||
byte[] pbData = ReadUtf8();
|
||||
uint uLen = (uint)pbData.Length;
|
||||
int cb = pbData.Length;
|
||||
|
||||
byte[] randomPad = crsRandomSource.GetRandomBytes(uLen);
|
||||
Debug.Assert(randomPad.Length == pbData.Length);
|
||||
byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb);
|
||||
Debug.Assert(pbPad.Length == cb);
|
||||
|
||||
for(uint i = 0; i < uLen; ++i)
|
||||
pbData[i] ^= randomPad[i];
|
||||
for (int i = 0; i < cb; ++i)
|
||||
pbData[i] ^= pbPad[i];
|
||||
|
||||
MemUtil.ZeroByteArray(pbPad);
|
||||
return pbData;
|
||||
}
|
||||
|
||||
public ProtectedString WithProtection(bool bProtect)
|
||||
{
|
||||
if(bProtect == m_bIsProtected) return this;
|
||||
if (bProtect == m_bIsProtected) return this;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
ProtectedString ps = new ProtectedString(bProtect, pb);
|
||||
|
||||
if(bProtect) MemUtil.ZeroByteArray(pb);
|
||||
return ps;
|
||||
// No need to clear pb; either the current or the new object is unprotected
|
||||
return new ProtectedString(bProtect, pb);
|
||||
}
|
||||
|
||||
public bool Equals(ProtectedString ps, bool bCheckProtEqual)
|
||||
{
|
||||
if (ps == null) throw new ArgumentNullException("ps");
|
||||
if (object.ReferenceEquals(this, ps)) return true; // Perf. opt.
|
||||
|
||||
bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected;
|
||||
if (bCheckProtEqual && (bPA != bPB)) return false;
|
||||
if (!bPA && !bPB) return (ReadString() == ps.ReadString());
|
||||
|
||||
byte[] pbA = ReadUtf8(), pbB = null;
|
||||
bool bEq;
|
||||
try
|
||||
{
|
||||
pbB = ps.ReadUtf8();
|
||||
bEq = MemUtil.ArraysEqual(pbA, pbB);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bPA) MemUtil.ZeroByteArray(pbA);
|
||||
if (bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB);
|
||||
}
|
||||
|
||||
return bEq;
|
||||
}
|
||||
|
||||
public ProtectedString Insert(int iStart, string strInsert)
|
||||
{
|
||||
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart");
|
||||
if(strInsert == null) throw new ArgumentNullException("strInsert");
|
||||
if(strInsert.Length == 0) return this;
|
||||
if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
|
||||
if (strInsert == null) throw new ArgumentNullException("strInsert");
|
||||
if (strInsert.Length == 0) return this;
|
||||
|
||||
// Only operate directly with strings when m_bIsProtected is
|
||||
// false, not in the case of non-null m_strPlainText, because
|
||||
// the operation creates a new sequence in memory
|
||||
if(!m_bIsProtected)
|
||||
if (!m_bIsProtected)
|
||||
return new ProtectedString(false, ReadString().Insert(
|
||||
iStart, strInsert));
|
||||
|
||||
UTF8Encoding utf8 = StrUtil.Utf8;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
char[] v = utf8.GetChars(pb);
|
||||
char[] vNew;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
if(iStart > v.Length)
|
||||
if (iStart > v.Length)
|
||||
throw new ArgumentOutOfRangeException("iStart");
|
||||
|
||||
char[] vIns = strInsert.ToCharArray();
|
||||
@@ -279,68 +331,104 @@ namespace KeePassLib.Security
|
||||
Array.Copy(vIns, 0, vNew, iStart, vIns.Length);
|
||||
Array.Copy(v, iStart, vNew, iStart + vIns.Length,
|
||||
v.Length - iStart);
|
||||
|
||||
pbNew = utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Insert(iStart, strInsert));
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(v);
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
byte[] pbNew = utf8.GetBytes(vNew);
|
||||
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Insert(iStart, strInsert));
|
||||
|
||||
MemUtil.ZeroArray<char>(vNew);
|
||||
MemUtil.ZeroByteArray(pbNew);
|
||||
return ps;
|
||||
}
|
||||
|
||||
public ProtectedString Remove(int iStart, int nCount)
|
||||
{
|
||||
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart");
|
||||
if(nCount < 0) throw new ArgumentOutOfRangeException("nCount");
|
||||
if(nCount == 0) return this;
|
||||
if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
|
||||
if (nCount < 0) throw new ArgumentOutOfRangeException("nCount");
|
||||
if (nCount == 0) return this;
|
||||
|
||||
// Only operate directly with strings when m_bIsProtected is
|
||||
// false, not in the case of non-null m_strPlainText, because
|
||||
// the operation creates a new sequence in memory
|
||||
if(!m_bIsProtected)
|
||||
if (!m_bIsProtected)
|
||||
return new ProtectedString(false, ReadString().Remove(
|
||||
iStart, nCount));
|
||||
|
||||
UTF8Encoding utf8 = StrUtil.Utf8;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
char[] v = utf8.GetChars(pb);
|
||||
char[] vNew;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
if((iStart + nCount) > v.Length)
|
||||
throw new ArgumentException("iStart + nCount");
|
||||
if ((iStart + nCount) > v.Length)
|
||||
throw new ArgumentException("(iStart + nCount) > v.Length");
|
||||
|
||||
vNew = new char[v.Length - nCount];
|
||||
Array.Copy(v, 0, vNew, 0, iStart);
|
||||
Array.Copy(v, iStart + nCount, vNew, iStart, v.Length -
|
||||
(iStart + nCount));
|
||||
|
||||
pbNew = utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Remove(iStart, nCount));
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(v);
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
byte[] pbNew = utf8.GetBytes(vNew);
|
||||
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Remove(iStart, nCount));
|
||||
|
||||
MemUtil.ZeroArray<char>(vNew);
|
||||
MemUtil.ZeroByteArray(pbNew);
|
||||
return ps;
|
||||
}
|
||||
|
||||
public static ProtectedString operator +(ProtectedString a, ProtectedString b)
|
||||
{
|
||||
if (a == null) throw new ArgumentNullException("a");
|
||||
if (b == null) throw new ArgumentNullException("b");
|
||||
|
||||
if (b.IsEmpty) return a.WithProtection(a.IsProtected || b.IsProtected);
|
||||
if (a.IsEmpty) return b.WithProtection(a.IsProtected || b.IsProtected);
|
||||
if (!a.IsProtected && !b.IsProtected)
|
||||
return new ProtectedString(false, a.ReadString() + b.ReadString());
|
||||
|
||||
char[] vA = a.ReadChars(), vB = null, vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
vB = b.ReadChars();
|
||||
|
||||
vNew = new char[vA.Length + vB.Length];
|
||||
Array.Copy(vA, vNew, vA.Length);
|
||||
Array.Copy(vB, 0, vNew, vA.Length, vB.Length);
|
||||
|
||||
pbNew = StrUtil.Utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(vA);
|
||||
if (vB != null) MemUtil.ZeroArray<char>(vB);
|
||||
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
public static ProtectedString operator +(ProtectedString a, string b)
|
||||
{
|
||||
ProtectedString psB = new ProtectedString(false, b);
|
||||
return (a + psB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,97 +20,90 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace KeePassLib.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that is encrypted using a XOR pad until
|
||||
/// it is read. <c>XorredBuffer</c> objects are immutable and
|
||||
/// thread-safe.
|
||||
/// A <c>XorredBuffer</c> object stores data that is encrypted
|
||||
/// using a XOR pad.
|
||||
/// </summary>
|
||||
public sealed class XorredBuffer
|
||||
public sealed class XorredBuffer : IDisposable
|
||||
{
|
||||
private byte[] m_pbData; // Never null
|
||||
private byte[] m_pbXorPad; // Always valid for m_pbData
|
||||
private byte[] m_pbCT;
|
||||
private byte[] m_pbXorPad;
|
||||
|
||||
/// <summary>
|
||||
/// Length of the protected data in bytes.
|
||||
/// </summary>
|
||||
public uint Length
|
||||
{
|
||||
get { return (uint)m_pbData.Length; }
|
||||
get
|
||||
{
|
||||
if (m_pbCT == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
|
||||
return (uint)m_pbCT.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new XOR-protected object using a protected byte array
|
||||
/// and a XOR pad that decrypts the protected data. The
|
||||
/// <paramref name="pbProtectedData" /> byte array must have the same size
|
||||
/// as the <paramref name="pbXorPad" /> byte array.
|
||||
/// Construct a new <c>XorredBuffer</c> object.
|
||||
/// The <paramref name="pbCT" /> byte array must have the same
|
||||
/// length as the <paramref name="pbXorPad" /> byte array.
|
||||
/// The <c>XorredBuffer</c> object takes ownership of the two byte
|
||||
/// arrays, i.e. the caller must not use or modify them afterwards.
|
||||
/// arrays, i.e. the caller must not use them afterwards.
|
||||
/// </summary>
|
||||
/// <param name="pbProtectedData">Protected data (XOR pad applied).</param>
|
||||
/// <param name="pbCT">Data with XOR pad applied.</param>
|
||||
/// <param name="pbXorPad">XOR pad that can be used to decrypt the
|
||||
/// <paramref name="pbProtectedData" /> parameter.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if one of the input
|
||||
/// parameters is <c>null</c>.</exception>
|
||||
/// <exception cref="System.ArgumentException">Thrown if the byte arrays are
|
||||
/// of different size.</exception>
|
||||
public XorredBuffer(byte[] pbProtectedData, byte[] pbXorPad)
|
||||
/// <paramref name="pbCT" /> byte array.</param>
|
||||
public XorredBuffer(byte[] pbCT, byte[] pbXorPad)
|
||||
{
|
||||
if(pbProtectedData == null) { Debug.Assert(false); throw new ArgumentNullException("pbProtectedData"); }
|
||||
if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
|
||||
if (pbCT == null) { Debug.Assert(false); throw new ArgumentNullException("pbCT"); }
|
||||
if (pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
|
||||
if (pbCT.Length != pbXorPad.Length)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("pbXorPad");
|
||||
}
|
||||
|
||||
Debug.Assert(pbProtectedData.Length == pbXorPad.Length);
|
||||
if(pbProtectedData.Length != pbXorPad.Length) throw new ArgumentException();
|
||||
|
||||
m_pbData = pbProtectedData;
|
||||
m_pbCT = pbCT;
|
||||
m_pbXorPad = pbXorPad;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
~XorredBuffer()
|
||||
{
|
||||
Debug.Assert((m_pbCT == null) && (m_pbXorPad == null));
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_pbCT == null) return;
|
||||
|
||||
MemUtil.ZeroByteArray(m_pbCT);
|
||||
m_pbCT = null;
|
||||
|
||||
MemUtil.ZeroByteArray(m_pbXorPad);
|
||||
m_pbXorPad = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a copy of the plain-text. The caller is responsible
|
||||
/// for clearing the byte array safely after using it.
|
||||
/// </summary>
|
||||
/// <returns>Unprotected plain-text byte array.</returns>
|
||||
/// <returns>Plain-text byte array.</returns>
|
||||
public byte[] ReadPlainText()
|
||||
{
|
||||
byte[] pbPlain = new byte[m_pbData.Length];
|
||||
|
||||
for(int i = 0; i < pbPlain.Length; ++i)
|
||||
pbPlain[i] = (byte)(m_pbData[i] ^ m_pbXorPad[i]);
|
||||
|
||||
return pbPlain;
|
||||
}
|
||||
|
||||
/* public bool EqualsValue(XorredBuffer xb)
|
||||
{
|
||||
if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
|
||||
|
||||
if(xb.m_pbData.Length != m_pbData.Length) return false;
|
||||
|
||||
for(int i = 0; i < m_pbData.Length; ++i)
|
||||
byte[] pbCT = m_pbCT, pbX = m_pbXorPad;
|
||||
if ((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length))
|
||||
{
|
||||
byte bt1 = (byte)(m_pbData[i] ^ m_pbXorPad[i]);
|
||||
byte bt2 = (byte)(xb.m_pbData[i] ^ xb.m_pbXorPad[i]);
|
||||
|
||||
if(bt1 != bt2) return false;
|
||||
Debug.Assert(false);
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
|
||||
return true;
|
||||
byte[] pbPT = new byte[pbCT.Length];
|
||||
|
||||
for (int i = 0; i < pbPT.Length; ++i)
|
||||
pbPT[i] = (byte)(pbCT[i] ^ pbX[i]);
|
||||
|
||||
return pbPT;
|
||||
}
|
||||
|
||||
public bool EqualsValue(byte[] pb)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
|
||||
|
||||
if(pb.Length != m_pbData.Length) return false;
|
||||
|
||||
for(int i = 0; i < m_pbData.Length; ++i)
|
||||
{
|
||||
if((byte)(m_pbData[i] ^ m_pbXorPad[i]) != pb[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -78,9 +78,9 @@ namespace KeePassLib.Serialization
|
||||
public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
|
||||
{
|
||||
Debug.Assert(sSource != null);
|
||||
if(sSource == null) throw new ArgumentNullException("sSource");
|
||||
if (sSource == null) throw new ArgumentNullException("sSource");
|
||||
|
||||
if(m_bUsedOnce)
|
||||
if (m_bUsedOnce)
|
||||
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
|
||||
m_bUsedOnce = true;
|
||||
|
||||
@@ -91,7 +91,8 @@ namespace KeePassLib.Serialization
|
||||
m_format = fmt;
|
||||
m_slLogger = slLogger;
|
||||
|
||||
m_pbsBinaries.Clear();
|
||||
// Other applications might not perform a deduplication
|
||||
m_pbsBinaries = new ProtectedBinarySet(false);
|
||||
|
||||
UTF8Encoding encNoBom = StrUtil.Utf8;
|
||||
byte[] pbCipherKey = null;
|
||||
@@ -103,153 +104,156 @@ namespace KeePassLib.Serialization
|
||||
HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
|
||||
lStreams.Add(sHashing);
|
||||
|
||||
try
|
||||
{
|
||||
Stream sXml;
|
||||
if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers)
|
||||
{
|
||||
BinaryReaderEx br = new BinaryReaderEx(sHashing,
|
||||
encNoBom, KLRes.FileCorrupted);
|
||||
byte[] pbHeader = LoadHeader(br);
|
||||
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
|
||||
try
|
||||
{
|
||||
Stream sXml;
|
||||
if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers)
|
||||
{
|
||||
BinaryReaderEx br = new BinaryReaderEx(sHashing,
|
||||
encNoBom, KLRes.FileCorrupted);
|
||||
byte[] pbHeader = LoadHeader(br);
|
||||
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
|
||||
|
||||
int cbEncKey, cbEncIV;
|
||||
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
|
||||
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
|
||||
|
||||
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
|
||||
int cbEncKey, cbEncIV;
|
||||
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
|
||||
|
||||
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
|
||||
KLRes.FileIncomplete;
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
|
||||
|
||||
Stream sPlain;
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Stream sDecrypted = EncryptStream(sHashing, iCipher,
|
||||
pbCipherKey, cbEncIV, false);
|
||||
if((sDecrypted == null) || (sDecrypted == sHashing))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
|
||||
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
|
||||
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
|
||||
KLRes.FileIncomplete;
|
||||
|
||||
lStreams.Add(sDecrypted);
|
||||
Stream sPlain;
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Stream sDecrypted = EncryptStream(sHashing, iCipher,
|
||||
pbCipherKey, cbEncIV, false);
|
||||
if ((sDecrypted == null) || (sDecrypted == sHashing))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
|
||||
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
|
||||
encNoBom, strIncomplete);
|
||||
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
|
||||
|
||||
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
|
||||
throw new InvalidCompositeKeyException();
|
||||
lStreams.Add(sDecrypted);
|
||||
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
|
||||
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
|
||||
encNoBom, strIncomplete);
|
||||
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
|
||||
|
||||
if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if (!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
|
||||
throw new InvalidCompositeKeyException();
|
||||
|
||||
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
|
||||
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
|
||||
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
|
||||
|
||||
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
|
||||
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
|
||||
if((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
|
||||
throw new InvalidCompositeKeyException();
|
||||
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
|
||||
if ((pbStoredHash == null) || (pbStoredHash.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if (!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
|
||||
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
|
||||
|
||||
HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
|
||||
false, !m_bRepairMode, pbHmacKey64);
|
||||
lStreams.Add(sBlocks);
|
||||
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
|
||||
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
|
||||
if ((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
|
||||
throw new EndOfStreamException(strIncomplete);
|
||||
if (!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
|
||||
throw new InvalidCompositeKeyException();
|
||||
|
||||
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
|
||||
cbEncIV, false);
|
||||
if((sPlain == null) || (sPlain == sBlocks))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
}
|
||||
lStreams.Add(sPlain);
|
||||
HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
|
||||
false, !m_bRepairMode, pbHmacKey64);
|
||||
lStreams.Add(sBlocks);
|
||||
|
||||
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
{
|
||||
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
|
||||
lStreams.Add(sXml);
|
||||
}
|
||||
else sXml = sPlain;
|
||||
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
|
||||
cbEncIV, false);
|
||||
if ((sPlain == null) || (sPlain == sBlocks))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
}
|
||||
|
||||
if(m_uFileVersion >= FileVersion32_4)
|
||||
LoadInnerHeader(sXml); // Binary header before XML
|
||||
}
|
||||
else if(fmt == KdbxFormat.PlainXml)
|
||||
sXml = sHashing;
|
||||
else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
|
||||
lStreams.Add(sPlain);
|
||||
|
||||
if(fmt == KdbxFormat.Default)
|
||||
{
|
||||
if(m_pbInnerRandomStreamKey == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new SecurityException("Invalid inner random stream key!");
|
||||
}
|
||||
if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
{
|
||||
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
|
||||
lStreams.Add(sXml);
|
||||
}
|
||||
else sXml = sPlain;
|
||||
|
||||
if (m_uFileVersion >= FileVersion32_4)
|
||||
LoadInnerHeader(sXml); // Binary header before XML
|
||||
}
|
||||
else if (fmt == KdbxFormat.PlainXml)
|
||||
sXml = sHashing;
|
||||
else
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("fmt");
|
||||
}
|
||||
|
||||
if (fmt == KdbxFormat.Default)
|
||||
{
|
||||
if (m_pbInnerRandomStreamKey == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new SecurityException("Invalid inner random stream key!");
|
||||
}
|
||||
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbInnerRandomStreamKey);
|
||||
}
|
||||
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
|
||||
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbInnerRandomStreamKey);
|
||||
}
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
|
||||
|
||||
#if KeePassDebug_WriteXml
|
||||
// FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
|
||||
// FileAccess.Write, FileShare.None);
|
||||
// try
|
||||
// {
|
||||
// while(true)
|
||||
// {
|
||||
// int b = sXml.ReadByte();
|
||||
// if(b == -1) break;
|
||||
// fsOut.WriteByte((byte)b);
|
||||
// }
|
||||
// }
|
||||
// catch(Exception) { }
|
||||
// fsOut.Close();
|
||||
#warning XML output is enabled!
|
||||
/* using(FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
|
||||
FileAccess.Write, FileShare.None))
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
int b = sXml.ReadByte();
|
||||
if(b == -1) throw new EndOfStreamException();
|
||||
fsOut.WriteByte((byte)b);
|
||||
}
|
||||
} */
|
||||
#endif
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
if (fmt == KdbxFormat.ProtocolBuffers)
|
||||
{
|
||||
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
if (fmt == KdbxFormat.ProtocolBuffers)
|
||||
{
|
||||
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
|
||||
ReadXmlStreamed(sXml, sHashing);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
// ReadXmlDom(sXml);
|
||||
}
|
||||
catch(CryptographicException) // Thrown on invalid padding
|
||||
{
|
||||
throw new CryptographicException(KLRes.FileCorrupted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
|
||||
if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
|
||||
ReadXmlStreamed(sXml, sHashing);
|
||||
|
||||
CommonCleanUpRead(lStreams, sHashing);
|
||||
}
|
||||
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
}
|
||||
// ReadXmlDom(sXml);
|
||||
catch (CryptographicException) // Thrown on invalid padding
|
||||
{
|
||||
throw new CryptographicException(KLRes.FileCorrupted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
|
||||
if (pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
|
||||
|
||||
CommonCleanUpRead(lStreams, sHashing);
|
||||
}
|
||||
|
||||
#if KDBX_BENCHMARK
|
||||
swTime.Stop();
|
||||
@@ -267,9 +271,9 @@ namespace KeePassLib.Serialization
|
||||
Debug.Assert(m_pbHashOfFileOnDisk != null);
|
||||
|
||||
CleanUpInnerRandomStream();
|
||||
|
||||
// Reset memory protection settings (to always use reasonable
|
||||
// defaults)
|
||||
// defaults)
|
||||
m_pwDatabase.MemoryProtection = new MemoryProtectionConfig();
|
||||
|
||||
// Remove old backups (this call is required here in order to apply
|
||||
@@ -282,7 +286,7 @@ namespace KeePassLib.Serialization
|
||||
// Expand the root group, such that in case the user accidently
|
||||
// collapses the root group he can simply reopen the database
|
||||
PwGroup pgRoot = m_pwDatabase.RootGroup;
|
||||
if(pgRoot != null) pgRoot.IsExpanded = true;
|
||||
if (pgRoot != null) pgRoot.IsExpanded = true;
|
||||
else { Debug.Assert(false); }
|
||||
|
||||
m_pbHashOfHeader = null;
|
||||
@@ -303,25 +307,25 @@ namespace KeePassLib.Serialization
|
||||
byte[] pbSig2 = br.ReadBytes(4);
|
||||
uint uSig2 = MemUtil.BytesToUInt32(pbSig2);
|
||||
|
||||
if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
|
||||
if ((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
|
||||
throw new OldFormatException(PwDefs.ShortProductName + @" 1.x",
|
||||
OldFormatException.OldFormatType.KeePass1x);
|
||||
|
||||
if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
|
||||
else if((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
|
||||
if ((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
|
||||
else if ((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
|
||||
FileSignaturePreRelease2)) { }
|
||||
else throw new FormatException(KLRes.FileSigInvalid);
|
||||
|
||||
byte[] pb = br.ReadBytes(4);
|
||||
uint uVersion = MemUtil.BytesToUInt32(pb);
|
||||
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
|
||||
if ((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
|
||||
throw new FormatException(KLRes.FileVersionUnsupported +
|
||||
MessageService.NewParagraph + KLRes.FileNewVerReq);
|
||||
m_uFileVersion = uVersion;
|
||||
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
if(!ReadHeaderField(br)) break;
|
||||
if (!ReadHeaderField(br)) break;
|
||||
}
|
||||
|
||||
br.CopyDataTo = null;
|
||||
@@ -335,23 +339,23 @@ namespace KeePassLib.Serialization
|
||||
private bool ReadHeaderField(BinaryReaderEx brSource)
|
||||
{
|
||||
Debug.Assert(brSource != null);
|
||||
if(brSource == null) throw new ArgumentNullException("brSource");
|
||||
if (brSource == null) throw new ArgumentNullException("brSource");
|
||||
|
||||
byte btFieldID = brSource.ReadByte();
|
||||
|
||||
int cbSize;
|
||||
Debug.Assert(m_uFileVersion > 0);
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2));
|
||||
else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4));
|
||||
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
|
||||
if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
|
||||
|
||||
byte[] pbData = MemUtil.EmptyByteArray;
|
||||
if(cbSize > 0) pbData = brSource.ReadBytes(cbSize);
|
||||
if (cbSize > 0) pbData = brSource.ReadBytes(cbSize);
|
||||
|
||||
bool bResult = true;
|
||||
KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID;
|
||||
switch(kdbID)
|
||||
switch (kdbID)
|
||||
{
|
||||
case KdbxHeaderFieldID.EndOfHeader:
|
||||
bResult = false; // Returning false indicates end of header
|
||||
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
|
||||
Debug.Assert(m_uFileVersion < FileVersion32_4);
|
||||
|
||||
AesKdf kdfS = new AesKdf();
|
||||
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
|
||||
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
|
||||
m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters();
|
||||
|
||||
// m_pbTransformSeed = pbData;
|
||||
@@ -389,7 +393,7 @@ namespace KeePassLib.Serialization
|
||||
Debug.Assert(m_uFileVersion < FileVersion32_4);
|
||||
|
||||
AesKdf kdfR = new AesKdf();
|
||||
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
|
||||
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
|
||||
m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters();
|
||||
|
||||
// m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData);
|
||||
@@ -429,8 +433,8 @@ namespace KeePassLib.Serialization
|
||||
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
if(m_slLogger != null)
|
||||
m_slLogger.SetText(KLRes.UnknownHeaderId + @": " +
|
||||
if (m_slLogger != null)
|
||||
m_slLogger.SetText(KLRes.UnknownHeaderId + ": " +
|
||||
kdbID.ToString() + "!", LogStatusType.Warning);
|
||||
break;
|
||||
}
|
||||
@@ -443,28 +447,28 @@ namespace KeePassLib.Serialization
|
||||
BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8,
|
||||
KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc);
|
||||
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
if(!ReadInnerHeaderField(br)) break;
|
||||
if (!ReadInnerHeaderField(br)) break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ReadInnerHeaderField(BinaryReaderEx br)
|
||||
{
|
||||
Debug.Assert(br != null);
|
||||
if(br == null) throw new ArgumentNullException("br");
|
||||
if (br == null) throw new ArgumentNullException("br");
|
||||
|
||||
byte btFieldID = br.ReadByte();
|
||||
|
||||
int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4));
|
||||
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
|
||||
if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
|
||||
|
||||
byte[] pbData = MemUtil.EmptyByteArray;
|
||||
if(cbSize > 0) pbData = br.ReadBytes(cbSize);
|
||||
if (cbSize > 0) pbData = br.ReadBytes(cbSize);
|
||||
|
||||
bool bResult = true;
|
||||
KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID;
|
||||
switch(kdbID)
|
||||
switch (kdbID)
|
||||
{
|
||||
case KdbxInnerHeaderFieldID.EndOfHeader:
|
||||
bResult = false; // Returning false indicates end of header
|
||||
@@ -481,15 +485,16 @@ namespace KeePassLib.Serialization
|
||||
break;
|
||||
|
||||
case KdbxInnerHeaderFieldID.Binary:
|
||||
if(pbData.Length < 1) throw new FormatException();
|
||||
if (pbData.Length < 1) throw new FormatException();
|
||||
KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0];
|
||||
bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None);
|
||||
|
||||
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
|
||||
1, pbData.Length - 1);
|
||||
Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
|
||||
m_pbsBinaries.Add(pb);
|
||||
|
||||
if(bProt) MemUtil.ZeroByteArray(pbData);
|
||||
if (bProt) MemUtil.ZeroByteArray(pbData);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -502,7 +507,7 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void SetCipher(byte[] pbID)
|
||||
{
|
||||
if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
|
||||
if ((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
|
||||
throw new FormatException(KLRes.FileUnknownCipher);
|
||||
|
||||
m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
|
||||
@@ -511,7 +516,7 @@ namespace KeePassLib.Serialization
|
||||
private void SetCompressionFlags(byte[] pbFlags)
|
||||
{
|
||||
int nID = (int)MemUtil.BytesToUInt32(pbFlags);
|
||||
if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
|
||||
if ((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
|
||||
throw new FormatException(KLRes.FileUnknownCompression);
|
||||
|
||||
m_pwDatabase.Compression = (PwCompressionAlgorithm)nID;
|
||||
@@ -520,12 +525,35 @@ namespace KeePassLib.Serialization
|
||||
private void SetInnerRandomStreamID(byte[] pbID)
|
||||
{
|
||||
uint uID = MemUtil.BytesToUInt32(pbID);
|
||||
if(uID >= (uint)CrsAlgorithm.Count)
|
||||
if (uID >= (uint)CrsAlgorithm.Count)
|
||||
throw new FormatException(KLRes.FileUnknownCipher);
|
||||
|
||||
m_craInnerRandomStream = (CrsAlgorithm)uID;
|
||||
}
|
||||
|
||||
internal static PwGroup ReadGroup(Stream msData, PwDatabase pdContext,
|
||||
bool bCopyIcons, bool bNewUuids, bool bSetCreatedNow)
|
||||
{
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Load(msData, KdbxFormat.PlainXml, null);
|
||||
|
||||
if (bCopyIcons)
|
||||
PwDatabase.CopyCustomIcons(pd, pdContext, pd.RootGroup, true);
|
||||
|
||||
if (bNewUuids)
|
||||
{
|
||||
pd.RootGroup.Uuid = new PwUuid(true);
|
||||
pd.RootGroup.CreateNewItemUuids(true, true, true);
|
||||
}
|
||||
|
||||
if (bSetCreatedNow) pd.RootGroup.SetCreatedNow(true);
|
||||
|
||||
return pd.RootGroup;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static List<PwEntry> ReadEntries(Stream msData)
|
||||
{
|
||||
@@ -537,81 +565,14 @@ namespace KeePassLib.Serialization
|
||||
{
|
||||
return ReadEntries(msData, pdContext, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Read entries from a stream.
|
||||
/// </summary>
|
||||
/// <param name="msData">Input stream to read the entries from.</param>
|
||||
/// <returns>Extracted entries.</returns>
|
||||
/// <param name="pdContext">Context database (e.g. for storing icons).</param>
|
||||
/// <param name="bCopyIcons">If <c>true</c>, custom icons required by
|
||||
/// the loaded entries are copied to the context database.</param>
|
||||
/// <returns>Loaded entries.</returns>
|
||||
|
||||
public static List<PwEntry> ReadEntries(Stream msData, PwDatabase pdContext,
|
||||
bool bCopyIcons)
|
||||
{
|
||||
List<PwEntry> lEntries = new List<PwEntry>();
|
||||
/* KdbxFile f = new KdbxFile(pwDatabase);
|
||||
if(msData == null) { Debug.Assert(false); return lEntries; }
|
||||
f.m_format = KdbxFormat.PlainXml;
|
||||
if (msData == null) { Debug.Assert(false); return new List<PwEntry>(); }
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(msData);
|
||||
|
||||
XmlElement el = doc.DocumentElement;
|
||||
if(el.Name != ElemRoot) throw new FormatException();
|
||||
|
||||
List<PwEntry> vEntries = new List<PwEntry>();
|
||||
|
||||
foreach(XmlNode xmlChild in el.ChildNodes)
|
||||
{
|
||||
if(xmlChild.Name == ElemEntry)
|
||||
{
|
||||
PwEntry pe = f.ReadEntry(xmlChild);
|
||||
pe.Uuid = new PwUuid(true);
|
||||
|
||||
foreach(PwEntry peHistory in pe.History)
|
||||
peHistory.Uuid = pe.Uuid;
|
||||
|
||||
vEntries.Add(pe);
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
|
||||
|
||||
return vEntries; */
|
||||
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Load(msData, KdbxFormat.PlainXml, null);
|
||||
|
||||
foreach(PwEntry pe in pd.RootGroup.Entries)
|
||||
{
|
||||
pe.SetUuid(new PwUuid(true), true);
|
||||
lEntries.Add(pe);
|
||||
|
||||
if(bCopyIcons && (pdContext != null))
|
||||
{
|
||||
PwUuid pu = pe.CustomIconUuid;
|
||||
if(!pu.Equals(PwUuid.Zero))
|
||||
{
|
||||
int iSrc = pd.GetCustomIconIndex(pu);
|
||||
int iDst = pdContext.GetCustomIconIndex(pu);
|
||||
|
||||
if(iSrc < 0) { Debug.Assert(false); }
|
||||
else if(iDst < 0)
|
||||
{
|
||||
pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]);
|
||||
|
||||
pdContext.Modified = true;
|
||||
pdContext.UINeedsIconUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lEntries;
|
||||
PwGroup pg = ReadGroup(msData, pdContext, bCopyIcons, true, true);
|
||||
return pg.GetEntries(true).CloneShallowToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +25,7 @@ using System.IO;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
using keepass2android;
|
||||
#if !KeePassUAP
|
||||
using System.Drawing;
|
||||
using System.Security.Cryptography;
|
||||
@@ -48,8 +48,6 @@ using KeePassLib.Resources;
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
using keepass2android;
|
||||
|
||||
namespace KeePassLib.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
@@ -81,14 +79,15 @@ namespace KeePassLib.Serialization
|
||||
IStatusLogger slLogger)
|
||||
{
|
||||
Debug.Assert(sSaveTo != null);
|
||||
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo");
|
||||
if (sSaveTo == null) throw new ArgumentNullException("sSaveTo");
|
||||
|
||||
if(m_bUsedOnce)
|
||||
if (m_bUsedOnce)
|
||||
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
|
||||
m_bUsedOnce = true;
|
||||
|
||||
m_format = fmt;
|
||||
m_slLogger = slLogger;
|
||||
m_xmlWriter = null;
|
||||
|
||||
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
|
||||
UTF8Encoding encNoBom = StrUtil.Utf8;
|
||||
@@ -96,7 +95,7 @@ namespace KeePassLib.Serialization
|
||||
byte[] pbCipherKey = null;
|
||||
byte[] pbHmacKey64 = null;
|
||||
|
||||
m_pbsBinaries.Clear();
|
||||
m_pbsBinaries = new ProtectedBinarySet(true);
|
||||
m_pbsBinaries.AddFrom(pgRoot);
|
||||
|
||||
List<Stream> lStreams = new List<Stream>();
|
||||
@@ -107,6 +106,10 @@ namespace KeePassLib.Serialization
|
||||
|
||||
try
|
||||
{
|
||||
// Fix history entries (should not be necessary; just for safety,
|
||||
// as e.g. XPath searches depend on correct history entry UUIDs)
|
||||
if (m_pwDatabase.MaintainBackups()) { Debug.Assert(false); }
|
||||
|
||||
m_uFileVersion = GetMinKdbxVersion();
|
||||
|
||||
int cbEncKey, cbEncIV;
|
||||
@@ -118,30 +121,30 @@ namespace KeePassLib.Serialization
|
||||
// m_pbTransformSeed = cr.GetRandomBytes(32);
|
||||
PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid;
|
||||
KdfEngine kdf = KdfPool.Get(puKdf);
|
||||
if(kdf == null)
|
||||
if (kdf == null)
|
||||
throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
|
||||
// KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
|
||||
"UUID: " + puKdf.ToHexString() + ".");
|
||||
kdf.Randomize(m_pwDatabase.KdfParameters);
|
||||
|
||||
if(m_format == KdbxFormat.Default)
|
||||
if (m_format == KdbxFormat.Default)
|
||||
{
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
|
||||
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
|
||||
m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
|
||||
m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
|
||||
}
|
||||
}
|
||||
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbInnerRandomStreamKey);
|
||||
}
|
||||
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
m_pbStreamStartBytes = cr.GetRandomBytes(32);
|
||||
|
||||
Stream sXml;
|
||||
@@ -156,11 +159,11 @@ namespace KeePassLib.Serialization
|
||||
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
|
||||
|
||||
Stream sPlain;
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Stream sEncrypted = EncryptStream(sHashing, iCipher,
|
||||
pbCipherKey, cbEncIV, true);
|
||||
if((sEncrypted == null) || (sEncrypted == sHashing))
|
||||
if ((sEncrypted == null) || (sEncrypted == sHashing))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
lStreams.Add(sEncrypted);
|
||||
|
||||
@@ -182,22 +185,22 @@ namespace KeePassLib.Serialization
|
||||
|
||||
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
|
||||
cbEncIV, true);
|
||||
if((sPlain == null) || (sPlain == sBlocks))
|
||||
if ((sPlain == null) || (sPlain == sBlocks))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
}
|
||||
lStreams.Add(sPlain);
|
||||
|
||||
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
{
|
||||
sXml = new GZipStream(sPlain, CompressionMode.Compress);
|
||||
lStreams.Add(sXml);
|
||||
}
|
||||
else sXml = sPlain;
|
||||
|
||||
if(m_uFileVersion >= FileVersion32_4)
|
||||
if (m_uFileVersion >= FileVersion32_4)
|
||||
WriteInnerHeader(sXml); // Binary header before XML
|
||||
}
|
||||
else if(m_format == KdbxFormat.PlainXml)
|
||||
else if (m_format == KdbxFormat.PlainXml)
|
||||
sXml = sHashing;
|
||||
else
|
||||
{
|
||||
@@ -251,6 +254,8 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void CommonCleanUpWrite(List<Stream> lStreams, HashingStreamEx sHashing)
|
||||
{
|
||||
if (m_xmlWriter != null) { m_xmlWriter.Close(); m_xmlWriter = null; }
|
||||
|
||||
CloseStreams(lStreams);
|
||||
|
||||
Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
|
||||
@@ -259,14 +264,13 @@ namespace KeePassLib.Serialization
|
||||
|
||||
CleanUpInnerRandomStream();
|
||||
|
||||
m_xmlWriter = null;
|
||||
m_pbHashOfHeader = null;
|
||||
}
|
||||
|
||||
private byte[] GenerateHeader()
|
||||
{
|
||||
byte[] pbHeader;
|
||||
using(MemoryStream ms = new MemoryStream())
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
|
||||
@@ -281,7 +285,7 @@ namespace KeePassLib.Serialization
|
||||
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
|
||||
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals(
|
||||
(new AesKdf()).Uuid));
|
||||
@@ -295,10 +299,10 @@ namespace KeePassLib.Serialization
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters,
|
||||
KdfParameters.SerializeExt(m_pwDatabase.KdfParameters));
|
||||
|
||||
if(m_pbEncryptionIV.Length > 0)
|
||||
if (m_pbEncryptionIV.Length > 0)
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
|
||||
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey,
|
||||
m_pbInnerRandomStreamKey);
|
||||
@@ -306,14 +310,14 @@ namespace KeePassLib.Serialization
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes,
|
||||
m_pbStreamStartBytes);
|
||||
|
||||
int nIrsID = (int)m_craInnerRandomStream;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
|
||||
MemUtil.Int32ToBytes(nIrsID));
|
||||
int nIrsID = (int)m_craInnerRandomStream;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
|
||||
MemUtil.Int32ToBytes(nIrsID));
|
||||
}
|
||||
|
||||
// Write public custom data only when there is at least one item,
|
||||
// because KDBX 3.1 didn't support this field yet
|
||||
if(m_pwDatabase.PublicCustomData.Count > 0)
|
||||
if (m_pwDatabase.PublicCustomData.Count > 0)
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData,
|
||||
VariantDictionary.Serialize(m_pwDatabase.PublicCustomData));
|
||||
|
||||
@@ -333,12 +337,12 @@ namespace KeePassLib.Serialization
|
||||
|
||||
byte[] pb = (pbData ?? MemUtil.EmptyByteArray);
|
||||
int cb = pb.Length;
|
||||
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
|
||||
Debug.Assert(m_uFileVersion > 0);
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
if (m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
if(cb > (int)ushort.MaxValue)
|
||||
if (cb > (int)ushort.MaxValue)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("pbData");
|
||||
@@ -361,13 +365,13 @@ namespace KeePassLib.Serialization
|
||||
m_pbInnerRandomStreamKey, null);
|
||||
|
||||
ProtectedBinary[] vBin = m_pbsBinaries.ToArray();
|
||||
for(int i = 0; i < vBin.Length; ++i)
|
||||
for (int i = 0; i < vBin.Length; ++i)
|
||||
{
|
||||
ProtectedBinary pb = vBin[i];
|
||||
if(pb == null) throw new InvalidOperationException();
|
||||
if (pb == null) throw new InvalidOperationException();
|
||||
|
||||
KdbxBinaryFlags f = KdbxBinaryFlags.None;
|
||||
if(pb.IsProtected) f |= KdbxBinaryFlags.Protected;
|
||||
if (pb.IsProtected) f |= KdbxBinaryFlags.Protected;
|
||||
|
||||
byte[] pbFlags = new byte[1] { (byte)f };
|
||||
byte[] pbData = pb.ReadData();
|
||||
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary,
|
||||
pbFlags, pbData);
|
||||
|
||||
if(pb.IsProtected) MemUtil.ZeroByteArray(pbData);
|
||||
if (pb.IsProtected) MemUtil.ZeroByteArray(pbData);
|
||||
}
|
||||
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader,
|
||||
@@ -391,7 +395,7 @@ namespace KeePassLib.Serialization
|
||||
byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray);
|
||||
|
||||
int cb = pb1.Length + pb2.Length;
|
||||
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
|
||||
MemUtil.Write(s, MemUtil.Int32ToBytes(cb));
|
||||
MemUtil.Write(s, pb1);
|
||||
@@ -401,7 +405,7 @@ namespace KeePassLib.Serialization
|
||||
private void WriteDocument(PwGroup pgRoot)
|
||||
{
|
||||
Debug.Assert(m_xmlWriter != null);
|
||||
if(m_xmlWriter == null) throw new InvalidOperationException();
|
||||
if (m_xmlWriter == null) throw new InvalidOperationException();
|
||||
|
||||
uint uNumGroups, uNumEntries, uCurEntry = 0;
|
||||
pgRoot.GetCounts(true, out uNumGroups, out uNumEntries);
|
||||
@@ -417,14 +421,14 @@ namespace KeePassLib.Serialization
|
||||
Stack<PwGroup> groupStack = new Stack<PwGroup>();
|
||||
groupStack.Push(pgRoot);
|
||||
|
||||
GroupHandler gh = delegate(PwGroup pg)
|
||||
GroupHandler gh = delegate (PwGroup pg)
|
||||
{
|
||||
Debug.Assert(pg != null);
|
||||
if(pg == null) throw new ArgumentNullException("pg");
|
||||
if (pg == null) throw new ArgumentNullException("pg");
|
||||
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
if(pg.ParentGroup == groupStack.Peek())
|
||||
if (pg.ParentGroup == groupStack.Peek())
|
||||
{
|
||||
groupStack.Push(pg);
|
||||
StartGroup(pg);
|
||||
@@ -433,7 +437,7 @@ namespace KeePassLib.Serialization
|
||||
else
|
||||
{
|
||||
groupStack.Pop();
|
||||
if(groupStack.Count <= 0) return false;
|
||||
if (groupStack.Count <= 0) return false;
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
@@ -442,23 +446,25 @@ namespace KeePassLib.Serialization
|
||||
return true;
|
||||
};
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
Debug.Assert(pe != null);
|
||||
WriteEntry(pe, false);
|
||||
|
||||
++uCurEntry;
|
||||
if(m_slLogger != null)
|
||||
if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
|
||||
if (m_slLogger != null)
|
||||
{
|
||||
if (!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
|
||||
throw new InvalidOperationException();
|
||||
if (!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
|
||||
throw new OperationCanceledException();
|
||||
|
||||
while(groupStack.Count > 1)
|
||||
while (groupStack.Count > 1)
|
||||
{
|
||||
m_xmlWriter.WriteEndElement();
|
||||
groupStack.Pop();
|
||||
@@ -479,11 +485,11 @@ namespace KeePassLib.Serialization
|
||||
|
||||
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false);
|
||||
|
||||
if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
|
||||
if ((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
|
||||
WriteObject(ElemHeaderHash, Convert.ToBase64String(
|
||||
m_pbHashOfHeader), false);
|
||||
|
||||
if(m_uFileVersion >= FileVersion32_4)
|
||||
if (m_uFileVersion >= FileVersion32_4)
|
||||
WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged);
|
||||
|
||||
WriteObject(ElemDbName, m_pwDatabase.Name, true);
|
||||
@@ -497,7 +503,7 @@ namespace KeePassLib.Serialization
|
||||
WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged);
|
||||
WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec);
|
||||
WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce);
|
||||
if(m_pwDatabase.MasterKeyChangeForceOnce)
|
||||
if (m_pwDatabase.MasterKeyChangeForceOnce)
|
||||
WriteObject(ElemDbKeyChangeForceOnce, true);
|
||||
|
||||
WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection);
|
||||
@@ -515,8 +521,8 @@ namespace KeePassLib.Serialization
|
||||
WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup);
|
||||
WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup);
|
||||
|
||||
if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
|
||||
WriteBinPool();
|
||||
if ((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
|
||||
WriteBinPool();
|
||||
|
||||
WriteList(ElemCustomData, m_pwDatabase.CustomData);
|
||||
|
||||
@@ -530,10 +536,10 @@ namespace KeePassLib.Serialization
|
||||
WriteObject(ElemName, pg.Name, true);
|
||||
WriteObject(ElemNotes, pg.Notes, true);
|
||||
WriteObject(ElemIcon, (int)pg.IconId);
|
||||
|
||||
if(!pg.CustomIconUuid.Equals(PwUuid.Zero))
|
||||
|
||||
if (!pg.CustomIconUuid.Equals(PwUuid.Zero))
|
||||
WriteObject(ElemCustomIconID, pg.CustomIconUuid);
|
||||
|
||||
|
||||
WriteList(ElemTimes, pg);
|
||||
WriteObject(ElemIsExpanded, pg.IsExpanded);
|
||||
WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true);
|
||||
@@ -541,7 +547,17 @@ namespace KeePassLib.Serialization
|
||||
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
|
||||
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
|
||||
|
||||
if(pg.CustomData.Count > 0)
|
||||
if (m_uFileVersion >= FileVersion32_4_1)
|
||||
{
|
||||
if (!pg.PreviousParentGroup.Equals(PwUuid.Zero))
|
||||
WriteObject(ElemPreviousParentGroup, pg.PreviousParentGroup);
|
||||
|
||||
List<string> lTags = pg.Tags;
|
||||
if (lTags.Count != 0)
|
||||
WriteObject(ElemTags, StrUtil.TagsToString(lTags, false), true);
|
||||
}
|
||||
|
||||
if (pg.CustomData.Count > 0)
|
||||
WriteList(ElemCustomData, pg.CustomData);
|
||||
}
|
||||
|
||||
@@ -552,31 +568,39 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void WriteEntry(PwEntry pe, bool bIsHistory)
|
||||
{
|
||||
Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe");
|
||||
Debug.Assert(pe != null); if (pe == null) throw new ArgumentNullException("pe");
|
||||
|
||||
m_xmlWriter.WriteStartElement(ElemEntry);
|
||||
|
||||
WriteObject(ElemUuid, pe.Uuid);
|
||||
WriteObject(ElemIcon, (int)pe.IconId);
|
||||
|
||||
if(!pe.CustomIconUuid.Equals(PwUuid.Zero))
|
||||
if (!pe.CustomIconUuid.Equals(PwUuid.Zero))
|
||||
WriteObject(ElemCustomIconID, pe.CustomIconUuid);
|
||||
|
||||
WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false);
|
||||
WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false);
|
||||
WriteObject(ElemOverrideUrl, pe.OverrideUrl, true);
|
||||
|
||||
if ((m_uFileVersion >= FileVersion32_4_1) && !pe.QualityCheck)
|
||||
WriteObject(ElemQualityCheck, false);
|
||||
|
||||
WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true);
|
||||
|
||||
if ((m_uFileVersion >= FileVersion32_4_1) &&
|
||||
!pe.PreviousParentGroup.Equals(PwUuid.Zero))
|
||||
WriteObject(ElemPreviousParentGroup, pe.PreviousParentGroup);
|
||||
|
||||
WriteList(ElemTimes, pe);
|
||||
|
||||
WriteList(pe.Strings, true);
|
||||
WriteList(pe.Binaries);
|
||||
WriteList(ElemAutoType, pe.AutoType);
|
||||
|
||||
if(pe.CustomData.Count > 0)
|
||||
if (pe.CustomData.Count > 0)
|
||||
WriteList(ElemCustomData, pe.CustomData);
|
||||
|
||||
if(!bIsHistory) WriteList(ElemHistory, pe.History, true);
|
||||
if (!bIsHistory) WriteList(ElemHistory, pe.History, true);
|
||||
else { Debug.Assert(pe.History.UCount == 0); }
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
@@ -585,18 +609,18 @@ namespace KeePassLib.Serialization
|
||||
private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings)
|
||||
{
|
||||
Debug.Assert(dictStrings != null);
|
||||
if(dictStrings == null) throw new ArgumentNullException("dictStrings");
|
||||
if (dictStrings == null) throw new ArgumentNullException("dictStrings");
|
||||
|
||||
foreach(KeyValuePair<string, ProtectedString> kvp in dictStrings)
|
||||
foreach (KeyValuePair<string, ProtectedString> kvp in dictStrings)
|
||||
WriteObject(kvp.Key, kvp.Value, bEntryStrings);
|
||||
}
|
||||
|
||||
private void WriteList(ProtectedBinaryDictionary dictBinaries)
|
||||
{
|
||||
Debug.Assert(dictBinaries != null);
|
||||
if(dictBinaries == null) throw new ArgumentNullException("dictBinaries");
|
||||
if (dictBinaries == null) throw new ArgumentNullException("dictBinaries");
|
||||
|
||||
foreach(KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
|
||||
foreach (KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
|
||||
WriteObject(kvp.Key, kvp.Value, true);
|
||||
}
|
||||
|
||||
@@ -604,19 +628,19 @@ namespace KeePassLib.Serialization
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(cfgAutoType != null);
|
||||
if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
|
||||
if (cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled);
|
||||
WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions);
|
||||
|
||||
if(cfgAutoType.DefaultSequence.Length > 0)
|
||||
if (cfgAutoType.DefaultSequence.Length > 0)
|
||||
WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true);
|
||||
|
||||
foreach(AutoTypeAssociation a in cfgAutoType.Associations)
|
||||
foreach (AutoTypeAssociation a in cfgAutoType.Associations)
|
||||
WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence,
|
||||
new KeyValuePair<string, string>(a.WindowName, a.Sequence));
|
||||
new KeyValuePair<string, string>(a.WindowName, a.Sequence), null);
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
@@ -624,7 +648,7 @@ namespace KeePassLib.Serialization
|
||||
private void WriteList(string name, ITimeLogger times)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times");
|
||||
Debug.Assert(times != null); if (times == null) throw new ArgumentNullException("times");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
@@ -636,17 +660,17 @@ namespace KeePassLib.Serialization
|
||||
WriteObject(ElemUsageCount, times.UsageCount);
|
||||
WriteObject(ElemLocationChanged, times.LocationChanged);
|
||||
|
||||
m_xmlWriter.WriteEndElement(); // Name
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
private void WriteList(string name, PwObjectList<PwEntry> value, bool bIsHistory)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
foreach(PwEntry pe in value)
|
||||
foreach (PwEntry pe in value)
|
||||
WriteEntry(pe, bIsHistory);
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
@@ -655,11 +679,11 @@ namespace KeePassLib.Serialization
|
||||
private void WriteList(string name, PwObjectList<PwDeletedObject> value)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
foreach(PwDeletedObject pdo in value)
|
||||
foreach (PwDeletedObject pdo in value)
|
||||
WriteObject(ElemDeletedObject, pdo);
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
@@ -685,31 +709,45 @@ namespace KeePassLib.Serialization
|
||||
private void WriteList(string name, StringDictionaryEx value)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in value)
|
||||
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp);
|
||||
foreach (KeyValuePair<string, string> kvp in value)
|
||||
{
|
||||
DateTime? odtLastMod = null;
|
||||
if (m_uFileVersion >= FileVersion32_4_1)
|
||||
odtLastMod = value.GetLastModificationTime(kvp.Key);
|
||||
|
||||
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp, odtLastMod);
|
||||
}
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
private void WriteCustomIconList()
|
||||
{
|
||||
if(m_pwDatabase.CustomIcons.Count == 0) return;
|
||||
if (m_pwDatabase.CustomIcons.Count == 0) return;
|
||||
|
||||
m_xmlWriter.WriteStartElement(ElemCustomIcons);
|
||||
|
||||
foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons)
|
||||
foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(ElemCustomIconItem);
|
||||
|
||||
WriteObject(ElemCustomIconItemID, pwci.Uuid);
|
||||
WriteObject(ElemCustomIconItemID, ci.Uuid);
|
||||
|
||||
string strData = Convert.ToBase64String(pwci.ImageDataPng);
|
||||
string strData = Convert.ToBase64String(ci.ImageDataPng);
|
||||
WriteObject(ElemCustomIconItemData, strData, false);
|
||||
|
||||
if (m_uFileVersion >= FileVersion32_4_1)
|
||||
{
|
||||
if (ci.Name.Length != 0)
|
||||
WriteObject(ElemName, ci.Name, true);
|
||||
if (ci.LastModificationTime.HasValue)
|
||||
WriteObject(ElemLastModTime, ci.LastModificationTime.Value);
|
||||
}
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
@@ -724,7 +762,7 @@ namespace KeePassLib.Serialization
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
if(bFilterValueXmlChars)
|
||||
if (bFilterValueXmlChars)
|
||||
m_xmlWriter.WriteString(StrUtil.SafeXmlString(value));
|
||||
else m_xmlWriter.WriteString(value);
|
||||
|
||||
@@ -741,7 +779,7 @@ namespace KeePassLib.Serialization
|
||||
private void WriteObject(string name, PwUuid value)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
WriteObject(name, Convert.ToBase64String(value.UuidBytes), false);
|
||||
}
|
||||
@@ -788,7 +826,7 @@ namespace KeePassLib.Serialization
|
||||
Debug.Assert(value.Kind == DateTimeKind.Utc);
|
||||
|
||||
// Cf. ReadTime
|
||||
if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
|
||||
if ((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
|
||||
{
|
||||
DateTime dt = TimeUtil.ToUtc(value, false);
|
||||
|
||||
@@ -805,29 +843,33 @@ namespace KeePassLib.Serialization
|
||||
|
||||
byte[] pb = MemUtil.Int64ToBytes(lSec);
|
||||
WriteObject(name, Convert.ToBase64String(pb), false);
|
||||
}
|
||||
}
|
||||
else WriteObject(name, TimeUtil.SerializeUtc(value), false);
|
||||
}
|
||||
|
||||
private void WriteObject(string name, string strKeyName,
|
||||
string strValueName, KeyValuePair<string, string> kvp)
|
||||
private void WriteObject(string name, string strKeyName, string strValueName,
|
||||
KeyValuePair<string, string> kvp, DateTime? odtLastMod)
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
|
||||
m_xmlWriter.WriteStartElement(strKeyName);
|
||||
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key));
|
||||
m_xmlWriter.WriteEndElement();
|
||||
|
||||
m_xmlWriter.WriteStartElement(strValueName);
|
||||
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value));
|
||||
m_xmlWriter.WriteEndElement();
|
||||
|
||||
if (odtLastMod.HasValue)
|
||||
WriteObject(ElemLastModTime, odtLastMod.Value);
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
private void WriteObject(string name, ProtectedString value, bool bIsEntryString)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(ElemString);
|
||||
m_xmlWriter.WriteStartElement(ElemKey);
|
||||
@@ -836,30 +878,30 @@ namespace KeePassLib.Serialization
|
||||
m_xmlWriter.WriteStartElement(ElemValue);
|
||||
|
||||
bool bProtected = value.IsProtected;
|
||||
if(bIsEntryString)
|
||||
if (bIsEntryString)
|
||||
{
|
||||
// Adjust memory protection setting (which might be different
|
||||
// from the database default, e.g. due to an import which
|
||||
// didn't specify the correct setting)
|
||||
if(name == PwDefs.TitleField)
|
||||
if (name == PwDefs.TitleField)
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectTitle;
|
||||
else if(name == PwDefs.UserNameField)
|
||||
else if (name == PwDefs.UserNameField)
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectUserName;
|
||||
else if(name == PwDefs.PasswordField)
|
||||
else if (name == PwDefs.PasswordField)
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectPassword;
|
||||
else if(name == PwDefs.UrlField)
|
||||
else if (name == PwDefs.UrlField)
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectUrl;
|
||||
else if(name == PwDefs.NotesField)
|
||||
else if (name == PwDefs.NotesField)
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectNotes;
|
||||
}
|
||||
|
||||
if(bProtected && (m_format == KdbxFormat.Default))
|
||||
if (bProtected && (m_format == KdbxFormat.Default))
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
|
||||
|
||||
byte[] pbEncoded = value.ReadXorredString(m_randomStream);
|
||||
if(pbEncoded.Length > 0)
|
||||
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
|
||||
byte[] pbEnc = value.ReadXorredString(m_randomStream);
|
||||
if (pbEnc.Length > 0)
|
||||
m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -869,25 +911,24 @@ namespace KeePassLib.Serialization
|
||||
// string transformation here. By default, language-dependent conversions
|
||||
// should be applied, otherwise characters could be rendered incorrectly
|
||||
// (code page problems).
|
||||
if(m_bLocalizedNames)
|
||||
if (g_bLocalizedNames)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(char ch in strValue)
|
||||
foreach (char ch in strValue)
|
||||
{
|
||||
char chMapped = ch;
|
||||
|
||||
// Symbols and surrogates must be moved into the correct code
|
||||
// page area
|
||||
if(char.IsSymbol(ch) || char.IsSurrogate(ch))
|
||||
if (char.IsSymbol(ch) || char.IsSurrogate(ch))
|
||||
{
|
||||
System.Globalization.UnicodeCategory cat =
|
||||
CharUnicodeInfo.GetUnicodeCategory(ch);
|
||||
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch);
|
||||
// Map character to correct position in code page
|
||||
chMapped = (char)((int)cat * 32 + ch);
|
||||
}
|
||||
else if(char.IsControl(ch))
|
||||
else if (char.IsControl(ch))
|
||||
{
|
||||
if(ch >= 256) // Control character in high ANSI code page
|
||||
if (ch >= 256) // Control character in high ANSI code page
|
||||
{
|
||||
// Some of the control characters map to corresponding ones
|
||||
// in the low ANSI range (up to 255) when calling
|
||||
@@ -907,7 +948,7 @@ namespace KeePassLib.Serialization
|
||||
strValue = sb.ToString(); // Correct string for current code page
|
||||
}
|
||||
|
||||
if((m_format == KdbxFormat.PlainXml) && bProtected)
|
||||
if ((m_format == KdbxFormat.PlainXml) && bProtected)
|
||||
m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue);
|
||||
|
||||
m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue));
|
||||
@@ -920,7 +961,7 @@ namespace KeePassLib.Serialization
|
||||
private void WriteObject(string name, ProtectedBinary value, bool bAllowRef)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(ElemBinary);
|
||||
m_xmlWriter.WriteStartElement(ElemKey);
|
||||
@@ -929,13 +970,13 @@ namespace KeePassLib.Serialization
|
||||
m_xmlWriter.WriteStartElement(ElemValue);
|
||||
|
||||
string strRef = null;
|
||||
if(bAllowRef)
|
||||
if (bAllowRef)
|
||||
{
|
||||
int iRef = m_pbsBinaries.Find(value);
|
||||
if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
|
||||
if (iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
if(strRef != null)
|
||||
if (strRef != null)
|
||||
m_xmlWriter.WriteAttributeString(AttrRef, strRef);
|
||||
else SubWriteValue(value);
|
||||
|
||||
@@ -945,17 +986,17 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void SubWriteValue(ProtectedBinary value)
|
||||
{
|
||||
if(value.IsProtected && (m_format == KdbxFormat.Default))
|
||||
if (value.IsProtected && (m_format == KdbxFormat.Default))
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
|
||||
|
||||
byte[] pbEncoded = value.ReadXorredData(m_randomStream);
|
||||
if(pbEncoded.Length > 0)
|
||||
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
|
||||
byte[] pbEnc = value.ReadXorredData(m_randomStream);
|
||||
if (pbEnc.Length > 0)
|
||||
m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_pwDatabase.Compression != PwCompressionAlgorithm.None)
|
||||
if (m_pwDatabase.Compression != PwCompressionAlgorithm.None)
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue);
|
||||
|
||||
@@ -963,18 +1004,18 @@ namespace KeePassLib.Serialization
|
||||
byte[] pbCmp = MemUtil.Compress(pbRaw);
|
||||
m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length);
|
||||
|
||||
if(value.IsProtected)
|
||||
if (value.IsProtected)
|
||||
{
|
||||
MemUtil.ZeroByteArray(pbRaw);
|
||||
MemUtil.ZeroByteArray(pbCmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] pbRaw = value.ReadData();
|
||||
m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length);
|
||||
|
||||
if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
|
||||
if (value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,7 +1023,7 @@ namespace KeePassLib.Serialization
|
||||
private void WriteObject(string name, PwDeletedObject value)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
|
||||
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
|
||||
|
||||
m_xmlWriter.WriteStartElement(name);
|
||||
WriteObject(ElemUuid, value.Uuid);
|
||||
@@ -995,7 +1036,7 @@ namespace KeePassLib.Serialization
|
||||
m_xmlWriter.WriteStartElement(ElemBinaries);
|
||||
|
||||
ProtectedBinary[] v = m_pbsBinaries.ToArray();
|
||||
for(int i = 0; i < v.Length; ++i)
|
||||
for (int i = 0; i < v.Length; ++i)
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(ElemBinary);
|
||||
m_xmlWriter.WriteAttributeString(AttrId,
|
||||
@@ -1007,6 +1048,25 @@ namespace KeePassLib.Serialization
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
internal static void WriteGroup(Stream msOutput, PwDatabase pdContext,
|
||||
PwGroup pg)
|
||||
{
|
||||
if (msOutput == null) throw new ArgumentNullException("msOutput");
|
||||
// pdContext may be null
|
||||
if (pg == null) throw new ArgumentNullException("pg");
|
||||
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey(), pg.Name);
|
||||
|
||||
pd.RootGroup = pg.CloneDeep();
|
||||
pd.RootGroup.ParentGroup = null;
|
||||
|
||||
PwDatabase.CopyCustomIcons(pdContext, pd, pd.RootGroup, true);
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries)
|
||||
{
|
||||
@@ -1014,66 +1074,21 @@ namespace KeePassLib.Serialization
|
||||
}
|
||||
|
||||
public static bool WriteEntries(Stream msOutput, PwDatabase pdContext,
|
||||
PwEntry[] vEntries)
|
||||
PwEntry[] vEntries)
|
||||
{
|
||||
if (msOutput == null) { Debug.Assert(false); return false; }
|
||||
// pdContext may be null
|
||||
if (vEntries == null) { Debug.Assert(false); return false; }
|
||||
|
||||
/* KdbxFile f = new KdbxFile(pwDatabase);
|
||||
f.m_format = KdbxFormat.PlainXml;
|
||||
|
||||
XmlTextWriter xtw = null;
|
||||
try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); }
|
||||
catch(Exception) { Debug.Assert(false); return false; }
|
||||
if(xtw == null) { Debug.Assert(false); return false; }
|
||||
|
||||
f.m_xmlWriter = xtw;
|
||||
|
||||
xtw.Formatting = Formatting.Indented;
|
||||
xtw.IndentChar = '\t';
|
||||
xtw.Indentation = 1;
|
||||
|
||||
xtw.WriteStartDocument(true);
|
||||
xtw.WriteStartElement(ElemRoot);
|
||||
|
||||
foreach(PwEntry pe in vEntries)
|
||||
f.WriteEntry(pe, false);
|
||||
|
||||
xtw.WriteEndElement();
|
||||
xtw.WriteEndDocument();
|
||||
|
||||
xtw.Flush();
|
||||
xtw.Close();
|
||||
return true; */
|
||||
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
|
||||
|
||||
PwGroup pg = pd.RootGroup;
|
||||
if (pg == null) { Debug.Assert(false); return false; }
|
||||
PwGroup pg = new PwGroup(true, true);
|
||||
|
||||
foreach (PwEntry pe in vEntries)
|
||||
{
|
||||
PwUuid pu = pe.CustomIconUuid;
|
||||
if (!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0))
|
||||
{
|
||||
int i = -1;
|
||||
if (pdContext != null) i = pdContext.GetCustomIconIndex(pu);
|
||||
if (i >= 0)
|
||||
{
|
||||
PwCustomIcon ci = pdContext.CustomIcons[i];
|
||||
pd.CustomIcons.Add(ci);
|
||||
}
|
||||
else { Debug.Assert(pdContext == null); }
|
||||
}
|
||||
|
||||
PwEntry peCopy = pe.CloneDeep();
|
||||
pg.AddEntry(peCopy, true);
|
||||
}
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
|
||||
WriteGroup(msOutput, pdContext, pg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -67,47 +67,6 @@ namespace KeePassLib.Serialization
|
||||
/// </summary>
|
||||
public sealed partial class KdbxFile
|
||||
{
|
||||
private class ColorTranslator
|
||||
{
|
||||
public static Color FromHtml(String colorString)
|
||||
{
|
||||
Color color;
|
||||
|
||||
if (colorString.StartsWith("#"))
|
||||
{
|
||||
colorString = colorString.Substring(1);
|
||||
}
|
||||
if (colorString.EndsWith(";"))
|
||||
{
|
||||
colorString = colorString.Substring(0, colorString.Length - 1);
|
||||
}
|
||||
|
||||
int red, green, blue;
|
||||
switch (colorString.Length)
|
||||
{
|
||||
case 6:
|
||||
red = int.Parse(colorString.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
green = int.Parse(colorString.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
blue = int.Parse(colorString.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
color = Color.FromArgb(red, green, blue);
|
||||
break;
|
||||
case 3:
|
||||
red = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
|
||||
green = int.Parse(colorString.Substring(1, 1), System.Globalization.NumberStyles.HexNumber);
|
||||
blue = int.Parse(colorString.Substring(2, 1), System.Globalization.NumberStyles.HexNumber);
|
||||
color = Color.FromArgb(red, green, blue);
|
||||
break;
|
||||
case 1:
|
||||
red = green = blue = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
|
||||
color = Color.FromArgb(red, green, blue);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Invalid color: " + colorString);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// File identifier, first 32-bit value.
|
||||
/// </summary>
|
||||
@@ -119,16 +78,17 @@ namespace KeePassLib.Serialization
|
||||
internal const uint FileSignature2 = 0xB54BFB67;
|
||||
|
||||
/// <summary>
|
||||
/// File version of files saved by the current <c>KdbxFile</c> class.
|
||||
/// Maximum supported version of database files.
|
||||
/// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00,
|
||||
/// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01.
|
||||
/// The first 2 bytes are critical (i.e. loading will fail, if the
|
||||
/// file version is too high), the last 2 bytes are informational.
|
||||
/// </summary>
|
||||
private const uint FileVersion32 = 0x00040000;
|
||||
private const uint FileVersion32 = 0x00040001;
|
||||
|
||||
public const uint FileVersion32_4 = 0x00040000; // First of 4.x series
|
||||
public const uint FileVersion32_3 = 0x00030001; // Old format 3.1
|
||||
public const uint FileVersion32_4_1 = 0x00040001; // 4.1
|
||||
public const uint FileVersion32_4 = 0x00040000; // 4.0
|
||||
public const uint FileVersion32_3_1 = 0x00030001; // 3.1
|
||||
|
||||
private const uint FileVersionCriticalMask = 0xFFFF0000;
|
||||
|
||||
@@ -143,7 +103,7 @@ namespace KeePassLib.Serialization
|
||||
private const string ElemMeta = "Meta";
|
||||
private const string ElemRoot = "Root";
|
||||
private const string ElemGroup = "Group";
|
||||
private const string ElemEntry = "Entry";
|
||||
internal const string ElemEntry = "Entry";
|
||||
|
||||
private const string ElemGenerator = "Generator";
|
||||
private const string ElemHeaderHash = "HeaderHash";
|
||||
@@ -188,12 +148,13 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private const string ElemName = "Name";
|
||||
private const string ElemNotes = "Notes";
|
||||
private const string ElemUuid = "UUID";
|
||||
internal const string ElemUuid = "UUID";
|
||||
private const string ElemIcon = "IconID";
|
||||
private const string ElemCustomIconID = "CustomIconUUID";
|
||||
private const string ElemFgColor = "ForegroundColor";
|
||||
private const string ElemBgColor = "BackgroundColor";
|
||||
private const string ElemOverrideUrl = "OverrideURL";
|
||||
private const string ElemQualityCheck = "QualityCheck";
|
||||
private const string ElemTimes = "Times";
|
||||
private const string ElemTags = "Tags";
|
||||
|
||||
@@ -205,6 +166,8 @@ namespace KeePassLib.Serialization
|
||||
private const string ElemUsageCount = "UsageCount";
|
||||
private const string ElemLocationChanged = "LocationChanged";
|
||||
|
||||
private const string ElemPreviousParentGroup = "PreviousParentGroup";
|
||||
|
||||
private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence";
|
||||
private const string ElemEnableAutoType = "EnableAutoType";
|
||||
private const string ElemEnableSearching = "EnableSearching";
|
||||
@@ -261,7 +224,7 @@ namespace KeePassLib.Serialization
|
||||
private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant;
|
||||
private byte[] m_pbInnerRandomStreamKey = null;
|
||||
|
||||
private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet();
|
||||
private ProtectedBinarySet m_pbsBinaries = null;
|
||||
|
||||
private byte[] m_pbHashOfHeader = null;
|
||||
private byte[] m_pbHashOfFileOnDisk = null;
|
||||
@@ -271,7 +234,7 @@ namespace KeePassLib.Serialization
|
||||
private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs
|
||||
private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs
|
||||
private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec;
|
||||
private static bool m_bLocalizedNames = false;
|
||||
private static bool g_bLocalizedNames = false;
|
||||
|
||||
private enum KdbxHeaderFieldID : byte
|
||||
{
|
||||
@@ -345,7 +308,7 @@ namespace KeePassLib.Serialization
|
||||
public KdbxFile(PwDatabase pwDataStore)
|
||||
{
|
||||
Debug.Assert(pwDataStore != null);
|
||||
if(pwDataStore == null) throw new ArgumentNullException("pwDataStore");
|
||||
if (pwDataStore == null) throw new ArgumentNullException("pwDataStore");
|
||||
|
||||
m_pwDatabase = pwDataStore;
|
||||
}
|
||||
@@ -356,57 +319,92 @@ namespace KeePassLib.Serialization
|
||||
public static void DetermineLanguageId()
|
||||
{
|
||||
// Test if localized names should be used. If localized names are used,
|
||||
// the m_bLocalizedNames value must be set to true. By default, localized
|
||||
// names should be used! (Otherwise characters could be corrupted
|
||||
// the g_bLocalizedNames value must be set to true. By default, localized
|
||||
// names should be used (otherwise characters could be corrupted
|
||||
// because of different code pages).
|
||||
unchecked
|
||||
{
|
||||
uint uTest = 0;
|
||||
foreach(char ch in PwDatabase.LocalizedAppName)
|
||||
foreach (char ch in PwDatabase.LocalizedAppName)
|
||||
uTest = uTest * 5 + ch;
|
||||
|
||||
m_bLocalizedNames = (uTest != NeutralLanguageID);
|
||||
g_bLocalizedNames = (uTest != NeutralLanguageID);
|
||||
}
|
||||
}
|
||||
|
||||
private uint GetMinKdbxVersion()
|
||||
private uint GetMinKdbxVersion()
|
||||
{
|
||||
if (m_uForceVersion != 0) return m_uForceVersion;
|
||||
|
||||
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
|
||||
|
||||
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
|
||||
|
||||
return Math.Max(minRequiredVersion, GetMinKdbxVersionOrig());
|
||||
}
|
||||
|
||||
private uint GetMinKdbxVersionOrig()
|
||||
{
|
||||
if(m_uForceVersion != 0) return m_uForceVersion;
|
||||
if (m_uForceVersion != 0) return m_uForceVersion;
|
||||
|
||||
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
|
||||
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
|
||||
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
|
||||
|
||||
uint uMin = 0;
|
||||
|
||||
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
|
||||
|
||||
|
||||
AesKdf kdfAes = new AesKdf();
|
||||
if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid))
|
||||
return Math.Max(FileVersion32, minRequiredVersion);
|
||||
|
||||
if(m_pwDatabase.PublicCustomData.Count > 0)
|
||||
return Math.Max(FileVersion32, minRequiredVersion);
|
||||
|
||||
|
||||
|
||||
bool bCustomData = false;
|
||||
GroupHandler gh = delegate(PwGroup pg)
|
||||
GroupHandler gh = delegate (PwGroup pg)
|
||||
{
|
||||
if(pg == null) { Debug.Assert(false); return true; }
|
||||
if(pg.CustomData.Count > 0) { bCustomData = true; return false; }
|
||||
if (pg == null) { Debug.Assert(false); return true; }
|
||||
|
||||
if (pg.Tags.Count != 0)
|
||||
uMin = Math.Max(uMin, FileVersion32_4_1);
|
||||
if (pg.CustomData.Count != 0)
|
||||
uMin = Math.Max(uMin, FileVersion32_4);
|
||||
|
||||
return true;
|
||||
};
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
|
||||
EntryHandler eh = delegate (PwEntry pe)
|
||||
{
|
||||
if(pe == null) { Debug.Assert(false); return true; }
|
||||
if(pe.CustomData.Count > 0) { bCustomData = true; return false; }
|
||||
if (pe == null) { Debug.Assert(false); return true; }
|
||||
|
||||
if (!pe.QualityCheck)
|
||||
uMin = Math.Max(uMin, FileVersion32_4_1);
|
||||
if (pe.CustomData.Count != 0)
|
||||
uMin = Math.Max(uMin, FileVersion32_4);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gh(m_pwDatabase.RootGroup);
|
||||
m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
|
||||
if(bCustomData)
|
||||
return Math.Max(FileVersion32, minRequiredVersion);
|
||||
|
||||
return Math.Max(FileVersion32_3, minRequiredVersion); ; // KDBX 3.1 is sufficient
|
||||
if (uMin >= FileVersion32_4_1) return uMin; // All below is <= 4.1
|
||||
|
||||
foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
|
||||
{
|
||||
if ((ci.Name.Length != 0) || ci.LastModificationTime.HasValue)
|
||||
return FileVersion32_4_1;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in m_pwDatabase.CustomData)
|
||||
{
|
||||
DateTime? odt = m_pwDatabase.CustomData.GetLastModificationTime(kvp.Key);
|
||||
if (odt.HasValue) return FileVersion32_4_1;
|
||||
}
|
||||
|
||||
if (uMin >= FileVersion32_4) return uMin; // All below is <= 4
|
||||
|
||||
if (m_pwDatabase.DataCipherUuid.Equals(ChaCha20Engine.ChaCha20Uuid))
|
||||
return FileVersion32_4;
|
||||
|
||||
AesKdf kdfAes = new AesKdf();
|
||||
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfAes.Uuid))
|
||||
return FileVersion32_4;
|
||||
|
||||
if (m_pwDatabase.PublicCustomData.Count != 0)
|
||||
return FileVersion32_4;
|
||||
|
||||
return FileVersion32_3_1; // KDBX 3.1 is sufficient
|
||||
}
|
||||
|
||||
private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey,
|
||||
@@ -416,12 +414,12 @@ namespace KeePassLib.Serialization
|
||||
try
|
||||
{
|
||||
Debug.Assert(m_pbMasterSeed != null);
|
||||
if(m_pbMasterSeed == null)
|
||||
if (m_pbMasterSeed == null)
|
||||
throw new ArgumentNullException("m_pbMasterSeed");
|
||||
Debug.Assert(m_pbMasterSeed.Length == 32);
|
||||
if(m_pbMasterSeed.Length != 32)
|
||||
if (m_pbMasterSeed.Length != 32)
|
||||
throw new FormatException(KLRes.MasterSeedLengthInvalid);
|
||||
|
||||
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
|
||||
|
||||
Debug.Assert(m_pwDatabase != null);
|
||||
Debug.Assert(m_pwDatabase.MasterKey != null);
|
||||
@@ -431,10 +429,10 @@ namespace KeePassLib.Serialization
|
||||
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
|
||||
|
||||
Debug.Assert(pbinUser != null);
|
||||
if(pbinUser == null)
|
||||
if (pbinUser == null)
|
||||
throw new SecurityException(KLRes.InvalidCompositeKey);
|
||||
byte[] pUserKey32 = pbinUser.ReadData();
|
||||
if((pUserKey32 == null) || (pUserKey32.Length != 32))
|
||||
if ((pUserKey32 == null) || (pUserKey32.Length != 32))
|
||||
throw new SecurityException(KLRes.InvalidCompositeKey);
|
||||
Array.Copy(pUserKey32, 0, pbCmp, 32, 32);
|
||||
MemUtil.ZeroByteArray(pUserKey32);
|
||||
@@ -442,7 +440,7 @@ namespace KeePassLib.Serialization
|
||||
pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey);
|
||||
|
||||
pbCmp[64] = 1;
|
||||
using(SHA512Managed h = new SHA512Managed())
|
||||
using (SHA512Managed h = new SHA512Managed())
|
||||
{
|
||||
pbHmacKey64 = h.ComputeHash(pbCmp);
|
||||
}
|
||||
@@ -454,19 +452,19 @@ namespace KeePassLib.Serialization
|
||||
{
|
||||
PwUuid pu = m_pwDatabase.DataCipherUuid;
|
||||
ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu);
|
||||
if(iCipher == null) // CryptographicExceptions are translated to "file corrupted"
|
||||
if (iCipher == null) // CryptographicExceptions are translated to "file corrupted"
|
||||
throw new Exception(KLRes.FileUnknownCipher +
|
||||
MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq +
|
||||
MessageService.NewParagraph + "UUID: " + pu.ToHexString() + ".");
|
||||
|
||||
ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2);
|
||||
if(iCipher2 != null)
|
||||
if (iCipher2 != null)
|
||||
{
|
||||
cbEncKey = iCipher2.KeyLength;
|
||||
if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
|
||||
if (cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
|
||||
|
||||
cbEncIV = iCipher2.IVLength;
|
||||
if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
|
||||
if (cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -481,13 +479,13 @@ namespace KeePassLib.Serialization
|
||||
byte[] pbKey, int cbIV, bool bEncrypt)
|
||||
{
|
||||
byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
|
||||
if(pbIV.Length != cbIV)
|
||||
if (pbIV.Length != cbIV)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new Exception(KLRes.FileCorrupted);
|
||||
}
|
||||
|
||||
if(bEncrypt)
|
||||
if (bEncrypt)
|
||||
return iCipher.EncryptStream(s, pbKey, pbIV);
|
||||
return iCipher.DecryptStream(s, pbKey, pbIV);
|
||||
}
|
||||
@@ -497,7 +495,7 @@ namespace KeePassLib.Serialization
|
||||
byte[] pbHeaderHmac;
|
||||
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(
|
||||
pbKey, ulong.MaxValue);
|
||||
using(HMACSHA256 h = new HMACSHA256(pbBlockKey))
|
||||
using (HMACSHA256 h = new HMACSHA256(pbBlockKey))
|
||||
{
|
||||
pbHeaderHmac = h.ComputeHash(pbHeader);
|
||||
}
|
||||
@@ -508,7 +506,7 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void CloseStreams(List<Stream> lStreams)
|
||||
{
|
||||
if(lStreams == null) { Debug.Assert(false); return; }
|
||||
if (lStreams == null) { Debug.Assert(false); return; }
|
||||
|
||||
// Typically, closing a stream also closes its base
|
||||
// stream; however, there may be streams that do not
|
||||
@@ -516,14 +514,14 @@ namespace KeePassLib.Serialization
|
||||
// we close all streams manually, from the innermost
|
||||
// to the outermost
|
||||
|
||||
for(int i = lStreams.Count - 1; i >= 0; --i)
|
||||
for (int i = lStreams.Count - 1; i >= 0; --i)
|
||||
{
|
||||
// Check for duplicates
|
||||
Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) &&
|
||||
(lStreams.LastIndexOf(lStreams[i]) == i));
|
||||
|
||||
try { lStreams[i].Close(); }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
}
|
||||
|
||||
// Do not clear the list
|
||||
@@ -531,18 +529,18 @@ namespace KeePassLib.Serialization
|
||||
|
||||
private void CleanUpInnerRandomStream()
|
||||
{
|
||||
if(m_randomStream != null) m_randomStream.Dispose();
|
||||
if (m_randomStream != null) m_randomStream.Dispose();
|
||||
|
||||
if(m_pbInnerRandomStreamKey != null)
|
||||
if (m_pbInnerRandomStreamKey != null)
|
||||
MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey);
|
||||
}
|
||||
|
||||
private static void SaveBinary(string strName, ProtectedBinary pb,
|
||||
string strSaveDir)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); return; }
|
||||
if (pb == null) { Debug.Assert(false); return; }
|
||||
|
||||
if(string.IsNullOrEmpty(strName)) strName = "File.bin";
|
||||
strName = UrlUtil.GetSafeFileName(strName);
|
||||
|
||||
string strPath;
|
||||
int iTry = 1;
|
||||
@@ -550,31 +548,23 @@ namespace KeePassLib.Serialization
|
||||
{
|
||||
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
|
||||
|
||||
string strExt = UrlUtil.GetExtension(strName);
|
||||
string strDesc = UrlUtil.StripExtension(strName);
|
||||
string strExt = UrlUtil.GetExtension(strName);
|
||||
|
||||
strPath += strDesc;
|
||||
if(iTry > 1)
|
||||
if (iTry > 1)
|
||||
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
|
||||
")";
|
||||
|
||||
if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
|
||||
if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
|
||||
|
||||
++iTry;
|
||||
}
|
||||
while(File.Exists(strPath));
|
||||
while (File.Exists(strPath));
|
||||
|
||||
#if !KeePassLibSD
|
||||
byte[] pbData = pb.ReadData();
|
||||
File.WriteAllBytes(strPath, pbData);
|
||||
MemUtil.ZeroByteArray(pbData);
|
||||
#else
|
||||
FileStream fs = new FileStream(strPath, FileMode.Create,
|
||||
FileAccess.Write, FileShare.None);
|
||||
byte[] pbData = pb.ReadData();
|
||||
fs.Write(pbData, 0, pbData.Length);
|
||||
fs.Close();
|
||||
#endif
|
||||
try { File.WriteAllBytes(strPath, pbData); }
|
||||
finally { if (pb.IsProtected) MemUtil.ZeroByteArray(pbData); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,5 +786,28 @@ namespace KeePassLib.Utility
|
||||
|
||||
yield break;
|
||||
}
|
||||
internal static bool ListsEqual<T>(List<T> a, List<T> b)
|
||||
where T : class, IEquatable<T>
|
||||
{
|
||||
if (object.ReferenceEquals(a, b)) return true;
|
||||
if ((a == null) || (b == null)) return false;
|
||||
|
||||
int n = a.Count;
|
||||
if (n != b.Count) return false;
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
T tA = a[i], tB = b[i];
|
||||
|
||||
if (tA == null)
|
||||
{
|
||||
if (tB != null) return false;
|
||||
}
|
||||
else if (tB == null) return false;
|
||||
else if (!tA.Equals(tB)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +429,7 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/
|
||||
if ((strFilePath != null) && (strFilePath.Length > 0))
|
||||
str += strFilePath + MessageService.NewParagraph;
|
||||
|
||||
str += KLRes.FileSaveFailed;
|
||||
str += KLRes.FileSaveFailed2;
|
||||
|
||||
if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0))
|
||||
str += MessageService.NewParagraph + ex.Message;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,8 +20,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using KeePassLib.Native;
|
||||
|
||||
@@ -33,9 +35,7 @@ namespace KeePassLib.Utility
|
||||
/// </summary>
|
||||
public static class UrlUtil
|
||||
{
|
||||
private static readonly char[] m_vDirSeps = new char[] {
|
||||
'\\', '/', UrlUtil.LocalDirSepChar };
|
||||
private static readonly char[] m_vPathTrimCharsWs = new char[] {
|
||||
private static readonly char[] g_vPathTrimCharsWs = new char[] {
|
||||
'\"', ' ', '\t', '\r', '\n' };
|
||||
|
||||
public static char LocalDirSepChar
|
||||
@@ -43,6 +43,32 @@ namespace KeePassLib.Utility
|
||||
get { return Path.DirectorySeparatorChar; }
|
||||
}
|
||||
|
||||
private static char[] g_vDirSepChars = null;
|
||||
private static char[] DirSepChars
|
||||
{
|
||||
get
|
||||
{
|
||||
if (g_vDirSepChars == null)
|
||||
{
|
||||
List<char> l = new List<char>();
|
||||
l.Add('/'); // For URLs, also on Windows
|
||||
|
||||
// On Unix-like systems, '\\' is not a separator
|
||||
if (!NativeLib.IsUnix()) l.Add('\\');
|
||||
|
||||
if (!l.Contains(UrlUtil.LocalDirSepChar))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
l.Add(UrlUtil.LocalDirSepChar);
|
||||
}
|
||||
|
||||
g_vDirSepChars = l.ToArray();
|
||||
}
|
||||
|
||||
return g_vDirSepChars;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the directory (path) of a file name. The returned string may be
|
||||
/// terminated by a directory separator character. Example:
|
||||
@@ -63,16 +89,16 @@ namespace KeePassLib.Utility
|
||||
bool bEnsureValidDirSpec)
|
||||
{
|
||||
Debug.Assert(strFile != null);
|
||||
if(strFile == null) throw new ArgumentNullException("strFile");
|
||||
if (strFile == null) throw new ArgumentNullException("strFile");
|
||||
|
||||
int nLastSep = strFile.LastIndexOfAny(m_vDirSeps);
|
||||
if(nLastSep < 0) return string.Empty; // No directory
|
||||
int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars);
|
||||
if (nLastSep < 0) return string.Empty; // No directory
|
||||
|
||||
if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
|
||||
if (bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
|
||||
(strFile[2] == '\\')) // Length >= 3 and Windows root directory
|
||||
bAppendTerminatingChar = true;
|
||||
|
||||
if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep);
|
||||
if (!bAppendTerminatingChar) return strFile.Substring(0, nLastSep);
|
||||
return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep),
|
||||
(strFile[nLastSep] == '/'));
|
||||
}
|
||||
@@ -87,12 +113,12 @@ namespace KeePassLib.Utility
|
||||
/// an empty string (<c>""</c>) if the input parameter is <c>null</c>.</returns>
|
||||
public static string GetFileName(string strPath)
|
||||
{
|
||||
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
int nLastSep = strPath.LastIndexOfAny(m_vDirSeps);
|
||||
int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
|
||||
|
||||
if(nLastSep < 0) return strPath;
|
||||
if(nLastSep >= (strPath.Length - 1)) return string.Empty;
|
||||
if (nLastSep < 0) return strPath;
|
||||
if (nLastSep >= (strPath.Length - 1)) return string.Empty;
|
||||
|
||||
return strPath.Substring(nLastSep + 1);
|
||||
}
|
||||
@@ -104,12 +130,12 @@ namespace KeePassLib.Utility
|
||||
/// <returns>File name without extension.</returns>
|
||||
public static string StripExtension(string strPath)
|
||||
{
|
||||
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
|
||||
int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
|
||||
int nLastExtDot = strPath.LastIndexOf('.');
|
||||
|
||||
if(nLastExtDot <= nLastDirSep) return strPath;
|
||||
if (nLastExtDot <= nLastDirSep) return strPath;
|
||||
|
||||
return strPath.Substring(0, nLastExtDot);
|
||||
}
|
||||
@@ -121,13 +147,13 @@ namespace KeePassLib.Utility
|
||||
/// <returns>Extension without prepending dot.</returns>
|
||||
public static string GetExtension(string strPath)
|
||||
{
|
||||
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
|
||||
int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
|
||||
int nLastExtDot = strPath.LastIndexOf('.');
|
||||
|
||||
if(nLastExtDot <= nLastDirSep) return string.Empty;
|
||||
if(nLastExtDot == (strPath.Length - 1)) return string.Empty;
|
||||
if (nLastExtDot <= nLastDirSep) return string.Empty;
|
||||
if (nLastExtDot == (strPath.Length - 1)) return string.Empty;
|
||||
|
||||
return strPath.Substring(nLastExtDot + 1);
|
||||
}
|
||||
@@ -142,19 +168,16 @@ namespace KeePassLib.Utility
|
||||
/// <returns>Path having a directory separator as last character.</returns>
|
||||
public static string EnsureTerminatingSeparator(string strPath, bool bUrl)
|
||||
{
|
||||
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
int nLength = strPath.Length;
|
||||
if(nLength <= 0) return string.Empty;
|
||||
if (nLength <= 0) return string.Empty;
|
||||
|
||||
char chLast = strPath[nLength - 1];
|
||||
if (Array.IndexOf<char>(UrlUtil.DirSepChars, chLast) >= 0)
|
||||
return strPath;
|
||||
|
||||
for(int i = 0; i < m_vDirSeps.Length; ++i)
|
||||
{
|
||||
if(chLast == m_vDirSeps[i]) return strPath;
|
||||
}
|
||||
|
||||
if(bUrl) return (strPath + '/');
|
||||
if (bUrl) return (strPath + '/');
|
||||
return (strPath + UrlUtil.LocalDirSepChar);
|
||||
}
|
||||
|
||||
@@ -216,55 +239,78 @@ namespace KeePassLib.Utility
|
||||
return false;
|
||||
} */
|
||||
|
||||
internal static int IndexOfSecondEnclQuote(string str)
|
||||
{
|
||||
if (str == null) { Debug.Assert(false); return -1; }
|
||||
if (str.Length <= 1) return -1;
|
||||
if (str[0] != '\"') { Debug.Assert(false); return -1; }
|
||||
|
||||
if (NativeLib.IsUnix())
|
||||
{
|
||||
// Find non-escaped quote
|
||||
string strFlt = str.Replace("\\\\", new string(
|
||||
StrUtil.GetUnusedChar(str + "\\\""), 2)); // Same length
|
||||
Match m = Regex.Match(strFlt, "[^\\\\]\\u0022");
|
||||
int i = (((m != null) && m.Success) ? m.Index : -1);
|
||||
return ((i >= 0) ? (i + 1) : -1); // Index of quote
|
||||
}
|
||||
|
||||
// Windows does not allow quotes in folder/file names
|
||||
return str.IndexOf('\"', 1);
|
||||
}
|
||||
|
||||
public static string GetQuotedAppPath(string strPath)
|
||||
{
|
||||
if(strPath == null) { Debug.Assert(false); return string.Empty; }
|
||||
|
||||
// int nFirst = strPath.IndexOf('\"');
|
||||
// int nSecond = strPath.IndexOf('\"', nFirst + 1);
|
||||
// if((nFirst >= 0) && (nSecond >= 0))
|
||||
// return strPath.Substring(nFirst + 1, nSecond - nFirst - 1);
|
||||
// return strPath;
|
||||
if (strPath == null) { Debug.Assert(false); return string.Empty; }
|
||||
|
||||
string str = strPath.Trim();
|
||||
if(str.Length <= 1) return str;
|
||||
if(str[0] != '\"') return str;
|
||||
if (str.Length <= 1) return str;
|
||||
if (str[0] != '\"') return str;
|
||||
|
||||
int iSecond = str.IndexOf('\"', 1);
|
||||
if(iSecond <= 0) return str;
|
||||
int iSecond = IndexOfSecondEnclQuote(str);
|
||||
if (iSecond <= 0) return str;
|
||||
|
||||
return str.Substring(1, iSecond - 1);
|
||||
}
|
||||
|
||||
public static string FileUrlToPath(string strUrl)
|
||||
{
|
||||
Debug.Assert(strUrl != null);
|
||||
if(strUrl == null) throw new ArgumentNullException("strUrl");
|
||||
if (strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); }
|
||||
if (strUrl.Length == 0) { Debug.Assert(false); return string.Empty; }
|
||||
|
||||
string str = strUrl;
|
||||
if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp))
|
||||
str = str.Substring(8, str.Length - 8);
|
||||
if (!strUrl.StartsWith(Uri.UriSchemeFile + ":", StrUtil.CaseIgnoreCmp))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return strUrl;
|
||||
}
|
||||
|
||||
str = str.Replace('/', UrlUtil.LocalDirSepChar);
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(strUrl);
|
||||
string str = uri.LocalPath;
|
||||
if (!string.IsNullOrEmpty(str)) return str;
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
return str;
|
||||
Debug.Assert(false);
|
||||
return strUrl;
|
||||
}
|
||||
|
||||
public static bool UnhideFile(string strFile)
|
||||
{
|
||||
#if (KeePassLibSD || KeePassRT)
|
||||
#if KeePassLibSD
|
||||
return false;
|
||||
#else
|
||||
if(strFile == null) throw new ArgumentNullException("strFile");
|
||||
if (strFile == null) throw new ArgumentNullException("strFile");
|
||||
|
||||
try
|
||||
{
|
||||
FileAttributes fa = File.GetAttributes(strFile);
|
||||
if((long)(fa & FileAttributes.Hidden) == 0) return false;
|
||||
if ((long)(fa & FileAttributes.Hidden) == 0) return false;
|
||||
|
||||
return HideFile(strFile, false);
|
||||
}
|
||||
catch(Exception) { }
|
||||
catch (Exception) { }
|
||||
|
||||
return false;
|
||||
#endif
|
||||
@@ -272,26 +318,26 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static bool HideFile(string strFile, bool bHide)
|
||||
{
|
||||
#if (KeePassLibSD || KeePassRT)
|
||||
#if KeePassLibSD
|
||||
return false;
|
||||
#else
|
||||
if(strFile == null) throw new ArgumentNullException("strFile");
|
||||
if (strFile == null) throw new ArgumentNullException("strFile");
|
||||
|
||||
try
|
||||
{
|
||||
FileAttributes fa = File.GetAttributes(strFile);
|
||||
|
||||
if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden);
|
||||
if (bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden);
|
||||
else // Unhide
|
||||
{
|
||||
fa &= ~FileAttributes.Hidden;
|
||||
if((long)fa == 0) fa = FileAttributes.Normal;
|
||||
if ((long)fa == 0) fa = FileAttributes.Normal;
|
||||
}
|
||||
|
||||
File.SetAttributes(strFile, fa);
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { }
|
||||
catch (Exception) { }
|
||||
|
||||
return false;
|
||||
#endif
|
||||
@@ -299,48 +345,47 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static string MakeRelativePath(string strBaseFile, string strTargetFile)
|
||||
{
|
||||
if(strBaseFile == null) throw new ArgumentNullException("strBasePath");
|
||||
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath");
|
||||
if(strBaseFile.Length == 0) return strTargetFile;
|
||||
if(strTargetFile.Length == 0) return string.Empty;
|
||||
if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
|
||||
if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
|
||||
if (strBaseFile.Length == 0) return strTargetFile;
|
||||
if (strTargetFile.Length == 0) return string.Empty;
|
||||
|
||||
// Test whether on different Windows drives
|
||||
if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3))
|
||||
if ((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3))
|
||||
{
|
||||
if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') &&
|
||||
if ((strBaseFile[1] == ':') && (strTargetFile[1] == ':') &&
|
||||
(strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') &&
|
||||
(strBaseFile[0] != strTargetFile[0]))
|
||||
return strTargetFile;
|
||||
}
|
||||
|
||||
#if (!KeePassLibSD && !KeePassUAP)
|
||||
if(NativeLib.IsUnix())
|
||||
#endif
|
||||
if (NativeLib.IsUnix())
|
||||
{
|
||||
|
||||
#endif
|
||||
bool bBaseUnc = IsUncPath(strBaseFile);
|
||||
bool bTargetUnc = IsUncPath(strTargetFile);
|
||||
if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
|
||||
if ((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
|
||||
return strTargetFile;
|
||||
|
||||
string strBase = GetShortestAbsolutePath(strBaseFile);
|
||||
string strTarget = GetShortestAbsolutePath(strTargetFile);
|
||||
string[] vBase = strBase.Split(m_vDirSeps);
|
||||
string[] vTarget = strTarget.Split(m_vDirSeps);
|
||||
string[] vBase = strBase.Split(UrlUtil.DirSepChars);
|
||||
string[] vTarget = strTarget.Split(UrlUtil.DirSepChars);
|
||||
|
||||
int i = 0;
|
||||
while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
|
||||
while ((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
|
||||
(vBase[i] == vTarget[i])) { ++i; }
|
||||
|
||||
StringBuilder sbRel = new StringBuilder();
|
||||
for(int j = i; j < (vBase.Length - 1); ++j)
|
||||
for (int j = i; j < (vBase.Length - 1); ++j)
|
||||
{
|
||||
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
|
||||
if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
|
||||
sbRel.Append("..");
|
||||
}
|
||||
for(int k = i; k < vTarget.Length; ++k)
|
||||
for (int k = i; k < vTarget.Length; ++k)
|
||||
{
|
||||
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
|
||||
if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
|
||||
sbRel.Append(vTarget[k]);
|
||||
}
|
||||
|
||||
@@ -352,28 +397,28 @@ namespace KeePassLib.Utility
|
||||
{
|
||||
const int nMaxPath = NativeMethods.MAX_PATH * 2;
|
||||
StringBuilder sb = new StringBuilder(nMaxPath + 2);
|
||||
if(NativeMethods.PathRelativePathTo(sb, strBaseFile, 0,
|
||||
strTargetFile, 0) == false)
|
||||
if (!NativeMethods.PathRelativePathTo(sb, strBaseFile, 0,
|
||||
strTargetFile, 0))
|
||||
return strTargetFile;
|
||||
|
||||
string str = sb.ToString();
|
||||
while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2);
|
||||
while (str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2);
|
||||
|
||||
return str;
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
return strTargetFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string MakeAbsolutePath(string strBaseFile, string strTargetFile)
|
||||
{
|
||||
if(strBaseFile == null) throw new ArgumentNullException("strBasePath");
|
||||
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath");
|
||||
if(strBaseFile.Length == 0) return strTargetFile;
|
||||
if(strTargetFile.Length == 0) return string.Empty;
|
||||
if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
|
||||
if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
|
||||
if (strBaseFile.Length == 0) return strTargetFile;
|
||||
if (strTargetFile.Length == 0) return string.Empty;
|
||||
|
||||
if(IsAbsolutePath(strTargetFile)) return strTargetFile;
|
||||
if (IsAbsolutePath(strTargetFile)) return strTargetFile;
|
||||
|
||||
string strBaseDir = GetFileDirectory(strBaseFile, true, false);
|
||||
return GetShortestAbsolutePath(strBaseDir + strTargetFile);
|
||||
@@ -381,55 +426,56 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static bool IsAbsolutePath(string strPath)
|
||||
{
|
||||
if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
if(strPath.Length == 0) return false;
|
||||
if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
if (strPath.Length == 0) return false;
|
||||
|
||||
if(IsUncPath(strPath)) return true;
|
||||
if (IsUncPath(strPath)) return true;
|
||||
|
||||
try { return Path.IsPathRooted(strPath); }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetShortestAbsolutePath(string strPath)
|
||||
{
|
||||
if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
if(strPath.Length == 0) return string.Empty;
|
||||
if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
if (strPath.Length == 0) return string.Empty;
|
||||
|
||||
// Path.GetFullPath is incompatible with UNC paths traversing over
|
||||
// different server shares (which are created by PathRelativePathTo);
|
||||
// we need to build the absolute path on our own...
|
||||
if(IsUncPath(strPath))
|
||||
if (IsUncPath(strPath))
|
||||
{
|
||||
char chSep = strPath[0];
|
||||
Debug.Assert(Array.IndexOf<char>(m_vDirSeps, chSep) >= 0);
|
||||
char[] vSep = ((chSep == '/') ? (new char[] { '/' }) :
|
||||
(new char[] { '\\', '/' }));
|
||||
|
||||
List<string> l = new List<string>();
|
||||
#if !KeePassLibSD
|
||||
string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None);
|
||||
string[] v = strPath.Split(vSep, StringSplitOptions.None);
|
||||
#else
|
||||
string[] v = strPath.Split(m_vDirSeps);
|
||||
string[] v = strPath.Split(vSep);
|
||||
#endif
|
||||
Debug.Assert((v.Length >= 3) && (v[0].Length == 0) &&
|
||||
(v[1].Length == 0));
|
||||
|
||||
foreach(string strPart in v)
|
||||
foreach (string strPart in v)
|
||||
{
|
||||
if(strPart.Equals(".")) continue;
|
||||
else if(strPart.Equals(".."))
|
||||
if (strPart.Equals(".")) continue;
|
||||
else if (strPart.Equals(".."))
|
||||
{
|
||||
if(l.Count > 0) l.RemoveAt(l.Count - 1);
|
||||
if (l.Count > 0) l.RemoveAt(l.Count - 1);
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
else l.Add(strPart); // Do not ignore zero length parts
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i = 0; i < l.Count; ++i)
|
||||
for (int i = 0; i < l.Count; ++i)
|
||||
{
|
||||
// Don't test length of sb, might be 0 due to initial UNC seps
|
||||
if(i > 0) sb.Append(chSep);
|
||||
if (i > 0) sb.Append(chSep);
|
||||
|
||||
sb.Append(l[i]);
|
||||
}
|
||||
@@ -438,20 +484,11 @@ namespace KeePassLib.Utility
|
||||
}
|
||||
|
||||
string str;
|
||||
try
|
||||
{
|
||||
#if KeePassRT
|
||||
var dirT = Windows.Storage.StorageFolder.GetFolderFromPathAsync(
|
||||
strPath).AwaitEx();
|
||||
str = dirT.Path;
|
||||
#else
|
||||
str = Path.GetFullPath(strPath);
|
||||
#endif
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); return strPath; }
|
||||
try { str = Path.GetFullPath(strPath); }
|
||||
catch (Exception) { Debug.Assert(false); return strPath; }
|
||||
|
||||
Debug.Assert(str.IndexOf("\\..\\") < 0);
|
||||
foreach(char ch in m_vDirSeps)
|
||||
Debug.Assert((str.IndexOf("\\..\\") < 0) || NativeLib.IsUnix());
|
||||
foreach (char ch in UrlUtil.DirSepChars)
|
||||
{
|
||||
string strSep = new string(ch, 1);
|
||||
str = str.Replace(strSep + "." + strSep, strSep);
|
||||
@@ -462,17 +499,17 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static int GetUrlLength(string strText, int nOffset)
|
||||
{
|
||||
if(strText == null) throw new ArgumentNullException("strText");
|
||||
if(nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len)
|
||||
if (strText == null) throw new ArgumentNullException("strText");
|
||||
if (nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len)
|
||||
|
||||
int iPosition = nOffset, nLength = 0, nStrLen = strText.Length;
|
||||
|
||||
while(iPosition < nStrLen)
|
||||
while (iPosition < nStrLen)
|
||||
{
|
||||
char ch = strText[iPosition];
|
||||
++iPosition;
|
||||
|
||||
if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
|
||||
if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
|
||||
break;
|
||||
|
||||
++nLength;
|
||||
@@ -481,24 +518,30 @@ namespace KeePassLib.Utility
|
||||
return nLength;
|
||||
}
|
||||
|
||||
internal static string GetScheme(string strUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strUrl)) return string.Empty;
|
||||
|
||||
int i = strUrl.IndexOf(':');
|
||||
if (i > 0) return strUrl.Substring(0, i);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string RemoveScheme(string strUrl)
|
||||
{
|
||||
if(string.IsNullOrEmpty(strUrl)) return string.Empty;
|
||||
if (string.IsNullOrEmpty(strUrl)) return string.Empty;
|
||||
|
||||
int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp);
|
||||
int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp);
|
||||
int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp);
|
||||
int i = strUrl.IndexOf(':');
|
||||
if (i < 0) return strUrl; // No scheme to remove
|
||||
++i;
|
||||
|
||||
if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0))
|
||||
return strUrl; // No scheme
|
||||
// A single '/' indicates a path (absolute) and should not be removed
|
||||
if (((i + 1) < strUrl.Length) && (strUrl[i] == '/') &&
|
||||
(strUrl[i + 1] == '/'))
|
||||
i += 2; // Skip authority prefix
|
||||
|
||||
int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue,
|
||||
(nShScheme >= 0) ? nShScheme : int.MaxValue),
|
||||
(nSmpScheme >= 0) ? nSmpScheme : int.MaxValue);
|
||||
|
||||
if(nMin == nNetScheme) return strUrl.Substring(nMin + 3);
|
||||
if(nMin == nShScheme) return strUrl.Substring(nMin + 2);
|
||||
return strUrl.Substring(nMin + 1);
|
||||
return strUrl.Substring(i);
|
||||
}
|
||||
|
||||
public static string ConvertSeparators(string strPath)
|
||||
@@ -508,7 +551,7 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static string ConvertSeparators(string strPath, char chSeparator)
|
||||
{
|
||||
if(string.IsNullOrEmpty(strPath)) return string.Empty;
|
||||
if (string.IsNullOrEmpty(strPath)) return string.Empty;
|
||||
|
||||
strPath = strPath.Replace('/', chSeparator);
|
||||
strPath = strPath.Replace('\\', chSeparator);
|
||||
@@ -518,33 +561,61 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static bool IsUncPath(string strPath)
|
||||
{
|
||||
if(strPath == null) throw new ArgumentNullException("strPath");
|
||||
if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
return (strPath.StartsWith("\\\\") || strPath.StartsWith("//"));
|
||||
}
|
||||
|
||||
public static string FilterFileName(string strName)
|
||||
{
|
||||
if(strName == null) { Debug.Assert(false); return string.Empty; }
|
||||
if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; }
|
||||
|
||||
string str = strName;
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||
|
||||
str = str.Replace('/', '-');
|
||||
str = str.Replace('\\', '-');
|
||||
str = str.Replace(":", string.Empty);
|
||||
str = str.Replace("*", string.Empty);
|
||||
str = str.Replace("?", string.Empty);
|
||||
str = str.Replace("\"", string.Empty);
|
||||
str = str.Replace(@"'", string.Empty);
|
||||
str = str.Replace('<', '(');
|
||||
str = str.Replace('>', ')');
|
||||
str = str.Replace('|', '-');
|
||||
StringBuilder sb = new StringBuilder(strName.Length);
|
||||
foreach (char ch in strName)
|
||||
{
|
||||
if (ch < '\u0020') continue;
|
||||
|
||||
return str;
|
||||
switch (ch)
|
||||
{
|
||||
case '\"':
|
||||
case '*':
|
||||
case ':':
|
||||
case '?':
|
||||
break;
|
||||
|
||||
case '/':
|
||||
case '\\':
|
||||
case '|':
|
||||
sb.Append('-');
|
||||
break;
|
||||
|
||||
case '<':
|
||||
sb.Append('(');
|
||||
break;
|
||||
|
||||
case '>':
|
||||
sb.Append(')');
|
||||
break;
|
||||
|
||||
default: sb.Append(ch); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim trailing spaces and periods
|
||||
for (int i = sb.Length - 1; i >= 0; --i)
|
||||
{
|
||||
char ch = sb[i];
|
||||
if ((ch == ' ') || (ch == '.')) sb.Remove(i, 1);
|
||||
else break;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the host component of an URL.
|
||||
/// Get the host component of a URL.
|
||||
/// This method is faster and more fault-tolerant than creating
|
||||
/// an <code>Uri</code> object and querying its <code>Host</code>
|
||||
/// property.
|
||||
@@ -555,60 +626,60 @@ namespace KeePassLib.Utility
|
||||
/// </example>
|
||||
public static string GetHost(string strUrl)
|
||||
{
|
||||
if(strUrl == null) { Debug.Assert(false); return string.Empty; }
|
||||
if (strUrl == null) { Debug.Assert(false); return string.Empty; }
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
bool bInExtHost = false;
|
||||
for(int i = 0; i < strUrl.Length; ++i)
|
||||
for (int i = 0; i < strUrl.Length; ++i)
|
||||
{
|
||||
char ch = strUrl[i];
|
||||
if(bInExtHost)
|
||||
if (bInExtHost)
|
||||
{
|
||||
if(ch == '/')
|
||||
if (ch == '/')
|
||||
{
|
||||
if(sb.Length == 0) { } // Ignore leading '/'s
|
||||
if (sb.Length == 0) { } // Ignore leading '/'s
|
||||
else break;
|
||||
}
|
||||
else sb.Append(ch);
|
||||
}
|
||||
else // !bInExtHost
|
||||
{
|
||||
if(ch == ':') bInExtHost = true;
|
||||
if (ch == ':') bInExtHost = true;
|
||||
}
|
||||
}
|
||||
|
||||
string str = sb.ToString();
|
||||
if(str.Length == 0) str = strUrl;
|
||||
if (str.Length == 0) str = strUrl;
|
||||
|
||||
// Remove the login part
|
||||
int nLoginLen = str.IndexOf('@');
|
||||
if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1);
|
||||
if (nLoginLen >= 0) str = str.Substring(nLoginLen + 1);
|
||||
|
||||
// Remove the port
|
||||
int iPort = str.LastIndexOf(':');
|
||||
if(iPort >= 0) str = str.Substring(0, iPort);
|
||||
if (iPort >= 0) str = str.Substring(0, iPort);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static bool AssemblyEquals(string strExt, string strShort)
|
||||
{
|
||||
if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; }
|
||||
if ((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; }
|
||||
|
||||
if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) ||
|
||||
if (strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) ||
|
||||
strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp))
|
||||
return true;
|
||||
|
||||
if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp))
|
||||
if (!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp))
|
||||
{
|
||||
if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) ||
|
||||
if (strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) ||
|
||||
strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp))
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp))
|
||||
if (!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp))
|
||||
{
|
||||
if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) ||
|
||||
if (strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) ||
|
||||
strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp))
|
||||
return true;
|
||||
}
|
||||
@@ -619,7 +690,7 @@ namespace KeePassLib.Utility
|
||||
public static string GetTempPath()
|
||||
{
|
||||
string strDir;
|
||||
if(NativeLib.IsUnix())
|
||||
if (NativeLib.IsUnix())
|
||||
strDir = NativeMethods.GetUserRuntimeDir();
|
||||
#if KeePassUAP
|
||||
else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
|
||||
@@ -629,9 +700,9 @@ namespace KeePassLib.Utility
|
||||
|
||||
try
|
||||
{
|
||||
if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir);
|
||||
if (!Directory.Exists(strDir)) Directory.CreateDirectory(strDir);
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
return strDir;
|
||||
}
|
||||
@@ -642,31 +713,29 @@ namespace KeePassLib.Utility
|
||||
SearchOption opt)
|
||||
{
|
||||
List<string> l = new List<string>();
|
||||
if(strDir == null) { Debug.Assert(false); return l; }
|
||||
if(strPattern == null) { Debug.Assert(false); return l; }
|
||||
if (strDir == null) { Debug.Assert(false); return l; }
|
||||
if (strPattern == null) { Debug.Assert(false); return l; }
|
||||
|
||||
string[] v = Directory.GetFiles(strDir, strPattern, opt);
|
||||
if(v == null) { Debug.Assert(false); return l; }
|
||||
if (v == null) { Debug.Assert(false); return l; }
|
||||
|
||||
// Only accept files with the correct extension; GetFiles may
|
||||
// return additional files, see GetFiles documentation
|
||||
string strExt = GetExtension(strPattern);
|
||||
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
|
||||
if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
|
||||
(strExt.IndexOf('?') < 0))
|
||||
{
|
||||
strExt = "." + strExt;
|
||||
|
||||
foreach(string strPathRaw in v)
|
||||
foreach (string strPathRaw in v)
|
||||
{
|
||||
if(strPathRaw == null) { Debug.Assert(false); continue; }
|
||||
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
|
||||
if(strPath.Length == 0) { Debug.Assert(false); continue; }
|
||||
if (strPathRaw == null) { Debug.Assert(false); continue; }
|
||||
string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
|
||||
if (strPath.Length == 0) { Debug.Assert(false); continue; }
|
||||
Debug.Assert(strPath == strPathRaw);
|
||||
|
||||
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
|
||||
continue;
|
||||
|
||||
l.Add(strPathRaw);
|
||||
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
|
||||
l.Add(strPathRaw);
|
||||
}
|
||||
}
|
||||
else l.AddRange(v);
|
||||
@@ -679,33 +748,31 @@ namespace KeePassLib.Utility
|
||||
SearchOption opt)
|
||||
{
|
||||
List<FileInfo> l = new List<FileInfo>();
|
||||
if(di == null) { Debug.Assert(false); return l; }
|
||||
if(strPattern == null) { Debug.Assert(false); return l; }
|
||||
if (di == null) { Debug.Assert(false); return l; }
|
||||
if (strPattern == null) { Debug.Assert(false); return l; }
|
||||
|
||||
FileInfo[] v = di.GetFiles(strPattern, opt);
|
||||
if(v == null) { Debug.Assert(false); return l; }
|
||||
if (v == null) { Debug.Assert(false); return l; }
|
||||
|
||||
// Only accept files with the correct extension; GetFiles may
|
||||
// return additional files, see GetFiles documentation
|
||||
string strExt = GetExtension(strPattern);
|
||||
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
|
||||
if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
|
||||
(strExt.IndexOf('?') < 0))
|
||||
{
|
||||
strExt = "." + strExt;
|
||||
|
||||
foreach(FileInfo fi in v)
|
||||
foreach (FileInfo fi in v)
|
||||
{
|
||||
if(fi == null) { Debug.Assert(false); continue; }
|
||||
if (fi == null) { Debug.Assert(false); continue; }
|
||||
string strPathRaw = fi.FullName;
|
||||
if(strPathRaw == null) { Debug.Assert(false); continue; }
|
||||
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
|
||||
if(strPath.Length == 0) { Debug.Assert(false); continue; }
|
||||
if (strPathRaw == null) { Debug.Assert(false); continue; }
|
||||
string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
|
||||
if (strPath.Length == 0) { Debug.Assert(false); continue; }
|
||||
Debug.Assert(strPath == strPathRaw);
|
||||
|
||||
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
|
||||
continue;
|
||||
|
||||
l.Add(fi);
|
||||
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
|
||||
l.Add(fi);
|
||||
}
|
||||
}
|
||||
else l.AddRange(v);
|
||||
@@ -713,5 +780,82 @@ namespace KeePassLib.Utility
|
||||
return l;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
public static char GetDriveLetter(string strPath)
|
||||
{
|
||||
if (strPath == null) throw new ArgumentNullException("strPath");
|
||||
|
||||
Debug.Assert(default(char) == '\0');
|
||||
if (strPath.Length < 3) return '\0';
|
||||
if ((strPath[1] != ':') || (strPath[2] != '\\')) return '\0';
|
||||
|
||||
char ch = char.ToUpperInvariant(strPath[0]);
|
||||
return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0');
|
||||
}
|
||||
|
||||
internal static string GetSafeFileName(string strName)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(strName));
|
||||
|
||||
string str = FilterFileName(GetFileName(strName ?? string.Empty));
|
||||
|
||||
if (string.IsNullOrEmpty(str))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
return "File.dat";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
internal static string GetCanonicalUri(string strUri)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strUri)) { Debug.Assert(false); return strUri; }
|
||||
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(strUri);
|
||||
|
||||
if (uri.IsAbsoluteUri) return uri.AbsoluteUri;
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
return strUri;
|
||||
}
|
||||
|
||||
/* internal static Dictionary<string, string> ParseQuery(string strQuery)
|
||||
{
|
||||
Dictionary<string, string> d = new Dictionary<string, string>();
|
||||
if(string.IsNullOrEmpty(strQuery)) return d;
|
||||
|
||||
string[] vKvp = strQuery.Split(new char[] { '?', '&' });
|
||||
if(vKvp == null) { Debug.Assert(false); return d; }
|
||||
|
||||
foreach(string strKvp in vKvp)
|
||||
{
|
||||
if(string.IsNullOrEmpty(strKvp)) continue;
|
||||
|
||||
string strKey, strValue;
|
||||
int iSep = strKvp.IndexOf('=');
|
||||
if(iSep < 0)
|
||||
{
|
||||
strKey = strKvp;
|
||||
strValue = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
strKey = strKvp.Substring(0, iSep);
|
||||
strValue = strKvp.Substring(iSep + 1);
|
||||
}
|
||||
|
||||
strKey = Uri.UnescapeDataString(strKey);
|
||||
strValue = Uri.UnescapeDataString(strValue);
|
||||
|
||||
d[strKey] = strValue;
|
||||
}
|
||||
|
||||
return d;
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,9 +353,6 @@ namespace keepass2android.search
|
||||
case PwDefs.NotesField:
|
||||
intlResourceId = Resource.String.entry_comment;
|
||||
break;
|
||||
case PwGroup.SearchContextTags:
|
||||
intlResourceId = Resource.String.entry_tags;
|
||||
break;
|
||||
default:
|
||||
//don't disclose protected strings:
|
||||
if (CurrentEntry.Strings.Get(context.Key).IsProtected)
|
||||
|
||||
Reference in New Issue
Block a user