General bugfixes and style cleanup

This commit is contained in:
Justin Kotalik 2016-08-23 13:46:39 -07:00
parent 74b0b7945d
commit 0cb7445921
35 changed files with 333 additions and 281 deletions

View File

@ -37,7 +37,7 @@ namespace RewriteSample
return RuleResult.Continue; return RuleResult.Continue;
})); }));
app.Run(context => context.Response.WriteAsync(context.Request.Path)); app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}"));
} }
public static void Main(string[] args) public static void Main(string[] args)

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Rewrite
public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort, bool stopProcessing) public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort, bool stopProcessing)
{ {
options.Rules.Add(new RewriteToHttpsRule {SSLPort = sslPort, stopProcessing = stopProcessing }); options.Rules.Add(new RewriteToHttpsRule {SSLPort = sslPort, StopProcessing = stopProcessing });
return options; return options;
} }

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
{ {
public class RewriteToHttpsRule : Rule public class RewriteToHttpsRule : Rule
{ {
public bool stopProcessing { get; set; } public bool StopProcessing { get; set; }
public int? SSLPort { get; set; } public int? SSLPort { get; set; }
public override RuleResult ApplyRule(RewriteContext context) public override RuleResult ApplyRule(RewriteContext context)
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
context.HttpContext.Request.Scheme = "https"; context.HttpContext.Request.Scheme = "https";
context.HttpContext.Request.Host = host; context.HttpContext.Request.Host = host;
return stopProcessing ? RuleResult.StopRules: RuleResult.Continue; return StopProcessing ? RuleResult.StopRules: RuleResult.Continue;
} }
return RuleResult.Continue; return RuleResult.Continue;
} }

View File

