Replace (most) placeholders before copying to clipboard/keyboard
This commit is contained in:
		
							
								
								
									
										229
									
								
								src/keepass2android/Utils/EntryUtil.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								src/keepass2android/Utils/EntryUtil.cs
									
									
									
									
									
										Normal 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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										209
									
								
								src/keepass2android/Utils/Spr/SprContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/keepass2android/Utils/Spr/SprContext.cs
									
									
									
									
									
										Normal 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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										75
									
								
								src/keepass2android/Utils/Spr/SprEncoding.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/keepass2android/Utils/Spr/SprEncoding.cs
									
									
									
									
									
										Normal 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("\"", "\"\"\""); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										153
									
								
								src/keepass2android/Utils/Spr/SprEngine.PickChars.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/keepass2android/Utils/Spr/SprEngine.PickChars.cs
									
									
									
									
									
										Normal 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(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										607
									
								
								src/keepass2android/Utils/Spr/SprEngine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										607
									
								
								src/keepass2android/Utils/Spr/SprEngine.cs
									
									
									
									
									
										Normal 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); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -162,6 +162,11 @@ | ||||
|     <Compile Include="EntryEditActivityState.cs" /> | ||||
|     <Compile Include="AttachmentContentProvider.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> | ||||
|     <None Include="Resources\AboutResources.txt" /> | ||||
| @@ -701,6 +706,7 @@ | ||||
|     <Folder Include="Resources\values-v14\" /> | ||||
|     <Folder Include="Resources\layout-v14\" /> | ||||
|     <Folder Include="Resources\anim\" /> | ||||
|     <Folder Include="Utils\Spr\" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> | ||||
|   | ||||
| @@ -31,6 +31,7 @@ using Android.Preferences; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Utility; | ||||
| using Android.Views.InputMethods; | ||||
| using KeePass.Util.Spr; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -152,7 +153,7 @@ namespace keepass2android | ||||
| 			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 | ||||
| 					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 | ||||
| 					Notification username = GetNotification(Intents.COPY_USERNAME, Resource.String.copy_username, Resource.Drawable.notify, entryName); | ||||
| @@ -237,7 +238,8 @@ namespace keepass2android | ||||
| 			int i=0; | ||||
| 			foreach (string key in keys) | ||||
| 			{ | ||||
| 				String value = entry.Strings.ReadSafe(key); | ||||
| 				String value = GetStringAndReplacePlaceholders(entry, key); | ||||
|  | ||||
| 				if (value.Length > 0) | ||||
| 				{ | ||||
| 					kbdataBuilder.AddPair(GetString(resIds[i]), value); | ||||
| @@ -249,9 +251,12 @@ namespace keepass2android | ||||
| 			foreach (var pair in entry.Strings) | ||||
| 			{ | ||||
| 				String key = pair.Key; | ||||
| 				 | ||||
|  | ||||
| 				var value = GetStringAndReplacePlaceholders(entry, 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) | ||||
| 		{ | ||||
| 			mNumElementsToWaitFor--; | ||||
| @@ -365,14 +377,14 @@ namespace keepass2android | ||||
| 				 | ||||
| 				if (action.Equals(Intents.COPY_USERNAME)) | ||||
| 				{ | ||||
| 					String username = mEntry.Strings.ReadSafe(PwDefs.UserNameField); | ||||
| 					String username = GetStringAndReplacePlaceholders(mEntry, PwDefs.UserNameField); | ||||
| 					if (username.Length > 0) | ||||
| 					{ | ||||
| 						mService.timeoutCopyToClipboard(username); | ||||
| 					} | ||||
| 				} else if (action.Equals(Intents.COPY_PASSWORD)) | ||||
| 				{ | ||||
| 					String password = mEntry.Strings.ReadSafe(PwDefs.PasswordField); | ||||
| 					String password = GetStringAndReplacePlaceholders(mEntry, PwDefs.PasswordField); | ||||
| 					if (password.Length > 0) | ||||
| 					{ | ||||
| 						mService.timeoutCopyToClipboard(password); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll