mirror of https://github.com/winsw/winsw
Finalize the Download Command changes (#203)
* Refactor parameter parsing in Download.cs, add more checks * Handle Download#unsecureAuth as boolean * Parse Enums in a case-insensitive mode, propagate error correctly * Add tests for the newly introduced functionality * Update the configuration sample to reflect the recent changes * Update the sample text according to the proposal from @nightman68pull/207/head
parent
f0770a0e15
commit
790b3a6541
|
@ -154,7 +154,7 @@ For servers requiring authentication some parameters must be specified depending
|
||||||
* `basic`: Basic authentication, sub-parameters:
|
* `basic`: Basic authentication, sub-parameters:
|
||||||
* `username=“UserName”`
|
* `username=“UserName”`
|
||||||
* `password=“Passw0rd”`
|
* `password=“Passw0rd”`
|
||||||
* `unsecureAuth=“enabled”: default=“disabled"`
|
* `unsecureAuth=“true”: default=“false"`
|
||||||
|
|
||||||
The parameter “unsecureAuth” is only effective when the transfer protocol is HTTP - unencrypted data transfer. This is a security vulnerability because the credentials are send in clear text! For a SSPI authentication this is not relevant because the authentication tokens are encrypted.
|
The parameter “unsecureAuth” is only effective when the transfer protocol is HTTP - unencrypted data transfer. This is a security vulnerability because the credentials are send in clear text! For a SSPI authentication this is not relevant because the authentication tokens are encrypted.
|
||||||
|
|
||||||
|
@ -176,8 +176,8 @@ Examples:
|
||||||
auth="basic" username="aUser" password="aPassw0rd" />
|
auth="basic" username="aUser" password="aPassw0rd" />
|
||||||
|
|
||||||
<download from="http://example.com/some.dat" to="%BASE%\some.dat"
|
<download from="http://example.com/some.dat" to="%BASE%\some.dat"
|
||||||
auth="basic" unsecureAuth=“enabled”
|
auth="basic" unsecureAuth="true"
|
||||||
username="aUser" password=“aPassw0rd" />
|
username="aUser" password="aPassw0rd" />
|
||||||
```
|
```
|
||||||
|
|
||||||
This is another useful building block for developing a self-updating service.
|
This is another useful building block for developing a self-updating service.
|
||||||
|
|
|
@ -251,18 +251,21 @@ SECTION: Environment setup
|
||||||
-->
|
-->
|
||||||
<!--
|
<!--
|
||||||
<download from="http://www.google.com/" to="%BASE%\index.html" />
|
<download from="http://www.google.com/" to="%BASE%\index.html" />
|
||||||
<download from="http://www.nosuchhostexists.com/" to="%BASE%\dummy.html" />
|
|
||||||
|
Download and fail the service startup on Error:
|
||||||
|
<download from="http://www.nosuchhostexists.com/" to="%BASE%\dummy.html" failOnError="true"/>
|
||||||
|
|
||||||
An example for unsecure Basic authentication because the connection is not encrypted:
|
An example for unsecure Basic authentication because the connection is not encrypted:
|
||||||
<download from="http://example.com/some.dat" to="%BASE%\some.dat"
|
<download from="http://example.com/some.dat" to="%BASE%\some.dat"
|
||||||
auth="basic" unsecureAuth=“enabled”
|
auth="basic" unsecureAuth=“true”
|
||||||
username="aUser" password=“aPassw0rd" />
|
username="aUser" password=“aPassw0rd" />
|
||||||
|
|
||||||
Secure Basic authentication via HTTPS:
|
Secure Basic authentication via HTTPS:
|
||||||
<download from="https://example.com/some.dat" to="%BASE%\some.dat"
|
<download from="https://example.com/some.dat" to="%BASE%\some.dat"
|
||||||
auth="basic" username="aUser" password="aPassw0rd" />
|
auth="basic" username="aUser" password="aPassw0rd" />
|
||||||
|
|
||||||
Secure authentication when the target server and the client are members of domain:
|
Secure authentication when the target server and the client are members of the same domain or
|
||||||
|
the server domain and the client domain belong to the same forest with a trust:
|
||||||
<download from="https://example.com/some.dat" to="%BASE%\some.dat" auth="sspi" />
|
<download from="https://example.com/some.dat" to="%BASE%\some.dat" auth="sspi" />
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using winsw.Util;
|
||||||
|
|
||||||
namespace winsw
|
namespace winsw
|
||||||
{
|
{
|
||||||
|
@ -19,72 +20,64 @@ namespace winsw
|
||||||
public readonly AuthType Auth = AuthType.none;
|
public readonly AuthType Auth = AuthType.none;
|
||||||
public readonly string Username;
|
public readonly string Username;
|
||||||
public readonly string Password;
|
public readonly string Password;
|
||||||
public readonly bool UnsecureAuth = false;
|
public readonly bool UnsecureAuth;
|
||||||
public readonly bool FailOnError;
|
public readonly bool FailOnError;
|
||||||
|
|
||||||
public Download(string from, string to, bool failOnError = false)
|
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;
|
From = from;
|
||||||
To = to;
|
To = to;
|
||||||
FailOnError = failOnError;
|
FailOnError = failOnError;
|
||||||
|
Auth = auth;
|
||||||
|
Username = username;
|
||||||
|
Password = password;
|
||||||
|
UnsecureAuth = unsecureAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Download(XmlNode n)
|
/// <summary>
|
||||||
|
/// Constructs the download setting sfrom the XML entry
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="n">XML element</param>
|
||||||
|
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
|
||||||
|
internal Download(XmlElement n)
|
||||||
{
|
{
|
||||||
From = Environment.ExpandEnvironmentVariables(n.Attributes["from"].Value);
|
From = XmlHelper.SingleAttribute<String>(n, "from");
|
||||||
To = Environment.ExpandEnvironmentVariables(n.Attributes["to"].Value);
|
To = XmlHelper.SingleAttribute<String>(n, "to");
|
||||||
|
|
||||||
var failOnErrorNode = n.Attributes["failOnError"];
|
// All arguments below are optional
|
||||||
FailOnError = failOnErrorNode != null ? Boolean.Parse(failOnErrorNode.Value) : false;
|
FailOnError = XmlHelper.SingleAttribute<bool>(n, "failOnError", false);
|
||||||
|
|
||||||
string tmpStr = "";
|
Auth = XmlHelper.EnumAttribute<AuthType>(n, "auth", AuthType.none);
|
||||||
try
|
Username = XmlHelper.SingleAttribute<String>(n, "user", null);
|
||||||
{
|
Password = XmlHelper.SingleAttribute<String>(n, "password", null);
|
||||||
tmpStr = Environment.ExpandEnvironmentVariables(n.Attributes["auth"].Value);
|
UnsecureAuth = XmlHelper.SingleAttribute<bool>(n, "unsecureAuth", false);
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Auth = tmpStr != "" ? (AuthType)Enum.Parse(typeof(AuthType), tmpStr) : AuthType.none;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tmpStr = Environment.ExpandEnvironmentVariables(n.Attributes["username"].Value);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Username = tmpStr;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tmpStr = Environment.ExpandEnvironmentVariables(n.Attributes["password"].Value);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Password = tmpStr;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tmpStr = Environment.ExpandEnvironmentVariables(n.Attributes["unsecureAuth"].Value);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
UnsecureAuth = tmpStr == "enabled" ? true : false;
|
|
||||||
|
|
||||||
if (Auth == AuthType.basic)
|
if (Auth == AuthType.basic)
|
||||||
{
|
{
|
||||||
if (From.StartsWith("http:") && UnsecureAuth == false)
|
// Allow it only for HTTPS or for UnsecureAuth
|
||||||
|
if (!From.StartsWith("https:") && !UnsecureAuth)
|
||||||
{
|
{
|
||||||
throw new Exception("Warning: you're sending your credentials in clear text to the server. If you really want this you must enable this in the configuration!");
|
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
|
// Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest
|
||||||
public void SetBasicAuthHeader(WebRequest request, String username, String password)
|
private void SetBasicAuthHeader(WebRequest request, String username, String password)
|
||||||
{
|
{
|
||||||
string authInfo = username + ":" + password;
|
string authInfo = username + ":" + password;
|
||||||
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
|
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
|
||||||
|
@ -103,6 +96,10 @@ namespace winsw
|
||||||
|
|
||||||
switch (Auth)
|
switch (Auth)
|
||||||
{
|
{
|
||||||
|
case AuthType.none:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
|
||||||
case AuthType.sspi:
|
case AuthType.sspi:
|
||||||
req.UseDefaultCredentials = true;
|
req.UseDefaultCredentials = true;
|
||||||
req.PreAuthenticate = true;
|
req.PreAuthenticate = true;
|
||||||
|
@ -112,6 +109,9 @@ namespace winsw
|
||||||
case AuthType.basic:
|
case AuthType.basic:
|
||||||
SetBasicAuthHeader(req, Username, Password);
|
SetBasicAuthHeader(req, Username, Password);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new WebException("Code defect. Unsupported authentication type: " + Auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebResponse rsp = req.GetResponse();
|
WebResponse rsp = req.GetResponse();
|
||||||
|
@ -123,15 +123,6 @@ namespace winsw
|
||||||
File.Move(To + ".tmp", To);
|
File.Move(To + ".tmp", To);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Produces the XML configuuration entry.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>XML String for the configuration file</returns>
|
|
||||||
public String toXMLConfig()
|
|
||||||
{
|
|
||||||
return "<download from=\"" + From + "\" to=\"" + To + "\" failOnError=\"" + FailOnError + "\"/>";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CopyStream(Stream i, Stream o)
|
private static void CopyStream(Stream i, Stream o)
|
||||||
{
|
{
|
||||||
byte[] buf = new byte[8192];
|
byte[] buf = new byte[8192];
|
||||||
|
|
|
@ -563,7 +563,11 @@ namespace winsw
|
||||||
List<Download> r = new List<Download>();
|
List<Download> r = new List<Download>();
|
||||||
foreach (XmlNode n in xmlNodeList)
|
foreach (XmlNode n in xmlNodeList)
|
||||||
{
|
{
|
||||||
r.Add(new Download(n));
|
XmlElement el = n as XmlElement;
|
||||||
|
if (el != null)
|
||||||
|
{
|
||||||
|
r.Add(new Download(el));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,5 +71,32 @@ namespace winsw.Util
|
||||||
var value = (TAttributeType)Convert.ChangeType(substitutedValue, typeof(TAttributeType));
|
var value = (TAttributeType)Convert.ChangeType(substitutedValue, typeof(TAttributeType));
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retireves a single enum attribute
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAttributeType">Type of the enum</typeparam>
|
||||||
|
/// <param name="node">Parent node</param>
|
||||||
|
/// <param name="attributeName">Attribute name</param>
|
||||||
|
/// <param name="defaultValue">Default value</param>
|
||||||
|
/// <returns>Attribute value (or default)</returns>
|
||||||
|
/// <exception cref="InvalidDataException">Wrong enum value</exception>
|
||||||
|
public static TAttributeType EnumAttribute<TAttributeType>(XmlElement node, string attributeName, TAttributeType defaultValue)
|
||||||
|
{
|
||||||
|
if (!node.HasAttribute(attributeName)) return defaultValue;
|
||||||
|
|
||||||
|
string rawValue = node.GetAttribute(attributeName);
|
||||||
|
string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = Enum.Parse(typeof(TAttributeType), substitutedValue, true);
|
||||||
|
return (TAttributeType)value;
|
||||||
|
}
|
||||||
|
catch (Exception ex) // Most likely ArgumentException
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Cannot parse <" + attributeName + "> Enum value from string '" + substitutedValue +
|
||||||
|
"'. Enum type: " + typeof(TAttributeType), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using winsw;
|
using winsw;
|
||||||
using winswTests.Util;
|
using winswTests.Util;
|
||||||
|
@ -10,9 +11,87 @@ namespace winswTests
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
class DownloadTest
|
class DownloadTest
|
||||||
{
|
{
|
||||||
private const string From = "http://www.nosuchhostexists.foo.myorg/foo.xml";
|
private const string From = "https://www.nosuchhostexists.foo.myorg/foo.xml";
|
||||||
private const string To = "%BASE%\\foo.xml";
|
private const string To = "%BASE%\\foo.xml";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Roundtrip_Defaults()
|
||||||
|
{
|
||||||
|
// Roundtrip data
|
||||||
|
Download d = new Download(From, To);
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithDownload(d)
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
var loaded = getSingleEntry(sd);
|
||||||
|
|
||||||
|
// Check default values
|
||||||
|
Assert.That(loaded.FailOnError, Is.EqualTo(false));
|
||||||
|
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.none));
|
||||||
|
Assert.That(loaded.Username, Is.Null);
|
||||||
|
Assert.That(loaded.Password, Is.Null);
|
||||||
|
Assert.That(loaded.UnsecureAuth, Is.EqualTo(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Roundtrip_BasicAuth()
|
||||||
|
{
|
||||||
|
// Roundtrip data
|
||||||
|
Download d = new Download(From, To, true, Download.AuthType.basic, "aUser", "aPassword", true);
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithDownload(d)
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
var loaded = getSingleEntry(sd);
|
||||||
|
|
||||||
|
// Check default values
|
||||||
|
Assert.That(loaded.FailOnError, Is.EqualTo(true));
|
||||||
|
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.basic));
|
||||||
|
Assert.That(loaded.Username, Is.EqualTo("aUser"));
|
||||||
|
Assert.That(loaded.Password, Is.EqualTo("aPassword"));
|
||||||
|
Assert.That(loaded.UnsecureAuth, Is.EqualTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Roundtrip_SSPI()
|
||||||
|
{
|
||||||
|
// Roundtrip data
|
||||||
|
Download d = new Download(From, To, false, Download.AuthType.sspi);
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithDownload(d)
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
var loaded = getSingleEntry(sd);
|
||||||
|
|
||||||
|
// Check default values
|
||||||
|
Assert.That(loaded.FailOnError, Is.EqualTo(false));
|
||||||
|
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.sspi));
|
||||||
|
Assert.That(loaded.Username, Is.Null);
|
||||||
|
Assert.That(loaded.Password, Is.Null);
|
||||||
|
Assert.That(loaded.UnsecureAuth, Is.EqualTo(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("http://")]
|
||||||
|
[TestCase("ftp://")]
|
||||||
|
[TestCase("file:///")]
|
||||||
|
[TestCase("jar://")]
|
||||||
|
[TestCase("\\\\")] // UNC
|
||||||
|
public void ShouldReject_BasicAuth_with_UnsecureProtocol(String protocolPrefix)
|
||||||
|
{
|
||||||
|
var d = new Download(protocolPrefix + "myServer.com:8080/file.txt", To,
|
||||||
|
auth: Download.AuthType.basic, username: "aUser", password: "aPassword");
|
||||||
|
assertInitializationFails(d, "you're sending your credentials in clear text to the server");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShouldRejectBasicAuth_without_username()
|
||||||
|
{
|
||||||
|
var d = new Download(From, To, auth: Download.AuthType.basic, username: null, password: "aPassword");
|
||||||
|
assertInitializationFails(d, "Basic Auth is enabled, but username is not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShouldRejectBasicAuth_without_password()
|
||||||
|
{
|
||||||
|
var d = new Download(From, To, auth: Download.AuthType.basic, username: "aUser", password: null);
|
||||||
|
assertInitializationFails(d, "Basic Auth is enabled, but password is not specified");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures that the fail-on-error field is being processed correctly.
|
/// Ensures that the fail-on-error field is being processed correctly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -46,11 +125,49 @@ namespace winswTests
|
||||||
Assert.That(loaded.FailOnError, Is.False);
|
Assert.That(loaded.FailOnError, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("sspi")]
|
||||||
|
[TestCase("SSPI")]
|
||||||
|
[TestCase("SsPI")]
|
||||||
|
[TestCase("Sspi")]
|
||||||
|
public void AuthType_Is_CaseInsensitive(String authType)
|
||||||
|
{
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"" + authType + "\"/>")
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
var loaded = getSingleEntry(sd);
|
||||||
|
Assert.That(loaded.Auth, Is.EqualTo(Download.AuthType.sspi));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Should_Fail_On_Unsupported_AuthType()
|
||||||
|
{
|
||||||
|
// TODO: will need refactoring once all fields are being parsed on startup
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>")
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
|
||||||
|
ExceptionHelper.assertFails("Cannot parse <auth> Enum value from string 'digest'", typeof(InvalidDataException), delegate {
|
||||||
|
var d = getSingleEntry(sd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Download getSingleEntry(ServiceDescriptor sd)
|
private Download getSingleEntry(ServiceDescriptor sd)
|
||||||
{
|
{
|
||||||
var downloads = sd.Downloads.ToArray();
|
var downloads = sd.Downloads.ToArray();
|
||||||
Assert.That(downloads.Length, Is.EqualTo(1), "Service Descriptor is expected to have only one entry");
|
Assert.That(downloads.Length, Is.EqualTo(1), "Service Descriptor is expected to have only one entry");
|
||||||
return downloads[0];
|
return downloads[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertInitializationFails(Download download, String expectedMessagePart = null, Type expectedExceptionType = null)
|
||||||
|
{
|
||||||
|
var sd = ConfigXmlBuilder.create()
|
||||||
|
.WithDownload(download)
|
||||||
|
.ToServiceDescriptor(true);
|
||||||
|
|
||||||
|
ExceptionHelper.assertFails(expectedMessagePart, expectedExceptionType ?? typeof(InvalidDataException), delegate
|
||||||
|
{
|
||||||
|
var d = getSingleEntry(sd);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,27 @@ namespace winswTests.Util
|
||||||
|
|
||||||
public ConfigXmlBuilder WithDownload(Download download)
|
public ConfigXmlBuilder WithDownload(Download download)
|
||||||
{
|
{
|
||||||
return WithRawEntry(download.toXMLConfig());
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.AppendFormat("<download from=\"{0}\" to=\"{1}\" failOnError=\"{2}\"", new Object[] { download.From, download.To, download.FailOnError});
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
if (download.Auth != Download.AuthType.none)
|
||||||
|
{
|
||||||
|
str.AppendFormat(" auth=\"{0}\"", download.Auth);
|
||||||
|
if (download.Auth == Download.AuthType.basic)
|
||||||
|
{
|
||||||
|
str.AppendFormat(" user=\"{0}\" password=\"{1}\"", new Object[] { download.Username, download.Password });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download.UnsecureAuth)
|
||||||
|
{
|
||||||
|
str.AppendFormat(" unsecureAuth=\"true\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str.Append("/>");
|
||||||
|
|
||||||
|
return WithRawEntry(str.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace winswTests.Util
|
||||||
|
{
|
||||||
|
class ExceptionHelper
|
||||||
|
{
|
||||||
|
public static void assertFails(String expectedMessagePart, Type expectedExceptionType, ExceptionHelperExecutionBody body)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
body();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Out.WriteLine("Caught exception: " + ex);
|
||||||
|
Assert.That(ex, Is.InstanceOf(expectedExceptionType ?? typeof(Exception)), "Wrong exception type");
|
||||||
|
if (expectedMessagePart != null)
|
||||||
|
{
|
||||||
|
Assert.That(ex.Message, Is.StringContaining(expectedMessagePart), "Wrong error message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else the exception is fine
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail("Expected exception " + expectedExceptionType + " to be thrown by the operation");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void ExceptionHelperExecutionBody();
|
||||||
|
}
|
|
@ -61,6 +61,7 @@
|
||||||
<Compile Include="Extensions\RunawayProcessKillerTest.cs" />
|
<Compile Include="Extensions\RunawayProcessKillerTest.cs" />
|
||||||
<Compile Include="Extensions\SharedDirectoryMapperTest.cs" />
|
<Compile Include="Extensions\SharedDirectoryMapperTest.cs" />
|
||||||
<Compile Include="MainTest.cs" />
|
<Compile Include="MainTest.cs" />
|
||||||
|
<Compile Include="Util\ExceptionHelper.cs" />
|
||||||
<Compile Include="Util\FilesystemTestHelper.cs" />
|
<Compile Include="Util\FilesystemTestHelper.cs" />
|
||||||
<Compile Include="Util\ProcessHelperTest.cs" />
|
<Compile Include="Util\ProcessHelperTest.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|
Loading…
Reference in New Issue