File: HstsMiddleware.cs
Web Access
Project: src\src\Middleware\HttpsPolicy\src\Microsoft.AspNetCore.HttpsPolicy.csproj (Microsoft.AspNetCore.HttpsPolicy)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
 
namespace Microsoft.AspNetCore.HttpsPolicy;
 
/// <summary>
/// Enables HTTP Strict Transport Security (HSTS)
/// See <see href="https://tools.ietf.org/html/rfc6797"/>.
/// </summary>
public class HstsMiddleware
{
    private const string IncludeSubDomains = "; includeSubDomains";
    private const string Preload = "; preload";
 
    private readonly RequestDelegate _next;
    private readonly StringValues _strictTransportSecurityValue;
    private readonly IList<string> _excludedHosts;
    private readonly ILogger _logger;
 
    /// <summary>
    /// Initialize the HSTS middleware.
    /// </summary>
    /// <param name="next"></param>
    /// <param name="options"></param>
    /// <param name="loggerFactory"></param>
    public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options, ILoggerFactory loggerFactory)
    {
        ArgumentNullException.ThrowIfNull(options);
 
        _next = next ?? throw new ArgumentNullException(nameof(next));
 
        var hstsOptions = options.Value;
        var maxAge = Convert.ToInt64(Math.Floor(hstsOptions.MaxAge.TotalSeconds))
                        .ToString(CultureInfo.InvariantCulture);
        var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
        var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
        _strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
        _excludedHosts = hstsOptions.ExcludedHosts;
        _logger = loggerFactory.CreateLogger<HstsMiddleware>();
    }
 
    /// <summary>
    /// Initialize the HSTS middleware.
    /// </summary>
    /// <param name="next"></param>
    /// <param name="options"></param>
    public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
        : this(next, options, NullLoggerFactory.Instance) { }
 
    /// <summary>
    /// Invoke the middleware.
    /// </summary>
    /// <param name="context">The <see cref="HttpContext"/>.</param>
    /// <returns></returns>
    public Task Invoke(HttpContext context)
    {
        if (!context.Request.IsHttps)
        {
            _logger.SkippingInsecure();
            return _next(context);
        }
 
        if (IsHostExcluded(context.Request.Host.Host))
        {
            _logger.SkippingExcludedHost(context.Request.Host.Host);
            return _next(context);
        }
 
        context.Response.Headers.StrictTransportSecurity = _strictTransportSecurityValue;
        _logger.AddingHstsHeader();
 
        return _next(context);
    }
 
    private bool IsHostExcluded(string host)
    {
        for (var i = 0; i < _excludedHosts.Count; i++)
        {
            if (string.Equals(host, _excludedHosts[i], StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }
 
        return false;
    }
}