diff --git a/src/Nancy.Demo.Hosting.Self/Models/Index.cs b/src/Nancy.Demo.Hosting.Self/Models/Index.cs
new file mode 100644
index 0000000000..9c6b413480
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/Models/Index.cs
@@ -0,0 +1,14 @@
+namespace Nancy.Demo.Hosting.Self.Models
+{
+ public class Index
+ {
+ public string Name { get; set; }
+
+ public string Posted { get; set; }
+
+ public Index()
+ {
+ this.Posted = "Nothing :-(";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Demo.Hosting.Self/Nancy.Demo.Hosting.Self.csproj b/src/Nancy.Demo.Hosting.Self/Nancy.Demo.Hosting.Self.csproj
new file mode 100644
index 0000000000..01ff356077
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/Nancy.Demo.Hosting.Self.csproj
@@ -0,0 +1,177 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}
+ Exe
+ Properties
+ Nancy.Demo.Hosting.Self
+ Nancy.Demo.Hosting.Self
+ v4.5
+ 512
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+ Nancy.Demo.Hosting.Self.Program
+
+
+ true
+ bin\x86\MonoDebug\
+ DEBUG;TRACE
+ full
+ x86
+ bin\Debug\Nancy.Demo.Hosting.Self.exe.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ false
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ false
+ 4
+ false
+ false
+
+
+ bin\x86\MonoRelease\
+ TRACE
+ true
+ pdbonly
+ x86
+ bin\Release\Nancy.Demo.Hosting.Self.exe.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ false
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ false
+ 4
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SharedAssemblyInfo.cs
+
+
+
+
+
+
+
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}
+ Nancy.Hosting.Self
+
+
+ {4b7e35df-1569-4346-b180-a09615723095}
+ Nancy.ViewEngines.Spark
+
+
+ {34576216-0DCA-4B0F-A0DC-9075E75A676F}
+ Nancy
+
+
+
+
+ Designer
+
+
+ Designer
+ Always
+
+
+
+
+ Always
+
+
+
+
+ False
+ Microsoft .NET Framework 4 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Nancy.Demo.Hosting.Self/Program.cs b/src/Nancy.Demo.Hosting.Self/Program.cs
new file mode 100644
index 0000000000..92cabad650
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/Program.cs
@@ -0,0 +1,30 @@
+namespace Nancy.Demo.Hosting.Self
+{
+ using System;
+ using System.Diagnostics;
+
+ using Nancy.Hosting.Self;
+
+ class Program
+ {
+ static void Main()
+ {
+ using (var nancyHost = new NancyHost(new Uri("http://localhost:8888/nancy/"), new Uri("http://127.0.0.1:8898/nancy/"), new Uri("http://localhost:8889/nancytoo/")))
+ {
+ nancyHost.Start();
+
+ Console.WriteLine("Nancy now listening - navigating to http://localhost:8888/nancy/. Press enter to stop");
+ try
+ {
+ Process.Start("http://localhost:8888/nancy/");
+ }
+ catch (Exception)
+ {
+ }
+ Console.ReadKey();
+ }
+
+ Console.WriteLine("Stopped. Good bye!");
+ }
+ }
+}
diff --git a/src/Nancy.Demo.Hosting.Self/README.txt b/src/Nancy.Demo.Hosting.Self/README.txt
new file mode 100644
index 0000000000..5f282702bb
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/README.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Nancy.Demo.Hosting.Self/TestModule.cs b/src/Nancy.Demo.Hosting.Self/TestModule.cs
new file mode 100644
index 0000000000..236f732734
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/TestModule.cs
@@ -0,0 +1,45 @@
+namespace Nancy.Demo.Hosting.Self
+{
+ using System.Linq;
+
+ using Nancy.Demo.Hosting.Self.Models;
+
+ public class TestModule : NancyModule
+ {
+ public TestModule()
+ {
+ Get["/"] = parameters => {
+ return View["staticview", this.Request.Url];
+ };
+
+ Get["/testing"] = parameters =>
+ {
+ return View["staticview", this.Request.Url];
+ };
+
+ Get["/fileupload"] = x =>
+ {
+ var model = new Index() { Name = "Boss Hawg" };
+
+ return View["FileUpload", model];
+ };
+
+ Post["/fileupload"] = x =>
+ {
+ var model = new Index() { Name = "Boss Hawg" };
+
+ var file = this.Request.Files.FirstOrDefault();
+ string fileDetails = "None";
+
+ if (file != null)
+ {
+ fileDetails = string.Format("{3} - {0} ({1}) {2}bytes", file.Name, file.ContentType, file.Value.Length, file.Key);
+ }
+
+ model.Posted = fileDetails;
+
+ return View["FileUpload", model];
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Demo.Hosting.Self/Views/FileUpload.spark b/src/Nancy.Demo.Hosting.Self/Views/FileUpload.spark
new file mode 100644
index 0000000000..4fab92d003
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/Views/FileUpload.spark
@@ -0,0 +1,17 @@
+
+
+
+ Nancy Self Host Demo
+
+
+ Hello ${Model.Name}!
+ This is a Spark view rendered via the self hosting.
+ You uploaded: ${Model.Posted}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Nancy.Demo.Hosting.Self/Views/staticview.html b/src/Nancy.Demo.Hosting.Self/Views/staticview.html
new file mode 100644
index 0000000000..f7a407d497
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/Views/staticview.html
@@ -0,0 +1,25 @@
+
+
+
+ Nancy - Static view served by self-host
+
+
+ Static view served by self-host
+
+ This view was served by the Nancy self-host.
+
+
+ - Scheme: @Model.Scheme
+ - HostName: @Model.HostName
+ - Port: @Model.Port
+ - BasePath: @Model.BasePath
+ - Path: @Model.Path
+
+ http://localhost:8888/nancy/
+ http://localhost:8888/nancy/testing
+ http://127.0.0.1:8898/nancy/
+ http://127.0.0.1:8898/nancy/testing
+ http://localhost:8889/nancytoo/
+ http://localhost:8889/nancytoo/testing
+
+
diff --git a/src/Nancy.Demo.Hosting.Self/app.config b/src/Nancy.Demo.Hosting.Self/app.config
new file mode 100644
index 0000000000..d757e6dc64
--- /dev/null
+++ b/src/Nancy.Demo.Hosting.Self/app.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Nancy.Hosting.Self.Tests/IsCaseInstensitiveBaseOfFixture.cs b/src/Nancy.Hosting.Self.Tests/IsCaseInstensitiveBaseOfFixture.cs
new file mode 100644
index 0000000000..95f16caa5a
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/IsCaseInstensitiveBaseOfFixture.cs
@@ -0,0 +1,185 @@
+namespace Nancy.Hosting.Self.Tests
+{
+ using System;
+
+ using Nancy.Tests;
+
+ using Xunit;
+
+ public class IsCaseInstensitiveBaseOfFixture
+ {
+ private readonly Uri baseUri = new Uri("http://host/path/path/file");
+ private readonly Uri baseSlashUri = new Uri("http://host/path/path/");
+ private readonly Uri baseLocalHostUri = new Uri("http://localhost/path/path/");
+
+ [Fact]
+ public void url_should_be_base_of_sub_directory()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file/"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_sub_path_with_fragment()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file#fragment"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_path_with_more_dirs()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file/MoreDir/"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_sub_path_with_file_and_query()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file/OtherFile?Query"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_path_with_extra_slash()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file/"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_sub_file()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_not_be_base_of_other_scheme()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("https://host/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void url_should_not_be_base_of_other_host()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://otherhost/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void url_should_not_be_base_of_other_port()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://otherhost:8080/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_host_with_different_casing()
+ {
+ // Given, When
+ var isBaseOf = baseUri.IsCaseInsensitiveBaseOf(new Uri("http://Host/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_exact_path_without_trailing_slash()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_exact_path_without_trailing_slash_with_query()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path?query"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_exact_path_without_trailing_slash_with_fragment()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path#Fragment"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_not_be_base_of_other_path()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://host/path/path2/"));
+
+ // Then
+ isBaseOf.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_same_path_with_different_host_casing()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://Host/path/path/"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_same_path_with_different_path_casing()
+ {
+ // Given, When
+ var isBaseOf = baseSlashUri.IsCaseInsensitiveBaseOf(new Uri("http://host/Path/PATH/"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void url_should_be_base_of_same_path_with_different_host_using_localhost_wildcard()
+ {
+ // Given, When
+ var isBaseOf = baseLocalHostUri.IsCaseInsensitiveBaseOf(new Uri("http://OtherHost/path/path/file"));
+
+ // Then
+ isBaseOf.ShouldBeTrue();
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self.Tests/MakeAppLocalPathFixture.cs b/src/Nancy.Hosting.Self.Tests/MakeAppLocalPathFixture.cs
new file mode 100644
index 0000000000..19cdc53fdc
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/MakeAppLocalPathFixture.cs
@@ -0,0 +1,102 @@
+namespace Nancy.Hosting.Self.Tests
+{
+ using System;
+
+ using Nancy.Tests;
+
+ using Xunit;
+
+ public class MakeAppLocalPathFixture
+ {
+ [Fact]
+ public void Should_return_path_as_local_path()
+ {
+ // Given
+ var uri = new Uri("http://host/base/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/base/rel"));
+
+ // Then
+ result.ShouldEqual("/rel");
+ }
+
+ [Fact]
+ public void Should_return_root_path_with_trailing_slash_as_slash()
+ {
+ // Given
+ var uri = new Uri("http://host/base/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/base/"));
+
+ // Then
+ result.ShouldEqual("/");
+ }
+
+ [Fact]
+ public void Should_return_root_path_without_trailing_slash_as_slash()
+ {
+ // Given
+ var uri = new Uri("http://host/base/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/base"));
+
+ // Then
+ result.ShouldEqual("/");
+ }
+
+ [Fact]
+ public void Should_return_path_with_same_casing_as_full_uri()
+ {
+ // Given
+ var uri = new Uri("http://host/base/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/base/ReL"));
+
+ // Then
+ result.ShouldEqual("/ReL");
+ }
+
+ [Fact]
+ public void Should_support_extended_site_root()
+ {
+ // Given
+ var uri = new Uri("http://host/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/rel/file"));
+
+ // Then
+ result.ShouldEqual("/rel/file");
+ }
+
+ [Fact]
+ public void Should_support_site_root_without_trailing_slash()
+ {
+ // Given
+ var uri = new Uri("http://host/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host"));
+
+ // Then
+ result.ShouldEqual("/");
+ }
+
+ [Fact]
+ public void Should_return_path_with_case_insensitive_base_uri_comparison()
+ {
+ // Given
+ var uri = new Uri("http://host/base/");
+
+ // When
+ string result = uri.MakeAppLocalPath(new Uri("http://host/Base/rel"));
+
+ // Then
+ result.ShouldEqual("/rel");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self.Tests/Nancy.Hosting.Self.Tests.csproj b/src/Nancy.Hosting.Self.Tests/Nancy.Hosting.Self.Tests.csproj
new file mode 100644
index 0000000000..1adb443276
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/Nancy.Hosting.Self.Tests.csproj
@@ -0,0 +1,173 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}
+ Library
+ Properties
+ Nancy.Hosting.Self.Tests
+ Nancy.Hosting.Self.Tests
+ v4.5
+ 512
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+ true
+ bin\MonoDebug\
+ DEBUG;TRACE
+ full
+ AnyCPU
+ bin\Debug\Nancy.Hosting.Self.Tests.dll.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ false
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ true
+ false
+
+
+ bin\MonoRelease\
+ TRACE
+ true
+ pdbonly
+ AnyCPU
+ bin\Release\Nancy.Hosting.Self.Tests.dll.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ true
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ true
+ false
+
+
+
+ False
+ ..\packages\FakeItEasy.1.19.0\lib\net40\FakeItEasy.dll
+
+
+
+
+
+
+
+
+
+ False
+ ..\packages\xunit.1.9.1\lib\net20\xunit.dll
+
+
+ False
+ ..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll
+
+
+
+
+ ShouldExtensions.cs
+
+
+ SkipException.cs
+
+
+ SkippableFactAttribute.cs
+
+
+ Properties\SharedAssemblyInfo.cs
+
+
+
+
+
+
+
+
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}
+ Nancy.Hosting.Self
+
+
+ {34576216-0DCA-4B0F-A0DC-9075E75A676F}
+ Nancy
+
+
+
+
+ False
+ Microsoft .NET Framework 4 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self.Tests/NancySelfHostFixture.cs b/src/Nancy.Hosting.Self.Tests/NancySelfHostFixture.cs
new file mode 100644
index 0000000000..cc058076ef
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/NancySelfHostFixture.cs
@@ -0,0 +1,268 @@
+#if !__MonoCS__
+namespace Nancy.Hosting.Self.Tests
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Threading;
+
+ using FakeItEasy;
+
+ using Nancy.Bootstrapper;
+ using Nancy.Helpers;
+ using Nancy.Tests;
+ using Nancy.Tests.xUnitExtensions;
+
+ using Xunit;
+
+ ///
+ /// These tests attempt to listen on port 1234, and so require either administrative
+ /// privileges or that a command similar to the following has been run with
+ /// administrative privileges:
+ /// netsh http add urlacl url=http://+:1234/base user=DOMAIN\user
+ /// See http://msdn.microsoft.com/en-us/library/ms733768.aspx for more information.
+ ///
+ public class NancySelfHostFixture
+ {
+ private static readonly Uri BaseUri = new Uri("http://localhost:1234/base/");
+
+ [SkippableFact]
+ public void Should_be_get_an_exception_indicating_a_conflict_when_trying_to_listen_on_a_used_prefix()
+ {
+ Exception ex;
+
+ // Given
+ using (CreateAndOpenSelfHost())
+ {
+ // When
+ ex = Record.Exception(() =>
+ {
+ using (var host = new NancyHost(BaseUri))
+ {
+ host.Start();
+ }
+ });
+ }
+
+ // Then
+ ex.Message.ShouldContain("conflict");
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_get_any_header_from_selfhost()
+ {
+ // Given
+ using (CreateAndOpenSelfHost())
+ {
+ // When
+ var request = WebRequest.Create(new Uri(BaseUri, "rel/header/?query=value"));
+ request.Method = "GET";
+
+ // Then
+ request.GetResponse().Headers["X-Some-Header"].ShouldEqual("Some value");
+ }
+ }
+
+ [SkippableFact]
+ public void Should_set_query_string_and_uri_correctly()
+ {
+ // Given
+ Request nancyRequest = null;
+ var fakeEngine = A.Fake();
+ A.CallTo(() => fakeEngine.HandleRequest(A.Ignored, A>.Ignored,A.Ignored))
+ .Invokes(f => nancyRequest = (Request)f.Arguments[0])
+ .ReturnsLazily(c => TaskHelpers.GetCompletedTask(new NancyContext { Request = (Request)c.Arguments[0], Response = new Response() }));
+
+ var fakeBootstrapper = A.Fake();
+ A.CallTo(() => fakeBootstrapper.GetEngine()).Returns(fakeEngine);
+
+ // When
+ using (CreateAndOpenSelfHost(fakeBootstrapper))
+ {
+ var request = WebRequest.Create(new Uri(BaseUri, "test/stuff?query=value&query2=value2"));
+ request.Method = "GET";
+
+ try
+ {
+ request.GetResponse();
+ }
+ catch (WebException)
+ {
+ // Will throw because it returns 404 - don't care.
+ }
+ }
+
+ // Then
+ nancyRequest.Path.ShouldEqual("/test/stuff");
+ Assert.True(nancyRequest.Query.query.HasValue);
+ Assert.True(nancyRequest.Query.query2.HasValue);
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_get_from_selfhost()
+ {
+ using (CreateAndOpenSelfHost())
+ {
+ var reader =
+ new StreamReader(WebRequest.Create(new Uri(BaseUri, "rel")).GetResponse().GetResponseStream());
+
+ var response = reader.ReadToEnd();
+
+ response.ShouldEqual("This is the site route");
+ }
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_get_from_chunked_selfhost()
+ {
+ using (CreateAndOpenSelfHost())
+ {
+ var response = WebRequest.Create(new Uri(BaseUri, "rel")).GetResponse();
+
+ Assert.Equal("chunked", response.Headers["Transfer-Encoding"]);
+ Assert.Equal(null, response.Headers["Content-Length"]);
+
+ using (var reader = new StreamReader(response.GetResponseStream()))
+ {
+ var contents = reader.ReadToEnd();
+ contents.ShouldEqual("This is the site route");
+ }
+ }
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_get_from_contentlength_selfhost()
+ {
+ HostConfiguration configuration = new HostConfiguration()
+ {
+ AllowChunkedEncoding = false
+ };
+ using (CreateAndOpenSelfHost(null, configuration))
+ {
+ var response = WebRequest.Create(new Uri(BaseUri, "rel")).GetResponse();
+
+ Assert.Equal(null, response.Headers["Transfer-Encoding"]);
+ Assert.Equal(22, Convert.ToInt32(response.Headers["Content-Length"]));
+
+ using (var reader = new StreamReader(response.GetResponseStream()))
+ {
+ var contents = reader.ReadToEnd();
+ contents.ShouldEqual("This is the site route");
+ }
+ }
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_post_body_to_selfhost()
+ {
+ using (CreateAndOpenSelfHost())
+ {
+ const string testBody = "This is the body of the request";
+
+ var request =
+ WebRequest.Create(new Uri(BaseUri, "rel"));
+ request.Method = "POST";
+
+ var writer =
+ new StreamWriter(request.GetRequestStream()) { AutoFlush = true };
+ writer.Write(testBody);
+
+ var responseBody =
+ new StreamReader(request.GetResponse().GetResponseStream()).ReadToEnd();
+
+ responseBody.ShouldEqual(testBody);
+ }
+ }
+
+ [SkippableFact]
+ public void Should_be_able_to_get_from_selfhost_with_slashless_uri()
+ {
+ using (CreateAndOpenSelfHost())
+ {
+ var reader =
+ new StreamReader(WebRequest.Create(BaseUri.ToString().TrimEnd('/')).GetResponse().GetResponseStream());
+
+ var response = reader.ReadToEnd();
+
+ response.ShouldEqual("This is the site home");
+ }
+ }
+
+ private static NancyHostWrapper CreateAndOpenSelfHost(INancyBootstrapper nancyBootstrapper = null, HostConfiguration configuration = null)
+ {
+ if (nancyBootstrapper == null)
+ {
+ nancyBootstrapper = new DefaultNancyBootstrapper();
+ }
+
+ var host = new NancyHost(
+ nancyBootstrapper,
+ configuration,
+ BaseUri);
+
+ try
+ {
+ host.Start();
+ }
+ catch
+ {
+ throw new SkipException("Skipped due to no Administrator access - please see test fixture for more information.");
+ }
+
+ return new NancyHostWrapper(host);
+ }
+
+
+ [SkippableFact]
+ public void Should_be_able_to_recover_from_rendering_exception()
+ {
+ using (CreateAndOpenSelfHost())
+ {
+
+ var reader =
+ new StreamReader(WebRequest.Create(new Uri(BaseUri, "exception")).GetResponse().GetResponseStream());
+
+ var response = reader.ReadToEnd();
+
+ response.ShouldEqual("Content");
+ }
+ }
+
+ [SkippableFact]
+ public void Should_be_serializable()
+ {
+ var type = typeof(NancyHost);
+ Assert.True(type.Attributes.ToString().Contains("Serializable"));
+ }
+
+ [Fact]
+ public void Should_include_default_port_in_uri_prefixes()
+ {
+ // Given
+ var host = new NancyHost(new Uri("http://localhost/"));
+
+ // When
+ var prefix = host.GetPrefixes().Single();
+
+ // Then
+ prefix.ShouldEqual("http://+:80/");
+ }
+
+ private class NancyHostWrapper : IDisposable
+ {
+ private readonly NancyHost host;
+
+ public NancyHostWrapper(NancyHost host)
+ {
+ this.host = host;
+ }
+
+ public void Dispose()
+ {
+ host.Stop();
+ }
+ }
+ }
+}
+#endif
diff --git a/src/Nancy.Hosting.Self.Tests/TestModule.cs b/src/Nancy.Hosting.Self.Tests/TestModule.cs
new file mode 100644
index 0000000000..463fb0eb32
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/TestModule.cs
@@ -0,0 +1,33 @@
+namespace Nancy.Hosting.Self.Tests
+{
+ using System;
+ using System.IO;
+
+ public class TestModule : NancyModule
+ {
+ public TestModule()
+ {
+ Get["/"] = parameters => "This is the site home";
+
+ Get["/rel"] = parameters => "This is the site route";
+
+ Get["/rel/header"] = parameters =>
+ {
+ var response = new Response();
+ response.Headers["X-Some-Header"] = "Some value";
+
+ return response;
+ };
+
+ Post["/rel"] = parameters => new StreamReader(this.Request.Body).ReadToEnd();
+
+ Get["/exception"] = parameters => new Response() {Contents = s =>
+ {
+ var writer = new StreamWriter(s);
+ writer.Write("Content");
+ writer.Flush();
+ throw new Exception("An error occured during content rendering");
+ }};
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self.Tests/packages.config b/src/Nancy.Hosting.Self.Tests/packages.config
new file mode 100644
index 0000000000..18f13949c6
--- /dev/null
+++ b/src/Nancy.Hosting.Self.Tests/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/Nancy.Hosting.Self/AutomaticUrlReservationCreationFailureException.cs b/src/Nancy.Hosting.Self/AutomaticUrlReservationCreationFailureException.cs
new file mode 100644
index 0000000000..f54c1f59f5
--- /dev/null
+++ b/src/Nancy.Hosting.Self/AutomaticUrlReservationCreationFailureException.cs
@@ -0,0 +1,52 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ ///
+ /// Exception for when automatic address reservation creation fails.
+ /// Provides the user with manual instructions.
+ ///
+ public class AutomaticUrlReservationCreationFailureException : Exception
+ {
+ private readonly IEnumerable prefixes;
+ private readonly string user;
+
+ public AutomaticUrlReservationCreationFailureException(IEnumerable prefixes, string user)
+ {
+ this.prefixes = prefixes;
+ this.user = user;
+ }
+
+ ///
+ /// Gets a message that describes the current exception.
+ ///
+ ///
+ /// The error message that explains the reason for the exception, or an empty string("").
+ ///
+ /// 1
+ public override string Message
+ {
+ get
+ {
+ var stringBuilder = new StringBuilder();
+
+ stringBuilder.AppendLine("The Nancy self host was unable to start, as no namespace reservation existed for the provided url(s).");
+ stringBuilder.AppendLine();
+
+ stringBuilder.AppendLine("Please either enable UrlReservations.CreateAutomatically on the HostConfiguration provided to ");
+ stringBuilder.AppendLine("the NancyHost, or create the reservations manually with the (elevated) command(s):");
+ stringBuilder.AppendLine();
+
+ foreach (var prefix in prefixes)
+ {
+ var command = NetSh.GetParameters(prefix, user);
+ stringBuilder.AppendLine(string.Format("netsh {0}", command));
+ }
+
+ return stringBuilder.ToString();
+ }
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self/FileSystemRootPathProvider.cs b/src/Nancy.Hosting.Self/FileSystemRootPathProvider.cs
new file mode 100644
index 0000000000..9fbf119cdb
--- /dev/null
+++ b/src/Nancy.Hosting.Self/FileSystemRootPathProvider.cs
@@ -0,0 +1,17 @@
+namespace Nancy.Hosting.Self
+{
+ using System.IO;
+ using System.Reflection;
+
+ public class FileSystemRootPathProvider : IRootPathProvider
+ {
+ public string GetRootPath()
+ {
+ var assembly = Assembly.GetEntryAssembly();
+
+ return assembly != null ?
+ Path.GetDirectoryName(assembly.Location) :
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self/HostConfiguration.cs b/src/Nancy.Hosting.Self/HostConfiguration.cs
new file mode 100644
index 0000000000..1007cf193a
--- /dev/null
+++ b/src/Nancy.Hosting.Self/HostConfiguration.cs
@@ -0,0 +1,70 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Diagnostics;
+
+ ///
+ /// Host configuration for the self host
+ ///
+ public sealed class HostConfiguration
+ {
+ ///
+ /// Gets or sets a property that determines if localhost uris are
+ /// rewritten to htp://+:port/ style uris to allow for listening on
+ /// all ports, but requiring either a url reservation, or admin
+ /// access
+ /// Defaults to true.
+ ///
+ public bool RewriteLocalhost { get; set; }
+
+ ///
+ /// Configuration around automatically creating url reservations
+ ///
+ public UrlReservations UrlReservations { get; set; }
+
+ ///
+ /// Gets or sets a property that determines if Transfer-Encoding: Chunked is allowed
+ /// for the response instead of Content-Length (default: true).
+ ///
+ public bool AllowChunkedEncoding { get; set; }
+
+ ///
+ /// Gets or sets a property that provides a callback to be called
+ /// if there's an unhandled exception in the self host.
+ /// Note: this will *not* be called for normal nancy Exceptions
+ /// that are handled by the Nancy handlers.
+ /// Defaults to writing to debug out.
+ ///
+ public Action UnhandledExceptionCallback { get; set; }
+
+ ///
+ /// Gets or sets a property that determines whether client certificates
+ /// are enabled or not.
+ /// When set to true the self host will request a client certificate if the
+ /// request is running over SSL.
+ /// Defaults to false.
+ ///
+ public bool EnableClientCertificates { get; set; }
+
+ ///
+ /// Gets or sets a property determining if base uri matching can fall back to just
+ /// using Authority (Schema + Host + Port) as base uri if it cannot match anything in
+ /// the known list. This should only be set to True if you have issues with port forwarding
+ /// (e.g. on Azure).
+ ///
+ public bool AllowAuthorityFallback { get; set; }
+
+ public HostConfiguration()
+ {
+ this.RewriteLocalhost = true;
+ this.UrlReservations = new UrlReservations();
+ this.AllowChunkedEncoding = true;
+ this.UnhandledExceptionCallback = e =>
+ {
+ var message = string.Format("---\n{0}\n---\n", e);
+ Debug.Write(message);
+ };
+ this.EnableClientCertificates = false;
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self/IgnoredHeaders.cs b/src/Nancy.Hosting.Self/IgnoredHeaders.cs
new file mode 100644
index 0000000000..4a6ea2da69
--- /dev/null
+++ b/src/Nancy.Hosting.Self/IgnoredHeaders.cs
@@ -0,0 +1,34 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// A helper class that checks for a header against a list of headers that should be ignored
+ /// when populating the headers of an object.
+ ///
+ public static class IgnoredHeaders
+ {
+
+ private static readonly HashSet knownHeaders = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "content-length",
+ "content-type",
+ "transfer-encoding",
+ "keep-alive"
+ };
+
+ ///
+ /// Determines if a header is ignored when populating the headers of an
+ /// object.
+ ///
+ /// The name of the header.
+ /// true if the header is ignored; otherwise, false.
+ public static bool IsIgnored(string headerName)
+ {
+ return knownHeaders.Contains(headerName);
+ }
+
+ }
+
+}
diff --git a/src/Nancy.Hosting.Self/Nancy.Hosting.Self.csproj b/src/Nancy.Hosting.Self/Nancy.Hosting.Self.csproj
new file mode 100644
index 0000000000..8ded63f0c9
--- /dev/null
+++ b/src/Nancy.Hosting.Self/Nancy.Hosting.Self.csproj
@@ -0,0 +1,161 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}
+ Library
+ Properties
+ Nancy.Hosting.Self
+ Nancy.Hosting.Self
+ v4.5
+ 512
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ bin\Debug\Nancy.Hosting.Self.XML
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ bin\Release\Nancy.Hosting.Self.XML
+ false
+
+
+ true
+ bin\MonoDebug\
+ DEBUG;TRACE
+ full
+ AnyCPU
+ bin\Debug\Nancy.Hosting.Self.dll.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ false
+ false
+ 4
+ false
+ bin\MonoDebug\Nancy.Hosting.Self.XML
+ false
+
+
+ bin\MonoRelease\
+ TRACE
+ true
+ pdbonly
+ AnyCPU
+ bin\Release\Nancy.Hosting.Self.dll.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ AllRules.ruleset
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets
+ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules
+ 4
+ bin\MonoRelease\Nancy.Hosting.Self.XML
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SharedAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+ {34576216-0DCA-4B0F-A0DC-9075E75A676F}
+ Nancy
+
+
+
+
+ False
+ Microsoft .NET Framework 4 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self/NancyHost.cs b/src/Nancy.Hosting.Self/NancyHost.cs
new file mode 100644
index 0000000000..314c5aed4d
--- /dev/null
+++ b/src/Nancy.Hosting.Self/NancyHost.cs
@@ -0,0 +1,451 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Security.Principal;
+
+ using Nancy.Bootstrapper;
+ using Nancy.Extensions;
+ using Nancy.Helpers;
+ using Nancy.IO;
+
+ ///
+ /// Allows to host Nancy server inside any application - console or windows service.
+ ///
+ ///
+ /// NancyHost uses internally. Therefore, it requires full .net 4.0 profile (not client profile)
+ /// to run. will launch a thread that will listen for requests and then process them. Each request is processed in
+ /// its own execution thread. NancyHost needs in order to be used from another appdomain under
+ /// mono. Working with AppDomains is necessary if you want to unload the dependencies that come with NancyHost.
+ ///
+ [Serializable]
+ public class NancyHost : IDisposable
+ {
+ private const int ACCESS_DENIED = 5;
+
+ private readonly IList baseUriList;
+ private HttpListener listener;
+ private readonly INancyEngine engine;
+ private readonly HostConfiguration configuration;
+ private readonly INancyBootstrapper bootstrapper;
+
+ ///
+ /// Initializes a new instance of the class for the specified .
+ /// Uses the default configuration
+ ///
+ /// The s that the host will listen to.
+ public NancyHost(params Uri[] baseUris)
+ : this(NancyBootstrapperLocator.Bootstrapper, new HostConfiguration(), baseUris) { }
+
+ ///
+ /// Initializes a new instance of the class for the specified .
+ /// Uses the specified configuration.
+ ///
+ /// The s that the host will listen to.
+ /// Configuration to use
+ public NancyHost(HostConfiguration configuration, params Uri[] baseUris)
+ : this(NancyBootstrapperLocator.Bootstrapper, configuration, baseUris){}
+
+ ///
+ /// Initializes a new instance of the class for the specified , using
+ /// the provided .
+ /// Uses the default configuration
+ ///
+ /// The bootstrapper that should be used to handle the request.
+ /// The s that the host will listen to.
+ public NancyHost(INancyBootstrapper bootstrapper, params Uri[] baseUris)
+ : this(bootstrapper, new HostConfiguration(), baseUris)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class for the specified , using
+ /// the provided .
+ /// Uses the specified configuration.
+ ///
+ /// The bootstrapper that should be used to handle the request.
+ /// Configuration to use
+ /// The s that the host will listen to.
+ public NancyHost(INancyBootstrapper bootstrapper, HostConfiguration configuration, params Uri[] baseUris)
+ {
+ this.bootstrapper = bootstrapper;
+ this.configuration = configuration ?? new HostConfiguration();
+ this.baseUriList = baseUris;
+
+ bootstrapper.Initialise();
+ this.engine = bootstrapper.GetEngine();
+ }
+
+ ///
+ /// Initializes a new instance of the class for the specified , using
+ /// the provided .
+ /// Uses the default configuration
+ ///
+ /// The that the host will listen to.
+ /// The bootstrapper that should be used to handle the request.
+ public NancyHost(Uri baseUri, INancyBootstrapper bootstrapper)
+ : this(bootstrapper, new HostConfiguration(), baseUri)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class for the specified , using
+ /// the provided .
+ /// Uses the specified configuration.
+ ///
+ /// The that the host will listen to.
+ /// The bootstrapper that should be used to handle the request.
+ /// Configuration to use
+ public NancyHost(Uri baseUri, INancyBootstrapper bootstrapper, HostConfiguration configuration)
+ : this (bootstrapper, configuration, baseUri)
+ {
+ }
+
+ ///
+ /// Stops the host if it is running.
+ ///
+ public void Dispose()
+ {
+ this.Stop();
+
+ this.bootstrapper.Dispose();
+ }
+
+ ///
+ /// Start listening for incoming requests with the given configuration
+ ///
+ public void Start()
+ {
+ this.StartListener();
+
+ try
+ {
+ this.listener.BeginGetContext(this.GotCallback, null);
+ }
+ catch (Exception e)
+ {
+ this.configuration.UnhandledExceptionCallback.Invoke(e);
+
+ throw;
+ }
+ }
+
+ private void StartListener()
+ {
+ if (this.TryStartListener())
+ {
+ return;
+ }
+
+ if (!this.configuration.UrlReservations.CreateAutomatically)
+ {
+ throw new AutomaticUrlReservationCreationFailureException(this.GetPrefixes(), this.GetUser());
+ }
+
+ if (!this.TryAddUrlReservations())
+ {
+ throw new InvalidOperationException("Unable to configure namespace reservation");
+ }
+
+ if (!TryStartListener())
+ {
+ throw new InvalidOperationException("Unable to start listener");
+ }
+ }
+
+ private bool TryStartListener()
+ {
+ try
+ {
+ // if the listener fails to start, it gets disposed;
+ // so we need a new one, each time.
+ this.listener = new HttpListener();
+ foreach (var prefix in this.GetPrefixes())
+ {
+ this.listener.Prefixes.Add(prefix);
+ }
+
+ this.listener.Start();
+
+ return true;
+ }
+ catch (HttpListenerException e)
+ {
+ if (e.ErrorCode == ACCESS_DENIED)
+ {
+ return false;
+ }
+
+ throw;
+ }
+ }
+
+ private bool TryAddUrlReservations()
+ {
+ var user = this.GetUser();
+
+ foreach (var prefix in this.GetPrefixes())
+ {
+ if (!NetSh.AddUrlAcl(prefix, user))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private string GetUser()
+ {
+ if (!string.IsNullOrWhiteSpace(this.configuration.UrlReservations.User))
+ {
+ return this.configuration.UrlReservations.User;
+ }
+
+ return WindowsIdentity.GetCurrent().Name;
+ }
+
+ ///
+ /// Stop listening for incoming requests.
+ ///
+ public void Stop()
+ {
+ if (this.listener.IsListening)
+ {
+ listener.Stop();
+ }
+ }
+
+ internal IEnumerable GetPrefixes()
+ {
+ foreach (var baseUri in this.baseUriList)
+ {
+ var prefix = new UriBuilder(baseUri).ToString();
+
+ if (this.configuration.RewriteLocalhost && !baseUri.Host.Contains("."))
+ {
+ prefix = prefix.Replace("localhost", "+");
+ }
+
+ yield return prefix;
+ }
+ }
+
+ private Request ConvertRequestToNancyRequest(HttpListenerRequest request)
+ {
+ var baseUri = this.GetBaseUri(request);
+
+ if (baseUri == null)
+ {
+ throw new InvalidOperationException(String.Format("Unable to locate base URI for request: {0}",request.Url));
+ }
+
+ var expectedRequestLength =
+ GetExpectedRequestLength(request.Headers.ToDictionary());
+
+ var relativeUrl = baseUri.MakeAppLocalPath(request.Url);
+
+ var nancyUrl = new Url
+ {
+ Scheme = request.Url.Scheme,
+ HostName = request.Url.Host,
+ Port = request.Url.IsDefaultPort ? null : (int?)request.Url.Port,
+ BasePath = baseUri.AbsolutePath.TrimEnd('/'),
+ Path = HttpUtility.UrlDecode(relativeUrl),
+ Query = request.Url.Query,
+ };
+
+ byte[] certificate = null;
+
+ if (this.configuration.EnableClientCertificates)
+ {
+ var x509Certificate = request.GetClientCertificate();
+
+ if (x509Certificate != null)
+ {
+ certificate = x509Certificate.RawData;
+ }
+ }
+
+ // NOTE: For HTTP/2 we want fieldCount = 1,
+ // otherwise (HTTP/1.0 and HTTP/1.1) we want fieldCount = 2
+ var fieldCount = request.ProtocolVersion.Major == 2 ? 1 : 2;
+
+ var protocolVersion = string.Format("HTTP/{0}", request.ProtocolVersion.ToString(fieldCount));
+
+ return new Request(
+ request.HttpMethod,
+ nancyUrl,
+ RequestStream.FromStream(request.InputStream, expectedRequestLength, StaticConfiguration.DisableRequestStreamSwitching ?? false),
+ request.Headers.ToDictionary(),
+ (request.RemoteEndPoint != null) ? request.RemoteEndPoint.Address.ToString() : null,
+ certificate,
+ protocolVersion);
+ }
+
+ private Uri GetBaseUri(HttpListenerRequest request)
+ {
+ var result = this.baseUriList.FirstOrDefault(uri => uri.IsCaseInsensitiveBaseOf(request.Url));
+
+ if (result != null)
+ {
+ return result;
+ }
+
+ if (!this.configuration.AllowAuthorityFallback)
+ {
+ return null;
+ }
+
+ return new Uri(request.Url.GetLeftPart(UriPartial.Authority));
+ }
+
+ private void ConvertNancyResponseToResponse(Response nancyResponse, HttpListenerResponse response)
+ {
+ foreach (var header in nancyResponse.Headers)
+ {
+ if (!IgnoredHeaders.IsIgnored(header.Key))
+ {
+ response.AddHeader(header.Key, header.Value);
+ }
+ }
+
+ foreach (var nancyCookie in nancyResponse.Cookies)
+ {
+ response.Headers.Add(HttpResponseHeader.SetCookie, nancyCookie.ToString());
+ }
+
+ if (nancyResponse.ReasonPhrase != null)
+ {
+ response.StatusDescription = nancyResponse.ReasonPhrase;
+ }
+
+ if (nancyResponse.ContentType != null)
+ {
+ response.ContentType = nancyResponse.ContentType;
+ }
+
+ response.StatusCode = (int)nancyResponse.StatusCode;
+
+ if (configuration.AllowChunkedEncoding)
+ {
+ OutputWithDefaultTransferEncoding(nancyResponse, response);
+ }
+ else
+ {
+ OutputWithContentLength(nancyResponse, response);
+ }
+ }
+
+ private static void OutputWithDefaultTransferEncoding(Response nancyResponse, HttpListenerResponse response)
+ {
+ using (var output = response.OutputStream)
+ {
+ nancyResponse.Contents.Invoke(output);
+ }
+ }
+
+ private static void OutputWithContentLength(Response nancyResponse, HttpListenerResponse response)
+ {
+ byte[] buffer;
+ using (var memoryStream = new MemoryStream())
+ {
+ nancyResponse.Contents.Invoke(memoryStream);
+ buffer = memoryStream.ToArray();
+ }
+
+ var contentLength = (nancyResponse.Headers.ContainsKey("Content-Length")) ?
+ Convert.ToInt64(nancyResponse.Headers["Content-Length"]) :
+ buffer.Length;
+
+ response.SendChunked = false;
+ response.ContentLength64 = contentLength;
+
+ using (var output = response.OutputStream)
+ {
+ using (var writer = new BinaryWriter(output))
+ {
+ writer.Write(buffer);
+ writer.Flush();
+ }
+ }
+ }
+
+ private static long GetExpectedRequestLength(IDictionary> incomingHeaders)
+ {
+ if (incomingHeaders == null)
+ {
+ return 0;
+ }
+
+ if (!incomingHeaders.ContainsKey("Content-Length"))
+ {
+ return 0;
+ }
+
+ var headerValue =
+ incomingHeaders["Content-Length"].SingleOrDefault();
+
+ if (headerValue == null)
+ {
+ return 0;
+ }
+
+ long contentLength;
+
+ return !long.TryParse(headerValue, NumberStyles.Any, CultureInfo.InvariantCulture, out contentLength) ?
+ 0 :
+ contentLength;
+ }
+
+ private void GotCallback(IAsyncResult ar)
+ {
+ try
+ {
+ var ctx = this.listener.EndGetContext(ar);
+ this.listener.BeginGetContext(this.GotCallback, null);
+ this.Process(ctx);
+ }
+ catch (Exception e)
+ {
+ this.configuration.UnhandledExceptionCallback.Invoke(e);
+
+ try
+ {
+ this.listener.BeginGetContext(this.GotCallback, null);
+ }
+ catch
+ {
+ this.configuration.UnhandledExceptionCallback.Invoke(e);
+ }
+ }
+ }
+
+ private void Process(HttpListenerContext ctx)
+ {
+ try
+ {
+ var nancyRequest = this.ConvertRequestToNancyRequest(ctx.Request);
+ using (var nancyContext = this.engine.HandleRequest(nancyRequest))
+ {
+ try
+ {
+ ConvertNancyResponseToResponse(nancyContext.Response, ctx.Response);
+ }
+ catch (Exception e)
+ {
+ this.configuration.UnhandledExceptionCallback.Invoke(e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ this.configuration.UnhandledExceptionCallback.Invoke(e);
+ }
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self/NetSh.cs b/src/Nancy.Hosting.Self/NetSh.cs
new file mode 100644
index 0000000000..b86d45ce1c
--- /dev/null
+++ b/src/Nancy.Hosting.Self/NetSh.cs
@@ -0,0 +1,37 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+
+ ///
+ /// Executes NetSh commands
+ ///
+ public static class NetSh
+ {
+ private const string NetshCommand = "netsh";
+
+ ///
+ /// Add a url reservation
+ ///
+ /// Url to add
+ /// User to add the reservation for
+ /// True if successful, false otherwise.
+ public static bool AddUrlAcl(string url, string user)
+ {
+ try
+ {
+ var arguments = GetParameters(url, user);
+
+ return UacHelper.RunElevated(NetshCommand, arguments);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ internal static string GetParameters(string url, string user)
+ {
+ return string.Format("http add urlacl url=\"{0}\" user=\"{1}\"", url, user);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self/UacHelper.cs b/src/Nancy.Hosting.Self/UacHelper.cs
new file mode 100644
index 0000000000..62c1c296d2
--- /dev/null
+++ b/src/Nancy.Hosting.Self/UacHelper.cs
@@ -0,0 +1,39 @@
+namespace Nancy.Hosting.Self
+{
+ using System.Diagnostics;
+
+ ///
+ /// Helpers for UAC
+ ///
+ public static class UacHelper
+ {
+ ///
+ /// Run an executable elevated
+ ///
+ /// File to execute
+ /// Arguments to pass to the executable
+ /// True if successful, false otherwise
+ public static bool RunElevated(string file, string args)
+ {
+ var process = CreateProcess(args, file);
+
+ process.Start();
+ process.WaitForExit();
+
+ return process.ExitCode == 0;
+ }
+
+ private static Process CreateProcess(string args, string file)
+ {
+ return new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ Verb = "runas",
+ Arguments = args,
+ FileName = file,
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self/UriExtensions.cs b/src/Nancy.Hosting.Self/UriExtensions.cs
new file mode 100644
index 0000000000..a7e0ca7fdf
--- /dev/null
+++ b/src/Nancy.Hosting.Self/UriExtensions.cs
@@ -0,0 +1,129 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ ///
+ /// Extension methods for working with instances.
+ ///
+ public static class UriExtensions
+ {
+ public static bool IsCaseInsensitiveBaseOf(this Uri source, Uri value)
+ {
+ var uriComponents = source.Host == "localhost" ? (UriComponents.Port | UriComponents.Scheme) : (UriComponents.HostAndPort | UriComponents.Scheme);
+ if (Uri.Compare(source, value, uriComponents, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
+ {
+ return false;
+ }
+
+ var sourceSegments = source.Segments;
+ var valueSegments = value.Segments;
+
+ return sourceSegments.ZipCompare(valueSegments, (s1, s2) => s1.Length == 0 || SegmentEquals(s1, s2));
+ }
+
+ public static string MakeAppLocalPath(this Uri appBaseUri, Uri fullUri)
+ {
+ return string.Concat("/", appBaseUri.Segments.ZipFill(fullUri.Segments, (x, y) => x != null && SegmentEquals(x, y) ? null : y).Join());
+ }
+
+ private static string AppendSlashIfNeeded(string segment)
+ {
+ if (!segment.EndsWith("/"))
+ {
+ segment = string.Concat(segment, "/");
+ }
+
+ return segment;
+ }
+
+ private static bool SegmentEquals(string segment1, string segment2)
+ {
+ return String.Equals(AppendSlashIfNeeded(segment1), AppendSlashIfNeeded(segment2), StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool ZipCompare(this IEnumerable source1, IEnumerable source2, Func comparison)
+ {
+ using (var enumerator1 = source1.GetEnumerator())
+ {
+ using (var enumerator2 = source2.GetEnumerator())
+ {
+ var has1 = enumerator1.MoveNext();
+ var has2 = enumerator2.MoveNext();
+
+ while (has1 || has2)
+ {
+ var current1 = has1 ? enumerator1.Current : "";
+ var current2 = has2 ? enumerator2.Current : "";
+
+ if (!comparison(current1, current2))
+ {
+ return false;
+ }
+
+ if (has1)
+ {
+ has1 = enumerator1.MoveNext();
+ }
+
+ if (has2)
+ {
+ has2 = enumerator2.MoveNext();
+ }
+ }
+
+ }
+ }
+
+ return true;
+ }
+
+ private static IEnumerable ZipFill(this IEnumerable source1, IEnumerable source2, Func selector)
+ {
+ using (var enumerator1 = source1.GetEnumerator())
+ {
+ using (var enumerator2 = source2.GetEnumerator())
+ {
+ var has1 = enumerator1.MoveNext();
+ var has2 = enumerator2.MoveNext();
+
+ while (has1 || has2)
+ {
+ var value1 = has1 ? enumerator1.Current : null;
+ var value2 = has2 ? enumerator2.Current : null;
+ var value = selector(value1, value2);
+
+ if (value != null)
+ {
+ yield return value;
+ }
+
+ if (has1)
+ {
+ has1 = enumerator1.MoveNext();
+ }
+
+ if (has2)
+ {
+ has2 = enumerator2.MoveNext();
+ }
+ }
+
+ }
+ }
+ }
+
+ private static string Join(this IEnumerable source)
+ {
+ var builder = new StringBuilder();
+
+ foreach (var value in source)
+ {
+ builder.Append(value);
+ }
+
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/Nancy.Hosting.Self/UrlReservations.cs b/src/Nancy.Hosting.Self/UrlReservations.cs
new file mode 100644
index 0000000000..5ed49734f7
--- /dev/null
+++ b/src/Nancy.Hosting.Self/UrlReservations.cs
@@ -0,0 +1,53 @@
+namespace Nancy.Hosting.Self
+{
+ using System;
+ using System.Security.Principal;
+
+ ///
+ /// Configuration for automatic url reservation creation
+ ///
+ public class UrlReservations
+ {
+ private const string EveryoneAccountName = "Everyone";
+
+ private static readonly IdentityReference EveryoneReference =
+ new SecurityIdentifier(WellKnownSidType.WorldSid, null);
+
+ public UrlReservations()
+ {
+ this.CreateAutomatically = false;
+ this.User = GetEveryoneAccountName();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether url reservations
+ /// are automatically created when necessary.
+ /// Defaults to false.
+ ///
+ public bool CreateAutomatically { get; set; }
+
+ ///
+ /// Gets or sets a value for the user to use to create the url reservations for.
+ /// Defaults to the "Everyone" group.
+ ///
+ public string User { get; set; }
+
+ private static string GetEveryoneAccountName()
+ {
+ try
+ {
+ var account = EveryoneReference.Translate(typeof(NTAccount)) as NTAccount;
+ if (account != null)
+ {
+ return account.Value;
+ }
+
+ return EveryoneAccountName;
+ }
+ catch (Exception)
+ {
+ return EveryoneAccountName;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Nancy.Hosting.Self/nancy.hosting.self.nuspec b/src/Nancy.Hosting.Self/nancy.hosting.self.nuspec
new file mode 100644
index 0000000000..e6de7acd6b
--- /dev/null
+++ b/src/Nancy.Hosting.Self/nancy.hosting.self.nuspec
@@ -0,0 +1,26 @@
+
+
+
+ Nancy.Hosting.Self
+ 0.0.0
+ Andreas Håkansson, Steven Robbins and contributors
+ false
+ Enables hosting Nancy in any application.
+ Nancy is a lightweight web framework for the .Net platform, inspired by Sinatra. Nancy aim at delivering a low ceremony approach to building light, fast web applications.
+ en-US
+ Andreas Håkansson, Steven Robbins and contributors
+ http://nancyfx.org/nancy-nuget.png
+ https://github.com/NancyFx/Nancy/blob/master/license.txt
+ http://nancyfx.org
+
+
+
+ Nancy
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Nancy.sln b/src/Nancy.sln
index a10fe748de..c207c992ff 100644
--- a/src/Nancy.sln
+++ b/src/Nancy.sln
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Hosting.Aspnet", "Nan
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Authentication.Forms", "Nancy.Authentication.Forms\Nancy.Authentication.Forms.csproj", "{E8B18958-7C8A-4FBA-AF00-3041C34A20CE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Hosting.Self", "Nancy.Hosting.Self\Nancy.Hosting.Self.csproj", "{AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Testing", "Nancy.Testing\Nancy.Testing.csproj", "{D79203C0-B672-4751-9C95-C3AB7D3FEFBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.ViewEngines.DotLiquid", "Nancy.ViewEngines.DotLiquid\Nancy.ViewEngines.DotLiquid.csproj", "{B795886D-9D70-45B1-BFB5-AD54CBC9A447}"
@@ -53,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Bootstrapping.As
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Caching", "Nancy.Demo.Caching\Nancy.Demo.Caching.csproj", "{28F9EA8B-90F7-4974-BB40-0B7FA9309D02}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Hosting.Self", "Nancy.Demo.Hosting.Self\Nancy.Demo.Hosting.Self.csproj", "{0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Hosting.Aspnet", "Nancy.Demo.Hosting.Aspnet\Nancy.Demo.Hosting.Aspnet.csproj", "{E127FED3-01C0-41BA-BF83-D8DCDD827D6A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Authentication.Forms.TestingDemo", "Nancy.Demo.Authentication.Forms.TestingDemo\Nancy.Demo.Authentication.Forms.TestingDemo.csproj", "{948A8EF6-D50C-45EA-9AFD-7A4723ADAB0B}"
@@ -81,6 +85,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.ViewEngines.Razor.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.ViewEngines.Razor.BuildProviders", "Nancy.ViewEngines.Razor.BuildProviders\Nancy.ViewEngines.Razor.BuildProviders.csproj", "{EDF3E264-2D0F-4440-99FF-45D279A598A9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Hosting.Self.Tests", "Nancy.Hosting.Self.Tests\Nancy.Hosting.Self.Tests.csproj", "{CA24ED85-DD68-4C10-B80A-D81C6745FCB8}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Authentication.Stateless", "Nancy.Authentication.Stateless\Nancy.Authentication.Stateless.csproj", "{211560C3-FDDF-46D6-AB0C-F3BC04B173B5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Authentication.Stateless.Website", "Nancy.Demo.Authentication.Stateless.Website\Nancy.Demo.Authentication.Stateless.Website.csproj", "{B5E3586D-81DE-49C3-83BC-062684795127}"
@@ -189,6 +195,18 @@ Global
{E8B18958-7C8A-4FBA-AF00-3041C34A20CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8B18958-7C8A-4FBA-AF00-3041C34A20CE}.Release|Any CPU.Build.0 = Release|Any CPU
{E8B18958-7C8A-4FBA-AF00-3041C34A20CE}.Release|x86.ActiveCfg = Release|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoDebug|x86.ActiveCfg = MonoDebug|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.MonoRelease|x86.ActiveCfg = MonoRelease|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}.Release|x86.ActiveCfg = Release|Any CPU
{D79203C0-B672-4751-9C95-C3AB7D3FEFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D79203C0-B672-4751-9C95-C3AB7D3FEFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D79203C0-B672-4751-9C95-C3AB7D3FEFBE}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -379,6 +397,22 @@ Global
{28F9EA8B-90F7-4974-BB40-0B7FA9309D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28F9EA8B-90F7-4974-BB40-0B7FA9309D02}.Release|Any CPU.Build.0 = Release|Any CPU
{28F9EA8B-90F7-4974-BB40-0B7FA9309D02}.Release|x86.ActiveCfg = Release|Any CPU
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Debug|Any CPU.Build.0 = Debug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Debug|x86.ActiveCfg = Debug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Debug|x86.Build.0 = Debug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoDebug|Any CPU.Build.0 = MonoDebug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoDebug|x86.ActiveCfg = MonoDebug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoDebug|x86.Build.0 = MonoDebug|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoRelease|Any CPU.Build.0 = MonoRelease|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoRelease|x86.ActiveCfg = MonoRelease|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.MonoRelease|x86.Build.0 = MonoRelease|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Release|Any CPU.ActiveCfg = Release|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Release|Any CPU.Build.0 = Release|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Release|x86.ActiveCfg = Release|x86
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1}.Release|x86.Build.0 = Release|x86
{E127FED3-01C0-41BA-BF83-D8DCDD827D6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E127FED3-01C0-41BA-BF83-D8DCDD827D6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E127FED3-01C0-41BA-BF83-D8DCDD827D6A}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -547,6 +581,18 @@ Global
{EDF3E264-2D0F-4440-99FF-45D279A598A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDF3E264-2D0F-4440-99FF-45D279A598A9}.Release|Any CPU.Build.0 = Release|Any CPU
{EDF3E264-2D0F-4440-99FF-45D279A598A9}.Release|x86.ActiveCfg = Release|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoDebug|x86.ActiveCfg = Debug|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.MonoRelease|x86.ActiveCfg = Release|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8}.Release|x86.ActiveCfg = Release|Any CPU
{211560C3-FDDF-46D6-AB0C-F3BC04B173B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{211560C3-FDDF-46D6-AB0C-F3BC04B173B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211560C3-FDDF-46D6-AB0C-F3BC04B173B5}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -773,6 +819,7 @@ Global
{4B7E35DF-1569-4346-B180-A09615723095} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{15B7F794-0BB2-4B66-AD78-4A951F1209B2} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{E8B18958-7C8A-4FBA-AF00-3041C34A20CE} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
+ {AA7F66EB-EC2C-47DE-855F-30B3E6EF2134} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{D79203C0-B672-4751-9C95-C3AB7D3FEFBE} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{B795886D-9D70-45B1-BFB5-AD54CBC9A447} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{BD72B98D-C81A-4013-B606-94B4BA2273E5} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
@@ -789,6 +836,7 @@ Global
{98940A30-1B48-4F71-A6BA-85F0AAF31A2F} = {4A24657F-9695-437B-9702-2541ED280628}
{EF660223-4DFD-4E36-BF36-9DD6AFB3F837} = {4A24657F-9695-437B-9702-2541ED280628}
{28F9EA8B-90F7-4974-BB40-0B7FA9309D02} = {4A24657F-9695-437B-9702-2541ED280628}
+ {0B3EA40E-F7D8-4E14-A30F-1536F41B62D1} = {4A24657F-9695-437B-9702-2541ED280628}
{E127FED3-01C0-41BA-BF83-D8DCDD827D6A} = {4A24657F-9695-437B-9702-2541ED280628}
{948A8EF6-D50C-45EA-9AFD-7A4723ADAB0B} = {4A24657F-9695-437B-9702-2541ED280628}
{1258BFCD-3BAD-4373-B786-4D698EC3C157} = {4A24657F-9695-437B-9702-2541ED280628}
@@ -803,6 +851,7 @@ Global
{FBC35268-377B-4DBE-87E3-B22D1314BEC6} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3}
{3F18F5DA-93E0-4513-8BF4-BC8EE5C4117C} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3}
{EDF3E264-2D0F-4440-99FF-45D279A598A9} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
+ {CA24ED85-DD68-4C10-B80A-D81C6745FCB8} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3}
{211560C3-FDDF-46D6-AB0C-F3BC04B173B5} = {E944109B-0B7A-4ADE-8602-004CEFA5897D}
{B5E3586D-81DE-49C3-83BC-062684795127} = {4A24657F-9695-437B-9702-2541ED280628}
{BAE74CD5-57C2-40E3-8F7A-EDE5721C2ACC} = {4A24657F-9695-437B-9702-2541ED280628}