Adding support for trackAllCaptures (#178)

This commit is contained in:
Chris R 2016-12-20 15:36:10 -08:00 committed by Mikael Mengistu
parent 46adbc3e8f
commit f813ee4d97
50 changed files with 284 additions and 118 deletions

View File

@ -30,11 +30,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
return; return;
} }
MatchResults condMatchRes = null; BackReferenceCollection condBackReferences = null;
if (Conditions != null) if (Conditions != null)
{ {
condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchRes); var condResult = ConditionHelper.Evaluate(Conditions, context, initMatchRes.BackReferences);
if (!condMatchRes.Success) if (!condResult.Success)
{ {
context.Logger?.ModRewriteDidNotMatchRule(); context.Logger?.ModRewriteDidNotMatchRule();
return; return;
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
foreach (var action in Actions) foreach (var action in Actions)
{ {
action.ApplyAction(context, initMatchRes, condMatchRes); action.ApplyAction(context, initMatchRes?.BackReferences, condBackReferences);
} }
} }
} }

View File

@ -0,0 +1,53 @@
// 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.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public class BackReferenceCollection
{
private List<string> _backReferences = new List<string>();
public BackReferenceCollection(GroupCollection references)
{
if (references != null)
{
for (var i = 0; i < references.Count; i++)
{
_backReferences.Add(references[i].Value);
}
}
}
public BackReferenceCollection(string reference)
{
_backReferences.Add(reference);
}
public string this[int index]
{
get
{
if (index < _backReferences.Count)
{
return _backReferences[index];
}
else
{
throw new IndexOutOfRangeException($"Cannot access back reference at index {index}. Only {_backReferences.Count} back references were captured.");
}
}
}
public void Add(BackReferenceCollection references)
{
if (references != null)
{
_backReferences.AddRange(references._backReferences);
}
}
}
}

View File

@ -9,9 +9,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
public UrlMatch Match { get; set; } public UrlMatch Match { get; set; }
public bool OrNext { get; set; } public bool OrNext { get; set; }
public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public MatchResults Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
var pattern = Input.Evaluate(context, ruleMatch, condMatch); var pattern = Input.Evaluate(context, ruleBackReferences, conditionBackReferences);
return Match.Evaluate(pattern, context); return Match.Evaluate(pattern, context);
} }
} }

View File

@ -7,10 +7,15 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public static class ConditionHelper public static class ConditionHelper
{ {
public static MatchResults Evaluate(IEnumerable<Condition> conditions, RewriteContext context, BackReferenceCollection backReferences)
public static MatchResults Evaluate(IEnumerable<Condition> conditions, RewriteContext context, MatchResults ruleMatch)
{ {
MatchResults prevCond = null; return Evaluate(conditions, context, backReferences, trackAllCaptures: false);
}
public static MatchResults Evaluate(IEnumerable<Condition> conditions, RewriteContext context, BackReferenceCollection backReferences, bool trackAllCaptures)
{
BackReferenceCollection prevBackReferences = null;
MatchResults condResult = null;
var orSucceeded = false; var orSucceeded = false;
foreach (var condition in conditions) foreach (var condition in conditions)
{ {
@ -24,18 +29,27 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
continue; continue;
} }
prevCond = condition.Evaluate(context, ruleMatch, prevCond); condResult = condition.Evaluate(context, backReferences, prevBackReferences);
var currentBackReferences = condResult.BackReferences;
if (condition.OrNext) if (condition.OrNext)
{ {
orSucceeded = prevCond.Success; orSucceeded = condResult.Success;
} }
else if (!prevCond.Success) else if (!condResult.Success)
{ {
return prevCond; return condResult;
} }
if (condResult.Success && trackAllCaptures && prevBackReferences!= null)
{
prevBackReferences.Add(currentBackReferences);
currentBackReferences = prevBackReferences;
}
prevBackReferences = currentBackReferences;
} }
return prevCond;
return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success }; ;
} }
} }
} }

View File

