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; 22 using System.Collections.Generic; 23 using System.Diagnostics; 24 using System.Globalization; 25 using System.IO; 26 using System.Text; 27 28 #if !KeePassUAP 29 using System.Drawing; 30 using System.Security.Cryptography; 31 #endif 32 33 using KeePassLib.Collections; 34 using KeePassLib.Cryptography; 35 using KeePassLib.Cryptography.PasswordGenerator; 36 using KeePassLib.Native; 37 using KeePassLib.Security; 38 39 namespace KeePassLib.Utility 40 { 41 /// <summary> 42 /// Character stream class. 43 /// </summary> 44 public sealed class CharStream 45 { 46 private readonly string m_str; 47 private int m_iPos = 0; 48 49 public long Position 50 { 51 get { return m_iPos; } 52 set 53 { 54 if((value < 0) || (value > int.MaxValue)) 55 throw new ArgumentOutOfRangeException("value"); 56 m_iPos = (int)value; 57 } 58 } 59 CharStream(string str)60 public CharStream(string str) 61 { 62 if(str == null) { Debug.Assert(false); throw new ArgumentNullException("str"); } 63 64 m_str = str; 65 } 66 ReadChar()67 public char ReadChar() 68 { 69 if(m_iPos >= m_str.Length) return char.MinValue; 70 71 return m_str[m_iPos++]; 72 } 73 ReadChar(bool bSkipWhiteSpace)74 public char ReadChar(bool bSkipWhiteSpace) 75 { 76 if(!bSkipWhiteSpace) return ReadChar(); 77 78 while(true) 79 { 80 char ch = ReadChar(); 81 if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) 82 return ch; 83 } 84 } 85 PeekChar()86 public char PeekChar() 87 { 88 if(m_iPos >= m_str.Length) return char.MinValue; 89 90 return m_str[m_iPos]; 91 } 92 PeekChar(bool bSkipWhiteSpace)93 public char PeekChar(bool bSkipWhiteSpace) 94 { 95 if(!bSkipWhiteSpace) return PeekChar(); 96 97 int i = m_iPos; 98 while(i < m_str.Length) 99 { 100 char ch = m_str[i]; 101 if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) 102 return ch; 103 ++i; 104 } 105 106 return char.MinValue; 107 } 108 } 109 110 public enum StrEncodingType 111 { 112 Unknown = 0, 113 Default, 114 Ascii, 115 Utf7, 116 Utf8, 117 Utf16LE, 118 Utf16BE, 119 Utf32LE, 120 Utf32BE 121 } 122 123 public sealed class StrEncodingInfo 124 { 125 private readonly StrEncodingType m_type; 126 public StrEncodingType Type 127 { 128 get { return m_type; } 129 } 130 131 private readonly string m_strName; 132 public string Name 133 { 134 get { return m_strName; } 135 } 136 137 private readonly Encoding m_enc; 138 public Encoding Encoding 139 { 140 get { return m_enc; } 141 } 142 143 private readonly uint m_cbCodePoint; 144 /// <summary> 145 /// Size of a character in bytes. 146 /// </summary> 147 public uint CodePointSize 148 { 149 get { return m_cbCodePoint; } 150 } 151 152 private readonly byte[] m_vSig; 153 /// <summary> 154 /// Start signature of the text (byte order mark). 155 /// May be <c>null</c> or empty, if no signature is known. 156 /// </summary> 157 public byte[] StartSignature 158 { 159 get { return m_vSig; } 160 } 161 StrEncodingInfo(StrEncodingType t, string strName, Encoding enc, uint cbCodePoint, byte[] vStartSig)162 public StrEncodingInfo(StrEncodingType t, string strName, Encoding enc, 163 uint cbCodePoint, byte[] vStartSig) 164 { 165 if(strName == null) throw new ArgumentNullException("strName"); 166 if(enc == null) throw new ArgumentNullException("enc"); 167 if(cbCodePoint <= 0) throw new ArgumentOutOfRangeException("cbCodePoint"); 168 169 m_type = t; 170 m_strName = strName; 171 m_enc = enc; 172 m_cbCodePoint = cbCodePoint; 173 m_vSig = vStartSig; 174 } 175 } 176 177 /// <summary> 178 /// A class containing various string helper methods. 179 /// </summary> 180 public static class StrUtil 181 { 182 public static readonly StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase; 183 184 public static StringComparer CaseIgnoreComparer 185 { 186 get { return StringComparer.OrdinalIgnoreCase; } 187 } 188 189 private static bool m_bRtl = false; 190 public static bool RightToLeft 191 { 192 get { return m_bRtl; } 193 set { m_bRtl = value; } 194 } 195 196 private static UTF8Encoding m_encUtf8 = null; 197 public static UTF8Encoding Utf8 198 { 199 get 200 { 201 if(m_encUtf8 == null) m_encUtf8 = new UTF8Encoding(false, false); 202 return m_encUtf8; 203 } 204 } 205 206 private static List<StrEncodingInfo> m_lEncs = null; 207 public static IEnumerable<StrEncodingInfo> Encodings 208 { 209 get 210 { 211 if(m_lEncs != null) return m_lEncs; 212 213 List<StrEncodingInfo> l = new List<StrEncodingInfo>(); 214 215 l.Add(new StrEncodingInfo(StrEncodingType.Default, 216 #if KeePassUAP 217 "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); 218 #else 219 #if !KeePassLibSD 220 Encoding.Default.EncodingName, 221 #else 222 Encoding.Default.WebName, 223 #endif 224 Encoding.Default, 225 (uint)Encoding.Default.GetBytes("a").Length, null)); 226 #endif 227 228 l.Add(new StrEncodingInfo(StrEncodingType.Ascii, 229 "ASCII", Encoding.ASCII, 1, null)); 230 l.Add(new StrEncodingInfo(StrEncodingType.Utf7, 231 "Unicode (UTF-7)", Encoding.UTF7, 1, null)); 232 l.Add(new StrEncodingInfo(StrEncodingType.Utf8, 233 "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); 234 l.Add(new StrEncodingInfo(StrEncodingType.Utf16LE, 235 "Unicode (UTF-16 LE)", new UnicodeEncoding(false, false), 236 2, new byte[] { 0xFF, 0xFE })); 237 l.Add(new StrEncodingInfo(StrEncodingType.Utf16BE, 238 "Unicode (UTF-16 BE)", new UnicodeEncoding(true, false), 239 2, new byte[] { 0xFE, 0xFF })); 240 241 #if !KeePassLibSD 242 l.Add(new StrEncodingInfo(StrEncodingType.Utf32LE, 243 "Unicode (UTF-32 LE)", new UTF32Encoding(false, false), 244 4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 })); 245 l.Add(new StrEncodingInfo(StrEncodingType.Utf32BE, 246 "Unicode (UTF-32 BE)", new UTF32Encoding(true, false), 247 4, new byte[] { 0x0, 0x0, 0xFE, 0xFF })); 248 #endif 249 250 m_lEncs = l; 251 return l; 252 } 253 } 254 255 // public static string RtfPar 256 // { 257 // // get { return (m_bRtl ? "\\rtlpar " : "\\par "); } 258 // get { return "\\par "; } 259 // } 260 261 // /// <summary> 262 // /// Convert a string into a valid RTF string. 263 // /// </summary> 264 // /// <param name="str">Any string.</param> 265 // /// <returns>RTF-encoded string.</returns> 266 // public static string MakeRtfString(string str) 267 // { 268 // Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); 269 // str = str.Replace("\\", "\\\\"); 270 // str = str.Replace("\r", string.Empty); 271 // str = str.Replace("{", "\\{"); 272 // str = str.Replace("}", "\\}"); 273 // str = str.Replace("\n", StrUtil.RtfPar); 274 // StringBuilder sbEncoded = new StringBuilder(); 275 // for(int i = 0; i < str.Length; ++i) 276 // { 277 // char ch = str[i]; 278 // if((int)ch >= 256) 279 // sbEncoded.Append(StrUtil.RtfEncodeChar(ch)); 280 // else sbEncoded.Append(ch); 281 // } 282 // return sbEncoded.ToString(); 283 // } 284 RtfEncodeChar(char ch)285 public static string RtfEncodeChar(char ch) 286 { 287 // Unicode character values must be encoded using 288 // 16-bit numbers (decimal); Unicode values greater 289 // than 32767 must be expressed as negative numbers 290 short sh = (short)ch; 291 return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?"); 292 } 293 RtfIsURtf(string str)294 internal static bool RtfIsURtf(string str) 295 { 296 if(str == null) { Debug.Assert(false); return false; } 297 298 const string p = "{\\urtf"; // Typically "{\\urtf1\\ansi\\ansicpg65001" 299 return (str.StartsWith(p) && (str.Length > p.Length) && 300 char.IsDigit(str[p.Length])); 301 } 302 RtfFix(string strRtf)303 public static string RtfFix(string strRtf) 304 { 305 if(strRtf == null) { Debug.Assert(false); return string.Empty; } 306 307 string str = strRtf; 308 309 // Workaround for .NET bug: the Rtf property of a RichTextBox 310 // can return an RTF text starting with "{\\urtf", but 311 // setting such an RTF text throws an exception (the setter 312 // checks for the RTF text to start with "{\\rtf"); 313 // https://sourceforge.net/p/keepass/discussion/329221/thread/7788872f/ 314 // https://www.microsoft.com/en-us/download/details.aspx?id=10725 315 // https://msdn.microsoft.com/en-us/library/windows/desktop/bb774284.aspx 316 // https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs 317 if(RtfIsURtf(str)) str = str.Remove(2, 1); // Remove the 'u' 318 319 return str; 320 } 321 RtfFilterText(string strText)322 internal static string RtfFilterText(string strText) 323 { 324 if(strText == null) { Debug.Assert(false); return string.Empty; } 325 326 // A U+FFFC character causes the rest of the text to be lost. 327 // With '?', the string length, substring indices and 328 // character visibility remain the same. 329 // More special characters (unproblematic) in rich text boxes: 330 // https://docs.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-gettextex 331 return strText.Replace('\uFFFC', '?'); 332 } 333 ContainsHighChar(string str)334 internal static bool ContainsHighChar(string str) 335 { 336 if(str == null) { Debug.Assert(false); return false; } 337 338 for(int i = 0; i < str.Length; ++i) 339 { 340 if(str[i] > '\u00FF') return true; 341 } 342 343 return false; 344 } 345 346 /// <summary> 347 /// Convert a string to a HTML sequence representing that string. 348 /// </summary> 349 /// <param name="str">String to convert.</param> 350 /// <returns>String, HTML-encoded.</returns> StringToHtml(string str)351 public static string StringToHtml(string str) 352 { 353 return StringToHtml(str, false); 354 } 355 StringToHtml(string str, bool bNbsp)356 internal static string StringToHtml(string str, bool bNbsp) 357 { 358 Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); 359 360 str = str.Replace(@"&", @"&"); // Must be first 361 str = str.Replace(@"<", @"<"); 362 str = str.Replace(@">", @">"); 363 str = str.Replace("\"", @"""); 364 str = str.Replace("\'", @"'"); 365 366 if(bNbsp) str = str.Replace(" ", @" "); // Before <br /> 367 368 str = NormalizeNewLines(str, false); 369 str = str.Replace("\n", @"<br />" + MessageService.NewLine); 370 371 return str; 372 } 373 XmlToString(string str)374 public static string XmlToString(string str) 375 { 376 Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); 377 378 str = str.Replace(@"&", @"&"); 379 str = str.Replace(@"<", @"<"); 380 str = str.Replace(@">", @">"); 381 str = str.Replace(@""", "\""); 382 str = str.Replace(@"'", "\'"); 383 384 return str; 385 } 386 ReplaceCaseInsensitive(string strString, string strFind, string strNew)387 public static string ReplaceCaseInsensitive(string strString, string strFind, 388 string strNew) 389 { 390 Debug.Assert(strString != null); if(strString == null) return strString; 391 Debug.Assert(strFind != null); if(strFind == null) return strString; 392 Debug.Assert(strNew != null); if(strNew == null) return strString; 393 394 string str = strString; 395 396 int nPos = 0; 397 while(nPos < str.Length) 398 { 399 nPos = str.IndexOf(strFind, nPos, StringComparison.OrdinalIgnoreCase); 400 if(nPos < 0) break; 401 402 str = str.Remove(nPos, strFind.Length); 403 str = str.Insert(nPos, strNew); 404 405 nPos += strNew.Length; 406 } 407 408 return str; 409 } 410 SplitCommandLine(string strCmdLine, out string strApp, out string strArgs)411 public static void SplitCommandLine(string strCmdLine, out string strApp, 412 out string strArgs) 413 { 414 if(strCmdLine == null) { Debug.Assert(false); throw new ArgumentNullException("strCmdLine"); } 415 416 string str = strCmdLine.Trim(); 417 strApp = null; 418 strArgs = null; 419 420 if(str.StartsWith("\"")) 421 { 422 int iSecond = UrlUtil.IndexOfSecondEnclQuote(str); 423 if(iSecond >= 1) 424 { 425 strApp = str.Substring(1, iSecond - 1).Trim(); 426 strArgs = str.Remove(0, iSecond + 1).Trim(); 427 } 428 } 429 430 if(strApp == null) 431 { 432 int iSpace = str.IndexOf(' '); 433 if(iSpace >= 0) 434 { 435 strApp = str.Substring(0, iSpace).Trim(); 436 strArgs = str.Remove(0, iSpace + 1).Trim(); 437 } 438 else strApp = str; 439 } 440 441 if(strApp == null) { Debug.Assert(false); strApp = string.Empty; } 442 if(strArgs == null) strArgs = string.Empty; 443 444 strApp = NativeLib.DecodeArgsToData(strApp); 445 } 446 447 // /// <summary> 448 // /// Initialize an RTF document based on given font face and size. 449 // /// </summary> 450 // /// <param name="sb"><c>StringBuilder</c> to put the generated RTF into.</param> 451 // /// <param name="strFontFace">Face name of the font to use.</param> 452 // /// <param name="fFontSize">Size of the font to use.</param> 453 // public static void InitRtf(StringBuilder sb, string strFontFace, float fFontSize) 454 // { 455 // Debug.Assert(sb != null); if(sb == null) throw new ArgumentNullException("sb"); 456 // Debug.Assert(strFontFace != null); if(strFontFace == null) throw new ArgumentNullException("strFontFace"); 457 // sb.Append("{\\rtf1"); 458 // if(m_bRtl) sb.Append("\\fbidis"); 459 // sb.Append("\\ansi\\ansicpg"); 460 // sb.Append(Encoding.Default.CodePage); 461 // sb.Append("\\deff0{\\fonttbl{\\f0\\fswiss MS Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\fswiss "); 462 // sb.Append(strFontFace); 463 // sb.Append(";}{\\f3\\fswiss Arial;}}"); 464 // sb.Append("{\\colortbl\\red0\\green0\\blue0;}"); 465 // if(m_bRtl) sb.Append("\\rtldoc"); 466 // sb.Append("\\deflang1031\\pard\\plain\\f2\\cf0 "); 467 // sb.Append("\\fs"); 468 // sb.Append((int)(fFontSize * 2)); 469 // if(m_bRtl) sb.Append("\\rtlpar\\qr\\rtlch "); 470 // } 471 472 // /// <summary> 473 // /// Convert a simple HTML string to an RTF string. 474 // /// </summary> 475 // /// <param name="strHtmlString">Input HTML string.</param> 476 // /// <returns>RTF string representing the HTML input string.</returns> 477 // public static string SimpleHtmlToRtf(string strHtmlString) 478 // { 479 // StringBuilder sb = new StringBuilder(); 480 // StrUtil.InitRtf(sb, "Microsoft Sans Serif", 8.25f); 481 // sb.Append(" "); 482 // string str = MakeRtfString(strHtmlString); 483 // str = str.Replace("<b>", "\\b "); 484 // str = str.Replace("</b>", "\\b0 "); 485 // str = str.Replace("<i>", "\\i "); 486 // str = str.Replace("</i>", "\\i0 "); 487 // str = str.Replace("<u>", "\\ul "); 488 // str = str.Replace("</u>", "\\ul0 "); 489 // str = str.Replace("<br />", StrUtil.RtfPar); 490 // sb.Append(str); 491 // return sb.ToString(); 492 // } 493 494 /// <summary> 495 /// Convert a <c>Color</c> to a HTML color identifier string. 496 /// </summary> 497 /// <param name="color">Color to convert.</param> 498 /// <param name="bEmptyIfTransparent">If this is <c>true</c>, an empty string 499 /// is returned if the color is transparent.</param> 500 /// <returns>HTML color identifier string.</returns> ColorToUnnamedHtml(Color color, bool bEmptyIfTransparent)501 public static string ColorToUnnamedHtml(Color color, bool bEmptyIfTransparent) 502 { 503 if(bEmptyIfTransparent && (color.A != 255)) 504 return string.Empty; 505 506 StringBuilder sb = new StringBuilder(); 507 byte bt; 508 509 sb.Append('#'); 510 511 bt = (byte)(color.R >> 4); 512 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 513 bt = (byte)(color.R & 0x0F); 514 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 515 516 bt = (byte)(color.G >> 4); 517 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 518 bt = (byte)(color.G & 0x0F); 519 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 520 521 bt = (byte)(color.B >> 4); 522 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 523 bt = (byte)(color.B & 0x0F); 524 if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); 525 526 return sb.ToString(); 527 } 528 529 /// <summary> 530 /// Format an exception and convert it to a string. 531 /// </summary> 532 /// <param name="excp"><c>Exception</c> to convert/format.</param> 533 /// <returns>String representing the exception.</returns> FormatException(Exception excp)534 public static string FormatException(Exception excp) 535 { 536 string strText = string.Empty; 537 538 if(!string.IsNullOrEmpty(excp.Message)) 539 strText += excp.Message + MessageService.NewLine; 540 #if !KeePassLibSD 541 if(!string.IsNullOrEmpty(excp.Source)) 542 strText += excp.Source + MessageService.NewLine; 543 #endif 544 if(!string.IsNullOrEmpty(excp.StackTrace)) 545 strText += excp.StackTrace + MessageService.NewLine; 546 #if !KeePassLibSD 547 #if !KeePassUAP 548 if(excp.TargetSite != null) 549 strText += excp.TargetSite.ToString() + MessageService.NewLine; 550 #endif 551 552 if(excp.Data != null) 553 { 554 strText += MessageService.NewLine; 555 foreach(DictionaryEntry de in excp.Data) 556 strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + 557 MessageService.NewLine; 558 } 559 #endif 560 561 if(excp.InnerException != null) 562 { 563 strText += MessageService.NewLine + "Inner:" + MessageService.NewLine; 564 if(!string.IsNullOrEmpty(excp.InnerException.Message)) 565 strText += excp.InnerException.Message + MessageService.NewLine; 566 #if !KeePassLibSD 567 if(!string.IsNullOrEmpty(excp.InnerException.Source)) 568 strText += excp.InnerException.Source + MessageService.NewLine; 569 #endif 570 if(!string.IsNullOrEmpty(excp.InnerException.StackTrace)) 571 strText += excp.InnerException.StackTrace + MessageService.NewLine; 572 #if !KeePassLibSD 573 #if !KeePassUAP 574 if(excp.InnerException.TargetSite != null) 575 strText += excp.InnerException.TargetSite.ToString(); 576 #endif 577 578 if(excp.InnerException.Data != null) 579 { 580 strText += MessageService.NewLine; 581 foreach(DictionaryEntry de in excp.InnerException.Data) 582 strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + 583 MessageService.NewLine; 584 } 585 #endif 586 } 587 588 return strText; 589 } 590 TryParseUShort(string str, out ushort u)591 public static bool TryParseUShort(string str, out ushort u) 592 { 593 #if !KeePassLibSD 594 return ushort.TryParse(str, out u); 595 #else 596 try { u = ushort.Parse(str); return true; } 597 catch(Exception) { u = 0; return false; } 598 #endif 599 } 600 TryParseInt(string str, out int n)601 public static bool TryParseInt(string str, out int n) 602 { 603 #if !KeePassLibSD 604 return int.TryParse(str, out n); 605 #else 606 try { n = int.Parse(str); return true; } 607 catch(Exception) { n = 0; } 608 return false; 609 #endif 610 } 611 TryParseIntInvariant(string str, out int n)612 public static bool TryParseIntInvariant(string str, out int n) 613 { 614 #if !KeePassLibSD 615 return int.TryParse(str, NumberStyles.Integer, 616 NumberFormatInfo.InvariantInfo, out n); 617 #else 618 try 619 { 620 n = int.Parse(str, NumberStyles.Integer, 621 NumberFormatInfo.InvariantInfo); 622 return true; 623 } 624 catch(Exception) { n = 0; } 625 return false; 626 #endif 627 } 628 TryParseUInt(string str, out uint u)629 public static bool TryParseUInt(string str, out uint u) 630 { 631 #if !KeePassLibSD 632 return uint.TryParse(str, out u); 633 #else 634 try { u = uint.Parse(str); return true; } 635 catch(Exception) { u = 0; } 636 return false; 637 #endif 638 } 639 TryParseUIntInvariant(string str, out uint u)640 public static bool TryParseUIntInvariant(string str, out uint u) 641 { 642 #if !KeePassLibSD 643 return uint.TryParse(str, NumberStyles.Integer, 644 NumberFormatInfo.InvariantInfo, out u); 645 #else 646 try 647 { 648 u = uint.Parse(str, NumberStyles.Integer, 649 NumberFormatInfo.InvariantInfo); 650 return true; 651 } 652 catch(Exception) { u = 0; } 653 return false; 654 #endif 655 } 656 TryParseLong(string str, out long n)657 public static bool TryParseLong(string str, out long n) 658 { 659 #if !KeePassLibSD 660 return long.TryParse(str, out n); 661 #else 662 try { n = long.Parse(str); return true; } 663 catch(Exception) { n = 0; } 664 return false; 665 #endif 666 } 667 TryParseLongInvariant(string str, out long n)668 public static bool TryParseLongInvariant(string str, out long n) 669 { 670 #if !KeePassLibSD 671 return long.TryParse(str, NumberStyles.Integer, 672 NumberFormatInfo.InvariantInfo, out n); 673 #else 674 try 675 { 676 n = long.Parse(str, NumberStyles.Integer, 677 NumberFormatInfo.InvariantInfo); 678 return true; 679 } 680 catch(Exception) { n = 0; } 681 return false; 682 #endif 683 } 684 TryParseULong(string str, out ulong u)685 public static bool TryParseULong(string str, out ulong u) 686 { 687 #if !KeePassLibSD 688 return ulong.TryParse(str, out u); 689 #else 690 try { u = ulong.Parse(str); return true; } 691 catch(Exception) { u = 0; } 692 return false; 693 #endif 694 } 695 TryParseULongInvariant(string str, out ulong u)696 public static bool TryParseULongInvariant(string str, out ulong u) 697 { 698 #if !KeePassLibSD 699 return ulong.TryParse(str, NumberStyles.Integer, 700 NumberFormatInfo.InvariantInfo, out u); 701 #else 702 try 703 { 704 u = ulong.Parse(str, NumberStyles.Integer, 705 NumberFormatInfo.InvariantInfo); 706 return true; 707 } 708 catch(Exception) { u = 0; } 709 return false; 710 #endif 711 } 712 TryParseDateTime(string str, out DateTime dt)713 public static bool TryParseDateTime(string str, out DateTime dt) 714 { 715 #if !KeePassLibSD 716 return DateTime.TryParse(str, out dt); 717 #else 718 try { dt = DateTime.Parse(str); return true; } 719 catch(Exception) { dt = DateTime.UtcNow; } 720 return false; 721 #endif 722 } 723 CompactString3Dots(string strText, int cchMax)724 public static string CompactString3Dots(string strText, int cchMax) 725 { 726 Debug.Assert(strText != null); 727 if(strText == null) throw new ArgumentNullException("strText"); 728 Debug.Assert(cchMax >= 0); 729 if(cchMax < 0) throw new ArgumentOutOfRangeException("cchMax"); 730 731 if(strText.Length <= cchMax) return strText; 732 733 if(cchMax == 0) return string.Empty; 734 if(cchMax <= 3) return new string('.', cchMax); 735 736 return (strText.Substring(0, cchMax - 3) + "..."); 737 } 738 739 private static readonly char[] g_vDots = new char[] { '.', '\u2026' }; 740 private static readonly char[] g_vDotsWS = new char[] { '.', '\u2026', 741 ' ', '\t', '\r', '\n' }; TrimDots(string strText, bool bTrimWhiteSpace)742 internal static string TrimDots(string strText, bool bTrimWhiteSpace) 743 { 744 if(strText == null) { Debug.Assert(false); return string.Empty; } 745 746 return strText.Trim(bTrimWhiteSpace ? g_vDotsWS : g_vDots); 747 } 748 GetStringBetween(string strText, int nStartIndex, string strStart, string strEnd)749 public static string GetStringBetween(string strText, int nStartIndex, 750 string strStart, string strEnd) 751 { 752 int nTemp; 753 return GetStringBetween(strText, nStartIndex, strStart, strEnd, out nTemp); 754 } 755 GetStringBetween(string strText, int nStartIndex, string strStart, string strEnd, out int nInnerStartIndex)756 public static string GetStringBetween(string strText, int nStartIndex, 757 string strStart, string strEnd, out int nInnerStartIndex) 758 { 759 if(strText == null) throw new ArgumentNullException("strText"); 760 if(strStart == null) throw new ArgumentNullException("strStart"); 761 if(strEnd == null) throw new ArgumentNullException("strEnd"); 762 763 nInnerStartIndex = -1; 764 765 int nIndex = strText.IndexOf(strStart, nStartIndex); 766 if(nIndex < 0) return string.Empty; 767 768 nIndex += strStart.Length; 769 770 int nEndIndex = strText.IndexOf(strEnd, nIndex); 771 if(nEndIndex < 0) return string.Empty; 772 773 nInnerStartIndex = nIndex; 774 return strText.Substring(nIndex, nEndIndex - nIndex); 775 } 776 777 /// <summary> 778 /// Removes all characters that are not valid XML characters, 779 /// according to https://www.w3.org/TR/xml/#charsets . 780 /// </summary> 781 /// <param name="strText">Source text.</param> 782 /// <returns>Text containing only valid XML characters.</returns> SafeXmlString(string strText)783 public static string SafeXmlString(string strText) 784 { 785 Debug.Assert(strText != null); // No throw 786 if(string.IsNullOrEmpty(strText)) return strText; 787 788 int nLength = strText.Length; 789 StringBuilder sb = new StringBuilder(nLength); 790 791 for(int i = 0; i < nLength; ++i) 792 { 793 char ch = strText[i]; 794 795 if(((ch >= '\u0020') && (ch <= '\uD7FF')) || 796 (ch == '\u0009') || (ch == '\u000A') || (ch == '\u000D') || 797 ((ch >= '\uE000') && (ch <= '\uFFFD'))) 798 sb.Append(ch); 799 else if((ch >= '\uD800') && (ch <= '\uDBFF')) // High surrogate 800 { 801 if((i + 1) < nLength) 802 { 803 char chLow = strText[i + 1]; 804 if((chLow >= '\uDC00') && (chLow <= '\uDFFF')) // Low sur. 805 { 806 sb.Append(ch); 807 sb.Append(chLow); 808 ++i; 809 } 810 else { Debug.Assert(false); } // Low sur. invalid 811 } 812 else { Debug.Assert(false); } // Low sur. missing 813 } 814 815 Debug.Assert((ch < '\uDC00') || (ch > '\uDFFF')); // Lonely low sur. 816 } 817 818 return sb.ToString(); 819 } 820 821 /* private static Regex g_rxNaturalSplit = null; 822 public static int CompareNaturally(string strX, string strY) 823 { 824 Debug.Assert(strX != null); 825 if(strX == null) throw new ArgumentNullException("strX"); 826 Debug.Assert(strY != null); 827 if(strY == null) throw new ArgumentNullException("strY"); 828 829 if(NativeMethods.SupportsStrCmpNaturally) 830 return NativeMethods.StrCmpNaturally(strX, strY); 831 832 if(g_rxNaturalSplit == null) 833 g_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); 834 835 string[] vPartsX = g_rxNaturalSplit.Split(strX); 836 string[] vPartsY = g_rxNaturalSplit.Split(strY); 837 838 int n = Math.Min(vPartsX.Length, vPartsY.Length); 839 for(int i = 0; i < n; ++i) 840 { 841 string strPartX = vPartsX[i], strPartY = vPartsY[i]; 842 int iPartCompare; 843 844 #if KeePassLibSD 845 try 846 { 847 ulong uX = ulong.Parse(strPartX); 848 ulong uY = ulong.Parse(strPartY); 849 iPartCompare = uX.CompareTo(uY); 850 } 851 catch(Exception) { iPartCompare = string.Compare(strPartX, strPartY, true); } 852 #else 853 ulong uX, uY; 854 if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) 855 iPartCompare = uX.CompareTo(uY); 856 else iPartCompare = string.Compare(strPartX, strPartY, true); 857 #endif 858 859 if(iPartCompare != 0) return iPartCompare; 860 } 861 862 if(vPartsX.Length == vPartsY.Length) return 0; 863 if(vPartsX.Length < vPartsY.Length) return -1; 864 return 1; 865 } */ 866 CompareNaturally(string strX, string strY)867 public static int CompareNaturally(string strX, string strY) 868 { 869 Debug.Assert(strX != null); 870 if(strX == null) throw new ArgumentNullException("strX"); 871 Debug.Assert(strY != null); 872 if(strY == null) throw new ArgumentNullException("strY"); 873 874 if(NativeMethods.SupportsStrCmpNaturally) 875 return NativeMethods.StrCmpNaturally(strX, strY); 876 877 int cX = strX.Length; 878 int cY = strY.Length; 879 if(cX == 0) return ((cY == 0) ? 0 : -1); 880 if(cY == 0) return 1; 881 882 char chFirstX = strX[0]; 883 char chFirstY = strY[0]; 884 bool bExpNum = ((chFirstX >= '0') && (chFirstX <= '9')); 885 bool bExpNumY = ((chFirstY >= '0') && (chFirstY <= '9')); 886 if(bExpNum != bExpNumY) return string.Compare(strX, strY, true); 887 888 int pX = 0; 889 int pY = 0; 890 while((pX < cX) && (pY < cY)) 891 { 892 Debug.Assert(((strX[pX] >= '0') && (strX[pX] <= '9')) == bExpNum); 893 Debug.Assert(((strY[pY] >= '0') && (strY[pY] <= '9')) == bExpNum); 894 895 int pExclX = pX + 1; 896 while(pExclX < cX) 897 { 898 char ch = strX[pExclX]; 899 bool bChNum = ((ch >= '0') && (ch <= '9')); 900 if(bChNum != bExpNum) break; 901 ++pExclX; 902 } 903 904 int pExclY = pY + 1; 905 while(pExclY < cY) 906 { 907 char ch = strY[pExclY]; 908 bool bChNum = ((ch >= '0') && (ch <= '9')); 909 if(bChNum != bExpNum) break; 910 ++pExclY; 911 } 912 913 string strPartX = strX.Substring(pX, pExclX - pX); 914 string strPartY = strY.Substring(pY, pExclY - pY); 915 916 bool bStrCmp = true; 917 if(bExpNum) 918 { 919 // 2^64 - 1 = 18446744073709551615 has length 20 920 if((strPartX.Length <= 19) && (strPartY.Length <= 19)) 921 { 922 ulong uX, uY; 923 if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) 924 { 925 if(uX < uY) return -1; 926 if(uX > uY) return 1; 927 928 bStrCmp = false; 929 } 930 else { Debug.Assert(false); } 931 } 932 else 933 { 934 double dX, dY; 935 if(double.TryParse(strPartX, out dX) && double.TryParse(strPartY, out dY)) 936 { 937 if(dX < dY) return -1; 938 if(dX > dY) return 1; 939 940 bStrCmp = false; 941 } 942 else { Debug.Assert(false); } 943 } 944 } 945 if(bStrCmp) 946 { 947 int c = string.Compare(strPartX, strPartY, true); 948 if(c != 0) return c; 949 } 950 951 bExpNum = !bExpNum; 952 pX = pExclX; 953 pY = pExclY; 954 } 955 956 if(pX >= cX) 957 { 958 Debug.Assert(pX == cX); 959 if(pY >= cY) { Debug.Assert(pY == cY); return 0; } 960 return -1; 961 } 962 963 Debug.Assert(pY == cY); 964 return 1; 965 } 966 RemoveAccelerator(string strMenuText)967 public static string RemoveAccelerator(string strMenuText) 968 { 969 if(strMenuText == null) throw new ArgumentNullException("strMenuText"); 970 971 string str = strMenuText; 972 int iOffset = 0; 973 bool bHasAmp = false; 974 975 // Remove keys of the form "(&X)" 976 while(iOffset < str.Length) 977 { 978 int i = str.IndexOf('&', iOffset); 979 if(i < iOffset) break; 980 981 if((i >= 1) && (str[i - 1] == '(') && ((i + 2) < str.Length) && 982 (str[i + 2] == ')')) 983 { 984 if((i >= 2) && char.IsWhiteSpace(str[i - 2])) 985 str = str.Remove(i - 2, 5); 986 else str = str.Remove(i - 1, 4); 987 988 continue; 989 } 990 991 iOffset = i + 1; 992 bHasAmp = true; 993 } 994 995 return (bHasAmp ? str.Replace(@"&", string.Empty) : str); 996 } 997 AddAccelerator(string strMenuText, List<char> lAvailKeys)998 public static string AddAccelerator(string strMenuText, 999 List<char> lAvailKeys) 1000 { 1001 if(strMenuText == null) { Debug.Assert(false); return string.Empty; } 1002 if(lAvailKeys == null) { Debug.Assert(false); return strMenuText; } 1003 1004 for(int i = 0; i < strMenuText.Length; ++i) 1005 { 1006 char ch = char.ToLowerInvariant(strMenuText[i]); 1007 1008 for(int j = 0; j < lAvailKeys.Count; ++j) 1009 { 1010 if(char.ToLowerInvariant(lAvailKeys[j]) == ch) 1011 { 1012 lAvailKeys.RemoveAt(j); 1013 return strMenuText.Insert(i, @"&"); 1014 } 1015 } 1016 } 1017 1018 return strMenuText; 1019 } 1020 EncodeMenuText(string strText)1021 public static string EncodeMenuText(string strText) 1022 { 1023 if(strText == null) throw new ArgumentNullException("strText"); 1024 1025 return strText.Replace(@"&", @"&&"); 1026 } 1027 EncodeToolTipText(string strText)1028 public static string EncodeToolTipText(string strText) 1029 { 1030 if(strText == null) throw new ArgumentNullException("strText"); 1031 1032 return strText.Replace(@"&", @"&&&"); 1033 } 1034 IsHexString(string str, bool bStrict)1035 public static bool IsHexString(string str, bool bStrict) 1036 { 1037 if(str == null) throw new ArgumentNullException("str"); 1038 1039 foreach(char ch in str) 1040 { 1041 if((ch >= '0') && (ch <= '9')) continue; 1042 if((ch >= 'a') && (ch <= 'f')) continue; 1043 if((ch >= 'A') && (ch <= 'F')) continue; 1044 1045 if(bStrict) return false; 1046 1047 if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) 1048 continue; 1049 1050 return false; 1051 } 1052 1053 return true; 1054 } 1055 IsHexString(byte[] pbUtf8, bool bStrict)1056 public static bool IsHexString(byte[] pbUtf8, bool bStrict) 1057 { 1058 if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); 1059 1060 for(int i = 0; i < pbUtf8.Length; ++i) 1061 { 1062 byte bt = pbUtf8[i]; 1063 if((bt >= (byte)'0') && (bt <= (byte)'9')) continue; 1064 if((bt >= (byte)'a') && (bt <= (byte)'f')) continue; 1065 if((bt >= (byte)'A') && (bt <= (byte)'F')) continue; 1066 1067 if(bStrict) return false; 1068 1069 if((bt == (byte)' ') || (bt == (byte)'\t') || 1070 (bt == (byte)'\r') || (bt == (byte)'\n')) 1071 continue; 1072 1073 return false; 1074 } 1075 1076 return true; 1077 } 1078 1079 #if !KeePassLibSD 1080 private static readonly char[] g_vPatternPartsSep = new char[] { '*' }; SimplePatternMatch(string strPattern, string strText, StringComparison sc)1081 public static bool SimplePatternMatch(string strPattern, string strText, 1082 StringComparison sc) 1083 { 1084 if(strPattern == null) throw new ArgumentNullException("strPattern"); 1085 if(strText == null) throw new ArgumentNullException("strText"); 1086 1087 if(strPattern.IndexOf('*') < 0) return strText.Equals(strPattern, sc); 1088 1089 string[] vPatternParts = strPattern.Split(g_vPatternPartsSep, 1090 StringSplitOptions.RemoveEmptyEntries); 1091 if(vPatternParts == null) { Debug.Assert(false); return true; } 1092 if(vPatternParts.Length == 0) return true; 1093 1094 if(strText.Length == 0) return false; 1095 1096 if((strPattern[0] != '*') && !strText.StartsWith(vPatternParts[0], sc)) 1097 return false; 1098 if((strPattern[strPattern.Length - 1] != '*') && !strText.EndsWith( 1099 vPatternParts[vPatternParts.Length - 1], sc)) 1100 return false; 1101 1102 int iOffset = 0; 1103 for(int i = 0; i < vPatternParts.Length; ++i) 1104 { 1105 string strPart = vPatternParts[i]; 1106 1107 int iFound = strText.IndexOf(strPart, iOffset, sc); 1108 if(iFound < iOffset) return false; 1109 1110 iOffset = iFound + strPart.Length; 1111 if(iOffset == strText.Length) return (i == (vPatternParts.Length - 1)); 1112 } 1113 1114 return true; 1115 } 1116 #endif // !KeePassLibSD 1117 StringToBool(string str)1118 public static bool StringToBool(string str) 1119 { 1120 if(string.IsNullOrEmpty(str)) return false; // No assert 1121 1122 string s = str.Trim().ToLower(); 1123 if(s == "true") return true; 1124 if(s == "yes") return true; 1125 if(s == "1") return true; 1126 if(s == "enabled") return true; 1127 if(s == "checked") return true; 1128 1129 return false; 1130 } 1131 StringToBoolEx(string str)1132 public static bool? StringToBoolEx(string str) 1133 { 1134 if(string.IsNullOrEmpty(str)) return null; 1135 1136 string s = str.Trim().ToLower(); 1137 if(s == "true") return true; 1138 if(s == "false") return false; 1139 1140 return null; 1141 } 1142 BoolToString(bool bValue)1143 public static string BoolToString(bool bValue) 1144 { 1145 return (bValue ? "true" : "false"); 1146 } 1147 1148 public static string BoolToStringEx(bool? bValue) 1149 { 1150 if(bValue.HasValue) return BoolToString(bValue.Value); 1151 return "null"; 1152 } 1153 1154 /// <summary> 1155 /// Normalize new line characters in a string. Input strings may 1156 /// contain mixed new line character sequences from all commonly 1157 /// used operating systems (i.e. \r\n from Windows, \n from Unix 1158 /// and \r from Mac OS. 1159 /// </summary> 1160 /// <param name="str">String with mixed new line characters.</param> 1161 /// <param name="bWindows">If <c>true</c>, new line characters 1162 /// are normalized for Windows (\r\n); if <c>false</c>, new line 1163 /// characters are normalized for Unix (\n).</param> 1164 /// <returns>String with normalized new line characters.</returns> NormalizeNewLines(string str, bool bWindows)1165 public static string NormalizeNewLines(string str, bool bWindows) 1166 { 1167 if(string.IsNullOrEmpty(str)) return str; 1168 1169 str = str.Replace("\r\n", "\n"); 1170 str = str.Replace("\r", "\n"); 1171 1172 if(bWindows) str = str.Replace("\n", "\r\n"); 1173 1174 return str; 1175 } 1176 NormalizeNewLines(ProtectedStringDictionary dict, bool bWindows)1177 public static void NormalizeNewLines(ProtectedStringDictionary dict, 1178 bool bWindows) 1179 { 1180 if(dict == null) { Debug.Assert(false); return; } 1181 1182 List<string> lKeys = dict.GetKeys(); 1183 foreach(string strKey in lKeys) 1184 { 1185 ProtectedString ps = dict.Get(strKey); 1186 if(ps == null) { Debug.Assert(false); continue; } 1187 1188 char[] v = ps.ReadChars(); 1189 if(!IsNewLineNormalized(v, bWindows)) 1190 dict.Set(strKey, new ProtectedString(ps.IsProtected, 1191 NormalizeNewLines(ps.ReadString(), bWindows))); 1192 MemUtil.ZeroArray<char>(v); 1193 } 1194 } 1195 IsNewLineNormalized(char[] v, bool bWindows)1196 internal static bool IsNewLineNormalized(char[] v, bool bWindows) 1197 { 1198 if(v == null) { Debug.Assert(false); return true; } 1199 1200 if(bWindows) 1201 { 1202 int iFreeCr = -2; // Must be < -1 (for test "!= (i - 1)") 1203 1204 for(int i = 0; i < v.Length; ++i) 1205 { 1206 char ch = v[i]; 1207 1208 if(ch == '\r') 1209 { 1210 if(iFreeCr >= 0) return false; 1211 iFreeCr = i; 1212 } 1213 else if(ch == '\n') 1214 { 1215 if(iFreeCr != (i - 1)) return false; 1216 iFreeCr = -2; // Consume \r 1217 } 1218 } 1219 1220 return (iFreeCr < 0); // Ensure no \r at end 1221 } 1222 1223 return (Array.IndexOf<char>(v, '\r') < 0); 1224 } 1225 GetNewLineSeq(string str)1226 public static string GetNewLineSeq(string str) 1227 { 1228 if(str == null) { Debug.Assert(false); return MessageService.NewLine; } 1229 1230 int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0; 1231 char chLast = char.MinValue; 1232 for(int i = 0; i < n; ++i) 1233 { 1234 char ch = str[i]; 1235 1236 if(ch == '\r') ++nCr; 1237 else if(ch == '\n') 1238 { 1239 ++nLf; 1240 if(chLast == '\r') ++nCrLf; 1241 } 1242 1243 chLast = ch; 1244 } 1245 1246 nCr -= nCrLf; 1247 nLf -= nCrLf; 1248 1249 int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf)); 1250 if(nMax == 0) return MessageService.NewLine; 1251 1252 if(nCrLf == nMax) return "\r\n"; 1253 return ((nLf == nMax) ? "\n" : "\r"); 1254 } 1255 AlphaNumericOnly(string str)1256 public static string AlphaNumericOnly(string str) 1257 { 1258 if(string.IsNullOrEmpty(str)) return str; 1259 1260 StringBuilder sb = new StringBuilder(); 1261 for(int i = 0; i < str.Length; ++i) 1262 { 1263 char ch = str[i]; 1264 if(((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || 1265 ((ch >= '0') && (ch <= '9'))) 1266 sb.Append(ch); 1267 } 1268 1269 return sb.ToString(); 1270 } 1271 FormatDataSize(ulong uBytes)1272 public static string FormatDataSize(ulong uBytes) 1273 { 1274 const ulong uKB = 1024; 1275 const ulong uMB = uKB * uKB; 1276 const ulong uGB = uMB * uKB; 1277 const ulong uTB = uGB * uKB; 1278 1279 if(uBytes == 0) return "0 KB"; 1280 if(uBytes <= uKB) return "1 KB"; 1281 if(uBytes <= uMB) return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; 1282 if(uBytes <= uGB) return (((uBytes - 1UL) / uMB) + 1UL).ToString() + " MB"; 1283 if(uBytes <= uTB) return (((uBytes - 1UL) / uGB) + 1UL).ToString() + " GB"; 1284 1285 return (((uBytes - 1UL) / uTB) + 1UL).ToString() + " TB"; 1286 } 1287 FormatDataSizeKB(ulong uBytes)1288 public static string FormatDataSizeKB(ulong uBytes) 1289 { 1290 const ulong uKB = 1024; 1291 1292 if(uBytes == 0) return "0 KB"; 1293 if(uBytes <= uKB) return "1 KB"; 1294 1295 return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; 1296 } 1297 1298 private static readonly char[] m_vVersionSep = new char[] { '.', ',' }; ParseVersion(string strVersion)1299 public static ulong ParseVersion(string strVersion) 1300 { 1301 if(strVersion == null) { Debug.Assert(false); return 0; } 1302 1303 string[] vVer = strVersion.Split(m_vVersionSep); 1304 if((vVer == null) || (vVer.Length == 0)) { Debug.Assert(false); return 0; } 1305 1306 ushort uPart; 1307 StrUtil.TryParseUShort(vVer[0].Trim(), out uPart); 1308 ulong uVer = ((ulong)uPart << 48); 1309 1310 if(vVer.Length >= 2) 1311 { 1312 StrUtil.TryParseUShort(vVer[1].Trim(), out uPart); 1313 uVer |= ((ulong)uPart << 32); 1314 } 1315 1316 if(vVer.Length >= 3) 1317 { 1318 StrUtil.TryParseUShort(vVer[2].Trim(), out uPart); 1319 uVer |= ((ulong)uPart << 16); 1320 } 1321 1322 if(vVer.Length >= 4) 1323 { 1324 StrUtil.TryParseUShort(vVer[3].Trim(), out uPart); 1325 uVer |= (ulong)uPart; 1326 } 1327 1328 return uVer; 1329 } 1330 VersionToString(ulong uVersion)1331 public static string VersionToString(ulong uVersion) 1332 { 1333 return VersionToString(uVersion, 1U); 1334 } 1335 1336 [Obsolete] VersionToString(ulong uVersion, bool bEnsureAtLeastTwoComp)1337 public static string VersionToString(ulong uVersion, 1338 bool bEnsureAtLeastTwoComp) 1339 { 1340 return VersionToString(uVersion, (bEnsureAtLeastTwoComp ? 2U : 1U)); 1341 } 1342 VersionToString(ulong uVersion, uint uMinComp)1343 public static string VersionToString(ulong uVersion, uint uMinComp) 1344 { 1345 StringBuilder sb = new StringBuilder(); 1346 uint uComp = 0; 1347 1348 for(int i = 0; i < 4; ++i) 1349 { 1350 if(uVersion == 0UL) break; 1351 1352 ushort us = (ushort)(uVersion >> 48); 1353 1354 if(sb.Length > 0) sb.Append('.'); 1355 1356 sb.Append(us.ToString(NumberFormatInfo.InvariantInfo)); 1357 ++uComp; 1358 1359 uVersion <<= 16; 1360 } 1361 1362 while(uComp < uMinComp) 1363 { 1364 if(sb.Length > 0) sb.Append('.'); 1365 1366 sb.Append('0'); 1367 ++uComp; 1368 } 1369 1370 return sb.ToString(); 1371 } 1372 1373 private static readonly byte[] m_pbOptEnt = { 0xA5, 0x74, 0x2E, 0xEC }; 1374 EncryptString(string strPlainText)1375 public static string EncryptString(string strPlainText) 1376 { 1377 if(string.IsNullOrEmpty(strPlainText)) return string.Empty; 1378 1379 try 1380 { 1381 byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText); 1382 byte[] pbEnc = CryptoUtil.ProtectData(pbPlain, m_pbOptEnt, 1383 DataProtectionScope.CurrentUser); 1384 1385 #if (!KeePassLibSD && !KeePassUAP) 1386 return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None); 1387 #else 1388 return Convert.ToBase64String(pbEnc); 1389 #endif 1390 } 1391 catch(Exception) { Debug.Assert(false); } 1392 1393 return strPlainText; 1394 } 1395 DecryptString(string strCipherText)1396 public static string DecryptString(string strCipherText) 1397 { 1398 if(string.IsNullOrEmpty(strCipherText)) return string.Empty; 1399 1400 try 1401 { 1402 byte[] pbEnc = Convert.FromBase64String(strCipherText); 1403 byte[] pbPlain = CryptoUtil.UnprotectData(pbEnc, m_pbOptEnt, 1404 DataProtectionScope.CurrentUser); 1405 1406 return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length); 1407 } 1408 catch(Exception) { Debug.Assert(false); } 1409 1410 return strCipherText; 1411 } 1412 SerializeIntArray(int[] vNumbers)1413 public static string SerializeIntArray(int[] vNumbers) 1414 { 1415 if(vNumbers == null) throw new ArgumentNullException("vNumbers"); 1416 1417 StringBuilder sb = new StringBuilder(); 1418 for(int i = 0; i < vNumbers.Length; ++i) 1419 { 1420 if(i > 0) sb.Append(' '); 1421 sb.Append(vNumbers[i].ToString(NumberFormatInfo.InvariantInfo)); 1422 } 1423 1424 return sb.ToString(); 1425 } 1426 DeserializeIntArray(string strSerialized)1427 public static int[] DeserializeIntArray(string strSerialized) 1428 { 1429 if(strSerialized == null) throw new ArgumentNullException("strSerialized"); 1430 if(strSerialized.Length == 0) return new int[0]; 1431 1432 string[] vParts = strSerialized.Split(' '); 1433 int[] v = new int[vParts.Length]; 1434 1435 for(int i = 0; i < vParts.Length; ++i) 1436 { 1437 int n; 1438 if(!TryParseIntInvariant(vParts[i], out n)) { Debug.Assert(false); } 1439 v[i] = n; 1440 } 1441 1442 return v; 1443 } 1444 1445 private static readonly char[] g_vTagSep = new char[] { ',', ';' }; NormalizeTag(string strTag)1446 internal static string NormalizeTag(string strTag) 1447 { 1448 if(strTag == null) { Debug.Assert(false); return string.Empty; } 1449 1450 strTag = strTag.Trim(); 1451 1452 for(int i = g_vTagSep.Length - 1; i >= 0; --i) 1453 strTag = strTag.Replace(g_vTagSep[i], '.'); 1454 1455 return strTag; 1456 } 1457 NormalizeTags(List<string> lTags)1458 internal static void NormalizeTags(List<string> lTags) 1459 { 1460 if(lTags == null) { Debug.Assert(false); return; } 1461 1462 bool bRemoveNulls = false; 1463 for(int i = lTags.Count - 1; i >= 0; --i) 1464 { 1465 string str = NormalizeTag(lTags[i]); 1466 1467 if(string.IsNullOrEmpty(str)) 1468 { 1469 lTags[i] = null; 1470 bRemoveNulls = true; 1471 } 1472 else lTags[i] = str; 1473 } 1474 1475 if(bRemoveNulls) 1476 { 1477 Predicate<string> f = delegate(string str) { return (str == null); }; 1478 lTags.RemoveAll(f); 1479 } 1480 1481 if(lTags.Count >= 2) 1482 { 1483 // Deduplicate 1484 Dictionary<string, bool> d = new Dictionary<string, bool>(); 1485 for(int i = lTags.Count - 1; i >= 0; --i) 1486 d[lTags[i]] = true; 1487 if(d.Count != lTags.Count) 1488 { 1489 lTags.Clear(); 1490 lTags.AddRange(d.Keys); 1491 } 1492 1493 lTags.Sort(StrUtil.CompareNaturally); 1494 } 1495 } 1496 AddTags(List<string> lTags, IEnumerable<string> eNewTags)1497 internal static void AddTags(List<string> lTags, IEnumerable<string> eNewTags) 1498 { 1499 if(lTags == null) { Debug.Assert(false); return; } 1500 if(eNewTags == null) { Debug.Assert(false); return; } 1501 1502 lTags.AddRange(eNewTags); 1503 NormalizeTags(lTags); 1504 } 1505 TagsToString(List<string> lTags, bool bForDisplay)1506 public static string TagsToString(List<string> lTags, bool bForDisplay) 1507 { 1508 if(lTags == null) throw new ArgumentNullException("lTags"); 1509 1510 #if DEBUG 1511 // The input should be normalized 1512 foreach(string str in lTags) { Debug.Assert(NormalizeTag(str) == str); } 1513 List<string> l = new List<string>(lTags); 1514 NormalizeTags(l); 1515 Debug.Assert(l.Count == lTags.Count); 1516 #endif 1517 1518 int n = lTags.Count; 1519 if(n == 0) return string.Empty; 1520 if(n == 1) return (lTags[0] ?? string.Empty); 1521 1522 StringBuilder sb = new StringBuilder(); 1523 bool bFirst = true; 1524 1525 foreach(string strTag in lTags) 1526 { 1527 if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); continue; } 1528 1529 if(bFirst) bFirst = false; 1530 else 1531 { 1532 if(bForDisplay) sb.Append(", "); 1533 else sb.Append(';'); 1534 } 1535 1536 sb.Append(strTag); 1537 } 1538 1539 return sb.ToString(); 1540 } 1541 StringToTags(string strTags)1542 public static List<string> StringToTags(string strTags) 1543 { 1544 if(strTags == null) throw new ArgumentNullException("strTags"); 1545 1546 List<string> lTags = new List<string>(); 1547 if(strTags.Length == 0) return lTags; 1548 1549 lTags.AddRange(strTags.Split(g_vTagSep)); 1550 1551 NormalizeTags(lTags); 1552 return lTags; 1553 } 1554 Obfuscate(string strPlain)1555 public static string Obfuscate(string strPlain) 1556 { 1557 if(strPlain == null) { Debug.Assert(false); return string.Empty; } 1558 if(strPlain.Length == 0) return string.Empty; 1559 1560 byte[] pb = StrUtil.Utf8.GetBytes(strPlain); 1561 1562 Array.Reverse(pb); 1563 for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); 1564 1565 #if (!KeePassLibSD && !KeePassUAP) 1566 return Convert.ToBase64String(pb, Base64FormattingOptions.None); 1567 #else 1568 return Convert.ToBase64String(pb); 1569 #endif 1570 } 1571 Deobfuscate(string strObf)1572 public static string Deobfuscate(string strObf) 1573 { 1574 if(strObf == null) { Debug.Assert(false); return string.Empty; } 1575 if(strObf.Length == 0) return string.Empty; 1576 1577 try 1578 { 1579 byte[] pb = Convert.FromBase64String(strObf); 1580 1581 for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); 1582 Array.Reverse(pb); 1583 1584 return StrUtil.Utf8.GetString(pb, 0, pb.Length); 1585 } 1586 catch(Exception) { Debug.Assert(false); } 1587 1588 return string.Empty; 1589 } 1590 1591 /// <summary> 1592 /// Split a string and include the separators in the splitted array. 1593 /// </summary> 1594 /// <param name="str">String to split.</param> 1595 /// <param name="vSeps">Separators.</param> 1596 /// <param name="bCaseSensitive">Specifies whether separators are 1597 /// matched case-sensitively or not.</param> 1598 /// <returns>Splitted string including separators.</returns> SplitWithSep(string str, string[] vSeps, bool bCaseSensitive)1599 public static List<string> SplitWithSep(string str, string[] vSeps, 1600 bool bCaseSensitive) 1601 { 1602 if(str == null) throw new ArgumentNullException("str"); 1603 if(vSeps == null) throw new ArgumentNullException("vSeps"); 1604 1605 List<string> v = new List<string>(); 1606 while(true) 1607 { 1608 int minIndex = int.MaxValue, minSep = -1; 1609 for(int i = 0; i < vSeps.Length; ++i) 1610 { 1611 string strSep = vSeps[i]; 1612 if(string.IsNullOrEmpty(strSep)) { Debug.Assert(false); continue; } 1613 1614 int iIndex = (bCaseSensitive ? str.IndexOf(strSep) : 1615 str.IndexOf(strSep, StrUtil.CaseIgnoreCmp)); 1616 if((iIndex >= 0) && (iIndex < minIndex)) 1617 { 1618 minIndex = iIndex; 1619 minSep = i; 1620 } 1621 } 1622 1623 if(minIndex == int.MaxValue) break; 1624 1625 v.Add(str.Substring(0, minIndex)); 1626 v.Add(vSeps[minSep]); 1627 1628 str = str.Substring(minIndex + vSeps[minSep].Length); 1629 } 1630 1631 v.Add(str); 1632 return v; 1633 } 1634 MultiToSingleLine(string strMulti)1635 public static string MultiToSingleLine(string strMulti) 1636 { 1637 if(strMulti == null) { Debug.Assert(false); return string.Empty; } 1638 if(strMulti.Length == 0) return string.Empty; 1639 1640 string str = strMulti; 1641 str = str.Replace("\r\n", " "); 1642 str = str.Replace('\r', ' '); 1643 str = str.Replace('\n', ' '); 1644 1645 return str; 1646 } 1647 SplitSearchTerms(string strSearch)1648 public static List<string> SplitSearchTerms(string strSearch) 1649 { 1650 List<string> l = new List<string>(); 1651 if(strSearch == null) { Debug.Assert(false); return l; } 1652 1653 StringBuilder sbTerm = new StringBuilder(); 1654 bool bQuoted = false; 1655 1656 for(int i = 0; i < strSearch.Length; ++i) 1657 { 1658 char ch = strSearch[i]; 1659 1660 if(((ch == ' ') || (ch == '\t') || (ch == '\r') || 1661 (ch == '\n')) && !bQuoted) 1662 { 1663 if(sbTerm.Length != 0) 1664 { 1665 l.Add(sbTerm.ToString()); 1666 sbTerm.Remove(0, sbTerm.Length); 1667 } 1668 } 1669 else if(ch == '\"') bQuoted = !bQuoted; 1670 else sbTerm.Append(ch); 1671 } 1672 if(sbTerm.Length != 0) l.Add(sbTerm.ToString()); 1673 1674 return l; 1675 } 1676 CompareLengthGt(string x, string y)1677 public static int CompareLengthGt(string x, string y) 1678 { 1679 if(x.Length == y.Length) return 0; 1680 return ((x.Length > y.Length) ? -1 : 1); 1681 } 1682 IsDataUri(string strUri)1683 public static bool IsDataUri(string strUri) 1684 { 1685 return IsDataUri(strUri, null); 1686 } 1687 IsDataUri(string strUri, string strReqMediaType)1688 public static bool IsDataUri(string strUri, string strReqMediaType) 1689 { 1690 if(strUri == null) { Debug.Assert(false); return false; } 1691 // strReqMediaType may be null 1692 1693 const string strPrefix = "data:"; 1694 if(!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp)) 1695 return false; 1696 1697 int iC = strUri.IndexOf(','); 1698 if(iC < 0) return false; 1699 1700 if(!string.IsNullOrEmpty(strReqMediaType)) 1701 { 1702 int iS = strUri.IndexOf(';', 0, iC); 1703 int iTerm = ((iS >= 0) ? iS : iC); 1704 1705 string strMedia = strUri.Substring(strPrefix.Length, 1706 iTerm - strPrefix.Length); 1707 if(!strMedia.Equals(strReqMediaType, StrUtil.CaseIgnoreCmp)) 1708 return false; 1709 } 1710 1711 return true; 1712 } 1713 1714 /// <summary> 1715 /// Create a data URI (according to RFC 2397). 1716 /// </summary> 1717 /// <param name="pbData">Data to encode.</param> 1718 /// <param name="strMediaType">Optional MIME type. If <c>null</c>, 1719 /// an appropriate type is used.</param> 1720 /// <returns>Data URI.</returns> DataToDataUri(byte[] pbData, string strMediaType)1721 public static string DataToDataUri(byte[] pbData, string strMediaType) 1722 { 1723 if(pbData == null) throw new ArgumentNullException("pbData"); 1724 1725 if(strMediaType == null) strMediaType = "application/octet-stream"; 1726 1727 #if (!KeePassLibSD && !KeePassUAP) 1728 return ("data:" + strMediaType + ";base64," + Convert.ToBase64String( 1729 pbData, Base64FormattingOptions.None)); 1730 #else 1731 return ("data:" + strMediaType + ";base64," + Convert.ToBase64String( 1732 pbData)); 1733 #endif 1734 } 1735 1736 /// <summary> 1737 /// Convert a data URI (according to RFC 2397) to binary data. 1738 /// </summary> 1739 /// <param name="strDataUri">Data URI to decode.</param> 1740 /// <returns>Decoded binary data.</returns> DataUriToData(string strDataUri)1741 public static byte[] DataUriToData(string strDataUri) 1742 { 1743 if(strDataUri == null) throw new ArgumentNullException("strDataUri"); 1744 if(!strDataUri.StartsWith("data:", StrUtil.CaseIgnoreCmp)) return null; 1745 1746 int iSep = strDataUri.IndexOf(','); 1747 if(iSep < 0) return null; 1748 1749 string strDesc = strDataUri.Substring(5, iSep - 5); 1750 bool bBase64 = strDesc.EndsWith(";base64", StrUtil.CaseIgnoreCmp); 1751 1752 string strData = strDataUri.Substring(iSep + 1); 1753 1754 if(bBase64) return Convert.FromBase64String(strData); 1755 1756 MemoryStream ms = new MemoryStream(); 1757 Encoding enc = Encoding.ASCII; 1758 1759 string[] v = strData.Split('%'); 1760 byte[] pb = enc.GetBytes(v[0]); 1761 ms.Write(pb, 0, pb.Length); 1762 for(int i = 1; i < v.Length; ++i) 1763 { 1764 ms.WriteByte(Convert.ToByte(v[i].Substring(0, 2), 16)); 1765 pb = enc.GetBytes(v[i].Substring(2)); 1766 ms.Write(pb, 0, pb.Length); 1767 } 1768 1769 pb = ms.ToArray(); 1770 ms.Close(); 1771 return pb; 1772 } 1773 1774 // https://www.iana.org/assignments/media-types/media-types.xhtml 1775 private static readonly string[] g_vMediaTypePfx = new string[] { 1776 "application/", "audio/", "example/", "font/", "image/", 1777 "message/", "model/", "multipart/", "text/", "video/" 1778 }; IsMediaType(string str)1779 internal static bool IsMediaType(string str) 1780 { 1781 if(str == null) { Debug.Assert(false); return false; } 1782 if(str.Length == 0) return false; 1783 1784 foreach(string strPfx in g_vMediaTypePfx) 1785 { 1786 if(str.StartsWith(strPfx, StrUtil.CaseIgnoreCmp)) 1787 return true; 1788 } 1789 1790 return false; 1791 } 1792 GetCustomMediaType(string strFormat)1793 internal static string GetCustomMediaType(string strFormat) 1794 { 1795 if(strFormat == null) 1796 { 1797 Debug.Assert(false); 1798 return "application/octet-stream"; 1799 } 1800 1801 if(IsMediaType(strFormat)) return strFormat; 1802 1803 StringBuilder sb = new StringBuilder(); 1804 for(int i = 0; i < strFormat.Length; ++i) 1805 { 1806 char ch = strFormat[i]; 1807 1808 if(((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || 1809 ((ch >= '0') && (ch <= '9'))) 1810 sb.Append(ch); 1811 else if((sb.Length != 0) && ((ch == '-') || (ch == '_'))) 1812 sb.Append(ch); 1813 else { Debug.Assert(false); } 1814 } 1815 1816 if(sb.Length == 0) return "application/octet-stream"; 1817 1818 return ("application/vnd." + PwDefs.ShortProductName + 1819 "." + sb.ToString()); 1820 } 1821 1822 /// <summary> 1823 /// Remove placeholders from a string (wrapped in '{' and '}'). 1824 /// This doesn't remove environment variables (wrapped in '%'). 1825 /// </summary> RemovePlaceholders(string str)1826 public static string RemovePlaceholders(string str) 1827 { 1828 if(str == null) { Debug.Assert(false); return string.Empty; } 1829 1830 while(true) 1831 { 1832 int iPlhStart = str.IndexOf('{'); 1833 if(iPlhStart < 0) break; 1834 1835 int iPlhEnd = str.IndexOf('}', iPlhStart); // '{' might be at end 1836 if(iPlhEnd < 0) break; 1837 1838 str = (str.Substring(0, iPlhStart) + str.Substring(iPlhEnd + 1)); 1839 } 1840 1841 return str; 1842 } 1843 GetEncoding(StrEncodingType t)1844 public static StrEncodingInfo GetEncoding(StrEncodingType t) 1845 { 1846 foreach(StrEncodingInfo sei in StrUtil.Encodings) 1847 { 1848 if(sei.Type == t) return sei; 1849 } 1850 1851 return null; 1852 } 1853 GetEncoding(string strName)1854 public static StrEncodingInfo GetEncoding(string strName) 1855 { 1856 foreach(StrEncodingInfo sei in StrUtil.Encodings) 1857 { 1858 if(sei.Name == strName) return sei; 1859 } 1860 1861 return null; 1862 } 1863 1864 private static string[] m_vPrefSepChars = null; 1865 /// <summary> 1866 /// Find a character that does not occur within a given text. 1867 /// </summary> GetUnusedChar(string strText)1868 public static char GetUnusedChar(string strText) 1869 { 1870 if(strText == null) { Debug.Assert(false); return '@'; } 1871 1872 if(m_vPrefSepChars == null) 1873 m_vPrefSepChars = new string[5] { 1874 "@!$%#/\\:;,.*-_?", 1875 PwCharSet.UpperCase, PwCharSet.LowerCase, 1876 PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial 1877 }; 1878 1879 for(int i = 0; i < m_vPrefSepChars.Length; ++i) 1880 { 1881 foreach(char ch in m_vPrefSepChars[i]) 1882 { 1883 if(strText.IndexOf(ch) < 0) return ch; 1884 } 1885 } 1886 1887 for(char ch = '\u00C0'; ch < char.MaxValue; ++ch) 1888 { 1889 if(strText.IndexOf(ch) < 0) return ch; 1890 } 1891 1892 return char.MinValue; 1893 } 1894 ByteToSafeChar(byte bt)1895 public static char ByteToSafeChar(byte bt) 1896 { 1897 const char chDefault = '.'; 1898 1899 // 00-1F are C0 control chars 1900 if(bt < 0x20) return chDefault; 1901 1902 // 20-7F are basic Latin; 7F is DEL 1903 if(bt < 0x7F) return (char)bt; 1904 1905 // 80-9F are C1 control chars 1906 if(bt < 0xA0) return chDefault; 1907 1908 // A0-FF are Latin-1 supplement; AD is soft hyphen 1909 if(bt == 0xAD) return '-'; 1910 return (char)bt; 1911 } 1912 Count(string str, string strNeedle)1913 public static int Count(string str, string strNeedle) 1914 { 1915 if(str == null) { Debug.Assert(false); return 0; } 1916 if(string.IsNullOrEmpty(strNeedle)) { Debug.Assert(false); return 0; } 1917 1918 int iOffset = 0, iCount = 0; 1919 while(iOffset < str.Length) 1920 { 1921 int p = str.IndexOf(strNeedle, iOffset); 1922 if(p < 0) break; 1923 1924 ++iCount; 1925 iOffset = p + 1; 1926 } 1927 1928 return iCount; 1929 } 1930 ReplaceNulls(string str)1931 internal static string ReplaceNulls(string str) 1932 { 1933 if(str == null) { Debug.Assert(false); return null; } 1934 1935 if(str.IndexOf('\0') < 0) return str; 1936 1937 // Replacing null characters by spaces is the 1938 // behavior of Notepad (on Windows 10) 1939 return str.Replace('\0', ' '); 1940 } 1941 1942 // https://sourceforge.net/p/keepass/discussion/329220/thread/f98dece5/ EnsureLtrPath(string strPath)1943 internal static string EnsureLtrPath(string strPath) 1944 { 1945 if(strPath == null) { Debug.Assert(false); return string.Empty; } 1946 1947 string str = strPath; 1948 1949 // U+200E = left-to-right mark 1950 str = str.Replace("\\", "\\\u200E"); 1951 str = str.Replace("/", "/\u200E"); 1952 str = str.Replace("\u200E\u200E", "\u200E"); // Remove duplicates 1953 1954 return str; 1955 } 1956 IsValid(string str)1957 internal static bool IsValid(string str) 1958 { 1959 if(str == null) { Debug.Assert(false); return false; } 1960 1961 int cc = str.Length; 1962 for(int i = 0; i < cc; ++i) 1963 { 1964 char ch = str[i]; 1965 if(ch == '\0') return false; 1966 1967 if(char.IsLowSurrogate(ch)) return false; 1968 if(char.IsHighSurrogate(ch)) 1969 { 1970 if(++i >= cc) return false; // High surrogate at end 1971 if(!char.IsLowSurrogate(str[i])) return false; 1972 1973 UnicodeCategory uc2 = char.GetUnicodeCategory(str, i - 1); 1974 if(uc2 == UnicodeCategory.OtherNotAssigned) return false; 1975 1976 continue; 1977 } 1978 1979 UnicodeCategory uc = char.GetUnicodeCategory(ch); 1980 if(uc == UnicodeCategory.OtherNotAssigned) return false; 1981 } 1982 1983 return true; 1984 } 1985 RemoveWhiteSpace(string str)1986 internal static string RemoveWhiteSpace(string str) 1987 { 1988 if(str == null) { Debug.Assert(false); return string.Empty; } 1989 1990 int cc = str.Length; 1991 if(cc == 0) return string.Empty; 1992 1993 StringBuilder sb = new StringBuilder(); 1994 1995 for(int i = 0; i < cc; ++i) 1996 { 1997 char ch = str[i]; 1998 if(char.IsWhiteSpace(ch)) continue; 1999 sb.Append(ch); 2000 } 2001 2002 return sb.ToString(); 2003 } 2004 } 2005 } 2006