Merge branch 'master' of c:/ph/keepass2android

This commit is contained in:
Philipp Crocoll
2021-05-15 15:26:19 +02:00
45 changed files with 3994 additions and 2450 deletions

View File

@@ -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,11 +50,6 @@ namespace KeePassLib.Collections
return m_d.GetEnumerator();
}
public void Clear()
{
m_d.Clear();
}
private int GetFreeID()
{
int i = m_d.Count;
@@ -105,11 +103,9 @@ namespace KeePassLib.Collections
{
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)

View File

@@ -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,35 +34,47 @@ 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;
}
@@ -70,14 +82,28 @@ namespace KeePassLib.Collections
{
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;
}
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;
}
@@ -85,8 +111,19 @@ namespace KeePassLib.Collections
{
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;
}
@@ -94,37 +131,40 @@ namespace KeePassLib.Collections
{
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);
}
}
}

View File

@@ -29,14 +29,28 @@ namespace KeePassLib.Cryptography.Cipher
{
public sealed class ChaCha20Engine : ICipherEngine2
{
private PwUuid m_uuid = new PwUuid(new byte[] {
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
});
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

View File

@@ -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,5 +33,10 @@ namespace KeePassLib.Interfaces
{
get;
}
PwUuid PreviousParentGroup
{
get;
}
}
}

View File

@@ -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" />

View File

@@ -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 pwUuid, byte[] pbImageDataPng)
public PwCustomIcon(PwUuid pu, byte[] pbImageDataPng)
{
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");
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"); }
Debug.Assert(pbImageDataPng != null);
if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng");
m_pwUuid = pwUuid;
m_uuid = pu;
m_pbImageDataPng = pbImageDataPng;
}
#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
private static long GetKey(int w, int h)
{
return (((long)w << 32) ^ (long)h);
}
/// <summary>
/// Get the icon as an <c>Image</c> (original size).
/// </summary>
public Android.Graphics.Bitmap GetImage()
{
const long lKey = -1;
Android.Graphics.Bitmap img;
if (m_dImageCache.TryGetValue(lKey, out img)) return img;
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;
}
}
}

View File

@@ -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;
@@ -739,6 +739,12 @@ namespace KeePassLib
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;
@@ -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,16 +1332,18 @@ 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;
puPrevParent = PwUuid.Zero;
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
if (ptOrg != null)
{
dtLoc = ptOrg.LocationChanged;
puPrevParent = ptOrg.PreviousParentGroup;
p = ppOrg;
}
@@ -1277,6 +1351,7 @@ namespace KeePassLib
if ((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
{
dtLoc = ptSrc.LocationChanged;
puPrevParent = ptSrc.PreviousParentGroup;
p = ppSrc;
}
@@ -1313,8 +1388,13 @@ namespace KeePassLib
GroupHandler gh = delegate (PwGroup pgSub)
{
DateTime dt;
if(GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt) != null)
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;
};
@@ -1322,8 +1402,13 @@ namespace KeePassLib
EntryHandler eh = delegate (PwEntry pe)
{
DateTime dt;
if(GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt) != null)
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;
};
@@ -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,
@@ -1585,68 +1676,64 @@ namespace KeePassLib
else { Debug.Assert(false); return null; }
}
public bool DeleteCustomIcons(List<PwUuid> vUuidsToDelete)
public bool DeleteCustomIcons(List<PwUuid> lUuids)
{
Debug.Assert(vUuidsToDelete != null);
if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete");
if(vUuidsToDelete.Count <= 0) return true;
if (lUuids == null) { Debug.Assert(false); throw new ArgumentNullException("lUuids"); }
if (lUuids.Count == 0) return false;
Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
foreach (PwUuid pu in lUuids) { dToDel[pu] = true; }
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));
}
}
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
m_vCustomIcons.RemoveAll(f);
FixCustomIconRefs();
return true;
}
private void FixCustomIconRefs()
{
Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>();
foreach (PwCustomIcon ci in m_vCustomIcons) { d[ci.Uuid] = true; }
GroupHandler gh = delegate (PwGroup pg)
{
PwUuid uuidThis = pg.CustomIconUuid;
if(uuidThis.Equals(PwUuid.Zero)) return true;
foreach(PwUuid uuidDelete in vUuidsToDelete)
{
if(uuidThis.Equals(uuidDelete))
{
pg.CustomIconUuid = PwUuid.Zero;
break;
}
}
PwUuid pu = pg.CustomIconUuid;
if (pu.Equals(PwUuid.Zero)) return true;
if (!d.ContainsKey(pu)) pg.CustomIconUuid = PwUuid.Zero;
return true;
};
EntryHandler eh = delegate (PwEntry pe)
{
RemoveCustomIconUuid(pe, vUuidsToDelete);
FixCustomIconRefs(pe, d);
return true;
};
gh(m_pgRootGroup);
if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh))
{
Debug.Assert(false);
return false;
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
}
foreach(PwUuid pwUuid in vUuidsToDelete)
private void FixCustomIconRefs(PwEntry pe, Dictionary<PwUuid, bool> d)
{
int nIndex = GetCustomIconIndex(pwUuid);
if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex);
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);
}
return true;
}
private static void RemoveCustomIconUuid(PwEntry pe, List<PwUuid> vToDelete)
{
PwUuid uuidThis = pe.CustomIconUuid;
if(uuidThis.Equals(PwUuid.Zero)) return;
foreach(PwUuid uuidDelete in vToDelete)
{
if(uuidThis.Equals(uuidDelete))
{
pe.CustomIconUuid = PwUuid.Zero;
break;
}
}
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)
{
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)
{
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);
}
}
}

View File

@@ -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
@@ -240,12 +252,20 @@ namespace KeePassLib
}
}
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
@@ -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;
}

View File

@@ -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,12 +917,10 @@ namespace KeePassLib
if (bAlsoChangeHistoryUuids)
{
foreach (PwEntry peHist in m_listHistory)
{
foreach (PwEntry peHist in m_lHistory)
peHist.Uuid = pwNewUuid;
}
}
}
public void SetCreatedNow()
{

View File

@@ -316,4 +316,13 @@ namespace KeePassLib
Cinnamon,
Pantheon
}
public enum PwSearchMode
{
None = 0,
Simple,
Regular,
XPath
}
}

View 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);
}
}
}
}
}

View File

