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.Threading; 26 27 using KeePassLib.Native; 28 using KeePassLib.Utility; 29 30 namespace KeePass.Util 31 { 32 [Flags] 33 internal enum TempClearFlags 34 { 35 None = 0, 36 37 RegisteredFiles = 1, 38 RegisteredDirectories = 2, 39 40 ContentTaggedFiles = 4, 41 42 All = 0x7FFF 43 } 44 45 public sealed class TempFilesPool 46 { 47 private List<string> m_lFiles = new List<string>(); 48 private List<KeyValuePair<string, bool>> m_lDirs = 49 new List<KeyValuePair<string, bool>>(); 50 51 private Dictionary<string, bool> m_dContentLoc = new Dictionary<string, bool>(); 52 private readonly object m_oContentLocSync = new object(); 53 54 private long m_nThreads = 0; 55 56 private string m_strContentTag = null; 57 public string TempContentTag 58 { 59 get 60 { 61 if(m_strContentTag == null) 62 { 63 // The tag should consist only of lower case letters 64 // and digits (for maximum compatibility); it can 65 // for instance be used as CSS class name 66 string strL = "cfbm4v27xyk0dk5lyeq5"; 67 string strR = "qxi7bxozyph6qyexr9kw"; // Together ~207 bit 68 69 // Avoid that the content tag is directly visible in 70 // the source code and binaries, in case the development 71 // environment creates temporary files containing the 72 // tag that might then be deleted unintentionally 73 StringBuilder sb = new StringBuilder(); 74 sb.Append(strL); 75 for(int i = strR.Length - 1; i >= 0; --i) 76 sb.Append(strR[i]); // Reverse 77 78 m_strContentTag = sb.ToString(); 79 } 80 81 return m_strContentTag; 82 } 83 } 84 TempFilesPool()85 public TempFilesPool() 86 { 87 } 88 89 #if DEBUG ~TempFilesPool()90 ~TempFilesPool() 91 { 92 Debug.Assert(Interlocked.Read(ref m_nThreads) == 0); 93 } 94 #endif 95 Clear(TempClearFlags f)96 internal void Clear(TempClearFlags f) 97 { 98 if((f & TempClearFlags.RegisteredFiles) != TempClearFlags.None) 99 { 100 List<string> lFailed = new List<string>(); 101 102 foreach(string strFile in m_lFiles) 103 { 104 try 105 { 106 if(File.Exists(strFile)) 107 File.Delete(strFile); 108 } 109 catch(Exception) 110 { 111 Debug.Assert(false); 112 lFailed.Add(strFile); 113 } 114 } 115 116 m_lFiles = lFailed; 117 } 118 119 if((f & TempClearFlags.RegisteredDirectories) != TempClearFlags.None) 120 { 121 List<KeyValuePair<string, bool>> lFailed = 122 new List<KeyValuePair<string, bool>>(); 123 124 foreach(KeyValuePair<string, bool> kvp in m_lDirs) 125 { 126 try 127 { 128 if(Directory.Exists(kvp.Key)) 129 Directory.Delete(kvp.Key, kvp.Value); 130 } 131 catch(Exception) 132 { 133 Debug.Assert(false); 134 lFailed.Add(kvp); 135 } 136 } 137 138 m_lDirs = lFailed; 139 } 140 141 if((f & TempClearFlags.ContentTaggedFiles) != TempClearFlags.None) 142 ClearContentAsync(); 143 } 144 WaitForThreads()145 internal void WaitForThreads() 146 { 147 try 148 { 149 while(Interlocked.Read(ref m_nThreads) > 0) 150 { 151 Thread.Sleep(1); 152 } 153 } 154 catch(Exception) { Debug.Assert(false); } 155 } 156 Add(string strTempFile)157 public void Add(string strTempFile) 158 { 159 if(string.IsNullOrEmpty(strTempFile)) { Debug.Assert(false); return; } 160 161 m_lFiles.Add(strTempFile); 162 } 163 AddDirectory(string strTempDir, bool bRecursive)164 public void AddDirectory(string strTempDir, bool bRecursive) 165 { 166 if(string.IsNullOrEmpty(strTempDir)) { Debug.Assert(false); return; } 167 168 m_lDirs.Add(new KeyValuePair<string, bool>(strTempDir, bRecursive)); 169 } 170 AddContent(string strFilePattern, bool bRecursive)171 public void AddContent(string strFilePattern, bool bRecursive) 172 { 173 if(string.IsNullOrEmpty(strFilePattern)) { Debug.Assert(false); return; } 174 175 lock(m_oContentLocSync) 176 { 177 if(m_dContentLoc.ContainsKey(strFilePattern) && !bRecursive) 178 return; // Do not overwrite recursive with non-recursive 179 180 m_dContentLoc[strFilePattern] = bRecursive; 181 } 182 } 183 AddWebBrowserPrintContent()184 public void AddWebBrowserPrintContent() 185 { 186 if(!NativeLib.IsUnix()) 187 { 188 // MSHTML may create and forget temporary files under 189 // C:\\Users\\USER\\AppData\\Local\\Temp\\*.htm 190 // (e.g. when printing fails) 191 AddContent("*.htm", false); 192 } 193 } 194 GetTempFileName()195 public string GetTempFileName() 196 { 197 return GetTempFileName(true); 198 } 199 GetTempFileName(bool bCreateEmptyFile)200 public string GetTempFileName(bool bCreateEmptyFile) 201 { 202 string strFile = Path.GetTempFileName(); 203 m_lFiles.Add(strFile); 204 205 if(!bCreateEmptyFile) 206 { 207 try { File.Delete(strFile); } 208 catch(Exception) { Debug.Assert(false); } 209 } 210 211 return strFile; 212 } 213 GetTempFileName(string strFileExt)214 public string GetTempFileName(string strFileExt) 215 { 216 if(string.IsNullOrEmpty(strFileExt)) 217 return GetTempFileName(); 218 219 try 220 { 221 while(true) 222 { 223 string str = UrlUtil.EnsureTerminatingSeparator( 224 UrlUtil.GetTempPath(), false); 225 str += "Temp_"; 226 227 byte[] pbRandom = new byte[9]; 228 Program.GlobalRandom.NextBytes(pbRandom); 229 str += StrUtil.AlphaNumericOnly(Convert.ToBase64String( 230 pbRandom, Base64FormattingOptions.None)); 231 232 str += "." + strFileExt; 233 234 if(!File.Exists(str)) 235 { 236 m_lFiles.Add(str); 237 return str; 238 } 239 } 240 } 241 catch(Exception) { Debug.Assert(false); } 242 243 return GetTempFileName(); 244 } 245 Delete(string strTempFile)246 public bool Delete(string strTempFile) 247 { 248 if(string.IsNullOrEmpty(strTempFile)) { Debug.Assert(false); return false; } 249 250 int i = m_lFiles.IndexOf(strTempFile); 251 if(i < 0) { Debug.Assert(false); return false; } 252 253 bool bResult = false; 254 try 255 { 256 File.Delete(strTempFile); 257 258 m_lFiles.RemoveAt(i); 259 bResult = true; 260 } 261 catch(Exception) { Debug.Assert(false); } 262 263 return bResult; 264 } 265 ClearContentAsync()266 private void ClearContentAsync() 267 { 268 lock(m_oContentLocSync) 269 { 270 if(m_dContentLoc.Count == 0) return; 271 } 272 273 Interlocked.Increment(ref m_nThreads); // Here, not in thread 274 try { ThreadPool.QueueUserWorkItem(this.ClearContentTh); } 275 catch(Exception) 276 { 277 Debug.Assert(false); 278 Interlocked.Decrement(ref m_nThreads); 279 } 280 } 281 ClearContentTh(object state)282 private void ClearContentTh(object state) 283 { 284 try 285 { 286 Debug.Assert(Interlocked.Read(ref m_nThreads) > 0); 287 288 string strTag = m_strContentTag; 289 if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return; } 290 291 UnicodeEncoding ue = new UnicodeEncoding(false, false, false); 292 byte[] pbTagA = StrUtil.Utf8.GetBytes(strTag); 293 byte[] pbTagW = ue.GetBytes(strTag); 294 295 string strTempPath = UrlUtil.GetTempPath(); 296 297 Dictionary<string, bool> dToDo; 298 lock(m_oContentLocSync) 299 { 300 dToDo = new Dictionary<string, bool>(m_dContentLoc); 301 m_dContentLoc.Clear(); 302 } 303 304 foreach(KeyValuePair<string, bool> kvp in dToDo) 305 { 306 bool bSuccess = false; 307 try 308 { 309 bSuccess = ClearContentPriv(strTempPath, kvp.Key, kvp.Value, 310 pbTagA, pbTagW); 311 } 312 catch(Exception) { Debug.Assert(false); } 313 314 if(!bSuccess) 315 { 316 lock(m_oContentLocSync) 317 { 318 m_dContentLoc[kvp.Key] = kvp.Value; // Try again next time 319 } 320 } 321 } 322 } 323 catch(Exception) { Debug.Assert(false); } 324 finally { Interlocked.Decrement(ref m_nThreads); } 325 } 326 ClearContentPriv(string strTempPath, string strFilePattern, bool bRecursive, byte[] pbTagA, byte[] pbTagW)327 private bool ClearContentPriv(string strTempPath, string strFilePattern, 328 bool bRecursive, byte[] pbTagA, byte[] pbTagW) 329 { 330 List<string> lFiles = UrlUtil.GetFilePaths(strTempPath, strFilePattern, 331 (bRecursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)); 332 bool bSuccess = true; 333 334 foreach(string strFile in lFiles) 335 { 336 if(string.IsNullOrEmpty(strFile)) continue; 337 if((strFile == ".") || (strFile == "..")) continue; 338 339 try 340 { 341 byte[] pb = File.ReadAllBytes(strFile); 342 if(pb == null) { Debug.Assert(false); continue; } 343 344 if((MemUtil.IndexOf(pb, pbTagA) >= 0) || 345 (MemUtil.IndexOf(pb, pbTagW) >= 0)) 346 { 347 File.Delete(strFile); 348 } 349 } 350 catch(Exception) { Debug.Assert(false); bSuccess = false; } 351 } 352 353 return bSuccess; 354 } 355 } 356 } 357