Replace (most) placeholders before copying to clipboard/keyboard

This commit is contained in:
Philipp Crocoll 2013-06-01 21:45:39 +02:00
parent 7e8e261dbb
commit 98e1727fcc
7 changed files with 1298 additions and 7 deletions

View File

@ -0,0 +1,229 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 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.Text;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using KeePass.Util.Spr;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Cryptography;
using KeePassLib.Cryptography.PasswordGenerator;
using KeePassLib.Delegates;
using KeePassLib.Security;
using KeePassLib.Utility;
using KeePassLib.Serialization;
namespace KeePass.Util
{
/// <summary>
/// This class contains various static functions for entry operations.
/// </summary>
public static class EntryUtil
{
// Old format name (<= 2.14): "KeePassEntriesCF"
public const string ClipFormatEntries = "KeePassEntriesCX";
private static byte[] AdditionalEntropy = { 0xF8, 0x03, 0xFA, 0x51, 0x87, 0x18, 0x49, 0x5D };
public static string FillPlaceholders(string strText, SprContext ctx)
{
if((ctx == null) || (ctx.Entry == null)) return strText;
string str = strText;
/*NOT SUPPORTED CURRENTLY if((ctx.Flags & SprCompileFlags.NewPassword) != SprCompileFlags.None)
str = ReplaceNewPasswordPlaceholder(str, ctx);
*/
if((ctx.Flags & SprCompileFlags.HmacOtp) != SprCompileFlags.None)
str = ReplaceHmacOtpPlaceholder(str, ctx);
return str;
}
/* private static string ReplaceNewPasswordPlaceholder(string strText,
SprContext ctx)
{
PwEntry pe = ctx.Entry;
PwDatabase pd = ctx.Database;
if((pe == null) || (pd == null)) return strText;
string str = strText;
const string strNewPwPlh = @"{NEWPASSWORD}";
if(str.IndexOf(strNewPwPlh, StrUtil.CaseIgnoreCmp) >= 0)
{
ProtectedString psAutoGen;
PwgError e = PwGenerator.Generate(out psAutoGen,
Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile,
null, Program.PwGeneratorPool);
psAutoGen = psAutoGen.WithProtection(pd.MemoryProtection.ProtectPassword);
if(e == PwgError.Success)
{
pe.CreateBackup(pd);
pe.Strings.Set(PwDefs.PasswordField, psAutoGen);
pe.Touch(true, false);
pd.Modified = true;
string strIns = SprEngine.TransformContent(psAutoGen.ReadString(), ctx);
str = StrUtil.ReplaceCaseInsensitive(str, strNewPwPlh, strIns);
}
}
return str;
}
*/
private static string ReplaceHmacOtpPlaceholder(string strText,
SprContext ctx)
{
PwEntry pe = ctx.Entry;
PwDatabase pd = ctx.Database;
if((pe == null) || (pd == null)) return strText;
string str = strText;
const string strHmacOtpPlh = @"{HMACOTP}";
if(str.IndexOf(strHmacOtpPlh, StrUtil.CaseIgnoreCmp) >= 0)
{
const string strKeyField = "HmacOtp-Secret";
const string strCounterField = "HmacOtp-Counter";
byte[] pbSecret = StrUtil.Utf8.GetBytes(pe.Strings.ReadSafe(
strKeyField));
string strCounter = pe.Strings.ReadSafe(strCounterField);
ulong uCounter;
ulong.TryParse(strCounter, out uCounter);
string strValue = HmacOtp.Generate(pbSecret, uCounter, 6, false, -1);
pe.Strings.Set(strCounterField, new ProtectedString(false,
(uCounter + 1).ToString()));
pd.Modified = true;
str = StrUtil.ReplaceCaseInsensitive(str, strHmacOtpPlh, strValue);
}
return str;
}
public static bool EntriesHaveSameParent(PwObjectList<PwEntry> v)
{
if(v == null) { Debug.Assert(false); return true; }
if(v.UCount == 0) return true;
PwGroup pg = v.GetAt(0).ParentGroup;
foreach(PwEntry pe in v)
{
if(pe.ParentGroup != pg) return false;
}
return true;
}
public static void ReorderEntriesAsInDatabase(PwObjectList<PwEntry> v,
PwDatabase pd)
{
if((v == null) || (pd == null)) { Debug.Assert(false); return; }
if(pd.RootGroup == null) { Debug.Assert(false); return; } // DB must be open
PwObjectList<PwEntry> vRem = v.CloneShallow();
v.Clear();
EntryHandler eh = delegate(PwEntry pe)
{
int p = vRem.IndexOf(pe);
if(p >= 0)
{
v.Add(pe);
vRem.RemoveAt((uint)p);
}
return true;
};
pd.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh);
foreach(PwEntry peRem in vRem) v.Add(peRem); // Entries not found
}
public static string CreateSummaryList(PwGroup pgItems, bool bStartWithNewPar)
{
List<PwEntry> l = pgItems.GetEntries(true).CloneShallowToList();
string str = CreateSummaryList(pgItems, l.ToArray());
if((str.Length == 0) || !bStartWithNewPar) return str;
return (MessageService.NewParagraph + str);
}
public static string CreateSummaryList(PwGroup pgSubGroups, PwEntry[] vEntries)
{
int nMaxEntries = 10;
string strSummary = string.Empty;
if(pgSubGroups != null)
{
PwObjectList<PwGroup> vGroups = pgSubGroups.GetGroups(true);
if(vGroups.UCount > 0)
{
StringBuilder sbGroups = new StringBuilder();
sbGroups.Append("- ");
uint uToList = Math.Min(3U, vGroups.UCount);
for(uint u = 0; u < uToList; ++u)
{
if(sbGroups.Length > 2) sbGroups.Append(", ");
sbGroups.Append(vGroups.GetAt(u).Name);
}
if(uToList < vGroups.UCount) sbGroups.Append(", ...");
strSummary += sbGroups.ToString(); // New line below
nMaxEntries -= 2;
}
}
int nSummaryShow = Math.Min(nMaxEntries, vEntries.Length);
if(nSummaryShow == (vEntries.Length - 1)) --nSummaryShow; // Plural msg
for(int iSumEnum = 0; iSumEnum < nSummaryShow; ++iSumEnum)
{
if(strSummary.Length > 0) strSummary += MessageService.NewLine;
PwEntry pe = vEntries[iSumEnum];
strSummary += ("- " + StrUtil.CompactString3Dots(
pe.Strings.ReadSafe(PwDefs.TitleField), 39));
if(PwDefs.IsTanEntry(pe))
{
string strTanIdx = pe.Strings.ReadSafe(PwDefs.UserNameField);
if(!string.IsNullOrEmpty(strTanIdx))
strSummary += (@" (#" + strTanIdx + @")");
}
}
return strSummary;
}
}
}

