Refactored the ProcessRunner and PasswordGen classes.

This commit is contained in:
LoRd_MuldeR 2022-02-10 22:55:52 +01:00
parent 5dfaabf6c4
commit 18e398a1e7
Signed by: mulder
GPG Key ID: 2B5913365F57E03F
5 changed files with 170 additions and 166 deletions

View File

@ -59,21 +59,31 @@ namespace com.muldersoft.slunkcrypt.gui.process
private static bool CheckExecutableFile(ref FileStream executableFile, string appBaseDirectory, string suffix) private static bool CheckExecutableFile(ref FileStream executableFile, string appBaseDirectory, string suffix)
{ {
bool success = false;
try try
{ {
executableFile = new FileStream(Path.Combine(appBaseDirectory, String.Format(FILENAME_FORMAT, suffix)), FileMode.Open, FileAccess.Read, FileShare.Read); executableFile = new FileStream(Path.Combine(appBaseDirectory, String.Format(FILENAME_FORMAT, suffix)), FileMode.Open, FileAccess.Read, FileShare.Read);
Version appVersion = VersionInfo.Version; try
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))
{ {
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 { } catch { }
return false; return success;
} }
} }
} }

View File

@ -4,19 +4,19 @@
/******************************************************************************/ /******************************************************************************/
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using com.muldersoft.slunkcrypt.gui.utils;
namespace com.muldersoft.slunkcrypt.gui.process namespace com.muldersoft.slunkcrypt.gui.process
{ {
public static class PasswordGen 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 // Exception classes
@ -24,7 +24,7 @@ namespace com.muldersoft.slunkcrypt.gui.process
public class GenerationFailedException : IOException 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<string> GeneratePassword(int length) public static async Task<string> GeneratePassword(int length)
{ {
if (length <= 0)
{
throw new ArgumentOutOfRangeException("Password length must be a positive value!");
}
using (FileStream executableFile = ExecutableHelper.GetExecutableFile()) using (FileStream executableFile = ExecutableHelper.GetExecutableFile())
{ {
try string[] arguments = new string[] { COMMAND_PASSWRD, string.Format("{0:D}", length) };
Tuple<int, string[]> result = await ProcessRunner.ExecAsnyc(executableFile.Name, arguments, null, ProcessPriorityClass.BelowNormal, TIMEOUT);
if (result.Item1 == 0)
{ {
string password = await StartProcess(executableFile, length); foreach (string password in result.Item2)
if (IsWeakPassword(password))
{ {
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); return (flags != 0xF);
} }
// =============================================================================
// Internal methods
// =============================================================================
private static async Task<string> 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<string> 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<Stack<string>> WaitForExit(Process process, TimeSpan timeout)
{
Task<Stack<string>> 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<string> ReadOutput(Process process)
{
Stack<string> result = new Stack<string>();
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;
}
} }
} }

View File

@ -7,7 +7,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading; using System.Windows.Threading;
@ -62,34 +61,37 @@ namespace com.muldersoft.slunkcrypt.gui.process
// Internal methods // Internal methods
// ============================================================================= // =============================================================================
protected override double ParseProgressString(StringBuilder currentLine) protected override double ParseProgressString(ref string currentLine, bool stream)
{ {
Match match; double temp, result = double.NaN;
double progress, result = double.NaN; int index = int.MaxValue;
do Match match = RX_PROGRESS.Match(currentLine);
while (match.Success)
{ {
match = RX_PROGRESS.Match(currentLine.ToString()); if (TryParseProgressValue(match, out temp))
if (match.Success)
{ {
if (!double.IsNaN(progress = ParseProgressValue(match))) result = temp;
{
result = progress;
}
currentLine.Remove(match.Index, match.Length);
} }
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; return result;
} }
private static double ParseProgressValue(Match match) private static bool TryParseProgressValue(Match match, out double progress)
{ {
uint intPart, fractPart; uint intPart, fractPart;
if (uint.TryParse(match.Groups[1].Value, out intPart) && uint.TryParse(match.Groups[2].Value, out 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) private static string GetCommandString(Mode mode)

View File

@ -660,9 +660,13 @@ namespace com.muldersoft.slunkcrypt.gui
try try
{ {
Task<string> passwordTask = Task.Run(() => PasswordGen.GeneratePassword(GEN_PASSWD_LENGTH)); Task<string> 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; 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) 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); 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(VersionInfo.VersionStr)
.AppendLine("This work has been released under the \u201CCC0 1.0\u201D license!") .AppendLine("This work has been released under the \u201CCC0 1.0\u201D license!")
.AppendLine() .AppendLine()
.AppendLine("Source-code repository:") .AppendLine(Environment.OSVersion.VersionString)
.AppendLine("https://gitlab.com/lord_mulder/slunkcrypt/")
.AppendLine()
.AppendLine("Operating System: " + Environment.OSVersion.VersionString)
.AppendLine(string.Format("Operating System Bitness: {0:D}, Process Bitness: {1:D}", Environment.Is64BitOperatingSystem ? 64 : 32, Environment.Is64BitProcess ? 64 : 32)) .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(".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()
.AppendLine("Using “Silk” icons, by Mark James") .AppendLine("Using “Silk” icons, by Mark James")
.ToString(); .ToString();

View File

@ -4,6 +4,7 @@
/******************************************************************************/ /******************************************************************************/
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
@ -29,7 +30,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils
private readonly Dispatcher m_dispatcher; private readonly Dispatcher m_dispatcher;
private readonly ProcessPriorityClass? m_priorityClass; 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 // Exception classes
@ -63,14 +64,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils
{ {
throw new ArgumentException("The given ProcessPriorityClass is undefined!"); throw new ArgumentException("The given ProcessPriorityClass is undefined!");
} }
m_process.StartInfo.UseShellExecute = false; InitializeProcess(m_process, m_hasExited, true);
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;
} }
~ProcessRunner() ~ProcessRunner()
@ -89,9 +83,9 @@ namespace com.muldersoft.slunkcrypt.gui.utils
{ {
throw new ObjectDisposedException("ProcessRunner"); 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; m_running = true;
try try
@ -100,10 +94,21 @@ namespace com.muldersoft.slunkcrypt.gui.utils
} }
finally finally
{ {
m_finished = true;
m_running = false; m_running = false;
} }
} }
public static async Task<Tuple<int, string[]>> ExecAsnyc(string executableFile, string[] arguments = null, IReadOnlyDictionary<string, string> environmentVariables = null, ProcessPriorityClass? priorityClass = null, TimeSpan? timeout = null)
{
TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>();
using (Process process = new Process())
{
InitializeProcess(process, completionSource);
return await DoExecAsnyc(process, completionSource.Task, executableFile, arguments, environmentVariables, priorityClass, timeout);
}
}
public void AbortProcess() public void AbortProcess()
{ {
if ((!m_disposed) && m_running) if ((!m_disposed) && m_running)
@ -132,32 +137,38 @@ namespace com.muldersoft.slunkcrypt.gui.utils
// Protected methods // Protected methods
// ============================================================================= // =============================================================================
protected virtual double ParseProgressString(StringBuilder currentLine) protected virtual double ParseProgressString(ref string currentLine, bool stream)
{ {
return double.NaN; return double.NaN;
} }
// =============================================================================
// Event handlers
// =============================================================================
private void ProcessExitedEventHandler(object sender, EventArgs e)
{
if (m_process.HasExited)
{
m_hasExited.TrySetResult(m_process.ExitCode);
}
}
// ============================================================================= // =============================================================================
// Internal methods // Internal methods
// ============================================================================= // =============================================================================
private static void InitializeProcess(Process process, TaskCompletionSource<int> 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<int> DoExecAsnyc(string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables) private async Task<int> DoExecAsnyc(string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables)
{ {
try try
{ {
StartProcess(executablePath, arguments, environmentVariables); StartProcess(m_process, executablePath, arguments, environmentVariables, m_priorityClass);
} }
catch (Exception err) catch (Exception err)
{ {
@ -166,20 +177,34 @@ namespace com.muldersoft.slunkcrypt.gui.utils
return await WaitForExit(); return await WaitForExit();
} }
private void StartProcess(string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables) private static async Task<Tuple<int,string[]>> DoExecAsnyc(Process process, Task<int> hasExited, string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables, ProcessPriorityClass? priorityClass, TimeSpan? timeout)
{ {
m_process.StartInfo.FileName = executablePath; try
m_process.StartInfo.Arguments = CreateArgumentList(arguments); {
SetupEnvironment(m_process.StartInfo.EnvironmentVariables, environmentVariables); StartProcess(process, executablePath, arguments, environmentVariables, priorityClass);
m_process.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory; }
m_process.Start(); catch (Exception err)
SetProcessPriority(m_priorityClass); {
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<string, string> 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<int> WaitForExit() private async Task<int> WaitForExit()
{ {
Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, STDOUT)); Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, (line) => HandleOutput(line, STDOUT)));
Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError, STDERR)); Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError, (line) => HandleOutput(line, STDERR)));
Task<int> hasExited = m_hasExited.Task; Task<int> hasExited = m_hasExited.Task;
await Task.WhenAll(readStdOutTask, readStdErrTask, hasExited); await Task.WhenAll(readStdOutTask, readStdErrTask, hasExited);
if (m_aborted || m_disposed) if (m_aborted || m_disposed)
@ -191,21 +216,33 @@ namespace com.muldersoft.slunkcrypt.gui.utils
return hasExited.Result; return hasExited.Result;
} }
private void ReadProcessOutput(StreamReader reader, bool stream) private static async Task<string[]> WaitForExit(Process process, Task<int> hasExited, TimeSpan timeout)
{ {
using(reader) ConcurrentStack<string> outputLines = new ConcurrentStack<string>();
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<string> outputHandler)
{
using (reader)
{ {
char[] buffer = new char[1024]; char[] buffer = new char[1024];
StringBuilder currentLine = new StringBuilder(); StringBuilder lineBuffer = new StringBuilder();
while (!reader.EndOfStream) 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<string> outputHandler)
{ {
int count = reader.Read(buffer, 0, buffer.Length); int count = reader.Read(buffer, 0, buffer.Length);
if (count > 0) if (count > 0)
@ -215,40 +252,44 @@ namespace com.muldersoft.slunkcrypt.gui.utils
char c = buffer[i]; char c = buffer[i];
if ((c != '\0') && (c != '\n') && (c != '\r') && (c != '\b')) if ((c != '\0') && (c != '\n') && (c != '\r') && (c != '\b'))
{ {
currentLine.Append(c); lineBuffer.Append(c);
} }
else else
{ {
CheckForProgressUpdate(currentLine); ProcessLine(lineBuffer, outputHandler);
NotifyOutputAvailable(currentLine, stderr); lineBuffer.Clear();
currentLine.Clear();
} }
} }
CheckForProgressUpdate(currentLine);
} }
} }
private void CheckForProgressUpdate(StringBuilder currentLine) private static void ProcessLine(StringBuilder lineBuffer, Action<string> outputHandler)
{ {
if (currentLine.Length > 0) if (lineBuffer.Length > 0)
{ {
double progress = ParseProgressString(currentLine); String currentLine;
if (!(double.IsNaN(progress) || double.IsInfinity(progress))) 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(); NotifyOutputAvailable(currentLine, stream);
if (!string.IsNullOrEmpty(line)) }
{ }
NotifyOutputAvailable(line, 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 { } 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 try
{ {
if (priorityClass.HasValue) if (priorityClass.HasValue)
{ {
m_process.PriorityClass = priorityClass.Value; process.PriorityClass = priorityClass.Value;
} }
} }
catch { } catch { }