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.Globalization; 24 using System.IO; 25 using System.Text; 26 using System.Text.RegularExpressions; 27 28 using KeePassLib.Native; 29 30 namespace KeePassLib.Utility 31 { 32 /// <summary> 33 /// A class containing various static path utility helper methods (like 34 /// stripping extension from a file, etc.). 35 /// </summary> 36 public static class UrlUtil 37 { 38 private static readonly char[] g_vPathTrimCharsWs = new char[] { 39 '\"', ' ', '\t', '\r', '\n' }; 40 41 public static char LocalDirSepChar 42 { 43 get { return Path.DirectorySeparatorChar; } 44 } 45 46 private static char[] g_vDirSepChars = null; 47 private static char[] DirSepChars 48 { 49 get 50 { 51 if(g_vDirSepChars == null) 52 { 53 List<char> l = new List<char>(); 54 l.Add('/'); // For URLs, also on Windows 55 56 // On Unix-like systems, '\\' is not a separator 57 if(!NativeLib.IsUnix()) l.Add('\\'); 58 59 if(!l.Contains(UrlUtil.LocalDirSepChar)) 60 { 61 Debug.Assert(false); 62 l.Add(UrlUtil.LocalDirSepChar); 63 } 64 65 g_vDirSepChars = l.ToArray(); 66 } 67 68 return g_vDirSepChars; 69 } 70 } 71 72 /// <summary> 73 /// Get the directory (path) of a file name. The returned string may be 74 /// terminated by a directory separator character. Example: 75 /// passing <c>C:\\My Documents\\My File.kdb</c> in <paramref name="strFile" /> 76 /// and <c>true</c> to <paramref name="bAppendTerminatingChar"/> 77 /// would produce this string: <c>C:\\My Documents\\</c>. 78 /// </summary> 79 /// <param name="strFile">Full path of a file.</param> 80 /// <param name="bAppendTerminatingChar">Append a terminating directory separator 81 /// character to the returned path.</param> 82 /// <param name="bEnsureValidDirSpec">If <c>true</c>, the returned path 83 /// is guaranteed to be a valid directory path (for example <c>X:\\</c> instead 84 /// of <c>X:</c>, overriding <paramref name="bAppendTerminatingChar" />). 85 /// This should only be set to <c>true</c>, if the returned path is directly 86 /// passed to some directory API.</param> 87 /// <returns>Directory of the file.</returns> GetFileDirectory(string strFile, bool bAppendTerminatingChar, bool bEnsureValidDirSpec)88 public static string GetFileDirectory(string strFile, bool bAppendTerminatingChar, 89 bool bEnsureValidDirSpec) 90 { 91 Debug.Assert(strFile != null); 92 if(strFile == null) throw new ArgumentNullException("strFile"); 93 94 int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars); 95 if(nLastSep < 0) return string.Empty; // No directory 96 97 if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') && 98 (strFile[2] == '\\')) // Length >= 3 and Windows root directory 99 bAppendTerminatingChar = true; 100 101 if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep); 102 return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), 103 (strFile[nLastSep] == '/')); 104 } 105 106 /// <summary> 107 /// Gets the file name of the specified file (full path). Example: 108 /// if <paramref name="strPath" /> is <c>C:\\My Documents\\My File.kdb</c> 109 /// the returned string is <c>My File.kdb</c>. 110 /// </summary> 111 /// <param name="strPath">Full path of a file.</param> 112 /// <returns>File name of the specified file. The return value is 113 /// an empty string (<c>""</c>) if the input parameter is <c>null</c>.</returns> GetFileName(string strPath)114 public static string GetFileName(string strPath) 115 { 116 Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); 117 118 int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); 119 120 if(nLastSep < 0) return strPath; 121 if(nLastSep >= (strPath.Length - 1)) return string.Empty; 122 123 return strPath.Substring(nLastSep + 1); 124 } 125 126 /// <summary> 127 /// Strip the extension of a file. 128 /// </summary> 129 /// <param name="strPath">Full path of a file with extension.</param> 130 /// <returns>File name without extension.</returns> StripExtension(string strPath)131 public static string StripExtension(string strPath) 132 { 133 Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); 134 135 int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); 136 int nLastExtDot = strPath.LastIndexOf('.'); 137 138 if(nLastExtDot <= nLastDirSep) return strPath; 139 140 return strPath.Substring(0, nLastExtDot); 141 } 142 143 /// <summary> 144 /// Get the extension of a file. 145 /// </summary> 146 /// <param name="strPath">Full path of a file with extension.</param> 147 /// <returns>Extension without prepending dot.</returns> GetExtension(string strPath)148 public static string GetExtension(string strPath) 149 { 150 Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); 151 152 int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); 153 int nLastExtDot = strPath.LastIndexOf('.'); 154 155 if(nLastExtDot <= nLastDirSep) return string.Empty; 156 if(nLastExtDot == (strPath.Length - 1)) return string.Empty; 157 158 return strPath.Substring(nLastExtDot + 1); 159 } 160 161 /// <summary> 162 /// Ensure that a path is terminated with a directory separator character. 163 /// </summary> 164 /// <param name="strPath">Input path.</param> 165 /// <param name="bUrl">If <c>true</c>, a slash (<c>/</c>) is appended to 166 /// the string if it's not terminated already. If <c>false</c>, the 167 /// default system directory separator character is used.</param> 168 /// <returns>Path having a directory separator as last character.</returns> EnsureTerminatingSeparator(string strPath, bool bUrl)169 public static string EnsureTerminatingSeparator(string strPath, bool bUrl) 170 { 171 Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); 172 173 int nLength = strPath.Length; 174 if(nLength <= 0) return string.Empty; 175 176 char chLast = strPath[nLength - 1]; 177 if(Array.IndexOf<char>(UrlUtil.DirSepChars, chLast) >= 0) 178 return strPath; 179 180 if(bUrl) return (strPath + '/'); 181 return (strPath + UrlUtil.LocalDirSepChar); 182 } 183 184 /* /// <summary> 185 /// File access mode enumeration. Used by the <c>FileAccessible</c> 186 /// method. 187 /// </summary> 188 public enum FileAccessMode 189 { 190 /// <summary> 191 /// Opening a file in read mode. The specified file must exist. 192 /// </summary> 193 Read = 0, 194 195 /// <summary> 196 /// Opening a file in create mode. If the file exists already, it 197 /// will be overwritten. If it doesn't exist, it will be created. 198 /// The return value is <c>true</c>, if data can be written to the 199 /// file. 200 /// </summary> 201 Create 202 } */ 203 204 /* /// <summary> 205 /// Test if a specified path is accessible, either in read or write mode. 206 /// </summary> 207 /// <param name="strFilePath">Path to test.</param> 208 /// <param name="fMode">Requested file access mode.</param> 209 /// <returns>Returns <c>true</c> if the specified path is accessible in 210 /// the requested mode, otherwise the return value is <c>false</c>.</returns> 211 public static bool FileAccessible(string strFilePath, FileAccessMode fMode) 212 { 213 Debug.Assert(strFilePath != null); 214 if(strFilePath == null) throw new ArgumentNullException("strFilePath"); 215 216 if(fMode == FileAccessMode.Read) 217 { 218 FileStream fs; 219 220 try { fs = File.OpenRead(strFilePath); } 221 catch(Exception) { return false; } 222 if(fs == null) return false; 223 224 fs.Close(); 225 return true; 226 } 227 else if(fMode == FileAccessMode.Create) 228 { 229 FileStream fs; 230 231 try { fs = File.Create(strFilePath); } 232 catch(Exception) { return false; } 233 if(fs == null) return false; 234 235 fs.Close(); 236 return true; 237 } 238 239 return false; 240 } */ 241 IndexOfSecondEnclQuote(string str)242 internal static int IndexOfSecondEnclQuote(string str) 243 { 244 if(str == null) { Debug.Assert(false); return -1; } 245 if(str.Length <= 1) return -1; 246 if(str[0] != '\"') { Debug.Assert(false); return -1; } 247 248 if(NativeLib.IsUnix()) 249 { 250 // Find non-escaped quote 251 string strFlt = str.Replace("\\\\", new string( 252 StrUtil.GetUnusedChar(str + "\\\""), 2)); // Same length 253 Match m = Regex.Match(strFlt, "[^\\\\]\\u0022"); 254 int i = (((m != null) && m.Success) ? m.Index : -1); 255 return ((i >= 0) ? (i + 1) : -1); // Index of quote 256 } 257 258 // Windows does not allow quotes in folder/file names 259 return str.IndexOf('\"', 1); 260 } 261 GetQuotedAppPath(string strPath)262 public static string GetQuotedAppPath(string strPath) 263 { 264 if(strPath == null) { Debug.Assert(false); return string.Empty; } 265 266 string str = strPath.Trim(); 267 if(str.Length <= 1) return str; 268 if(str[0] != '\"') return str; 269 270 int iSecond = IndexOfSecondEnclQuote(str); 271 if(iSecond <= 0) return str; 272 273 return str.Substring(1, iSecond - 1); 274 } 275 FileUrlToPath(string strUrl)276 public static string FileUrlToPath(string strUrl) 277 { 278 if(strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); } 279 if(strUrl.Length == 0) { Debug.Assert(false); return string.Empty; } 280 281 if(!strUrl.StartsWith(Uri.UriSchemeFile + ":", StrUtil.CaseIgnoreCmp)) 282 { 283 Debug.Assert(false); 284 return strUrl; 285 } 286 287 try 288 { 289 Uri uri = new Uri(strUrl); 290 string str = uri.LocalPath; 291 if(!string.IsNullOrEmpty(str)) return str; 292 } 293 catch(Exception) { Debug.Assert(false); } 294 295 Debug.Assert(false); 296 return strUrl; 297 } 298 UnhideFile(string strFile)299 public static bool UnhideFile(string strFile) 300 { 301 #if KeePassLibSD 302 return false; 303 #else 304 if(strFile == null) throw new ArgumentNullException("strFile"); 305 306 try 307 { 308 FileAttributes fa = File.GetAttributes(strFile); 309 if((long)(fa & FileAttributes.Hidden) == 0) return false; 310 311 return HideFile(strFile, false); 312 } 313 catch(Exception) { } 314 315 return false; 316 #endif 317 } 318 HideFile(string strFile, bool bHide)319 public static bool HideFile(string strFile, bool bHide) 320 { 321 #if KeePassLibSD 322 return false; 323 #else 324 if(strFile == null) throw new ArgumentNullException("strFile"); 325 326 try 327 { 328 FileAttributes fa = File.GetAttributes(strFile); 329 330 if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden); 331 else // Unhide 332 { 333 fa &= ~FileAttributes.Hidden; 334 if((long)fa == 0) fa = FileAttributes.Normal; 335 } 336 337 File.SetAttributes(strFile, fa); 338 return true; 339 } 340 catch(Exception) { } 341 342 return false; 343 #endif 344 } 345 MakeRelativePath(string strBaseFile, string strTargetFile)346 public static string MakeRelativePath(string strBaseFile, string strTargetFile) 347 { 348 if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); 349 if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); 350 if(strBaseFile.Length == 0) return strTargetFile; 351 if(strTargetFile.Length == 0) return string.Empty; 352 353 // Test whether on different Windows drives 354 if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3)) 355 { 356 if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') && 357 (strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') && 358 (strBaseFile[0] != strTargetFile[0])) 359 return strTargetFile; 360 } 361 362 #if (!KeePassLibSD && !KeePassUAP) 363 if(NativeLib.IsUnix()) 364 { 365 #endif 366 bool bBaseUnc = IsUncPath(strBaseFile); 367 bool bTargetUnc = IsUncPath(strTargetFile); 368 if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) 369 return strTargetFile; 370 371 string strBase = GetShortestAbsolutePath(strBaseFile); 372 string strTarget = GetShortestAbsolutePath(strTargetFile); 373 string[] vBase = strBase.Split(UrlUtil.DirSepChars); 374 string[] vTarget = strTarget.Split(UrlUtil.DirSepChars); 375 376 int i = 0; 377 while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) && 378 (vBase[i] == vTarget[i])) { ++i; } 379 380 StringBuilder sbRel = new StringBuilder(); 381 for(int j = i; j < (vBase.Length - 1); ++j) 382 { 383 if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); 384 sbRel.Append(".."); 385 } 386 for(int k = i; k < vTarget.Length; ++k) 387 { 388 if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); 389 sbRel.Append(vTarget[k]); 390 } 391 392 return sbRel.ToString(); 393 #if (!KeePassLibSD && !KeePassUAP) 394 } 395 396 try // Windows 397 { 398 const int nMaxPath = NativeMethods.MAX_PATH * 2; 399 StringBuilder sb = new StringBuilder(nMaxPath + 2); 400 if(!NativeMethods.PathRelativePathTo(sb, strBaseFile, 0, 401 strTargetFile, 0)) 402 return strTargetFile; 403 404 string str = sb.ToString(); 405 while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2); 406 407 return str; 408 } 409 catch(Exception) { Debug.Assert(false); } 410 return strTargetFile; 411 #endif 412 } 413 MakeAbsolutePath(string strBaseFile, string strTargetFile)414 public static string MakeAbsolutePath(string strBaseFile, string strTargetFile) 415 { 416 if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); 417 if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); 418 if(strBaseFile.Length == 0) return strTargetFile; 419 if(strTargetFile.Length == 0) return string.Empty; 420 421 if(IsAbsolutePath(strTargetFile)) return strTargetFile; 422 423 string strBaseDir = GetFileDirectory(strBaseFile, true, false); 424 return GetShortestAbsolutePath(strBaseDir + strTargetFile); 425 } 426 IsAbsolutePath(string strPath)427 public static bool IsAbsolutePath(string strPath) 428 { 429 if(strPath == null) throw new ArgumentNullException("strPath"); 430 if(strPath.Length == 0) return false; 431 432 if(IsUncPath(strPath)) return true; 433 434 try { return Path.IsPathRooted(strPath); } 435 catch(Exception) { Debug.Assert(false); } 436 437 return true; 438 } 439 GetShortestAbsolutePath(string strPath)440 public static string GetShortestAbsolutePath(string strPath) 441 { 442 if(strPath == null) throw new ArgumentNullException("strPath"); 443 if(strPath.Length == 0) return string.Empty; 444 445 // Path.GetFullPath is incompatible with UNC paths traversing over 446 // different server shares (which are created by PathRelativePathTo); 447 // we need to build the absolute path on our own... 448 if(IsUncPath(strPath)) 449 { 450 char chSep = strPath[0]; 451 char[] vSep = ((chSep == '/') ? (new char[] { '/' }) : 452 (new char[] { '\\', '/' })); 453 454 List<string> l = new List<string>(); 455 #if !KeePassLibSD 456 string[] v = strPath.Split(vSep, StringSplitOptions.None); 457 #else 458 string[] v = strPath.Split(vSep); 459 #endif 460 Debug.Assert((v.Length >= 3) && (v[0].Length == 0) && 461 (v[1].Length == 0)); 462 463 foreach(string strPart in v) 464 { 465 if(strPart.Equals(".")) continue; 466 else if(strPart.Equals("..")) 467 { 468 if(l.Count > 0) l.RemoveAt(l.Count - 1); 469 else { Debug.Assert(false); } 470 } 471 else l.Add(strPart); // Do not ignore zero length parts 472 } 473 474 StringBuilder sb = new StringBuilder(); 475 for(int i = 0; i < l.Count; ++i) 476 { 477 // Don't test length of sb, might be 0 due to initial UNC seps 478 if(i > 0) sb.Append(chSep); 479 480 sb.Append(l[i]); 481 } 482 483 return sb.ToString(); 484 } 485 486 string str; 487 try { str = Path.GetFullPath(strPath); } 488 catch(Exception) { Debug.Assert(false); return strPath; } 489 490 Debug.Assert((str.IndexOf("\\..\\") < 0) || NativeLib.IsUnix()); 491 foreach(char ch in UrlUtil.DirSepChars) 492 { 493 string strSep = new string(ch, 1); 494 str = str.Replace(strSep + "." + strSep, strSep); 495 } 496 497 return str; 498 } 499 IsUriChar(char ch)500 internal static bool IsUriChar(char ch) 501 { 502 Debug.Assert(((ulong)'!' == 0x21) && ((ulong)'~' == 0x7E) && 503 ((ulong)'`' == 0x60)); 504 if((ch < '!') || (ch > '~')) return false; 505 506 bool b = true; 507 switch(ch) 508 { 509 case '\"': 510 case '<': 511 case '>': 512 case '\\': 513 case '^': 514 case '`': 515 case '{': 516 case '|': 517 case '}': 518 b = false; 519 break; 520 521 default: break; 522 } 523 524 return b; 525 } 526 IsUriSchemeChar(char ch)527 internal static bool IsUriSchemeChar(char ch) 528 { 529 return (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || 530 ((ch >= '0') && (ch <= '9')) || (ch == '+') || (ch == '-') || 531 (ch == '.')); 532 } 533 GetUrlLength(string strText, int iOffset)534 public static int GetUrlLength(string strText, int iOffset) 535 { 536 return GetUrlLength(strText, iOffset, false); 537 } 538 GetUrlLength(string strText, int iOffset, bool bExclStdTerm)539 internal static int GetUrlLength(string strText, int iOffset, bool bExclStdTerm) 540 { 541 if(strText == null) throw new ArgumentNullException("strText"); 542 if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); 543 544 int i = iOffset, n = strText.Length; 545 if(iOffset > n) throw new ArgumentOutOfRangeException("iOffset"); 546 547 while(i < n) 548 { 549 char ch = strText[i]; 550 if(!IsUriChar(ch)) 551 { 552 if((ch == '\"') || (ch == '>') || (ch == '}')) 553 bExclStdTerm = false; 554 break; 555 } 556 557 ++i; 558 } 559 560 if(bExclStdTerm) 561 { 562 while(i != iOffset) 563 { 564 char ch = strText[i - 1]; 565 566 bool bIsStdTerm = false; 567 switch(ch) 568 { 569 case '!': 570 case ',': 571 case '.': 572 case ':': 573 case ';': 574 case '?': 575 bIsStdTerm = true; 576 break; 577 578 default: break; 579 } 580 581 if(bIsStdTerm) --i; 582 else break; 583 } 584 } 585 586 return (i - iOffset); 587 } 588 589 private static readonly string[] g_vKnownSchemes = new string[] { 590 "callto", "file", "ftp", "http", "https", "ldap", "ldaps", 591 "mailto", "news", "nntp", "sftp", "tel", "telnet" 592 }; 593 // This method only knows some popular schemes; subset of 594 // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml IsKnownScheme(string strScheme)595 internal static bool IsKnownScheme(string strScheme) 596 { 597 if(strScheme == null) { Debug.Assert(false); return false; } 598 599 string str = strScheme.ToLowerInvariant(); 600 return (Array.IndexOf(g_vKnownSchemes, str) >= 0); 601 } 602 GetScheme(string strUrl)603 internal static string GetScheme(string strUrl) 604 { 605 if(string.IsNullOrEmpty(strUrl)) return string.Empty; 606 607 int i = strUrl.IndexOf(':'); 608 if(i > 0) return strUrl.Substring(0, i); 609 610 return string.Empty; 611 } 612 RemoveScheme(string strUrl)613 public static string RemoveScheme(string strUrl) 614 { 615 if(string.IsNullOrEmpty(strUrl)) return string.Empty; 616 617 int i = strUrl.IndexOf(':'); 618 if(i < 0) return strUrl; // No scheme to remove 619 ++i; 620 621 // A single '/' indicates a path (absolute) and should not be removed 622 if(((i + 1) < strUrl.Length) && (strUrl[i] == '/') && 623 (strUrl[i + 1] == '/')) 624 i += 2; // Skip authority prefix 625 626 return strUrl.Substring(i); 627 } 628 ConvertSeparators(string strPath)629 public static string ConvertSeparators(string strPath) 630 { 631 return ConvertSeparators(strPath, UrlUtil.LocalDirSepChar); 632 } 633 ConvertSeparators(string strPath, char chSeparator)634 public static string ConvertSeparators(string strPath, char chSeparator) 635 { 636 if(string.IsNullOrEmpty(strPath)) return string.Empty; 637 638 strPath = strPath.Replace('/', chSeparator); 639 strPath = strPath.Replace('\\', chSeparator); 640 641 return strPath; 642 } 643 IsUncPath(string strPath)644 public static bool IsUncPath(string strPath) 645 { 646 if(strPath == null) throw new ArgumentNullException("strPath"); 647 648 return (strPath.StartsWith("\\\\") || strPath.StartsWith("//")); 649 } 650 FilterFileName(string strName)651 public static string FilterFileName(string strName) 652 { 653 if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; } 654 655 // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file 656 657 StringBuilder sb = new StringBuilder(strName.Length); 658 foreach(char ch in strName) 659 { 660 if(ch < '\u0020') continue; 661 662 switch(ch) 663 { 664 case '\"': 665 case '*': 666 case ':': 667 case '?': 668 break; 669 670 case '/': 671 case '\\': 672 case '|': 673 sb.Append('-'); 674 break; 675 676 case '<': 677 sb.Append('('); 678 break; 679 680 case '>': 681 sb.Append(')'); 682 break; 683 684 default: sb.Append(ch); break; 685 } 686 } 687 688 // Trim trailing spaces and periods 689 for(int i = sb.Length - 1; i >= 0; --i) 690 { 691 char ch = sb[i]; 692 if((ch == ' ') || (ch == '.')) sb.Remove(i, 1); 693 else break; 694 } 695 696 return sb.ToString(); 697 } 698 699 /// <summary> 700 /// Get the host component of a URL. 701 /// This method is faster and more fault-tolerant than creating 702 /// an <code>Uri</code> object and querying its <code>Host</code> 703 /// property. 704 /// </summary> 705 /// <example> 706 /// For the input <code>s://u:p@d.tld:p/p?q#f</code> the return 707 /// value is <code>d.tld</code>. 708 /// </example> GetHost(string strUrl)709 public static string GetHost(string strUrl) 710 { 711 if(strUrl == null) { Debug.Assert(false); return string.Empty; } 712 713 StringBuilder sb = new StringBuilder(); 714 bool bInExtHost = false; 715 for(int i = 0; i < strUrl.Length; ++i) 716 { 717 char ch = strUrl[i]; 718 if(bInExtHost) 719 { 720 if(ch == '/') 721 { 722 if(sb.Length == 0) { } // Ignore leading '/'s 723 else break; 724 } 725 else sb.Append(ch); 726 } 727 else // !bInExtHost 728 { 729 if(ch == ':') bInExtHost = true; 730 } 731 } 732 733 string str = sb.ToString(); 734 if(str.Length == 0) str = strUrl; 735 736 // Remove the login part 737 int nLoginLen = str.IndexOf('@'); 738 if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1); 739 740 // Remove the port 741 int iPort = str.LastIndexOf(':'); 742 if(iPort >= 0) str = str.Substring(0, iPort); 743 744 return str; 745 } 746 AssemblyEquals(string strExt, string strShort)747 public static bool AssemblyEquals(string strExt, string strShort) 748 { 749 if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; } 750 751 if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) || 752 strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp)) 753 return true; 754 755 if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp)) 756 { 757 if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) || 758 strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp)) 759 return true; 760 } 761 762 if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp)) 763 { 764 if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) || 765 strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp)) 766 return true; 767 } 768 769 return false; 770 } 771 GetTempPath()772 public static string GetTempPath() 773 { 774 string strDir; 775 if(NativeLib.IsUnix()) 776 strDir = NativeMethods.GetUserRuntimeDir(); 777 #if KeePassUAP 778 else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; 779 #else 780 else strDir = Path.GetTempPath(); 781 #endif 782 783 try 784 { 785 if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); 786 } 787 catch(Exception) { Debug.Assert(false); } 788 789 return strDir; 790 } 791 792 #if !KeePassLibSD 793 // Structurally mostly equivalent to UrlUtil.GetFileInfos GetFilePaths(string strDir, string strPattern, SearchOption opt)794 public static List<string> GetFilePaths(string strDir, string strPattern, 795 SearchOption opt) 796 { 797 List<string> l = new List<string>(); 798 if(strDir == null) { Debug.Assert(false); return l; } 799 if(strPattern == null) { Debug.Assert(false); return l; } 800 801 string[] v = Directory.GetFiles(strDir, strPattern, opt); 802 if(v == null) { Debug.Assert(false); return l; } 803 804 // Only accept files with the correct extension; GetFiles may 805 // return additional files, see GetFiles documentation 806 string strExt = GetExtension(strPattern); 807 if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && 808 (strExt.IndexOf('?') < 0)) 809 { 810 strExt = "." + strExt; 811 812 foreach(string strPathRaw in v) 813 { 814 if(strPathRaw == null) { Debug.Assert(false); continue; } 815 string strPath = strPathRaw.Trim(g_vPathTrimCharsWs); 816 if(strPath.Length == 0) { Debug.Assert(false); continue; } 817 Debug.Assert(strPath == strPathRaw); 818 819 if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) 820 l.Add(strPathRaw); 821 } 822 } 823 else l.AddRange(v); 824 825 return l; 826 } 827 828 // Structurally mostly equivalent to UrlUtil.GetFilePaths GetFileInfos(DirectoryInfo di, string strPattern, SearchOption opt)829 public static List<FileInfo> GetFileInfos(DirectoryInfo di, string strPattern, 830 SearchOption opt) 831 { 832 List<FileInfo> l = new List<FileInfo>(); 833 if(di == null) { Debug.Assert(false); return l; } 834 if(strPattern == null) { Debug.Assert(false); return l; } 835 836 FileInfo[] v = di.GetFiles(strPattern, opt); 837 if(v == null) { Debug.Assert(false); return l; } 838 839 // Only accept files with the correct extension; GetFiles may 840 // return additional files, see GetFiles documentation 841 string strExt = GetExtension(strPattern); 842 if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && 843 (strExt.IndexOf('?') < 0)) 844 { 845 strExt = "." + strExt; 846 847 foreach(FileInfo fi in v) 848 { 849 if(fi == null) { Debug.Assert(false); continue; } 850 string strPathRaw = fi.FullName; 851 if(strPathRaw == null) { Debug.Assert(false); continue; } 852 string strPath = strPathRaw.Trim(g_vPathTrimCharsWs); 853 if(strPath.Length == 0) { Debug.Assert(false); continue; } 854 Debug.Assert(strPath == strPathRaw); 855 856 if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) 857 l.Add(fi); 858 } 859 } 860 else l.AddRange(v); 861 862 return l; 863 } 864 #endif 865 866 /// <summary> 867 /// Expand shell variables in a string. 868 /// <paramref name="vParams" />[0] is the value of <c>%1</c>, etc. 869 /// </summary> ExpandShellVariables(string strText, string[] vParams, bool bEncParamsToArgs)870 internal static string ExpandShellVariables(string strText, string[] vParams, 871 bool bEncParamsToArgs) 872 { 873 if(strText == null) { Debug.Assert(false); return string.Empty; } 874 875 string[] v = vParams; 876 if(v == null) { Debug.Assert(false); v = new string[0]; } 877 if(bEncParamsToArgs) 878 { 879 for(int i = 0; i < v.Length; ++i) 880 v[i] = NativeLib.EncodeDataToArgs(v[i] ?? string.Empty); 881 } 882 883 string str = strText; 884 NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; 885 886 string strPctPlh = Guid.NewGuid().ToString(); 887 str = str.Replace("%%", strPctPlh); 888 889 for(int i = 0; i <= 9; ++i) 890 { 891 string strPlh = "%" + i.ToString(nfi); 892 893 string strValue = string.Empty; 894 if((i > 0) && ((i - 1) < v.Length)) 895 strValue = (v[i - 1] ?? string.Empty); 896 897 str = str.Replace(strPlh, strValue); 898 899 if(i == 1) 900 { 901 // %L is replaced by the long version of %1; e.g. 902 // HKEY_CLASSES_ROOT\\IE.AssocFile.URL\\Shell\\Open\\Command 903 str = str.Replace("%L", strValue); 904 str = str.Replace("%l", strValue); 905 } 906 } 907 908 if(str.IndexOf("%*") >= 0) 909 { 910 StringBuilder sb = new StringBuilder(); 911 foreach(string strValue in v) 912 { 913 if(!string.IsNullOrEmpty(strValue)) 914 { 915 if(sb.Length > 0) sb.Append(' '); 916 sb.Append(strValue); 917 } 918 } 919 920 str = str.Replace("%*", sb.ToString()); 921 } 922 923 str = str.Replace(strPctPlh, "%"); 924 return str; 925 } 926 GetDriveLetter(string strPath)927 public static char GetDriveLetter(string strPath) 928 { 929 if(strPath == null) throw new ArgumentNullException("strPath"); 930 931 Debug.Assert(default(char) == '\0'); 932 if(strPath.Length < 3) return '\0'; 933 if((strPath[1] != ':') || (strPath[2] != '\\')) return '\0'; 934 935 char ch = char.ToUpperInvariant(strPath[0]); 936 return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0'); 937 } 938 GetSafeFileName(string strName)939 internal static string GetSafeFileName(string strName) 940 { 941 Debug.Assert(!string.IsNullOrEmpty(strName)); 942 943 string str = FilterFileName(GetFileName(strName ?? string.Empty)); 944 945 if(string.IsNullOrEmpty(str)) 946 { 947 Debug.Assert(false); 948 return "File.dat"; 949 } 950 return str; 951 } 952 GetCanonicalUri(string strUri)953 internal static string GetCanonicalUri(string strUri) 954 { 955 if(string.IsNullOrEmpty(strUri)) { Debug.Assert(false); return strUri; } 956 957 try 958 { 959 Uri uri = new Uri(strUri); 960 961 if(uri.IsAbsoluteUri) return uri.AbsoluteUri; 962 else { Debug.Assert(false); } 963 } 964 catch(Exception) { Debug.Assert(false); } 965 966 return strUri; 967 } 968 ParseQuery(string strQuery)969 internal static Dictionary<string, string> ParseQuery(string strQuery) 970 { 971 Dictionary<string, string> d = new Dictionary<string, string>(); 972 if(string.IsNullOrEmpty(strQuery)) return d; 973 974 string[] vKvps = strQuery.Split(new char[] { '?', '&' }); 975 if(vKvps == null) { Debug.Assert(false); return d; } 976 977 foreach(string strKvp in vKvps) 978 { 979 if(string.IsNullOrEmpty(strKvp)) continue; 980 981 string strKey = strKvp, strValue = string.Empty; 982 int iSep = strKvp.IndexOf('='); 983 if(iSep >= 0) 984 { 985 strKey = strKvp.Substring(0, iSep); 986 strValue = strKvp.Substring(iSep + 1); 987 } 988 989 strKey = Uri.UnescapeDataString(strKey); 990 strValue = Uri.UnescapeDataString(strValue); 991 992 d[strKey] = strValue; 993 } 994 995 return d; 996 } 997 } 998 } 999