@ -172,19 +172,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
case 'g': case 'g':
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
if (context.Current == 't') switch (context.Current)
{ {
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Greater, operand: null); case 't':
} return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Greater, operand: null);
else if (context.Current == 'e') case 'e':
{ return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null);
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null); default:
} throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
else
{
throw new FormatException(context.Error());
} }
case 'l': case 'l':
// name conflict with -l and -lt/-le, so the assumption is if there is no // name conflict with -l and -lt/-le, so the assumption is if there is no
@ -193,17 +190,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null);
} }
if (context.Current == 't') switch (context.Current)
{ {
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Less, operand: null); case 't':
} return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Less, operand: null);
else if (context.Current == 'e') case 'e':
{ return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null);
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null); default:
} throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
else
{
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
case 'n': case 'n':
if (!context.Next() || context.Current != 'e') if (!context.Next() || context.Current != 'e')

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public List<Rule> Parse(TextReader input) public List<Rule> Parse(TextReader input)
{ {
string line = null; string line;
var rules = new List<Rule>(); var rules = new List<Rule>();
var builder = new RuleBuilder(); var builder = new RuleBuilder();
var lineNum = 0; var lineNum = 0;

View File

@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// Invalid syntax to have any spaces. // Invalid syntax to have any spaces.
var tokens = flagString.Substring(1, flagString.Length - 2).Split(','); var tokens = flagString.Substring(1, flagString.Length - 2).Split(',');
var flags = new Flags(); var flags = new Flags();
foreach (string token in tokens) foreach (var token in tokens)
{ {
var hasPayload = token.Split('='); var hasPayload = token.Split('=');

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
@ -33,9 +34,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
var pattern = Url.Evaluate(context, ruleMatch, condMatch); var pattern = Url.Evaluate(context, ruleMatch, condMatch);
// TODO PERF, substrings, object creation, etc. // TODO PERF, substrings, object creation, etc.
if (pattern.IndexOf("://") >= 0) if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0)
{ {
string scheme = null; string scheme;
HostString host; HostString host;
PathString path; PathString path;
QueryString query; QueryString query;

View File

@ -62,91 +62,90 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
condition.OrNext = flags.HasFlag(FlagType.Or); condition.OrNext = flags.HasFlag(FlagType.Or);
condition.Input = pattern; condition.Input = pattern;
if (input.ConditionType == ConditionType.Regex) switch (input.ConditionType)
{ {
if (flags.HasFlag(FlagType.NoCase)) case ConditionType.Regex:
{ if (flags.HasFlag(FlagType.NoCase))
condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert); {
} condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert);
else }
{ else
condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert); {
} condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout), input.Invert);
} }
else if (input.ConditionType == ConditionType.IntComp) break;
{ case ConditionType.IntComp:
switch (input.OperationType) switch (input.OperationType)
{ {
case OperationType.Equal: case OperationType.Equal:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Equal); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Equal);
break; break;
case OperationType.Greater: case OperationType.Greater:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Greater); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Greater);
break; break;
case OperationType.GreaterEqual: case OperationType.GreaterEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.GreaterEqual); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.GreaterEqual);
break; break;
case OperationType.Less: case OperationType.Less:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Less); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Less);
break; break;
case OperationType.LessEqual: case OperationType.LessEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.LessEqual); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.LessEqual);
break; break;
case OperationType.NotEqual: case OperationType.NotEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.NotEqual); condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.NotEqual);
break; break;
default: default:
throw new ArgumentException("Invalid operation for integer comparison."); throw new ArgumentException("Invalid operation for integer comparison.");
} }
} break;
else if (input.ConditionType == ConditionType.StringComp) case ConditionType.StringComp:
{ switch (input.OperationType)
switch (input.OperationType) {
{ case OperationType.Equal:
case OperationType.Equal: condition.Match = new StringMatch(input.Operand, StringOperationType.Equal, input.Invert);
condition.Match = new StringMatch(input.Operand, StringOperationType.Equal); break;
break; case OperationType.Greater:
case OperationType.Greater: condition.Match = new StringMatch(input.Operand, StringOperationType.Greater, input.Invert);
condition.Match = new StringMatch(input.Operand, StringOperationType.Greater); break;
break; case OperationType.GreaterEqual:
case OperationType.GreaterEqual: condition.Match = new StringMatch(input.Operand, StringOperationType.GreaterEqual, input.Invert);
condition.Match = new StringMatch(input.Operand, StringOperationType.GreaterEqual); break;
break; case OperationType.Less:
case OperationType.Less: condition.Match = new StringMatch(input.Operand, StringOperationType.Less, input.Invert);
condition.Match = new StringMatch(input.Operand, StringOperationType.Less); break;
break; case OperationType.LessEqual:
case OperationType.LessEqual: condition.Match = new StringMatch(input.Operand, StringOperationType.LessEqual, input.Invert);
condition.Match = new StringMatch(input.Operand, StringOperationType.LessEqual); break;
break; default:
default: throw new ArgumentException("Invalid operation for string comparison.");
throw new ArgumentException("Invalid operation for string comparison."); }
} break;
} default:
else switch (input.OperationType)
{ {
switch (input.OperationType) case OperationType.Directory:
{ condition.Match = new IsDirectoryMatch(input.Invert);
case OperationType.Directory: break;
condition.Match = new IsDirectoryMatch(input.Invert); case OperationType.RegularFile:
break; condition.Match = new IsFileMatch(input.Invert);
case OperationType.RegularFile: break;
condition.Match = new IsFileMatch(input.Invert); case OperationType.ExistingFile:
break; condition.Match = new IsFileMatch(input.Invert);
case OperationType.ExistingFile: break;
condition.Match = new IsFileMatch(input.Invert); case OperationType.SymbolicLink:
break; throw new NotImplementedException("Symbolic links are not implemented");
case OperationType.SymbolicLink: case OperationType.Size:
throw new NotImplementedException("Symbolic links are not implemented"); condition.Match = new FileSizeMatch(input.Invert);
case OperationType.Size: break;
condition.Match = new FileSizeMatch(input.Invert); case OperationType.ExistingUrl:
break; throw new NotImplementedException("Existing Url lookups not implemented");
case OperationType.ExistingUrl: case OperationType.Executable:
throw new NotImplementedException("Existing Url lookups not implemented"); throw new NotImplementedException("Executable Property search is not implemented");
case OperationType.Executable: default:
throw new NotImplementedException("Executable Property search is not implemented"); throw new ArgumentException("Invalid operation for property comparison.");
default: }
throw new ArgumentException("Invalid operation for property comparison."); break;
}
} }
_conditions.ConditionList.Add(condition); _conditions.ConditionList.Add(condition);
} }
@ -157,11 +156,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
if (flags.HasFlag(FlagType.NoCase)) if (flags.HasFlag(FlagType.NoCase))
{ {
_match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert); _match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert);
} }
else else
{ {
_match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert); _match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout), input.Invert);
} }
} }

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public ParsedModRewriteInput ParseRuleRegex(string regex) public ParsedModRewriteInput ParseRuleRegex(string regex)
{ {
if (regex == null || regex == string.Empty) if (string.IsNullOrEmpty(regex))
{ {
throw new FormatException("Regex expression is null"); throw new FormatException("Regex expression is null");
} }

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
private const char Percent = '%'; private const char Percent = '%';
private const char Dollar = '$'; private const char Dollar = '$';
private const char Space = ' ';
private const char Colon = ':'; private const char Colon = ':';
private const char OpenBrace = '{'; private const char OpenBrace = '{';
private const char CloseBrace = '}'; private const char CloseBrace = '}';
@ -41,43 +40,41 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
var results = new List<PatternSegment>(); var results = new List<PatternSegment>();
while (context.Next()) while (context.Next())
{ {
if (context.Current == Percent) switch (context.Current)
{ {
// This is a server parameter, parse for a condition variable case Percent:
if (!context.Next()) // This is a server parameter, parse for a condition variable
{ if (!context.Next())
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index)); {
} throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index));
ParseConditionParameter(context, results); }
} ParseConditionParameter(context, results);
else if (context.Current == Dollar) break;
{ case Dollar:
// This is a parameter from the rule, verify that it is a number from 0 to 9 directly after it // This is a parameter from the rule, verify that it is a number from 0 to 9 directly after it
// and create a new Pattern Segment. // and create a new Pattern Segment.
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index));
} }
context.Mark(); context.Mark();
if (context.Current >= '0' && context.Current <= '9') if (context.Current >= '0' && context.Current <= '9')
{ {
context.Next(); context.Next();
var ruleVariable = context.Capture(); var ruleVariable = context.Capture();
context.Back(); context.Back();
var parsedIndex = int.Parse(ruleVariable); var parsedIndex = int.Parse(ruleVariable);
results.Add(new RuleMatchSegment(parsedIndex)); results.Add(new RuleMatchSegment(parsedIndex));
} }
else else
{ {
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index)); throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index));
} }
} break;
else default:
{ ParseLiteral(context, results);
// Parse for literals, which will return on either the end of the test string break;
// or when it hits a special character
ParseLiteral(context, results);
} }
} }
return new Pattern(results); return new Pattern(results);

