1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.IO;
24 using System.Text;
25 using System.Windows.Forms;
26 
27 using KeePass.App;
28 using KeePass.Ecas;
29 using KeePass.Forms;
30 using KeePass.Native;
31 using KeePass.UI;
32 using KeePass.Util;
33 using KeePass.Util.Spr;
34 
35 using KeePassLib;
36 using KeePassLib.Cryptography;
37 using KeePassLib.Security;
38 using KeePassLib.Utility;
39 
40 using NativeLib = KeePassLib.Native.NativeLib;
41 
42 namespace KeePass.Util
43 {
44 	public static partial class ClipboardUtil
45 	{
46 		private static byte[] g_pbDataHash = null;
47 		private static readonly CriticalSectionEx g_csClearing = new CriticalSectionEx();
48 
49 		[Obsolete]
Copy(string strToCopy, bool bIsEntryInfo, PwEntry peEntryInfo, PwDatabase pwReferenceSource)50 		public static bool Copy(string strToCopy, bool bIsEntryInfo,
51 			PwEntry peEntryInfo, PwDatabase pwReferenceSource)
52 		{
53 			return Copy(strToCopy, true, bIsEntryInfo, peEntryInfo,
54 				pwReferenceSource, IntPtr.Zero);
55 		}
56 
57 		[Obsolete]
Copy(ProtectedString psToCopy, bool bIsEntryInfo, PwEntry peEntryInfo, PwDatabase pwReferenceSource)58 		public static bool Copy(ProtectedString psToCopy, bool bIsEntryInfo,
59 			PwEntry peEntryInfo, PwDatabase pwReferenceSource)
60 		{
61 			if(psToCopy == null) throw new ArgumentNullException("psToCopy");
62 			return Copy(psToCopy.ReadString(), true, bIsEntryInfo, peEntryInfo,
63 				pwReferenceSource, IntPtr.Zero);
64 		}
65 
Copy(string strToCopy, bool bSprCompile, bool bIsEntryInfo, PwEntry peEntryInfo, PwDatabase pwReferenceSource, IntPtr hOwner)66 		public static bool Copy(string strToCopy, bool bSprCompile, bool bIsEntryInfo,
67 			PwEntry peEntryInfo, PwDatabase pwReferenceSource, IntPtr hOwner)
68 		{
69 			if(strToCopy == null) throw new ArgumentNullException("strToCopy");
70 			if(strToCopy.Length == 0) { Clear(); return true; }
71 
72 			if(bIsEntryInfo && !AppPolicy.Try(AppPolicyId.CopyToClipboard))
73 				return false;
74 
75 			string strData = strToCopy;
76 			if(bSprCompile)
77 				strData = SprEngine.Compile(strData, new SprContext(
78 					peEntryInfo, pwReferenceSource, SprCompileFlags.All));
79 
80 			try
81 			{
82 				// if(SetStringUwp(strData)) { } else
83 				if(!NativeLib.IsUnix()) // Windows
84 				{
85 					if(!OpenW(hOwner, true))
86 						throw new InvalidOperationException();
87 
88 					bool bFailed = false;
89 					if(!AttachIgnoreFormatsW()) bFailed = true;
90 					if(!SetDataW(null, strData, null)) bFailed = true;
91 					CloseW();
92 
93 					if(bFailed) return false;
94 				}
95 				else if(NativeLib.GetPlatformID() == PlatformID.MacOSX)
96 					SetStringM(strData);
97 				else if(NativeLib.IsUnix())
98 					SetStringU(strData);
99 				else
100 				{
101 					Debug.Assert(false);
102 					Clipboard.SetText(strData);
103 				}
104 			}
105 			catch(Exception) { Debug.Assert(false); return false; }
106 
107 			g_pbDataHash = HashString(strData);
108 
109 			if(peEntryInfo != null) peEntryInfo.Touch(false);
110 
111 			if(bIsEntryInfo)
112 				Program.TriggerSystem.RaiseEvent(EcasEventIDs.CopiedEntryInfo,
113 					EcasProperty.Text, strData);
114 
115 			// SprEngine.Compile might have modified the database
116 			MainForm mf = Program.MainForm;
117 			if((mf != null) && bSprCompile)
118 			{
119 				mf.RefreshEntriesList();
120 				mf.UpdateUI(false, null, false, null, false, null, false);
121 			}
122 
123 			return true;
124 		}
125 
126 		[Obsolete]
Copy(byte[] pbToCopy, string strFormat, bool bIsEntryInfo)127 		public static bool Copy(byte[] pbToCopy, string strFormat, bool bIsEntryInfo)
128 		{
129 			return Copy(pbToCopy, strFormat, bIsEntryInfo, IntPtr.Zero);
130 		}
131 
132 		[Obsolete]
Copy(byte[] pbToCopy, string strFormat, bool bEncode, bool bIsEntryInfo, IntPtr hOwner)133 		public static bool Copy(byte[] pbToCopy, string strFormat, bool bEncode,
134 			bool bIsEntryInfo, IntPtr hOwner)
135 		{
136 			return Copy(pbToCopy, strFormat, bIsEntryInfo, hOwner);
137 		}
138 
Copy(byte[] pbToCopy, string strFormat, bool bIsEntryInfo, IntPtr hOwner)139 		public static bool Copy(byte[] pbToCopy, string strFormat, bool bIsEntryInfo,
140 			IntPtr hOwner)
141 		{
142 			if(pbToCopy == null) throw new ArgumentNullException("pbToCopy");
143 			if(pbToCopy.Length == 0) { Clear(); return true; }
144 
145 			string strMedia = StrUtil.GetCustomMediaType(strFormat);
146 			string strData = StrUtil.DataToDataUri(pbToCopy, strMedia);
147 
148 			return Copy(strData, false, bIsEntryInfo, null, null, hOwner);
149 		}
150 
151 		[Obsolete]
GetEncodedData(string strFormat, IntPtr hOwner)152 		public static byte[] GetEncodedData(string strFormat, IntPtr hOwner)
153 		{
154 			return GetData(strFormat);
155 		}
156 
CopyAndMinimize(string strToCopy, bool bIsEntryInfo, Form formContext, PwEntry peContext, PwDatabase pdContext)157 		public static bool CopyAndMinimize(string strToCopy, bool bIsEntryInfo,
158 			Form formContext, PwEntry peContext, PwDatabase pdContext)
159 		{
160 			if(strToCopy == null) { Debug.Assert(false); return false; }
161 
162 			IntPtr hOwner = ((formContext != null) ? formContext.Handle : IntPtr.Zero);
163 
164 			if(Copy(strToCopy, true, bIsEntryInfo, peContext, pdContext, hOwner))
165 			{
166 				if(formContext != null)
167 				{
168 					if(Program.Config.MainWindow.DropToBackAfterClipboardCopy)
169 						NativeMethods.LoseFocus(formContext, true);
170 
171 					if(Program.Config.MainWindow.MinimizeAfterClipboardCopy &&
172 						formContext.MinimizeBox && formContext.Enabled)
173 						UIUtil.SetWindowState(formContext, FormWindowState.Minimized);
174 				}
175 
176 				return true;
177 			}
178 
179 			return false;
180 		}
181 
CopyAndMinimize(ProtectedString psToCopy, bool bIsEntryInfo, Form formContext, PwEntry peContext, PwDatabase pdContext)182 		public static bool CopyAndMinimize(ProtectedString psToCopy, bool bIsEntryInfo,
183 			Form formContext, PwEntry peContext, PwDatabase pdContext)
184 		{
185 			if(psToCopy == null) { Debug.Assert(false); return false; }
186 
187 			return CopyAndMinimize(psToCopy.ReadString(), bIsEntryInfo,
188 				formContext, peContext, pdContext);
189 		}
190 
191 		/// <summary>
192 		/// Safely clear the clipboard. The clipboard clearing method
193 		/// of the .NET Framework stores an empty <c>DataObject</c>
194 		/// in the clipboard; this can cause incompatibilities with
195 		/// other applications. Therefore, the <c>Clear</c> method of
196 		/// <c>ClipboardUtil</c> first tries to clear the clipboard using
197 		/// native Windows functions (which *really* clear the clipboard).
198 		/// </summary>
Clear()199 		public static void Clear()
200 		{
201 			// Ensure that there's no infinite recursion
202 			if(!g_csClearing.TryEnter()) { Debug.Assert(false); return; }
203 
204 			// In some situations (e.g. when running in a VM, when using
205 			// a clipboard extension utility, ...) the clipboard cannot
206 			// be cleared; for this case we first overwrite the clipboard
207 			// with a non-sensitive text
208 			try { Copy("--", false, false, null, null, IntPtr.Zero); }
209 			catch(Exception) { Debug.Assert(false); }
210 
211 			bool bNativeSuccess = false;
212 			try
213 			{
214 				if(!NativeLib.IsUnix()) // Windows
215 				{
216 					if(OpenW(IntPtr.Zero, true)) // Clears the clipboard
217 					{
218 						CloseW();
219 						bNativeSuccess = true;
220 					}
221 				}
222 				else if(NativeLib.GetPlatformID() == PlatformID.MacOSX)
223 				{
224 					SetStringM(string.Empty);
225 					bNativeSuccess = true;
226 				}
227 				else if(NativeLib.IsUnix())
228 				{
229 					SetStringU(string.Empty);
230 					bNativeSuccess = true;
231 				}
232 			}
233 			catch(Exception) { Debug.Assert(false); }
234 
235 			g_pbDataHash = null;
236 			g_csClearing.Exit();
237 
238 			if(bNativeSuccess) return;
239 
240 			Debug.Assert(false);
241 			try { Clipboard.Clear(); } // Fallback; empty data object
242 			catch(Exception) { Debug.Assert(false); }
243 		}
244 
ClearIfOwner()245 		public static void ClearIfOwner()
246 		{
247 			// Handle-based detection doesn't work well, because a control
248 			// or dialog that stored the data may not exist anymore and
249 			// thus GetClipboardOwner returns null
250 			/* bool bOwnHandle = false;
251 			try
252 			{
253 				if(!NativeLib.IsUnix())
254 				{
255 					IntPtr h = NativeMethods.GetClipboardOwner();
256 					bOwnHandle = GlobalWindowManager.HasWindowMW(h);
257 				}
258 			}
259 			catch(Exception) { Debug.Assert(false); } */
260 
261 			if(g_pbDataHash == null) return;
262 
263 			byte[] pbCur = ComputeHash();
264 			if((pbCur == null) || !MemUtil.ArraysEqual(pbCur, g_pbDataHash))
265 				return;
266 
267 			Clear();
268 		}
269 
HashString(string str)270 		private static byte[] HashString(string str)
271 		{
272 			try
273 			{
274 				if(string.IsNullOrEmpty(str)) return null;
275 
276 				byte[] pb = StrUtil.Utf8.GetBytes(str);
277 				return CryptoUtil.HashSha256(pb);
278 			}
279 			catch(Exception) { Debug.Assert(false); }
280 
281 			return null;
282 		}
283 
ComputeHash()284 		public static byte[] ComputeHash()
285 		{
286 			try { return HashString(GetText()); }
287 			catch(Exception) { Debug.Assert(false); }
288 
289 			return null;
290 		}
291 
ContainsText()292 		public static bool ContainsText()
293 		{
294 			if(NativeLib.IsUnix()) return true;
295 			return Clipboard.ContainsText();
296 		}
297 
ContainsData(string strFormat)298 		public static bool ContainsData(string strFormat)
299 		{
300 			if(string.IsNullOrEmpty(strFormat)) { Debug.Assert(false); return false; }
301 			if(strFormat.Equals(DataFormats.UnicodeText, StrUtil.CaseIgnoreCmp) ||
302 				strFormat.Equals(DataFormats.Text, StrUtil.CaseIgnoreCmp) ||
303 				strFormat.Equals(DataFormats.OemText, StrUtil.CaseIgnoreCmp))
304 				return ContainsText();
305 
306 			string strData = GetText();
307 			if(string.IsNullOrEmpty(strData)) return false;
308 
309 			return StrUtil.IsDataUri(strData, StrUtil.GetCustomMediaType(strFormat));
310 		}
311 
GetText()312 		public static string GetText()
313 		{
314 			if(!NativeLib.IsUnix()) // Windows
315 				return Clipboard.GetText();
316 			if(NativeLib.GetPlatformID() == PlatformID.MacOSX)
317 				return GetStringM();
318 			if(NativeLib.IsUnix())
319 				return GetStringU();
320 
321 			Debug.Assert(false);
322 			return Clipboard.GetText();
323 		}
324 
GetData(string strFormat)325 		public static byte[] GetData(string strFormat)
326 		{
327 			try
328 			{
329 				string str = GetText();
330 				if(string.IsNullOrEmpty(str)) return null;
331 
332 				string strMedia = StrUtil.GetCustomMediaType(strFormat);
333 				if(!StrUtil.IsDataUri(str, strMedia)) return null;
334 
335 				return StrUtil.DataUriToData(str);
336 			}
337 			catch(Exception) { Debug.Assert(false); }
338 
339 			return null;
340 		}
341 	}
342 }
343