@ -13,16 +13,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
public UrlMatch InitialMatch { get; } public UrlMatch InitialMatch { get; }
public IList<Condition> Conditions { get; } public IList<Condition> Conditions { get; }
public UrlAction Action { get; } public UrlAction Action { get; }
public bool TrackAllCaptures { get; }
public IISUrlRewriteRule(string name, public IISUrlRewriteRule(string name,
UrlMatch initialMatch, UrlMatch initialMatch,
IList<Condition> conditions, IList<Condition> conditions,
UrlAction action) UrlAction action,
bool trackAllCaptures)
{ {
Name = name; Name = name;
InitialMatch = initialMatch; InitialMatch = initialMatch;
Conditions = conditions; Conditions = conditions;
Action = action; Action = action;
TrackAllCaptures = trackAllCaptures;
} }
public virtual void ApplyRule(RewriteContext context) public virtual void ApplyRule(RewriteContext context)
@ -46,11 +49,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
return; return;
} }
MatchResults condMatchRes = null; MatchResults condResult = null;
if (Conditions != null) if (Conditions != null)
{ {
condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchResults); condResult = ConditionHelper.Evaluate(Conditions, context, initMatchResults.BackReferences, TrackAllCaptures);
if (!condMatchRes.Success) if (!condResult.Success)
{ {
context.Logger?.UrlRewriteDidNotMatchRule(Name); context.Logger?.UrlRewriteDidNotMatchRule(Name);
return; return;
@ -59,7 +62,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
context.Logger?.UrlRewriteMatchedRule(Name); context.Logger?.UrlRewriteMatchedRule(Name);
// at this point we know the rule passed, evaluate the replacement. // at this point we know the rule passed, evaluate the replacement.
Action.ApplyAction(context, initMatchResults, condMatchRes); Action.ApplyAction(context, initMatchResults?.BackReferences, condResult?.BackReferences);
} }
} }
} }

View File

@ -110,19 +110,15 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
var grouping = ParseEnum(conditions, RewriteTags.LogicalGrouping, LogicalGrouping.MatchAll); var grouping = ParseEnum(conditions, RewriteTags.LogicalGrouping, LogicalGrouping.MatchAll);
var trackAllCaptures = ParseBool(conditions, RewriteTags.TrackAllCaptures, defaultValue: false); var trackAllCaptures = ParseBool(conditions, RewriteTags.TrackAllCaptures, defaultValue: false);
if (trackAllCaptures)
{
throw new NotSupportedException("Support for trackAllCaptures has not been implemented yet");
}
builder.AddUrlConditions(grouping, trackAllCaptures); builder.AddUrlConditions(grouping, trackAllCaptures);
foreach (var cond in conditions.Elements(RewriteTags.Add)) foreach (var cond in conditions.Elements(RewriteTags.Add))
{ {
ParseCondition(cond, builder, patternSyntax); ParseCondition(cond, builder, patternSyntax, trackAllCaptures);
} }
} }
private void ParseCondition(XElement condition, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) private void ParseCondition(XElement condition, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax, bool trackAllCaptures)
{ {
var ignoreCase = ParseBool(condition, RewriteTags.IgnoreCase, defaultValue: true); var ignoreCase = ParseBool(condition, RewriteTags.IgnoreCase, defaultValue: true);
var negate = ParseBool(condition, RewriteTags.Negate, defaultValue: false); var negate = ParseBool(condition, RewriteTags.Negate, defaultValue: false);
@ -138,7 +134,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
try try
{ {
var input = _inputParser.ParseInputString(parsedInputString); var input = _inputParser.ParseInputString(parsedInputString);
builder.AddUrlCondition(input, parsedPatternString, patternSyntax, matchType, ignoreCase, negate); builder.AddUrlCondition(input, parsedPatternString, patternSyntax, matchType, ignoreCase, negate, trackAllCaptures);
} }
catch (FormatException formatException) catch (FormatException formatException)
{ {

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
private IList<Condition> _conditions; private IList<Condition> _conditions;
private UrlAction _action; private UrlAction _action;
private bool _matchAny; private bool _matchAny;
private bool _trackAllCaptures;
public IISUrlRewriteRule Build() public IISUrlRewriteRule Build()
{ {
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match"); throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match");
} }
return new IISUrlRewriteRule(Name, _initialMatch, _conditions, _action); return new IISUrlRewriteRule(Name, _initialMatch, _conditions, _action, _trackAllCaptures);
} }
public void AddUrlAction( public void AddUrlAction(
@ -85,12 +86,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
} }
} }
public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate) public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate, bool trackAllCaptures)
{ {
// If there are no conditions specified // If there are no conditions specified
if (_conditions == null) if (_conditions == null)
{ {
AddUrlConditions(LogicalGrouping.MatchAll, trackAllCaptures: false); AddUrlConditions(LogicalGrouping.MatchAll, trackAllCaptures);
} }
switch (patternSyntax) switch (patternSyntax)
@ -148,6 +149,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{ {
_conditions = new List<Condition>(); _conditions = new List<Condition>();
_matchAny = logicalGrouping == LogicalGrouping.MatchAny; _matchAny = logicalGrouping == LogicalGrouping.MatchAny;
_trackAllCaptures = trackAllCaptures;
} }
} }
} }

