using System; using System.IO; using System.Net; using System.Text; using System.Xml; using winsw.Util; namespace winsw { /// /// Specify the download activities prior to the launch. /// This enables self-updating services. /// public class Download { public enum AuthType { none = 0, sspi, basic } public readonly string From; public readonly string To; public readonly AuthType Auth = AuthType.none; public readonly string Username; public readonly string Password; public readonly bool UnsecureAuth; public readonly bool FailOnError; public string ShortId { get { return String.Format("(download from {0})", From); } } public Download(string from, string to, bool failOnError = false, AuthType auth = AuthType.none, string username = null, string password = null, bool unsecureAuth = false) { From = from; To = to; FailOnError = failOnError; Auth = auth; Username = username; Password = password; UnsecureAuth = unsecureAuth; } /// /// Constructs the download setting sfrom the XML entry /// /// XML element /// The required attribute is missing or the configuration is invalid internal Download(XmlElement n) { From = XmlHelper.SingleAttribute(n, "from"); To = XmlHelper.SingleAttribute(n, "to"); // All arguments below are optional FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false); Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.none); Username = XmlHelper.SingleAttribute(n, "user", null); Password = XmlHelper.SingleAttribute(n, "password", null); UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false); if (Auth == AuthType.basic) { // Allow it only for HTTPS or for UnsecureAuth if (!From.StartsWith("https:") && !UnsecureAuth) { throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + ShortId + "If you really want this you must enable 'unsecureAuth' in the configuration"); } // Also fail if there is no user/password if (Username == null) { throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + ShortId); } if (Password == null) { throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + ShortId); } } } // Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest private void SetBasicAuthHeader(WebRequest request, String username, String password) { string authInfo = username + ":" + password; authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); request.Headers["Authorization"] = "Basic " + authInfo; } /// /// Downloads the requested file and puts it to the specified target. /// /// /// Download failure. FailOnError flag should be processed outside. /// public void Perform() { WebRequest req = WebRequest.Create(From); switch (Auth) { case AuthType.none: // Do nothing break; case AuthType.sspi: req.UseDefaultCredentials = true; req.PreAuthenticate = true; req.Credentials = CredentialCache.DefaultCredentials; break; case AuthType.basic: SetBasicAuthHeader(req, Username, Password); break; default: throw new WebException("Code defect. Unsupported authentication type: " + Auth); } WebResponse rsp = req.GetResponse(); FileStream tmpstream = new FileStream(To + ".tmp", FileMode.Create); CopyStream(rsp.GetResponseStream(), tmpstream); // only after we successfully downloaded a file, overwrite the existing one if (File.Exists(To)) File.Delete(To); File.Move(To + ".tmp", To); } private static void CopyStream(Stream i, Stream o) { byte[] buf = new byte[8192]; while (true) { int len = i.Read(buf, 0, buf.Length); if (len <= 0) break; o.Write(buf, 0, len); } i.Close(); o.Close(); } } }