@@ -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
@@ -20,35 +20,36 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Text;
using KeePassLib.Collections;
using KeePassLib.Delegates;
using KeePassLib.Interfaces;
using KeePassLib.Security;
using KeePassLib.Resources;
using KeePassLib.Utility;
namespace KeePassLib
{
/// <summary>
/// A group containing several password entries.
/// A group containing subgroups and entries.
/// </summary>
public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<PwGroup>
public sealed partial class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<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";
public const bool DefaultAutoTypeEnabled = true;
public const bool DefaultSearchingEnabled = true;
private PwObjectList<PwGroup> m_listGroups = new PwObjectList<PwGroup>();
private PwObjectList<PwEntry> m_listEntries = new PwObjectList<PwEntry>();
private PwGroup m_pParentGroup = null;
private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
// In the tree view of Windows 10, the X coordinate is reset
// to 0 after 256 nested nodes
private const uint MaxDepth = 126; // Depth 126 = level 127 < 256/2
private PwUuid m_uuid = PwUuid.Zero;
private PwGroup m_pParentGroup = null;
private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
private PwUuid m_puPrevParentGroup = PwUuid.Zero;
private PwObjectList<PwGroup> m_listGroups = new PwObjectList<PwGroup>();
private PwObjectList<PwEntry> m_listEntries = new PwObjectList<PwEntry>();
private string m_strName = string.Empty;
private string m_strNotes = string.Empty;
@@ -72,6 +73,8 @@ namespace KeePassLib
private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero;
private List<string> m_lTags = new List<string>();
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
/// <summary>
@@ -82,11 +85,42 @@ namespace KeePassLib
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;
}
}
/// <summary>
/// Reference to the group to which this group belongs. May be <c>null</c>.
/// </summary>
public PwGroup ParentGroup
{
get { return m_pParentGroup; }
// Plugins: use the PwGroup.AddGroup method instead.
// Internal: check depth using CanAddGroup/CheckCanAddGroup.
internal set { Debug.Assert(value != this); m_pParentGroup = value; }
}
/// <summary>
/// The date/time when the location of the object was last changed.
/// </summary>
public DateTime LocationChanged
{
get { return m_tParentGroupLastMod; }
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>
/// The name of this group. Cannot be <c>null</c>.
/// </summary>
@@ -95,7 +129,7 @@ namespace KeePassLib
get { return m_strName; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_strName = value;
}
}
@@ -108,7 +142,7 @@ namespace KeePassLib
get { return m_strNotes; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_strNotes = value;
}
}
@@ -132,31 +166,11 @@ namespace KeePassLib
get { return m_pwCustomIconID; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_pwCustomIconID = value;
}
}
/// <summary>
/// Reference to the group to which this group belongs. May be <c>null</c>.
/// </summary>
public PwGroup ParentGroup
{
get { return m_pParentGroup; }
// Plugins: use <c>PwGroup.AddGroup</c> instead.
internal set { Debug.Assert(value != this); m_pParentGroup = value; }
}
/// <summary>
/// The date/time when the location of the object was last changed.
/// </summary>
public DateTime LocationChanged
{
get { return m_tParentGroupLastMod; }
set { m_tParentGroupLastMod = value; }
}
/// <summary>
/// A flag that specifies if the group is shown as expanded or
/// collapsed in the user interface.
@@ -261,7 +275,7 @@ namespace KeePassLib
get { return m_strDefaultAutoTypeSequence; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_strDefaultAutoTypeSequence = value;
}
}
@@ -283,11 +297,21 @@ namespace KeePassLib
get { return m_pwLastTopVisibleEntry; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_pwLastTopVisibleEntry = value;
}
}
public List<string> Tags
{
get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
set
{
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_lTags = value;
}
}
/// <summary>
/// Custom data container that can be used by plugins to store
/// own data in KeePass groups.
@@ -326,8 +350,11 @@ namespace KeePassLib
if (bSetTimes)
{
m_tCreation = m_tLastMod = m_tLastAccess =
m_tParentGroupLastMod = DateTime.Now;
DateTime dtNow = DateTime.UtcNow;
m_tCreation = dtNow;
m_tLastMod = dtNow;
m_tLastAccess = dtNow;
m_tParentGroupLastMod = dtNow;
}
}
@@ -344,8 +371,11 @@ namespace KeePassLib
if (bSetTimes)
{
m_tCreation = m_tLastMod = m_tLastAccess =
m_tParentGroupLastMod = DateTime.Now;
DateTime dtNow = DateTime.UtcNow;
m_tCreation = dtNow;
m_tLastMod = dtNow;
m_tLastAccess = dtNow;
m_tParentGroupLastMod = dtNow;
}
if (strName != null) m_strName = strName;
@@ -374,8 +404,11 @@ namespace KeePassLib
pg.m_listGroups = m_listGroups.CloneDeep();
pg.m_listEntries = m_listEntries.CloneDeep();
pg.TakeOwnership(true, true, false);
pg.m_pParentGroup = m_pParentGroup;
pg.m_tParentGroupLastMod = m_tParentGroupLastMod;
pg.m_puPrevParentGroup = m_puPrevParentGroup;
pg.m_strName = m_strName;
pg.m_strNotes = m_strNotes;
@@ -400,6 +433,8 @@ namespace KeePassLib
pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry;
pg.m_lTags.AddRange(m_lTags);
pg.m_dCustomData = m_dCustomData.CloneDeep();
return pg;
@@ -438,6 +473,8 @@ namespace KeePassLib
if (m_pParentGroup != pg.m_pParentGroup) return false;
if (!bIgnoreLastMod && (m_tParentGroupLastMod != pg.m_tParentGroupLastMod))
return false;
if (!m_puPrevParentGroup.Equals(pg.m_puPrevParentGroup))
return false;
}
if (m_strName != pg.m_strName) return false;
@@ -470,6 +507,9 @@ namespace KeePassLib
if (!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false;
// The Tags property normalizes
if (!MemUtil.ListsEqual<string>(this.Tags, pg.Tags)) return false;
if (!m_dCustomData.Equals(pg.m_dCustomData)) return false;
if ((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None)
@@ -516,7 +556,10 @@ namespace KeePassLib
m_uuid = pgTemplate.m_uuid;
if (bAssignLocationChanged)
{
m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod;
m_puPrevParentGroup = pgTemplate.m_puPrevParentGroup;
}
m_strName = pgTemplate.m_strName;
m_strNotes = pgTemplate.m_strNotes;
@@ -538,6 +581,8 @@ namespace KeePassLib
m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry;
m_lTags = new List<string>(pgTemplate.m_lTags);
m_dCustomData = pgTemplate.m_dCustomData.CloneDeep();
}
@@ -562,7 +607,7 @@ namespace KeePassLib
/// get touched, too.</param>
public void Touch(bool bModified, bool bTouchParents)
{
m_tLastAccess = DateTime.Now;
m_tLastAccess = DateTime.UtcNow;
++m_uUsageCount;
if (bModified) m_tLastMod = m_tLastAccess;
@@ -667,21 +712,15 @@ namespace KeePassLib
}
}
foreach (PwGroup pg in m_listGroups)
{
if (groupHandler != null)
{
foreach(PwGroup pg in m_listGroups)
{
if (!groupHandler(pg)) return false;
}
pg.PreOrderTraverseTree(groupHandler, entryHandler);
}
}
else // groupHandler == null
{
foreach(PwGroup pg in m_listGroups)
{
pg.PreOrderTraverseTree(null, entryHandler);
}
if (!pg.PreOrderTraverseTree(groupHandler, entryHandler))
return false;
}
return true;
@@ -768,357 +807,95 @@ namespace KeePassLib
return PreOrderTraverseTree(null, eh);
}
/// <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)
internal List<string> GetTagsInherited(bool bNormalize)
{
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
List<string> l = new List<string>();
PwGroup pg = this;
for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm)
while (pg != null)
{
// Update counters for a better state guess
if (slStatus != null)
{
ulong uRemRounds = (ulong)(lTerms.Count - iTerm);
uTotalEntries = uCurEntries + (uRemRounds *
pg.GetEntriesCount(true));
l.AddRange(pg.Tags);
pg = pg.m_pParentGroup;
}
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 = "<22> " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " <20>" : null);
}
resultContexts[pe.Uuid] = new KeyValuePair<string, string>(contextFieldName, contextString);
}
}
if (bNormalize) StrUtil.NormalizeTags(l);
return l;
}
public List<string> BuildEntryTagsList()
{
return BuildEntryTagsList(false);
return BuildEntryTagsList(false, false);
}
public List<string> BuildEntryTagsList(bool bSort)
{
List<string> vTags = new List<string>();
return BuildEntryTagsList(bSort, false);
}
internal List<string> BuildEntryTagsList(bool bSort, bool bGroupTags)
{
Dictionary<string, bool> d = new Dictionary<string, bool>();
GroupHandler gh = null;
if (bGroupTags)
{
gh = delegate (PwGroup pg)
{
foreach (string strTag in pg.Tags) d[strTag] = true;
return true;
};
}
EntryHandler eh = delegate (PwEntry pe)
{
foreach (string strTag in pe.Tags)
{
bool bFound = false;
for (int i = 0; i < vTags.Count; ++i)
{
if (vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp))
{
bFound = true;
break;
}
}
if (!bFound) vTags.Add(strTag);
}
foreach (string strTag in pe.Tags) d[strTag] = true;
return true;
};
TraverseTree(TraversalMethod.PreOrder, null, eh);
if (bSort) vTags.Sort(StrUtil.CaseIgnoreComparer);
return vTags;
if (gh != null) gh(this);
TraverseTree(TraversalMethod.PreOrder, gh, eh);
List<string> l = new List<string>(d.Keys);
if (bSort) l.Sort(StrUtil.CompareNaturally);
return l;
}
#if !KeePassLibSD
public IDictionary<string, uint> BuildEntryTagsDict(bool bSort)
{
IDictionary<string, uint> d;
if (!bSort) d = new Dictionary<string, uint>(StrUtil.CaseIgnoreComparer);
else d = new SortedDictionary<string, uint>(StrUtil.CaseIgnoreComparer);
Debug.Assert(!bSort); // Obsolete
EntryHandler eh = delegate(PwEntry pe)
IDictionary<string, uint> d;
if (!bSort) d = new Dictionary<string, uint>();
else d = new SortedDictionary<string, uint>();
GroupHandler gh = delegate (PwGroup pg)
{
foreach (string strTag in pe.Tags)
foreach (string strTag in pg.Tags)
{
uint u;
if (d.TryGetValue(strTag, out u)) d[strTag] = u + 1;
else d[strTag] = 1;
// For groups without entries
if (!d.ContainsKey(strTag)) d[strTag] = 0;
}
return true;
};
TraverseTree(TraversalMethod.PreOrder, null, eh);
EntryHandler eh = delegate (PwEntry pe)
{
foreach (string strTag in pe.GetTagsInherited())
{
uint u;
d.TryGetValue(strTag, out u);
d[strTag] = u + 1;
}
return true;
};
gh(this);
TraverseTree(TraversalMethod.PreOrder, gh, eh);
return d;
}
#endif
@@ -1127,24 +904,29 @@ namespace KeePassLib
bool bSearchRecursive)
{
if (strTag == null) throw new ArgumentNullException("strTag");
if (strTag.Length == 0) return;
foreach (PwEntry pe in m_listEntries)
strTag = StrUtil.NormalizeTag(strTag);
if (string.IsNullOrEmpty(strTag)) return;
EntryHandler eh = delegate (PwEntry pe)
{
foreach (string strEntryTag in pe.Tags)
foreach (string strEntryTag in pe.GetTagsInherited())
{
if (strEntryTag.Equals(strTag, StrUtil.CaseIgnoreCmp))
if (strEntryTag == strTag)
{
listStorage.Add(pe);
break;
}
}
}
return true;
};
if (bSearchRecursive)
TraverseTree(TraversalMethod.PreOrder, null, eh);
else
{
foreach (PwGroup pg in m_listGroups)
pg.FindEntriesByTag(strTag, listStorage, true);
foreach (PwEntry pe in m_listEntries) eh(pe);
}
}
@@ -1279,7 +1061,7 @@ namespace KeePassLib
PwGroup pg = m_pParentGroup;
while (pg != null)
{
if ((!bIncludeTopMostGroup) && (pg.m_pParentGroup == null))
if (!bIncludeTopMostGroup && (pg.m_pParentGroup == null))
break;
strPath = pg.Name + strSeparator + strPath;
@@ -1402,21 +1184,34 @@ namespace KeePassLib
#endif
/// <summary>
/// Get the level of the group (i.e. the number of parent groups).
/// Get the depth of this group (i.e. the number of ancestors).
/// </summary>
/// <returns>Number of parent groups.</returns>
public uint GetLevel()
/// <returns>Depth of this group.</returns>
public uint GetDepth()
{
PwGroup pg = m_pParentGroup;
uint uLevel = 0;
uint d = 0;
while (pg != null)
{
pg = pg.ParentGroup;
++uLevel;
pg = pg.m_pParentGroup;
++d;
}
return uLevel;
return d;
}
private uint GetHeight()
{
if (m_listGroups.UCount == 0) return 0;
uint h = 0;
foreach (PwGroup pgSub in m_listGroups)
{
h = Math.Max(h, pgSub.GetHeight());
}
return (h + 1);
}
public string GetAutoTypeSequenceInherited()
@@ -1460,7 +1255,7 @@ namespace KeePassLib
/// subgroups.</returns>
public PwObjectList<PwGroup> GetGroups(bool bRecursive)
{
if (bRecursive == false) return m_listGroups;
if (!bRecursive) return m_listGroups;
PwObjectList<PwGroup> list = m_listGroups.CloneShallow();
foreach (PwGroup pgSub in m_listGroups)
@@ -1473,15 +1268,20 @@ namespace KeePassLib
public PwObjectList<PwEntry> GetEntries(bool bIncludeSubGroupEntries)
{
if (bIncludeSubGroupEntries == false) return m_listEntries;
PwObjectList<PwEntry> l = new PwObjectList<PwEntry>();
PwObjectList<PwEntry> list = m_listEntries.CloneShallow();
foreach (PwGroup pgSub in m_listGroups)
GroupHandler gh = delegate (PwGroup pg)
{
list.Add(pgSub.GetEntries(true));
}
l.Add(pg.Entries);
return true;
};
return list;
gh(this);
if (bIncludeSubGroupEntries)
PreOrderTraverseTree(gh, null);
Debug.Assert(l.UCount == GetEntriesCount(bIncludeSubGroupEntries));
return l;
}
/// <summary>
@@ -1550,11 +1350,29 @@ namespace KeePassLib
{
if (subGroup == null) throw new ArgumentNullException("subGroup");
CheckCanAddGroup(subGroup);
m_listGroups.Add(subGroup);
if (bTakeOwnership) subGroup.m_pParentGroup = this;
if (bTakeOwnership) subGroup.ParentGroup = this;
if (bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.Now;
if (bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.UtcNow;
}
internal bool CanAddGroup(PwGroup pgSub)
{
if (pgSub == null) { Debug.Assert(false); return false; }
uint dCur = GetDepth(), hSub = pgSub.GetHeight();
return ((dCur + hSub + 1) <= MaxDepth);
}
internal void CheckCanAddGroup(PwGroup pgSub)
{
if (!CanAddGroup(pgSub))
{
Debug.Assert(false);
throw new InvalidOperationException(KLRes.StructsTooDeep);
}
}
/// <summary>
@@ -1589,7 +1407,7 @@ namespace KeePassLib
// only assign it to the new one
if (bTakeOwnership) pe.ParentGroup = this;
if (bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.Now;
if (bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.UtcNow;
}
public void SortSubGroups(bool bRecursive)
@@ -1605,7 +1423,7 @@ namespace KeePassLib
public void DeleteAllObjects(PwDatabase pdContext)
{
DateTime dtNow = DateTime.Now;
DateTime dtNow = DateTime.UtcNow;
foreach (PwEntry pe in m_listEntries)
{
@@ -1649,7 +1467,7 @@ namespace KeePassLib
public void SetCreatedNow(bool bRecursive)
{
DateTime dt = DateTime.Now;
DateTime dt = DateTime.UtcNow;
m_tCreation = dt;
m_tLastAccess = dt;
@@ -1682,10 +1500,63 @@ namespace KeePassLib
pg.SetCreatedNow(true);
pg.TakeOwnership(true, true, true);
return pg;
}
internal string[] GetAutoTypeSequences(bool bWithStd)
{
try
{
Dictionary<string, bool> d = new Dictionary<string, bool>();
Action<string> fAdd = delegate (string str)
{
if (!string.IsNullOrEmpty(str)) d[str] = true;
};
if (bWithStd)
{
fAdd(PwDefs.DefaultAutoTypeSequence);
fAdd(PwDefs.DefaultAutoTypeSequenceTan);
}
GroupHandler gh = delegate (PwGroup pg)
{
fAdd(pg.DefaultAutoTypeSequence);
return true;
};
EntryHandler eh = delegate (PwEntry pe)
{
AutoTypeConfig c = pe.AutoType;
fAdd(c.DefaultSequence);
foreach (AutoTypeAssociation a in c.Associations)
{
fAdd(a.Sequence);
}
return true;
};
gh(this);
TraverseTree(TraversalMethod.PreOrder, gh, eh);
string[] v = new string[d.Count];
if (d.Count != 0)
{
d.Keys.CopyTo(v, 0);
Array.Sort<string>(v, StrUtil.CaseIgnoreComparer);
}
return v;
}
catch (Exception) { Debug.Assert(false); }
return new string[0];
}
}
public sealed class PwGroupComparer : IComparer<PwGroup>

View File

@@ -26,6 +26,9 @@ namespace KeePassLib.Resources
{
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>

View File

@@ -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;
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,18 +156,16 @@ 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)
@@ -160,7 +174,7 @@ namespace KeePassLib.Security
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;
}
@@ -178,8 +192,8 @@ namespace KeePassLib.Security
}
/// <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>
@@ -194,46 +208,64 @@ 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;
}
@@ -242,10 +274,34 @@ namespace KeePassLib.Security
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)
@@ -254,18 +310,14 @@ namespace KeePassLib.Security
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)
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
{
@@ -279,21 +331,20 @@ 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;
}
@@ -303,44 +354,81 @@ namespace KeePassLib.Security
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)
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");
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);
}
}
}

View File

@@ -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 (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)
byte[] pbCT = m_pbCT, pbX = m_pbXorPad;
if ((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length))
{
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 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];
public bool EqualsValue(byte[] pb)
{
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
for (int i = 0; i < pbPT.Length; ++i)
pbPT[i] = (byte)(pbCT[i] ^ pbX[i]);
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;
} */
return pbPT;
}
}
}

View File

@@ -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
@@ -86,8 +86,11 @@ namespace KeePassLib.Serialization
private PwDeletedObject m_ctxDeletedObject = null;
private PwUuid m_uuidCustomIconID = PwUuid.Zero;
private byte[] m_pbCustomIconData = null;
private string m_strCustomIconName = null;
private DateTime? m_odtCustomIconLastMod = null;
private string m_strCustomDataKey = null;
private string m_strCustomDataValue = null;
private DateTime? m_odtCustomDataLastMod = null;
private string m_strGroupCustomDataKey = null;
private string m_strGroupCustomDataValue = null;
private string m_strEntryCustomDataKey = null;
@@ -95,35 +98,10 @@ namespace KeePassLib.Serialization
private void ReadXmlStreamed(Stream sXml, Stream sParent)
{
ReadDocumentStreamed(CreateXmlReader(sXml), sParent);
}
internal static XmlReaderSettings CreateStdXmlReaderSettings()
using (XmlReader xr = XmlUtilEx.CreateXmlReader(sXml))
{
XmlReaderSettings xrs = new XmlReaderSettings();
xrs.CloseInput = true;
xrs.IgnoreComments = true;
xrs.IgnoreProcessingInstructions = true;
xrs.IgnoreWhitespace = true;
#if KeePassUAP
xrs.DtdProcessing = DtdProcessing.Prohibit;
#else
#if !KeePassLibSD
xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there
// xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only
#endif
xrs.ValidationType = ValidationType.None;
#endif
return xrs;
ReadDocumentStreamed(xr, sParent);
}
private static XmlReader CreateXmlReader(Stream readerStream)
{
XmlReaderSettings xrs = CreateStdXmlReaderSettings();
return XmlReader.Create(readerStream, xrs);
}
private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream)
@@ -176,7 +154,7 @@ namespace KeePassLib.Serialization
}
++uTagCounter;
if(((uTagCounter % 256) == 0) && bSupportsStatus)
if (((uTagCounter & 0xFFU) == 0) && bSupportsStatus)
{
Debug.Assert(lStreamLength == sParentStream.Length);
uint uPct = (uint)((sParentStream.Position * 100) /
@@ -186,7 +164,8 @@ namespace KeePassLib.Serialization
// position/length values (M120413)
if (uPct > 100) { Debug.Assert(false); uPct = 100; }
m_slLogger.SetProgress(uPct);
if (!m_slLogger.SetProgress(uPct))
throw new OperationCanceledException();
}
}
@@ -330,6 +309,10 @@ namespace KeePassLib.Serialization
m_pbCustomIconData = Convert.FromBase64String(strData);
else { Debug.Assert(false); }
}
else if (xr.Name == ElemName)
m_strCustomIconName = ReadString(xr);
else if (xr.Name == ElemLastModTime)
m_odtCustomIconLastMod = ReadTime(xr);
else ReadUnknown(xr);
break;
@@ -366,6 +349,8 @@ namespace KeePassLib.Serialization
m_strCustomDataKey = ReadString(xr);
else if (xr.Name == ElemValue)
m_strCustomDataValue = ReadString(xr);
else if (xr.Name == ElemLastModTime)
m_odtCustomDataLastMod = ReadTime(xr);
else ReadUnknown(xr);
break;
@@ -409,6 +394,10 @@ namespace KeePassLib.Serialization
m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr));
else if (xr.Name == ElemLastTopVisibleEntry)
m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr);
else if (xr.Name == ElemPreviousParentGroup)
m_ctxGroup.PreviousParentGroup = ReadUuid(xr);
else if (xr.Name == ElemTags)
m_ctxGroup.Tags = StrUtil.StringToTags(ReadString(xr));
else if (xr.Name == ElemCustomData)
return SwitchContext(ctx, KdbContext.GroupCustomData, xr);
else if (xr.Name == ElemGroup)
@@ -466,8 +455,12 @@ namespace KeePassLib.Serialization
}
else if (xr.Name == ElemOverrideUrl)
m_ctxEntry.OverrideUrl = ReadString(xr);
else if (xr.Name == ElemQualityCheck)
m_ctxEntry.QualityCheck = ReadBool(xr, true);
else if (xr.Name == ElemTags)
m_ctxEntry.Tags = StrUtil.StringToTags(ReadString(xr));
else if (xr.Name == ElemPreviousParentGroup)
m_ctxEntry.PreviousParentGroup = ReadUuid(xr);
else if (xr.Name == ElemTimes)
return SwitchContext(ctx, KdbContext.EntryTimes, xr);
else if (xr.Name == ElemString)
@@ -514,6 +507,7 @@ namespace KeePassLib.Serialization
tl.LocationChanged = ReadTime(xr);
else ReadUnknown(xr);
break;
case KdbContext.EntryString:
if (xr.Name == ElemKey)
m_ctxStringName = ReadString(xr);
@@ -622,12 +616,19 @@ namespace KeePassLib.Serialization
{
if (!m_uuidCustomIconID.Equals(PwUuid.Zero) &&
(m_pbCustomIconData != null))
m_pwDatabase.CustomIcons.Add(new PwCustomIcon(
m_uuidCustomIconID, m_pbCustomIconData));
{
PwCustomIcon ci = new PwCustomIcon(m_uuidCustomIconID,
m_pbCustomIconData);
if (m_strCustomIconName != null) ci.Name = m_strCustomIconName;
ci.LastModificationTime = m_odtCustomIconLastMod;
m_pwDatabase.CustomIcons.Add(ci);
}
else { Debug.Assert(false); }
m_uuidCustomIconID = PwUuid.Zero;
m_pbCustomIconData = null;
m_strCustomIconName = null;
m_odtCustomIconLastMod = null;
return KdbContext.CustomIcons;
}
@@ -638,11 +639,13 @@ namespace KeePassLib.Serialization
else if ((ctx == KdbContext.CustomDataItem) && (xr.Name == ElemStringDictExItem))
{
if ((m_strCustomDataKey != null) && (m_strCustomDataValue != null))
m_pwDatabase.CustomData.Set(m_strCustomDataKey, m_strCustomDataValue);
m_pwDatabase.CustomData.Set(m_strCustomDataKey,
m_strCustomDataValue, m_odtCustomDataLastMod);
else { Debug.Assert(false); }
m_strCustomDataKey = null;
m_strCustomDataValue = null;
m_odtCustomDataLastMod = null;
return KdbContext.CustomData;
}
@@ -765,10 +768,16 @@ namespace KeePassLib.Serialization
{
XorredBuffer xb = ProcessNode(xr);
if (xb != null)
{
Debug.Assert(false); // Protected data is unexpected here
try
{
byte[] pb = xb.ReadPlainText();
if (pb.Length == 0) return string.Empty;
return StrUtil.Utf8.GetString(pb, 0, pb.Length);
try { return StrUtil.Utf8.GetString(pb, 0, pb.Length); }
finally { MemUtil.ZeroByteArray(pb); }
}
finally { xb.Dispose(); }
}
m_bReadNextNode = false; // ReadElementString skips end tag
@@ -781,6 +790,55 @@ namespace KeePassLib.Serialization
return xr.ReadElementString();
}
private byte[] ReadBase64(XmlReader xr, bool bRaw)
{
// if(bRaw) return ReadBase64RawInChunks(xr);
string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr));
if (string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray;
return Convert.FromBase64String(str);
}
/* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3];
private byte[] ReadBase64RawInChunks(XmlReader xr)
{
xr.MoveToContent();
List<byte[]> lParts = new List<byte[]>();
byte[] pbBuf = m_pbBase64ReadBuf;
while(true)
{
int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length);
if(cb == 0) break;
byte[] pb = new byte[cb];
Array.Copy(pbBuf, 0, pb, 0, cb);
lParts.Add(pb);
// No break when cb < pbBuf.Length, because ReadElementContentAsBase64
// moves to the next XML node only when returning 0
}
m_bReadNextNode = false;
if(lParts.Count == 0) return MemUtil.EmptyByteArray;
if(lParts.Count == 1) return lParts[0];
long cbRes = 0;
for(int i = 0; i < lParts.Count; ++i)
cbRes += lParts[i].Length;
byte[] pbRes = new byte[cbRes];
int cbCur = 0;
for(int i = 0; i < lParts.Count; ++i)
{
Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length);
cbCur += lParts[i].Length;
}
return pbRes;
} */
private bool ReadBool(XmlReader xr, bool bDefault)
{
string str = ReadString(xr);
@@ -793,9 +851,9 @@ namespace KeePassLib.Serialization
private PwUuid ReadUuid(XmlReader xr)
{
string str = ReadString(xr);
if(string.IsNullOrEmpty(str)) return PwUuid.Zero;
return new PwUuid(Convert.FromBase64String(str));
byte[] pb = ReadBase64(xr, false);
if (pb.Length == 0) return PwUuid.Zero;
return new PwUuid(pb);
}
private int ReadInt(XmlReader xr, int nDefault)
@@ -862,8 +920,7 @@ namespace KeePassLib.Serialization
// long l = ReadLong(xr, -1);
// if(l != -1) return DateTime.FromBinary(l);
string str = ReadString(xr);
byte[] pb = Convert.FromBase64String(str);
byte[] pb = ReadBase64(xr, false);
if (pb.Length != 8)
{
Debug.Assert(false);
@@ -907,7 +964,11 @@ namespace KeePassLib.Serialization
private ProtectedString ReadProtectedString(XmlReader xr)
{
XorredBuffer xb = ProcessNode(xr);
if(xb != null) return new ProtectedString(true, xb);
if (xb != null)
{
try { return new ProtectedString(true, xb); }
finally { xb.Dispose(); }
}
bool bProtect = false;
if (m_format == KdbxFormat.PlainXml)
@@ -919,8 +980,7 @@ namespace KeePassLib.Serialization
}
}
ProtectedString ps = new ProtectedString(bProtect, ReadString(xr));
return ps;
return new ProtectedString(bProtect, ReadString(xr));
}
private ProtectedBinary ReadProtectedBinary(XmlReader xr)
@@ -962,13 +1022,13 @@ namespace KeePassLib.Serialization
if (xb != null)
{
Debug.Assert(!bCompressed); // See SubWriteValue(ProtectedBinary value)
return new ProtectedBinary(true, xb);
try { return new ProtectedBinary(true, xb); }
finally { xb.Dispose(); }
}
string strValue = ReadString(xr);
if(strValue.Length == 0) return new ProtectedBinary();
byte[] pbData = ReadBase64(xr, true);
if (pbData.Length == 0) return new ProtectedBinary();
byte[] pbData = Convert.FromBase64String(strValue);
if (bCompressed) pbData = MemUtil.Decompress(pbData);
return new ProtectedBinary(false, pbData);
}
@@ -976,28 +1036,37 @@ namespace KeePassLib.Serialization
private void ReadUnknown(XmlReader xr)
{
Debug.Assert(false); // Unknown node!
Debug.Assert(xr.NodeType == XmlNodeType.Element);
if(xr.IsEmptyElement) return;
bool bRead = false;
int cOpen = 0;
string strUnknownName = xr.Name;
ProcessNode(xr);
while(xr.Read())
do
{
if(xr.NodeType == XmlNodeType.EndElement) break;
if(xr.NodeType != XmlNodeType.Element) continue;
if (bRead) xr.Read();
bRead = true;
ReadUnknown(xr);
if (xr.NodeType == XmlNodeType.EndElement) --cOpen;
else if (xr.NodeType == XmlNodeType.Element)
{
if (!xr.IsEmptyElement)
{
XorredBuffer xb = ProcessNode(xr);
if (xb != null) { xb.Dispose(); bRead = m_bReadNextNode; continue; }
++cOpen;
}
}
}
while (cOpen > 0);
Debug.Assert(xr.Name == strUnknownName);
m_bReadNextNode = bRead;
}
private XorredBuffer ProcessNode(XmlReader xr)
{
// Debug.Assert(xr.NodeType == XmlNodeType.Element);
XorredBuffer xb = null;
if (xr.HasAttributes)
{
if (xr.MoveToAttribute(AttrProtected))
@@ -1005,21 +1074,16 @@ namespace KeePassLib.Serialization
if (xr.Value == ValTrue)
{
xr.MoveToElement();
string strEncrypted = ReadStringRaw(xr);
byte[] pbEncrypted;
if(strEncrypted.Length > 0)
pbEncrypted = Convert.FromBase64String(strEncrypted);
else pbEncrypted = MemUtil.EmptyByteArray;
byte[] pbCT = ReadBase64(xr, true);
byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbCT.Length);
byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length);
xb = new XorredBuffer(pbEncrypted, pbPad);
return new XorredBuffer(pbCT, pbPad);
}
}
}
return xb;
return null;
}
private static KdbContext SwitchContext(KdbContext ctxCurrent,

View File

@@ -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
@@ -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;
@@ -149,7 +150,6 @@ namespace KeePassLib.Serialization
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
}
else // KDBX >= 4
@@ -176,6 +176,7 @@ namespace KeePassLib.Serialization
if ((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed);
}
lStreams.Add(sPlain);
if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
@@ -190,7 +191,11 @@ namespace KeePassLib.Serialization
}
else if (fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
else
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("fmt");
}
if (fmt == KdbxFormat.Default)
{
@@ -203,23 +208,22 @@ namespace KeePassLib.Serialization
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();
@@ -237,8 +241,8 @@ namespace KeePassLib.Serialization
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
}
// ReadXmlDom(sXml);
}
// ReadXmlDom(sXml);
catch (CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
@@ -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
@@ -430,7 +434,7 @@ namespace KeePassLib.Serialization
default:
Debug.Assert(false);
if (m_slLogger != null)
m_slLogger.SetText(KLRes.UnknownHeaderId + @": " +
m_slLogger.SetText(KLRes.UnknownHeaderId + ": " +
kdbID.ToString() + "!", LogStatusType.Warning);
break;
}
@@ -487,6 +491,7 @@ namespace KeePassLib.Serialization
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);
@@ -526,6 +531,29 @@ namespace KeePassLib.Serialization
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();
}
}
}