View File

@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
private const char Space = ' '; private const char Space = ' ';
private const char Escape = '\\'; private const char Escape = '\\';
private const char Tab = '\t'; private const char Tab = '\t';
private const char Quote = '"';
/// <summary> /// <summary>
/// Splits a string on whitespace, ignoring spaces, creating into a list of strings. /// Splits a string on whitespace, ignoring spaces, creating into a list of strings.
@ -36,49 +37,51 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
context.Mark(); context.Mark();
while (true) while (true)
{ {
if (context.Current == Escape) switch (context.Current)
{ {
// Need to progress such that the next character is not evaluated. case Escape:
if (!context.Next()) // Need to progress such that the next character is not evaluated.
{ if (!context.Next())
// Means that a character was not escaped appropriately Ex: "foo\" {
throw new FormatException($"Invalid escaper character in string: {rule}"); // Means that a character was not escaped appropriately Ex: "foo\"
} throw new FormatException($"Invalid escaper character in string: {rule}");
} }
else if (context.Current == '"') break;
{ case Quote:
// Ignore all characters until the next quote is hit // Ignore all characters until the next quote is hit
if (!context.Next())
{
throw new FormatException($"Mismatched number of quotes: {rule}");
}
while (context.Current != '"')
{
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException($"Mismatched number of quotes: {rule}"); throw new FormatException($"Mismatched number of quotes: {rule}");
} }
}
} while (context.Current != Quote)
else if (context.Current == Space || context.Current == Tab)
{
// time to capture!
var token = context.Capture();
if (!string.IsNullOrEmpty(token))
{
tokens.Add(token);
while (context.Current == Space || context.Current == Tab)
{ {
if (!context.Next()) if (!context.Next())
{ {
// At end of string, we can return at this point. throw new FormatException($"Mismatched number of quotes: {rule}");
RemoveQuotesAndEscapeCharacters(tokens);
return tokens;
} }
} }
context.Mark(); break;
context.Back(); case Space:
} case Tab:
// time to capture!
var token = context.Capture();
if (!string.IsNullOrEmpty(token))
{
tokens.Add(token);
do
{
if (!context.Next())
{
// At end of string, we can return at this point.
RemoveQuotesAndEscapeCharacters(tokens);
return tokens;
}
} while (context.Current == Space || context.Current == Tab);
context.Mark();
context.Back();
}
break;
} }
if (!context.Next()) if (!context.Next())
{ {
@ -99,9 +102,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// Need to remove leading and trailing slashes if they exist. // Need to remove leading and trailing slashes if they exist.
// This is on start-up, so more forgivening towards substrings/ new strings // This is on start-up, so more forgivening towards substrings/ new strings
// If this is a perf/memory problem, discuss later. // If this is a perf/memory problem, discuss later.
private void RemoveQuotesAndEscapeCharacters(List<string> tokens) private static void RemoveQuotesAndEscapeCharacters(IList<string> tokens)
{ {
for (int i = 0; i < tokens.Count; i++) for (var i = 0; i < tokens.Count; i++)
{ {
var token = tokens[i]; var token = tokens[i];
var trimmed = token.Trim('\"'); var trimmed = token.Trim('\"');

View File

@ -18,10 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
Index = -1; Index = -1;
} }
public char Current public char Current => (Index < Template.Length && Index >= 0) ? Template[Index] : (char)0;
{
get { return (Index < Template.Length && Index >= 0) ? Template[Index] : (char)0; }
}
public bool Back() public bool Back()
{ {
@ -62,10 +59,5 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
return null; return null;
} }
} }
public string Error()
{
return string.Format("Syntax Error at index: ", Index, " with character: ", Current);
}
} }
} }

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
public class Pattern public class Pattern
{ {
public IList<PatternSegment> PatternSegments { get; } public IList<PatternSegment> PatternSegments { get; }
public Pattern(List<PatternSegment> patternSegments) public Pattern(IList<PatternSegment> patternSegments)
{ {
PatternSegments = patternSegments; PatternSegments = patternSegments;
} }

View File

@ -5,16 +5,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class ConditionMatchSegment : PatternSegment public class ConditionMatchSegment : PatternSegment
{ {
public int Index { get; set; } private readonly int _index;
public ConditionMatchSegment(int index) public ConditionMatchSegment(int index)
{ {
Index = index; _index = index;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return condMatch?.BackReference[Index]?.Value; return condMatch?.BackReference[_index].Value;
} }
} }
} }

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class DateTimeSegment : PatternSegment public class DateTimeSegment : PatternSegment
{ {
private DateTimePortion _portion; private readonly DateTimePortion _portion;
public DateTimeSegment(string segment) public DateTimeSegment(string segment)
{ {

View File

@ -5,16 +5,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class HeaderSegment : PatternSegment public class HeaderSegment : PatternSegment
{ {
public string Header { get; set; } private readonly string _header;
public HeaderSegment(string header) public HeaderSegment(string header)
{ {
Header = header; _header = header;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return context.HttpContext.Request.Headers[Header]; return context.HttpContext.Request.Headers[_header];
} }
} }
} }

View File

@ -5,16 +5,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class LiteralSegment : PatternSegment public class LiteralSegment : PatternSegment
{ {
public string Literal { get; set; } private readonly string _literal;
public LiteralSegment(string literal) public LiteralSegment(string literal)
{ {
Literal = literal; _literal = literal;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return Literal; return _literal;
} }
} }
} }

