aspnetcore/src/Microsoft.AspNet.Cors/CorsService.cs

250 lines
8.8 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.OptionsModel;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Cors.Infrastructure
{
/// <summary>
/// Default implementation of <see cref="ICorsService"/>.
/// </summary>
public class CorsService : ICorsService
{
private readonly CorsOptions _options;
/// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
public CorsService(IOptions<CorsOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options.Value;
}
/// <summary>
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
/// <paramref name="context"/>.
/// </summary>
/// <param name="requestContext"></param>
/// <param name="policyName"></param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set appropriate response headers.</returns>
public CorsResult EvaluatePolicy(HttpContext context, string policyName)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var policy = _options.GetPolicy(policyName);
return EvaluatePolicy(context, policy);
}
/// <inheritdoc />
public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
var corsResult = new CorsResult();
var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.Ordinal) &&
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
EvaluatePreflightRequest(context, policy, corsResult);
}
else
{
EvaluateRequest(context, policy, corsResult);
}
return corsResult;
}
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[CorsConstants.Origin];
if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
}
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[CorsConstants.Origin];
if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
return;
}
var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
if (StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
return;
}
var requestHeaders =
context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders);
if (!policy.AllowAnyMethod && !policy.Methods.Contains(accessControlRequestMethod))
{
return;
}
if (!policy.AllowAnyHeader &&
requestHeaders != null &&
!requestHeaders.All(header => CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase) ||
policy.Headers.Contains(header, StringComparer.OrdinalIgnoreCase)))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge;
result.AllowedMethods.Add(accessControlRequestMethod);
AddHeaderValues(result.AllowedHeaders, requestHeaders);
}
/// <inheritdoc />
public virtual void ApplyResult(CorsResult result, HttpResponse response)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
var headers = response.Headers;
if (result.AllowedOrigin != null)
{
headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
}
if (result.VaryByOrigin)
{
headers["Vary"] = "Origin";
}
if (result.SupportsCredentials)
{
headers[CorsConstants.AccessControlAllowCredentials] = "true";
}
if (result.AllowedMethods.Count > 0)
{
// Filter out simple methods
var nonSimpleAllowMethods = result.AllowedMethods
.Where(m =>
!CorsConstants.SimpleMethods.Contains(m, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowMethods.Length > 0)
{
headers.SetCommaSeparatedValues(
CorsConstants.AccessControlAllowMethods,
nonSimpleAllowMethods);
}
}
if (result.AllowedHeaders.Count > 0)
{
// Filter out simple request headers
var nonSimpleAllowRequestHeaders = result.AllowedHeaders
.Where(header =>
!CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowRequestHeaders.Length > 0)
{
headers.SetCommaSeparatedValues(
CorsConstants.AccessControlAllowHeaders,
nonSimpleAllowRequestHeaders);
}
}
if (result.AllowedExposedHeaders.Count > 0)
{
// Filter out simple response headers
var nonSimpleAllowResponseHeaders = result.AllowedExposedHeaders
.Where(header =>
!CorsConstants.SimpleResponseHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowResponseHeaders.Length > 0)
{
headers.SetCommaSeparatedValues(
CorsConstants.AccessControlExposeHeaders,
nonSimpleAllowResponseHeaders);
}
}
if (result.PreflightMaxAge.HasValue)
{
headers[CorsConstants.AccessControlMaxAge]
= result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
}
}
private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
{
if (policy.AllowAnyOrigin)
{
if (policy.SupportsCredentials)
{
result.AllowedOrigin = origin;
result.VaryByOrigin = true;
}
else
{
result.AllowedOrigin = CorsConstants.AnyOrigin;
}
}
else if (policy.Origins.Contains(origin))
{
result.AllowedOrigin = origin;
}
}
private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues)
{
if (headerValues == null)
{
return;
}
foreach (var current in headerValues)
{
target.Add(current);
}
}
}
}