View File

@@ -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>
@@ -89,6 +87,7 @@ namespace KeePassLib.Serialization
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;
@@ -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,7 +264,6 @@ namespace KeePassLib.Serialization
CleanUpInnerRandomStream();
m_xmlWriter = null;
m_pbHashOfHeader = null;
}
@@ -449,14 +453,16 @@ namespace KeePassLib.Serialization
++uCurEntry;
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();
throw new OperationCanceledException();
while (groupStack.Count > 1)
{
@@ -541,6 +547,16 @@ namespace KeePassLib.Serialization
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
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);
}
@@ -565,8 +581,16 @@ namespace KeePassLib.Serialization
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);
@@ -616,7 +640,7 @@ namespace KeePassLib.Serialization
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();
}
@@ -636,7 +660,7 @@ 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)
@@ -690,7 +714,13 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(name);
foreach (KeyValuePair<string, string> kvp in value)
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp);
{
DateTime? odtLastMod = null;
if (m_uFileVersion >= FileVersion32_4_1)
odtLastMod = value.GetLastModificationTime(kvp.Key);
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp, odtLastMod);
}
m_xmlWriter.WriteEndElement();
}
@@ -701,15 +731,23 @@ namespace KeePassLib.Serialization
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();
}
@@ -809,18 +847,22 @@ namespace KeePassLib.Serialization
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();
}
@@ -857,9 +899,9 @@ namespace KeePassLib.Serialization
{
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,7 +911,7 @@ 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)
@@ -880,8 +922,7 @@ namespace KeePassLib.Serialization
// page area
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);
}
@@ -949,9 +990,9 @@ namespace KeePassLib.Serialization
{
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
{
@@ -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)
{
@@ -1020,60 +1080,15 @@ namespace KeePassLib.Serialization
// 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;
}
}

View File

