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.ComponentModel; 23 using System.Diagnostics; 24 using System.Drawing; 25 using System.Drawing.Drawing2D; 26 using System.Drawing.Imaging; 27 using System.IO; 28 using System.Text; 29 using System.Windows.Forms; 30 31 using KeePass.App; 32 using KeePass.Native; 33 using KeePass.Resources; 34 using KeePass.UI; 35 using KeePass.Util; 36 37 using KeePassLib; 38 using KeePassLib.Utility; 39 40 namespace KeePass.Forms 41 { 42 public partial class FileBrowserForm : Form 43 { 44 private bool m_bSaveMode = false; 45 private string m_strTitle = PwDefs.ShortProductName; 46 private string m_strHint = string.Empty; 47 private string m_strContext = null; 48 49 private ImageList m_ilFolders = null; 50 private List<Image> m_vFolderImages = new List<Image>(); 51 private ImageList m_ilFiles = null; 52 private List<Image> m_vFileImages = new List<Image>(); 53 54 private int m_nIconDim = DpiUtil.ScaleIntY(16); 55 56 private const string StrDummyNode = "66913D76EA3F4F2A8B1A0899B7322EC3"; 57 58 private sealed class FbfPrivTviComparer : IComparer<TreeNode> 59 { Compare(TreeNode x, TreeNode y)60 public int Compare(TreeNode x, TreeNode y) 61 { 62 Debug.Assert((x != null) && (y != null)); 63 return StrUtil.CompareNaturally(x.Text, y.Text); 64 } 65 } 66 67 private sealed class FbfPrivLviComparer : IComparer<ListViewItem> 68 { Compare(ListViewItem x, ListViewItem y)69 public int Compare(ListViewItem x, ListViewItem y) 70 { 71 Debug.Assert((x != null) && (y != null)); 72 return StrUtil.CompareNaturally(x.Text, y.Text); 73 } 74 } 75 76 private string m_strSelectedFile = null; 77 public string SelectedFile 78 { 79 get { return m_strSelectedFile; } 80 } 81 InitEx(bool bSaveMode, string strTitle, string strHint, string strContext)82 public void InitEx(bool bSaveMode, string strTitle, string strHint, 83 string strContext) 84 { 85 m_bSaveMode = bSaveMode; 86 if(strTitle != null) m_strTitle = strTitle; 87 if(strHint != null) m_strHint = strHint; 88 m_strContext = strContext; 89 } 90 FileBrowserForm()91 public FileBrowserForm() 92 { 93 InitializeComponent(); 94 GlobalWindowManager.InitializeForm(this); 95 } 96 OnFormLoad(object sender, EventArgs e)97 private void OnFormLoad(object sender, EventArgs e) 98 { 99 GlobalWindowManager.AddWindow(this); 100 101 this.Icon = AppIcons.Default; 102 this.Text = m_strTitle; 103 104 m_nIconDim = m_tvFolders.ItemHeight; 105 106 if(UIUtil.VistaStyleListsSupported) 107 { 108 UIUtil.SetExplorerTheme(m_tvFolders, true); 109 UIUtil.SetExplorerTheme(m_lvFiles, true); 110 } 111 112 m_btnOK.Text = (m_bSaveMode ? KPRes.SaveCmd : KPRes.OpenCmd); 113 Debug.Assert(!m_lblHint.AutoSize); // For RTL support 114 m_lblHint.Text = m_strHint; 115 116 if(UIUtil.ColorsEqual(m_lblHint.ForeColor, Color.Black)) 117 m_lblHint.ForeColor = Color.FromArgb(96, 96, 96); 118 119 int nWidth = m_lvFiles.ClientSize.Width - UIUtil.GetVScrollBarWidth(); 120 m_lvFiles.Columns.Add(KPRes.Name, (nWidth * 3) / 4); 121 m_lvFiles.Columns.Add(KPRes.Size, nWidth / 4, HorizontalAlignment.Right); 122 123 InitialPopulateFolders(); 124 125 string strWorkDir = Program.Config.Application.GetWorkingDirectory(m_strContext); 126 if(string.IsNullOrEmpty(strWorkDir)) 127 strWorkDir = WinUtil.GetHomeDirectory(); 128 BrowseToFolder(strWorkDir); 129 130 EnableControlsEx(); 131 } 132 OnFormClosed(object sender, FormClosedEventArgs e)133 private void OnFormClosed(object sender, FormClosedEventArgs e) 134 { 135 m_tvFolders.Nodes.Clear(); 136 m_lvFiles.Items.Clear(); 137 m_tvFolders.ImageList = null; 138 m_lvFiles.SmallImageList = null; 139 140 if(m_ilFolders != null) { m_ilFolders.Dispose(); m_ilFolders = null; } 141 if(m_ilFiles != null) { m_ilFiles.Dispose(); m_ilFiles = null; } 142 143 foreach(Image imgFld in m_vFolderImages) imgFld.Dispose(); 144 m_vFolderImages.Clear(); 145 foreach(Image imgFile in m_vFileImages) imgFile.Dispose(); 146 m_vFileImages.Clear(); 147 148 GlobalWindowManager.RemoveWindow(this); 149 } 150 EnableControlsEx()151 private void EnableControlsEx() 152 { 153 m_btnOK.Enabled = (m_lvFiles.SelectedIndices.Count == 1); 154 } 155 InitialPopulateFolders()156 private void InitialPopulateFolders() 157 { 158 List<TreeNode> l = new List<TreeNode>(); 159 160 string str = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); 161 if(!string.IsNullOrEmpty(str)) 162 { 163 TreeNode tn = CreateFolderNode(str, false, null); 164 if(tn != null) l.Add(tn); 165 } 166 167 str = Environment.GetEnvironmentVariable("USERPROFILE"); 168 if(!string.IsNullOrEmpty(str)) 169 { 170 TreeNode tn = CreateFolderNode(str, false, null); 171 if(tn != null) l.Add(tn); 172 } 173 174 DriveInfo[] vDrives = DriveInfo.GetDrives(); 175 foreach(DriveInfo drv in vDrives) 176 { 177 try 178 { 179 DirectoryInfo diDrive = drv.RootDirectory; 180 TreeNode tn = CreateFolderNode(diDrive.FullName, true, drv); 181 if(tn != null) l.Add(tn); 182 } 183 catch(Exception) { Debug.Assert(false); } 184 } 185 186 RebuildFolderImageList(); 187 m_tvFolders.Nodes.AddRange(l.ToArray()); 188 } 189 GetObjectProps(string strPath, DriveInfo drvHint, out Image img, ref string strDisplayName)190 private void GetObjectProps(string strPath, DriveInfo drvHint, 191 out Image img, ref string strDisplayName) 192 { 193 GetObjectPropsUnscaled(strPath, drvHint, out img, ref strDisplayName); 194 195 if(img != null) 196 { 197 if((img.Width != m_nIconDim) || (img.Height != m_nIconDim)) 198 { 199 Image imgScaled = GfxUtil.ScaleImage(img, m_nIconDim, 200 m_nIconDim, ScaleTransformFlags.UIIcon); 201 img.Dispose(); // Dispose unscaled version 202 img = imgScaled; 203 } 204 } 205 } 206 GetObjectPropsUnscaled(string strPath, DriveInfo drvHint, out Image img, ref string strDisplayName)207 private void GetObjectPropsUnscaled(string strPath, DriveInfo drvHint, 208 out Image img, ref string strDisplayName) 209 { 210 img = null; 211 212 try 213 { 214 string strName; 215 NativeMethods.SHGetFileInfo(strPath, m_nIconDim, m_nIconDim, 216 out img, out strName); 217 218 if(!string.IsNullOrEmpty(strName) && (strName.IndexOf( 219 Path.DirectorySeparatorChar) < 0)) 220 strDisplayName = strName; 221 222 if(img != null) return; 223 } 224 catch(Exception) { Debug.Assert(false); } 225 226 ImageList.ImageCollection icons = Program.MainForm.ClientIcons.Images; 227 228 if((strPath.Length <= 3) && (drvHint != null)) 229 { 230 switch(drvHint.DriveType) 231 { 232 case DriveType.Fixed: 233 img = new Bitmap(icons[(int)PwIcon.Drive]); 234 break; 235 case DriveType.CDRom: 236 img = new Bitmap(icons[(int)PwIcon.CDRom]); 237 break; 238 case DriveType.Network: 239 img = new Bitmap(icons[(int)PwIcon.NetworkServer]); 240 break; 241 case DriveType.Ram: 242 img = new Bitmap(icons[(int)PwIcon.Memory]); 243 break; 244 case DriveType.Removable: 245 img = new Bitmap(icons[(int)PwIcon.Disk]); 246 break; 247 default: 248 img = new Bitmap(icons[(int)PwIcon.Folder]); 249 break; 250 } 251 252 return; 253 } 254 255 img = UIUtil.GetFileIcon(strPath, m_nIconDim, m_nIconDim); 256 if(img != null) return; 257 258 if(Directory.Exists(strPath)) 259 img = new Bitmap(icons[(int)PwIcon.Folder]); 260 else if(File.Exists(strPath)) 261 img = new Bitmap(icons[(int)PwIcon.PaperNew]); 262 else 263 { 264 Debug.Assert(false); 265 img = new Bitmap(icons[(int)PwIcon.Star]); 266 } 267 } 268 CreateFolderNode(string strDir, bool bForcePlusMinus, DriveInfo drvHint)269 private TreeNode CreateFolderNode(string strDir, bool bForcePlusMinus, 270 DriveInfo drvHint) 271 { 272 try 273 { 274 DirectoryInfo di = new DirectoryInfo(strDir); 275 276 Image img; 277 string strText = di.Name; 278 GetObjectProps(di.FullName, drvHint, out img, ref strText); 279 280 m_vFolderImages.Add(img); 281 282 TreeNode tn = new TreeNode(strText, m_vFolderImages.Count - 1, 283 m_vFolderImages.Count - 1); 284 tn.Tag = di.FullName; 285 286 InitNodePlusMinus(tn, di, bForcePlusMinus); 287 return tn; 288 } 289 catch(Exception) { Debug.Assert(false); } 290 291 return null; 292 } 293 InitNodePlusMinus(TreeNode tn, DirectoryInfo di, bool bForce)294 private static void InitNodePlusMinus(TreeNode tn, DirectoryInfo di, 295 bool bForce) 296 { 297 bool bMark = true; 298 299 if(!bForce) 300 { 301 try 302 { 303 DirectoryInfo[] vDirs = di.GetDirectories(); 304 bool bFoundDir = false; 305 foreach(DirectoryInfo diSub in vDirs) 306 { 307 if(!IsValidFileSystemObject(diSub)) continue; 308 309 bFoundDir = true; 310 break; 311 } 312 313 if(!bFoundDir) bMark = false; 314 } 315 catch(Exception) { bMark = false; } // Usually unauthorized 316 } 317 318 if(bMark) 319 { 320 tn.Nodes.Add(StrDummyNode); 321 tn.Collapse(); 322 } 323 } 324 RebuildFolderImageList()325 private void RebuildFolderImageList() 326 { 327 ImageList imgNew = UIUtil.BuildImageListUnscaled( 328 m_vFolderImages, m_nIconDim, m_nIconDim); 329 m_tvFolders.ImageList = imgNew; 330 331 if(m_ilFolders != null) m_ilFolders.Dispose(); 332 m_ilFolders = imgNew; 333 } 334 BuildFilesList(DirectoryInfo di)335 private void BuildFilesList(DirectoryInfo di) 336 { 337 m_lvFiles.BeginUpdate(); 338 m_lvFiles.Items.Clear(); 339 340 DirectoryInfo[] vDirs; 341 FileInfo[] vFiles; 342 try 343 { 344 vDirs = di.GetDirectories(); 345 vFiles = di.GetFiles(); 346 } 347 catch(Exception) { m_lvFiles.EndUpdate(); return; } // Unauthorized 348 349 foreach(Image imgFile in m_vFileImages) imgFile.Dispose(); 350 m_vFileImages.Clear(); 351 352 List<ListViewItem> lDirItems = new List<ListViewItem>(); 353 List<ListViewItem> lFileItems = new List<ListViewItem>(); 354 355 foreach(DirectoryInfo diSub in vDirs) 356 { 357 AddFileItem(diSub, m_vFileImages, lDirItems, -1); 358 } 359 foreach(FileInfo fi in vFiles) 360 { 361 AddFileItem(fi, m_vFileImages, lFileItems, fi.Length); 362 } 363 364 m_lvFiles.SmallImageList = null; 365 if(m_ilFiles != null) m_ilFiles.Dispose(); 366 m_ilFiles = UIUtil.BuildImageListUnscaled(m_vFileImages, m_nIconDim, m_nIconDim); 367 m_lvFiles.SmallImageList = m_ilFiles; 368 369 lDirItems.Sort(new FbfPrivLviComparer()); 370 m_lvFiles.Items.AddRange(lDirItems.ToArray()); 371 lFileItems.Sort(new FbfPrivLviComparer()); 372 m_lvFiles.Items.AddRange(lFileItems.ToArray()); 373 m_lvFiles.EndUpdate(); 374 375 EnableControlsEx(); 376 } 377 IsValidFileSystemObject(FileSystemInfo fsi)378 private static bool IsValidFileSystemObject(FileSystemInfo fsi) 379 { 380 if(fsi == null) { Debug.Assert(false); return false; } 381 382 string strName = fsi.Name; 383 if(string.IsNullOrEmpty(strName) || (strName == ".") || 384 (strName == "..")) return false; 385 if(strName.EndsWith(".lnk", StrUtil.CaseIgnoreCmp)) return false; 386 if(strName.EndsWith(".url", StrUtil.CaseIgnoreCmp)) return false; 387 388 FileAttributes fa = fsi.Attributes; 389 if((long)(fa & FileAttributes.ReparsePoint) != 0) return false; 390 if(((long)(fa & FileAttributes.System) != 0) && 391 ((long)(fa & FileAttributes.Hidden) != 0)) return false; 392 393 return true; 394 } 395 AddFileItem(FileSystemInfo fsi, List<Image> lImages, List<ListViewItem> lItems, long lFileLength)396 private void AddFileItem(FileSystemInfo fsi, List<Image> lImages, 397 List<ListViewItem> lItems, long lFileLength) 398 { 399 if(!IsValidFileSystemObject(fsi)) return; 400 401 Image img; 402 string strText = fsi.Name; 403 GetObjectProps(fsi.FullName, null, out img, ref strText); 404 405 lImages.Add(img); 406 407 ListViewItem lvi = new ListViewItem(strText, lImages.Count - 1); 408 lvi.Tag = fsi.FullName; 409 410 if(lFileLength < 0) lvi.SubItems.Add(string.Empty); 411 else lvi.SubItems.Add(StrUtil.FormatDataSizeKB((ulong)lFileLength)); 412 413 lItems.Add(lvi); 414 } 415 PerformFileSelection()416 private bool PerformFileSelection() 417 { 418 ListView.SelectedListViewItemCollection lvsic = m_lvFiles.SelectedItems; 419 if((lvsic == null) || (lvsic.Count != 1)) { Debug.Assert(false); return false; } 420 421 string str = (lvsic[0].Tag as string); 422 if(string.IsNullOrEmpty(str)) { Debug.Assert(false); return false; } 423 424 try 425 { 426 if(Directory.Exists(str)) 427 { 428 TreeNode tn = m_tvFolders.SelectedNode; 429 if(tn == null) { Debug.Assert(false); return false; } 430 431 if(!tn.IsExpanded) tn.Expand(); 432 433 foreach(TreeNode tnSub in tn.Nodes) 434 { 435 string strSub = (tnSub.Tag as string); 436 if(string.IsNullOrEmpty(strSub)) { Debug.Assert(false); continue; } 437 438 if(strSub.Equals(str, StrUtil.CaseIgnoreCmp)) 439 { 440 m_tvFolders.SelectedNode = tnSub; 441 tnSub.EnsureVisible(); 442 return false; // Success, but not a file selection! 443 } 444 } 445 446 Debug.Assert(false); 447 } 448 else if(File.Exists(str)) 449 { 450 m_strSelectedFile = str; 451 452 Program.Config.Application.SetWorkingDirectory(m_strContext, 453 UrlUtil.GetFileDirectory(str, false, true)); 454 455 return true; 456 } 457 else { Debug.Assert(false); } 458 } 459 catch(Exception) { Debug.Assert(false); } 460 461 return false; 462 } 463 OnBtnOK(object sender, EventArgs e)464 private void OnBtnOK(object sender, EventArgs e) 465 { 466 if(!PerformFileSelection()) this.DialogResult = DialogResult.None; 467 } 468 OnFilesItemActivate(object sender, EventArgs e)469 private void OnFilesItemActivate(object sender, EventArgs e) 470 { 471 if(PerformFileSelection()) this.DialogResult = DialogResult.OK; 472 } 473 OnFoldersBeforeExpand(object sender, TreeViewCancelEventArgs e)474 private void OnFoldersBeforeExpand(object sender, TreeViewCancelEventArgs e) 475 { 476 TreeNode tn = e.Node; 477 if(tn == null) { Debug.Assert(false); e.Cancel = true; return; } 478 479 if((tn.Nodes.Count == 1) && (tn.Nodes[0].Text == StrDummyNode)) 480 { 481 tn.Nodes.Clear(); 482 List<TreeNode> lNodes = new List<TreeNode>(); 483 484 try 485 { 486 DirectoryInfo di = new DirectoryInfo(tn.Tag as string); 487 DirectoryInfo[] vSubDirs = di.GetDirectories(); 488 foreach(DirectoryInfo diSub in vSubDirs) 489 { 490 if(!IsValidFileSystemObject(diSub)) continue; 491 492 TreeNode tnSub = CreateFolderNode(diSub.FullName, false, null); 493 if(tnSub != null) lNodes.Add(tnSub); 494 } 495 } 496 catch(Exception) { Debug.Assert(false); } 497 498 RebuildFolderImageList(); 499 lNodes.Sort(new FbfPrivTviComparer()); 500 tn.Nodes.AddRange(lNodes.ToArray()); 501 } 502 } 503 BrowseToFolder(string strPath)504 private void BrowseToFolder(string strPath) 505 { 506 try 507 { 508 DirectoryInfo di = new DirectoryInfo(strPath); 509 string[] vPath = di.FullName.Split(Path.DirectorySeparatorChar); 510 if((vPath == null) || (vPath.Length == 0)) { Debug.Assert(false); return; } 511 512 TreeNode tn = null; 513 string str = string.Empty; 514 for(int i = 0; i < vPath.Length; ++i) 515 { 516 if(i > 0) str = UrlUtil.EnsureTerminatingSeparator(str, false); 517 str += vPath[i]; 518 if(i == 0) str = UrlUtil.EnsureTerminatingSeparator(str, false); 519 520 TreeNodeCollection tnc = ((tn != null) ? tn.Nodes : m_tvFolders.Nodes); 521 tn = null; 522 523 foreach(TreeNode tnSub in tnc) 524 { 525 string strSub = (tnSub.Tag as string); 526 if(string.IsNullOrEmpty(strSub)) { Debug.Assert(false); continue; } 527 528 if(strSub.Equals(str, StrUtil.CaseIgnoreCmp)) 529 { 530 tn = tnSub; 531 break; 532 } 533 } 534 535 if(tn == null) { Debug.Assert(false); break; } 536 537 if((i != (vPath.Length - 1)) && !tn.IsExpanded) tn.Expand(); 538 } 539 540 if(tn != null) 541 { 542 m_tvFolders.SelectedNode = tn; 543 tn.EnsureVisible(); 544 } 545 else { Debug.Assert(false); } 546 } 547 catch(Exception) { Debug.Assert(false); } 548 } 549 OnFoldersAfterSelect(object sender, TreeViewEventArgs e)550 private void OnFoldersAfterSelect(object sender, TreeViewEventArgs e) 551 { 552 TreeNode tn = e.Node; 553 string strPath = (tn.Tag as string); 554 if(strPath == null) { Debug.Assert(false); return; } 555 556 try 557 { 558 DirectoryInfo di = new DirectoryInfo(strPath); 559 BuildFilesList(di); 560 } 561 catch(Exception) { Debug.Assert(false); } 562 } 563 OnFilesSelectedIndexChanged(object sender, EventArgs e)564 private void OnFilesSelectedIndexChanged(object sender, EventArgs e) 565 { 566 EnableControlsEx(); 567 } 568 } 569 } 570