Make condition collection a first class citizen (#198)

This commit is contained in:
David Peden 2017-02-13 17:35:20 -06:00 committed by Mikael Mengistu
parent b254f42d19
commit ae7d0b9582
24 changed files with 423 additions and 224 deletions

View File

@ -22,7 +22,6 @@ namespace RewriteSample
.AddApacheModRewrite(env.ContentRootFileProvider, "Rewrite.txt");
app.UseRewriter(options);
app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}"));
}

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
BackReferenceCollection condBackReferences = null;
if (Conditions != null)
{
var condResult = ConditionHelper.Evaluate(Conditions, context, initMatchRes.BackReferences);
var condResult = ConditionEvaluator.Evaluate(Conditions, context, initMatchRes.BackReferences);
if (!condResult.Success)
{
context.Logger?.ModRewriteDidNotMatchRule();
@ -51,4 +51,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
}
}
}
}
}

View File

@ -1,7 +1,7 @@
// 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.
namespace Microsoft.AspNetCore.Rewrite.Internal
namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
{
public class Condition
{
@ -15,4 +15,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
return Match.Evaluate(pattern, context);
}
}
}
}

View File

@ -3,9 +3,9 @@
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Rewrite.Internal
namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
{
public static class ConditionHelper
public static class ConditionEvaluator
{
public static MatchResults Evaluate(IEnumerable<Condition> conditions, RewriteContext context, BackReferenceCollection backReferences)
{
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
return condResult;
}
if (condResult.Success && trackAllCaptures && prevBackReferences!= null)
if (condResult.Success && trackAllCaptures && prevBackReferences != null)
{
prevBackReferences.Add(currentBackReferences);
currentBackReferences = prevBackReferences;
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
prevBackReferences = currentBackReferences;
}
return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success }; ;
return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success };
}
}
}
}

View File

@ -222,4 +222,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite
}
}
}
}
}

View File

@ -0,0 +1,17 @@
// 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.
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public class Condition
{
public Pattern Input { get; set; }
public UrlMatch Match { get; set; }
public MatchResults Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{
var pattern = Input.Evaluate(context, ruleBackReferences, conditionBackReferences);
return Match.Evaluate(pattern, context);
}
}
}

View File

@ -0,0 +1,68 @@
// 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;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public class ConditionCollection : IEnumerable<Condition>
{
private readonly List<Condition> _conditions = new List<Condition>();
public LogicalGrouping Grouping { get; }
public bool TrackAllCaptures { get; }
public ConditionCollection()
:this(LogicalGrouping.MatchAll, trackAllCaptures: false)
{
}
public ConditionCollection(LogicalGrouping grouping, bool trackAllCaptures)
{
Grouping = grouping;
TrackAllCaptures = trackAllCaptures;
}
public int Count => _conditions.Count;
public Condition this[int index]
{
get
{
if (index < _conditions.Count)
{
return _conditions[index];
}
throw new IndexOutOfRangeException($"Cannot access condition at index {index}. Only {_conditions.Count} conditions were captured.");
}
}
public void Add(Condition condition)
{
if (condition != null)
{
_conditions.Add(condition);
}
}
public void AddConditions(IEnumerable<Condition> conditions)
{
if (conditions != null)
{
_conditions.AddRange(conditions);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return _conditions.GetEnumerator();
}
public IEnumerator<Condition> GetEnumerator()
{
return _conditions.GetEnumerator();
}
}
}

View File

