OSDN Git Service

Refactored the ProcessRunner and PasswordGen classes.
[slunkcrypt/SlunkCrypt.git] / gui / SlunkCryptGUI.xaml.cs
1 /******************************************************************************/
2 /* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
3 /* This work has been released under the CC0 1.0 Universal license!           */
4 /******************************************************************************/
5
6 using System;
7 using System.Collections.ObjectModel;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.IO;
12 using System.Media;
13 using System.Text;
14 using System.Threading.Tasks;
15 using System.Windows;
16 using System.Windows.Controls;
17 using System.Windows.Input;
18 using System.Windows.Media;
19 using System.Windows.Media.Effects;
20 using System.Windows.Shell;
21 using System.Windows.Threading;
22 using Microsoft.Win32;
23
24 using com.muldersoft.slunkcrypt.gui.ctrls;
25 using com.muldersoft.slunkcrypt.gui.process;
26 using com.muldersoft.slunkcrypt.gui.utils;
27
28 namespace com.muldersoft.slunkcrypt.gui
29 {
30     public enum ModeOfOperation { Encrypt, Decrypt }
31
32     public partial class SlunkCryptGUI : Window, INotifyBusyChanged
33     {
34         private enum Status { Default, Success, Failure }
35         private delegate Task<bool> SlunkProcessor(string inputFile, string outputFile, string password);
36
37         public event PropertyChangedEventHandler PropertyChanged;
38         public const int MIN_PASSWD_LENGTH = 8, REC_PASSWD_LENGTH = 12, GEN_PASSWD_LENGTH = 24, MAX_PASSWD_LENGTH = 256, MAX_PATH = 259;
39
40         private readonly ApplicationConfig m_config = new ApplicationConfig();
41         private readonly Lazy<string> m_about = new Lazy<string>(CreateAboutText);
42         private readonly Random m_random = new Random();
43         private readonly ObservableCollection<string> m_logFile = new ObservableCollection<string>();
44         private readonly string m_defaultStatusText;
45         private readonly DispatcherTimer m_dispatcherTimer;
46         private readonly ReadOnlyObservableCollection<string> m_logFileReadOnly;
47
48         private volatile ModeOfOperation m_modeOfOperation = (ModeOfOperation)(-1);
49         private volatile bool m_busyFlag = false, m_checksumError = false, m_processReceived = false, m_disableAnimation = false;
50         private volatile SlunkCryptRunner m_processRunner = null;
51         private uint? m_menuId_disableAnimation = null, m_menuId_enableExpertMode = null;
52
53         public const string ASCII_CHARS = "!#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~";
54
55         // =============================================================================
56         // Constructor
57         // =============================================================================
58
59         public SlunkCryptGUI()
60         {
61             InitializeComponent();
62             m_defaultStatusText = Label_Status.Text;
63             m_dispatcherTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle, Dispatcher);
64             m_dispatcherTimer.Tick += DispatcherTimer_Tick;
65             m_dispatcherTimer.Interval = TimeSpan.FromMilliseconds(200);
66             m_logFileReadOnly = new ReadOnlyObservableCollection<string>(m_logFile);
67             m_disableAnimation = m_config.DisableBusyIndicator;
68         }
69
70         // =============================================================================
71         // Properties
72         // =============================================================================
73
74         public bool IsBusy
75         {
76             get
77             {
78                 return m_busyFlag;
79             }
80             set
81             {
82                 if (m_busyFlag != value)
83                 {
84                     m_dispatcherTimer.IsEnabled = (m_busyFlag = value) && (!m_disableAnimation);
85                     NotifyPropertyChanged("IsBusy");
86                     NotifyPropertyChanged("IsBusyIndicatorVisible");
87                 }
88             }
89         }
90
91         public bool IsBusyIndicatorVisible
92         {
93             get
94             {
95                 return m_busyFlag && (!m_disableAnimation);
96             }
97         }
98
99         public ReadOnlyObservableCollection<string> LogFile
100         {
101             get
102             {
103                 return m_logFileReadOnly;
104             }
105         }
106
107         // =============================================================================
108         // Event handlers
109         // =============================================================================
110
111         protected override void OnContentRendered(EventArgs e)
112         {
113             base.OnContentRendered(e);
114             TabControl.MinHeight = TabControl.MaxHeight = TabControl.ActualHeight;
115             MinWidth = MaxWidth = ActualWidth;
116             MinHeight = MaxHeight = ActualHeight;
117         }
118
119         private void Button_Encrypt_InputFile_Click(object sender, RoutedEventArgs e)
120         {
121             string fileName;
122             if (!string.IsNullOrEmpty(fileName = BrowseForFile(Edit_Encrypt_InputFile.Text, false)))
123             {
124                 Edit_Encrypt_InputFile.Text = fileName;
125                 Edit_Encrypt_OutputFile.Text = GenerateEncryptOutputFileName(fileName);
126             }
127         }
128
129         private void Button_Encrypt_OutputFile_Click(object sender, RoutedEventArgs e)
130         {
131             string fileName;
132             if (!string.IsNullOrEmpty(fileName = BrowseForFile(Edit_Encrypt_OutputFile.Text, true, "Encrypted file (*.enc)|*.enc")))
133             {
134                 Edit_Encrypt_OutputFile.Text = fileName;
135             }
136         }
137
138         private void Button_Decrypt_InputFile_Click(object sender, RoutedEventArgs e)
139         {
140             string fileName;
141             if (!string.IsNullOrEmpty(fileName = BrowseForFile(Edit_Decrypt_InputFile.Text, false)))
142             {
143                 Edit_Decrypt_InputFile.Text = fileName;
144                 Edit_Decrypt_OutputFile.Text = GenerateDecryptOutputFileName(fileName);
145             }
146         }
147
148         private void Button_Decrypt_OutputFile_Click(object sender, RoutedEventArgs e)
149         {
150             string fileName;
151             if (!string.IsNullOrEmpty(fileName = BrowseForFile(Edit_Decrypt_OutputFile.Text, true, "Decrypted file (*.out)|*.out")))
152             {
153                 Edit_Decrypt_OutputFile.Text = fileName;
154             }
155         }
156
157         private async void Button_GeneratePasswd_Click(object sender, RoutedEventArgs e)
158         {
159             using (BusyManager busy = new BusyManager(this))
160             {
161                 Edit_Encrypt_Password.Password = string.Empty;
162                 string password;
163                 if (!string.IsNullOrEmpty(password = await GeneratePassword()))
164                 {
165                     Button_Encrypt_Toggle.IsChecked = true;
166                     Edit_Encrypt_Password.Password = password;
167                 }
168             }
169         }
170
171         private async void Button_Start_Click(object sender, RoutedEventArgs e)
172         {
173             if (!IsBusy)
174             {
175                 ResetKeyboardFocus(Button_Start);
176                 switch (GetModeOfOperation(TabControl.SelectedItem))
177                 {
178                     case ModeOfOperation.Encrypt:
179                         Debug.Assert(m_modeOfOperation == ModeOfOperation.Encrypt);
180                         await ValidateInputFile(Edit_Encrypt_InputFile, Edit_Encrypt_OutputFile, Edit_Encrypt_Password, Encrypt, true);
181                         break;
182                     case ModeOfOperation.Decrypt:
183                         Debug.Assert(m_modeOfOperation == ModeOfOperation.Decrypt);
184                         await ValidateInputFile(Edit_Decrypt_InputFile, Edit_Decrypt_OutputFile, Edit_Decrypt_Password, Decrypt, false);
185                         break;
186                     default:
187                         TabControl.SelectedIndex = GetTabIndexOf(m_modeOfOperation);
188                         break;
189                 }
190             }
191         }
192
193         private void Button_About_Click(object sender, RoutedEventArgs e)
194         {
195             if (!IsBusy)
196             {
197                 MessageBox.Show(this, m_about.Value, "About...", MessageBoxButton.OK, MessageBoxImage.Information);
198             }
199         }
200
201         private void Button_Exit_Click(object sender, RoutedEventArgs e)
202         {
203             if (!IsBusy)
204             {
205                 Application.Current.Shutdown();
206             }
207         }
208
209         private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
210         {
211             foreach (object currrentItem in e.AddedItems)
212             {
213                 ModeOfOperation? modeOfOperation = GetModeOfOperation(currrentItem);
214                 if (modeOfOperation.HasValue)
215                 {
216                     if (m_modeOfOperation != modeOfOperation.Value)
217                     {
218                         SetStatus(m_defaultStatusText);
219                         ClearProgress();
220                         m_modeOfOperation = modeOfOperation.Value;
221                     }
222                 }
223                 break;
224             }
225         }
226
227         private void Button_AbortProcess_Click(object sender, RoutedEventArgs e)
228         {
229             AbortProcess();
230         }
231
232         private void Link_CopyToClipboard_Click(object sender, MouseButtonEventArgs e)
233         {
234             if (m_logFile.Count > 0)
235             {
236                 StringBuilder builder = new StringBuilder();
237                 foreach (string logItem in m_logFile)
238                 {
239                     builder.AppendLine(logItem);
240                 }
241                 Clipboard.SetText(builder.ToString());
242                 SystemSounds.Beep.Play();
243             }
244         }
245
246         private void Link_ClearLog_Click(object sender, MouseButtonEventArgs e)
247         {
248             if (m_logFile.Count > 0)
249             {
250                 ClearLogFile();
251                 SystemSounds.Beep.Play();
252             }
253         }
254
255         private void Process_OutputAvailable(string line, bool stderr)
256         {
257             AppendLogFile(line);
258             if (line.IndexOf("Checksum mismatch detected!", StringComparison.OrdinalIgnoreCase) >= 0)
259             {
260                 m_checksumError = true;
261             }
262         }
263
264         private void Porcess_ProgressChanged(double progress)
265         {
266             if (!m_processReceived)
267             {
268                 switch (m_modeOfOperation)
269                 {
270                     case ModeOfOperation.Encrypt:
271                         SetStatus("Encrypting file contents. Please be patient, this can take a few moments...");
272                         goto default;
273                     case ModeOfOperation.Decrypt:
274                         SetStatus("Decrypting file contents. Please be patient, this can take a few moments...");
275                         goto default;
276                     default:
277                         m_processReceived = true;
278                         break;
279                 }
280             }
281             SetProgress(progress);
282         }
283
284         private void Edit_FileName_KeyDown(object sender, KeyEventArgs e)
285         {
286             if (e.Key == Key.Return)
287             {
288                 FrameworkElement source = sender as FrameworkElement;
289                 if (!ReferenceEquals(source, null))
290                 {
291                     FrameworkElement target = source.Tag as FrameworkElement;
292                     if (!ReferenceEquals(target, null))
293                     {
294                         SetFocusAndSelectAll(target);
295                     }
296                 }
297                 e.Handled = true;
298             }
299         }
300
301         private void Edit_Password_Entered(object sender, KeyEventArgs e)
302         {
303             if (!IsBusy)
304             {
305                 Button_Start_Click(sender, e);
306             }
307         }
308
309         private void Edit_FileName_LostFocus(object sender, RoutedEventArgs e)
310         {
311             TextBox textBox;
312             if (!ReferenceEquals(textBox = sender as TextBox, null))
313             {
314                 textBox.Text = PathUtils.CleanUpFilePathString(textBox.Text);
315             }
316         }
317
318         private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
319         {
320             DragMove();
321         }
322
323         private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
324         {
325             if (e.Key == Key.Escape)
326             {
327                 AbortProcess();
328             }
329         }
330
331         protected void Window_PreviewDragEnter(object sender, DragEventArgs e)
332         {
333             e.Effects = ((!IsBusy) && e.Data.GetDataPresent(DataFormats.FileDrop)) ? DragDropEffects.Copy : DragDropEffects.None;
334             e.Handled = true;
335         }
336
337         private void Window_PreviewDragLeave(object sender, DragEventArgs e)
338         {
339             e.Handled = true;
340         }
341
342         private void Window_PreviewDrop(object sender, DragEventArgs e)
343         {
344             if (!IsBusy)
345             {
346                 string[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as string[];
347                 if (!ReferenceEquals(droppedFiles, null))
348                 {
349                     foreach (string currentFile in droppedFiles)
350                     {
351                         string fullFilePath = PathUtils.CleanUpFilePathString(currentFile);
352                         if ((!string.IsNullOrEmpty(fullFilePath)) && File.Exists(fullFilePath))
353                         {
354                             TabControl.SelectedIndex = GetTabIndexOf(m_modeOfOperation);
355                             switch (m_modeOfOperation)
356                             {
357                                 case ModeOfOperation.Encrypt:
358                                     Edit_Encrypt_InputFile .Text = fullFilePath;
359                                     Edit_Encrypt_OutputFile.Text = GenerateEncryptOutputFileName(fullFilePath);
360                                     break;
361                                 case ModeOfOperation.Decrypt:
362                                     Edit_Decrypt_InputFile .Text = fullFilePath;
363                                     Edit_Decrypt_OutputFile.Text = GenerateDecryptOutputFileName(fullFilePath);
364                                     break;
365                             }
366                             break;
367                         }
368                     }
369                 }
370             }
371             e.Handled = true;
372         }
373
374         private async void Window_Loaded(object sender, RoutedEventArgs e)
375         {
376             await Task.Yield();
377             SystemMenu systemMenu = new SystemMenu(this, SystemMenu_Activated);
378             m_menuId_disableAnimation = systemMenu.AppendMenu("Disable Busy Indicator");
379             m_menuId_enableExpertMode = systemMenu.AppendMenu("Expert Settings");
380             if (m_disableAnimation && m_menuId_disableAnimation.HasValue)
381             {
382                 systemMenu.ModifyMenu(m_menuId_disableAnimation.Value, m_disableAnimation);
383             }
384             CreateIndicatorElements();
385         }
386
387         private void Window_Closing(object sender, CancelEventArgs e)
388         {
389             if (IsBusy)
390             {
391                 SystemSounds.Hand.Play();
392                 e.Cancel = true;
393             }
394         }
395
396         private void DispatcherTimer_Tick(object sender, EventArgs e)
397         {
398             ShuffleIndicatorElements();
399         }
400
401         private void SystemMenu_Activated(SystemMenu sender, uint menuId)
402         {
403             if (m_menuId_disableAnimation.HasValue && (menuId == m_menuId_disableAnimation.Value))
404             {
405                 sender.ModifyMenu(menuId, m_disableAnimation = !m_disableAnimation);
406                 if (m_busyFlag)
407                 {
408                     m_dispatcherTimer.IsEnabled = !m_disableAnimation;
409                     NotifyPropertyChanged("IsBusyIndicatorVisible");
410                 }
411             }
412             else if (m_menuId_enableExpertMode.HasValue && (menuId == m_menuId_enableExpertMode.Value))
413             {
414                 try
415                 {
416                     Process.Start("https://youtu.be/Is_8bjYVmnA").Dispose();
417                 }
418                 catch { }
419             }
420         }
421
422         // =============================================================================
423         // Internal methods
424         // =============================================================================
425
426         private async Task ValidateInputFile(TextBox inputFileEdit, TextBox outputFileEdit, PasswordToggleBox passwordEdit, SlunkProcessor processor, bool checkStrongPasswd)
427         {
428             string inputFilePath;
429             if (string.IsNullOrEmpty(inputFileEdit.Text = inputFilePath = PathUtils.CleanUpFilePathString(inputFileEdit.Text)))
430             {
431                 MessageBox.Show(this, "Input file must be selected first!", "Input File Missing", MessageBoxButton.OK, MessageBoxImage.Warning);
432                 SetFocusAndSelectAll(inputFileEdit);
433                 return;
434             }
435             if (PathUtils.IsInvalidPath(inputFilePath))
436             {
437                 MessageBox.Show(this, "The specified input file path is invalid!", "Input File Invalid", MessageBoxButton.OK, MessageBoxImage.Warning);
438                 SetFocusAndSelectAll(inputFileEdit);
439                 return;
440             }
441             if (Directory.Exists(inputFilePath))
442             {
443                 MessageBox.Show(this, "Specified input file appears to be a directory!", "Input File Invalid", MessageBoxButton.OK, MessageBoxImage.Warning);
444                 SetFocusAndSelectAll(inputFileEdit);
445                 return;
446             }
447             if (!File.Exists(inputFilePath))
448             {
449                 MessageBox.Show(this, "Specified input file could not be found!", "Input Not Found", MessageBoxButton.OK, MessageBoxImage.Warning);
450                 SetFocusAndSelectAll(inputFileEdit);
451                 return;
452             }
453             await ValidateOutputFile(inputFilePath, outputFileEdit, passwordEdit, processor, checkStrongPasswd);
454         }
455
456         private async Task ValidateOutputFile(string inputFilePath, TextBox outputFileEdit, PasswordToggleBox passwordEdit, SlunkProcessor processor, bool checkStrongPasswd)
457         {
458             string outputFilePath;
459             if (string.IsNullOrEmpty(outputFileEdit.Text = outputFilePath = PathUtils.CleanUpFilePathString(outputFileEdit.Text)))
460             {
461                 MessageBox.Show(this, "Output file must be selected first!", "Output File Missing", MessageBoxButton.OK, MessageBoxImage.Warning);
462                 SetFocusAndSelectAll(outputFileEdit);
463                 return;
464             }
465             if (PathUtils.IsInvalidPath(outputFilePath))
466             {
467                 MessageBox.Show(this, "The specified output file path is invalid!", "Output File Invalid", MessageBoxButton.OK, MessageBoxImage.Warning);
468                 SetFocusAndSelectAll(outputFileEdit);
469                 return;
470             }
471             if (Directory.Exists(outputFilePath))
472             {
473                 MessageBox.Show(this, "Specified output file appears to be a directory!", "Output File Invalid", MessageBoxButton.OK, MessageBoxImage.Warning);
474                 SetFocusAndSelectAll(outputFileEdit);
475                 return;
476             }
477             if (string.Equals(inputFilePath, outputFilePath, StringComparison.OrdinalIgnoreCase))
478             {
479                 MessageBox.Show(this, "Input and output file can not be the same!", "File Name Conflict", MessageBoxButton.OK, MessageBoxImage.Warning);
480                 SetFocusAndSelectAll(outputFileEdit);
481                 return;
482             }
483             if (File.Exists(outputFilePath))
484             {
485                 if (MessageBox.Show(this, "Specified output file already existst! Overwrite?", "Output File Exists", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) != MessageBoxResult.Yes)
486                 {
487                     SetFocusAndSelectAll(outputFileEdit);
488                     return;
489                 }
490             }
491             await ValidateOutputDirectory(inputFilePath, outputFilePath, passwordEdit, processor, checkStrongPasswd);
492
493         }
494
495         private async Task ValidateOutputDirectory(string inputFilePath, string outputFilePath, PasswordToggleBox passwordEdit, SlunkProcessor processor, bool checkStrongPasswd)
496         {
497             string outputDirectory;
498             if (string.IsNullOrEmpty(outputDirectory = PathUtils.TryGetDirectoryName(outputFilePath)))
499             {
500                 MessageBox.Show(this, "The output directory could not be determined!", "Output Directory Invalid", MessageBoxButton.OK, MessageBoxImage.Warning);
501                 return;
502             }
503             while (!Directory.Exists(outputDirectory))
504             {
505                 if (MessageBox.Show(this, "Output directory does not exist yet! Create it now?", "Output Directory Nonexistent", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes) != MessageBoxResult.Yes)
506                 {
507                     return;
508                 }
509                 if (!PathUtils.TryCreateDirectory(outputDirectory))
510                 {
511                     MessageBox.Show(this, "The output directory could not be created!", "Directory Creation Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
512                     return;
513                 }
514             }
515             await ValidatePassword(inputFilePath, outputFilePath, passwordEdit, processor, checkStrongPasswd);
516         }
517
518         private async Task ValidatePassword(string inputFilePath, string outputFilePath, PasswordToggleBox passwordEdit, SlunkProcessor processor, bool checkStrongPasswd)
519         {
520             string passwordStr;
521             if (string.IsNullOrEmpty(passwordStr = passwordEdit.Password) || (passwordStr.Length < MIN_PASSWD_LENGTH))
522             {
523                 MessageBox.Show(this, String.Format("Passphrase must be at least {0:D} characters in length!", MIN_PASSWD_LENGTH), "Passphrase Missing", MessageBoxButton.OK, MessageBoxImage.Warning);
524                 SetFocusAndSelectAll(passwordEdit);
525                 return;
526             }
527             if (checkStrongPasswd)
528             {
529                 if (passwordStr.Length < REC_PASSWD_LENGTH)
530                 {
531                     if (MessageBox.Show(this, String.Format("Recommended passphrase length is at least {0:D} characters!", REC_PASSWD_LENGTH), "Short Passphrase", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel) != MessageBoxResult.OK)
532                     {
533                         SetFocusAndSelectAll(passwordEdit);
534                         return;
535                     }
536                 }
537                 else if (PasswordGen.IsWeakPassword(passwordStr))
538                 {
539                     if (MessageBox.Show(this, "Passphrase should contain a mix of upper case characters, lower case characters, digits and other characters!", "Weak Passphrase", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel) != MessageBoxResult.OK)
540                     {
541                         SetFocusAndSelectAll(passwordEdit);
542                         return;
543                     }
544                 }
545             }
546             await InvokeProcessor(inputFilePath, outputFilePath, passwordStr, processor);
547         }
548
549         private async Task InvokeProcessor(string inputFile, string outputFile, string password, SlunkProcessor processor)
550         {
551             using (BusyManager busyManager = new BusyManager(this))
552             {
553                 ResetKeyboardFocus(this);
554                 SetProgress(double.PositiveInfinity);
555                 ClearLogFile();
556                 Button_Decrypt_Toggle.IsChecked = Button_Encrypt_Toggle.IsChecked = m_checksumError = m_processReceived = false;
557                 if (!await processor(inputFile, outputFile, password))
558                 {
559                     SetProgress(double.NaN, true);
560                     PathUtils.TryRemoveFile(outputFile);
561                 }
562                 await Task.Yield();
563             }
564         }
565
566         private async Task<bool> Encrypt(string inputFile, string outputFile, string password)
567         {
568             SetStatus("Please wait while the encryption process is initializing...");
569             int? exitCode = await RunProcess(SlunkCryptRunner.Mode.Encrypt, inputFile, outputFile, password);
570             if (exitCode.HasValue)
571             {
572                 if (exitCode.Value == 0)
573                 {
574                     SetProgress(1);
575                     SetStatus("Completed: The file has been encrypted successfully.", Status.Success);
576                     SystemSounds.Asterisk.Play();
577                 }
578                 else
579                 {
580                     SetProgress(1, true);
581                     SetStatus("Error: Failed to enecrypt the file. Please see the log file for details!", Status.Failure);
582                     SystemSounds.Hand.Play();
583                 }
584                 return true;
585             }
586             return false;
587         }
588
589         private async Task<bool> Decrypt(string inputFile, string outputFile, string password)
590         {
591             SetStatus("Please wait while the decryption process is initializing...");
592             int? exitCode = await RunProcess(SlunkCryptRunner.Mode.Decrypt, inputFile, outputFile, password);
593             if (exitCode.HasValue)
594             {
595                 if (exitCode.Value == 0)
596                 {
597                     SetStatus("Completed: The file has been decrypted successfully (checksum is correct).", Status.Success);
598                     SetProgress(1);
599                     SystemSounds.Asterisk.Play();
600                 }
601                 else
602                 {
603                     if (m_checksumError)
604                     {
605                         SetStatus("Error: Checksum mismatch detected! Wrong passphrase or corrupted file?", Status.Failure);
606                     }
607                     else
608                     {
609                         SetStatus("Error: Failed to decrypt the file. Please see the log file for details!", Status.Failure);
610                     }
611                     SetProgress(1, true);
612                     SystemSounds.Hand.Play();
613                 }
614                 return true;
615             }
616             return false;
617         }
618
619         private async Task<int?> RunProcess(SlunkCryptRunner.Mode mode, string inputFile, string outputFile, string password)
620         {
621             if (!ReferenceEquals(m_processRunner, null))
622             {
623                 throw new InvalidOperationException("Process has already been started!");
624             }
625             try
626             {
627                 using (m_processRunner = new SlunkCryptRunner(Dispatcher))
628                 {
629                     m_processRunner.OutputAvailable += Process_OutputAvailable;
630                     m_processRunner.ProgressChanged += Porcess_ProgressChanged;
631                     SetProcessPriority(ProcessPriorityClass.AboveNormal);
632                     return await m_processRunner.ExecuteAsync(mode, inputFile, outputFile, password);
633                 }
634             }
635             catch (ProcessRunner.ProcessStartException err)
636             {
637                 SetStatus(string.Format("Error: The {0} process could not be created! (Error code: {1:D})", GetModeString(m_modeOfOperation), GetWin32ErrorCode(err)), Status.Failure);
638                 MessageBox.Show(this, "Failed to create SlunkCrypt process:\n\n" + err.InnerException?.Message ?? err.Message, "Process Creation Error", MessageBoxButton.OK, MessageBoxImage.Error);
639             }
640             catch (ExecutableHelper.ExecutableNotFoundException)
641             {
642                 SetStatus("Error: The required SlunkCrypt executable file could not be found!", Status.Failure);
643                 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);
644             }
645             catch (ProcessRunner.ProcessInterruptedException)
646             {
647                 SetStatus(string.Format("Aborted: The {0} process was aborted by the user!", GetModeString(m_modeOfOperation)), Status.Failure);
648                 SystemSounds.Hand.Play();
649             }
650             finally
651             {
652                 m_processRunner = null; /*final clean-up*/
653             }
654             return null;
655         }
656
657         private async Task<string> GeneratePassword()
658         {
659             string password = string.Empty;
660             try
661             {
662                 Task<string> passwordTask = Task.Run(() => PasswordGen.GeneratePassword(GEN_PASSWD_LENGTH));
663                 await Task.WhenAll(passwordTask, Task.Delay(333));
664                 password = passwordTask.Result;
665             }
666             catch (ProcessRunner.ProcessStartException err)
667             {
668                 MessageBox.Show(this, "Failed to create SlunkCrypt process:\n\n" + err.InnerException?.Message ?? err.Message, "Process Creation Error", MessageBoxButton.OK, MessageBoxImage.Error);
669             }
670             catch (ExecutableHelper.ExecutableNotFoundException)
671             {
672                 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);
673             }
674             catch (PasswordGen.GenerationFailedException)
675             {
676                 MessageBox.Show(this, "Error: The password could not be generated!", "Generation Failed", MessageBoxButton.OK, MessageBoxImage.Error);
677             }
678             return password;
679         }
680
681         private void SetStatus(string text, Status status = Status.Default)
682         {
683             switch (status)
684             {
685                 case Status.Success:
686                     Label_Status.Foreground = Brushes.DarkGreen;
687                     break;
688                 case Status.Failure:
689                     Label_Status.Foreground = Brushes.DarkRed;
690                     break;
691                 default:
692                     Label_Status.Foreground = SystemColors.WindowTextBrush;
693                     break;
694             }
695             Label_Status.Text = text;
696         }
697
698         private void SetProgress(double progress, bool failed = false)
699         {
700             if (!(double.IsNaN(progress) || double.IsInfinity(progress)))
701             {
702                 ProgressBar.IsIndeterminate = false;
703                 ProgressBar.Value = progress;
704                 TaskbarItemInfo.ProgressState = failed ? TaskbarItemProgressState.Error : TaskbarItemProgressState.Normal;
705                 TaskbarItemInfo.ProgressValue = progress;
706                 Label_Progress.Text = string.Format(CultureInfo.InvariantCulture, "{0:0.0}%", progress * 100.0);
707             }
708             else
709             {
710                 if (double.IsInfinity(progress))
711                 {
712                     ProgressBar.IsIndeterminate = true;
713                     ProgressBar.Value = 0;
714                     TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate;
715                     Label_Progress.Text = string.Empty;
716                 }
717                 else
718                 {
719                     ProgressBar.IsIndeterminate = false;
720                     TaskbarItemInfo.ProgressState = failed ? TaskbarItemProgressState.Error : TaskbarItemProgressState.None;
721                 }
722             }
723         }
724
725         private void ClearProgress()
726         {        
727             ProgressBar.IsIndeterminate = false;
728             ProgressBar.Value = 0;
729             TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
730             Label_Progress.Text = string.Empty;
731         }
732
733         private void AbortProcess()
734         {
735             ProcessRunner processRunner;
736             if (!ReferenceEquals(processRunner = m_processRunner, null))
737             {
738                 try
739                 {
740                     processRunner.AbortProcess();
741                 }
742                 catch { }
743             }
744         }
745
746         private string BrowseForFile(string fileName, bool saveDialog, string filterString = null)
747         {
748             FileDialog openFileDialog = saveDialog ? new SaveFileDialog() { OverwritePrompt = false } : (FileDialog) new OpenFileDialog();
749             openFileDialog.Filter = string.IsNullOrEmpty(filterString) ? "All files (*.*)|*.*" : filterString;
750             if (!string.IsNullOrEmpty(fileName))
751             {
752                 openFileDialog.FileName = fileName;
753             }
754             if (openFileDialog.ShowDialog().GetValueOrDefault(false))
755             {
756                 return openFileDialog.FileName;
757             }
758             return null;
759         }
760
761         private void CreateIndicatorElements()
762         {
763             FontFamily hackFont = new FontFamily(new Uri("pack://application:,,,/"), "./Resources/Fonts/#Hack");
764             DropShadowEffect dropShadowEffect = CreateShadowEffect(Colors.Black, 3.0);
765             TextBlock reference = CreateTextBlock('0', Brushes.Gold, hackFont, dropShadowEffect);
766             reference.Measure(new Size(double.MaxValue, double.MaxValue));
767             Size desiredSize = reference.DesiredSize;
768             double actualWidth = Canvas.ActualWidth, actualHeight = Canvas.ActualHeight;
769             int lenX = (int)Math.Ceiling(desiredSize.Width  * 1.25);
770             int lenY = (int)Math.Ceiling(desiredSize.Height * 1.25);
771             int numX = (int)Math.Floor(actualWidth  / lenX);
772             int numY = (int)Math.Floor(actualHeight / lenY);
773             int offX = (int)Math.Round((actualWidth  - (numX * lenX)) / 2.0);
774             int offY = (int)Math.Round((actualHeight - (numY * lenY)) / 2.0);
775             for (int i = 0; i < numX; ++i)
776             {
777                 for (int j = 0; j < numY; ++j)
778                 {
779                     TextBlock element = CreateTextBlock('0', Brushes.Gold, hackFont, dropShadowEffect);
780                     Canvas.Children.Add(element);
781                     Canvas.SetLeft(element, offX + (i * lenX));
782                     Canvas.SetTop (element, offY + (j * lenY));
783                 }
784             }
785             ShuffleIndicatorElements();
786         }
787
788         private void ShuffleIndicatorElements()
789         {
790             char[] chars = ASCII_CHARS.ToCharArray();
791             UIElementCollection children = Canvas.Children;
792             for (int i = 0; i < children.Count; ++i)
793             {
794                 TextBlock element;
795                 if (!ReferenceEquals(element = children[i] as TextBlock, null))
796                 {
797                     if (m_random.Next(7) == 0)
798                     {
799                         element.Visibility = Visibility.Visible;
800                         element.Text = char.ToString(chars[m_random.Next(chars.Length)]);
801                     }
802                     else
803                     {
804                         element.Visibility = Visibility.Hidden;
805                     }
806                 }
807             }
808         }
809
810         private void AppendLogFile(string line)
811         {
812             if (!string.IsNullOrEmpty(line))
813             {
814                 m_logFile.Add(line);
815             }
816         }
817
818         private int GetTabIndexOf(ModeOfOperation modeOfOperation)
819         {
820             ItemCollection collection = TabControl.Items;
821             for (int index = 0; index < collection.Count; ++index)
822             {
823                 ModeOfOperation? current = GetModeOfOperation(collection[index]);
824                 if (current.HasValue && (current.Value == modeOfOperation))
825                 {
826                     return index;
827                 }
828             }
829             return -1;
830         }
831
832         private void ClearLogFile()
833         {
834             m_logFile.Clear();
835         }
836
837         private void NotifyPropertyChanged(string name)
838         {
839             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
840         }
841
842         // -----------------------------------------------------------------------------
843         // Static methods
844         // -----------------------------------------------------------------------------
845
846         private static string CreateAboutText()
847         {
848             CPUFeatures cpuFeatures = CPUFeatures.Features;
849             return new StringBuilder()
850                 .AppendLine("SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>")
851                 .AppendLine(VersionInfo.VersionStr)
852                 .AppendLine("This work has been released under the \u201CCC0 1.0\u201D license!")
853                 .AppendLine()
854                 .AppendLine(Environment.OSVersion.VersionString)
855                 .AppendLine(string.Format("Operating System Bitness: {0:D}, Process Bitness: {1:D}", Environment.Is64BitOperatingSystem ? 64 : 32, Environment.Is64BitProcess ? 64 : 32))
856                 .AppendLine(".NET Runtime Version: " + Environment.Version)
857                 .AppendLine(string.Format("CPU Count: {0:D}, Architecture: {1}, SSE2 Support: {2}", Environment.ProcessorCount, cpuFeatures.x64 ? "x64" : "x86", cpuFeatures.sse2 ? "Yes" : "No"))
858                 .AppendLine()
859                 .AppendLine("Using “Silk” icons, by Mark James")
860                 .ToString();
861         }
862
863         private static string GenerateEncryptOutputFileName(string inputFilePath)
864         {
865             string directoryPath = Path.GetDirectoryName(inputFilePath), fileName = Path.GetFileNameWithoutExtension(inputFilePath), extension = Path.GetExtension(inputFilePath);
866             string outputFile = Path.Combine(directoryPath, string.Format("{0}{1}.enc", fileName, extension));
867             for (int count = 2; File.Exists(outputFile); ++count)
868             {
869                 outputFile = Path.Combine(directoryPath, string.Format("{0} ({1:D}){2}.enc", fileName, count, extension));
870             }
871             return outputFile;
872         }
873
874         private static string GenerateDecryptOutputFileName(string inputFilePath)
875         {
876             string directoryPath = Path.GetDirectoryName(inputFilePath), fileName = Path.GetFileNameWithoutExtension(inputFilePath), extension = Path.GetExtension(inputFilePath);
877             while (extension.Equals(".enc", StringComparison.OrdinalIgnoreCase))
878             {
879                 extension = Path.GetExtension(fileName);
880                 fileName = Path.GetFileNameWithoutExtension(fileName);
881             }
882             if (string.IsNullOrEmpty(extension))
883             {
884                 extension = ".out";
885             }
886             string outputFile = Path.Combine(directoryPath, string.Concat(fileName, extension));
887             for (int count = 2; File.Exists(outputFile); ++count)
888             {
889                 outputFile = Path.Combine(directoryPath, String.Format("{0} ({1:D}){2}", fileName, count, extension));
890             }
891             return outputFile;
892         }
893
894         private static ModeOfOperation? GetModeOfOperation(object selectedItem)
895         {
896             TabItem selectedTabItem = selectedItem as TabItem;
897             if (!ReferenceEquals(selectedTabItem, null))
898             {
899                 return selectedTabItem.Tag as ModeOfOperation?;
900             }
901             return null;
902         }
903
904         private static string GetModeString(ModeOfOperation modeOfOperation)
905         {
906             switch(modeOfOperation)
907             {
908                 case ModeOfOperation.Encrypt:
909                     return "encryption";
910                 case ModeOfOperation.Decrypt:
911                     return "decryption";
912                 default:
913                     throw new ArgumentException("modeOfOperation");
914             }
915         }
916
917         private static void SetFocusAndSelectAll(FrameworkElement element)
918         {
919             TextBox textBox;
920             if (!ReferenceEquals(textBox = element as TextBox, null))
921             {
922                 textBox.Focus();
923                 textBox.SelectAll();
924             }
925             else
926             {
927                 PasswordToggleBox passwordToggleBox;
928                 if (!ReferenceEquals(passwordToggleBox = element as PasswordToggleBox, null))
929                 {
930                     passwordToggleBox.Focus();
931                     passwordToggleBox.SelectAll();
932                 }
933             }
934         }
935
936         private static void SetProcessPriority(ProcessPriorityClass priorityClass)
937         {
938             try
939             {
940                 using (Process currentProcess = Process.GetCurrentProcess())
941                 {
942                     currentProcess.PriorityClass = priorityClass;
943                 }
944             }
945             catch { }
946         }
947
948         private static int GetWin32ErrorCode(Exception err)
949         {
950             while (!ReferenceEquals(err, null))
951             {
952                 if (err is Win32Exception)
953                 {
954                     return ((Win32Exception)err).NativeErrorCode;
955                 }
956                 err = err.InnerException;
957             }
958             return 0;
959         }
960
961         private static TextBlock CreateTextBlock(char c, Brush foreground, FontFamily fontFamily, Effect effect)
962         {
963             return new TextBlock()
964             {
965                 Text = char.ToString(c),
966                 Foreground = foreground,
967                 FontFamily = fontFamily,
968                 Effect = effect
969             };
970         }
971
972         private static DropShadowEffect CreateShadowEffect(Color color, double blurRadius)
973         {
974             return new DropShadowEffect()
975             {
976                 Color = color,
977                 BlurRadius = blurRadius,
978                 Direction = 0.0,
979                 ShadowDepth = 0.0
980             };
981         }
982
983         private static void ResetKeyboardFocus(UIElement element)
984         {
985             Keyboard.ClearFocus();
986             element.Focus();
987         }
988     }
989 }