View File

@ -0,0 +1,209 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 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.Text;
using System.Diagnostics;
using KeePassLib;
using KeePassLib.Interfaces;
using SprRefsCache = System.Collections.Generic.Dictionary<string, string>;
namespace KeePass.Util.Spr
{
[Flags]
public enum SprCompileFlags
{
None = 0,
AppPaths = 0x1, // Paths to IE, Firefox, Opera, ...
PickChars = 0x2,
EntryStrings = 0x4,
EntryStringsSpecial = 0x8, // {URL:RMVSCM}, ...
PasswordEnc = 0x10,
Group = 0x20,
Paths = 0x40, // App-dir, doc-dir, path sep, ...
AutoType = 0x80, // Replacements like {CLEARFIELD}, ...
DateTime = 0x100,
References = 0x200,
EnvVars = 0x400,
NewPassword = 0x800,
HmacOtp = 0x1000,
Comments = 0x2000,
ExtActive = 0x4000, // Active transformations provided by plugins
ExtNonActive = 0x8000, // Non-active transformations provided by plugins
// Next free: 0x10000
All = 0xFFFF,
// Internal:
UIInteractive = SprCompileFlags.PickChars,
StateChanging = (SprCompileFlags.NewPassword | SprCompileFlags.HmacOtp),
Active = (SprCompileFlags.UIInteractive | SprCompileFlags.StateChanging |
SprCompileFlags.ExtActive),
NonActive = (SprCompileFlags.All & ~SprCompileFlags.Active),
Deref = (SprCompileFlags.EntryStrings | SprCompileFlags.EntryStringsSpecial |
SprCompileFlags.References)
}
public sealed class SprContext
{
private PwEntry m_pe = null;
public PwEntry Entry
{
get { return m_pe; }
set { m_pe = value; }
}
private PwDatabase m_pd = null;
public PwDatabase Database
{
get { return m_pd; }
set { m_pd = value; }
}
private bool m_bMakeAT = false;
public bool EncodeAsAutoTypeSequence
{
get { return m_bMakeAT; }
set { m_bMakeAT = value; }
}
private bool m_bMakeCmdQuotes = false;
public bool EncodeQuotesForCommandLine
{
get { return m_bMakeCmdQuotes; }
set { m_bMakeCmdQuotes = value; }
}
private bool m_bForcePlainTextPasswords = true;
public bool ForcePlainTextPasswords
{
get { return m_bForcePlainTextPasswords; }
set { m_bForcePlainTextPasswords = value; }
}
private SprCompileFlags m_flags = SprCompileFlags.All;
public SprCompileFlags Flags
{
get { return m_flags; }
set { m_flags = value; }
}
private SprRefsCache m_refsCache = new SprRefsCache();
/// <summary>
/// Used internally by <c>SprEngine</c>; don't modify it.
/// </summary>
internal SprRefsCache RefsCache
{
get { return m_refsCache; }
}
// private bool m_bNoUrlSchemeOnce = false;
// /// <summary>
// /// Used internally by <c>SprEngine</c>; don't modify it.
// /// </summary>
// internal bool UrlRemoveSchemeOnce
// {
// get { return m_bNoUrlSchemeOnce; }
// set { m_bNoUrlSchemeOnce = value; }
// }
public SprContext() { }
public SprContext(PwEntry pe, PwDatabase pd, SprCompileFlags fl)
{
Init(pe, pd, false, false, fl);
}
public SprContext(PwEntry pe, PwDatabase pd, SprCompileFlags fl,
bool bEncodeAsAutoTypeSequence, bool bEncodeQuotesForCommandLine)
{
Init(pe, pd, bEncodeAsAutoTypeSequence, bEncodeQuotesForCommandLine, fl);
}
private void Init(PwEntry pe, PwDatabase pd, bool bAT, bool bCmdQuotes,
SprCompileFlags fl)
{
m_pe = pe;
m_pd = pd;
m_bMakeAT = bAT;
m_bMakeCmdQuotes = bCmdQuotes;
m_flags = fl;
}
public SprContext Clone()
{
return (SprContext)this.MemberwiseClone();
}
/// <summary>
/// Used by <c>SprEngine</c> internally; do not use.
/// </summary>
internal SprContext WithoutContentTransformations()
{
SprContext ctx = Clone();
ctx.m_bMakeAT = false;
ctx.m_bMakeCmdQuotes = false;
// ctx.m_bNoUrlSchemeOnce = false;
Debug.Assert(object.ReferenceEquals(m_pe, ctx.m_pe));
Debug.Assert(object.ReferenceEquals(m_pd, ctx.m_pd));
Debug.Assert(object.ReferenceEquals(m_refsCache, ctx.m_refsCache));
return ctx;
}
}
public sealed class SprEventArgs : EventArgs
{
private string m_str = string.Empty;
public string Text
{
get { return m_str; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_str = value;
}
}
private SprContext m_ctx = null;
public SprContext Context
{
get { return m_ctx; }
}
public SprEventArgs() { }
public SprEventArgs(string strText, SprContext ctx)
{
if(strText == null) throw new ArgumentNullException("strText");
// ctx == null is allowed
m_str = strText;
m_ctx = ctx;
}
}
}