View File

@ -5,16 +5,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class RuleMatchSegment : PatternSegment public class RuleMatchSegment : PatternSegment
{ {
public int Index { get; set; } private readonly int _index;
public RuleMatchSegment(int index) public RuleMatchSegment(int index)
{ {
Index = index; _index = index;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return ruleMatch?.BackReference[Index]?.Value; return ruleMatch?.BackReference[_index].Value;
} }
} }
} }

View File

@ -7,11 +7,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class ToLowerSegment : PatternSegment public class ToLowerSegment : PatternSegment
{ {
public Pattern Pattern { get; set; } private readonly Pattern _pattern;
public ToLowerSegment(Pattern pattern) public ToLowerSegment(Pattern pattern)
{ {
Pattern = pattern; _pattern = pattern;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
// 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, ruleMatch, condMatch);
context.Builder = tempBuilder; context.Builder = tempBuilder;
return pattern.ToLowerInvariant(); return pattern.ToLowerInvariant();
} }

View File

@ -8,18 +8,18 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class UrlEncodeSegment : PatternSegment public class UrlEncodeSegment : PatternSegment
{ {
public Pattern Pattern { get; set; } private readonly Pattern _pattern;
public UrlEncodeSegment(Pattern pattern) public UrlEncodeSegment(Pattern pattern)
{ {
Pattern = pattern; _pattern = pattern;
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
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, ruleMatch, condMatch);
context.Builder = tempBuilder; context.Builder = tempBuilder;
return UrlEncoder.Default.Encode(pattern); return UrlEncoder.Default.Encode(pattern);
} }

View File

@ -1,6 +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.
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
@ -30,7 +31,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
// TODO PERF, substrings, object creation, etc. // TODO PERF, substrings, object creation, etc.
if (pattern.IndexOf("://") >= 0) if (pattern.IndexOf("://") >= 0)
{ {
string scheme = null; string scheme;
HostString host; HostString host;
PathString path; PathString path;
QueryString query; QueryString query;

View File

@ -5,10 +5,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class VoidAction : UrlAction public class VoidAction : UrlAction
{ {
private readonly RuleResult _results;
public VoidAction(RuleResult results)
{
_results = results;
}
// Explicitly say that nothing happens // Explicitly say that nothing happens
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return RuleResult.Continue; return _results;
} }
} }
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class UrlMatch public abstract class UrlMatch
{ {
public bool Negate { get; set; } protected bool Negate { get; set; }
public abstract MatchResults Evaluate(string input, RewriteContext context); public abstract MatchResults Evaluate(string input, RewriteContext context);
} }
} }

View File

@ -5,19 +5,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class ExactMatch : UrlMatch public class ExactMatch : UrlMatch
{ {
public bool IgnoreCase { get; } private readonly bool _ignoreCase;
public string StringMatch { get; } private readonly string _stringMatch;
public ExactMatch(bool ignoreCase, string input, bool negate) public ExactMatch(bool ignoreCase, string input, bool negate)
{ {
IgnoreCase = ignoreCase; _ignoreCase = ignoreCase;
StringMatch = input; _stringMatch = input;
Negate = negate; Negate = negate;
} }
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) }; return new MatchResults { Success = ((pathMatch == 0) != Negate) };
} }
} }

