1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.ComponentModel;
23 using System.Diagnostics;
24 using System.Reflection;
25 using System.Runtime.InteropServices;
26 using System.Text;
27 using System.Text.RegularExpressions;
28 
29 #if !KeePassUAP
30 using System.IO;
31 using System.Threading;
32 using System.Windows.Forms;
33 #endif
34 
35 using KeePassLib.Resources;
36 using KeePassLib.Utility;
37 
38 namespace KeePassLib.Native
39 {
40 	/// <summary>
41 	/// Interface to native library (library containing fast versions of
42 	/// several cryptographic functions).
43 	/// </summary>
44 	public static class NativeLib
45 	{
46 		private static bool m_bAllowNative = true;
47 
48 		/// <summary>
49 		/// If this property is set to <c>true</c>, the native library is used.
50 		/// If it is <c>false</c>, all calls to functions in this class will fail.
51 		/// </summary>
52 		public static bool AllowNative
53 		{
54 			get { return m_bAllowNative; }
55 			set { m_bAllowNative = value; }
56 		}
57 
58 		private static ulong? m_ouMonoVersion = null;
59 		public static ulong MonoVersion
60 		{
61 			get
62 			{
63 				if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value;
64 
65 				ulong uVersion = 0;
66 				try
67 				{
68 					Type t = Type.GetType("Mono.Runtime");
69 					if(t != null)
70 					{
71 						MethodInfo mi = t.GetMethod("GetDisplayName",
72 							BindingFlags.NonPublic | BindingFlags.Static);
73 						if(mi != null)
74 						{
75 							string strName = (mi.Invoke(null, null) as string);
76 							if(!string.IsNullOrEmpty(strName))
77 							{
78 								Match m = Regex.Match(strName, "\\d+(\\.\\d+)+");
79 								if(m.Success)
80 									uVersion = StrUtil.ParseVersion(m.Value);
81 								else { Debug.Assert(false); }
82 							}
83 							else { Debug.Assert(false); }
84 						}
85 						else { Debug.Assert(false); }
86 					}
87 				}
88 				catch(Exception) { Debug.Assert(false); }
89 
90 				m_ouMonoVersion = uVersion;
91 				return uVersion;
92 			}
93 		}
94 
95 		/// <summary>
96 		/// Determine if the native library is installed.
97 		/// </summary>
98 		/// <returns>Returns <c>true</c>, if the native library is installed.</returns>
IsLibraryInstalled()99 		public static bool IsLibraryInstalled()
100 		{
101 			byte[] pDummy0 = new byte[32];
102 			byte[] pDummy1 = new byte[32];
103 
104 			// Save the native state
105 			bool bCachedNativeState = m_bAllowNative;
106 
107 			// Temporarily allow native functions and try to load the library
108 			m_bAllowNative = true;
109 			bool bResult = TransformKey256(pDummy0, pDummy1, 16);
110 
111 			// Pop native state and return result
112 			m_bAllowNative = bCachedNativeState;
113 			return bResult;
114 		}
115 
116 		private static bool? m_bIsUnix = null;
IsUnix()117 		public static bool IsUnix()
118 		{
119 			if(m_bIsUnix.HasValue) return m_bIsUnix.Value;
120 
121 			PlatformID p = GetPlatformID();
122 
123 			// Mono defines Unix as 128 in early .NET versions
124 #if !KeePassLibSD
125 			m_bIsUnix = ((p == PlatformID.Unix) || (p == PlatformID.MacOSX) ||
126 				((int)p == 128));
127 #else
128 			m_bIsUnix = (((int)p == 4) || ((int)p == 6) || ((int)p == 128));
129 #endif
130 			return m_bIsUnix.Value;
131 		}
132 
133 		private static PlatformID? m_platID = null;
GetPlatformID()134 		public static PlatformID GetPlatformID()
135 		{
136 			if(m_platID.HasValue) return m_platID.Value;
137 
138 #if KeePassUAP
139 			m_platID = EnvironmentExt.OSVersion.Platform;
140 #else
141 			m_platID = Environment.OSVersion.Platform;
142 #endif
143 
144 #if (!KeePassLibSD && !KeePassUAP)
145 			// Mono returns PlatformID.Unix on Mac OS X, workaround this
146 			if(m_platID.Value == PlatformID.Unix)
147 			{
148 				if((RunConsoleApp("uname", null) ?? string.Empty).Trim().Equals(
149 					"Darwin", StrUtil.CaseIgnoreCmp))
150 					m_platID = PlatformID.MacOSX;
151 			}
152 #endif
153 
154 			return m_platID.Value;
155 		}
156 
157 		private static DesktopType? m_tDesktop = null;
GetDesktopType()158 		public static DesktopType GetDesktopType()
159 		{
160 			if(!m_tDesktop.HasValue)
161 			{
162 				DesktopType t = DesktopType.None;
163 				if(!IsUnix()) t = DesktopType.Windows;
164 				else
165 				{
166 					try
167 					{
168 						string strXdg = (Environment.GetEnvironmentVariable(
169 							"XDG_CURRENT_DESKTOP") ?? string.Empty).Trim();
170 						string strGdm = (Environment.GetEnvironmentVariable(
171 							"GDMSESSION") ?? string.Empty).Trim();
172 						StringComparison sc = StrUtil.CaseIgnoreCmp;
173 
174 						if(strXdg.Equals("Unity", sc))
175 							t = DesktopType.Unity;
176 						else if(strXdg.Equals("LXDE", sc))
177 							t = DesktopType.Lxde;
178 						else if(strXdg.Equals("XFCE", sc))
179 							t = DesktopType.Xfce;
180 						else if(strXdg.Equals("MATE", sc))
181 							t = DesktopType.Mate;
182 						else if(strXdg.Equals("X-Cinnamon", sc)) // Mint 18.3
183 							t = DesktopType.Cinnamon;
184 						else if(strXdg.Equals("Pantheon", sc)) // Elementary OS
185 							t = DesktopType.Pantheon;
186 						else if(strXdg.Equals("KDE", sc) || // Mint 16, Kubuntu 17.10
187 							strGdm.Equals("kde-plasma", sc)) // Ubuntu 12.04
188 							t = DesktopType.Kde;
189 						else if(strXdg.Equals("GNOME", sc))
190 						{
191 							if(strGdm.Equals("cinnamon", sc)) // Mint 13
192 								t = DesktopType.Cinnamon;
193 							else t = DesktopType.Gnome; // Fedora 27
194 						}
195 						else if(strXdg.Equals("ubuntu:GNOME", sc))
196 							t = DesktopType.Gnome;
197 					}
198 					catch(Exception) { Debug.Assert(false); }
199 				}
200 
201 				m_tDesktop = t;
202 			}
203 
204 			return m_tDesktop.Value;
205 		}
206 
207 		private static bool? g_obWayland = null;
IsWayland()208 		internal static bool IsWayland()
209 		{
210 			if(!g_obWayland.HasValue)
211 			{
212 				bool b = false;
213 				try
214 				{
215 					// https://www.freedesktop.org/software/systemd/man/pam_systemd.html
216 					b = ((Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") ??
217 						string.Empty).Trim().Equals("wayland", StrUtil.CaseIgnoreCmp));
218 				}
219 				catch(Exception) { Debug.Assert(false); }
220 
221 				g_obWayland = b;
222 			}
223 
224 			return g_obWayland.Value;
225 		}
226 
227 #if (!KeePassLibSD && !KeePassUAP)
RunConsoleApp(string strAppPath, string strParams)228 		public static string RunConsoleApp(string strAppPath, string strParams)
229 		{
230 			return RunConsoleApp(strAppPath, strParams, null);
231 		}
232 
RunConsoleApp(string strAppPath, string strParams, string strStdInput)233 		public static string RunConsoleApp(string strAppPath, string strParams,
234 			string strStdInput)
235 		{
236 			return RunConsoleApp(strAppPath, strParams, strStdInput,
237 				(AppRunFlags.GetStdOutput | AppRunFlags.WaitForExit));
238 		}
239 
RunProcessDelegate()240 		private delegate string RunProcessDelegate();
241 
RunConsoleApp(string strAppPath, string strParams, string strStdInput, AppRunFlags f)242 		public static string RunConsoleApp(string strAppPath, string strParams,
243 			string strStdInput, AppRunFlags f)
244 		{
245 			if(strAppPath == null) throw new ArgumentNullException("strAppPath");
246 			if(strAppPath.Length == 0) throw new ArgumentException("strAppPath");
247 
248 			bool bStdOut = ((f & AppRunFlags.GetStdOutput) != AppRunFlags.None);
249 
250 			RunProcessDelegate fnRun = delegate()
251 			{
252 				Process pToDispose = null;
253 				try
254 				{
255 					ProcessStartInfo psi = new ProcessStartInfo();
256 
257 					psi.FileName = strAppPath;
258 					if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams;
259 
260 					psi.CreateNoWindow = true;
261 					psi.WindowStyle = ProcessWindowStyle.Hidden;
262 					psi.UseShellExecute = false;
263 
264 					psi.RedirectStandardOutput = bStdOut;
265 					if(strStdInput != null) psi.RedirectStandardInput = true;
266 
267 					Process p = StartProcessEx(psi);
268 					pToDispose = p;
269 
270 					if(strStdInput != null)
271 					{
272 						EnsureNoBom(p.StandardInput);
273 
274 						p.StandardInput.Write(strStdInput);
275 						p.StandardInput.Close();
276 					}
277 
278 					string strOutput = string.Empty;
279 					if(bStdOut) strOutput = p.StandardOutput.ReadToEnd();
280 
281 					if((f & AppRunFlags.WaitForExit) != AppRunFlags.None)
282 						p.WaitForExit();
283 					else if((f & AppRunFlags.GCKeepAlive) != AppRunFlags.None)
284 					{
285 						pToDispose = null; // Thread disposes it
286 
287 						Thread th = new Thread(delegate()
288 						{
289 							try { p.WaitForExit(); p.Dispose(); }
290 							catch(Exception) { Debug.Assert(false); }
291 						});
292 						th.Start();
293 					}
294 
295 					return strOutput;
296 				}
297 #if DEBUG
298 				catch(ThreadAbortException) { }
299 				catch(Win32Exception exW)
300 				{
301 					Debug.Assert((strAppPath == ClipboardU.XSel) &&
302 						(exW.NativeErrorCode == 2)); // XSel not found
303 				}
304 				catch(Exception) { Debug.Assert(false); }
305 #else
306 				catch(Exception) { }
307 #endif
308 				finally
309 				{
310 					try { if(pToDispose != null) pToDispose.Dispose(); }
311 					catch(Exception) { Debug.Assert(false); }
312 				}
313 
314 				return null;
315 			};
316 
317 			if((f & AppRunFlags.DoEvents) != AppRunFlags.None)
318 			{
319 				List<Form> lDisabledForms = new List<Form>();
320 				if((f & AppRunFlags.DisableForms) != AppRunFlags.None)
321 				{
322 					foreach(Form form in Application.OpenForms)
323 					{
324 						if(!form.Enabled) continue;
325 
326 						lDisabledForms.Add(form);
327 						form.Enabled = false;
328 					}
329 				}
330 
331 				IAsyncResult ar = fnRun.BeginInvoke(null, null);
332 
333 				while(!ar.AsyncWaitHandle.WaitOne(0))
334 				{
335 					Application.DoEvents();
336 					Thread.Sleep(2);
337 				}
338 
339 				string strRet = fnRun.EndInvoke(ar);
340 
341 				for(int i = lDisabledForms.Count - 1; i >= 0; --i)
342 					lDisabledForms[i].Enabled = true;
343 
344 				return strRet;
345 			}
346 
347 			return fnRun();
348 		}
349 
EnsureNoBom(StreamWriter sw)350 		private static void EnsureNoBom(StreamWriter sw)
351 		{
352 			if(sw == null) { Debug.Assert(false); return; }
353 			if(!MonoWorkarounds.IsRequired(1219)) return;
354 
355 			try
356 			{
357 				Encoding enc = sw.Encoding;
358 				if(enc == null) { Debug.Assert(false); return; }
359 				byte[] pbBom = enc.GetPreamble();
360 				if((pbBom == null) || (pbBom.Length == 0)) return;
361 
362 				// For Mono >= 4.0 (using Microsoft's reference source)
363 				try
364 				{
365 					FieldInfo fi = typeof(StreamWriter).GetField("haveWrittenPreamble",
366 						BindingFlags.Instance | BindingFlags.NonPublic);
367 					if(fi != null)
368 					{
369 						fi.SetValue(sw, true);
370 						return;
371 					}
372 				}
373 				catch(Exception) { Debug.Assert(false); }
374 
375 				// For Mono < 4.0
376 				FieldInfo fiPD = typeof(StreamWriter).GetField("preamble_done",
377 					BindingFlags.Instance | BindingFlags.NonPublic);
378 				if(fiPD != null) fiPD.SetValue(sw, true);
379 				else { Debug.Assert(false); }
380 			}
381 			catch(Exception) { Debug.Assert(false); }
382 		}
383 #endif
384 
385 		/// <summary>
386 		/// Transform a key.
387 		/// </summary>
388 		/// <param name="pBuf256">Source and destination buffer.</param>
389 		/// <param name="pKey256">Key to use in the transformation.</param>
390 		/// <param name="uRounds">Number of transformation rounds.</param>
391 		/// <returns>Returns <c>true</c>, if the key was transformed successfully.</returns>
TransformKey256(byte[] pBuf256, byte[] pKey256, ulong uRounds)392 		public static bool TransformKey256(byte[] pBuf256, byte[] pKey256,
393 			ulong uRounds)
394 		{
395 #if KeePassUAP
396 			return false;
397 #else
398 			if(!m_bAllowNative) return false;
399 
400 			KeyValuePair<IntPtr, IntPtr> kvp = PrepareArrays256(pBuf256, pKey256);
401 			bool bResult = false;
402 
403 			try
404 			{
405 				bResult = NativeMethods.TransformKey(kvp.Key, kvp.Value, uRounds);
406 			}
407 			catch(Exception) { bResult = false; }
408 
409 			if(bResult) GetBuffers256(kvp, pBuf256, pKey256);
410 
411 			FreeArrays(kvp);
412 			return bResult;
413 #endif
414 		}
415 
416 		/// <summary>
417 		/// Benchmark key transformation.
418 		/// </summary>
419 		/// <param name="uTimeMs">Number of milliseconds to perform the benchmark.</param>
420 		/// <param name="puRounds">Number of transformations done.</param>
421 		/// <returns>Returns <c>true</c>, if the benchmark was successful.</returns>
TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds)422 		public static bool TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds)
423 		{
424 			puRounds = 0;
425 
426 #if KeePassUAP
427 			return false;
428 #else
429 			if(!m_bAllowNative) return false;
430 
431 			try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); }
432 			catch(Exception) { return false; }
433 
434 			return true;
435 #endif
436 		}
437 
PrepareArrays256(byte[] pBuf256, byte[] pKey256)438 		private static KeyValuePair<IntPtr, IntPtr> PrepareArrays256(byte[] pBuf256,
439 			byte[] pKey256)
440 		{
441 			Debug.Assert((pBuf256 != null) && (pBuf256.Length == 32));
442 			if(pBuf256 == null) throw new ArgumentNullException("pBuf256");
443 			if(pBuf256.Length != 32) throw new ArgumentException();
444 
445 			Debug.Assert((pKey256 != null) && (pKey256.Length == 32));
446 			if(pKey256 == null) throw new ArgumentNullException("pKey256");
447 			if(pKey256.Length != 32) throw new ArgumentException();
448 
449 			IntPtr hBuf = Marshal.AllocHGlobal(pBuf256.Length);
450 			Marshal.Copy(pBuf256, 0, hBuf, pBuf256.Length);
451 
452 			IntPtr hKey = Marshal.AllocHGlobal(pKey256.Length);
453 			Marshal.Copy(pKey256, 0, hKey, pKey256.Length);
454 
455 			return new KeyValuePair<IntPtr, IntPtr>(hBuf, hKey);
456 		}
457 
GetBuffers256(KeyValuePair<IntPtr, IntPtr> kvpSource, byte[] pbDestBuf, byte[] pbDestKey)458 		private static void GetBuffers256(KeyValuePair<IntPtr, IntPtr> kvpSource,
459 			byte[] pbDestBuf, byte[] pbDestKey)
460 		{
461 			if(kvpSource.Key != IntPtr.Zero)
462 				Marshal.Copy(kvpSource.Key, pbDestBuf, 0, pbDestBuf.Length);
463 
464 			if(kvpSource.Value != IntPtr.Zero)
465 				Marshal.Copy(kvpSource.Value, pbDestKey, 0, pbDestKey.Length);
466 		}
467 
FreeArrays(KeyValuePair<IntPtr, IntPtr> kvpPointers)468 		private static void FreeArrays(KeyValuePair<IntPtr, IntPtr> kvpPointers)
469 		{
470 			if(kvpPointers.Key != IntPtr.Zero)
471 				Marshal.FreeHGlobal(kvpPointers.Key);
472 
473 			if(kvpPointers.Value != IntPtr.Zero)
474 				Marshal.FreeHGlobal(kvpPointers.Value);
475 		}
476 
477 		// internal static Type GetUwpType(string strType)
478 		// {
479 		//	if(string.IsNullOrEmpty(strType)) { Debug.Assert(false); return null; }
480 		//	// https://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/winrtclassactivator.cs
481 		//	return Type.GetType(strType + ", Windows, ContentType=WindowsRuntime", false);
482 		// }
483 
484 		// Cf. DecodeArgsToData
EncodeDataToArgs(string strData)485 		internal static string EncodeDataToArgs(string strData)
486 		{
487 			if(strData == null) { Debug.Assert(false); return string.Empty; }
488 
489 			if(MonoWorkarounds.IsRequired(3471228285U) && IsUnix())
490 			{
491 				string str = strData;
492 
493 				str = str.Replace("\\", "\\\\");
494 				str = str.Replace("\"", "\\\"");
495 
496 				// Whether '\'' needs to be encoded depends on the context
497 				// (e.g. surrounding quotes); as we do not know what the
498 				// caller does with the returned string, we assume that
499 				// it will be used in a context where '\'' must not be
500 				// encoded; this behavior is documented
501 				// str = str.Replace("\'", "\\\'");
502 
503 				return str;
504 			}
505 
506 			// SHELLEXECUTEINFOW structure documentation:
507 			// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/ns-shellapi-shellexecuteinfow
508 			// return strData.Replace("\"", "\"\"\"");
509 
510 			// Microsoft C/C++ startup code:
511 			// https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
512 			// CommandLineToArgvW function:
513 			// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw
514 
515 			StringBuilder sb = new StringBuilder();
516 			int i = 0;
517 			while(i < strData.Length)
518 			{
519 				char ch = strData[i++];
520 
521 				if(ch == '\\')
522 				{
523 					int cBackslashes = 1;
524 					while((i < strData.Length) && (strData[i] == '\\'))
525 					{
526 						++cBackslashes;
527 						++i;
528 					}
529 
530 					if(i == strData.Length)
531 						sb.Append('\\', cBackslashes); // Assume no quote follows
532 					else if(strData[i] == '\"')
533 					{
534 						sb.Append('\\', (cBackslashes * 2) + 1);
535 						sb.Append('\"');
536 						++i;
537 					}
538 					else sb.Append('\\', cBackslashes);
539 				}
540 				else if(ch == '\"') sb.Append("\\\"");
541 				else sb.Append(ch);
542 			}
543 
544 			return sb.ToString();
545 		}
546 
547 		// Cf. EncodeDataToArgs
DecodeArgsToData(string strArgs)548 		internal static string DecodeArgsToData(string strArgs)
549 		{
550 			if(strArgs == null) { Debug.Assert(false); return string.Empty; }
551 
552 			Debug.Assert(StrUtil.Count(strArgs, "\"") == StrUtil.Count(strArgs, "\\\""));
553 
554 			if(MonoWorkarounds.IsRequired(3471228285U) && IsUnix())
555 			{
556 				string str = strArgs;
557 
558 				str = str.Replace("\\\"", "\"");
559 				str = str.Replace("\\\\", "\\");
560 
561 				return str;
562 			}
563 
564 			StringBuilder sb = new StringBuilder();
565 			int i = 0;
566 			while(i < strArgs.Length)
567 			{
568 				char ch = strArgs[i++];
569 
570 				if(ch == '\\')
571 				{
572 					int cBackslashes = 1;
573 					while((i < strArgs.Length) && (strArgs[i] == '\\'))
574 					{
575 						++cBackslashes;
576 						++i;
577 					}
578 
579 					if(i == strArgs.Length)
580 						sb.Append('\\', cBackslashes); // Assume no quote follows
581 					else if(strArgs[i] == '\"')
582 					{
583 						Debug.Assert((cBackslashes & 1) == 1);
584 						sb.Append('\\', (cBackslashes - 1) / 2);
585 						sb.Append('\"');
586 						++i;
587 					}
588 					else sb.Append('\\', cBackslashes);
589 				}
590 				else sb.Append(ch);
591 			}
592 
593 			return sb.ToString();
594 		}
595 
StartProcess(string strFile)596 		internal static void StartProcess(string strFile)
597 		{
598 			StartProcess(strFile, null);
599 		}
600 
StartProcess(string strFile, string strArgs)601 		internal static void StartProcess(string strFile, string strArgs)
602 		{
603 			ProcessStartInfo psi = new ProcessStartInfo();
604 			if(!string.IsNullOrEmpty(strFile)) psi.FileName = strFile;
605 			if(!string.IsNullOrEmpty(strArgs)) psi.Arguments = strArgs;
606 			psi.UseShellExecute = true;
607 
608 			StartProcess(psi);
609 		}
610 
StartProcess(ProcessStartInfo psi)611 		internal static void StartProcess(ProcessStartInfo psi)
612 		{
613 			Process p = StartProcessEx(psi);
614 
615 			try { if(p != null) p.Dispose(); }
616 			catch(Exception) { Debug.Assert(false); }
617 		}
618 
StartProcessEx(ProcessStartInfo psi)619 		internal static Process StartProcessEx(ProcessStartInfo psi)
620 		{
621 			if(psi == null) { Debug.Assert(false); return null; }
622 
623 			string strFileOrg = psi.FileName;
624 			if(string.IsNullOrEmpty(strFileOrg)) { Debug.Assert(false); return null; }
625 			string strArgsOrg = psi.Arguments;
626 
627 			Process p;
628 			try
629 			{
630 				CustomizeProcessStartInfo(psi);
631 				p = Process.Start(psi);
632 			}
633 			finally
634 			{
635 				psi.FileName = strFileOrg; // Restore
636 				psi.Arguments = strArgsOrg;
637 			}
638 
639 			return p;
640 		}
641 
CustomizeProcessStartInfo(ProcessStartInfo psi)642 		private static void CustomizeProcessStartInfo(ProcessStartInfo psi)
643 		{
644 			string strFile = psi.FileName, strArgs = psi.Arguments;
645 
646 			string[] vUrlEncSchemes = new string[] {
647 				"file:", "ftp:", "ftps:", "http:", "https:",
648 				"mailto:", "scp:", "sftp:"
649 			};
650 			foreach(string strPfx in vUrlEncSchemes)
651 			{
652 				if(strFile.StartsWith(strPfx, StrUtil.CaseIgnoreCmp))
653 				{
654 					Debug.Assert(string.IsNullOrEmpty(strArgs));
655 
656 					strFile = strFile.Replace("\"", "%22");
657 					strFile = strFile.Replace("\'", "%27");
658 					strFile = strFile.Replace("\\", "%5C");
659 					break;
660 				}
661 			}
662 
663 			if(IsUnix())
664 			{
665 				if(MonoWorkarounds.IsRequired(19836) && string.IsNullOrEmpty(strArgs))
666 				{
667 					if(Regex.IsMatch(strFile, "^[a-zA-Z][a-zA-Z0-9\\+\\-\\.]*:",
668 						RegexOptions.Singleline) ||
669 						strFile.EndsWith(".html", StrUtil.CaseIgnoreCmp))
670 					{
671 						bool bMacOSX = (GetPlatformID() == PlatformID.MacOSX);
672 
673 						strArgs = "\"" + EncodeDataToArgs(strFile) + "\"";
674 						strFile = (bMacOSX ? "open" : "xdg-open");
675 					}
676 				}
677 
678 				// Mono's Process.Start method replaces '\\' by '/',
679 				// which may cause a different file to be executed;
680 				// therefore, we refuse to start such files
681 				if(strFile.Contains("\\") && MonoWorkarounds.IsRequired(190417))
682 					throw new ArgumentException(KLRes.PathBackslash);
683 
684 				strFile = strFile.Replace("\\", "\\\\"); // If WA not required
685 				strFile = strFile.Replace("\"", "\\\"");
686 			}
687 
688 			psi.FileName = strFile;
689 			psi.Arguments = strArgs;
690 		}
691 	}
692 }
693