diff --git a/mRemoteNGTests/App/UpdaterTests.cs b/mRemoteNGTests/App/UpdaterTests.cs index 083616c3..1889b5df 100644 --- a/mRemoteNGTests/App/UpdaterTests.cs +++ b/mRemoteNGTests/App/UpdaterTests.cs @@ -9,37 +9,15 @@ namespace mRemoteNGTests.App [TestFixture] public class UpdaterTests { - private AppUpdater _appUpdate; - [SetUp] public void Setup() { GeneralAppInfo.ApplicationVersion = "1.0.0.0"; - _appUpdate = new AppUpdater(); - _appUpdate.GetUpdateInfoCompletedEvent += TestGetUpdateInfoCompleted; - } [Test] public void TestStableChannel() { } - - private void TestGetUpdateInfoCompleted(object sender, AsyncCompletedEventArgs e) - { - try - { - _appUpdate.GetUpdateInfoCompletedEvent -= TestGetUpdateInfoCompleted; - - if (_appUpdate.IsUpdateAvailable()) - { - - } - } - catch (Exception ex) - { - - } - } } } diff --git a/mRemoteV1/App/Info/GeneralAppInfo.cs b/mRemoteV1/App/Info/GeneralAppInfo.cs index 5e3506be..ff98ea5b 100644 --- a/mRemoteV1/App/Info/GeneralAppInfo.cs +++ b/mRemoteV1/App/Info/GeneralAppInfo.cs @@ -19,15 +19,19 @@ namespace mRemoteNG.App.Info public static readonly string ProductName = Application.ProductName; public static readonly string Copyright = ((AssemblyCopyrightAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyCopyrightAttribute), false)).Copyright; public static readonly string HomePath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - public static string ReportingFilePath = ""; + //public static string ReportingFilePath = ""; public static readonly string PuttyPath = HomePath + "\\PuTTYNG.exe"; public static string UserAgent { get { - var details = new List(); - details.Add("compatible"); - details.Add(OSVersion.Platform == PlatformID.Win32NT ? $"Windows NT {OSVersion.Version.Major}.{OSVersion.Version.Minor}": OSVersion.VersionString); + var details = new List + { + "compatible", + OSVersion.Platform == PlatformID.Win32NT + ? $"Windows NT {OSVersion.Version.Major}.{OSVersion.Version.Minor}" + : OSVersion.VersionString + }; if (Is64BitProcess) { details.Add("WOW64"); diff --git a/mRemoteV1/App/Info/UpdateChannelInfo.cs b/mRemoteV1/App/Info/UpdateChannelInfo.cs index 6954e4cc..8a347671 100644 --- a/mRemoteV1/App/Info/UpdateChannelInfo.cs +++ b/mRemoteV1/App/Info/UpdateChannelInfo.cs @@ -1,12 +1,35 @@ -namespace mRemoteNG.App.Info +using System; + +namespace mRemoteNG.App.Info { - public static class UpdateChannelInfo + public class UpdateChannelInfo { internal const string STABLE = "Stable"; internal const string BETA = "Beta"; internal const string DEV = "Development"; - public static string FileName + /* no #if here since they are used for unit tests as well */ + public const string STABLE_PORTABLE = "update-portable.txt"; + public const string BETA_PORTABLE = "beta-update-portable.txt"; + public const string DEV_PORTABLE = "dev-update-portable.txt"; + + public const string STABLE_MSI = "update.txt"; + public const string BETA_MSI = "beta-update.txt"; + public const string DEV_MSI = "dev-update.txt"; + + private readonly string channel; + + public UpdateChannelInfo() + { + channel = IsValidChannel(Settings.Default.UpdateChannel) ? Settings.Default.UpdateChannel : STABLE; + } + + public UpdateChannelInfo(string s) + { + channel = IsValidChannel(s) ? s : STABLE; + } + + private string FileName { #if PORTABLE get @@ -14,16 +37,16 @@ /* */ /* return PORTABLE update files here */ /* */ - switch (Settings.Default.UpdateChannel) + switch (channel) { case STABLE: - return "update-portable.txt"; + return STABLE_PORTABLE; case BETA: - return "beta-update-portable.txt"; + return BETA_PORTABLE; case DEV: - return "dev-update-portable.txt"; + return DEV_PORTABLE; default: - return "update-portable.txt"; + return STABLE_PORTABLE; } } #else //NOT portable @@ -32,19 +55,29 @@ /* */ /* return INSTALLER update files here */ /* */ - switch (Settings.Default.UpdateChannel) + switch (channel) { case STABLE: - return "update.txt"; + return STABLE_MSI; case BETA: - return "beta-update.txt"; + return BETA_MSI; case DEV: - return "dev-update.txt"; + return DEV_MSI; default: - return "update.txt"; + return STABLE_MSI; } } #endif //endif for PORTABLE } + + public Uri GetUpdateTxtUri() + { + return new Uri(new Uri(Settings.Default.UpdateAddress), new Uri(FileName, UriKind.Relative)); + } + + private static bool IsValidChannel(string s) + { + return s.Equals(STABLE) || s.Equals(BETA) || s.Equals(DEV); + } } } \ No newline at end of file diff --git a/mRemoteV1/App/Startup.cs b/mRemoteV1/App/Startup.cs index 16366937..95390a9f 100644 --- a/mRemoteV1/App/Startup.cs +++ b/mRemoteV1/App/Startup.cs @@ -223,7 +223,7 @@ namespace mRemoteNG.App } if (e.Error != null) { - throw (e.Error); + throw e.Error; } if (_appUpdate.IsUpdateAvailable()) diff --git a/mRemoteV1/App/Update/AppUpdater.cs b/mRemoteV1/App/Update/AppUpdater.cs index 7f406f07..2cee01c9 100644 --- a/mRemoteV1/App/Update/AppUpdater.cs +++ b/mRemoteV1/App/Update/AppUpdater.cs @@ -9,116 +9,122 @@ using mRemoteNG.Security.SymmetricEncryption; using System.Security.Cryptography; #if !PORTABLE using mRemoteNG.Tools; + #else using System.Windows.Forms; #endif namespace mRemoteNG.App.Update { - public class AppUpdater - { - private WebProxy _webProxy; + public class AppUpdater + { + private WebProxy _webProxy; private Thread _getUpdateInfoThread; private Thread _getChangeLogThread; -#region Public Properties + private UpdateChannelInfo updChannel; + + #region Public Properties + public UpdateInfo CurrentUpdateInfo { get; private set; } - public string ChangeLog { get; private set; } + public string ChangeLog { get; private set; } - public bool IsGetUpdateInfoRunning => _getUpdateInfoThread != null && _getUpdateInfoThread.IsAlive; + public bool IsGetUpdateInfoRunning => _getUpdateInfoThread != null && _getUpdateInfoThread.IsAlive; - private bool IsGetChangeLogRunning => _getChangeLogThread != null && _getChangeLogThread.IsAlive; + private bool IsGetChangeLogRunning => _getChangeLogThread != null && _getChangeLogThread.IsAlive; - public bool IsDownloadUpdateRunning => _downloadUpdateWebClient != null; + public bool IsDownloadUpdateRunning => _downloadUpdateWebClient != null; -#endregion - -#region Public Methods - public AppUpdater() - { - SetProxySettings(); - } + #endregion - private void SetProxySettings() - { - var shouldWeUseProxy = Settings.Default.UpdateUseProxy; - var proxyAddress = Settings.Default.UpdateProxyAddress; - var port = Settings.Default.UpdateProxyPort; - var useAuthentication = Settings.Default.UpdateProxyUseAuthentication; - var username = Settings.Default.UpdateProxyAuthUser; + #region Public Methods + + public AppUpdater() + { + SetProxySettings(); + } + + private void SetProxySettings() + { + var shouldWeUseProxy = Settings.Default.UpdateUseProxy; + var proxyAddress = Settings.Default.UpdateProxyAddress; + var port = Settings.Default.UpdateProxyPort; + var useAuthentication = Settings.Default.UpdateProxyUseAuthentication; + var username = Settings.Default.UpdateProxyAuthUser; var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); - var password = cryptographyProvider.Decrypt(Settings.Default.UpdateProxyAuthPass, Runtime.EncryptionKey); + var password = cryptographyProvider.Decrypt(Settings.Default.UpdateProxyAuthPass, Runtime.EncryptionKey); SetProxySettings(shouldWeUseProxy, proxyAddress, port, useAuthentication, username, password); - } - - public void SetProxySettings(bool useProxy, string address, int port, bool useAuthentication, string username, string password) - { - if (useProxy && !string.IsNullOrEmpty(address)) - { - _webProxy = port != 0 ? new WebProxy(address, port) : new WebProxy(address); + } - _webProxy.Credentials = useAuthentication ? new NetworkCredential(username, password) : null; - } - else - { - _webProxy = null; - } - } - - public bool IsUpdateAvailable() - { - if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) - { - return false; - } - - return CurrentUpdateInfo.Version > GeneralAppInfo.GetApplicationVersion(); - } - - public void GetUpdateInfoAsync() - { - if (IsGetUpdateInfoRunning) - { - _getUpdateInfoThread.Abort(); - } - - _getUpdateInfoThread = new Thread(GetUpdateInfo); - _getUpdateInfoThread.SetApartmentState(ApartmentState.STA); - _getUpdateInfoThread.IsBackground = true; - _getUpdateInfoThread.Start(); - } - - public void GetChangeLogAsync() - { - if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) - { - throw new InvalidOperationException("CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling GetChangeLogAsync()."); - } - - if (IsGetChangeLogRunning) - { - _getChangeLogThread.Abort(); - } - - _getChangeLogThread = new Thread(GetChangeLog); - _getChangeLogThread.SetApartmentState(ApartmentState.STA); - _getChangeLogThread.IsBackground = true; - _getChangeLogThread.Start(); - } - - public void DownloadUpdateAsync() - { - if (_downloadUpdateWebClient != null) - { - throw new InvalidOperationException("A previous call to DownloadUpdateAsync() is still in progress."); - } - - if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) - { - throw new InvalidOperationException("CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling DownloadUpdateAsync()."); - } + public void SetProxySettings(bool useProxy, string address, int port, bool useAuthentication, string username, string password) + { + if (useProxy && !string.IsNullOrEmpty(address)) + { + _webProxy = port != 0 ? new WebProxy(address, port) : new WebProxy(address); + + _webProxy.Credentials = useAuthentication ? new NetworkCredential(username, password) : null; + } + else + { + _webProxy = null; + } + } + + public bool IsUpdateAvailable() + { + if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) + { + return false; + } + + return CurrentUpdateInfo.Version > GeneralAppInfo.GetApplicationVersion(); + } + + public void GetUpdateInfoAsync() + { + if (IsGetUpdateInfoRunning) + { + _getUpdateInfoThread.Abort(); + } + + _getUpdateInfoThread = new Thread(GetUpdateInfo); + _getUpdateInfoThread.SetApartmentState(ApartmentState.STA); + _getUpdateInfoThread.IsBackground = true; + _getUpdateInfoThread.Start(); + } + + public void GetChangeLogAsync() + { + if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) + { + throw new InvalidOperationException("CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling GetChangeLogAsync()."); + } + + if (IsGetChangeLogRunning) + { + _getChangeLogThread.Abort(); + } + + _getChangeLogThread = new Thread(GetChangeLog); + _getChangeLogThread.SetApartmentState(ApartmentState.STA); + _getChangeLogThread.IsBackground = true; + _getChangeLogThread.Start(); + } + + public void DownloadUpdateAsync() + { + if (_downloadUpdateWebClient != null) + { + throw new InvalidOperationException("A previous call to DownloadUpdateAsync() is still in progress."); + } + + if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid) + { + throw new InvalidOperationException( + "CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling DownloadUpdateAsync()."); + } #if !PORTABLE CurrentUpdateInfo.UpdateFilePath = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), "msi")); #else @@ -138,133 +144,139 @@ namespace mRemoteNG.App.Update } #endif DownloadUpdateWebClient.DownloadFileAsync(CurrentUpdateInfo.DownloadAddress, CurrentUpdateInfo.UpdateFilePath); - } -#endregion - -#region Private Properties - private WebClient _downloadUpdateWebClient; - private WebClient DownloadUpdateWebClient - { - get - { - if (_downloadUpdateWebClient != null) - { - return _downloadUpdateWebClient; - } - - _downloadUpdateWebClient = CreateWebClient(); - - _downloadUpdateWebClient.DownloadProgressChanged += DownloadUpdateProgressChanged; - _downloadUpdateWebClient.DownloadFileCompleted += DownloadUpdateCompleted; - - return _downloadUpdateWebClient; - } - } -#endregion - -#region Private Methods - private WebClient CreateWebClient() - { - var webClient = new WebClient(); - webClient.Headers.Add("user-agent", GeneralAppInfo.UserAgent); - webClient.Proxy = _webProxy; - return webClient; - } - - private static DownloadStringCompletedEventArgs NewDownloadStringCompletedEventArgs(string result, Exception exception, bool cancelled, object userToken) - { - var type = typeof(DownloadStringCompletedEventArgs); - const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; - Type[] argumentTypes = {typeof(string), typeof(Exception), typeof(bool), typeof(object)}; - var constructor = type.GetConstructor(bindingFlags, null, argumentTypes, null); - object[] arguments = {result, exception, cancelled, userToken}; + } - return (DownloadStringCompletedEventArgs)constructor.Invoke(arguments); - } - - private DownloadStringCompletedEventArgs DownloadString(Uri address) - { - var webClient = CreateWebClient(); - var result = string.Empty; - Exception exception = null; - var cancelled = false; - - try - { - result = webClient.DownloadString(address); - } - catch (ThreadAbortException) - { - cancelled = true; - } - catch (Exception ex) - { - exception = ex; - } - - return NewDownloadStringCompletedEventArgs(result, exception, cancelled, null); - } - - private void GetUpdateInfo() - { - var updateFileUri = new Uri(new Uri(Convert.ToString(Settings.Default.UpdateAddress)), new Uri(UpdateChannelInfo.FileName, UriKind.Relative)); - var e = DownloadString(updateFileUri); - - if (!e.Cancelled && e.Error == null) - { - CurrentUpdateInfo = UpdateInfo.FromString(e.Result); + #endregion + + #region Private Properties + + private WebClient _downloadUpdateWebClient; + + private WebClient DownloadUpdateWebClient + { + get + { + if (_downloadUpdateWebClient != null) + { + return _downloadUpdateWebClient; + } + + _downloadUpdateWebClient = CreateWebClient(); + + _downloadUpdateWebClient.DownloadProgressChanged += DownloadUpdateProgressChanged; + _downloadUpdateWebClient.DownloadFileCompleted += DownloadUpdateCompleted; + + return _downloadUpdateWebClient; + } + } + + #endregion + + #region Private Methods + + private WebClient CreateWebClient() + { + var webClient = new WebClient(); + webClient.Headers.Add("user-agent", GeneralAppInfo.UserAgent); + webClient.Proxy = _webProxy; + return webClient; + } + + private static DownloadStringCompletedEventArgs NewDownloadStringCompletedEventArgs(string result, + Exception exception, bool cancelled, object userToken) + { + var type = typeof(DownloadStringCompletedEventArgs); + const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; + Type[] argumentTypes = {typeof(string), typeof(Exception), typeof(bool), typeof(object)}; + var constructor = type.GetConstructor(bindingFlags, null, argumentTypes, null); + object[] arguments = {result, exception, cancelled, userToken}; + + return (DownloadStringCompletedEventArgs) constructor.Invoke(arguments); + } + + private DownloadStringCompletedEventArgs DownloadString(Uri address) + { + var webClient = CreateWebClient(); + var result = string.Empty; + Exception exception = null; + var cancelled = false; + + try + { + result = webClient.DownloadString(address); + } + catch (ThreadAbortException) + { + cancelled = true; + } + catch (Exception ex) + { + exception = ex; + } + + return NewDownloadStringCompletedEventArgs(result, exception, cancelled, null); + } + + public void GetUpdateInfo() + { + updChannel = new UpdateChannelInfo(); + var e = DownloadString(updChannel.GetUpdateTxtUri()); + + if (!e.Cancelled && e.Error == null) + { + CurrentUpdateInfo = UpdateInfo.FromString(e.Result); Settings.Default.CheckForUpdatesLastCheck = DateTime.UtcNow; - if (!Settings.Default.UpdatePending) - { + if (!Settings.Default.UpdatePending) + { Settings.Default.UpdatePending = IsUpdateAvailable(); - } - } + } + } GetUpdateInfoCompletedEventEvent?.Invoke(this, e); } - - private void GetChangeLog() - { - var e = DownloadString(CurrentUpdateInfo.ChangeLogAddress); - - if (!e.Cancelled && e.Error == null) - { - ChangeLog = e.Result; - } + + private void GetChangeLog() + { + var e = DownloadString(CurrentUpdateInfo.ChangeLogAddress); + + if (!e.Cancelled && e.Error == null) + { + ChangeLog = e.Result; + } GetChangeLogCompletedEventEvent?.Invoke(this, e); } - - private void DownloadUpdateProgressChanged(object sender, DownloadProgressChangedEventArgs e) - { + + private void DownloadUpdateProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { DownloadUpdateProgressChangedEventEvent?.Invoke(sender, e); } - - private void DownloadUpdateCompleted(object sender, AsyncCompletedEventArgs e) - { - var raiseEventArgs = e; - - if (!e.Cancelled && e.Error == null) - { - try - { + + private void DownloadUpdateCompleted(object sender, AsyncCompletedEventArgs e) + { + var raiseEventArgs = e; + + if (!e.Cancelled && e.Error == null) + { + try + { #if !PORTABLE var updateAuthenticode = new Authenticode(CurrentUpdateInfo.UpdateFilePath) - { - RequireThumbprintMatch = true, - ThumbprintToMatch = CurrentUpdateInfo.CertificateThumbprint - }; + { + RequireThumbprintMatch = true, + ThumbprintToMatch = CurrentUpdateInfo.CertificateThumbprint + }; - if (updateAuthenticode.Verify() != Authenticode.StatusValue.Verified) - { - if (updateAuthenticode.Status == Authenticode.StatusValue.UnhandledException) - { - throw (updateAuthenticode.Exception); - } + if (updateAuthenticode.Verify() != Authenticode.StatusValue.Verified) + { + if (updateAuthenticode.Status == Authenticode.StatusValue.UnhandledException) + { + throw updateAuthenticode.Exception; + } - throw (new Exception(updateAuthenticode.StatusMessage)); - } + throw new Exception(updateAuthenticode.StatusMessage); + } #endif using (var md5 = MD5.Create()) @@ -278,26 +290,29 @@ namespace mRemoteNG.App.Update } } } - catch (Exception ex) - { - raiseEventArgs = new AsyncCompletedEventArgs(ex, false, null); - } - } - - if (raiseEventArgs.Cancelled || raiseEventArgs.Error != null) - { - File.Delete(CurrentUpdateInfo.UpdateFilePath); - } + catch (Exception ex) + { + raiseEventArgs = new AsyncCompletedEventArgs(ex, false, null); + } + } + + if (raiseEventArgs.Cancelled || raiseEventArgs.Error != null) + { + File.Delete(CurrentUpdateInfo.UpdateFilePath); + } DownloadUpdateCompletedEventEvent?.Invoke(this, raiseEventArgs); _downloadUpdateWebClient.Dispose(); - _downloadUpdateWebClient = null; - } -#endregion - -#region Events + _downloadUpdateWebClient = null; + } + + #endregion + + #region Events + private AsyncCompletedEventHandler GetUpdateInfoCompletedEventEvent; + public event AsyncCompletedEventHandler GetUpdateInfoCompletedEvent { add @@ -311,6 +326,7 @@ namespace mRemoteNG.App.Update } private AsyncCompletedEventHandler GetChangeLogCompletedEventEvent; + public event AsyncCompletedEventHandler GetChangeLogCompletedEvent { add @@ -323,7 +339,8 @@ namespace mRemoteNG.App.Update } } - private DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEventEvent; + private DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEventEvent; + public event DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEvent { add @@ -337,6 +354,7 @@ namespace mRemoteNG.App.Update } private AsyncCompletedEventHandler DownloadUpdateCompletedEventEvent; + public event AsyncCompletedEventHandler DownloadUpdateCompletedEvent { add @@ -348,6 +366,7 @@ namespace mRemoteNG.App.Update DownloadUpdateCompletedEventEvent = (AsyncCompletedEventHandler)Delegate.Remove(DownloadUpdateCompletedEventEvent, value); } } -#endregion - } + + #endregion + } } \ No newline at end of file