View File

@ -0,0 +1,75 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 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.Text;
using System.Diagnostics;
namespace KeePass.Util.Spr
{
internal static class SprEncoding
{
internal static string MakeAutoTypeSequence(string str)
{
if(str == null) { Debug.Assert(false); return string.Empty; }
str = SprEncoding.EscapeAutoTypeBrackets(str);
str = str.Replace(@"[", @"{[}");
str = str.Replace(@"]", @"{]}");
str = str.Replace(@"+", @"{+}");
str = str.Replace(@"%", @"{%}");
str = str.Replace(@"~", @"{~}");
str = str.Replace(@"(", @"{(}");
str = str.Replace(@")", @"{)}");
str = str.Replace(@"^", @"{^}");
return str;
}
private static string EscapeAutoTypeBrackets(string str)
{
char chOpen = '\u25A1';
while(str.IndexOf(chOpen) >= 0) ++chOpen;
char chClose = chOpen;
++chClose;
while(str.IndexOf(chClose) >= 0) ++chClose;
str = str.Replace('{', chOpen);
str = str.Replace('}', chClose);
str = str.Replace(new string(chOpen, 1), @"{{}");
str = str.Replace(new string(chClose, 1), @"{}}");
return str;
}
internal static string MakeCommandQuotes(string str)
{
if(str == null) { Debug.Assert(false); return string.Empty; }
// See SHELLEXECUTEINFO structure documentation
return str.Replace("\"", "\"\"\"");
}
}
}

View File

