diff --git a/gui/Process/ExecutableHelper.cs b/gui/Process/ExecutableHelper.cs index 31facdc..11e4d2b 100644 --- a/gui/Process/ExecutableHelper.cs +++ b/gui/Process/ExecutableHelper.cs @@ -59,21 +59,31 @@ namespace com.muldersoft.slunkcrypt.gui.process private static bool CheckExecutableFile(ref FileStream executableFile, string appBaseDirectory, string suffix) { + bool success = false; try { executableFile = new FileStream(Path.Combine(appBaseDirectory, String.Format(FILENAME_FORMAT, suffix)), FileMode.Open, FileAccess.Read, FileShare.Read); - Version appVersion = VersionInfo.Version; - FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executableFile.Name); - if (string.Equals(fileVersion.FileDescription, "SlunkCrypt", StringComparison.OrdinalIgnoreCase) && - string.Equals(fileVersion.CompanyName, "Muldersoft", StringComparison.OrdinalIgnoreCase) && - (fileVersion.FileMajorPart == appVersion.Major) && (fileVersion.FileMinorPart == appVersion.Minor)) + try { - return true; + Version appVersion = VersionInfo.Version; + FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executableFile.Name); + if (string.Equals(fileVersion.FileDescription, "SlunkCrypt", StringComparison.OrdinalIgnoreCase) && + string.Equals(fileVersion.CompanyName, "Muldersoft", StringComparison.OrdinalIgnoreCase) && + (fileVersion.FileMajorPart == appVersion.Major) && (fileVersion.FileMinorPart == appVersion.Minor)) + { + success = true; + } + } + finally + { + if (!success) + { + executableFile.Dispose(); /*clean-up*/ + } } - executableFile.Dispose(); /*clean-up*/ } catch { } - return false; + return success; } } } diff --git a/gui/Process/PasswordGen.cs b/gui/Process/PasswordGen.cs index 9592b0f..cb13037 100644 --- a/gui/Process/PasswordGen.cs +++ b/gui/Process/PasswordGen.cs @@ -4,19 +4,19 @@ /******************************************************************************/ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Text; using System.Threading.Tasks; +using com.muldersoft.slunkcrypt.gui.utils; + namespace com.muldersoft.slunkcrypt.gui.process { public static class PasswordGen { - private static readonly TimeSpan TIMEOUT_MSEC = TimeSpan.FromSeconds(90); + private static readonly TimeSpan TIMEOUT = TimeSpan.FromSeconds(180); - private const string COMMAND_PASSWRD = "-p {0:D}"; + private const string COMMAND_PASSWRD = "-p"; // ============================================================================= // Exception classes @@ -24,7 +24,7 @@ namespace com.muldersoft.slunkcrypt.gui.process public class GenerationFailedException : IOException { - public GenerationFailedException(string message, Exception innerException) : base(message, innerException) + public GenerationFailedException(string message) : base(message) { } } @@ -35,21 +35,25 @@ namespace com.muldersoft.slunkcrypt.gui.process public static async Task GeneratePassword(int length) { + if (length <= 0) + { + throw new ArgumentOutOfRangeException("Password length must be a positive value!"); + } using (FileStream executableFile = ExecutableHelper.GetExecutableFile()) { - try + string[] arguments = new string[] { COMMAND_PASSWRD, string.Format("{0:D}", length) }; + Tuple result = await ProcessRunner.ExecAsnyc(executableFile.Name, arguments, null, ProcessPriorityClass.BelowNormal, TIMEOUT); + if (result.Item1 == 0) { - string password = await StartProcess(executableFile, length); - if (IsWeakPassword(password)) + foreach (string password in result.Item2) { - throw new InvalidDataException("The generated password string is invalid!"); + if ((password.Length >= length) && (!IsWeakPassword(password))) + { + return password; + } } - return password; - } - catch (Exception e) - { - throw new GenerationFailedException("Failed to generate password string!", e); } + throw new GenerationFailedException("Failed to generate password string!"); } } @@ -72,68 +76,5 @@ namespace com.muldersoft.slunkcrypt.gui.process } return (flags != 0xF); } - - // ============================================================================= - // Internal methods - // ============================================================================= - - private static async Task StartProcess(FileStream executableFile, int length) - { - using (Process process = new Process()) - { - process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.StandardOutputEncoding = Encoding.UTF8; - process.StartInfo.FileName = executableFile.Name; - process.StartInfo.Arguments = string.Format(COMMAND_PASSWRD, length); - process.Start(); - - Stack outputLines = await WaitForExit(process, TIMEOUT_MSEC); - - if (process.ExitCode == 0) - { - while (outputLines.Count > 0) - { - string line = outputLines.Pop(); - if (line.Length >= length) - { - return line; - } - } - } - return string.Empty; - } - } - - private static async Task> WaitForExit(Process process, TimeSpan timeout) - { - Task> readTask; - await Task.WhenAny(readTask = Task.Run(() => ReadOutput(process)), Task.Delay(timeout)); - if (!process.WaitForExit(125)) - { - process.Kill(); - process.WaitForExit(); - } - return await readTask; - } - - private static Stack ReadOutput(Process process) - { - Stack result = new Stack(); - using (StreamReader reader = process.StandardOutput) - { - string line; - while ((line = reader.ReadLine()) != null) - { - line = line.Trim(); - if (line.Length != 0) - { - result.Push(line.Trim()); - } - } - } - return result; - } } } diff --git a/gui/Process/SlunkCryptRunner.cs b/gui/Process/SlunkCryptRunner.cs index fabd8b0..af78ccd 100644 --- a/gui/Process/SlunkCryptRunner.cs +++ b/gui/Process/SlunkCryptRunner.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Threading; @@ -62,34 +61,37 @@ namespace com.muldersoft.slunkcrypt.gui.process // Internal methods // ============================================================================= - protected override double ParseProgressString(StringBuilder currentLine) + protected override double ParseProgressString(ref string currentLine, bool stream) { - Match match; - double progress, result = double.NaN; - do + double temp, result = double.NaN; + int index = int.MaxValue; + Match match = RX_PROGRESS.Match(currentLine); + while (match.Success) { - match = RX_PROGRESS.Match(currentLine.ToString()); - if (match.Success) + if (TryParseProgressValue(match, out temp)) { - if (!double.IsNaN(progress = ParseProgressValue(match))) - { - result = progress; - } - currentLine.Remove(match.Index, match.Length); + result = temp; } + index = Math.Min(index, match.Index); + match = RX_PROGRESS.Match(currentLine, match.Index + match.Length); + } + if (index != int.MaxValue) + { + currentLine = (index > 0) ? currentLine.Substring(0, index - 1).TrimEnd() : string.Empty; } - while (match.Success); return result; } - private static double ParseProgressValue(Match match) + private static bool TryParseProgressValue(Match match, out double progress) { uint intPart, fractPart; if (uint.TryParse(match.Groups[1].Value, out intPart) && uint.TryParse(match.Groups[2].Value, out fractPart)) { - return ((intPart * 10) + fractPart) / 1000.0; + progress = ((intPart * 10) + fractPart) / 1000.0; + return true; } - return double.NaN; + progress = double.NaN; + return false; } private static string GetCommandString(Mode mode) diff --git a/gui/SlunkCryptGUI.xaml.cs b/gui/SlunkCryptGUI.xaml.cs index 1c0a5a3..5936348 100644 --- a/gui/SlunkCryptGUI.xaml.cs +++ b/gui/SlunkCryptGUI.xaml.cs @@ -660,9 +660,13 @@ namespace com.muldersoft.slunkcrypt.gui try { Task passwordTask = Task.Run(() => PasswordGen.GeneratePassword(GEN_PASSWD_LENGTH)); - await Task.WhenAll(passwordTask, Task.Delay(1000)); + await Task.WhenAll(passwordTask, Task.Delay(333)); password = passwordTask.Result; } + catch (ProcessRunner.ProcessStartException err) + { + MessageBox.Show(this, "Failed to create SlunkCrypt process:\n\n" + err.InnerException?.Message ?? err.Message, "Process Creation Error", MessageBoxButton.OK, MessageBoxImage.Error); + } catch (ExecutableHelper.ExecutableNotFoundException) { MessageBox.Show(this, "The SlunkCrypt executable file could not be found.\n\nPlease make sure that the SlunkCrypt CLI executable file is located in the same directory as the GUI program!", "Executable Not Found", MessageBoxButton.OK, MessageBoxImage.Error); @@ -847,13 +851,10 @@ namespace com.muldersoft.slunkcrypt.gui .AppendLine(VersionInfo.VersionStr) .AppendLine("This work has been released under the \u201CCC0 1.0\u201D license!") .AppendLine() - .AppendLine("Source-code repository:") - .AppendLine("https://gitlab.com/lord_mulder/slunkcrypt/") - .AppendLine() - .AppendLine("Operating System: " + Environment.OSVersion.VersionString) + .AppendLine(Environment.OSVersion.VersionString) .AppendLine(string.Format("Operating System Bitness: {0:D}, Process Bitness: {1:D}", Environment.Is64BitOperatingSystem ? 64 : 32, Environment.Is64BitProcess ? 64 : 32)) .AppendLine(".NET Runtime Version: " + Environment.Version) - .AppendLine(string.Format("CPU Count: {0:D}, Architecture: {1}, SSE2 Supported: {2}", Environment.ProcessorCount, cpuFeatures.x64 ? "x64" : "x86", cpuFeatures.sse2 ? "Yes" : "No")) + .AppendLine(string.Format("CPU Count: {0:D}, Architecture: {1}, SSE2 Support: {2}", Environment.ProcessorCount, cpuFeatures.x64 ? "x64" : "x86", cpuFeatures.sse2 ? "Yes" : "No")) .AppendLine() .AppendLine("Using “Silk” icons, by Mark James") .ToString(); diff --git a/gui/Utilities/ProcessRunner.cs b/gui/Utilities/ProcessRunner.cs index 75f9c5e..b5de147 100644 --- a/gui/Utilities/ProcessRunner.cs +++ b/gui/Utilities/ProcessRunner.cs @@ -4,6 +4,7 @@ /******************************************************************************/ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -29,7 +30,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils private readonly Dispatcher m_dispatcher; private readonly ProcessPriorityClass? m_priorityClass; - private volatile bool m_running = false, m_aborted = false, m_disposed = false; + private volatile bool m_running = false, m_finished = false, m_aborted = false, m_disposed = false; // ============================================================================= // Exception classes @@ -63,14 +64,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils { throw new ArgumentException("The given ProcessPriorityClass is undefined!"); } - m_process.StartInfo.UseShellExecute = false; - m_process.StartInfo.CreateNoWindow = true; - m_process.StartInfo.RedirectStandardOutput = true; - m_process.StartInfo.RedirectStandardError = true; - m_process.StartInfo.StandardOutputEncoding = Encoding.UTF8; - m_process.StartInfo.StandardErrorEncoding = Encoding.UTF8; - m_process.EnableRaisingEvents = true; - m_process.Exited += ProcessExitedEventHandler; + InitializeProcess(m_process, m_hasExited, true); } ~ProcessRunner() @@ -89,9 +83,9 @@ namespace com.muldersoft.slunkcrypt.gui.utils { throw new ObjectDisposedException("ProcessRunner"); } - if (m_running) + if (m_running || m_finished) { - throw new InvalidOperationException("Process is already running!"); + throw new InvalidOperationException("Process is still running or has already finished!"); } m_running = true; try @@ -100,10 +94,21 @@ namespace com.muldersoft.slunkcrypt.gui.utils } finally { + m_finished = true; m_running = false; } } + public static async Task> ExecAsnyc(string executableFile, string[] arguments = null, IReadOnlyDictionary environmentVariables = null, ProcessPriorityClass? priorityClass = null, TimeSpan? timeout = null) + { + TaskCompletionSource completionSource = new TaskCompletionSource(); + using (Process process = new Process()) + { + InitializeProcess(process, completionSource); + return await DoExecAsnyc(process, completionSource.Task, executableFile, arguments, environmentVariables, priorityClass, timeout); + } + } + public void AbortProcess() { if ((!m_disposed) && m_running) @@ -132,32 +137,38 @@ namespace com.muldersoft.slunkcrypt.gui.utils // Protected methods // ============================================================================= - protected virtual double ParseProgressString(StringBuilder currentLine) + protected virtual double ParseProgressString(ref string currentLine, bool stream) { return double.NaN; } - // ============================================================================= - // Event handlers - // ============================================================================= - - private void ProcessExitedEventHandler(object sender, EventArgs e) - { - if (m_process.HasExited) - { - m_hasExited.TrySetResult(m_process.ExitCode); - } - } - // ============================================================================= // Internal methods // ============================================================================= + private static void InitializeProcess(Process process, TaskCompletionSource completionSource = null, bool redirStdErr = false) + { + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.StandardOutputEncoding = Encoding.UTF8; + if (redirStdErr) + { + process.StartInfo.RedirectStandardError = true; + process.StartInfo.StandardErrorEncoding = Encoding.UTF8; + } + if (!ReferenceEquals(completionSource, null)) + { + process.EnableRaisingEvents = true; + process.Exited += (sndr, e) => completionSource.TrySetResult(process.ExitCode); + } + } + private async Task DoExecAsnyc(string executablePath, string[] arguments, IReadOnlyDictionary environmentVariables) { try { - StartProcess(executablePath, arguments, environmentVariables); + StartProcess(m_process, executablePath, arguments, environmentVariables, m_priorityClass); } catch (Exception err) { @@ -166,20 +177,34 @@ namespace com.muldersoft.slunkcrypt.gui.utils return await WaitForExit(); } - private void StartProcess(string executablePath, string[] arguments, IReadOnlyDictionary environmentVariables) + private static async Task> DoExecAsnyc(Process process, Task hasExited, string executablePath, string[] arguments, IReadOnlyDictionary environmentVariables, ProcessPriorityClass? priorityClass, TimeSpan? timeout) { - m_process.StartInfo.FileName = executablePath; - m_process.StartInfo.Arguments = CreateArgumentList(arguments); - SetupEnvironment(m_process.StartInfo.EnvironmentVariables, environmentVariables); - m_process.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory; - m_process.Start(); - SetProcessPriority(m_priorityClass); + try + { + StartProcess(process, executablePath, arguments, environmentVariables, priorityClass); + } + catch (Exception err) + { + throw new ProcessStartException("Failed to create the process!", err); + } + string[] outputLines = await WaitForExit(process, hasExited, timeout.GetValueOrDefault(TimeSpan.MaxValue)); + return Tuple.Create(hasExited.Result, outputLines); + } + + private static void StartProcess(Process process, string executablePath, string[] arguments, IReadOnlyDictionary environmentVariables, ProcessPriorityClass? priorityClass) + { + process.StartInfo.FileName = executablePath; + process.StartInfo.Arguments = CreateArgumentList(arguments); + SetupEnvironment(process.StartInfo.EnvironmentVariables, environmentVariables); + process.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory; + process.Start(); + SetProcessPriority(process, priorityClass); } private async Task WaitForExit() { - Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, STDOUT)); - Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError, STDERR)); + Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, (line) => HandleOutput(line, STDOUT))); + Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError, (line) => HandleOutput(line, STDERR))); Task hasExited = m_hasExited.Task; await Task.WhenAll(readStdOutTask, readStdErrTask, hasExited); if (m_aborted || m_disposed) @@ -191,21 +216,33 @@ namespace com.muldersoft.slunkcrypt.gui.utils return hasExited.Result; } - private void ReadProcessOutput(StreamReader reader, bool stream) + private static async Task WaitForExit(Process process, Task hasExited, TimeSpan timeout) { - using(reader) + ConcurrentStack outputLines = new ConcurrentStack(); + Task readerTask = Task.Run(() => ReadProcessOutput(process.StandardOutput, (line) => outputLines.Push(line))); + if (await Task.WhenAny(hasExited, Task.Delay(timeout)) != hasExited) + { + KillProcess(process); + } + await Task.WhenAll(readerTask, hasExited); + return outputLines.ToArray(); + } + + private static void ReadProcessOutput(StreamReader reader, Action outputHandler) + { + using (reader) { char[] buffer = new char[1024]; - StringBuilder currentLine = new StringBuilder(); + StringBuilder lineBuffer = new StringBuilder(); while (!reader.EndOfStream) { - ReadNextChunk(reader, buffer, currentLine, stream); + ReadNextChunk(reader, buffer, lineBuffer, outputHandler); } - NotifyOutputAvailable(currentLine, stream); + ProcessLine(lineBuffer, outputHandler); } } - private void ReadNextChunk(StreamReader reader, char[] buffer, StringBuilder currentLine, bool stderr) + private static void ReadNextChunk(StreamReader reader, char[] buffer, StringBuilder lineBuffer, Action outputHandler) { int count = reader.Read(buffer, 0, buffer.Length); if (count > 0) @@ -215,40 +252,44 @@ namespace com.muldersoft.slunkcrypt.gui.utils char c = buffer[i]; if ((c != '\0') && (c != '\n') && (c != '\r') && (c != '\b')) { - currentLine.Append(c); + lineBuffer.Append(c); } else { - CheckForProgressUpdate(currentLine); - NotifyOutputAvailable(currentLine, stderr); - currentLine.Clear(); + ProcessLine(lineBuffer, outputHandler); + lineBuffer.Clear(); } } - CheckForProgressUpdate(currentLine); } } - private void CheckForProgressUpdate(StringBuilder currentLine) + private static void ProcessLine(StringBuilder lineBuffer, Action outputHandler) { - if (currentLine.Length > 0) + if (lineBuffer.Length > 0) { - double progress = ParseProgressString(currentLine); - if (!(double.IsNaN(progress) || double.IsInfinity(progress))) + String currentLine; + if (!string.IsNullOrWhiteSpace(currentLine = lineBuffer.ToString())) { - NotifyProgressChanged(progress); + outputHandler.Invoke(currentLine.Trim()); } } } - private void NotifyOutputAvailable(StringBuilder currentLine, bool stream) + private void HandleOutput(string currentLine, bool stream) { - if (currentLine.Length > 0) + CheckForProgressUpdate(ref currentLine, stream); + if (!String.IsNullOrEmpty(currentLine)) { - String line = currentLine.ToString().Trim(); - if (!string.IsNullOrEmpty(line)) - { - NotifyOutputAvailable(line, stream); - } + NotifyOutputAvailable(currentLine, stream); + } + } + + private void CheckForProgressUpdate(ref string currentLine, bool stream) + { + double progress = ParseProgressString(ref currentLine, stream); + if (!double.IsNaN(progress)) + { + NotifyProgressChanged(progress); } } @@ -338,13 +379,22 @@ namespace com.muldersoft.slunkcrypt.gui.utils catch { } } - private void SetProcessPriority(ProcessPriorityClass? priorityClass) + private static void KillProcess(Process process) + { + try + { + process.Kill(); + } + catch { } + } + + private static void SetProcessPriority(Process process, ProcessPriorityClass? priorityClass) { try { if (priorityClass.HasValue) { - m_process.PriorityClass = priorityClass.Value; + process.PriorityClass = priorityClass.Value; } } catch { }