View File

@ -8,12 +8,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IntegerMatch : UrlMatch public class IntegerMatch : UrlMatch
{ {
public int Value { get; } private readonly int _value;
public IntegerOperationType Operation { get; } private readonly IntegerOperationType _operation;
public IntegerMatch(int value, IntegerOperationType operation) public IntegerMatch(int value, IntegerOperationType operation)
{ {
Value = value; _value = value;
Operation = operation; _operation = operation;
} }
public IntegerMatch(string value, IntegerOperationType operation) public IntegerMatch(string value, IntegerOperationType operation)
@ -23,8 +23,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
throw new FormatException("Syntax error for integers in comparison."); throw new FormatException("Syntax error for integers in comparison.");
} }
Value = compValue; _value = compValue;
Operation = operation; _operation = operation;
} }
public override MatchResults Evaluate(string input, RewriteContext context) public override MatchResults Evaluate(string input, RewriteContext context)
@ -35,20 +35,20 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
return MatchResults.EmptyFailure; return MatchResults.EmptyFailure;
} }
switch (Operation) switch (_operation)
{ {
case IntegerOperationType.Equal: case IntegerOperationType.Equal:
return compValue == Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue == _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Greater: case IntegerOperationType.Greater:
return compValue > Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue > _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.GreaterEqual: case IntegerOperationType.GreaterEqual:
return compValue >= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue >= _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Less: case IntegerOperationType.Less:
return compValue < Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue < _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.LessEqual: case IntegerOperationType.LessEqual:
return compValue <= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue <= _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.NotEqual: case IntegerOperationType.NotEqual:
return compValue != Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return compValue != _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
default: default:
return null; return null;
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IsDirectoryMatch : UrlMatch public class IsDirectoryMatch : UrlMatch
{ {
public IsDirectoryMatch( bool negate) public IsDirectoryMatch(bool negate)
{ {
Negate = negate; Negate = negate;
} }

View File

@ -1,26 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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 System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class RegexMatch : UrlMatch public class RegexMatch : UrlMatch
{ {
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); private readonly Regex _match;
public Regex Match { get; }
public RegexMatch(Regex match, bool negate) public RegexMatch(Regex match, bool negate)
{ {
Match = match; _match = match;
Negate = negate; Negate = negate;
} }
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 { BackReference = res.Groups, Success = (res.Success != Negate)};
} }
} }

View File

@ -5,29 +5,31 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class StringMatch : UrlMatch public class StringMatch : UrlMatch
{ {
public string Value { get; set; } private readonly string _value;
public StringOperationType Operation { get; set; } private readonly StringOperationType _operation;
public bool IgnoreCase { get; set; } private readonly bool _ignoreCase;
public StringMatch(string value, StringOperationType operation)
public StringMatch(string value, StringOperationType operation, bool ignoreCase)
{ {
Value = value; _value = value;
Operation = operation; _operation = operation;
_ignoreCase = ignoreCase;
} }
public override MatchResults Evaluate(string input, RewriteContext context) public override MatchResults Evaluate(string input, RewriteContext context)
{ {
switch (Operation) switch (_operation)
{ {
case StringOperationType.Equal: case StringOperationType.Equal:
return string.Compare(input, Value, IgnoreCase) == 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return string.Compare(input, _value, _ignoreCase) == 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.Greater: case StringOperationType.Greater:
return string.Compare(input, Value, IgnoreCase) > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return string.Compare(input, _value, _ignoreCase) > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.GreaterEqual: case StringOperationType.GreaterEqual:
return string.Compare(input, Value, IgnoreCase) >= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return string.Compare(input, _value, _ignoreCase) >= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.Less: case StringOperationType.Less:
return string.Compare(input, Value, IgnoreCase) < 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return string.Compare(input, _value, _ignoreCase) < 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.LessEqual: case StringOperationType.LessEqual:
return string.Compare(input, Value, IgnoreCase) <= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return string.Compare(input, _value, _ignoreCase) <= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
default: default:
return null; return null;
} }

View File

@ -12,9 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
public class FileParser public class FileParser
{ {
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); private readonly InputParser _inputParser = new InputParser();
private InputParser _inputParser = new InputParser();
public List<UrlRewriteRule> Parse(TextReader reader) public List<UrlRewriteRule> Parse(TextReader reader)
{ {
@ -168,12 +166,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
var parsedPatternString = condition.Attribute(RewriteTags.Pattern)?.Value; var parsedPatternString = condition.Attribute(RewriteTags.Pattern)?.Value;
Pattern input = null;
try try
{ {
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);
} }
catch (FormatException formatException) catch (FormatException formatException)
{ {
@ -212,14 +208,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
} }
} }
private void ThrowUrlFormatException(XElement element, string message) private static void ThrowUrlFormatException(XElement element, string message)
{ {
var line = ((IXmlLineInfo)element).LineNumber; var line = ((IXmlLineInfo)element).LineNumber;
var col = ((IXmlLineInfo)element).LinePosition; var col = ((IXmlLineInfo)element).LinePosition;
throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col)); throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col));
} }
private void ThrowUrlFormatException(XElement element, string message, Exception ex) private static void ThrowUrlFormatException(XElement element, string message, Exception ex)
{ {
var line = ((IXmlLineInfo)element).LineNumber; var line = ((IXmlLineInfo)element).LineNumber;
var col = ((IXmlLineInfo)element).LinePosition; var col = ((IXmlLineInfo)element).LinePosition;

View File

@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return index; return index;
} }
private static bool ParseLiteral(ParserContext context, List<PatternSegment> results) private static void ParseLiteral(ParserContext context, List<PatternSegment> results)
{ {
context.Mark(); context.Mark();
string literal; string literal;
@ -186,7 +186,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
} }
results.Add(new LiteralSegment(literal)); results.Add(new LiteralSegment(literal));
return true;
} }
} }
} }

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
switch (actionType) switch (actionType)
{ {
case ActionType.None: case ActionType.None:
_action = new VoidAction(); _action = new VoidAction(stopProcessing ? RuleResult.StopRules : RuleResult.Continue);
break; break;
case ActionType.Rewrite: case ActionType.Rewrite:
if (appendQueryString) if (appendQueryString)
@ -79,12 +79,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
if (ignoreCase) if (ignoreCase)
{ {
var regex = new Regex(input, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout);
_initialMatch = new RegexMatch(regex, negate); _initialMatch = new RegexMatch(regex, negate);
} }
else else
{ {
var regex = new Regex(input, RegexOptions.Compiled, RegexTimeout); var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout);
_initialMatch = new RegexMatch(regex, negate); _initialMatch = new RegexMatch(regex, negate);
} }
break; break;
@ -121,11 +121,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
Regex regex = null; Regex regex = null;
if (ignoreCase) if (ignoreCase)
{ {
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout);
} }
else else
{ {
regex = new Regex(pattern, RegexOptions.Compiled, RegexTimeout); regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout);
} }
_conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny}); _conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny});