View File

@ -7,10 +7,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class MatchResults public class MatchResults
{ {
public static readonly MatchResults EmptySuccess = new MatchResults { BackReference = null, Success = true }; public static readonly MatchResults EmptySuccess = new MatchResults { Success = true };
public static readonly MatchResults EmptyFailure = new MatchResults { BackReference = null, Success = false }; public static readonly MatchResults EmptyFailure = new MatchResults { Success = false };
public GroupCollection BackReference { get; set; }
public bool Success { get; set; } public bool Success { get; set; }
public BackReferenceCollection BackReferences { get; set; }
} }
} }

View File

@ -13,11 +13,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
PatternSegments = patternSegments; PatternSegments = patternSegments;
} }
public string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
foreach (var pattern in PatternSegments) foreach (var pattern in PatternSegments)
{ {
context.Builder.Append(pattern.Evaluate(context, ruleMatch, condMatch)); context.Builder.Append(pattern.Evaluate(context, ruleBackReferences, conditionBackReferences));
} }
var retVal = context.Builder.ToString(); var retVal = context.Builder.ToString();
context.Builder.Clear(); context.Builder.Clear();

View File

@ -5,6 +5,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class PatternSegment public abstract class PatternSegment
{ {
public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences);
} }
} }

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_index = index; _index = index;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return condMatch?.BackReference[_index].Value; return conditionBackReferences?[_index];
} }
} }
} }

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
} }
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReference)
{ {
switch (_portion) switch (_portion)
{ {

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_header = header; _header = header;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.Headers[_header]; return context.HttpContext.Request.Headers[_header];
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
// Note: Mod rewrite pattern matches on lower case "on" and "off" // Note: Mod rewrite pattern matches on lower case "on" and "off"
// while IIS looks for capitalized "ON" and "OFF" // while IIS looks for capitalized "ON" and "OFF"
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.IsHttps ? "on" : "off"; return context.HttpContext.Request.IsHttps ? "on" : "off";
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
// Note: Mod rewrite pattern matches on lower case "on" and "off" // Note: Mod rewrite pattern matches on lower case "on" and "off"
// while IIS looks for capitalized "ON" and "OFF" // while IIS looks for capitalized "ON" and "OFF"
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.IsHttps ? "ON" : "OFF"; return context.HttpContext.Request.IsHttps ? "ON" : "OFF";
} }

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
public class IsIPV6Segment : PatternSegment public class IsIPV6Segment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
if (context.HttpContext.Connection.RemoteIpAddress == null) if (context.HttpContext.Connection.RemoteIpAddress == null)
{ {

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_literal = literal; _literal = literal;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return _literal; return _literal;
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class LocalAddressSegment : PatternSegment public class LocalAddressSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Connection.LocalIpAddress?.ToString(); return context.HttpContext.Connection.LocalIpAddress?.ToString();
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class LocalPortSegment : PatternSegment public class LocalPortSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Connection.LocalPort.ToString(CultureInfo.InvariantCulture); return context.HttpContext.Connection.LocalPort.ToString(CultureInfo.InvariantCulture);
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class QueryStringSegment : PatternSegment public class QueryStringSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackRefernces, BackReferenceCollection conditionBackReferences)
{ {
var queryString = context.HttpContext.Request.QueryString.ToString(); var queryString = context.HttpContext.Request.QueryString.ToString();

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class RemoteAddressSegment : PatternSegment public class RemoteAddressSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Connection.RemoteIpAddress?.ToString(); return context.HttpContext.Connection.RemoteIpAddress?.ToString();
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class RemotePortSegment : PatternSegment public class RemotePortSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); return context.HttpContext.Connection.RemotePort.ToString(CultureInfo.InvariantCulture);
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class RequestFileNameSegment : PatternSegment public class RequestFileNameSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.Path; return context.HttpContext.Request.Path;
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class RequestMethodSegment : PatternSegment public class RequestMethodSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.Method; return context.HttpContext.Request.Method;
} }

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_index = index; _index = index;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return ruleMatch?.BackReference[_index].Value; return ruleBackReferences[_index];
} }
} }
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class SchemeSegment : PatternSegment public class SchemeSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.Scheme; return context.HttpContext.Request.Scheme;
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class ServerProtocolSegment : PatternSegment public class ServerProtocolSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Features.Get<IHttpRequestFeature>()?.Protocol; return context.HttpContext.Features.Get<IHttpRequestFeature>()?.Protocol;
} }