@ -0,0 +1,49 @@
// 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.
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public static class ConditionEvaluator
{
public static MatchResults Evaluate(ConditionCollection conditions, RewriteContext context, BackReferenceCollection backReferences)
{
BackReferenceCollection prevBackReferences = null;
MatchResults condResult = null;
var orSucceeded = false;
foreach (var condition in conditions)
{
if (orSucceeded && conditions.Grouping == LogicalGrouping.MatchAny)
{
continue;
}
if (orSucceeded)
{
orSucceeded = false;
continue;
}
condResult = condition.Evaluate(context, backReferences, prevBackReferences);
var currentBackReferences = condResult.BackReferences;
if (conditions.Grouping == LogicalGrouping.MatchAny)
{
orSucceeded = condResult.Success;
}
else if (!condResult.Success)
{
return condResult;
}
if (condResult.Success && conditions.TrackAllCaptures && prevBackReferences!= null)
{
prevBackReferences.Add(currentBackReferences);
currentBackReferences = prevBackReferences;
}
prevBackReferences = currentBackReferences;
}
return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success };
}
}
}

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Logging;
@ -11,32 +10,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public string Name { get; }
public UrlMatch InitialMatch { get; }
public IList<Condition> Conditions { get; }
public ConditionCollection Conditions { get; }
public UrlAction Action { get; }
public bool TrackAllCaptures { get; }
public bool Global { get; }
public IISUrlRewriteRule(string name,
UrlMatch initialMatch,
IList<Condition> conditions,
UrlAction action,
bool trackAllCaptures)
: this(name, initialMatch, conditions, action, trackAllCaptures, false)
ConditionCollection conditions,
UrlAction action)
: this(name, initialMatch, conditions, action, false)
{
}
public IISUrlRewriteRule(string name,
UrlMatch initialMatch,
IList<Condition> conditions,
ConditionCollection conditions,
UrlAction action,
bool trackAllCaptures,
bool global)
{
Name = name;
InitialMatch = initialMatch;
Conditions = conditions;
Action = action;
TrackAllCaptures = trackAllCaptures;
Global = global;
}
@ -64,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
MatchResults condResult = null;
if (Conditions != null)
{
condResult = ConditionHelper.Evaluate(Conditions, context, initMatchResults.BackReferences, TrackAllCaptures);
condResult = ConditionEvaluator.Evaluate(Conditions, context, initMatchResults.BackReferences);
if (!condResult.Success)
{
context.Logger?.UrlRewriteDidNotMatchRule(Name);

View File

@ -28,9 +28,20 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
/// compare to the condition. Can contain server variables, back references, etc.
/// </summary>
/// <param name="testString"></param>
/// <param name="global"></param>
/// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns>
public Pattern ParseInputString(string testString, bool global)
public Pattern ParseInputString(string testString)
{
return ParseInputString(testString, UriMatchPart.Path);
}
/// <summary>
/// Creates a pattern, which is a template to create a new test string to
/// compare to the condition. Can contain server variables, back references, etc.
/// </summary>
/// <param name="testString"></param>
/// <param name="uriMatchPart">When testString evaluates to a URL segment, specify which part of the URI to evaluate.</param>
/// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns>
public Pattern ParseInputString(string testString, UriMatchPart uriMatchPart)
{
if (testString == null)
{
@ -38,10 +49,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
}
var context = new ParserContext(testString);
return ParseString(context, global);
return ParseString(context, uriMatchPart);
}
private Pattern ParseString(ParserContext context, bool global)
private Pattern ParseString(ParserContext context, UriMatchPart uriMatchPart)
{
var results = new List<PatternSegment>();
while (context.Next())
@ -54,7 +65,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
// missing {
throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index));
}
ParseParameter(context, results, global);
ParseParameter(context, results, uriMatchPart);
}
else if (context.Current == CloseBrace)
{
@ -70,7 +81,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
return new Pattern(results);
}
private void ParseParameter(ParserContext context, IList<PatternSegment> results, bool global)
private void ParseParameter(ParserContext context, IList<PatternSegment> results, UriMatchPart uriMatchPart)
{
context.Mark();
// Four main cases:
@ -86,7 +97,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
// This is just a server variable, so we do a lookup and verify the server variable exists.
parameter = context.Capture();
results.Add(ServerVariables.FindServerVariable(parameter, context, global));
results.Add(ServerVariables.FindServerVariable(parameter, context, uriMatchPart));
return;
}
else if (context.Current == Colon)
@ -98,7 +109,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
case "ToLower":
{
var pattern = ParseString(context, global);
var pattern = ParseString(context, uriMatchPart);
results.Add(new ToLowerSegment(pattern));
// at this point, we expect our context to be on the ending closing brace,
@ -116,7 +127,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
}
case "UrlEncode":
{
var pattern = ParseString(context, global);
var pattern = ParseString(context, uriMatchPart);
results.Add(new UrlEncodeSegment(pattern));
if (context.Current != CloseBrace)
@ -141,7 +152,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
var rewriteMap = _rewriteMaps?[parameter];
if (rewriteMap != null)
{
var pattern = ParseString(context, global);
var pattern = ParseString(context, uriMatchPart);
results.Add(new RewriteMapSegment(rewriteMap, pattern));
return;
}

View File

@ -14,10 +14,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
/// </summary>
/// <param name="serverVariable">The server variable</param>
/// <param name="context">The parser context which is utilized when an exception is thrown</param>
/// <param name="global">Indicates if the rule being parsed is a global rule</param>
/// <param name="uriMatchPart">Indicates whether the full URI or the path should be evaluated for URL segments</param>
/// <exception cref="FormatException">Thrown when the server variable is unknown</exception>
/// <returns>The matching <see cref="PatternSegment"/></returns>
public static PatternSegment FindServerVariable(string serverVariable, ParserContext context, bool global)
public static PatternSegment FindServerVariable(string serverVariable, ParserContext context, UriMatchPart uriMatchPart)
{
switch (serverVariable)
{
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
case "HTTP_CONNECTION":
return new HeaderSegment(HeaderNames.Connection);
case "HTTP_URL":
return global ? (PatternSegment)new GlobalRuleUrlSegment() : (PatternSegment)new UrlSegment();
return new UrlSegment(uriMatchPart);
case "HTTPS":
return new IsHttpsUrlSegment();
case "LOCAL_ADDR":
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
case "REQUEST_FILENAME":
return new RequestFileNameSegment();
case "REQUEST_URI":
return global ? (PatternSegment)new GlobalRuleUrlSegment() : (PatternSegment)new UrlSegment();
return new UrlSegment(uriMatchPart);
default:
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(serverVariable, context.Index));
}

View File

@ -0,0 +1,22 @@
// 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.Rewrite.Internal.UrlMatches;
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public class UriMatchCondition : Condition
{
public UriMatchCondition(InputParser inputParser, string input, string pattern, UriMatchPart uriMatchPart, bool ignoreCase, bool negate)
{
var regex = new Regex(
pattern,
ignoreCase ? RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase : RegexOptions.CultureInvariant | RegexOptions.Compiled,
TimeSpan.FromMilliseconds(1));
Input = inputParser.ParseInputString(input, uriMatchPart);
Match = new RegexMatch(regex, negate);
}
}
}

