File: RedirectRule.cs
Web Access
Project: src\aspnetcore\src\Middleware\Rewrite\src\Microsoft.AspNetCore.Rewrite.csproj (Microsoft.AspNetCore.Rewrite)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Rewrite.Logging;

namespace Microsoft.AspNetCore.Rewrite;

internal sealed class RedirectRule : IRule
{
    private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1);
    public Regex InitialMatch { get; }
    public string Replacement { get; }
    public int StatusCode { get; }
    public RedirectRule(string regex, string replacement, int statusCode)
    {
        ArgumentException.ThrowIfNullOrEmpty(regex);
        ArgumentException.ThrowIfNullOrEmpty(replacement);

        InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout);
        Replacement = replacement;
        StatusCode = statusCode;
    }

    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var path = request.Path;
        var pathBase = request.PathBase;

        Match initMatchResults;
        if (!path.HasValue)
        {
            initMatchResults = InitialMatch.Match(string.Empty);
        }
        else
        {
            initMatchResults = InitialMatch.Match(path.ToString().Substring(1));
        }

        if (initMatchResults.Success)
        {
            var newPath = initMatchResults.Result(Replacement);
            var response = context.HttpContext.Response;

            response.StatusCode = StatusCode;
            context.Result = RuleResult.EndResponse;

            string encodedPath;

            if (string.IsNullOrEmpty(newPath))
            {
                encodedPath = pathBase.HasValue ? pathBase.Value : "/";
            }
            else
            {
                var host = default(HostString);
                var schemeSplit = newPath.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
                string scheme = request.Scheme;
                if (schemeSplit >= 0)
                {
                    scheme = newPath.Substring(0, schemeSplit);
                    schemeSplit += Uri.SchemeDelimiter.Length;
                    var pathSplit = newPath.IndexOf('/', schemeSplit);

                    if (pathSplit == -1)
                    {
                        host = new HostString(newPath.Substring(schemeSplit));
                        newPath = "/";
                    }
                    else
                    {
                        host = new HostString(newPath.Substring(schemeSplit, pathSplit - schemeSplit));
                        newPath = newPath.Substring(pathSplit);
                    }
                }

                if (newPath[0] != '/')
                {
                    newPath = '/' + newPath;
                }

                var resolvedQuery = request.QueryString;
                var resolvedPath = newPath;
                var querySplit = newPath.IndexOf('?');
                if (querySplit >= 0)
                {
                    resolvedQuery = request.QueryString.Add(QueryString.FromUriComponent(newPath.Substring(querySplit)));
                    resolvedPath = newPath.Substring(0, querySplit);
                }

                encodedPath = host.HasValue
                    ? UriHelper.BuildAbsolute(scheme, host, pathBase, resolvedPath, resolvedQuery, default)
                    : UriHelper.BuildRelative(pathBase, resolvedPath, resolvedQuery, default);
            }

            // not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308
            response.Headers.Location = encodedPath;

            context.Logger.RedirectedRequest(newPath);
        }
    }
}