View File

@ -1,10 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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 System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
@ -26,9 +23,18 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
// Due to the path string always having a leading slash, // Due to the path string always having a leading slash,
// remove it from the path before regex comparison // remove it from the path before regex comparison
// TODO may need to check if there is a leading slash and remove conditionally // TODO may need to check if there is a leading slash and remove conditionally
var initMatchRes = InitialMatch.Evaluate(context.HttpContext.Request.Path.ToString().Substring(1), context); var path = context.HttpContext.Request.Path;
MatchResults initMatchResults;
if (path == PathString.Empty)
{
initMatchResults = InitialMatch.Evaluate(path.ToString(), context);
}
else
{
initMatchResults = InitialMatch.Evaluate(path.ToString().Substring(1), context);
}
if (!initMatchRes.Success) if (!initMatchResults.Success)
{ {
return RuleResult.Continue; return RuleResult.Continue;
} }
@ -36,7 +42,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
MatchResults condMatchRes = null; MatchResults condMatchRes = null;
if (Conditions != null) if (Conditions != null)
{ {
condMatchRes = Conditions.Evaluate(context, initMatchRes); condMatchRes = Conditions.Evaluate(context, initMatchResults);
if (!condMatchRes.Success) if (!condMatchRes.Success)
{ {
return RuleResult.Continue; return RuleResult.Continue;
@ -44,7 +50,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
} }
// at this point we know the rule passed, evaluate the replacement. // at this point we know the rule passed, evaluate the replacement.
return Action.ApplyAction(context, initMatchRes, condMatchRes); return Action.ApplyAction(context, initMatchResults, condMatchRes);
} }
} }
} }

