#region Disclaimer / License // Copyright (C) 2011, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; namespace FreshKeeper { /// /// FreshKeeper is an LGPL update system for .Net /// public class FreshKeeper { /// /// The configuration file /// private string m_configFile; /// /// The configuration object /// private Config m_config = null; /// /// Information about the last update check /// private LastCheck m_lastCheck = null; /// /// The event type used to signal an update /// /// The event sender /// The update found public delegate void UpdateavailableEvent(FreshKeeper sender, Update update); /// /// The event type used to signal an error /// /// The event sender /// The exception that occured public delegate void UpdateErrorEvent(FreshKeeper sender, Exception ex); /// /// An event that is raised when an update is available /// public UpdateavailableEvent Updateavailable; /// /// An event that is raised when an error occurs while checking for updates /// public UpdateErrorEvent UpdateError; /// /// The list of available updates /// private List m_updates = null; /// /// Constructs a new FreshKeeper instance /// public FreshKeeper() { m_configFile = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "FreshKeeper.xml"); } /// /// Constructs a new FreshKeeper instance /// /// The configuration file to use public FreshKeeper(string configFile) { m_configFile = configFile; } /// /// Gets or sets the configuration item /// public Config Config { get { return m_config; } set { m_config = value; } } /// /// Initiates an syncronous check for updates /// /// A value indicating if the duration and user-enabled check should be bypassed public void CheckForUpdates(bool force) { try { if (m_config == null) { System.Xml.Serialization.XmlSerializer src = new System.Xml.Serialization.XmlSerializer(typeof(Config)); using (System.IO.FileStream fs = new System.IO.FileStream(m_configFile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) m_config = (Config)src.Deserialize(fs); } //This throws an exception if somethings broken m_config.CheckValid(); if (!m_config.Enabled && !force) return; if (m_lastCheck == null) { string file = m_config.ApplicationName + ".xml"; foreach (char c in System.IO.Path.GetInvalidFileNameChars()) file = file.Replace(c, '-'); file = System.IO.Path.Combine(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreshKeeper"), file); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(file))) System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); if (System.IO.File.Exists(file)) { System.Xml.Serialization.XmlSerializer srl = new System.Xml.Serialization.XmlSerializer(typeof(LastCheck)); using (System.IO.FileStream fs = new System.IO.FileStream(m_configFile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) m_lastCheck = (LastCheck)srl.Deserialize(fs); } else m_lastCheck = new LastCheck(); } if (Duplicati.Library.Core.Timeparser.ParseTimeInterval(m_config.CheckInterval, m_lastCheck.Time) > DateTime.Now) return; Random r = new Random(); string url = m_config.Urls[r.Next(0, m_config.Urls.Length)]; System.Net.WebClient wc = new System.Net.WebClient(); System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.PreserveWhitespace = true; //Make sure we don't alter the document using (System.IO.MemoryStream ms = new System.IO.MemoryStream(wc.DownloadData(url))) doc.Load(ms); string hash = doc["UpdateList"].Attributes["SignedHash"].Value; doc["UpdateList"].Attributes["SignedHash"].Value = ""; System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider(); rsa.FromXmlString(m_config.PublicKey); UpdateList lst = null; using(System.IO.MemoryStream ms = new System.IO.MemoryStream()) { doc.Save(ms); if (!rsa.VerifyData(ms.ToArray(), System.Security.Cryptography.CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(hash))) throw new Exception("Failed to verify signature"); ms.Position = 0; System.Xml.Serialization.XmlSerializer sr = new System.Xml.Serialization.XmlSerializer(typeof(UpdateList)); lst = (UpdateList)sr.Deserialize(ms); lst.SignedHash = hash; } if (lst == null || lst.Updates == null || lst.Updates.Length == 0) return; Update newest = lst.Updates[0]; foreach(Update u in lst.Updates) if (u.Version > newest.Version && (!u.BugfixUpdate || (u.BugfixUpdate && m_config.NotifyOnRevisionChange))) newest = u; if (newest.Version > m_config.LocalVersion) if (Updateavailable != null) Updateavailable(this, newest); } catch (Exception ex) { RaiseErrorEvent(ex); return; } } private void RaiseErrorEvent(Exception ex) { if (UpdateError != null) UpdateError(this, ex); } } }