Files
2018-04-11 09:31:39 -07:00

433 lines
22 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.Cors
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.Cors;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public sealed class CorsHandlerTest
{
[Fact]
public void NonCorsRequest()
{
IHttpResponse response = SimpleRequest(CorsConfigBuilder.ForAnyOrigin().Build(), null);
Assert.False(response.Headers.Contains(HttpHeaderNames.AccessControlAllowOrigin));
}
[Fact]
public void SimpleRequestWithAnyOrigin()
{
IHttpResponse response = SimpleRequest(CorsConfigBuilder.ForAnyOrigin().Build(), "http://localhost:7777");
Assert.Equal("*", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null).ToString());
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
}
[Fact]
public void SimpleRequestWithNullOrigin()
{
IHttpResponse response = SimpleRequest(CorsConfigBuilder.ForOrigin((AsciiString)"http://test.com")
.AllowNullOrigin()
.AllowCredentials()
.Build(), "null");
Assert.Equal("null", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null).ToString());
Assert.Equal("true", response.Headers.Get(HttpHeaderNames.AccessControlAllowCredentials, null).ToString());
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
}
[Fact]
public void SimpleRequestWithOrigin()
{
var origin = new AsciiString("http://localhost:8888");
IHttpResponse response = SimpleRequest(CorsConfigBuilder.ForOrigin(origin).Build(), origin.ToString());
Assert.Equal(origin, response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
}
[Fact]
public void SimpleRequestWithOrigins()
{
var origin1 = new AsciiString("http://localhost:8888");
var origin2 = new AsciiString("https://localhost:8888");
ICharSequence[] origins = { origin1, origin2};
IHttpResponse response1 = SimpleRequest(CorsConfigBuilder.ForOrigins(origins).Build(), origin1.ToString());
Assert.Equal(origin1, response1.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Null(response1.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
IHttpResponse response2 = SimpleRequest(CorsConfigBuilder.ForOrigins(origins).Build(), origin2.ToString());
Assert.Equal(origin2, response2.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Null(response2.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
}
[Fact]
public void SimpleRequestWithNoMatchingOrigin()
{
var origin = new AsciiString("http://localhost:8888");
IHttpResponse response = SimpleRequest(CorsConfigBuilder.ForOrigins(
new AsciiString("https://localhost:8888")).Build(), origin.ToString());
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowHeaders, null));
}
[Fact]
public void PreflightDeleteRequestWithCustomHeaders()
{
CorsConfig config = CorsConfigBuilder.ForOrigin(
new AsciiString("http://localhost:8888")).AllowedRequestMethods(HttpMethod.Get, HttpMethod.Delete).Build();
IHttpResponse response = PreflightRequest(config, "http://localhost:8888", "content-type, xheader1");
Assert.Equal("http://localhost:8888", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Contains("GET", response.Headers.Get(HttpHeaderNames.AccessControlAllowMethods, null).ToString());
Assert.Contains("DELETE", response.Headers.Get(HttpHeaderNames.AccessControlAllowMethods, null).ToString());
Assert.Equal(HttpHeaderNames.Origin.ToString(), response.Headers.Get(HttpHeaderNames.Vary, null));
}
[Fact]
public void PreflightRequestWithCustomHeaders()
{
const string HeaderName = "CustomHeader";
const string Value1 = "value1";
const string Value2 = "value2";
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888")
.PreflightResponseHeader((AsciiString)HeaderName, (AsciiString)Value1, (AsciiString)Value2).Build();
IHttpResponse response = PreflightRequest(config, "http://localhost:8888", "content-type, xheader1");
AssertValues(response, HeaderName, Value1, Value2);
Assert.Equal(HttpHeaderNames.Origin.ToString(), response.Headers.Get(HttpHeaderNames.Vary, null));
Assert.Equal("0", response.Headers.Get(HttpHeaderNames.ContentLength, null).ToString());
}
[Fact]
public void PreflightRequestWithCustomHeadersIterable()
{
const string HeaderName = "CustomHeader";
const string Value1 = "value1";
const string Value2 = "value2";
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888")
.PreflightResponseHeader((AsciiString)HeaderName, new List<ICharSequence> { (AsciiString)Value1, (AsciiString)Value2 })
.Build();
IHttpResponse response = PreflightRequest(config, "http://localhost:8888", "content-type, xheader1");
AssertValues(response, HeaderName, Value1, Value2);
Assert.Equal(HttpHeaderNames.Origin.ToString(), response.Headers.Get(HttpHeaderNames.Vary, null));
}
class ValueGenerator : ICallable<object>
{
public object Call() => "generatedValue";
}
[Fact]
public void PreflightRequestWithValueGenerator()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888")
.PreflightResponseHeader((AsciiString)"GenHeader", new ValueGenerator()).Build();
IHttpResponse response = PreflightRequest(config, "http://localhost:8888", "content-type, xheader1");
Assert.Equal("generatedValue", response.Headers.Get((AsciiString)"GenHeader", null).ToString());
Assert.Equal(HttpHeaderNames.Origin.ToString(), response.Headers.Get(HttpHeaderNames.Vary, null));
}
[Fact]
public void PreflightRequestWithNullOrigin()
{
var origin = new AsciiString("null");
CorsConfig config = CorsConfigBuilder.ForOrigin(origin)
.AllowNullOrigin()
.AllowCredentials()
.Build();
IHttpResponse response = PreflightRequest(config, origin.ToString(), "content-type, xheader1");
Assert.Equal("null", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Equal("true", response.Headers.Get(HttpHeaderNames.AccessControlAllowCredentials, null));
}
[Fact]
public void PreflightRequestAllowCredentials()
{
var origin = new AsciiString("null");
CorsConfig config = CorsConfigBuilder.ForOrigin(origin).AllowCredentials().Build();
IHttpResponse response = PreflightRequest(config, origin.ToString(), "content-type, xheader1");
Assert.Equal("true", response.Headers.Get(HttpHeaderNames.AccessControlAllowCredentials, null));
}
[Fact]
public void PreflightRequestDoNotAllowCredentials()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888").Build();
IHttpResponse response = PreflightRequest(config, "http://localhost:8888", "");
// the only valid value for Access-Control-Allow-Credentials is true.
Assert.False(response.Headers.Contains(HttpHeaderNames.AccessControlAllowCredentials));
}
[Fact]
public void SimpleRequestCustomHeaders()
{
CorsConfig config = CorsConfigBuilder.ForAnyOrigin()
.ExposeHeaders((AsciiString)"custom1", (AsciiString)"custom2").Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Equal("*", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
Assert.Contains("custom1", response.Headers.Get(HttpHeaderNames.AccessControlExposeHeaders, null).ToString());
Assert.Contains("custom2", response.Headers.Get(HttpHeaderNames.AccessControlExposeHeaders, null).ToString());
}
[Fact]
public void SimpleRequestAllowCredentials()
{
CorsConfig config = CorsConfigBuilder.ForAnyOrigin().AllowCredentials().Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Equal("true", response.Headers.Get(HttpHeaderNames.AccessControlAllowCredentials, null));
}
[Fact]
public void SimpleRequestDoNotAllowCredentials()
{
CorsConfig config = CorsConfigBuilder.ForAnyOrigin().Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.False(response.Headers.Contains(HttpHeaderNames.AccessControlAllowCredentials));
}
[Fact]
public void AnyOriginAndAllowCredentialsShouldEchoRequestOrigin()
{
CorsConfig config = CorsConfigBuilder.ForAnyOrigin().AllowCredentials().Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Equal("true", response.Headers.Get(HttpHeaderNames.AccessControlAllowCredentials, null));
Assert.Equal("http://localhost:7777", response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null).ToString());
Assert.Equal(HttpHeaderNames.Origin.ToString(), response.Headers.Get(HttpHeaderNames.Vary, null));
}
[Fact]
public void SimpleRequestExposeHeaders()
{
CorsConfig config = CorsConfigBuilder.ForAnyOrigin()
.ExposeHeaders((AsciiString)"one", (AsciiString)"two").Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Contains("one", response.Headers.Get(HttpHeaderNames.AccessControlExposeHeaders, null).ToString());
Assert.Contains("two", response.Headers.Get(HttpHeaderNames.AccessControlExposeHeaders, null).ToString());
}
[Fact]
public void SimpleRequestShortCircuit()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8080")
.ShortCircuit().Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Equal(HttpResponseStatus.Forbidden, response.Status);
Assert.Equal("0", response.Headers.Get(HttpHeaderNames.ContentLength, null).ToString());
}
[Fact]
public void SimpleRequestNoShortCircuit()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8080").Build();
IHttpResponse response = SimpleRequest(config, "http://localhost:7777");
Assert.Equal(HttpResponseStatus.OK, response.Status);
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
}
[Fact]
public void ShortCircuitNonCorsRequest()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"https://localhost")
.ShortCircuit().Build();
IHttpResponse response = SimpleRequest(config, null);
Assert.Equal(HttpResponseStatus.OK, response.Status);
Assert.Null(response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null));
}
[Fact]
public void ShortCircuitWithConnectionKeepAliveShouldStayOpen()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8080")
.ShortCircuit().Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = CreateHttpRequest(HttpMethod.Get);
request.Headers.Set(HttpHeaderNames.Origin, (AsciiString)"http://localhost:8888");
request.Headers.Set(HttpHeaderNames.Connection, HttpHeaderValues.KeepAlive);
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.True(HttpUtil.IsKeepAlive(response));
Assert.True(channel.Open);
Assert.Equal(HttpResponseStatus.Forbidden, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void ShortCircuitWithoutConnectionShouldStayOpen()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8080")
.ShortCircuit().Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = CreateHttpRequest(HttpMethod.Get);
request.Headers.Set(HttpHeaderNames.Origin, (AsciiString)"http://localhost:8888");
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.True(HttpUtil.IsKeepAlive(response));
Assert.True(channel.Open);
Assert.Equal(HttpResponseStatus.Forbidden, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void ShortCircuitWithConnectionCloseShouldClose()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8080")
.ShortCircuit().Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = CreateHttpRequest(HttpMethod.Get);
request.Headers.Set(HttpHeaderNames.Origin, "http://localhost:8888");
request.Headers.Set(HttpHeaderNames.Connection, HttpHeaderValues.Close);
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.False(HttpUtil.IsKeepAlive(response));
Assert.False(channel.Open);
Assert.Equal(HttpResponseStatus.Forbidden, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void PreflightRequestShouldReleaseRequest()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888")
.PreflightResponseHeader((AsciiString)"CustomHeader", new List<ICharSequence>{(AsciiString)"value1", (AsciiString)"value2"})
.Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = OptionsRequest("http://localhost:8888", "content-type, xheader1", null);
Assert.False(channel.WriteInbound(request));
Assert.Equal(0, request.ReferenceCount);
Assert.True(ReferenceCountUtil.Release(channel.ReadOutbound<object>()));
Assert.False(channel.Finish());
}
[Fact]
public void PreflightRequestWithConnectionKeepAliveShouldStayOpen()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888").Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = OptionsRequest("http://localhost:8888", "", HttpHeaderValues.KeepAlive);
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.True(HttpUtil.IsKeepAlive(response));
Assert.True(channel.Open);
Assert.Equal(HttpResponseStatus.OK, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void PreflightRequestWithoutConnectionShouldStayOpen()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888").Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = OptionsRequest("http://localhost:8888", "", null);
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.True(HttpUtil.IsKeepAlive(response));
Assert.True(channel.Open);
Assert.Equal(HttpResponseStatus.OK, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void PreflightRequestWithConnectionCloseShouldClose()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"http://localhost:8888").Build();
var channel = new EmbeddedChannel(new CorsHandler(config));
IFullHttpRequest request = OptionsRequest("http://localhost:8888", "", HttpHeaderValues.Close);
Assert.False(channel.WriteInbound(request));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.False(HttpUtil.IsKeepAlive(response));
Assert.False(channel.Open);
Assert.Equal(HttpResponseStatus.OK, response.Status);
Assert.True(ReferenceCountUtil.Release(response));
Assert.False(channel.Finish());
}
[Fact]
public void ForbiddenShouldReleaseRequest()
{
CorsConfig config = CorsConfigBuilder.ForOrigin((AsciiString)"https://localhost").ShortCircuit().Build();
var channel = new EmbeddedChannel(new CorsHandler(config), new EchoHandler());
IFullHttpRequest request = CreateHttpRequest(HttpMethod.Get);
request.Headers.Set(HttpHeaderNames.Origin, "http://localhost:8888");
Assert.False(channel.WriteInbound(request));
Assert.Equal(0, request.ReferenceCount);
Assert.True(ReferenceCountUtil.Release(channel.ReadOutbound<object>()));
Assert.False(channel.Finish());
}
static IHttpResponse SimpleRequest(CorsConfig config, string origin, string requestHeaders = null) =>
SimpleRequest(config, origin, requestHeaders, HttpMethod.Get);
static IHttpResponse SimpleRequest(CorsConfig config, string origin, string requestHeaders, HttpMethod method)
{
var channel = new EmbeddedChannel(new CorsHandler(config), new EchoHandler());
IFullHttpRequest httpRequest = CreateHttpRequest(method);
if (origin != null)
{
httpRequest.Headers.Set(HttpHeaderNames.Origin, new AsciiString(origin));
}
if (requestHeaders != null)
{
httpRequest.Headers.Set(HttpHeaderNames.AccessControlRequestHeaders, new AsciiString(requestHeaders));
}
Assert.False(channel.WriteInbound(httpRequest));
return channel.ReadOutbound<IHttpResponse>();
}
static IHttpResponse PreflightRequest(CorsConfig config, string origin, string requestHeaders)
{
var channel = new EmbeddedChannel(new CorsHandler(config));
Assert.False(channel.WriteInbound(OptionsRequest(origin, requestHeaders, null)));
var response = channel.ReadOutbound<IHttpResponse>();
Assert.False(channel.Finish());
return response;
}
static IFullHttpRequest OptionsRequest(string origin, string requestHeaders, AsciiString connection)
{
IFullHttpRequest httpRequest = CreateHttpRequest(HttpMethod.Options);
httpRequest.Headers.Set(HttpHeaderNames.Origin, new AsciiString(origin));
httpRequest.Headers.Set(HttpHeaderNames.AccessControlRequestMethod, httpRequest.Method);
httpRequest.Headers.Set(HttpHeaderNames.AccessControlRequestHeaders, new AsciiString(requestHeaders));
if (connection != null)
{
httpRequest.Headers.Set(HttpHeaderNames.Connection, connection);
}
return httpRequest;
}
static IFullHttpRequest CreateHttpRequest(HttpMethod method) => new DefaultFullHttpRequest(HttpVersion.Http11, method, "/info");
sealed class EchoHandler : SimpleChannelInboundHandler<object>
{
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg) =>
ctx.WriteAndFlushAsync(new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK, true, true));
}
static void AssertValues(IHttpResponse response, string headerName, params string[] values)
{
ICharSequence header = response.Headers.Get(new AsciiString(headerName), null);
foreach (string value in values)
{
Assert.Contains(value, header.ToString());
}
}
}
}