| File: UrlActions\RewriteAction.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 Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; namespace Microsoft.AspNetCore.Rewrite.UrlActions; internal sealed class RewriteAction : UrlAction { public RuleResult Result { get; } public bool QueryStringAppend { get; } public bool QueryStringDelete { get; } public bool EscapeBackReferences { get; } public RewriteAction( RuleResult result, Pattern pattern, bool queryStringAppend, bool queryStringDelete, bool escapeBackReferences) { // For the replacement, we must have at least // one segment (cannot have an empty replacement) Result = result; Url = pattern; QueryStringAppend = queryStringAppend; QueryStringDelete = queryStringDelete; EscapeBackReferences = escapeBackReferences; } public RewriteAction( RuleResult result, Pattern pattern, bool queryStringAppend) : this(result, pattern, queryStringAppend, queryStringDelete: false, escapeBackReferences: false) { } public override void ApplyAction(RewriteContext context, BackReferenceCollection? ruleBackReferences, BackReferenceCollection? conditionBackReferences) { var pattern = Url!.Evaluate(context, ruleBackReferences, conditionBackReferences); var request = context.HttpContext.Request; if (string.IsNullOrEmpty(pattern)) { pattern = "/"; } if (EscapeBackReferences) { // because escapebackreferences will be encapsulated by the pattern, just escape the pattern pattern = Uri.EscapeDataString(pattern); } // Defense in depth: this rule does not emit a Location header itself, but the rewritten value is // assigned to Request.Path below and a downstream component (or a later rule) may issue a redirect // from it. PathString does not reject a leading '//' or '/\', so neutralize the scheme-relative // shape here rather than auditing every downstream consumer. pattern = UrlNormalizer.CollapseLeadingSlashes(pattern); // TODO PERF, substrings, object creation, etc. if (pattern.Contains(Uri.SchemeDelimiter, StringComparison.Ordinal)) { string scheme; HostString host; PathString path; QueryString query; UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out _); if (query.HasValue) { if (QueryStringAppend) { request.QueryString = request.QueryString.Add(query); } else { request.QueryString = query; } } else if (QueryStringDelete) { request.QueryString = QueryString.Empty; } request.Scheme = scheme; request.Host = host; request.Path = path; } else { var split = pattern.IndexOf('?'); if (split >= 0) { var path = pattern.Substring(0, split); if (path[0] == '/') { request.Path = PathString.FromUriComponent(path); } else { request.Path = PathString.FromUriComponent('/' + path); } if (QueryStringAppend) { request.QueryString = request.QueryString.Add( QueryString.FromUriComponent( pattern.Substring(split))); } else { request.QueryString = QueryString.FromUriComponent( pattern.Substring(split)); } } else { if (pattern[0] == '/') { request.Path = PathString.FromUriComponent(pattern); } else { request.Path = PathString.FromUriComponent('/' + pattern); } if (QueryStringDelete) { request.QueryString = QueryString.Empty; } } } context.Result = Result; } }