@ -0,0 +1,153 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 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.Text;
using System.Diagnostics;
using KeePassLib;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePass.Util.Spr
{
/// <summary>
/// String placeholders and field reference replacement engine.
/// </summary>
public static partial class SprEngine
{
// Legacy, for backward compatibility only; see PickChars
private static string ReplacePickPw(string strText, SprContext ctx,
uint uRecursionLevel)
{
if(ctx.Entry == null) { Debug.Assert(false); return strText; }
string str = strText;
while(true)
{
const string strStart = @"{PICKPASSWORDCHARS";
int iStart = str.IndexOf(strStart, StrUtil.CaseIgnoreCmp);
if(iStart < 0) break;
int iEnd = str.IndexOf('}', iStart);
if(iEnd < 0) break;
string strPlaceholder = str.Substring(iStart, iEnd - iStart + 1);
string strParam = str.Substring(iStart + strStart.Length,
iEnd - (iStart + strStart.Length));
string[] vParams = strParam.Split(new char[] { ':' });
uint uCharCount = 0;
if(vParams.Length >= 2) uint.TryParse(vParams[1], out uCharCount);
str = ReplacePickPwPlaceholder(str, strPlaceholder, uCharCount,
ctx, uRecursionLevel);
}
return str;
}
private static string ReplacePickPwPlaceholder(string str,
string strPlaceholder, uint uCharCount, SprContext ctx,
uint uRecursionLevel)
{
if(str.IndexOf(strPlaceholder, StrUtil.CaseIgnoreCmp) < 0) return str;
ProtectedString ps = ctx.Entry.Strings.Get(PwDefs.PasswordField);
if(ps != null)
{
string strPassword = ps.ReadString();
string strPick = SprEngine.CompileInternal(strPassword,
ctx.WithoutContentTransformations(), uRecursionLevel + 1);
if(!string.IsNullOrEmpty(strPick))
{
ProtectedString psPick = new ProtectedString(false, strPick);
string strPicked = string.Empty;
str = StrUtil.ReplaceCaseInsensitive(str, strPlaceholder,
SprEngine.TransformContent(strPicked, ctx));
}
}
return StrUtil.ReplaceCaseInsensitive(str, strPlaceholder, string.Empty);
}
private static string ConvertToDownArrows(string str, int iOffset,
string strLayout)
{
if(string.IsNullOrEmpty(str)) return string.Empty;
StringBuilder sb = new StringBuilder();
for(int i = 0; i < str.Length; ++i)
{
// if((sb.Length > 0) && !string.IsNullOrEmpty(strSep)) sb.Append(strSep);
char ch = str[i];
int? iDowns = null;
if(strLayout.Length == 0)
{
if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0';
else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a';
else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A';
}
else if(strLayout.Equals("0a", StrUtil.CaseIgnoreCmp))
{
if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0';
else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a' + 10;
else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A' + 10;
}
else if(strLayout.Equals("a0", StrUtil.CaseIgnoreCmp))
{
if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0' + 26;
else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a';
else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A';
}
else if(strLayout.Equals("1a", StrUtil.CaseIgnoreCmp))
{
if((ch >= '1') && (ch <= '9')) iDowns = (int)ch - '1';
else if(ch == '0') iDowns = 9;
else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a' + 10;
else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A' + 10;
}
else if(strLayout.Equals("a1", StrUtil.CaseIgnoreCmp))
{
if((ch >= '1') && (ch <= '9')) iDowns = (int)ch - '1' + 26;
else if(ch == '0') iDowns = 9 + 26;
else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a';
else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A';
}
if(!iDowns.HasValue) continue;
for(int j = 0; j < (iOffset + iDowns); ++j) sb.Append(@"{DOWN}");
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,607 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 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;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Security;
using KeePassLib.Utility;
using keepass2android;
namespace KeePass.Util.Spr
{
/// <summary>
/// String placeholders and field reference replacement engine.
/// </summary>
public static partial class SprEngine
{
private const uint MaxRecursionDepth = 12;
private const StringComparison ScMethod = StringComparison.OrdinalIgnoreCase;
// private static readonly char[] m_vPlhEscapes = new char[] { '{', '}', '%' };
// Important notes for plugin developers subscribing to the following events:
// * If possible, prefer subscribing to FilterCompile instead of
// FilterCompilePre.
// * If your plugin provides an active transformation (e.g. replacing a
// placeholder that changes some state or requires UI interaction), you
// must only perform the transformation if the ExtActive bit is set in
// args.Context.Flags of the event arguments object args provided to the
// event handler.
// * Non-active transformations should only be performed if the ExtNonActive
// bit is set in args.Context.Flags.
// * If your plugin provides a placeholder (like e.g. {EXAMPLE}), you
// should add this placeholder to the FilterPlaceholderHints list
// (e.g. add the string "{EXAMPLE}"). Please remove your strings from
// the list when your plugin is terminated.
public static event EventHandler<SprEventArgs> FilterCompilePre;
public static event EventHandler<SprEventArgs> FilterCompile;
private static List<string> m_lFilterPlh = new List<string>();
// See the events above
public static List<string> FilterPlaceholderHints
{
get { return m_lFilterPlh; }
}
private static void InitializeStatic()
{
}
[Obsolete]
public static string Compile(string strText, bool bIsAutoTypeSequence,
PwEntry pwEntry, PwDatabase pwDatabase, bool bEscapeForAutoType,
bool bEscapeQuotesForCommandLine)
{
SprContext ctx = new SprContext(pwEntry, pwDatabase, SprCompileFlags.All,
bEscapeForAutoType, bEscapeQuotesForCommandLine);
return Compile(strText, ctx);
}
public static string Compile(string strText, SprContext ctx)
{
if(strText == null) { Debug.Assert(false); return string.Empty; }
if(strText.Length == 0) return string.Empty;
SprEngine.InitializeStatic();
if(ctx == null) ctx = new SprContext();
ctx.RefsCache.Clear();
string str = SprEngine.CompileInternal(strText, ctx, 0);
// if(bEscapeForAutoType && !bIsAutoTypeSequence)
// str = SprEncoding.MakeAutoTypeSequence(str);
return str;
}
private static string CompileInternal(string strText, SprContext ctx,
uint uRecursionLevel)
{
if(strText == null) { Debug.Assert(false); return string.Empty; }
if(ctx == null) { Debug.Assert(false); ctx = new SprContext(); }
if(uRecursionLevel >= SprEngine.MaxRecursionDepth)
{
Debug.Assert(false); // Most likely a recursive reference
return string.Empty; // Do not return strText (endless loop)
}
string str = strText;
bool bExt = ((ctx.Flags & (SprCompileFlags.ExtActive |
SprCompileFlags.ExtNonActive)) != SprCompileFlags.None);
if(bExt && (SprEngine.FilterCompilePre != null))
{
SprEventArgs args = new SprEventArgs(str, ctx.Clone());
SprEngine.FilterCompilePre(null, args);
str = args.Text;
}
if((ctx.Flags & SprCompileFlags.Comments) != SprCompileFlags.None)
str = RemoveComments(str);
if(ctx.Entry != null)
{
if((ctx.Flags & SprCompileFlags.PickChars) != SprCompileFlags.None)
str = ReplacePickPw(str, ctx, uRecursionLevel);
if((ctx.Flags & SprCompileFlags.EntryStrings) != SprCompileFlags.None)
str = FillEntryStrings(str, ctx, uRecursionLevel);
if((ctx.Flags & SprCompileFlags.EntryStringsSpecial) != SprCompileFlags.None)
{
// ctx.UrlRemoveSchemeOnce = true;
// str = SprEngine.FillIfExists(str, @"{URL:RMVSCM}",
// ctx.Entry.Strings.GetSafe(PwDefs.UrlField), ctx, uRecursionLevel);
// Debug.Assert(!ctx.UrlRemoveSchemeOnce);
str = FillEntryStringsSpecial(str, ctx, uRecursionLevel);
}
if(((ctx.Flags & SprCompileFlags.PasswordEnc) != SprCompileFlags.None) &&
(str.IndexOf(@"{PASSWORD_ENC}", SprEngine.ScMethod) >= 0))
str = SprEngine.FillIfExists(str, @"{PASSWORD_ENC}", new ProtectedString(false,
StrUtil.EncryptString(ctx.Entry.Strings.ReadSafe(PwDefs.PasswordField))),
ctx, uRecursionLevel);
if(((ctx.Flags & SprCompileFlags.Group) != SprCompileFlags.None) &&
(ctx.Entry.ParentGroup != null))
{
str = SprEngine.FillIfExists(str, @"{GROUP}", new ProtectedString(
false, ctx.Entry.ParentGroup.Name), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{GROUPPATH}", new ProtectedString(
false, ctx.Entry.ParentGroup.GetFullPath()), ctx, uRecursionLevel);
}
}
if(ctx.Database != null)
{
if((ctx.Flags & SprCompileFlags.Paths) != SprCompileFlags.None)
{
// For backward compatibility only
str = SprEngine.FillIfExists(str, @"{DOCDIR}", new ProtectedString(
false, UrlUtil.GetFileDirectory(ctx.Database.IOConnectionInfo.Path,
false, false)), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DB_PATH}", new ProtectedString(
false, ctx.Database.IOConnectionInfo.Path), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DB_DIR}", new ProtectedString(
false, UrlUtil.GetFileDirectory(ctx.Database.IOConnectionInfo.Path,
false, false)), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DB_NAME}", new ProtectedString(
false, UrlUtil.GetFileName(ctx.Database.IOConnectionInfo.Path)),
ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DB_BASENAME}", new ProtectedString(
false, UrlUtil.StripExtension(UrlUtil.GetFileName(
ctx.Database.IOConnectionInfo.Path))), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DB_EXT}", new ProtectedString(
false, UrlUtil.GetExtension(ctx.Database.IOConnectionInfo.Path)),
ctx, uRecursionLevel);
}
}
if((ctx.Flags & SprCompileFlags.Paths) != SprCompileFlags.None)
{
str = SprEngine.FillIfExists(str, @"{ENV_DIRSEP}", new ProtectedString(
false, Path.DirectorySeparatorChar.ToString()), ctx, uRecursionLevel);
string strPF86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
if(string.IsNullOrEmpty(strPF86))
strPF86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if(strPF86 != null)
str = SprEngine.FillIfExists(str, @"{ENV_PROGRAMFILES_X86}",
new ProtectedString(false, strPF86), ctx, uRecursionLevel);
else { Debug.Assert(false); }
}
if((ctx.Flags & SprCompileFlags.AutoType) != SprCompileFlags.None)
{
str = StrUtil.ReplaceCaseInsensitive(str, @"{CLEARFIELD}",
@"{HOME}+({END}){DEL}{DELAY 50}");
str = StrUtil.ReplaceCaseInsensitive(str, @"{WIN}", @"{VKEY 91}");
str = StrUtil.ReplaceCaseInsensitive(str, @"{LWIN}", @"{VKEY 91}");
str = StrUtil.ReplaceCaseInsensitive(str, @"{RWIN}", @"{VKEY 92}");
str = StrUtil.ReplaceCaseInsensitive(str, @"{APPS}", @"{VKEY 93}");
for(int np = 0; np < 10; ++np)
str = StrUtil.ReplaceCaseInsensitive(str, @"{NUMPAD" +
Convert.ToString(np, 10) + @"}", @"{VKEY " +
Convert.ToString(np + 0x60, 10) + @"}");
}
if((ctx.Flags & SprCompileFlags.DateTime) != SprCompileFlags.None)
{
DateTime dtNow = DateTime.Now; // Local time
str = SprEngine.FillIfExists(str, @"{DT_YEAR}", new ProtectedString(
false, dtNow.Year.ToString("D4")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_MONTH}", new ProtectedString(
false, dtNow.Month.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_DAY}", new ProtectedString(
false, dtNow.Day.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_HOUR}", new ProtectedString(
false, dtNow.Hour.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_MINUTE}", new ProtectedString(
false, dtNow.Minute.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_SECOND}", new ProtectedString(
false, dtNow.Second.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_SIMPLE}", new ProtectedString(
false, dtNow.ToString("yyyyMMddHHmmss")), ctx, uRecursionLevel);
dtNow = dtNow.ToUniversalTime();
str = SprEngine.FillIfExists(str, @"{DT_UTC_YEAR}", new ProtectedString(
false, dtNow.Year.ToString("D4")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_MONTH}", new ProtectedString(
false, dtNow.Month.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_DAY}", new ProtectedString(
false, dtNow.Day.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_HOUR}", new ProtectedString(
false, dtNow.Hour.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_MINUTE}", new ProtectedString(
false, dtNow.Minute.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_SECOND}", new ProtectedString(
false, dtNow.Second.ToString("D2")), ctx, uRecursionLevel);
str = SprEngine.FillIfExists(str, @"{DT_UTC_SIMPLE}", new ProtectedString(
false, dtNow.ToString("yyyyMMddHHmmss")), ctx, uRecursionLevel);
}
if((ctx.Flags & SprCompileFlags.References) != SprCompileFlags.None)
str = SprEngine.FillRefPlaceholders(str, ctx, uRecursionLevel);
if(((ctx.Flags & SprCompileFlags.EnvVars) != SprCompileFlags.None) &&
(str.IndexOf('%') >= 0))
{
// Replace environment variables
foreach(DictionaryEntry de in Environment.GetEnvironmentVariables())
{
string strKey = (de.Key as string);
string strValue = (de.Value as string);
if((strKey != null) && (strValue != null))
str = SprEngine.FillIfExists(str, @"%" + strKey + @"%",
new ProtectedString(false, strValue), ctx, uRecursionLevel);
else { Debug.Assert(false); }
}
}
str = EntryUtil.FillPlaceholders(str, ctx);
if(bExt && (SprEngine.FilterCompile != null))
{
SprEventArgs args = new SprEventArgs(str, ctx.Clone());
SprEngine.FilterCompile(null, args);
str = args.Text;
}
if(ctx.EncodeAsAutoTypeSequence)
{
str = StrUtil.NormalizeNewLines(str, false);
str = str.Replace("\n", @"{ENTER}");
}
return str;
}
private static string FillIfExists(string strData, string strPlaceholder,
ProtectedString psParsable, SprContext ctx, uint uRecursionLevel)
{
// // The UrlRemoveSchemeOnce property of ctx must be cleared
// // before this method returns and before any recursive call
// bool bRemoveScheme = false;
// if(ctx != null)
// {
// bRemoveScheme = ctx.UrlRemoveSchemeOnce;
// ctx.UrlRemoveSchemeOnce = false;
// }
if(strData == null) { Debug.Assert(false); return string.Empty; }
if(strPlaceholder == null) { Debug.Assert(false); return strData; }
if(strPlaceholder.Length == 0) { Debug.Assert(false); return strData; }
if(psParsable == null) { Debug.Assert(false); return strData; }
if(strData.IndexOf(strPlaceholder, SprEngine.ScMethod) >= 0)
{
string strReplacement = SprEngine.CompileInternal(
psParsable.ReadString(), ctx.WithoutContentTransformations(),
uRecursionLevel + 1);
// if(bRemoveScheme)
// strReplacement = UrlUtil.RemoveScheme(strReplacement);
return SprEngine.FillPlaceholder(strData, strPlaceholder,
strReplacement, ctx);
}
return strData;
}
private static string FillPlaceholder(string strData, string strPlaceholder,
string strReplaceWith, SprContext ctx)
{
if(strData == null) { Debug.Assert(false); return string.Empty; }
if(strPlaceholder == null) { Debug.Assert(false); return strData; }
if(strPlaceholder.Length == 0) { Debug.Assert(false); return strData; }
if(strReplaceWith == null) { Debug.Assert(false); return strData; }
return StrUtil.ReplaceCaseInsensitive(strData, strPlaceholder,
SprEngine.TransformContent(strReplaceWith, ctx));
}
public static string TransformContent(string strContent, SprContext ctx)
{
if(strContent == null) { Debug.Assert(false); return string.Empty; }
string str = strContent;
if(ctx != null)
{
if(ctx.EncodeQuotesForCommandLine)
str = SprEncoding.MakeCommandQuotes(str);
if(ctx.EncodeAsAutoTypeSequence)
str = SprEncoding.MakeAutoTypeSequence(str);
}
return str;
}
private static string FillEntryStrings(string str, SprContext ctx,
uint uRecursionLevel)
{
List<string> vKeys = ctx.Entry.Strings.GetKeys();
// Ensure that all standard field names are in the list
// (this is required in order to replace the standard placeholders
// even if the corresponding standard field isn't present in
// the entry)
List<string> vStdNames = PwDefs.GetStandardFields();
foreach(string strStdField in vStdNames)
{
if(!vKeys.Contains(strStdField)) vKeys.Add(strStdField);
}
// Do not directly enumerate the strings in ctx.Entry.Strings,
// because strings might change during the Spr compilation
foreach(string strField in vKeys)
{
string strKey = (PwDefs.IsStandardField(strField) ?
(@"{" + strField + @"}") :
(@"{" + PwDefs.AutoTypeStringPrefix + strField + @"}"));
if(!ctx.ForcePlainTextPasswords && strKey.Equals(@"{" +
PwDefs.PasswordField + @"}", StrUtil.CaseIgnoreCmp))
{
str = SprEngine.FillIfExists(str, strKey, new ProtectedString(
false, PwDefs.HiddenPassword), ctx, uRecursionLevel);
continue;
}
// Use GetSafe because the field doesn't necessarily exist
// (might be a standard field that has been added above)
str = SprEngine.FillIfExists(str, strKey, ctx.Entry.Strings.GetSafe(
strField), ctx, uRecursionLevel);
}
return str;
}
private const string UrlSpecialRmvScm = @"{URL:RMVSCM}";
private const string UrlSpecialScm = @"{URL:SCM}";
private const string UrlSpecialHost = @"{URL:HOST}";
private const string UrlSpecialPort = @"{URL:PORT}";
private const string UrlSpecialPath = @"{URL:PATH}";
private const string UrlSpecialQuery = @"{URL:QUERY}";
private static string FillEntryStringsSpecial(string str, SprContext ctx,
uint uRecursionLevel)
{
if((str.IndexOf(UrlSpecialRmvScm, SprEngine.ScMethod) >= 0) ||
(str.IndexOf(UrlSpecialScm, SprEngine.ScMethod) >= 0) ||
(str.IndexOf(UrlSpecialHost, SprEngine.ScMethod) >= 0) ||
(str.IndexOf(UrlSpecialPort, SprEngine.ScMethod) >= 0) ||
(str.IndexOf(UrlSpecialPath, SprEngine.ScMethod) >= 0) ||
(str.IndexOf(UrlSpecialQuery, SprEngine.ScMethod) >= 0))
{
string strUrl = SprEngine.FillIfExists(@"{URL}", @"{URL}",
ctx.Entry.Strings.GetSafe(PwDefs.UrlField), ctx, uRecursionLevel);
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialRmvScm,
UrlUtil.RemoveScheme(strUrl));
try
{
Uri uri = new Uri(strUrl);
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialScm,
uri.Scheme);
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialHost,
uri.Host);
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialPort,
uri.Port.ToString());
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialPath,
uri.AbsolutePath);
str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialQuery,
uri.Query);
}
catch(Exception) { } // Invalid URI
}
return str;
}
private const string StrRemStart = @"{C:";
private const string StrRemEnd = @"}";
private static string RemoveComments(string strSeq)
{
string str = strSeq;
while(true)
{
int iStart = str.IndexOf(StrRemStart, SprEngine.ScMethod);
if(iStart < 0) break;
int iEnd = str.IndexOf(StrRemEnd, iStart + 1, SprEngine.ScMethod);
if(iEnd <= iStart) break;
str = (str.Substring(0, iStart) + str.Substring(iEnd + StrRemEnd.Length));
}
return str;
}
internal const string StrRefStart = @"{REF:";
internal const string StrRefEnd = @"}";
private static string FillRefPlaceholders(string strSeq, SprContext ctx,
uint uRecursionLevel)
{
if(ctx.Database == null) return strSeq;
string str = strSeq;
int nOffset = 0;
for(int iLoop = 0; iLoop < 20; ++iLoop)
{
str = SprEngine.FillRefsUsingCache(str, ctx);
int nStart = str.IndexOf(StrRefStart, nOffset, SprEngine.ScMethod);
if(nStart < 0) break;
int nEnd = str.IndexOf(StrRefEnd, nStart + 1, SprEngine.ScMethod);
if(nEnd <= nStart) break;
string strFullRef = str.Substring(nStart, nEnd - nStart + 1);
char chScan, chWanted;
PwEntry peFound = FindRefTarget(strFullRef, ctx, out chScan, out chWanted);
if(peFound != null)
{
string strInsData;
if(chWanted == 'T')
strInsData = peFound.Strings.ReadSafe(PwDefs.TitleField);
else if(chWanted == 'U')
strInsData = peFound.Strings.ReadSafe(PwDefs.UserNameField);
else if(chWanted == 'A')
strInsData = peFound.Strings.ReadSafe(PwDefs.UrlField);
else if(chWanted == 'P')
strInsData = peFound.Strings.ReadSafe(PwDefs.PasswordField);
else if(chWanted == 'N')
strInsData = peFound.Strings.ReadSafe(PwDefs.NotesField);
else if(chWanted == 'I')
strInsData = peFound.Uuid.ToHexString();
else { nOffset = nStart + 1; continue; }
if((chWanted == 'P') && !ctx.ForcePlainTextPasswords)
strInsData = PwDefs.HiddenPassword;
SprContext sprSub = ctx.WithoutContentTransformations();
sprSub.Entry = peFound;
string strInnerContent = SprEngine.CompileInternal(strInsData,
sprSub, uRecursionLevel + 1);
strInnerContent = SprEngine.TransformContent(strInnerContent, ctx);
// str = str.Substring(0, nStart) + strInnerContent + str.Substring(nEnd + 1);
SprEngine.AddRefToCache(strFullRef, strInnerContent, ctx);
str = SprEngine.FillRefsUsingCache(str, ctx);
}
else { nOffset = nStart + 1; continue; }
}
return str;
}
public static PwEntry FindRefTarget(string strFullRef, SprContext ctx,
out char chScan, out char chWanted)
{
chScan = char.MinValue;
chWanted = char.MinValue;
if(strFullRef == null) { Debug.Assert(false); return null; }
if(!strFullRef.StartsWith(StrRefStart, SprEngine.ScMethod) ||
!strFullRef.EndsWith(StrRefEnd, SprEngine.ScMethod))
return null;
if((ctx == null) || (ctx.Database == null)) { Debug.Assert(false); return null; }
string strRef = strFullRef.Substring(StrRefStart.Length,
strFullRef.Length - StrRefStart.Length - StrRefEnd.Length);
if(strRef.Length <= 4) return null;
if(strRef[1] != '@') return null;
if(strRef[3] != ':') return null;
chScan = char.ToUpper(strRef[2]);
chWanted = char.ToUpper(strRef[0]);
SearchParameters sp = SearchParameters.None;
sp.SearchString = strRef.Substring(4);
if(chScan == 'T') sp.SearchInTitles = true;
else if(chScan == 'U') sp.SearchInUserNames = true;
else if(chScan == 'A') sp.SearchInUrls = true;
else if(chScan == 'P') sp.SearchInPasswords = true;
else if(chScan == 'N') sp.SearchInNotes = true;
else if(chScan == 'I') sp.SearchInUuids = true;
else if(chScan == 'O') sp.SearchInOther = true;
else return null;
PwObjectList<PwEntry> lFound = new PwObjectList<PwEntry>();
ctx.Database.RootGroup.SearchEntries(sp, lFound);
return ((lFound.UCount > 0) ? lFound.GetAt(0) : null);
}
private static string FillRefsUsingCache(string strText, SprContext ctx)
{
string str = strText;
foreach(KeyValuePair<string, string> kvp in ctx.RefsCache)
{
// str = str.Replace(kvp.Key, kvp.Value);
str = StrUtil.ReplaceCaseInsensitive(str, kvp.Key, kvp.Value);
}
return str;
}
private static void AddRefToCache(string strRef, string strValue,
SprContext ctx)
{
if(strRef == null) { Debug.Assert(false); return; }
if(strValue == null) { Debug.Assert(false); return; }
if(ctx == null) { Debug.Assert(false); return; }
// Only add if not exists, do not overwrite
if(!ctx.RefsCache.ContainsKey(strRef))
ctx.RefsCache.Add(strRef, strValue);
}
// internal static bool MightChange(string strText)
// {
// if(string.IsNullOrEmpty(strText)) return false;
// return (strText.IndexOfAny(m_vPlhEscapes) >= 0);
// }
/// <summary>
/// Fast probabilistic test whether a string might be
/// changed when compiling with <c>SprCompileFlags.Deref</c>.
/// </summary>
internal static bool MightDeref(string strText)
{
if(strText == null) return false;
return (strText.IndexOf('{') >= 0);
}
internal static string DerefFn(string str, PwEntry pe)
{
if(!MightDeref(str)) return str;
SprContext ctx = new SprContext(pe,
App.getDB().pm,
SprCompileFlags.Deref);
// ctx.ForcePlainTextPasswords = false;
return Compile(str, ctx);
}
}
}