@@ -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
{
@@ -356,8 +319,8 @@ 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
{
@@ -365,7 +328,7 @@ namespace KeePassLib.Serialization
foreach (char ch in PwDatabase.LocalizedAppName)
uTest = uTest * 5 + ch;
m_bLocalizedNames = (uTest != NeutralLanguageID);
g_bLocalizedNames = (uTest != NeutralLanguageID);
}
}
@@ -373,40 +336,75 @@ namespace KeePassLib.Serialization
{
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();
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
return Math.Max(minRequiredVersion, GetMinKdbxVersionOrig());
}
AesKdf kdfAes = new AesKdf();
if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid))
return Math.Max(FileVersion32, minRequiredVersion);
private uint GetMinKdbxVersionOrig()
{
if (m_uForceVersion != 0) return m_uForceVersion;
if(m_pwDatabase.PublicCustomData.Count > 0)
return Math.Max(FileVersion32, minRequiredVersion);
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
uint uMin = 0;
bool bCustomData = false;
GroupHandler gh = delegate (PwGroup pg)
{
if (pg == null) { Debug.Assert(false); return true; }
if(pg.CustomData.Count > 0) { bCustomData = true; return false; }
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)
{
if (pe == null) { Debug.Assert(false); return true; }
if(pe.CustomData.Count > 0) { bCustomData = true; return false; }
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,
@@ -421,7 +419,7 @@ namespace KeePassLib.Serialization
Debug.Assert(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);
@@ -542,7 +540,7 @@ namespace KeePassLib.Serialization
{
if (pb == null) { Debug.Assert(false); return; }
if(string.IsNullOrEmpty(strName)) strName = "File.bin";
strName = UrlUtil.GetSafeFileName(strName);
string strPath;
int iTry = 1;
@@ -550,8 +548,8 @@ 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)
@@ -564,17 +562,9 @@ namespace KeePassLib.Serialization
}
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); }
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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
@@ -24,7 +24,6 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
#if !KeePassUAP
using System.Drawing;
@@ -32,6 +31,7 @@ using System.Security.Cryptography;
#endif
using KeePassLib.Collections;
using KeePassLib.Cryptography;
using KeePassLib.Cryptography.PasswordGenerator;
using KeePassLib.Native;
using KeePassLib.Security;
@@ -43,45 +43,41 @@ namespace KeePassLib.Utility
/// </summary>
public sealed class CharStream
{
private string m_strString = string.Empty;
private int m_nPos = 0;
private readonly string m_str;
private int m_iPos = 0;
public long Position
{
get { return m_iPos; }
set
{
if ((value < 0) || (value > int.MaxValue))
throw new ArgumentOutOfRangeException("value");
m_iPos = (int)value;
}
}
public CharStream(string str)
{
Debug.Assert(str != null);
if(str == null) throw new ArgumentNullException("str");
if (str == null) { Debug.Assert(false); throw new ArgumentNullException("str"); }
m_strString = str;
}
public void Seek(SeekOrigin org, int nSeek)
{
if(org == SeekOrigin.Begin)
m_nPos = nSeek;
else if(org == SeekOrigin.Current)
m_nPos += nSeek;
else if(org == SeekOrigin.End)
m_nPos = m_strString.Length + nSeek;
m_str = str;
}
public char ReadChar()
{
if(m_nPos < 0) return char.MinValue;
if(m_nPos >= m_strString.Length) return char.MinValue;
if (m_iPos >= m_str.Length) return char.MinValue;
char chRet = m_strString[m_nPos];
++m_nPos;
return chRet;
return m_str[m_iPos++];
}
public char ReadChar(bool bSkipWhiteSpace)
{
if(bSkipWhiteSpace == false) return ReadChar();
if (!bSkipWhiteSpace) return ReadChar();
while (true)
{
char ch = ReadChar();
if ((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n'))
return ch;
}
@@ -89,29 +85,25 @@ namespace KeePassLib.Utility
public char PeekChar()
{
if(m_nPos < 0) return char.MinValue;
if(m_nPos >= m_strString.Length) return char.MinValue;
if (m_iPos >= m_str.Length) return char.MinValue;
return m_strString[m_nPos];
return m_str[m_iPos];
}
public char PeekChar(bool bSkipWhiteSpace)
{
if(bSkipWhiteSpace == false) return PeekChar();
if (!bSkipWhiteSpace) return PeekChar();
int iIndex = m_nPos;
while(true)
int i = m_iPos;
while (i < m_str.Length)
{
if(iIndex < 0) return char.MinValue;
if(iIndex >= m_strString.Length) return char.MinValue;
char ch = m_strString[iIndex];
char ch = m_str[i];
if ((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n'))
return ch;
++iIndex;
++i;
}
return char.MinValue;
}
}
@@ -187,7 +179,7 @@ namespace KeePassLib.Utility
/// </summary>
public static class StrUtil
{
public const StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase;
public static readonly StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase;
public static StringComparer CaseIgnoreComparer
{
@@ -299,23 +291,82 @@ namespace KeePassLib.Utility
return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?");
}
internal static bool RtfIsURtf(string str)
{
if (str == null) { Debug.Assert(false); return false; }
const string p = "{\\urtf"; // Typically "{\\urtf1\\ansi\\ansicpg65001"
return (str.StartsWith(p) && (str.Length > p.Length) &&
char.IsDigit(str[p.Length]));
}
public static string RtfFix(string strRtf)
{
if (strRtf == null) { Debug.Assert(false); return string.Empty; }
string str = strRtf;
// Workaround for .NET bug: the Rtf property of a RichTextBox
// can return an RTF text starting with "{\\urtf", but
// setting such an RTF text throws an exception (the setter
// checks for the RTF text to start with "{\\rtf");
// https://sourceforge.net/p/keepass/discussion/329221/thread/7788872f/
// https://www.microsoft.com/en-us/download/details.aspx?id=10725
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb774284.aspx
// https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs
if (RtfIsURtf(str)) str = str.Remove(2, 1); // Remove the 'u'
return str;
}
internal static string RtfFilterText(string strText)
{
if (strText == null) { Debug.Assert(false); return string.Empty; }
// A U+FFFC character causes the rest of the text to be lost.
// With '?', the string length, substring indices and
// character visibility remain the same.
// More special characters (unproblematic) in rich text boxes:
// https://docs.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-gettextex
return strText.Replace('\uFFFC', '?');
}
internal static bool ContainsHighChar(string str)
{
if (str == null) { Debug.Assert(false); return false; }
for (int i = 0; i < str.Length; ++i)
{
if (str[i] > '\u00FF') return true;
}
return false;
}
/// <summary>
/// Convert a string into a valid HTML sequence representing that string.
/// Convert a string to a HTML sequence representing that string.
/// </summary>
/// <param name="str">String to convert.</param>
/// <returns>String, HTML-encoded.</returns>
public static string StringToHtml(string str)
{
return StringToHtml(str, false);
}
internal static string StringToHtml(string str, bool bNbsp)
{
Debug.Assert(str != null); if (str == null) throw new ArgumentNullException("str");
str = str.Replace(@"&", @"&amp;");
str = str.Replace(@"&", @"&amp;"); // Must be first
str = str.Replace(@"<", @"&lt;");
str = str.Replace(@">", @"&gt;");
str = str.Replace("\"", @"&quot;");
str = str.Replace("\'", @"&#39;");
if (bNbsp) str = str.Replace(" ", @"&nbsp;"); // Before <br />
str = NormalizeNewLines(str, false);
str = str.Replace("\n", @"<br />\n" );
str = str.Replace("\n", @"<br />" + MessageService.NewLine);
return str;
}
@@ -357,46 +408,6 @@ namespace KeePassLib.Utility
return str;
}
/// <summary>
/// Split up a command line into application and argument.
/// </summary>
/// <param name="strCmdLine">Command line to split.</param>
/// <param name="strApp">Application path.</param>
/// <param name="strArgs">Arguments.</param>
public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs)
{
Debug.Assert(strCmdLine != null); if(strCmdLine == null) throw new ArgumentNullException("strCmdLine");
string str = strCmdLine.Trim();
strApp = null; strArgs = null;
if(str.StartsWith("\""))
{
int nSecond = str.IndexOf('\"', 1);
if(nSecond >= 1)
{
strApp = str.Substring(1, nSecond - 1).Trim();
strArgs = str.Remove(0, nSecond + 1).Trim();
}
}
if(strApp == null)
{
int nSpace = str.IndexOf(' ');
if(nSpace >= 0)
{
strApp = str.Substring(0, nSpace);
strArgs = str.Remove(0, nSpace).Trim();
}
else strApp = strCmdLine;
}
if(strApp == null) strApp = string.Empty;
if(strArgs == null) strArgs = string.Empty;
}
// /// <summary>
// /// Initialize an RTF document based on given font face and size.
// /// </summary>
@@ -487,41 +498,41 @@ namespace KeePassLib.Utility
public static string FormatException(Exception excp)
{
string strText = string.Empty;
string NewLine = "\n";
if(excp.Message != null)
strText += excp.Message + NewLine;
if (!string.IsNullOrEmpty(excp.Message))
strText += excp.Message + MessageService.NewLine;
#if !KeePassLibSD
if(excp.Source != null)
strText += excp.Source + NewLine;
if (!string.IsNullOrEmpty(excp.Source))
strText += excp.Source + MessageService.NewLine;
#endif
if(excp.StackTrace != null)
strText += excp.StackTrace + NewLine;
if (!string.IsNullOrEmpty(excp.StackTrace))
strText += excp.StackTrace + MessageService.NewLine;
#if !KeePassLibSD
#if !KeePassUAP
if (excp.TargetSite != null)
strText += excp.TargetSite.ToString() + NewLine;
strText += excp.TargetSite.ToString() + MessageService.NewLine;
#endif
if (excp.Data != null)
{
strText += NewLine;
strText += MessageService.NewLine;
foreach (DictionaryEntry de in excp.Data)
strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" +
NewLine;
MessageService.NewLine;
}
#endif
if (excp.InnerException != null)
{
strText += NewLine + "Inner:" + NewLine;
if(excp.InnerException.Message != null)
strText += excp.InnerException.Message + NewLine;
strText += MessageService.NewLine + "Inner:" + MessageService.NewLine;
if (!string.IsNullOrEmpty(excp.InnerException.Message))
strText += excp.InnerException.Message + MessageService.NewLine;
#if !KeePassLibSD
if(excp.InnerException.Source != null)
strText += excp.InnerException.Source + NewLine;
if (!string.IsNullOrEmpty(excp.InnerException.Source))
strText += excp.InnerException.Source + MessageService.NewLine;
#endif
if(excp.InnerException.StackTrace != null)
strText += excp.InnerException.StackTrace + NewLine;
if (!string.IsNullOrEmpty(excp.InnerException.StackTrace))
strText += excp.InnerException.StackTrace + MessageService.NewLine;
#if !KeePassLibSD
#if !KeePassUAP
if (excp.InnerException.TargetSite != null)
@@ -530,10 +541,10 @@ namespace KeePassLib.Utility
if (excp.InnerException.Data != null)
{
strText += NewLine;
strText += MessageService.NewLine;
foreach (DictionaryEntry de in excp.InnerException.Data)
strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" +
NewLine;
MessageService.NewLine;
}
#endif
}
@@ -674,19 +685,29 @@ namespace KeePassLib.Utility
#endif
}
public static string CompactString3Dots(string strText, int nMaxChars)
public static string CompactString3Dots(string strText, int cchMax)
{
Debug.Assert(strText != null);
if (strText == null) throw new ArgumentNullException("strText");
Debug.Assert(nMaxChars >= 0);
if(nMaxChars < 0) throw new ArgumentOutOfRangeException("nMaxChars");
Debug.Assert(cchMax >= 0);
if (cchMax < 0) throw new ArgumentOutOfRangeException("cchMax");
if(nMaxChars == 0) return string.Empty;
if(strText.Length <= nMaxChars) return strText;
if (strText.Length <= cchMax) return strText;
if(nMaxChars <= 3) return strText.Substring(0, nMaxChars);
if (cchMax == 0) return string.Empty;
if (cchMax <= 3) return new string('.', cchMax);
return strText.Substring(0, nMaxChars - 3) + "...";
return (strText.Substring(0, cchMax - 3) + "...");
}
private static readonly char[] g_vDots = new char[] { '.', '\u2026' };
private static readonly char[] g_vDotsWS = new char[] { '.', '\u2026',
' ', '\t', '\r', '\n' };
internal static string TrimDots(string strText, bool bTrimWhiteSpace)
{
if (strText == null) { Debug.Assert(false); return string.Empty; }
return strText.Trim(bTrimWhiteSpace ? g_vDotsWS : g_vDots);
}
public static string GetStringBetween(string strText, int nStartIndex,
@@ -719,7 +740,7 @@ namespace KeePassLib.Utility
/// <summary>
/// Removes all characters that are not valid XML characters,
/// according to http://www.w3.org/TR/xml/#charsets .
/// according to https://www.w3.org/TR/xml/#charsets .
/// </summary>
/// <param name="strText">Source text.</param>
/// <returns>Text containing only valid XML characters.</returns>
@@ -915,10 +936,10 @@ namespace KeePassLib.Utility
for (char ch = 'A'; ch <= 'Z'; ++ch)
{
string strEnhAcc = @"(&" + ch.ToString() + @")";
string strEnhAcc = @"(&" + ch.ToString() + ")";
if (str.IndexOf(strEnhAcc) >= 0)
{
str = str.Replace(@" " + strEnhAcc, string.Empty);
str = str.Replace(" " + strEnhAcc, string.Empty);
str = str.Replace(strEnhAcc, string.Empty);
}
}
@@ -931,35 +952,24 @@ namespace KeePassLib.Utility
public static string AddAccelerator(string strMenuText,
List<char> lAvailKeys)
{
if(strMenuText == null) { Debug.Assert(false); return null; }
if (strMenuText == null) { Debug.Assert(false); return string.Empty; }
if (lAvailKeys == null) { Debug.Assert(false); return strMenuText; }
int xa = -1, xs = 0;
for (int i = 0; i < strMenuText.Length; ++i)
{
char ch = strMenuText[i];
char ch = char.ToLowerInvariant(strMenuText[i]);
#if KeePassLibSD
char chUpper = char.ToUpper(ch);
#else
char chUpper = char.ToUpperInvariant(ch);
#endif
xa = lAvailKeys.IndexOf(chUpper);
if(xa >= 0) { xs = i; break; }
#if KeePassLibSD
char chLower = char.ToLower(ch);
#else
char chLower = char.ToLowerInvariant(ch);
#endif
xa = lAvailKeys.IndexOf(chLower);
if(xa >= 0) { xs = i; break; }
for (int j = 0; j < lAvailKeys.Count; ++j)
{
if (char.ToLowerInvariant(lAvailKeys[j]) == ch)
{
lAvailKeys.RemoveAt(j);
return strMenuText.Insert(i, @"&");
}
}
}
if(xa < 0) return strMenuText;
lAvailKeys.RemoveAt(xa);
return strMenuText.Insert(xs, @"&");
return strMenuText;
}
public static string EncodeMenuText(string strText)
@@ -1021,7 +1031,7 @@ namespace KeePassLib.Utility
}
#if !KeePassLibSD
private static readonly char[] m_vPatternPartsSep = new char[] { '*' };
private static readonly char[] g_vPatternPartsSep = new char[] { '*' };
public static bool SimplePatternMatch(string strPattern, string strText,
StringComparison sc)
{
@@ -1030,23 +1040,18 @@ namespace KeePassLib.Utility
if (strPattern.IndexOf('*') < 0) return strText.Equals(strPattern, sc);
string[] vPatternParts = strPattern.Split(m_vPatternPartsSep,
string[] vPatternParts = strPattern.Split(g_vPatternPartsSep,
StringSplitOptions.RemoveEmptyEntries);
if (vPatternParts == null) { Debug.Assert(false); return true; }
if (vPatternParts.Length == 0) return true;
if (strText.Length == 0) return false;
if(!strPattern.StartsWith(@"*") && !strText.StartsWith(vPatternParts[0], sc))
{
if ((strPattern[0] != '*') && !strText.StartsWith(vPatternParts[0], sc))
return false;
}
if(!strPattern.EndsWith(@"*") && !strText.EndsWith(vPatternParts[
vPatternParts.Length - 1], sc))
{
if ((strPattern[strPattern.Length - 1] != '*') && !strText.EndsWith(
vPatternParts[vPatternParts.Length - 1], sc))
return false;
}
int iOffset = 0;
for (int i = 0; i < vPatternParts.Length; ++i)
@@ -1123,32 +1128,58 @@ namespace KeePassLib.Utility
return str;
}
private static char[] m_vNewLineChars = null;
public static void NormalizeNewLines(ProtectedStringDictionary dict,
bool bWindows)
{
if (dict == null) { Debug.Assert(false); return; }
if(m_vNewLineChars == null)
m_vNewLineChars = new char[]{ '\r', '\n' };
List<string> vKeys = dict.GetKeys();
foreach(string strKey in vKeys)
List<string> lKeys = dict.GetKeys();
foreach (string strKey in lKeys)
{
ProtectedString ps = dict.Get(strKey);
if (ps == null) { Debug.Assert(false); continue; }
string strValue = ps.ReadString();
if(strValue.IndexOfAny(m_vNewLineChars) < 0) continue;
char[] v = ps.ReadChars();
if (!IsNewLineNormalized(v, bWindows))
dict.Set(strKey, new ProtectedString(ps.IsProtected,
NormalizeNewLines(strValue, bWindows)));
NormalizeNewLines(ps.ReadString(), bWindows)));
MemUtil.ZeroArray<char>(v);
}
}
internal static bool IsNewLineNormalized(char[] v, bool bWindows)
{
if (v == null) { Debug.Assert(false); return true; }
if (bWindows)
{
int iFreeCr = -2; // Must be < -1 (for test "!= (i - 1)")
for (int i = 0; i < v.Length; ++i)
{
char ch = v[i];
if (ch == '\r')
{
if (iFreeCr >= 0) return false;
iFreeCr = i;
}
else if (ch == '\n')
{
if (iFreeCr != (i - 1)) return false;
iFreeCr = -2; // Consume \r
}
}
return (iFreeCr < 0); // Ensure no \r at end
}
return (Array.IndexOf<char>(v, '\r') < 0);
}
public static string GetNewLineSeq(string str)
{
if(str == null) { Debug.Assert(false); return "\n"; }
if (str == null) { Debug.Assert(false); return MessageService.NewLine; }
int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0;
char chLast = char.MinValue;
@@ -1170,7 +1201,7 @@ namespace KeePassLib.Utility
nLf -= nCrLf;
int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf));
if(nMax == 0) return "\n";
if (nMax == 0) return MessageService.NewLine;
if (nCrLf == nMax) return "\r\n";
return ((nLf == nMax) ? "\n" : "\r");
@@ -1301,7 +1332,15 @@ namespace KeePassLib.Utility
try
{
throw new NotSupportedException();
byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText);
byte[] pbEnc = CryptoUtil.ProtectData(pbPlain, m_pbOptEnt,
DataProtectionScope.CurrentUser);
#if (!KeePassLibSD && !KeePassUAP)
return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None);
#else
return Convert.ToBase64String(pbEnc);
#endif
}
catch (Exception) { Debug.Assert(false); }
@@ -1315,8 +1354,10 @@ namespace KeePassLib.Utility
try
{
byte[] pbEnc = Convert.FromBase64String(strCipherText);
throw new NotSupportedException();
byte[] pbPlain = CryptoUtil.UnprotectData(pbEnc, m_pbOptEnt,
DataProtectionScope.CurrentUser);
return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length);
}
catch (Exception) { Debug.Assert(false); }
@@ -1355,27 +1396,98 @@ namespace KeePassLib.Utility
return v;
}
private static readonly char[] m_vTagSep = new char[] { ',', ';', ':' };
public static string TagsToString(List<string> vTags, bool bForDisplay)
private static readonly char[] g_vTagSep = new char[] { ',', ';' };
internal static string NormalizeTag(string strTag)
{
if(vTags == null) throw new ArgumentNullException("vTags");
if (strTag == null) { Debug.Assert(false); return string.Empty; }
strTag = strTag.Trim();
for (int i = g_vTagSep.Length - 1; i >= 0; --i)
strTag = strTag.Replace(g_vTagSep[i], '.');
return strTag;
}
internal static void NormalizeTags(List<string> lTags)
{
if (lTags == null) { Debug.Assert(false); return; }
bool bRemoveNulls = false;
for (int i = lTags.Count - 1; i >= 0; --i)
{
string str = NormalizeTag(lTags[i]);
if (string.IsNullOrEmpty(str))
{
lTags[i] = null;
bRemoveNulls = true;
}
else lTags[i] = str;
}
if (bRemoveNulls)
{
Predicate<string> f = delegate (string str) { return (str == null); };
lTags.RemoveAll(f);
}
if (lTags.Count >= 2)
{
// Deduplicate
Dictionary<string, bool> d = new Dictionary<string, bool>();
for (int i = lTags.Count - 1; i >= 0; --i)
d[lTags[i]] = true;
if (d.Count != lTags.Count)
{
lTags.Clear();
lTags.AddRange(d.Keys);
}
lTags.Sort(StrUtil.CompareNaturally);
}
}
internal static void AddTags(List<string> lTags, IEnumerable<string> eNewTags)
{
if (lTags == null) { Debug.Assert(false); return; }
if (eNewTags == null) { Debug.Assert(false); return; }
lTags.AddRange(eNewTags);
NormalizeTags(lTags);
}
public static string TagsToString(List<string> lTags, bool bForDisplay)
{
if (lTags == null) throw new ArgumentNullException("lTags");
#if DEBUG
// The input should be normalized
foreach (string str in lTags) { Debug.Assert(NormalizeTag(str) == str); }
List<string> l = new List<string>(lTags);
NormalizeTags(l);
Debug.Assert(l.Count == lTags.Count);
#endif
int n = lTags.Count;
if (n == 0) return string.Empty;
if (n == 1) return (lTags[0] ?? string.Empty);
StringBuilder sb = new StringBuilder();
bool bFirst = true;
foreach(string strTag in vTags)
foreach (string strTag in lTags)
{
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); continue; }
Debug.Assert(strTag.IndexOfAny(m_vTagSep) < 0);
if(!bFirst)
if (bFirst) bFirst = false;
else
{
if (bForDisplay) sb.Append(", ");
else sb.Append(';');
}
sb.Append(strTag);
bFirst = false;
sb.Append(strTag);
}
return sb.ToString();
@@ -1388,13 +1500,9 @@ namespace KeePassLib.Utility
List<string> lTags = new List<string>();
if (strTags.Length == 0) return lTags;
string[] vTags = strTags.Split(m_vTagSep);
foreach(string strTag in vTags)
{
string strFlt = strTag.Trim();
if(strFlt.Length > 0) lTags.Add(strFlt);
}
lTags.AddRange(strTags.Split(g_vTagSep));
NormalizeTags(lTags);
return lTags;
}
@@ -1485,8 +1593,8 @@ namespace KeePassLib.Utility
string str = strMulti;
str = str.Replace("\r\n", " ");
str = str.Replace("\r", " ");
str = str.Replace("\n", " ");
str = str.Replace('\r', ' ');
str = str.Replace('\n', ' ');
return str;
}
@@ -1506,14 +1614,16 @@ namespace KeePassLib.Utility
if (((ch == ' ') || (ch == '\t') || (ch == '\r') ||
(ch == '\n')) && !bQuoted)
{
if(sbTerm.Length > 0) l.Add(sbTerm.ToString());
if (sbTerm.Length != 0)
{
l.Add(sbTerm.ToString());
sbTerm.Remove(0, sbTerm.Length);
}
}
else if (ch == '\"') bQuoted = !bQuoted;
else sbTerm.Append(ch);
}
if(sbTerm.Length > 0) l.Add(sbTerm.ToString());
if (sbTerm.Length != 0) l.Add(sbTerm.ToString());
return l;
}
@@ -1529,10 +1639,10 @@ namespace KeePassLib.Utility
return IsDataUri(strUri, null);
}
public static bool IsDataUri(string strUri, string strReqMimeType)
public static bool IsDataUri(string strUri, string strReqMediaType)
{
if (strUri == null) { Debug.Assert(false); return false; }
// strReqMimeType may be null
// strReqMediaType may be null
const string strPrefix = "data:";
if (!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp))
@@ -1541,14 +1651,14 @@ namespace KeePassLib.Utility
int iC = strUri.IndexOf(',');
if (iC < 0) return false;
if(!string.IsNullOrEmpty(strReqMimeType))
if (!string.IsNullOrEmpty(strReqMediaType))
{
int iS = strUri.IndexOf(';', 0, iC);
int iTerm = ((iS >= 0) ? iS : iC);
string strMime = strUri.Substring(strPrefix.Length,
string strMedia = strUri.Substring(strPrefix.Length,
iTerm - strPrefix.Length);
if(!strMime.Equals(strReqMimeType, StrUtil.CaseIgnoreCmp))
if (!strMedia.Equals(strReqMediaType, StrUtil.CaseIgnoreCmp))
return false;
}
@@ -1559,20 +1669,20 @@ namespace KeePassLib.Utility
/// Create a data URI (according to RFC 2397).
/// </summary>
/// <param name="pbData">Data to encode.</param>
/// <param name="strMimeType">Optional MIME type. If <c>null</c>,
/// <param name="strMediaType">Optional MIME type. If <c>null</c>,
/// an appropriate type is used.</param>
/// <returns>Data URI.</returns>
public static string DataToDataUri(byte[] pbData, string strMimeType)
public static string DataToDataUri(byte[] pbData, string strMediaType)
{
if (pbData == null) throw new ArgumentNullException("pbData");
if(strMimeType == null) strMimeType = "application/octet-stream";
if (strMediaType == null) strMediaType = "application/octet-stream";
#if (!KeePassLibSD && !KeePassUAP)
return ("data:" + strMimeType + ";base64," + Convert.ToBase64String(
return ("data:" + strMediaType + ";base64," + Convert.ToBase64String(
pbData, Base64FormattingOptions.None));
#else
return ("data:" + strMimeType + ";base64," + Convert.ToBase64String(
return ("data:" + strMediaType + ";base64," + Convert.ToBase64String(
pbData));
#endif
}
@@ -1615,6 +1725,54 @@ namespace KeePassLib.Utility
return pb;
}
// https://www.iana.org/assignments/media-types/media-types.xhtml
private static readonly string[] g_vMediaTypePfx = new string[] {
"application/", "audio/", "example/", "font/", "image/",
"message/", "model/", "multipart/", "text/", "video/"
};
internal static bool IsMediaType(string str)
{
if (str == null) { Debug.Assert(false); return false; }
if (str.Length == 0) return false;
foreach (string strPfx in g_vMediaTypePfx)
{
if (str.StartsWith(strPfx, StrUtil.CaseIgnoreCmp))
return true;
}
return false;
}
internal static string GetCustomMediaType(string strFormat)
{
if (strFormat == null)
{
Debug.Assert(false);
return "application/octet-stream";
}
if (IsMediaType(strFormat)) return strFormat;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strFormat.Length; ++i)
{
char ch = strFormat[i];
if (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) ||
((ch >= '0') && (ch <= '9')))
sb.Append(ch);
else if ((sb.Length != 0) && ((ch == '-') || (ch == '_')))
sb.Append(ch);
else { Debug.Assert(false); }
}
if (sb.Length == 0) return "application/octet-stream";
return ("application/vnd." + PwDefs.ShortProductName +
"." + sb.ToString());
}
/// <summary>
/// Remove placeholders from a string (wrapped in '{' and '}').
/// This doesn't remove environment variables (wrapped in '%').
@@ -1735,6 +1893,50 @@ namespace KeePassLib.Utility
return str.Replace('\0', ' ');
}
// https://sourceforge.net/p/keepass/discussion/329220/thread/f98dece5/
internal static string EnsureLtrPath(string strPath)
{
if (strPath == null) { Debug.Assert(false); return string.Empty; }
string str = strPath;
// U+200E = left-to-right mark
str = str.Replace("\\", "\\\u200E");
str = str.Replace("/", "/\u200E");
str = str.Replace("\u200E\u200E", "\u200E"); // Remove duplicates
return str;
}
internal static bool IsValid(string str)
{
if (str == null) { Debug.Assert(false); return false; }
int cc = str.Length;
for (int i = 0; i < cc; ++i)
{
char ch = str[i];
if (ch == '\0') return false;
if (char.IsLowSurrogate(ch)) return false;
if (char.IsHighSurrogate(ch))
{
if (++i >= cc) return false; // High surrogate at end
if (!char.IsLowSurrogate(str[i])) return false;
UnicodeCategory uc2 = char.GetUnicodeCategory(str, i - 1);
if (uc2 == UnicodeCategory.OtherNotAssigned) return false;
continue;
}
UnicodeCategory uc = char.GetUnicodeCategory(ch);
if (uc == UnicodeCategory.OtherNotAssigned) return false;
}
return true;
}
internal static string RemoveWhiteSpace(string str)
{
if (str == null) { Debug.Assert(false); return string.Empty; }

View File

@@ -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:
@@ -65,7 +91,7 @@ namespace KeePassLib.Utility
Debug.Assert(strFile != null);
if (strFile == null) throw new ArgumentNullException("strFile");
int nLastSep = strFile.LastIndexOfAny(m_vDirSeps);
int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars);
if (nLastSep < 0) return string.Empty; // No directory
if (bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
@@ -89,7 +115,7 @@ namespace KeePassLib.Utility
{
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;
@@ -106,7 +132,7 @@ namespace KeePassLib.Utility
{
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;
@@ -123,7 +149,7 @@ namespace KeePassLib.Utility
{
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;
@@ -148,11 +174,8 @@ namespace KeePassLib.Utility
if (nLength <= 0) return string.Empty;
char chLast = strPath[nLength - 1];
for(int i = 0; i < m_vDirSeps.Length; ++i)
{
if(chLast == m_vDirSeps[i]) return strPath;
}
if (Array.IndexOf<char>(UrlUtil.DirSepChars, chLast) >= 0)
return strPath;
if (bUrl) return (strPath + '/');
return (strPath + UrlUtil.LocalDirSepChar);
@@ -216,21 +239,35 @@ 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;
string str = strPath.Trim();
if (str.Length <= 1) return str;
if (str[0] != '\"') return str;
int iSecond = str.IndexOf('\"', 1);
int iSecond = IndexOfSecondEnclQuote(str);
if (iSecond <= 0) return str;
return str.Substring(1, iSecond - 1);
@@ -238,21 +275,30 @@ namespace KeePassLib.Utility
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");
@@ -272,7 +318,7 @@ 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");
@@ -315,9 +361,8 @@ namespace KeePassLib.Utility
#if (!KeePassLibSD && !KeePassUAP)
if (NativeLib.IsUnix())
#endif
{
#endif
bool bBaseUnc = IsUncPath(strBaseFile);
bool bTargetUnc = IsUncPath(strTargetFile);
if ((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
@@ -325,8 +370,8 @@ namespace KeePassLib.Utility
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)) &&
@@ -352,8 +397,8 @@ 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();
@@ -403,13 +448,14 @@ namespace KeePassLib.Utility
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));
@@ -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
}
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);
@@ -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;
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)
@@ -525,26 +568,54 @@ namespace KeePassLib.Utility
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.
@@ -659,13 +730,11 @@ namespace KeePassLib.Utility
foreach (string strPathRaw in v)
{
if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
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;
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
l.Add(strPathRaw);
}
}
@@ -698,13 +767,11 @@ namespace KeePassLib.Utility
if (fi == null) { Debug.Assert(false); continue; }
string strPathRaw = fi.FullName;
if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
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;
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
l.Add(fi);
}
}
@@ -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;
} */
}
}