View File

@ -14,13 +14,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_pattern = pattern; _pattern = pattern;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
// PERF as we share the string builder across the context, we need to make a new one here to evaluate // PERF as we share the string builder across the context, we need to make a new one here to evaluate
// lowercase segments. // lowercase segments.
var tempBuilder = context.Builder; var tempBuilder = context.Builder;
context.Builder = new StringBuilder(64); context.Builder = new StringBuilder(64);
var pattern = _pattern.Evaluate(context, ruleMatch, condMatch); var pattern = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences);
context.Builder = tempBuilder; context.Builder = tempBuilder;
return pattern.ToLowerInvariant(); return pattern.ToLowerInvariant();
} }

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_pattern = pattern; _pattern = pattern;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
var oldBuilder = context.Builder; var oldBuilder = context.Builder;
// PERF // PERF
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
// we provided a new string builder and evaluate the new pattern, // we provided a new string builder and evaluate the new pattern,
// and restore it after evaluation. // and restore it after evaluation.
context.Builder = new StringBuilder(64); context.Builder = new StringBuilder(64);
var pattern = _pattern.Evaluate(context, ruleMatch, condMatch); var pattern = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences);
context.Builder = oldBuilder; context.Builder = oldBuilder;
return UrlEncoder.Default.Encode(pattern); return UrlEncoder.Default.Encode(pattern);
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class UrlSegment : PatternSegment public class UrlSegment : PatternSegment
{ {
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
return context.HttpContext.Request.Path; return context.HttpContext.Request.Path;
} }

View File