View File

@ -0,0 +1,11 @@
// 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.
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public enum UriMatchPart
{
Full,
Path
}
}

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
@ -45,17 +46,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
foreach (var rule in rules.Elements(RewriteTags.Rule))
{
var builder = new UrlRewriteRuleBuilder();
ParseRuleAttributes(rule, builder, global);
var builder = new UrlRewriteRuleBuilder { Global = global };
ParseRuleAttributes(rule, builder);
if (builder.Enabled)
{
result.Add(builder.Build(global));
result.Add(builder.Build());
}
}
}
private void ParseRuleAttributes(XElement rule, UrlRewriteRuleBuilder builder, bool global)
private void ParseRuleAttributes(XElement rule, UrlRewriteRuleBuilder builder)
{
builder.Name = rule.Attribute(RewriteTags.Name)?.Value;
@ -84,8 +85,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
}
ParseMatch(match, builder, patternSyntax);
ParseConditions(rule.Element(RewriteTags.Conditions), builder, patternSyntax, global);
ParseUrlAction(action, builder, stopProcessing, global);
ParseConditions(rule.Element(RewriteTags.Conditions), builder, patternSyntax);
ParseUrlAction(action, builder, stopProcessing);
}
private void ParseMatch(XElement match, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
@ -101,7 +102,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax);
}
private void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax, bool global)
private void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
{
if (conditions == null)
{
@ -110,32 +111,76 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
var grouping = ParseEnum(conditions, RewriteTags.LogicalGrouping, LogicalGrouping.MatchAll);
var trackAllCaptures = ParseBool(conditions, RewriteTags.TrackAllCaptures, defaultValue: false);
builder.AddUrlConditions(grouping, trackAllCaptures);
builder.ConfigureConditionBehavior(grouping, trackAllCaptures);
foreach (var cond in conditions.Elements(RewriteTags.Add))
{
ParseCondition(cond, builder, patternSyntax, trackAllCaptures, global);
ParseCondition(cond, builder, patternSyntax);
}
}
private void ParseCondition(XElement condition, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax, bool trackAllCaptures, bool global)
private void ParseCondition(XElement conditionElement, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
{
var ignoreCase = ParseBool(condition, RewriteTags.IgnoreCase, defaultValue: true);
var negate = ParseBool(condition, RewriteTags.Negate, defaultValue: false);
var matchType = ParseEnum(condition, RewriteTags.MatchType, MatchType.Pattern);
var parsedInputString = condition.Attribute(RewriteTags.Input)?.Value;
var ignoreCase = ParseBool(conditionElement, RewriteTags.IgnoreCase, defaultValue: true);
var negate = ParseBool(conditionElement, RewriteTags.Negate, defaultValue: false);
var matchType = ParseEnum(conditionElement, RewriteTags.MatchType, MatchType.Pattern);
var parsedInputString = conditionElement.Attribute(RewriteTags.Input)?.Value;
if (parsedInputString == null)
{
throw new InvalidUrlRewriteFormatException(condition, "Conditions must have an input attribute");
throw new InvalidUrlRewriteFormatException(conditionElement, "Conditions must have an input attribute");
}
var parsedPatternString = condition.Attribute(RewriteTags.Pattern)?.Value;
var input = _inputParser.ParseInputString(parsedInputString, global);
builder.AddUrlCondition(input, parsedPatternString, patternSyntax, matchType, ignoreCase, negate, trackAllCaptures);
var parsedPatternString = conditionElement.Attribute(RewriteTags.Pattern)?.Value;
Condition condition;
switch (patternSyntax)
{
case PatternSyntax.ECMAScript:
{
switch (matchType)
{
case MatchType.Pattern:
{
if (string.IsNullOrEmpty(parsedPatternString))
{
throw new FormatException("Match does not have an associated pattern attribute in condition");
}
condition = new UriMatchCondition(_inputParser, parsedInputString, parsedPatternString, builder.UriMatchPart, ignoreCase, negate);
break;
}
case MatchType.IsDirectory:
{
condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new IsDirectoryMatch(negate) };
break;
}
case MatchType.IsFile:
{
condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new IsFileMatch(negate) };
break;
}
default:
throw new FormatException("Unrecognized matchType");
}
break;
}
case PatternSyntax.Wildcard:
throw new NotSupportedException("Wildcard syntax is not supported");
case PatternSyntax.ExactMatch:
if (string.IsNullOrEmpty(parsedPatternString))
{
throw new FormatException("Match does not have an associated pattern attribute in condition");
}
condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new ExactMatch(ignoreCase, parsedPatternString, negate) };
break;
default:
throw new FormatException("Unrecognized pattern syntax");
}
builder.AddUrlCondition(condition);
}
private void ParseUrlAction(XElement urlAction, UrlRewriteRuleBuilder builder, bool stopProcessing, bool global)
private void ParseUrlAction(XElement urlAction, UrlRewriteRuleBuilder builder, bool stopProcessing)
{
var actionType = ParseEnum(urlAction, RewriteTags.Type, ActionType.None);
UrlAction action;
@ -156,7 +201,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
}
}
var urlPattern = _inputParser.ParseInputString(url, global);
var urlPattern = _inputParser.ParseInputString(url, builder.UriMatchPart);
var appendQuery = ParseBool(urlAction, RewriteTags.AppendQueryString, defaultValue: true);
if (actionType == ActionType.Rewrite)

View File

@ -14,21 +14,21 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
public string Name { get; set; }
public bool Enabled { get; set; }
public bool Global { get; set; }
public UriMatchPart UriMatchPart => Global ? UriMatchPart.Full : UriMatchPart.Path;
private UrlMatch _initialMatch;
private IList<Condition> _conditions;
private ConditionCollection _conditions;
private UrlAction _action;
private bool _matchAny;
private bool _trackAllCaptures;
public IISUrlRewriteRule Build(bool global)
public IISUrlRewriteRule Build()
{
if (_initialMatch == null || _action == null)
{
throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match");
}
return new IISUrlRewriteRule(Name, _initialMatch, _conditions, _action, _trackAllCaptures, global);
return new IISUrlRewriteRule(Name, _initialMatch, _conditions, _action, Global);
}
public void AddUrlAction(UrlAction action)
@ -66,70 +66,31 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
}
}
public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate, bool trackAllCaptures)
public void ConfigureConditionBehavior(LogicalGrouping logicalGrouping, bool trackAllCaptures)
{
_conditions = new ConditionCollection(logicalGrouping, trackAllCaptures);
}
public void AddUrlCondition(Condition condition)
{
// If there are no conditions specified
if (_conditions == null)
{
AddUrlConditions(LogicalGrouping.MatchAll, trackAllCaptures);
throw new InvalidOperationException($"You must first configure condition behavior by calling {nameof(ConfigureConditionBehavior)}");
}
switch (patternSyntax)
if (condition == null)
{
case PatternSyntax.ECMAScript:
{
switch (matchType)
{
case MatchType.Pattern:
{
if (string.IsNullOrEmpty(pattern))
{
throw new FormatException("Match does not have an associated pattern attribute in condition");
}
var regex = new Regex(
pattern,
ignoreCase ? RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase :
RegexOptions.CultureInvariant | RegexOptions.Compiled,
RegexTimeout);
_conditions.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny });
break;
}
case MatchType.IsDirectory:
{
_conditions.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate), OrNext = _matchAny });
break;
}
case MatchType.IsFile:
{
_conditions.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny });
break;
}
default:
throw new FormatException("Unrecognized matchType");
}
break;
}
case PatternSyntax.Wildcard:
throw new NotSupportedException("Wildcard syntax is not supported");
case PatternSyntax.ExactMatch:
if (pattern == null)
{
throw new FormatException("Match does not have an associated pattern attribute in condition");
}
_conditions.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny });
break;
default:
throw new FormatException("Unrecognized pattern syntax");
throw new ArgumentNullException(nameof(condition));
}
_conditions.Add(condition);
}
public void AddUrlConditions(LogicalGrouping logicalGrouping, bool trackAllCaptures)
public void AddUrlConditions(IEnumerable<Condition> conditions)
{
_conditions = new List<Condition>();
_matchAny = logicalGrouping == LogicalGrouping.MatchAny;
_trackAllCaptures = trackAllCaptures;
if (_conditions == null)
{
throw new InvalidOperationException($"You must first configure condition behavior by calling {nameof(ConfigureConditionBehavior)}");
}
_conditions.AddConditions(conditions);
}
}
}
}

View File

@ -1,15 +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 Microsoft.AspNetCore.Http.Extensions;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class GlobalRuleUrlSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{
return context.HttpContext.Request.GetEncodedUrl();
}
}
}

View File

@ -1,13 +1,28 @@
// 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 Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class UrlSegment : PatternSegment
{
private readonly UriMatchPart _uriMatchPart;
public UrlSegment()
: this(UriMatchPart.Path)
{
}
public UrlSegment(UriMatchPart uriMatchPart)
{
_uriMatchPart = uriMatchPart;
}
public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
{
return context.HttpContext.Request.Path;
return _uriMatchPart == UriMatchPart.Full ? context.HttpContext.Request.GetEncodedUrl() : (string)context.HttpContext.Request.Path;
}
}
}

View File

@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
</rewrite>";
var expected = new List<IISUrlRewriteRule>();
expected.Add(CreateTestRule(new List<Condition>(),
expected.Add(CreateTestRule(new ConditionCollection(),
url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite,
@ -58,10 +57,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
</rules>
</rewrite>";
var condList = new List<Condition>();
var condList = new ConditionCollection();
condList.Add(new Condition
{
Input = new InputParser().ParseInputString("{HTTPS}", global: false),
Input = new InputParser().ParseInputString("{HTTPS}"),
Match = new RegexMatch(new Regex("^OFF$"), false)
});
@ -102,10 +101,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
</rules>
</rewrite>";
var condList = new List<Condition>();
var condList = new ConditionCollection();
condList.Add(new Condition
{
Input = new InputParser().ParseInputString("{HTTPS}", global: false),
Input = new InputParser().ParseInputString("{HTTPS}"),
Match = new RegexMatch(new Regex("^OFF$"), false)
});
@ -160,9 +159,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
}
// Creates a rule with appropriate default values of the url rewrite rule.
private IISUrlRewriteRule CreateTestRule(List<Condition> conditions,
LogicalGrouping condGrouping = LogicalGrouping.MatchAll,
bool condTracking = false,
private IISUrlRewriteRule CreateTestRule(ConditionCollection conditions,
string name = "",
bool enabled = true,
PatternSyntax patternSyntax = PatternSyntax.ECMAScript,
@ -174,11 +171,17 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
string pattern = "",
bool appendQueryString = false,
bool rewrittenUrl = false,
bool global = false,
UriMatchPart uriMatchPart = UriMatchPart.Path,
RedirectType redirectType = RedirectType.Permanent
)
{
return new IISUrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url, global: false), queryStringAppend: false), trackAllCaptures: false);
return new IISUrlRewriteRule(
name,
new RegexMatch(new Regex("^OFF$"), negate),
conditions,
new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url, uriMatchPart), queryStringAppend: false),
global);
}
// TODO make rules comparable?

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void InputParser_ParseLiteralString()
{
var testString = "hello/hey/what";
var result = new InputParser().ParseInputString(testString, global: false);
var result = new InputParser().ParseInputString(testString, UriMatchPart.Path);
Assert.Equal(1, result.PatternSegments.Count);
}
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[InlineData("foo/", 1)]
public void InputParser_ParseStringWithBackReference(string testString, int expected)
{
var result = new InputParser().ParseInputString(testString, global: false);
var result = new InputParser().ParseInputString(testString, UriMatchPart.Path);
Assert.Equal(expected, result.PatternSegments.Count);
}
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[InlineData("hey/{R:1}/{C:1}", "hey/foo/foo")]
public void EvaluateBackReferenceRule(string testString, string expected)
{
var middle = new InputParser().ParseInputString(testString, global: false);
var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result);
}
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[InlineData("hey/ToLower:/what", "hey/ToLower:/what")]
public void EvaluatToLowerRule(string testString, string expected)
{
var middle = new InputParser().ParseInputString(testString, global: false);
var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result);
}
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[InlineData("hey/{UrlEncode:<hey>}", "hey/%3Chey%3E")]
public void EvaluatUriEncodeRule(string testString, string expected)
{
var middle = new InputParser().ParseInputString(testString, global: false);
var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path);
var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences());
Assert.Equal(expected, result);
}
@ -88,13 +88,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[InlineData("{HTTPS")]
public void FormatExceptionsOnBadSyntax(string testString)
{
Assert.Throws<FormatException>(() => new InputParser().ParseInputString(testString, global: false));
Assert.Throws<FormatException>(() => new InputParser().ParseInputString(testString, UriMatchPart.Path));
}
[Fact]
public void Should_throw_FormatException_if_no_rewrite_maps_are_defined()
{
Assert.Throws<FormatException>(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}", global: false));
Assert.Throws<FormatException>(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}", UriMatchPart.Path));
}
[Fact]
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
const string undefinedMapName = "apiMap";
var map = new IISRewriteMap(definedMapName);
var maps = new IISRewriteMapCollection { map };
Assert.Throws<FormatException>(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}", global: false));
Assert.Throws<FormatException>(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}", UriMatchPart.Path));
}
[Fact]
@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var maps = new IISRewriteMapCollection { map };
var inputString = $"{{{expectedMapName}:{{R:1}}}}";
var pattern = new InputParser(maps).ParseInputString(inputString, global: false);
var pattern = new InputParser(maps).ParseInputString(inputString, UriMatchPart.Path);
Assert.Equal(1, pattern.PatternSegments.Count);
var segment = pattern.PatternSegments.Single();