View File

@@ -247,12 +247,14 @@ public class PCloudFileStorage extends JavaFileStorageBase
}
private void handleAuthResult(FileStorageSetupActivity activity, AuthorizationData authorizationData) {
if (authorizationData.result == AuthorizationResult.ACCESS_GRANTED) {
String authToken = authorizationData.token;
String apiHost = authorizationData.apiHost;
setAuthToken(authToken, apiHost);
finishActivityWithSuccess(activity);
} else {
android.util.Log.d("KP2A", "Auth failed with " + authorizationData.result.toString() + ", code=" + authorizationData.authCode + ", error=" + authorizationData.errorMessage);
Activity castedActivity = (Activity)activity;
Intent resultData = new Intent();
resultData.putExtra(EXTRA_ERROR_MESSAGE, "Authentication failed!");

View File

@@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="C:\Program Files\Android\Android Studio1\gradle\gradle-2.10" />
@@ -17,7 +20,7 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -627,6 +627,10 @@ public class KP2AKeyboard extends InputMethodService
updateShowKp2aMode();
Log.d("KP2AK", "updateKeyboardMode -> setKM");
Log.d("KP2AK", "variation = " + variation);
Log.d("KP2AK", "input type = " + attribute.inputType);
if ((mShowKp2aKeyboard) && (mKp2aEnableSimpleKeyboard))
{
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_KP2A, attribute.imeOptions);

View File

@@ -84,9 +84,9 @@
<string name="settings_key_mode_always_hide_name">Ocultar siempre</string>
<!-- Array of the settings key modes -->
<string-array name="settings_key_modes">
<item>@string/modo-de-configuracion-de-clave-nombramiento-automatico</item>
<item>@string/modo-de-configuracion-de-clave-siempre-mostrar-nombre</item>
<item>@string/modo-de-configuracion-de-clave-siempre-ocultar-nombre</item>
<item>@string/settings_key_mode_auto_name</item>
<item>@string/settings_key_mode_auto_name</item>
<item>@string/settings_key_mode_auto_name</item>
</string-array>
<!-- Option to enable bigram completion -->
<string name="bigram_suggestion">Sugerencias de bigramas</string>

View File

@@ -606,11 +606,10 @@ namespace keepass2android
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
string binaryDirectory = prefs.GetString(GetString(Resource.String.BinaryDirectory_key),
GetString(Resource.String.BinaryDirectory_default));
if (writeToCacheDirectory)
{
binaryDirectory = CacheDir.Path + File.Separator + AttachmentContentProvider.AttachmentCacheSubDir;
string binaryDirectory = CacheDir.Path + File.Separator + AttachmentContentProvider.AttachmentCacheSubDir;
string filepart = key;
Java.Lang.String javaFilename = new Java.Lang.String(filepart);
@@ -888,9 +887,10 @@ namespace keepass2android
popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey]));
if (isProtected)
popupItems.Add(new ToggleVisibilityPopupMenuItem(this));
if (_stringViews[fieldKey].Text.StartsWith(KeePass.AndroidAppScheme)
if (fieldKey != PwDefs.UrlField //url already has a go-to-url menu
&& (_stringViews[fieldKey].Text.StartsWith(KeePass.AndroidAppScheme)
|| _stringViews[fieldKey].Text.StartsWith("http://")
|| _stringViews[fieldKey].Text.StartsWith("https://"))
|| _stringViews[fieldKey].Text.StartsWith("https://")))
{
popupItems.Add(new GotoUrlMenuItem(this, fieldKey));
}