@ -6,6 +6,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
public abstract class UrlAction public abstract class UrlAction
{ {
protected Pattern Url { get; set; } protected Pattern Url { get; set; }
public abstract void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences);
} }
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class AbortAction : UrlAction public class AbortAction : UrlAction
{ {
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
context.HttpContext.Abort(); context.HttpContext.Abort();
context.Result = RuleResult.EndResponse; context.Result = RuleResult.EndResponse;

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
public bool Secure { get; set; } public bool Secure { get; set; }
public bool HttpOnly { get; set; } public bool HttpOnly { get; set; }
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
var options = GetOrCreateOptions(); var options = GetOrCreateOptions();
context.HttpContext.Response.Cookies.Append(Name, Value ?? string.Empty, options); context.HttpContext.Response.Cookies.Append(Name, Value ?? string.Empty, options);

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class ForbiddenAction : UrlAction public class ForbiddenAction : UrlAction
{ {
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
context.Result = RuleResult.EndResponse; context.Result = RuleResult.EndResponse;

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class GoneAction : UrlAction public class GoneAction : UrlAction
{ {
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone; context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone;
context.Result = RuleResult.EndResponse; context.Result = RuleResult.EndResponse;

View File

@ -41,9 +41,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
} }
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
var pattern = Url.Evaluate(context, ruleMatch, condMatch); var pattern = Url.Evaluate(context, ruleBackReferences, conditionBackReferences);
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
var pathBase = context.HttpContext.Request.PathBase; var pathBase = context.HttpContext.Request.PathBase;
if (EscapeBackReferences) if (EscapeBackReferences)

View File

@ -43,9 +43,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
} }
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
var pattern = Url.Evaluate(context, ruleMatch, condMatch); var pattern = Url.Evaluate(context, ruleBackReferences, conditionBackReferences);
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
if (string.IsNullOrEmpty(pattern)) if (string.IsNullOrEmpty(pattern))

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
Result = result; Result = result;
} }
// Explicitly say that nothing happens // Explicitly say that nothing happens
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{ {
context.Result = Result; context.Result = Result;
} }

View File

@ -18,7 +18,15 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public override MatchResults Evaluate(string pattern, RewriteContext context) public override MatchResults Evaluate(string pattern, RewriteContext context)
{ {
var pathMatch = string.Compare(pattern, _stringMatch, _ignoreCase); var pathMatch = string.Compare(pattern, _stringMatch, _ignoreCase);
return new MatchResults { Success = ((pathMatch == 0) != Negate) }; var success = ((pathMatch == 0) != Negate);
if (success)
{
return new MatchResults { Success = success, BackReferences = new BackReferenceCollection(pattern) };
}
else
{
return MatchResults.EmptyFailure;
}
} }
} }
} }

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public override MatchResults Evaluate(string pattern, RewriteContext context) public override MatchResults Evaluate(string pattern, RewriteContext context)
{ {
var res = _match.Match(pattern); var res = _match.Match(pattern);
return new MatchResults { BackReference = res.Groups, Success = (res.Success != Negate) }; return new MatchResults { BackReferences = new BackReferenceCollection(res.Groups), Success = (res.Success != Negate) };
} }
} }
} }

View File

@ -136,10 +136,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
Assert.Equal("/", response); Assert.Equal("/", response);
} }
[Fact] [Theory]
public async Task Invoke_BackReferencesShouldBeApplied() [InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php", "/homepage.php")]
[InlineData("http://www.foo.org/pages/homepage.aspx", @"RewriteRule (.*)/(.*)\.aspx $2.php", "/homepage.php")]
public async Task Invoke_BackReferencesShouldBeApplied(string url, string rule, string expected)
{ {
var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule (.*)\.aspx $1.php")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(rule));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -148,9 +150,9 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
}); });
var server = new TestServer(builder); var server = new TestServer(builder);
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/homepage.aspx"); var response = await server.CreateClient().GetStringAsync(url);
Assert.Equal("/homepage.php", response); Assert.Equal(expected, response);
} }
[Theory] [Theory]

View File

@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
) )
{ {
return new IISUrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions, return new IISUrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url), queryStringAppend: false)); new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url), queryStringAppend: false), trackAllCaptures: false);
} }
// TODO make rules comparable? // TODO make rules comparable?

View File

@ -264,26 +264,5 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var ex = Assert.Throws<FormatException>(() => new UrlRewriteFileParser().Parse(new StringReader(input))); var ex = Assert.Throws<FormatException>(() => new UrlRewriteFileParser().Parse(new StringReader(input)));
Assert.Equal(expected, ex.Message); Assert.Equal(expected, ex.Message);
} }
[Theory]
[InlineData(
@"<rewrite>
<rules>
<rule name=""Remove trailing slash"">
<match url = ""(.*)/$""/>
<conditions trackAllCaptures=""true"">
<add input=""{REQUEST_FILENAME}""/>
</conditions>
<action type=""Redirect"" url =""{R:1}"" />
</rule>
</rules>
</rewrite>",
"Support for trackAllCaptures has not been implemented yet")]
public void ThrowNotSupportedExceptionWithCorrectMessage(string input, string expected)
{
// Arrange, Act, Assert
var ex = Assert.Throws<NotSupportedException>(() => new UrlRewriteFileParser().Parse(new StringReader(input)));
Assert.Equal(expected, ex.Message);
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void EvaluateBackReferenceRule(string testString, string expected) public void EvaluateBackReferenceRule(string testString, string expected)
{ {
var middle = new InputParser().ParseInputString(testString); var middle = new InputParser().ParseInputString(testString);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void EvaluatToLowerRule(string testString, string expected) public void EvaluatToLowerRule(string testString, string expected)
{ {
var middle = new InputParser().ParseInputString(testString); var middle = new InputParser().ParseInputString(testString);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void EvaluatUriEncodeRule(string testString, string expected) public void EvaluatUriEncodeRule(string testString, string expected)
{ {
var middle = new InputParser().ParseInputString(testString); var middle = new InputParser().ParseInputString(testString);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -95,16 +95,16 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
return new RewriteContext { HttpContext = context, StaticFileProvider = null }; return new RewriteContext { HttpContext = context, StaticFileProvider = null };
} }
private MatchResults CreateTestRuleMatch() private BackReferenceCollection CreateTestRuleBackReferences()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success }; return new BackReferenceCollection(match.Groups);
} }
private MatchResults CreateTestCondMatch() private BackReferenceCollection CreateTestCondBackReferences()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success }; return new BackReferenceCollection(match.Groups);
} }
} }
} }

View File

@ -364,7 +364,92 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var response = await server.CreateClient().GetAsync("hey/hello"); var response = await server.CreateClient().GetAsync("hey/hello");
Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString); ; Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString);
}
[Fact]
public async Task VerifyTrackAllCaptures()
{
var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules>
<rule name=""Test"">
<match url=""(.*)"" ignoreCase=""false"" />
<conditions trackAllCaptures = ""true"" >
<add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" />
<add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" />
</conditions>
<action type=""Redirect"" url =""blogposts/{C:1}/{C:4}"" />
<!--rewrite action uses back - references to both conditions -->
</rule>
</rules>
</rewrite>"));
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRewriter(options);
});
var server = new TestServer(builder);
var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc");
Assert.Equal("/blogposts/article/abc", response.Headers.Location.OriginalString);
}
[Fact]
public async Task VerifyTrackAllCapturesRuleAndConditionCapture()
{
var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules>
<rule name=""Test"">
<match url=""(.*)"" ignoreCase=""false"" />
<conditions trackAllCaptures = ""true"" >
<add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" />
<add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" />
</conditions>
<action type=""Redirect"" url =""blog/{R:0}/{C:4}"" />
<!--rewrite action uses back - references to both conditions -->
</rule>
</rules>
</rewrite>"));
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRewriter(options);
});
var server = new TestServer(builder);
var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc");
Assert.Equal("/blog/article/23/abc", response.Headers.Location.OriginalString);
}
[Fact]
public async Task ThrowIndexOutOfRangeExceptionWithCorrectMessage()
{
// Arrange, Act, Assert
var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules>
<rule name=""Test"">
<match url=""(.*)"" ignoreCase=""false"" />
<conditions trackAllCaptures = ""true"" >
<add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" />
<add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" />
</conditions>
<action type=""Redirect"" url =""blog/{R:0}/{C:9}"" />
<!--rewrite action uses back - references to both conditions -->
</rule>
</rules>
</rewrite>"));
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRewriter(options);
});
var server = new TestServer(builder);
var ex = await Assert.ThrowsAsync<IndexOutOfRangeException>(() => server.CreateClient().GetAsync("article/23?p1=123&p2=abc"));
Assert.Equal("Cannot access back reference at index 9. Only 5 back references were captured.", ex.Message);
} }
} }
} }

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
// Arrange and Act // Arrange and Act
var testParserContext = new ParserContext("test"); var testParserContext = new ParserContext("test");
var serverVar = ServerVariables.FindServerVariable(variable, testParserContext); var serverVar = ServerVariables.FindServerVariable(variable, testParserContext);
var lookup = serverVar.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch()); var lookup = serverVar.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences);
// Assert // Assert
Assert.Equal(expected, lookup); Assert.Equal(expected, lookup);
} }
@ -53,13 +53,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
private MatchResults CreateTestRuleMatch() private MatchResults CreateTestRuleMatch()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success }; return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
} }
private MatchResults CreateTestCondMatch() private MatchResults CreateTestCondMatch()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success }; return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
} }
[Fact] [Fact]
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var rewriteContext = new RewriteContext { HttpContext = context }; var rewriteContext = new RewriteContext { HttpContext = context };
var testParserContext = new ParserContext("test"); var testParserContext = new ParserContext("test");
var serverVar = ServerVariables.FindServerVariable("QUERY_STRING", testParserContext); var serverVar = ServerVariables.FindServerVariable("QUERY_STRING", testParserContext);
var lookup = serverVar.Evaluate(rewriteContext, CreateTestRuleMatch(), CreateTestCondMatch()); var lookup = serverVar.Evaluate(rewriteContext, CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences);
Assert.Equal(string.Empty, lookup); Assert.Equal(string.Empty, lookup);
} }