View File

@ -2,14 +2,19 @@
// 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.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Net.Http.Headers;
using Xunit;
@ -456,8 +461,9 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
}
[Fact]
public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri()
public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_ParsedRule()
{
// arrange
var xml = @"<rewrite>
<globalRules>
<rule name=""Test"" patternSyntax=""ECMAScript"" stopProcessing=""true"">
@ -478,8 +484,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
});
var server = new TestServer(builder);
// act
var response = await server.CreateClient().GetStringAsync($"http://localhost/{Guid.NewGuid()}/foo/bar");
// assert
Assert.Equal("http://www.test.com/foo/bar", response);
}
@ -543,5 +551,52 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
Assert.Equal("reason", response.ReasonPhrase);
Assert.Equal("description", content);
}
[Theory]
[InlineData(@"^http://localhost(/.*)", "http://localhost/foo/bar", UriMatchPart.Path)]
[InlineData(@"^http://localhost(/.*)", "http://www.test.com/foo/bar", UriMatchPart.Full)]
public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_CodedRule(string conditionInputPattern, string expectedResult, UriMatchPart uriMatchPart)
{
// arrange
var inputParser = new InputParser();
var ruleBuilder = new UrlRewriteRuleBuilder
{
Name = "test",
Global = false
};
ruleBuilder.AddUrlMatch(".*");
var condition = new UriMatchCondition(
inputParser,
"{REQUEST_URI}",
conditionInputPattern,
uriMatchPart,
ignoreCase: true,
negate: false);
ruleBuilder.ConfigureConditionBehavior(LogicalGrouping.MatchAll, trackAllCaptures: true);
ruleBuilder.AddUrlCondition(condition);
var action = new RewriteAction(
RuleResult.SkipRemainingRules,
inputParser.ParseInputString(@"http://www.test.com{C:1}", uriMatchPart),
queryStringAppend: false);
ruleBuilder.AddUrlAction(action);
var options = new RewriteOptions().Add(ruleBuilder.Build());
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRewriter(options);
app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl()));
});
var server = new TestServer(builder);
// act
var response = await server.CreateClient().GetStringAsync("http://localhost/foo/bar");
// assert
Assert.Equal(expectedResult, response);
}
}
}