View File

@@ -33,12 +33,17 @@ using KeePassLib.Security;
using Android.Content.PM;
using System.IO;
using System.Globalization;
using System.Net;
using System.Text;
using Android.Content.Res;
using Android.Database;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Util;
using keepass2android.Io;
using KeePassLib.Serialization;
using KeeTrayTOTP.Libraries;
using PluginTOTP;
using Debug = System.Diagnostics.Debug;
using File = System.IO.File;
using Object = Java.Lang.Object;
@@ -287,6 +292,30 @@ namespace keepass2android
EditAdvancedString(ees.FindViewById(Resource.Id.edit_extra));
};
SetAddExtraStringEnabled();
Button configureTotpButton = (Button)FindViewById(Resource.Id.configure_totp);
configureTotpButton.Visibility = CanConfigureOtpSettings() ? ViewStates.Gone : ViewStates.Visible;
configureTotpButton.Click += (sender, e) =>
{
bool added = false;
View ees = FindExtraEditSection("otp");
if (ees == null)
{
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
KeyValuePair<string, ProtectedString> pair =
new KeyValuePair<string, ProtectedString>("otp", new ProtectedString(true, ""));
ees = CreateExtraStringView(pair);
container.AddView(ees);
added = true;
}
EditTotpString(ees.FindViewById(Resource.Id.edit_extra));
};
FindViewById(Resource.Id.entry_extras_container).Visibility =
State.EditMode.ShowAddExtras || State.Entry.Strings.Any(s => !PwDefs.IsStandardField(s.Key)) ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.entry_binaries_container).Visibility =
@@ -402,6 +431,14 @@ namespace keepass2android
private void SetAddExtraStringEnabled()
{
((Button)FindViewById(Resource.Id.add_advanced)).Visibility = (!App.Kp2a.CurrentDb.DatabaseFormat.CanHaveCustomFields || !State.EditMode.ShowAddExtras) ? ViewStates.Gone : ViewStates.Visible;
((Button)FindViewById(Resource.Id.configure_totp)).Visibility = CanConfigureOtpSettings() ? ViewStates.Gone : ViewStates.Visible;
}
private bool CanConfigureOtpSettings()
{
return (!App.Kp2a.CurrentDb.DatabaseFormat.CanHaveCustomFields || !State.EditMode.ShowAddExtras)
&& (new Kp2aTotp().TryGetAdapter(new PwEntryOutput(State.Entry, App.Kp2a.CurrentDb)) == null || (State.Entry.Strings.GetKeys().Contains("otp"))) //only allow to edit KeeWeb/KeepassXC style otps
;
}
private void MakePasswordVisibleOrHidden()
@@ -942,6 +979,7 @@ namespace keepass2android
binariesGroup.Visibility = ViewStates.Visible;
FindViewById(Resource.Id.entry_binaries_container).Visibility = ViewStates.Visible;
((Button)FindViewById(Resource.Id.add_advanced)).Visibility = ViewStates.Visible;
((Button)FindViewById(Resource.Id.configure_totp)).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.entry_extras_container).Visibility = ViewStates.Visible;
return true;
@@ -1020,6 +1058,7 @@ namespace keepass2android
if (type == "bool")
{
RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_bool, null);
ees.Tag = pair.Key;
var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey));
var checkbox = ((CheckBox)ees.FindViewById(Resource.Id.checkbox));
var valueView = ((TextView)ees.FindViewById(Resource.Id.value));
@@ -1037,6 +1076,7 @@ namespace keepass2android
else if (type == "file")
{
RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_file, null);
ees.Tag = pair.Key;
var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey));
var titleView = ((TextView)ees.FindViewById(Resource.Id.title));
keyView.Text = pair.Key;
@@ -1054,6 +1094,7 @@ namespace keepass2android
else
{
RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section, null);
ees.Tag = pair.Key;
var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey));
var titleView = ((TextView)ees.FindViewById(Resource.Id.title));
keyView.Text = pair.Key;
@@ -1091,6 +1132,182 @@ namespace keepass2android
}
private void EditTotpString(View sender)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dlgView = LayoutInflater.Inflate(Resource.Layout.
configure_totp_dialog, null);
builder.SetView(dlgView);
builder.SetNegativeButton(Android.Resource.String.Cancel, (o, args) => { });
builder.SetPositiveButton(Android.Resource.String.Ok, (o, args) =>
{
var targetField = ((TextView)((View)sender.Parent).FindViewById(Resource.Id.value));
if (targetField != null)
{
string entryTitle = Util.GetEditText(this, Resource.Id.entry_title);
string username = Util.GetEditText(this, Resource.Id.entry_user_name);
string secret = dlgView.FindViewById<TextView>(Resource.Id.totp_secret_key).Text;
string totpLength = dlgView.FindViewById<EditText>(Resource.Id.totp_length).Text;
string timeStep = dlgView.FindViewById<EditText>(Resource.Id.totp_time_step).Text;
var checkedTotpId = (int)dlgView.FindViewById<RadioGroup>(Resource.Id.totp_encoding).CheckedRadioButtonId;
TotpEncoding encoding = (checkedTotpId == Resource.Id.totp_encoding_steam)
? TotpEncoding.Steam : (checkedTotpId == Resource.Id.totp_encoding_rfc6238 ? TotpEncoding.Default : TotpEncoding.Custom);
var algorithm = (int)dlgView.FindViewById<Spinner>(Resource.Id.totp_algorithm).SelectedItemPosition;
targetField.Text = BuildOtpString(entryTitle, username, secret, totpLength, timeStep, encoding, algorithm);
}
else
{
Toast.MakeText(this, "did not find target field", ToastLength.Long).Show();
}
//not calling State.Entry.Strings.Set(...). We only do this when the user saves the changes.
State.EntryModified = true;
});
Dialog dialog = builder.Create();
dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).CheckedChange += (o, args) =>
{
dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = args.IsChecked ? ViewStates.Visible : ViewStates.Gone;
};
//copy values from entry into dialog
View ees = (View)sender.Parent;
TotpData totpData = new Kp2aTotp().TryGetTotpData(new PwEntryOutput(State.Entry, App.Kp2a.CurrentDb));
if (totpData != null)
{
dlgView.FindViewById<TextView>(Resource.Id.totp_secret_key).Text = totpData.TotpSeed;
if (totpData.Encoder == TotpData.EncoderSteam)
{
dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_steam).Checked = true;
}
else if ((totpData.Encoder == TotpData.EncoderRfc6238) && (totpData.IsDefaultRfc6238))
{
dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_rfc6238).Checked = true;
}
else
{
dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).Checked = true;
}
dlgView.FindViewById<EditText>(Resource.Id.totp_length).Text = totpData.Length;
dlgView.FindViewById<EditText>(Resource.Id.totp_time_step).Text = totpData.Duration;
dlgView.FindViewById <Spinner>(Resource.Id.totp_algorithm).SetSelection(totpData.HashAlgorithm == TotpData.HashSha1 ? 0 : (
totpData.HashAlgorithm == TotpData.HashSha256 ? 1:
(totpData.HashAlgorithm == TotpData.HashSha256 ? 2 : 0)));
dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).Checked ? ViewStates.Visible : ViewStates.Gone;
}
_passwordFont.ApplyTo(dlgView.FindViewById<EditText>(Resource.Id.totp_secret_key));
Util.SetNoPersonalizedLearning(dlgView);
dialog.Show();
}
string SanitizeInput(string encodedData)
{
if (encodedData.Length <= 0)
{
return encodedData;
}
StringBuilder newEncodedDataBuilder = new StringBuilder(encodedData);
int i = 0;
foreach (var ch in encodedData)
{
switch (ch)
{
case '0':
newEncodedDataBuilder[i++] = 'O';
break;
case '1':
newEncodedDataBuilder[i++] = 'L';
break;
case '8':
newEncodedDataBuilder[i++] = 'B';
break;
default:
if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('2' <= ch && ch <= '7'))
{
newEncodedDataBuilder[i++] = ch;
}
break;
}
}
string newEncodedData = newEncodedDataBuilder.ToString().Substring(0, i);
return AddPadding(newEncodedData);
}
string AddPadding(string encodedData)
{
if (encodedData.Length <= 0 || encodedData.Length % 8 == 0) {
return encodedData;
}
int rBytes = encodedData.Length % 8;
// rBytes must be a member of {2, 4, 5, 7}
if (1 == rBytes || 3 == rBytes || 6 == rBytes) {
return encodedData;
}
string newEncodedData = encodedData;
for (int nPads = 8 - rBytes; nPads > 0; --nPads)
{
newEncodedData += "=";
}
return newEncodedData;
}
enum TotpEncoding
{
Default, Steam, Custom
}
private string BuildOtpString(string entryTitle, string userName, string secret, string totpLength, string timeStep, TotpEncoding encoding, int algorithm)
{
string entryEncoded = string.IsNullOrWhiteSpace(entryTitle)
? "Keepass2Android"
: System.Uri.EscapeUriString(entryTitle);
return $"otpauth://totp/{entryEncoded}:{System.Uri.EscapeUriString(userName)}?" +
$"secret={SanitizeInput(secret)}" +
$"&issuer={ entryEncoded}"
+ (encoding != TotpEncoding.Custom? "" : $"&period={timeStep}&digits={totpLength}&algorithm={AlgorithmIndexToString(algorithm)}") +
(encoding == TotpEncoding.Steam ? "&encoder=steam" : "");
}
private string AlgorithmIndexToString(in int algorithm)
{
switch (algorithm)
{
case 0:
return "SHA1";
case 1:
return "SHA256";
case 2:
return "SHA512";
default:
return "";
}
}
private void EditAdvancedString(View sender)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/totp_secret_key"
android:singleLine="true"
android:inputType="text"
android:hint="@string/totp_secret_key"
android:dropDownWidth="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/totp_encoding">
<RadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/totp_encoding_rfc6238"
android:id="@+id/totp_encoding_rfc6238" />
<RadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/totp_encoding_steam"
android:id="@+id/totp_encoding_steam" />
<RadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/totp_encoding_custom"
android:id="@+id/totp_encoding_custom" />
</RadioGroup>
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/totp_custom_settings_group">
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/totp_algorithms"
android:prompt="@string/algorithm"
android:id="@+id/totp_algorithm" />
<EditText
android:inputType="numberDecimal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/totp_time_step"
android:id="@+id/totp_time_step" />
<EditText
android:inputType="numberDecimal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/totp_length"
android:id="@+id/totp_length" />
</LinearLayout>
</LinearLayout>

View File

@@ -178,6 +178,19 @@
android:layout_height="wrap_content"
android:text="@string/add_extra_string"/>
<Button
android:id="@+id/configure_totp"
android:background="?android:selectableItemBackground"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAllCaps="true"
android:paddingLeft="8dp"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/baseline_schedule_white_24"
android:text="@string/configure_totp"/>
</LinearLayout>
</LinearLayout>
<!-- file attachments -->

View File

@@ -558,7 +558,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="FileIsTemporarilyAvailable">Die Datei ist nur kurzzeitig für Keepass2Android verfügbar.</string>
<string name="FileIsReadOnly">The gewählte Datei ist schreibgeschützt.</string>
<string name="FileIsReadOnlyOnKitkat">The gewählte Datei kann aufgrund von Beschränkungen in Android 4.4+ von Keepass2Android nicht geändert werden.</string>
<string name="CopyFileRequired">Um sie zu benutzen, musst sie an einen anderen Speicherort kopiert werden.</string>
<string name="CopyFileRequired">Um die Datei zu benutzen, muss sie an einen anderen Speicherort kopiert werden.</string>
<string name="CopyFileRequiredForEditing">Um sie zu bearbeiten, muss sie an einen anderen Speicherort kopiert werden.</string>
<string name="ClickOkToSelectLocation">Tippe auf OK, um einen Speicherort zu wählen.</string>
<string name="FileReadOnlyTitle">Datenbank ist schreibgeschützt</string>

View File

@@ -40,7 +40,7 @@
<string name="homepage">https://github.com/PhilippC/keepass2android</string>
<string name="further_author_names">Alex Vallat, Ben Rush, Matthieu, Wiktor Ławski, gilbsgilbs, Chih-Hsuan Yen, DDoSolitary, marcoDallas</string>
<string name="designer_names">Niki Hüttner (http://www.close-cut.de), Stefano Pignataro (http://www.spstudio.at)</string>
<string name="supporter_names">Arno Welzel, Sebastián Ramírez, A. Finkhäuser</string>
<string name="supporter_names">Arno Welzel, Sebastián Ramírez, A. Finkhäuser, Makoto Mizukami</string>
<string name="issues">https://github.com/PhilippC/keepass2android/issues</string>
<string name="oi_filemanager_market">market://details?id=org.openintents.filemanager</string>
<string name="oi_filemanager_web">https://openintents.googlecode.com/files/FileManager-2.0.2.apk</string>
@@ -75,7 +75,7 @@
<string name="RememberRecentFiles_key">RememberRecentFiles_key</string>
<string name="default_username_key">defaultUsername</string>
<string name="database_name_key">databaseName</string>
<string name="BinaryDirectory_key">binaryDirectory</string>
<string name="BinaryDirectory_default">/mnt/sdcard/keepass2android/binaries/</string>
<bool name="maskpass_default">true</bool>
<bool name="keyfile_default">true</bool>
@@ -177,6 +177,12 @@
<item>28</item>
</string-array>
<string-array name="totp_algorithms">
<item>SHA-1</item>
<item>SHA-256</item>
<item>SHA-512</item>
</string-array>
<string name="design_default">System</string>
<string-array name="design_values">
<item>Light</item>

View File

@@ -325,8 +325,6 @@
<string name="QuickUnlockHideLength_summary">If enabled, the length of the QuickUnlock code is not displayed on the QuickUnlock screen.</string>
<string name="QuickUnlock_fail">QuickUnlock failed: incorrect password!</string>
<string name="BinaryDirectory_title">File attachments directory</string>
<string name="BinaryDirectory_summary">Directory where file attachments are saved to.</string>
<string name="SaveAttachmentDialog_title">Save attachment</string>
<string name="SaveAttachmentDialog_text">Please select where to save the attachment.</string>
<string name="SaveAttachmentDialog_save">Export to file...</string>
@@ -348,6 +346,14 @@
<string name="protection">Protected field</string>
<string name="add_binary">Add file attachment</string>
<string name="add_extra_string">Add additional string</string>
<string name="configure_totp">Configure TOTP</string>
<string name="totp_secret_key">Secret key</string>
<string name="totp_encoding_rfc6238">Default RFC6238 token settings</string>
<string name="totp_encoding_steam">Steam token settings</string>
<string name="totp_encoding_custom">Custom token settings</string>
<string name="totp_time_step">Time step</string>
<string name="totp_length">Code length</string>
<string name="delete_extra_string">Delete additional string</string>
<string name="database_loaded_quickunlock_enabled">%1$s: Locked. QuickUnlock enabled.</string>
<string name="database_loaded_unlocked">%1$s: Unlocked.</string>

View File

@@ -509,13 +509,7 @@
android:key="@string/FileHandling_prefs_key"
android:title="@string/FileHandling_prefs"/>
<EditTextPreference
android:enabled="true"
android:persistent="true"
android:summary="@string/BinaryDirectory_summary"
android:defaultValue="@string/BinaryDirectory_default"
android:title="@string/BinaryDirectory_title"
android:key="@string/BinaryDirectory_key" />
<CheckBoxPreference
android:enabled="true"
android:persistent="true"

View File

@@ -33,9 +33,9 @@ namespace keepass2android
res.Encoder = parsedQuery.Get("encoder");
string algo = parsedQuery.Get("algorithm");
if (algo == "SHA512")
res.HashAlgorithm = "HMAC-SHA-512";
res.HashAlgorithm = TotpData.HashSha512;
if (algo == "SHA256")
res.HashAlgorithm = "HMAC-SHA-256";
res.HashAlgorithm = TotpData.HashSha256;
//set defaults according to https://github.com/google/google-authenticator/wiki/Key-Uri-Format

View File

@@ -42,7 +42,7 @@ namespace PluginTOTP
string strAlg;
entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg);
res.HashAlgorithm = strAlg;
res.TotpSecret = pbSecret;
res.Length = uLength.ToString();
res.Duration = uPeriod.ToString();

View File

@@ -19,6 +19,23 @@ namespace keepass2android
new Keepass2TotpPluginAdapter(),
};
public TotpData TryGetTotpData(PwEntryOutput entry)
{
if (entry == null)
return null;
foreach (ITotpPluginAdapter adapter in _pluginAdapters)
{
TotpData totpData = adapter.GetTotpData(entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString()), Application.Context, false);
if (totpData.IsTotpEntry)
{
return totpData;
}
}
return null;
}
public ITotpPluginAdapter TryGetAdapter(PwEntryOutput entry)
{
if (entry == null)

View File

@@ -127,7 +127,7 @@ namespace KeeTrayTOTP.Libraries
this.encoder = TOTPEncoder.rfc6238;
}
if(data.Url != null)
if(data.TimeCorrectionUrl != null)
{
{
this.TimeCorrection = TimeSpan.Zero;

View File

@@ -7,11 +7,14 @@ namespace PluginTOTP
{
public TotpData()
{
HashAlgorithm = "HMAC-SHA-1";
HashAlgorithm = HashSha1;
}
public const string EncoderSteam = "steam";
public const string EncoderRfc6238 = "rfc6238";
public const string HashSha1 = "HMAC-SHA-1";
public const string HashSha256 = "HMAC-SHA-256";
public const string HashSha512 = "HMAC-SHA-512";
public bool IsTotpEntry { get; set; }
@@ -20,12 +23,28 @@ namespace PluginTOTP
public string TotpSeed
{
set { TotpSecret = Base32.Decode(value.Trim()); }
get { return Base32.Encode(TotpSecret); }
}
public string Duration { get; set; }
public string Encoder { get; set; }
public string Length { get; set; }
public string Url { get; set; }
public string TimeCorrectionUrl { get; set; }
public string HashAlgorithm { get; set; }
public bool IsDefaultRfc6238
{
get { return Length == "6" && Duration == "30" && (HashAlgorithm == null || HashAlgorithm == HashSha1); }
}
public static TotpData MakeDefaultRfc6238()
{
return new TotpData()
{
Duration = "30",
Length = "6",
HashAlgorithm = HashSha1
};
}
}
}

View File

@@ -117,10 +117,12 @@ namespace PluginTOTP
string[] Settings = SettingsGet(entryFields);
res.Duration = Settings[0];
res.Length = Settings[1];
if (res.Length == "S")
res.Encoder = TotpData.EncoderSteam;
if (ValidUrl)
{
NoTimeCorrection = true;
res.Url = Settings[2];
res.TimeCorrectionUrl = Settings[2];
/*var CurrentTimeCorrection = TimeCorrections[Settings[2]];
if (CurrentTimeCorrection != null)
{

View File

@@ -316,9 +316,6 @@
<None Include="Resources\drawable-xhdpi\2_action_about.png">
<Visible>False</Visible>
</None>
<AndroidResource Include="Resources\layout\edit_extra_string_dialog.xml">
<SubType>AndroidResource</SubType>
</AndroidResource>
<AndroidResource Include="Resources\layout\file_storage_setup.xml">
<SubType>AndroidResource</SubType>
</AndroidResource>
@@ -2072,6 +2069,21 @@
<ItemGroup>
<AndroidResource Include="Resources\menu\menu_selectdb.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\configure_totp_dialog.xml">
<SubType>AndroidResource</SubType>
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\edit_extra_string_dialog.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\baseline_schedule_24.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\baseline_schedule_white_24.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

View File

@@ -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)