View File

@ -162,6 +162,11 @@
<Compile Include="EntryEditActivityState.cs" /> <Compile Include="EntryEditActivityState.cs" />
<Compile Include="AttachmentContentProvider.cs" /> <Compile Include="AttachmentContentProvider.cs" />
<Compile Include="app\AppTask.cs" /> <Compile Include="app\AppTask.cs" />
<Compile Include="Utils\Spr\SprContext.cs" />
<Compile Include="Utils\Spr\SprEncoding.cs" />
<Compile Include="Utils\Spr\SprEngine.cs" />
<Compile Include="Utils\Spr\SprEngine.PickChars.cs" />
<Compile Include="Utils\EntryUtil.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Resources\AboutResources.txt" /> <None Include="Resources\AboutResources.txt" />
@ -701,6 +706,7 @@
<Folder Include="Resources\values-v14\" /> <Folder Include="Resources\values-v14\" />
<Folder Include="Resources\layout-v14\" /> <Folder Include="Resources\layout-v14\" />
<Folder Include="Resources\anim\" /> <Folder Include="Resources\anim\" />
<Folder Include="Utils\Spr\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">

View File

@ -31,6 +31,7 @@ using Android.Preferences;
using KeePassLib; using KeePassLib;
using KeePassLib.Utility; using KeePassLib.Utility;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using KeePass.Util.Spr;
namespace keepass2android namespace keepass2android
{ {
@ -152,7 +153,7 @@ namespace keepass2android
if (prefs.GetBoolean(GetString(Resource.String.CopyToClipboardNotification_key), Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default))) if (prefs.GetBoolean(GetString(Resource.String.CopyToClipboardNotification_key), Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default)))
{ {
if (entry.Strings.ReadSafe(PwDefs.PasswordField).Length > 0) if (GetStringAndReplacePlaceholders(entry, PwDefs.PasswordField).Length > 0)
{ {
// only show notification if password is available // only show notification if password is available
Notification password = GetNotification(Intents.COPY_PASSWORD, Resource.String.copy_password, Resource.Drawable.notify, entryName); Notification password = GetNotification(Intents.COPY_PASSWORD, Resource.String.copy_password, Resource.Drawable.notify, entryName);
@ -163,7 +164,7 @@ namespace keepass2android
} }
if (entry.Strings.ReadSafe(PwDefs.UserNameField).Length > 0) if (GetStringAndReplacePlaceholders(entry, PwDefs.UserNameField).Length > 0)
{ {
// only show notification if username is available // only show notification if username is available
Notification username = GetNotification(Intents.COPY_USERNAME, Resource.String.copy_username, Resource.Drawable.notify, entryName); Notification username = GetNotification(Intents.COPY_USERNAME, Resource.String.copy_username, Resource.Drawable.notify, entryName);
@ -237,7 +238,8 @@ namespace keepass2android
int i=0; int i=0;
foreach (string key in keys) foreach (string key in keys)
{ {
String value = entry.Strings.ReadSafe(key); String value = GetStringAndReplacePlaceholders(entry, key);
if (value.Length > 0) if (value.Length > 0)
{ {
kbdataBuilder.AddPair(GetString(resIds[i]), value); kbdataBuilder.AddPair(GetString(resIds[i]), value);
@ -250,8 +252,11 @@ namespace keepass2android
{ {
String key = pair.Key; String key = pair.Key;
var value = GetStringAndReplacePlaceholders(entry, key);
if (!PwDefs.IsStandardField(key)) { if (!PwDefs.IsStandardField(key)) {
kbdataBuilder.AddPair(pair.Key, entry.Strings.ReadSafe(pair.Key)); kbdataBuilder.AddPair(pair.Key, value);
} }
} }
@ -263,6 +268,13 @@ namespace keepass2android
} }
static string GetStringAndReplacePlaceholders(PwEntry entry, string key)
{
String value = entry.Strings.ReadSafe(key);
value = SprEngine.Compile(value, new SprContext(entry, App.getDB().pm, SprCompileFlags.All));
return value;
}
public void OnWaitElementDeleted(int itemId) public void OnWaitElementDeleted(int itemId)
{ {
mNumElementsToWaitFor--; mNumElementsToWaitFor--;
@ -365,14 +377,14 @@ namespace keepass2android
if (action.Equals(Intents.COPY_USERNAME)) if (action.Equals(Intents.COPY_USERNAME))
{ {
String username = mEntry.Strings.ReadSafe(PwDefs.UserNameField); String username = GetStringAndReplacePlaceholders(mEntry, PwDefs.UserNameField);
if (username.Length > 0) if (username.Length > 0)
{ {
mService.timeoutCopyToClipboard(username); mService.timeoutCopyToClipboard(username);
} }
} else if (action.Equals(Intents.COPY_PASSWORD)) } else if (action.Equals(Intents.COPY_PASSWORD))
{ {
String password = mEntry.Strings.ReadSafe(PwDefs.PasswordField); String password = GetStringAndReplacePlaceholders(mEntry, PwDefs.PasswordField);
if (password.Length > 0) if (password.Length > 0)
{ {
mService.timeoutCopyToClipboard(password); mService.timeoutCopyToClipboard(password);