mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 14:07:46 +08:00
403 lines
14 KiB
C#
403 lines
14 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.ComponentModel;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
using mRemoteNG.App.Info;
|
|
using mRemoteNG.Security.SymmetricEncryption;
|
|
using System.Security.Cryptography;
|
|
using mRemoteNG.Properties;
|
|
#if !PORTABLE
|
|
using mRemoteNG.Tools;
|
|
|
|
#else
|
|
using System.Windows.Forms;
|
|
|
|
#endif
|
|
// ReSharper disable ArrangeAccessorOwnerBody
|
|
|
|
namespace mRemoteNG.App.Update
|
|
{
|
|
public class AppUpdater
|
|
{
|
|
private WebProxy _webProxy;
|
|
private Thread _getUpdateInfoThread;
|
|
private Thread _getChangeLogThread;
|
|
|
|
#region Public Properties
|
|
|
|
public UpdateInfo CurrentUpdateInfo { get; private set; }
|
|
|
|
public string ChangeLog { get; private set; }
|
|
|
|
public bool IsGetUpdateInfoRunning
|
|
{
|
|
get { return _getUpdateInfoThread != null && _getUpdateInfoThread.IsAlive; }
|
|
}
|
|
|
|
private bool IsGetChangeLogRunning
|
|
{
|
|
get { return _getChangeLogThread != null && _getChangeLogThread.IsAlive; }
|
|
}
|
|
|
|
public bool IsDownloadUpdateRunning
|
|
{
|
|
get { return _downloadUpdateWebClient != null; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#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);
|
|
|
|
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().");
|
|
}
|
|
#if !PORTABLE
|
|
CurrentUpdateInfo.UpdateFilePath =
|
|
Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), "msi"));
|
|
#else
|
|
var sfd = new SaveFileDialog
|
|
{
|
|
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
|
FileName = CurrentUpdateInfo.FileName,
|
|
RestoreDirectory = true
|
|
};
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
CurrentUpdateInfo.UpdateFilePath = sfd.FileName;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
#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};
|
|
|
|
if (constructor == null)
|
|
return null;
|
|
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 e = DownloadString(UpdateChannelInfo.GetUpdateChannelInfo());
|
|
|
|
if (!e.Cancelled && e.Error == null)
|
|
{
|
|
CurrentUpdateInfo = UpdateInfo.FromString(e.Result);
|
|
|
|
Settings.Default.CheckForUpdatesLastCheck = DateTime.UtcNow;
|
|
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;
|
|
}
|
|
|
|
GetChangeLogCompletedEventEvent?.Invoke(this, 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
|
|
{
|
|
#if !PORTABLE
|
|
var updateAuthenticode = new Authenticode(CurrentUpdateInfo.UpdateFilePath)
|
|
{
|
|
RequireThumbprintMatch = true,
|
|
ThumbprintToMatch = CurrentUpdateInfo.CertificateThumbprint
|
|
};
|
|
|
|
if (updateAuthenticode.Verify() != Authenticode.StatusValue.Verified)
|
|
{
|
|
if (updateAuthenticode.Status == Authenticode.StatusValue.UnhandledException)
|
|
{
|
|
throw updateAuthenticode.Exception;
|
|
}
|
|
|
|
throw new Exception(updateAuthenticode.GetStatusMessage());
|
|
}
|
|
#endif
|
|
|
|
using (var cksum = SHA512.Create())
|
|
{
|
|
using (var stream = File.OpenRead(CurrentUpdateInfo.UpdateFilePath))
|
|
{
|
|
var hash = cksum.ComputeHash(stream);
|
|
var hashString = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
|
|
if (!hashString.Equals(CurrentUpdateInfo.Checksum))
|
|
throw new Exception("SHA512 Hashes didn't match!");
|
|
}
|
|
}
|
|
}
|
|
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
|
|
|
|
private AsyncCompletedEventHandler GetUpdateInfoCompletedEventEvent;
|
|
|
|
public event AsyncCompletedEventHandler GetUpdateInfoCompletedEvent
|
|
{
|
|
add
|
|
{
|
|
GetUpdateInfoCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Combine(GetUpdateInfoCompletedEventEvent, value);
|
|
}
|
|
remove
|
|
{
|
|
GetUpdateInfoCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Remove(GetUpdateInfoCompletedEventEvent, value);
|
|
}
|
|
}
|
|
|
|
private AsyncCompletedEventHandler GetChangeLogCompletedEventEvent;
|
|
|
|
public event AsyncCompletedEventHandler GetChangeLogCompletedEvent
|
|
{
|
|
add
|
|
{
|
|
GetChangeLogCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Combine(GetChangeLogCompletedEventEvent, value);
|
|
}
|
|
remove
|
|
{
|
|
GetChangeLogCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Remove(GetChangeLogCompletedEventEvent, value);
|
|
}
|
|
}
|
|
|
|
private DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEventEvent;
|
|
|
|
public event DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEvent
|
|
{
|
|
add
|
|
{
|
|
DownloadUpdateProgressChangedEventEvent =
|
|
(DownloadProgressChangedEventHandler)Delegate.Combine(DownloadUpdateProgressChangedEventEvent,
|
|
value);
|
|
}
|
|
remove
|
|
{
|
|
DownloadUpdateProgressChangedEventEvent =
|
|
(DownloadProgressChangedEventHandler)Delegate.Remove(DownloadUpdateProgressChangedEventEvent,
|
|
value);
|
|
}
|
|
}
|
|
|
|
private AsyncCompletedEventHandler DownloadUpdateCompletedEventEvent;
|
|
|
|
public event AsyncCompletedEventHandler DownloadUpdateCompletedEvent
|
|
{
|
|
add
|
|
{
|
|
DownloadUpdateCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Combine(DownloadUpdateCompletedEventEvent, value);
|
|
}
|
|
remove
|
|
{
|
|
DownloadUpdateCompletedEventEvent =
|
|
(AsyncCompletedEventHandler)Delegate.Remove(DownloadUpdateCompletedEventEvent, value);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |