diff --git a/src/NzbDrone.Api/Extensions/AccessControlHeaders.cs b/src/NzbDrone.Api/Extensions/AccessControlHeaders.cs
new file mode 100644
index 000000000..5a32395cb
--- /dev/null
+++ b/src/NzbDrone.Api/Extensions/AccessControlHeaders.cs
@@ -0,0 +1,12 @@
+namespace NzbDrone.Api.Extensions
+{
+ public static class AccessControlHeaders
+ {
+ public const string RequestMethod = "Access-Control-Request-Method";
+ public const string RequestHeaders = "Access-Control-Request-Headers";
+
+ public const string AllowOrigin = "Access-Control-Allow-Origin";
+ public const string AllowMethods = "Access-Control-Allow-Methods";
+ public const string AllowHeaders = "Access-Control-Allow-Headers";
+ }
+}
diff --git a/src/NzbDrone.Api/Extensions/Pipelines/CorsPipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/CorsPipeline.cs
new file mode 100644
index 000000000..028be1dd5
--- /dev/null
+++ b/src/NzbDrone.Api/Extensions/Pipelines/CorsPipeline.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Nancy;
+using Nancy.Bootstrapper;
+
+namespace NzbDrone.Api.Extensions.Pipelines
+{
+ public class CorsPipeline : IRegisterNancyPipeline
+ {
+ public void Register(IPipelines pipelines)
+ {
+ pipelines.AfterRequest.AddItemToEndOfPipeline(Handle);
+ }
+
+ private void Handle(NancyContext context)
+ {
+ if (context == null || context.Response.Headers.ContainsKey(AccessControlHeaders.AllowOrigin))
+ {
+ return;
+ }
+
+ ApplyResponseHeaders(context.Response, context.Request);
+ }
+
+ private static void ApplyResponseHeaders(Response response, Request request)
+ {
+ var allowedMethods = "GET, OPTIONS, PATCH, POST, PUT, DELETE";
+
+ if (response.Headers.ContainsKey("Allow"))
+ {
+ allowedMethods = response.Headers["Allow"];
+ }
+
+ var requestedHeaders = String.Join(", ", request.Headers[AccessControlHeaders.RequestHeaders]);
+
+ response.Headers.Add(AccessControlHeaders.AllowOrigin, "*");
+ response.Headers.Add(AccessControlHeaders.AllowMethods, allowedMethods);
+
+ if (request.Headers[AccessControlHeaders.RequestHeaders].Any())
+ {
+ response.Headers.Add(AccessControlHeaders.AllowHeaders, requestedHeaders);
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index 36d1be281..aa4e9dbc3 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -96,6 +96,8 @@
+
+
diff --git a/src/NzbDrone.Integration.Test/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs
index 68c738009..5d09a7373 100644
--- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs
+++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs
@@ -45,7 +45,6 @@ namespace NzbDrone.Integration.Test.Client
request.AddParameter("sortKey", sortKey);
request.AddParameter("sortDir", sortDir);
return Get>(request);
-
}
public TResource Post(TResource body, HttpStatusCode statusCode = HttpStatusCode.Created)
diff --git a/src/NzbDrone.Integration.Test/CorsFixture.cs b/src/NzbDrone.Integration.Test/CorsFixture.cs
new file mode 100644
index 000000000..2d9d8ac4f
--- /dev/null
+++ b/src/NzbDrone.Integration.Test/CorsFixture.cs
@@ -0,0 +1,57 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Api.Extensions;
+using RestSharp;
+
+namespace NzbDrone.Integration.Test
+{
+ [TestFixture]
+ public class CorsFixture : IntegrationTest
+ {
+ private RestRequest BuildRequest()
+ {
+ var request = new RestRequest("series");
+ request.AddHeader(AccessControlHeaders.RequestMethod, "POST");
+
+ return request;
+ }
+
+ [Test]
+ public void should_not_have_allow_headers_in_response_when_not_included_in_the_request()
+ {
+ var request = BuildRequest();
+ var response = RestClient.Get(request);
+
+ response.Headers.Should().NotContain(h => h.Name == AccessControlHeaders.AllowHeaders);
+ }
+
+ [Test]
+ public void should_have_allow_headers_in_response_when_included_in_the_request()
+ {
+ var request = BuildRequest();
+ request.AddHeader(AccessControlHeaders.RequestHeaders, "X-Test");
+
+ var response = RestClient.Get(request);
+
+ response.Headers.Should().Contain(h => h.Name == AccessControlHeaders.AllowHeaders);
+ }
+
+ [Test]
+ public void should_have_allow_origin_in_response()
+ {
+ var request = BuildRequest();
+ var response = RestClient.Get(request);
+
+ response.Headers.Should().Contain(h => h.Name == AccessControlHeaders.AllowOrigin);
+ }
+
+ [Test]
+ public void should_have_allow_methods_in_response()
+ {
+ var request = BuildRequest();
+ var response = RestClient.Get(request);
+
+ response.Headers.Should().Contain(h => h.Name == AccessControlHeaders.AllowMethods);
+ }
+ }
+}
diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs
index 3a1b16446..82ba4a148 100644
--- a/src/NzbDrone.Integration.Test/IntegrationTest.cs
+++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs
@@ -63,7 +63,6 @@ namespace NzbDrone.Integration.Test
}
[TestFixtureSetUp]
- //[SetUp]
public void SmokeTestSetup()
{
_runner = new NzbDroneRunner();
@@ -87,6 +86,8 @@ namespace NzbDrone.Integration.Test
private void InitRestClients()
{
RestClient = new RestClient(RootUrl + "api/");
+ RestClient.AddDefaultHeader("Authentication", _runner.ApiKey);
+
Series = new SeriesClient(RestClient, _runner.ApiKey);
Releases = new ReleaseClient(RestClient, _runner.ApiKey);
RootFolders = new ClientBase(RestClient, _runner.ApiKey);
@@ -99,7 +100,6 @@ namespace NzbDrone.Integration.Test
}
[TestFixtureTearDown]
- //[TearDown]
public void SmokeTestTearDown()
{
_runner.KillAll();
@@ -153,6 +153,5 @@ namespace NzbDrone.Integration.Test
_signalrConnection.Received += json => _signalRReceived.Add(Json.Deserialize(json)); ;
}
-
}
}
diff --git a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
index 1c4bfc2bb..e22605994 100644
--- a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
+++ b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
@@ -109,6 +109,7 @@
+