View File

@ -69,6 +69,8 @@ namespace Microsoft.AspNetCore.Rewrite
return CompletedTask; return CompletedTask;
case RuleTerminiation.StopRules: case RuleTerminiation.StopRules:
return _next(context); return _next(context);
default:
throw new ArgumentOutOfRangeException($"Invalid rule termination {result}");
} }
} }
return _next(context); return _next(context);

View File

@ -0,0 +1,56 @@
// 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.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
{
// TODO add more of these
public class UrlRewriteApplicationTests
{
[Fact]
public void ApplyRule_AssertStopProcessingFlagWillTerminateOnNoAction()
{
var xml = new StringReader(@"<rewrite>
<rules>
<rule name=""Test"" stopProcessing=""true"">
<match url = ""(.*)""/>
<action type = ""None""/>
</rule>
</rules>
</rewrite>");
var rules = new FileParser().Parse(xml);
Assert.Equal(rules.Count, 1);
var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext {HttpContext = new DefaultHttpContext()});
Assert.Equal(ruleResults.Result, RuleTerminiation.StopRules);
}
[Fact]
public void ApplyRule_AssertNoTerminateFlagWillNotTerminateOnNoAction()
{
var xml = new StringReader(@"<rewrite>
<rules>
<rule name=""Test"">
<match url = ""(.*)"" ignoreCase=""false"" />
<action type = ""None""/>
</rule>
</rules>
</rewrite>");
var rules = new FileParser().Parse(xml);
Assert.Equal(rules.Count, 1);
var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext { HttpContext = new DefaultHttpContext() });
Assert.Equal(ruleResults.Result, RuleTerminiation.Continue);
}
}
}