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(@"&", @"&amp;"); // Must be first
361 			str = str.Replace(@"<", @"&lt;");
362 			str = str.Replace(@">", @"&gt;");
363 			str = str.Replace("\"", @"&quot;");
364 			str = str.Replace("\'", @"&#39;");
365 
366 			if(bNbsp) str = str.Replace(" ", @"&nbsp;"); // 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(@"&amp;", @"&");
379 			str = str.Replace(@"&lt;", @"<");
380 			str = str.Replace(@"&gt;", @">");
381 			str = str.Replace(@"&quot;", "\"");
382 			str = str.Replace(@"&#39;", "\'");
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