View File

@ -49,5 +49,28 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
rules.FirstOrDefault().ApplyRule(context); rules.FirstOrDefault().ApplyRule(context);
Assert.Equal(RuleResult.ContinueRules, context.Result); Assert.Equal(RuleResult.ContinueRules, context.Result);
} }
[Fact]
public void ApplyRule_TrackAllCaptures()
{
var xml = new StringReader(@"<rewrite>
<rules>
<rule name=""Test"">
<match url = ""(.*)"" ignoreCase=""false"" />
<conditions trackAllCaptures = ""true"" >
<add input = ""{REQUEST_URI}"" pattern = ""^/([a-zA-Z]+)/([0-9]+)/$"" />
</conditions >
<action type = ""None""/>
</rule>
</rules>
</rewrite>");
var rules = new UrlRewriteFileParser().Parse(xml);
Assert.Equal(rules.Count, 1);
Assert.True(rules[0].TrackAllCaptures);
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
rules.FirstOrDefault().ApplyRule(context);
Assert.Equal(RuleResult.ContinueRules, context.Result);
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
var segment = new ConditionMatchSegment(index); var segment = new ConditionMatchSegment(index);
// Act // Act
var results = segment.Evaluate(null, null, condMatch); var results = segment.Evaluate(null, null, condMatch.BackReferences);
// Assert // Assert
Assert.Equal(expected, results); Assert.Equal(expected, results);
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
private static MatchResults CreateTestMatch() private static MatchResults CreateTestMatch()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults {BackReference = match.Groups, Success = match.Success}; return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
} }
} }
} }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
var segment = new RuleMatchSegment(index); var segment = new RuleMatchSegment(index);
// Act // Act
var results = segment.Evaluate(null, ruleMatch, null); var results = segment.Evaluate(null, ruleMatch.BackReferences, null);
// Assert // Assert
Assert.Equal(expected, results); Assert.Equal(expected, results);
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
private static MatchResults CreateTestMatch() private static MatchResults CreateTestMatch()
{ {
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success }; return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
} }
} }
} }

View File

@ -5,6 +5,7 @@
}, },
"dependencies": { "dependencies": {
"dotnet-test-xunit": "2.2.0-*", "dotnet-test-xunit": "2.2.0-*",
"Microsoft.AspNetCore.Hosting": "1.2.0-*",
"Microsoft.AspNetCore.Rewrite": "1.1.0-*", "Microsoft.AspNetCore.Rewrite": "1.1.0-*",
"Microsoft.AspNetCore.TestHost": "1.2.0-*", "Microsoft.AspNetCore.TestHost": "1.2.0-*",
"Microsoft.Extensions.Logging.Testing": "1.2.0-*", "Microsoft.Extensions.Logging.Testing": "1.2.0-*",