View File

@ -13,25 +13,25 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public class ServerVariableTests
{
[Theory]
[InlineData("CONTENT_LENGTH", "10", false)]
[InlineData("CONTENT_TYPE", "json", false)]
[InlineData("HTTP_ACCEPT", "accept", false)]
[InlineData("HTTP_COOKIE", "cookie", false)]
[InlineData("HTTP_HOST", "example.com", false)]
[InlineData("HTTP_REFERER", "referer", false)]
[InlineData("HTTP_USER_AGENT", "useragent", false)]
[InlineData("HTTP_CONNECTION", "connection", false)]
[InlineData("HTTP_URL", "/foo", false)]
[InlineData("HTTP_URL", "http://example.com/foo?bar=1", true)]
[InlineData("QUERY_STRING", "bar=1", false)]
[InlineData("REQUEST_FILENAME", "/foo", false)]
[InlineData("REQUEST_URI", "/foo", false)]
[InlineData("REQUEST_URI", "http://example.com/foo?bar=1", true)]
public void CheckServerVariableParsingAndApplication(string variable, string expected, bool global)
[InlineData("CONTENT_LENGTH", "10", UriMatchPart.Path)]
[InlineData("CONTENT_TYPE", "json", UriMatchPart.Path)]
[InlineData("HTTP_ACCEPT", "accept", UriMatchPart.Path)]
[InlineData("HTTP_COOKIE", "cookie", UriMatchPart.Path)]
[InlineData("HTTP_HOST", "example.com", UriMatchPart.Path)]
[InlineData("HTTP_REFERER", "referer", UriMatchPart.Path)]
[InlineData("HTTP_USER_AGENT", "useragent", UriMatchPart.Path)]
[InlineData("HTTP_CONNECTION", "connection", UriMatchPart.Path)]
[InlineData("HTTP_URL", "/foo", UriMatchPart.Path)]
[InlineData("HTTP_URL", "http://example.com/foo?bar=1", UriMatchPart.Full)]
[InlineData("QUERY_STRING", "bar=1", UriMatchPart.Path)]
[InlineData("REQUEST_FILENAME", "/foo", UriMatchPart.Path)]
[InlineData("REQUEST_URI", "/foo", UriMatchPart.Path)]
[InlineData("REQUEST_URI", "http://example.com/foo?bar=1", UriMatchPart.Full)]
public void CheckServerVariableParsingAndApplication(string variable, string expected, UriMatchPart uriMatchPart)
{
// Arrange and Act
var testParserContext = new ParserContext("test");
var serverVar = ServerVariables.FindServerVariable(variable, testParserContext, global);
var serverVar = ServerVariables.FindServerVariable(variable, testParserContext, uriMatchPart);
var lookup = serverVar.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences);
// Assert
Assert.Equal(expected, lookup);
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var context = new DefaultHttpContext();
var rewriteContext = new RewriteContext { HttpContext = context };
var testParserContext = new ParserContext("test");
var serverVar = ServerVariables.FindServerVariable("QUERY_STRING", testParserContext, global: false);
var serverVar = ServerVariables.FindServerVariable("QUERY_STRING", testParserContext, UriMatchPart.Path);
var lookup = serverVar.Evaluate(rewriteContext, CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences);
Assert.Equal(string.Empty, lookup);

View File

@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var rules = new UrlRewriteFileParser().Parse(xml);
Assert.Equal(rules.Count, 1);
Assert.True(rules[0].TrackAllCaptures);
Assert.True(rules[0].Conditions.TrackAllCaptures);
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
rules.FirstOrDefault().ApplyRule(context);
Assert.Equal(RuleResult.ContinueRules, context.Result);

View File

@ -1,47 +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 Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class GlobalRuleUrlSegmentTests
{
[Theory]
[InlineData("http", "localhost", 80, null, null, "http://localhost:80/")]
[InlineData("http", "localhost", 80, "/foo/bar", null, "http://localhost:80/foo/bar")]
[InlineData("http", "localhost", 80, "/foo bar", null, "http://localhost:80/foo%20bar")]
[InlineData("http", "localhost", 81, "/foo/bar", null, "http://localhost:81/foo/bar")]
[InlineData("http", "localhost", 80, null, "?foo=bar", "http://localhost:80/?foo=bar")]
[InlineData("https", "localhost", 443, "/foo/bar", null, "https://localhost:443/foo/bar")]
public void AssertSegmentIsCorrect(string scheme, string host, int port, string path, string queryString, string expectedResult)
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.Request.Scheme = scheme;
httpContext.Request.Host = new HostString(host, port);
if (!string.IsNullOrEmpty(path))
{
httpContext.Request.Path = new PathString(path);
}
if (!string.IsNullOrEmpty(queryString))
{
httpContext.Request.QueryString = new QueryString(queryString);
}
var context = new RewriteContext { HttpContext = httpContext };
context.HttpContext = httpContext;
// Act
var segment = new GlobalRuleUrlSegment();
var results = segment.Evaluate(context, null, null);
// Assert
Assert.Equal(expectedResult, results);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
@ -10,10 +11,19 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
public class UrlSegmentTests
{
[Theory]
[InlineData("http", "localhost", 80, "/foo/bar", "/foo/bar")]
[InlineData("http", "localhost", 80, "/foo:bar", "/foo:bar")]
[InlineData("http", "localhost", 80, "/foo bar", "/foo%20bar")]
public void AssertSegmentIsCorrect(string scheme, string host, int port, string path, string expectedResult)
[InlineData("http", "localhost", 80, null, UriMatchPart.Path, "")]
[InlineData("http", "localhost", 80, "", UriMatchPart.Path, "")]
[InlineData("http", "localhost", 80, "/foo/bar", UriMatchPart.Path, "/foo/bar")]
[InlineData("http", "localhost", 80, "/foo:bar", UriMatchPart.Path, "/foo:bar")]
[InlineData("http", "localhost", 80, "/foo bar", UriMatchPart.Path, "/foo%20bar")]
[InlineData("http", "localhost", 80, null, UriMatchPart.Full, "http://localhost:80/")]
[InlineData("http", "localhost", 80, "", UriMatchPart.Full, "http://localhost:80/")]
[InlineData("http", "localhost", 80, "/foo:bar", UriMatchPart.Full, "http://localhost:80/foo:bar")]
[InlineData("http", "localhost", 80, "/foo bar", UriMatchPart.Full, "http://localhost:80/foo%20bar")]
[InlineData("http", "localhost", 80, "/foo/bar", UriMatchPart.Full, "http://localhost:80/foo/bar")]
[InlineData("http", "localhost", 81, "/foo/bar", UriMatchPart.Full, "http://localhost:81/foo/bar")]
[InlineData("https", "localhost", 443, "/foo/bar", UriMatchPart.Full, "https://localhost:443/foo/bar")]
public void AssertSegmentIsCorrect(string scheme, string host, int port, string path, UriMatchPart uriMatchPart, string expectedResult)
{
// Arrange
var httpContext = new DefaultHttpContext();
@ -25,7 +35,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
context.HttpContext = httpContext;
// Act
var segment = new UrlSegment();
var segment = new UrlSegment(uriMatchPart);
var results = segment.Evaluate(context, null, null);
// Assert