Modifies .Rewrite and .Redirect to use normal regex syntax for backreferences
This commit is contained in:
parent
28133b6808
commit
4a06b37280
|
|
@ -27,7 +27,7 @@ namespace RewriteSample
|
||||||
// TODO make this startup do something useful.
|
// TODO make this startup do something useful.
|
||||||
|
|
||||||
app.UseRewriter(new RewriteOptions()
|
app.UseRewriter(new RewriteOptions()
|
||||||
.Rewrite(@"foo/(\d+)", "foo?id={R:1}")
|
.Rewrite(@"foo/(\d+)", "foo?id=$1")
|
||||||
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
|
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
|
||||||
.ImportFromModRewrite(hostingEnv, "Rewrite.txt"));
|
.ImportFromModRewrite(hostingEnv, "Rewrite.txt"));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
// 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.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
|
||||||
|
{
|
||||||
|
public class RedirectRule : Rule
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout);
|
||||||
|
Replacement = replacement;
|
||||||
|
StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyRule(RewriteContext context)
|
||||||
|
{
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
Match initMatchResults;
|
||||||
|
if (path == PathString.Empty)
|
||||||
|
{
|
||||||
|
initMatchResults = InitialMatch.Match(path.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
initMatchResults = InitialMatch.Match(path.ToString().Substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initMatchResults.Success)
|
||||||
|
{
|
||||||
|
var newPath = initMatchResults.Result(Replacement);
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
|
response.StatusCode = StatusCode;
|
||||||
|
|
||||||
|
if (newPath.IndexOf("://", StringComparison.Ordinal) == -1 && !newPath.StartsWith("/"))
|
||||||
|
{
|
||||||
|
newPath = '/' + newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = newPath.IndexOf('?');
|
||||||
|
if (split >= 0)
|
||||||
|
{
|
||||||
|
var query = context.HttpContext.Request.QueryString.Add(
|
||||||
|
QueryString.FromUriComponent(
|
||||||
|
newPath.Substring(split)));
|
||||||
|
// not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308
|
||||||
|
response.Headers[HeaderNames.Location] = newPath.Substring(0, split) + query;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Headers[HeaderNames.Location] = newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Result = RuleTermination.ResponseComplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
// 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.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
|
||||||
|
{
|
||||||
|
public class RewriteRule : Rule
|
||||||
|
{
|
||||||
|
private readonly string ForwardSlash = "/";
|
||||||
|
private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1);
|
||||||
|
public Regex InitialMatch { get; }
|
||||||
|
public string Replacement { get; }
|
||||||
|
public bool StopProcessing { get; }
|
||||||
|
public RewriteRule(string regex, string replacement, bool stopProcessing)
|
||||||
|
{
|
||||||
|
InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout);
|
||||||
|
Replacement = replacement;
|
||||||
|
StopProcessing = stopProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyRule(RewriteContext context)
|
||||||
|
{
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
Match initMatchResults;
|
||||||
|
if (path == PathString.Empty)
|
||||||
|
{
|
||||||
|
initMatchResults = InitialMatch.Match(path.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
initMatchResults = InitialMatch.Match(path.ToString().Substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initMatchResults.Success)
|
||||||
|
{
|
||||||
|
var result = initMatchResults.Result(Replacement);
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
|
|
||||||
|
if (result.IndexOf("://", StringComparison.Ordinal) >= 0)
|
||||||
|
{
|
||||||
|
string scheme;
|
||||||
|
HostString host;
|
||||||
|
PathString pathString;
|
||||||
|
QueryString query;
|
||||||
|
FragmentString fragment;
|
||||||
|
UriHelper.FromAbsolute(result, out scheme, out host, out pathString, out query, out fragment);
|
||||||
|
|
||||||
|
request.Scheme = scheme;
|
||||||
|
request.Host = host;
|
||||||
|
request.Path = pathString;
|
||||||
|
request.QueryString = query.Add(request.QueryString);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var split = result.IndexOf('?');
|
||||||
|
if (split >= 0)
|
||||||
|
{
|
||||||
|
var newPath = result.Substring(0, split);
|
||||||
|
if (newPath.StartsWith(ForwardSlash))
|
||||||
|
{
|
||||||
|
request.Path = PathString.FromUriComponent(newPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.Path = PathString.FromUriComponent(ForwardSlash + newPath);
|
||||||
|
}
|
||||||
|
request.QueryString = request.QueryString.Add(
|
||||||
|
QueryString.FromUriComponent(
|
||||||
|
result.Substring(split)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (result.StartsWith(ForwardSlash))
|
||||||
|
{
|
||||||
|
request.Path = PathString.FromUriComponent(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.Path = PathString.FromUriComponent(ForwardSlash + result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StopProcessing)
|
||||||
|
{
|
||||||
|
context.Result = RuleTermination.StopRules;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// 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 Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
|
|
||||||
{
|
|
||||||
public class ModRewriteRedirectAction : UrlAction
|
|
||||||
{
|
|
||||||
public int StatusCode { get; }
|
|
||||||
public bool QueryStringAppend { get; }
|
|
||||||
public bool QueryStringDelete { get; }
|
|
||||||
public bool EscapeBackReferences { get; }
|
|
||||||
|
|
||||||
public ModRewriteRedirectAction(
|
|
||||||
int statusCode,
|
|
||||||
Pattern pattern,
|
|
||||||
bool queryStringAppend,
|
|
||||||
bool queryStringDelete,
|
|
||||||
bool escapeBackReferences)
|
|
||||||
{
|
|
||||||
StatusCode = statusCode;
|
|
||||||
Url = pattern;
|
|
||||||
QueryStringAppend = queryStringAppend;
|
|
||||||
QueryStringDelete = queryStringDelete;
|
|
||||||
EscapeBackReferences = escapeBackReferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
|
||||||
{
|
|
||||||
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
|
||||||
if (EscapeBackReferences)
|
|
||||||
{
|
|
||||||
// because escapebackreferences will be encapsulated by the pattern, just escape the pattern
|
|
||||||
pattern = Uri.EscapeDataString(pattern);
|
|
||||||
}
|
|
||||||
context.HttpContext.Response.StatusCode = StatusCode;
|
|
||||||
|
|
||||||
// url can either contain the full url or the path and query
|
|
||||||
// always add to location header.
|
|
||||||
// TODO check for false positives
|
|
||||||
var split = pattern.IndexOf('?');
|
|
||||||
if (split >= 0 && QueryStringAppend)
|
|
||||||
{
|
|
||||||
var query = context.HttpContext.Request.QueryString.Add(
|
|
||||||
QueryString.FromUriComponent(
|
|
||||||
pattern.Substring(split)));
|
|
||||||
|
|
||||||
// not using the response.redirect here because status codes may be 301, 302, 307, 308
|
|
||||||
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the request url has a query string and the target does not, append the query string
|
|
||||||
// by default.
|
|
||||||
if (QueryStringDelete)
|
|
||||||
{
|
|
||||||
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern + context.HttpContext.Request.QueryString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.Result = RuleTermination.ResponseComplete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
// 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 Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
|
|
||||||
{
|
|
||||||
public class ModRewriteRewriteAction : UrlAction
|
|
||||||
{
|
|
||||||
private readonly string ForwardSlash = "/";
|
|
||||||
public RuleTermination Result { get; }
|
|
||||||
public bool QueryStringAppend { get; }
|
|
||||||
public bool QueryStringDelete { get; }
|
|
||||||
public bool EscapeBackReferences { get; }
|
|
||||||
|
|
||||||
public ModRewriteRewriteAction(
|
|
||||||
RuleTermination result,
|
|
||||||
Pattern pattern,
|
|
||||||
bool queryStringAppend,
|
|
||||||
bool queryStringDelete,
|
|
||||||
bool escapeBackReferences)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
Url = pattern;
|
|
||||||
QueryStringAppend = queryStringAppend;
|
|
||||||
QueryStringDelete = queryStringDelete;
|
|
||||||
EscapeBackReferences = escapeBackReferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
|
||||||
{
|
|
||||||
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
|
||||||
|
|
||||||
// TODO PERF, substrings, object creation, etc.
|
|
||||||
if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0)
|
|
||||||
{
|
|
||||||
string scheme;
|
|
||||||
HostString host;
|
|
||||||
PathString path;
|
|
||||||
QueryString query;
|
|
||||||
FragmentString fragment;
|
|
||||||
UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment);
|
|
||||||
|
|
||||||
if (query.HasValue)
|
|
||||||
{
|
|
||||||
if (QueryStringAppend)
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(query);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (QueryStringDelete)
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = QueryString.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.HttpContext.Request.Scheme = scheme;
|
|
||||||
context.HttpContext.Request.Host = host;
|
|
||||||
context.HttpContext.Request.Path = path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var split = pattern.IndexOf('?');
|
|
||||||
if (split >= 0)
|
|
||||||
{
|
|
||||||
var path = pattern.Substring(0, split);
|
|
||||||
if (path.StartsWith(ForwardSlash))
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QueryStringAppend)
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(
|
|
||||||
QueryString.FromUriComponent(
|
|
||||||
pattern.Substring(split)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = QueryString.FromUriComponent(
|
|
||||||
pattern.Substring(split));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (pattern.StartsWith(ForwardSlash))
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(pattern);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QueryStringDelete)
|
|
||||||
{
|
|
||||||
context.HttpContext.Request.QueryString = QueryString.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.Result = Result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -210,13 +210,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
|
||||||
{
|
{
|
||||||
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1));
|
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1));
|
||||||
}
|
}
|
||||||
_action = new ModRewriteRedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
|
_action = new RedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last);
|
var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last);
|
||||||
var termination = last ? RuleTermination.StopRules : RuleTermination.Continue;
|
var termination = last ? RuleTermination.StopRules : RuleTermination.Continue;
|
||||||
_action = new ModRewriteRewriteAction(termination, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
|
_action = new RewriteAction(termination, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
||||||
|
|
@ -10,41 +12,80 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
||||||
public class RedirectAction : UrlAction
|
public class RedirectAction : UrlAction
|
||||||
{
|
{
|
||||||
public int StatusCode { get; }
|
public int StatusCode { get; }
|
||||||
public bool AppendQueryString { get; }
|
public bool QueryStringAppend { get; }
|
||||||
|
public bool QueryStringDelete { get; }
|
||||||
|
public bool EscapeBackReferences { get; }
|
||||||
|
|
||||||
public RedirectAction(int statusCode, Pattern pattern, bool appendQueryString)
|
public RedirectAction(
|
||||||
|
int statusCode,
|
||||||
|
Pattern pattern,
|
||||||
|
bool queryStringAppend,
|
||||||
|
bool queryStringDelete,
|
||||||
|
bool escapeBackReferences)
|
||||||
{
|
{
|
||||||
StatusCode = statusCode;
|
StatusCode = statusCode;
|
||||||
Url = pattern;
|
Url = pattern;
|
||||||
AppendQueryString = appendQueryString;
|
QueryStringAppend = queryStringAppend;
|
||||||
|
QueryStringDelete = queryStringDelete;
|
||||||
|
EscapeBackReferences = escapeBackReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedirectAction(
|
||||||
|
int statusCode,
|
||||||
|
Pattern pattern,
|
||||||
|
bool queryStringAppend)
|
||||||
|
: this(
|
||||||
|
statusCode,
|
||||||
|
pattern,
|
||||||
|
queryStringAppend,
|
||||||
|
queryStringDelete: true,
|
||||||
|
escapeBackReferences: false)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
||||||
{
|
{
|
||||||
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
||||||
context.HttpContext.Response.StatusCode = StatusCode;
|
var response = context.HttpContext.Response;
|
||||||
|
if (EscapeBackReferences)
|
||||||
|
{
|
||||||
|
// because escapebackreferences will be encapsulated by the pattern, just escape the pattern
|
||||||
|
pattern = Uri.EscapeDataString(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO IIS guarantees that there will be a leading slash
|
|
||||||
if (pattern.IndexOf("://", StringComparison.Ordinal) == -1 && !pattern.StartsWith("/"))
|
if (pattern.IndexOf("://", StringComparison.Ordinal) == -1 && !pattern.StartsWith("/"))
|
||||||
{
|
{
|
||||||
pattern = '/' + pattern;
|
pattern = '/' + pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response.StatusCode = StatusCode;
|
||||||
|
|
||||||
// url can either contain the full url or the path and query
|
// url can either contain the full url or the path and query
|
||||||
// always add to location header.
|
// always add to location header.
|
||||||
// TODO check for false positives
|
// TODO check for false positives
|
||||||
var split = pattern.IndexOf('?');
|
var split = pattern.IndexOf('?');
|
||||||
if (split >= 0 && AppendQueryString)
|
if (split >= 0 && QueryStringAppend)
|
||||||
{
|
{
|
||||||
var query = context.HttpContext.Request.QueryString.Add(
|
var query = context.HttpContext.Request.QueryString.Add(
|
||||||
QueryString.FromUriComponent(
|
QueryString.FromUriComponent(
|
||||||
pattern.Substring(split)));
|
pattern.Substring(split)));
|
||||||
// not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308
|
|
||||||
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
|
// not using the response.redirect here because status codes may be 301, 302, 307, 308
|
||||||
|
response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
|
// If the request url has a query string and the target does not, append the query string
|
||||||
|
// by default.
|
||||||
|
if (QueryStringDelete)
|
||||||
|
{
|
||||||
|
response.Headers[HeaderNames.Location] = pattern;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Headers[HeaderNames.Location] = pattern + context.HttpContext.Request.QueryString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
context.Result = RuleTermination.ResponseComplete;
|
context.Result = RuleTermination.ResponseComplete;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,48 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
||||||
{
|
{
|
||||||
private readonly string ForwardSlash = "/";
|
private readonly string ForwardSlash = "/";
|
||||||
public RuleTermination Result { get; }
|
public RuleTermination Result { get; }
|
||||||
public bool ClearQuery { get; }
|
public bool QueryStringAppend { get; }
|
||||||
|
public bool QueryStringDelete { get; }
|
||||||
|
public bool EscapeBackReferences { get; }
|
||||||
|
|
||||||
public RewriteAction(RuleTermination result, Pattern pattern, bool clearQuery)
|
public RewriteAction(
|
||||||
|
RuleTermination result,
|
||||||
|
Pattern pattern,
|
||||||
|
bool queryStringAppend,
|
||||||
|
bool queryStringDelete,
|
||||||
|
bool escapeBackReferences)
|
||||||
{
|
{
|
||||||
Result = result;
|
Result = result;
|
||||||
Url = pattern;
|
Url = pattern;
|
||||||
ClearQuery = clearQuery;
|
QueryStringAppend = queryStringAppend;
|
||||||
|
QueryStringDelete = queryStringDelete;
|
||||||
|
EscapeBackReferences = escapeBackReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RewriteAction(
|
||||||
|
RuleTermination result,
|
||||||
|
Pattern pattern,
|
||||||
|
bool queryStringAppend):
|
||||||
|
this (result,
|
||||||
|
pattern,
|
||||||
|
queryStringAppend,
|
||||||
|
queryStringDelete: false,
|
||||||
|
escapeBackReferences: false)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
|
||||||
{
|
{
|
||||||
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
if (ClearQuery)
|
if (EscapeBackReferences)
|
||||||
{
|
{
|
||||||
context.HttpContext.Request.QueryString = QueryString.Empty;
|
// because escapebackreferences will be encapsulated by the pattern, just escape the pattern
|
||||||
|
pattern = Uri.EscapeDataString(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO PERF, substrings, object creation, etc.
|
||||||
if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0)
|
if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0)
|
||||||
{
|
{
|
||||||
string scheme;
|
string scheme;
|
||||||
|
|
@ -38,10 +62,25 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
||||||
FragmentString fragment;
|
FragmentString fragment;
|
||||||
UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment);
|
UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment);
|
||||||
|
|
||||||
context.HttpContext.Request.Scheme = scheme;
|
if (query.HasValue)
|
||||||
context.HttpContext.Request.Host = host;
|
{
|
||||||
context.HttpContext.Request.Path = path;
|
if (QueryStringAppend)
|
||||||
context.HttpContext.Request.QueryString = query.Add(context.HttpContext.Request.QueryString);
|
{
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -51,25 +90,39 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
||||||
var path = pattern.Substring(0, split);
|
var path = pattern.Substring(0, split);
|
||||||
if (path.StartsWith(ForwardSlash))
|
if (path.StartsWith(ForwardSlash))
|
||||||
{
|
{
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(path);
|
request.Path = PathString.FromUriComponent(path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path);
|
request.Path = PathString.FromUriComponent(ForwardSlash + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QueryStringAppend)
|
||||||
|
{
|
||||||
|
request.QueryString = request.QueryString.Add(
|
||||||
|
QueryString.FromUriComponent(
|
||||||
|
pattern.Substring(split)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.QueryString = QueryString.FromUriComponent(
|
||||||
|
pattern.Substring(split));
|
||||||
}
|
}
|
||||||
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(
|
|
||||||
QueryString.FromUriComponent(
|
|
||||||
pattern.Substring(split)));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (pattern.StartsWith(ForwardSlash))
|
if (pattern.StartsWith(ForwardSlash))
|
||||||
{
|
{
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(pattern);
|
request.Path = PathString.FromUriComponent(pattern);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern);
|
request.Path = PathString.FromUriComponent(ForwardSlash + pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QueryStringDelete)
|
||||||
|
{
|
||||||
|
request.QueryString = QueryString.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
|
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
|
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
|
||||||
break;
|
break;
|
||||||
case ActionType.Rewrite:
|
case ActionType.Rewrite:
|
||||||
_action = new RewriteAction(stopProcessing ? RuleTermination.StopRules : RuleTermination.Continue,
|
_action = new RewriteAction(stopProcessing ? RuleTermination.StopRules : RuleTermination.Continue,
|
||||||
url, clearQuery: !appendQueryString);
|
url, appendQueryString);
|
||||||
break;
|
break;
|
||||||
case ActionType.Redirect:
|
case ActionType.Redirect:
|
||||||
_action = new RedirectAction(statusCode, url, appendQueryString);
|
_action = new RedirectAction(statusCode, url, appendQueryString);
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ namespace Microsoft.AspNetCore.Rewrite
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The Rewrite options.</param>
|
/// <param name="options">The Rewrite options.</param>
|
||||||
/// <param name="regex">The regex string to compare with.</param>
|
/// <param name="regex">The regex string to compare with.</param>
|
||||||
/// <param name="urlPattern">If the regex matches, what to replace HttpContext with.</param>
|
/// <param name="replacement">If the regex matches, what to replace HttpContext with.</param>
|
||||||
/// <returns>The Rewrite options.</returns>
|
/// <returns>The Rewrite options.</returns>
|
||||||
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern)
|
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string replacement)
|
||||||
{
|
{
|
||||||
return Rewrite(options, regex, urlPattern, stopProcessing: false);
|
return Rewrite(options, regex, replacement, stopProcessing: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -54,17 +54,12 @@ namespace Microsoft.AspNetCore.Rewrite
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The Rewrite options.</param>
|
/// <param name="options">The Rewrite options.</param>
|
||||||
/// <param name="regex">The regex string to compare with.</param>
|
/// <param name="regex">The regex string to compare with.</param>
|
||||||
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
|
/// <param name="replacement">If the regex matches, what to replace the uri with.</param>
|
||||||
/// <param name="stopProcessing">If the regex matches, conditionally stop processing other rules.</param>
|
/// <param name="stopProcessing">If the regex matches, conditionally stop processing other rules.</param>
|
||||||
/// <returns>The Rewrite options.</returns>
|
/// <returns>The Rewrite options.</returns>
|
||||||
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern, bool stopProcessing)
|
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string replacement, bool stopProcessing)
|
||||||
{
|
{
|
||||||
var builder = new UrlRewriteRuleBuilder();
|
options.Rules.Add(new RewriteRule(regex, replacement, stopProcessing));
|
||||||
var pattern = new InputParser().ParseInputString(urlPattern);
|
|
||||||
|
|
||||||
builder.AddUrlMatch(regex);
|
|
||||||
builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing);
|
|
||||||
options.Rules.Add(builder.Build());
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,11 +68,11 @@ namespace Microsoft.AspNetCore.Rewrite
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The Rewrite options.</param>
|
/// <param name="options">The Rewrite options.</param>
|
||||||
/// <param name="regex">The regex string to compare with.</param>
|
/// <param name="regex">The regex string to compare with.</param>
|
||||||
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
|
/// <param name="replacement">If the regex matches, what to replace the uri with.</param>
|
||||||
/// <returns>The Rewrite options.</returns>
|
/// <returns>The Rewrite options.</returns>
|
||||||
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern)
|
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string replacement)
|
||||||
{
|
{
|
||||||
return Redirect(options, regex, urlPattern, statusCode: 302);
|
return Redirect(options, regex, replacement, statusCode: 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -85,21 +80,19 @@ namespace Microsoft.AspNetCore.Rewrite
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The Rewrite options.</param>
|
/// <param name="options">The Rewrite options.</param>
|
||||||
/// <param name="regex">The regex string to compare with.</param>
|
/// <param name="regex">The regex string to compare with.</param>
|
||||||
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
|
/// <param name="replacement">If the regex matches, what to replace the uri with.</param>
|
||||||
/// <param name="statusCode">The status code to add to the response.</param>
|
/// <param name="statusCode">The status code to add to the response.</param>
|
||||||
/// <returns>The Rewrite options.</returns>
|
/// <returns>The Rewrite options.</returns>
|
||||||
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern, int statusCode)
|
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string replacement, int statusCode)
|
||||||
{
|
{
|
||||||
var builder = new UrlRewriteRuleBuilder();
|
options.Rules.Add(new RedirectRule(regex, replacement, statusCode));
|
||||||
var pattern = new InputParser().ParseInputString(urlPattern);
|
|
||||||
|
|
||||||
builder.AddUrlMatch(regex);
|
|
||||||
builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: false);
|
|
||||||
options.Rules.Add(builder.Build());
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 301 overload
|
public static RewriteOptions RedirectToHttpsPermanent(this RewriteOptions options)
|
||||||
|
{
|
||||||
|
return RedirectToHttps(options, statusCode: 301, sslPort: null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Redirect a request to https if the incoming request is http
|
/// Redirect a request to https if the incoming request is http
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CheckRewritePath()
|
public async Task CheckRewritePath()
|
||||||
{
|
{
|
||||||
var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/{R:1}");
|
var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/$1");
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CheckRedirectPath()
|
public async Task CheckRedirectPath()
|
||||||
{
|
{
|
||||||
var options = new RewriteOptions().Redirect("(.*)","http://example.com/{R:1}", statusCode: 301);
|
var options = new RewriteOptions().Redirect("(.*)","http://example.com/$1", statusCode: 301);
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal;
|
using Microsoft.AspNetCore.Rewrite.Internal;
|
||||||
|
using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
|
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
|
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
|
||||||
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
|
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
|
||||||
|
|
@ -146,7 +147,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return new UrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
|
return new UrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
|
||||||
new RewriteAction(RuleTermination.Continue, new InputParser().ParseInputString(url), clearQuery: false));
|
new RewriteAction(RuleTermination.Continue, new InputParser().ParseInputString(url), queryStringAppend: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make rules comparable?
|
// TODO make rules comparable?
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue