diff --git a/samples/RewriteSample/Rewrite.txt b/samples/RewriteSample/Rewrite.txt index ed2ae33ce1..574f251600 100644 --- a/samples/RewriteSample/Rewrite.txt +++ b/samples/RewriteSample/Rewrite.txt @@ -1,7 +1,6 @@ # Ensure Https RewriteCond %{HTTPS} off -# U is a new flag to represent full URL rewrites -RewriteRule ^(.*)$ https://www.example.com$1 [L,U] +RewriteRule ^(.*)$ https://www.example.com$1 [L] # Rewrite path with additional sub directory RewriteRule ^(.*)$ /foo$1 diff --git a/samples/RewriteSample/Startup.cs b/samples/RewriteSample/Startup.cs index b83d5677e7..1dbcb048e5 100644 --- a/samples/RewriteSample/Startup.cs +++ b/samples/RewriteSample/Startup.cs @@ -27,18 +27,17 @@ namespace RewriteSample // AddFunctionalRule(Func); // TODO make this startup do something useful. app.UseRewriter(new RewriteOptions() - .ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml") - .ImportFromModRewrite(hostingEnv, "Rewrite.txt") - .RedirectToHttps(StatusCodes.Status307TemporaryRedirect) - .RewriteRule("/foo/(.*)/bar", "{R:1}/bar") - .AddRule(ctx => - { - ctx.HttpContext.Request.Path = "/index"; - return RuleResult.Continue; - })); + .ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml") + .ImportFromModRewrite(hostingEnv, "Rewrite.txt") + .RedirectToHttps(StatusCodes.Status307TemporaryRedirect) + .RewriteRule("/foo/(.*)/bar", "{R:1}/bar") + .AddRule(ctx => + { + ctx.HttpContext.Request.Path = "/index"; + return RuleResult.Continue; + })); app.Run(context => context.Response.WriteAsync(context.Request.Path)); - } public static void Main(string[] args) diff --git a/samples/RewriteSample/UrlRewrite.xml b/samples/RewriteSample/UrlRewrite.xml index 4f5bac7a31..9ef1097666 100644 --- a/samples/RewriteSample/UrlRewrite.xml +++ b/samples/RewriteSample/UrlRewrite.xml @@ -1,12 +1,8 @@  - - - - - - - + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs index 3dd9d6df39..04b1c8dd97 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs @@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules public int StatusCode { get; set; } public override RuleResult ApplyRule(RewriteContext context) { - - // TODO this only does http to https, add more features in the future. if (!context.HttpContext.Request.IsHttps) { var host = context.HttpContext.Request.Host; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs index cccb76e6d1..e276baa85b 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs @@ -1,4 +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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules @@ -10,7 +12,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules public int? SSLPort { get; set; } public override RuleResult ApplyRule(RewriteContext context) { - // TODO this only does http to https, add more features in the future. if (!context.HttpContext.Request.IsHttps) { var host = context.HttpContext.Request.Host; @@ -27,14 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules context.HttpContext.Request.Scheme = "https"; context.HttpContext.Request.Host = host; - if (stopProcessing) - { - return RuleResult.StopRules; - } - else - { - return RuleResult.Continue; - } + return stopProcessing ? RuleResult.StopRules: RuleResult.Continue; } return RuleResult.Continue; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Condition.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/Condition.cs similarity index 74% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Condition.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/Condition.cs index f6541d48f9..5c60e38d82 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Condition.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/Condition.cs @@ -1,15 +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.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public class Condition { public Pattern Input { get; set; } public UrlMatch Match { get; set; } + public bool OrNext { get; set; } + public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - var pattern = Input.Evaluate(context.HttpContext, ruleMatch, condMatch); + var pattern = Input.Evaluate(context, ruleMatch, condMatch); return Match.Evaluate(pattern, context); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs new file mode 100644 index 0000000000..d8154c6b1d --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs @@ -0,0 +1,43 @@ +// 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; + +namespace Microsoft.AspNetCore.Rewrite.Internal +{ + public class Conditions + { + public List ConditionList { get; set; } = new List(); + + public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch) + { + MatchResults prevCond = null; + var orSucceeded = false; + foreach (var condition in ConditionList) + { + if (orSucceeded && condition.OrNext) + { + continue; + } + else if (orSucceeded) + { + orSucceeded = false; + continue; + } + + prevCond = condition.Evaluate(context, ruleMatch, prevCond); + + if (condition.OrNext) + { + orSucceeded = prevCond.Success; + continue; + } + else if (!prevCond.Success) + { + return prevCond; + } + } + return prevCond; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/MatchResults.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/MatchResults.cs similarity index 55% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/MatchResults.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/MatchResults.cs index 6f182109f8..71eb20b8da 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/MatchResults.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/MatchResults.cs @@ -3,10 +3,13 @@ using System.Text.RegularExpressions; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public class MatchResults { + public static readonly MatchResults EmptySuccess = new MatchResults { BackReference = null, Success = true }; + public static readonly MatchResults EmptyFailure = new MatchResults { BackReference = null, Success = false }; + public GroupCollection BackReference { get; set; } public bool Success { get; set; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Condition.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Condition.cs deleted file mode 100644 index e1af53bda7..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Condition.cs +++ /dev/null @@ -1,18 +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. - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - public class Condition - { - public Pattern TestStringSegments { get; } - public ConditionExpression ConditionExpression { get; } - public ConditionFlags Flags { get; } - public Condition(Pattern testStringSegments, ConditionExpression conditionRegex, ConditionFlags flags) - { - TestStringSegments = testStringSegments; - ConditionExpression = conditionRegex; - Flags = flags; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionBuilder.cs deleted file mode 100644 index 743b2d8a33..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionBuilder.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - public class ConditionBuilder - { - private Pattern _testString; - private ParsedModRewriteExpression _pce; - private ConditionFlags _flags; - - public ConditionBuilder(string conditionString) - { - var tokens = Tokenizer.Tokenize(conditionString); - if (tokens.Count == 3) - { - CreateCondition(tokens[1], tokens[2], flagsString: null); - } - else if (tokens.Count == 4) - { - CreateCondition(tokens[1], tokens[2], tokens[3]); - } - else - { - throw new FormatException("Invalid number of tokens."); - } - } - - public ConditionBuilder(string testString, string condition) - { - CreateCondition(testString, condition, flagsString: null); - } - - public ConditionBuilder(string testString, string condition, string flags) - { - CreateCondition(testString, condition, flags); - } - - public Condition Build() - { - var expression = ExpressionCreator.CreateConditionExpression(_pce, _flags); - return new Condition(_testString, expression, _flags); - } - - private void CreateCondition(string testString, string condition, string flagsString) - { - _testString = ConditionTestStringParser.ParseConditionTestString(testString); - _pce = ConditionPatternParser.ParseActionCondition(condition); - _flags = FlagParser.ParseConditionFlags(flagsString); - } - - public void SetFlag(string flag) - { - SetFlag(flag, value: null); - } - - public void SetFlag(ConditionFlagType flag) - { - SetFlag(flag, value: null); - } - - public void SetFlag(string flag, string value) - { - if (_flags == null) - { - _flags = new ConditionFlags(); - } - _flags.SetFlag(flag, value); - } - - public void SetFlag(ConditionFlagType flag, string value) - { - if (_flags == null) - { - _flags = new ConditionFlags(); - } - _flags.SetFlag(flag, value); - } - - public void SetFlags(string flags) - { - if (_flags == null) - { - _flags = FlagParser.ParseConditionFlags(flags); - } - else - { - FlagParser.ParseConditionFlags(flags, _flags); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionExpression.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionExpression.cs deleted file mode 100644 index 4d16a03ede..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionExpression.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - /// - /// Represents the ConditionPattern for a mod_rewrite rule. - /// - public class ConditionExpression - { - public Operand Operand { get; set; } - public bool Invert { get; set; } - - /// - /// Checks if a condition matches the context. - /// - /// The UrlRewriteContext. - /// The previous condition results (for backreferences). - /// The testString created from the . - /// If the testString satisfies the condition - public bool? CheckConditionExpression(RewriteContext context, Match previous, string testString) - { - return Operand.CheckOperation(previous, testString, context.FileProvider) ^ Invert; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlagType.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlagType.cs deleted file mode 100644 index 06c7559090..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlagType.cs +++ /dev/null @@ -1,12 +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. - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - public enum ConditionFlagType - { - NoCase, - Or, - NoVary - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlags.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlags.cs deleted file mode 100644 index e9687f8dce..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionFlags.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - // TODO Refactor Condition Flags and Rule Flags under base flag class - public class ConditionFlags - { - private IDictionary _conditionFlagLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "nc", ConditionFlagType.NoCase}, - { "nocase", ConditionFlagType.NoCase }, - { "or", ConditionFlagType.Or}, - { "ornext", ConditionFlagType.Or }, - { "nv", ConditionFlagType.NoVary}, - { "novary", ConditionFlagType.NoVary} - }; - - public IDictionary FlagDictionary { get; } - - public ConditionFlags(IDictionary flags) - { - FlagDictionary = flags; - } - - public ConditionFlags() - { - FlagDictionary = new Dictionary(); - } - public void SetFlag(string flag) - { - SetFlag(flag, null); - } - - public void SetFlag(string flag, string value) - { - ConditionFlagType res; - if (!_conditionFlagLookup.TryGetValue(flag, out res)) - { - throw new ArgumentException("Invalid flag"); - } - SetFlag(res, value); - } - - public void SetFlag(ConditionFlagType flag, string value) - { - if (value == null) - { - value = string.Empty; - } - FlagDictionary[flag] = value; - } - - public string GetFlag(ConditionFlagType flag) - { - CleanupResources(); - string res; - if (!FlagDictionary.TryGetValue(flag, out res)) - { - return null; - } - return res; - } - - public string this[ConditionFlagType flag] - { - get - { - string res; - if (!FlagDictionary.TryGetValue(flag, out res)) - { - return null; - } - return res; - } - set - { - FlagDictionary[flag] = value ?? string.Empty; - } - } - - public bool HasFlag(ConditionFlagType flag) - { - CleanupResources(); - string res; - return FlagDictionary.TryGetValue(flag, out res); - } - - // If this method is called, all flags have been processed, - // therefore to clean up memory, delete dictionary. - private void CleanupResources() - { - if (_conditionFlagLookup != null) - { - _conditionFlagLookup = null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs index 2d14d9c7d0..8279da7831 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs @@ -26,17 +26,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// /// The CondPattern portion of a mod_rewrite RewriteCond. /// A new parsed condition. - public static ParsedModRewriteExpression ParseActionCondition(string condition) + public static ParsedModRewriteInput ParseActionCondition(string condition) { - if (condition == null) + if (condition == null) { condition = string.Empty; } var context = new ParserContext(condition); - var results = new ParsedModRewriteExpression(); + var results = new ParsedModRewriteInput(); if (!context.Next()) { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } // If we hit a !, make sure the condition is inverted when resolving the string @@ -45,88 +45,90 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite results.Invert = true; if (!context.Next()) { - throw new FormatException(context.Error()); + // Dangling ! + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } } // Control Block for strings. Set the operation and type fields based on the sign + // Switch on current character switch (context.Current) { case Greater: if (!context.Next()) { // Dangling ">" - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } if (context.Current == EqualSign) { if (!context.Next()) { // Dangling ">=" - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } - results.Operation = OperationType.GreaterEqual; - results.Type = ConditionType.StringComp; + results.OperationType = OperationType.GreaterEqual; + results.ConditionType = ConditionType.StringComp; } else { - results.Operation = OperationType.Greater; - results.Type = ConditionType.StringComp; + results.OperationType = OperationType.Greater; + results.ConditionType = ConditionType.StringComp; } break; case Less: if (!context.Next()) { // Dangling "<" - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } if (context.Current == EqualSign) { if (!context.Next()) { // Dangling "<=" - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } - results.Operation = OperationType.LessEqual; - results.Type = ConditionType.StringComp; + results.OperationType = OperationType.LessEqual; + results.ConditionType = ConditionType.StringComp; } else { - results.Operation = OperationType.Less; - results.Type = ConditionType.StringComp; + results.OperationType = OperationType.Less; + results.ConditionType = ConditionType.StringComp; } break; case EqualSign: if (!context.Next()) { // Dangling "=" - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } - results.Operation = OperationType.Equal; - results.Type = ConditionType.StringComp; + results.OperationType = OperationType.Equal; + results.ConditionType = ConditionType.StringComp; break; case Dash: results = ParseProperty(context, results.Invert); - if (results.Type == ConditionType.PropertyTest) + if (results.ConditionType == ConditionType.PropertyTest) { return results; } context.Next(); break; default: - results.Type = ConditionType.Regex; + results.ConditionType = ConditionType.Regex; break; } // Capture the rest of the string guarantee validity. - results.Operand = (condition.Substring(context.GetIndex())); + results.Operand = condition.Substring(context.GetIndex()); if (IsValidActionCondition(results)) { return results; } else { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } } @@ -137,37 +139,37 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// /// /// - public static ParsedModRewriteExpression ParseProperty(ParserContext context, bool invert) + public static ParsedModRewriteInput ParseProperty(ParserContext context, bool invert) { if (!context.Next()) { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } + switch (context.Current) { case 'd': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Directory, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Directory, operand: null); case 'f': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.RegularFile, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.RegularFile, operand: null); case 'F': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingFile, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingFile, operand: null); case 'h': case 'L': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null); case 's': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Size, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Size, operand: null); case 'U': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, operand: null); case 'x': - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Executable, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Executable, operand: null); case 'e': - if (!context.Next() || context.Current != 'q') { // Illegal statement. - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Equal, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Equal, operand: null); case 'g': if (!context.Next()) { @@ -175,47 +177,49 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite } if (context.Current == 't') { - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Greater, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Greater, operand: null); } else if (context.Current == 'e') { - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.GreaterEqual, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null); } else { throw new FormatException(context.Error()); } case 'l': + // name conflict with -l and -lt/-le, so the assumption is if there is no + // charcters after -l, we assume it a symbolic link if (!context.Next()) { - return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null); + return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null); } if (context.Current == 't') { - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Less, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Less, operand: null); } else if (context.Current == 'e') { - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.LessEqual, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null); } else { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } case 'n': if (!context.Next() || context.Current != 'e') { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } - return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.NotEqual, null); + return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.NotEqual, operand: null); default: - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } } - private static bool IsValidActionCondition(ParsedModRewriteExpression results) + private static bool IsValidActionCondition(ParsedModRewriteInput results) { - if (results.Type == ConditionType.IntComp) + if (results.ConditionType == ConditionType.IntComp) { // If the type is an integer, verify operand is actually an int int res; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ExpressionCreator.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ExpressionCreator.cs deleted file mode 100644 index 0fab8c0d3f..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ExpressionCreator.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - /// - /// Converts a parsed expression into a mod_rewrite condition. - /// - public class ExpressionCreator - { - private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); - public static ConditionExpression CreateConditionExpression(ParsedModRewriteExpression pce, ConditionFlags flags) - { - var condExp = new ConditionExpression(); - condExp.Invert = pce.Invert; - if (pce.Type == ConditionType.Regex) - { - // TODO make nullable? - if (flags != null && flags.HasFlag(ConditionFlagType.NoCase)) - { - condExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout)); - } - else - { - condExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.Compiled, RegexTimeout)); - } - } - else if (pce.Type == ConditionType.IntComp) - { - switch (pce.Operation) - { - case OperationType.Equal: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Equal); - break; - case OperationType.Greater: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Greater); - break; - case OperationType.GreaterEqual: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.GreaterEqual); - break; - case OperationType.Less: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Less); - break; - case OperationType.LessEqual: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.LessEqual); - break; - case OperationType.NotEqual: - condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.NotEqual); - break; - default: - throw new ArgumentException("No defined operation for integer comparison."); - } - } - else if (pce.Type == ConditionType.StringComp) - { - switch (pce.Operation) - { - case OperationType.Equal: - condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Equal); - break; - case OperationType.Greater: - condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Greater); - break; - case OperationType.GreaterEqual: - condExp.Operand = new StringOperand(pce.Operand, StringOperationType.GreaterEqual); - break; - case OperationType.Less: - condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Less); - break; - case OperationType.LessEqual: - condExp.Operand = new StringOperand(pce.Operand, StringOperationType.LessEqual); - break; - default: - throw new ArgumentException("No defined operation for string comparison."); - } - } - else - { - switch (pce.Operation) - { - case OperationType.Directory: - condExp.Operand = new PropertyOperand(PropertyOperationType.Directory); - break; - case OperationType.RegularFile: - condExp.Operand = new PropertyOperand(PropertyOperationType.RegularFile); - break; - case OperationType.ExistingFile: - condExp.Operand = new PropertyOperand(PropertyOperationType.ExistingFile); - break; - case OperationType.SymbolicLink: - condExp.Operand = new PropertyOperand(PropertyOperationType.SymbolicLink); - break; - case OperationType.Size: - condExp.Operand = new PropertyOperand(PropertyOperationType.Size); - break; - case OperationType.ExistingUrl: - condExp.Operand = new PropertyOperand(PropertyOperationType.ExistingUrl); - break; - case OperationType.Executable: - condExp.Operand = new PropertyOperand(PropertyOperationType.Executable); - break; - default: - throw new ArgumentException("No defined operation for property comparison."); - } - } - return condExp; - } - public static RuleExpression CreateRuleExpression(ParsedModRewriteExpression pce, RuleFlags flags) - { - var ruleExp = new RuleExpression(); - ruleExp.Invert = pce.Invert; - if (flags.HasFlag(RuleFlagType.NoCase)) - { - ruleExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout)); - } - else - { - ruleExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.Compiled, RegexTimeout)); - } - return ruleExp; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs index 3cee0d1e72..de38c0ce3b 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs @@ -4,23 +4,20 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.AspNetCore.Rewrite.Internal; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { - /// - /// - /// - public static class FileParser + public class FileParser { - public static List Parse(TextReader input) + public List Parse(TextReader input) { string line = null; var rules = new List(); - var conditions = new List(); - // TODO consider passing Itokenizer and Ifileparser and provide implementations + var builder = new RuleBuilder(); + var lineNum = 0; while ((line = input.ReadLine()) != null) { + lineNum++; if (string.IsNullOrEmpty(line)) { continue; @@ -33,71 +30,65 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite if (tokens.Count > 4) { // This means the line didn't have an appropriate format, throw format exception - throw new FormatException(); + throw new FormatException(Resources.FormatError_ModRewriteParseError("Too many tokens on line", lineNum)); } - // TODO make a new class called rule parser that does and either return an exception or return the rule. + switch (tokens[0]) { case "RewriteBase": - throw new NotSupportedException(); - //if (tokens.Count == 2) - //{ - // ModRewriteBase.Base = tokens[1]; - //} - //else - //{ - // throw new FormatException(""); - //} - //break; + throw new NotImplementedException("RewriteBase is not implemented"); case "RewriteCond": + try { - ConditionBuilder builder = null; - if (tokens.Count == 3) + var pattern = TestStringParser.Parse(tokens[1]); + var condActionParsed = ConditionPatternParser.ParseActionCondition(tokens[2]); + + var flags = new Flags(); + if (tokens.Count == 4) { - builder = new ConditionBuilder(tokens[1], tokens[2]); + flags = FlagParser.Parse(tokens[3]); } - else if (tokens.Count == 4) - { - builder = new ConditionBuilder(tokens[1], tokens[2], tokens[3]); - } - else - { - throw new FormatException(); - } - conditions.Add(builder.Build()); - break; + + builder.AddConditionFromParts(pattern, condActionParsed, flags); } + catch (FormatException formatException) + { + throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); + } + break; case "RewriteRule": + try { - RuleBuilder builder = null; - if (tokens.Count == 3) + var regex = RuleRegexParser.ParseRuleRegex(tokens[1]); + var pattern = TestStringParser.Parse(tokens[2]); + + // TODO see if we can have flags be null. + var flags = new Flags(); + if (tokens.Count == 4) { - builder = new RuleBuilder(tokens[1], tokens[2]); + flags = FlagParser.Parse(tokens[3]); } - else if (tokens.Count == 4) - { - builder = new RuleBuilder(tokens[1], tokens[2], tokens[3]); - } - else - { - throw new FormatException(); - } - builder.AddConditions(conditions); + + builder.AddMatch(regex, flags); + builder.AddAction(pattern, flags); rules.Add(builder.Build()); - conditions = new List(); - break; + builder = new RuleBuilder(); + } + catch (FormatException formatException) + { + throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); } + break; case "RewriteMap": - throw new NotImplementedException("RewriteMaps to be added soon."); + throw new NotImplementedException("RewriteMap to be added soon."); case "RewriteEngine": // Explicitly do nothing here, no notion of turning on regex engine. break; default: - throw new FormatException(tokens[0]); + throw new FormatException(Resources.FormatError_ModRewriteParseError("Unrecognized keyword: " + tokens[0], lineNum)); } } return rules; } - } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagParser.cs index 4053ea8d7e..8252c89637 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagParser.cs @@ -5,99 +5,38 @@ using System; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { - /// - /// Parses the flags - /// public class FlagParser - { - // TODO Refactor Rule and Condition Flags under IFlags - public static RuleFlags ParseRuleFlags(string flagString) - { - var flags = new RuleFlags(); - ParseRuleFlags(flagString, flags); - return flags; - } - - public static void ParseRuleFlags(string flagString, RuleFlags flags) + { + public static Flags Parse(string flagString) { if (string.IsNullOrEmpty(flagString)) { - return; + return null; } + // Check that flags are contained within [] if (!flagString.StartsWith("[") || !flagString.EndsWith("]")) { throw new FormatException(); } - // Illegal syntax to have any spaces. - var tokens = flagString.Substring(1, flagString.Length - 2).Split(','); - // Go through tokens and verify they have meaning. - // Flags can be KVPs, delimited by '='. - foreach (string token in tokens) - { - if (string.IsNullOrEmpty(token)) - { - continue; - } - string[] kvp = token.Split('='); - if (kvp.Length == 1) - { - flags.SetFlag(kvp[0], null); - } - else if (kvp.Length == 2) - { - flags.SetFlag(kvp[0], kvp[1]); - } - else - { - throw new FormatException(); - } - } - } - public static ConditionFlags ParseConditionFlags(string flagString) - { - var flags = new ConditionFlags(); - ParseConditionFlags(flagString, flags); - return flags; - } - - public static void ParseConditionFlags(string flagString, ConditionFlags flags) - { - if (string.IsNullOrEmpty(flagString)) - { - return; - } - // Check that flags are contained within [] - if (!flagString.StartsWith("[") || !flagString.EndsWith("]")) - { - throw new FormatException(); - } // Lexing esque step to split all flags. - // Illegal syntax to have any spaces. + // Invalid syntax to have any spaces. var tokens = flagString.Substring(1, flagString.Length - 2).Split(','); - // Go through tokens and verify they have meaning. - // Flags can be KVPs, delimited by '='. + var flags = new Flags(); foreach (string token in tokens) { - if (string.IsNullOrEmpty(token)) + var hasPayload = token.Split('='); + if (hasPayload.Length == 2) { - continue; - } - string[] kvp = token.Split('='); - if (kvp.Length == 1) - { - flags.SetFlag(kvp[0], null); - } - else if (kvp.Length == 2) - { - flags.SetFlag(kvp[0], kvp[1]); + flags.SetFlag(hasPayload[0], hasPayload[1]); } else { - throw new FormatException(); + flags.SetFlag(hasPayload[0], string.Empty); } } + return flags; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlagType.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagType.cs similarity index 87% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlagType.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagType.cs index acdb289d2b..85919a7a41 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlagType.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FlagType.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { - public enum RuleFlagType + public enum FlagType { EscapeBackreference, Chain, @@ -28,8 +28,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite QSLast, Redirect, Skip, - Type, - // Non-modrewrite rule - FullUrl + Type } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Flags.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Flags.cs new file mode 100644 index 0000000000..29d3ea5fc5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Flags.cs @@ -0,0 +1,125 @@ +// 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; + +namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite +{ + // For more information of flags, and what flags we currently support: + // https://github.com/aspnet/BasicMiddleware/issues/66 + // http://httpd.apache.org/docs/current/expr.html#vars + public class Flags + { + private static IDictionary _ruleFlagLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { + { "b", FlagType.EscapeBackreference}, + { "c", FlagType.Chain }, + { "chain", FlagType.Chain}, + { "co", FlagType.Cookie }, + { "cookie", FlagType.Cookie }, + { "dpi", FlagType.DiscardPath }, + { "discardpath", FlagType.DiscardPath }, + { "e", FlagType.Env}, + { "env", FlagType.Env}, + { "end", FlagType.End }, + { "f", FlagType.Forbidden }, + { "forbidden", FlagType.Forbidden }, + { "g", FlagType.Gone }, + { "gone", FlagType.Gone }, + { "h", FlagType.Handler }, + { "handler", FlagType.Handler }, + { "l", FlagType.Last }, + { "last", FlagType.Last }, + { "n", FlagType.Next }, + { "next", FlagType.Next }, + { "nc", FlagType.NoCase }, + { "nocase", FlagType.NoCase }, + { "ne", FlagType.NoEscape }, + { "noescape", FlagType.NoEscape }, + { "ns", FlagType.NoSubReq }, + { "nosubreq", FlagType.NoSubReq }, + { "p", FlagType.Proxy }, + { "proxy", FlagType.Proxy }, + { "pt", FlagType.PassThrough }, + { "passthrough", FlagType.PassThrough }, + { "qsa", FlagType.QSAppend }, + { "qsappend", FlagType.QSAppend }, + { "qsd", FlagType.QSDiscard }, + { "qsdiscard", FlagType.QSDiscard }, + { "qsl", FlagType.QSLast }, + { "qslast", FlagType.QSLast }, + { "r", FlagType.Redirect }, + { "redirect", FlagType.Redirect }, + { "s", FlagType.Skip }, + { "skip", FlagType.Skip }, + { "t", FlagType.Type }, + { "type", FlagType.Type }, + }; + + public IDictionary FlagDictionary { get; } + + public Flags(IDictionary flags) + { + FlagDictionary = flags; + } + + public Flags() + { + FlagDictionary = new Dictionary(); + } + + public void SetFlag(string flag, string value) + { + FlagType res; + if (!_ruleFlagLookup.TryGetValue(flag, out res)) + { + throw new FormatException("Unrecognized flag"); + } + SetFlag(res, value); + } + + public void SetFlag(FlagType flag, string value) + { + if (value == null) + { + value = string.Empty; + } + FlagDictionary[flag] = value; + } + + public bool GetValue(FlagType flag, out string value) + { + string res; + if (!FlagDictionary.TryGetValue(flag, out res)) + { + value = null; + return false; + } + value = res; + return true; + } + + public string this[FlagType flag] + { + get + { + string res; + if (!FlagDictionary.TryGetValue(flag, out res)) + { + return null; + } + return res; + } + set + { + FlagDictionary[flag] = value ?? string.Empty; + } + } + + public bool HasFlag(FlagType flag) + { + string res; + return FlagDictionary.TryGetValue(flag, out res); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs new file mode 100644 index 0000000000..35c2ecd0df --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs @@ -0,0 +1,77 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite +{ + public class ModRewriteRedirectAction : UrlAction + { + public int StatusCode { get; } + public bool QueryStringAppend { get; } + public bool QueryStringDelete { get; } + public bool EscapeBackReferences { get; } + + public ModRewriteRedirectAction( + int statusCode, + Pattern pattern, + bool queryStringAppend, + bool queryStringDelete, + bool escapeBackReferences) + { + StatusCode = statusCode; + Url = pattern; + QueryStringAppend = queryStringAppend; + QueryStringDelete = queryStringDelete; + EscapeBackReferences = escapeBackReferences; + } + + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + var pattern = Url.Evaluate(context, ruleMatch, condMatch); + if (EscapeBackReferences) + { + // TODO right way to escape backreferences? + pattern = Uri.EscapeDataString(pattern); + } + context.HttpContext.Response.StatusCode = StatusCode; + + // url can either contain the full url or the path and query + // always add to location header. + // TODO check for false positives + var split = pattern.IndexOf('?'); + if (split >= 0) + { + QueryString query; + if (QueryStringAppend) + { + query = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split))); + } + else + { + query = new QueryString(pattern.Substring(split)); + } + + // not using the response.redirect here because status codes may be 301, 302, 307, 308 + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; + } + else + { + // If the request url has a query string and the target does not, append the query string + // by default. + if (QueryStringDelete) + { + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern; + } + else + { + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern + context.HttpContext.Request.QueryString; + } + } + return RuleResult.ResponseComplete; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs new file mode 100644 index 0000000000..2fd6a0940c --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs @@ -0,0 +1,98 @@ +// 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.Extensions; +using Microsoft.AspNetCore.Rewrite.Internal; + +namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite +{ + public class ModRewriteRewriteAction : UrlAction + { + private readonly string ForwardSlash = "/"; + public RuleResult Result { get; } + public bool QueryStringAppend { get; } + public bool QueryStringDelete { get; } + public bool EscapeBackReferences { get; } + + public ModRewriteRewriteAction( + RuleResult result, + Pattern pattern, + bool queryStringAppend, + bool queryStringDelete, + bool escapeBackReferences) + { + Result = result; + Url = pattern; + QueryStringAppend = queryStringAppend; + QueryStringDelete = queryStringDelete; + EscapeBackReferences = escapeBackReferences; + } + + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + var pattern = Url.Evaluate(context, ruleMatch, condMatch); + + // TODO PERF, substrings, object creation, etc. + if (pattern.IndexOf("://") >= 0) + { + string scheme = null; + HostString host; + PathString path; + QueryString query; + FragmentString fragment; + UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment); + + if (query.HasValue) + { + if (QueryStringAppend) + { + context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(query); + } + else + { + context.HttpContext.Request.QueryString = query; + } + } + else if (QueryStringDelete) + { + context.HttpContext.Request.QueryString = QueryString.Empty; + } + + context.HttpContext.Request.Scheme = scheme; + context.HttpContext.Request.Host = host; + context.HttpContext.Request.Path = path; + } + else + { + // TODO fix with redirect action logic + var split = pattern.IndexOf('?'); + if (split >= 0) + { + var path = pattern.Substring(0, split); + if (path.StartsWith(ForwardSlash)) + { + context.HttpContext.Request.Path = PathString.FromUriComponent(path); + } + else + { + context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path); + } + context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split))); + } + else + { + if (pattern.StartsWith(ForwardSlash)) + { + context.HttpContext.Request.Path = PathString.FromUriComponent(pattern); + } + else + { + context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern); + } + } + } + return Result; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/Operand.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/Operand.cs deleted file mode 100644 index 565cd932fc..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/Operand.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text.RegularExpressions; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands -{ - public abstract class Operand - { - public abstract bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider); - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperand.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperand.cs deleted file mode 100644 index fd7f649e7d..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperand.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Text.RegularExpressions; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands -{ - public class PropertyOperand : Operand - { - public PropertyOperationType Operation { get; } - - public PropertyOperand(PropertyOperationType operation) - { - Operation = operation; - } - public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider) - { - switch(Operation) - { - case PropertyOperationType.Directory: - return fileProvider.GetFileInfo(testString).IsDirectory; - case PropertyOperationType.RegularFile: - return fileProvider.GetFileInfo(testString).Exists; - case PropertyOperationType.Size: - var fileInfo = fileProvider.GetFileInfo(testString); - return fileInfo.Exists && fileInfo.Length > 0; - case PropertyOperationType.ExistingUrl: - throw new NotSupportedException("No support for internal sub requests."); - case PropertyOperationType.ExistingFile: - throw new NotSupportedException("No support for internal sub requests."); - case PropertyOperationType.SymbolicLink: - throw new NotSupportedException("No support for checking symbolic links."); - case PropertyOperationType.Executable: - throw new NotSupportedException("No support for checking executable permissions."); - default: - return false; - } - } - } - - -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperation.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperation.cs deleted file mode 100644 index f151c1db64..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/PropertyOperation.cs +++ /dev/null @@ -1,17 +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. - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands -{ - public enum PropertyOperationType - { - None, - Directory, - RegularFile, - ExistingFile, - SymbolicLink, - Size, - ExistingUrl, - Executable - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/RegexOperand.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/RegexOperand.cs deleted file mode 100644 index e7159b8e43..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/RegexOperand.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text.RegularExpressions; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands -{ - public class RegexOperand : Operand - { - public Regex RegexOperation { get; } - - public RegexOperand(Regex regex) - { - RegexOperation = regex; - } - - public override bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider) - { - previous = RegexOperation.Match(concatTestString); - return previous.Success; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperand.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperand.cs deleted file mode 100644 index a48b929eff..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperand.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Text.RegularExpressions; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands -{ - public class StringOperand : Operand - { - public string Value { get; set; } - public StringOperationType Operation { get; set; } - - public StringOperand(string value, StringOperationType operation) - { - Value = value; - Operation = operation; - } - - public override bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider) - { - switch (Operation) - { - case StringOperationType.Equal: - return concatTestString.CompareTo(Value) == 0; - case StringOperationType.Greater: - return concatTestString.CompareTo(Value) > 0; - case StringOperationType.GreaterEqual: - return concatTestString.CompareTo(Value) >= 0; - case StringOperationType.Less: - return concatTestString.CompareTo(Value) < 0; - case StringOperationType.LessEqual: - return concatTestString.CompareTo(Value) <= 0; - default: - return null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedConditionExpression.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedModRewriteCondition.cs similarity index 50% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedConditionExpression.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedModRewriteCondition.cs index 5bb69e6e8d..e854343fe5 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedConditionExpression.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ParsedModRewriteCondition.cs @@ -3,20 +3,21 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { - public class ParsedModRewriteExpression + public class ParsedModRewriteInput { public bool Invert { get; set; } - public ConditionType Type { get; set; } - public OperationType Operation { get; set; } + public ConditionType ConditionType { get; set; } + public OperationType OperationType { get; set; } public string Operand { get; set; } - public ParsedModRewriteExpression(bool invert, ConditionType type, OperationType operation, string operand) + + public ParsedModRewriteInput() { } + + public ParsedModRewriteInput(bool invert, ConditionType conditionType, OperationType operationType, string operand) { Invert = invert; - Type = type; - Operation = operation; + ConditionType = conditionType; + OperationType = operationType; Operand = operand; } - - public ParsedModRewriteExpression() { } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Pattern.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Pattern.cs deleted file mode 100644 index 8d7de10791..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Pattern.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - /// - /// Contains a sequence of pattern segments, which on obtaining the context, will create the appropriate - /// test string and condition for rules and conditions. - /// - public class Pattern - { - private IReadOnlyList Segments { get; } - /// - /// Creates a new Pattern - /// - /// List of pattern segments which will be applied. - public Pattern(IReadOnlyList segments) - { - Segments = segments; - } - - /// - /// Creates the appropriate test string from the Httpcontext and Segments. - /// - /// - /// - /// - /// - public string GetPattern(HttpContext context, Match ruleMatch, Match prevCondition) - { - var res = new StringBuilder(); - foreach (var segment in Segments) - { - // TODO handle case when segment.Variable is 0 in rule and condition - switch (segment.Type) - { - case SegmentType.Literal: - res.Append(segment.Variable); - break; - case SegmentType.ServerParameter: - res.Append(ServerVariables.Resolve(segment.Variable, context)); - break; - case SegmentType.RuleParameter: - var ruleParam = ruleMatch.Groups[segment.Variable]; - if (ruleParam != null) - { - res.Append(ruleParam); - } - break; - case SegmentType.ConditionParameter: - var condParam = prevCondition.Groups[segment.Variable]; - if (condParam != null) - { - res.Append(condParam); - } - break; - } - } - return res.ToString(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/PatternSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/PatternSegment.cs deleted file mode 100644 index 8a59179b5a..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/PatternSegment.cs +++ /dev/null @@ -1,26 +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. - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - /// - /// A Pattern segment contains a portion of the test string/ substitution segment with a type associated. - /// This type can either be: Regex, Rule Variable, Condition Variable, or a Server Variable. - /// - public class PatternSegment - { - public string Variable { get; } // TODO make this a range s.t. we don't copy the string. - public SegmentType Type { get; } - - /// - /// Create a Pattern segment. - /// - /// - /// - public PatternSegment(string variable, SegmentType type) - { - Variable = variable; - Type = type; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs index 9c288b00c3..0510803c4f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs @@ -3,119 +3,221 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PreActions; +using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; +using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public class RuleBuilder { - private ParsedModRewriteExpression _pce; - private List _conditions; - private RuleFlags _flags; - private Pattern _patterns; + private Conditions _conditions; + private UrlAction _action; + private UrlMatch _match; + private List _preActions; + + private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); + public ModRewriteRule Build() { - var ruleExpression = ExpressionCreator.CreateRuleExpression(_pce, _flags); - return new ModRewriteRule(_conditions, ruleExpression, _patterns, _flags); + if (_action == null || _match == null) + { + // TODO throw an exception here, find apporpriate exception + } + return new ModRewriteRule(_match, _conditions, _action, _preActions); } - public RuleBuilder(string initialRule, string transformation) : - this(initialRule, transformation, flags: null) - { - } - public RuleBuilder(string rule) + public void AddRule(string rule) { + // TODO var tokens = Tokenizer.Tokenize(rule); - if (tokens.Count == 3) + var regex = RuleRegexParser.ParseRuleRegex(tokens[1]); + var pattern = TestStringParser.Parse(tokens[2]); + var flags = new Flags(); + if (tokens.Count == 4) { - CreateRule(tokens[1], tokens[2], flags: null); + flags = FlagParser.Parse(tokens[3]); } - else if (tokens.Count == 4) + AddMatch(regex, flags); + AddAction(pattern, flags); + } + + public void AddConditionFromParts( + Pattern pattern, + ParsedModRewriteInput input, + Flags flags) + { + if (_conditions == null) { - CreateRule(tokens[1], tokens[2], tokens[3]); + _conditions = new Conditions(); + } + + var condition = new Condition(); + + condition.OrNext = flags.HasFlag(FlagType.Or); + condition.Input = pattern; + + if (input.ConditionType == ConditionType.Regex) + { + // TODO make nullable? + if (flags.HasFlag(FlagType.NoCase)) + { + condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert); + } + else + { + condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert); + } + } + else if (input.ConditionType == ConditionType.IntComp) + { + switch (input.OperationType) + { + case OperationType.Equal: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Equal); + break; + case OperationType.Greater: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Greater); + break; + case OperationType.GreaterEqual: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.GreaterEqual); + break; + case OperationType.Less: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Less); + break; + case OperationType.LessEqual: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.LessEqual); + break; + case OperationType.NotEqual: + condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.NotEqual); + break; + default: + throw new ArgumentException("Invalid operation for integer comparison."); + } + } + else if (input.ConditionType == ConditionType.StringComp) + { + switch (input.OperationType) + { + case OperationType.Equal: + condition.Match = new StringMatch(input.Operand, StringOperationType.Equal); + break; + case OperationType.Greater: + condition.Match = new StringMatch(input.Operand, StringOperationType.Greater); + break; + case OperationType.GreaterEqual: + condition.Match = new StringMatch(input.Operand, StringOperationType.GreaterEqual); + break; + case OperationType.Less: + condition.Match = new StringMatch(input.Operand, StringOperationType.Less); + break; + case OperationType.LessEqual: + condition.Match = new StringMatch(input.Operand, StringOperationType.LessEqual); + break; + default: + throw new ArgumentException("Invalid operation for string comparison."); + } } else { - throw new ArgumentException(); + switch (input.OperationType) + { + case OperationType.Directory: + condition.Match = new IsDirectoryMatch(input.Invert); + break; + case OperationType.RegularFile: + condition.Match = new IsFileMatch(input.Invert); + break; + case OperationType.ExistingFile: + condition.Match = new IsFileMatch(input.Invert); + break; + case OperationType.SymbolicLink: + throw new NotImplementedException("Symbolic links are not implemented"); + case OperationType.Size: + condition.Match = new FileSizeMatch(input.Invert); + break; + case OperationType.ExistingUrl: + throw new NotImplementedException("Existing Url lookups not implemented"); + case OperationType.Executable: + throw new NotImplementedException("Executable Property search is not implemented"); + default: + // TODO change exception + throw new ArgumentException("Invalid operation for property comparison."); + } } + _conditions.ConditionList.Add(condition); } - public RuleBuilder(string initialRule, string transformation, string flags) + public void AddMatch( + ParsedModRewriteInput input, + Flags flags) { - CreateRule(initialRule, transformation, flags); - } - - public void CreateRule(string initialRule, string transformation, string flags) - { - _pce = RuleRegexParser.ParseRuleRegex(initialRule); - _patterns = ConditionTestStringParser.ParseConditionTestString(transformation); - _flags = FlagParser.ParseRuleFlags(flags); - } - - public void AddCondition(string condition) - { - if (_conditions == null) + if (flags.HasFlag(FlagType.NoCase)) { - _conditions = new List(); - } - var condBuilder = new ConditionBuilder(condition); - _conditions.Add(condBuilder.Build()); - } - - public void AddCondition(Condition condition) - { - if (_conditions == null) - { - _conditions = new List(); - } - _conditions.Add(condition); - } - - public void AddConditions(List conditions) - { - if (_conditions == null) - { - _conditions = new List(); - } - _conditions.AddRange(conditions); - } - - public void SetFlag(string flag) - { - SetFlag(flag, value: null); - } - - public void SetFlag(RuleFlagType flag) - { - SetFlag(flag, value: null); - } - - public void SetFlag(string flag, string value) - { - if (_flags == null) - { - _flags = new RuleFlags(); - } - _flags.SetFlag(flag, value); - } - - public void SetFlag(RuleFlagType flag, string value) - { - if (_flags == null) - { - _flags = new RuleFlags(); - } - _flags.SetFlag(flag, value); - } - - public void SetFlags(string flags) - { - if (_flags == null) - { - _flags = FlagParser.ParseRuleFlags(flags); + _match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert); } else { - FlagParser.ParseRuleFlags(flags, _flags); + _match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert); + } + } + + public void AddAction( + Pattern pattern, + Flags flags) + { + // first create pre conditions + if (_preActions == null) + { + _preActions = new List(); + } + + string flag; + if (flags.GetValue(FlagType.Cookie, out flag)) + { + // parse cookie + _preActions.Add(new ChangeCookiePreAction(flag)); + } + + if (flags.GetValue(FlagType.Env, out flag)) + { + // parse env + _preActions.Add(new ChangeEnvironmentPreAction(flag)); + } + + if (flags.HasFlag(FlagType.Forbidden)) + { + _action = new ForbiddenAction(); + } + else if (flags.HasFlag(FlagType.Gone)) + { + _action = new GoneAction(); + } + else + { + var escapeBackReference = flags.HasFlag(FlagType.EscapeBackreference); + var queryStringAppend = flags.HasFlag(FlagType.QSAppend); + var queryStringDelete = flags.HasFlag(FlagType.QSDiscard); + + // is redirect? + string statusCode; + if (flags.GetValue(FlagType.Redirect, out statusCode)) + { + int res; + if (!int.TryParse(statusCode, out res)) + { + throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1)); + } + _action = new ModRewriteRedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference); + } + else + { + var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last); + var redirect = last ? RuleResult.StopRules : RuleResult.Continue; + _action = new ModRewriteRewriteAction(redirect, pattern, queryStringAppend, queryStringDelete, escapeBackReference); + } } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs deleted file mode 100644 index 1c29b18f0e..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs +++ /dev/null @@ -1,13 +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.Rewrite.Internal.ModRewrite.Operands; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - public class RuleExpression - { - public RegexOperand Operand { get; set; } - public bool Invert { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlags.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlags.cs deleted file mode 100644 index 1c2d632d41..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleFlags.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite -{ - public class RuleFlags - { - private IDictionary _ruleFlagLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "b", RuleFlagType.EscapeBackreference}, - { "c", RuleFlagType.Chain }, - { "chain", RuleFlagType.Chain}, - { "co", RuleFlagType.Cookie }, - { "cookie", RuleFlagType.Cookie }, - { "dpi", RuleFlagType.DiscardPath }, - { "discardpath", RuleFlagType.DiscardPath }, - { "e", RuleFlagType.Env}, - { "env", RuleFlagType.Env}, - { "end", RuleFlagType.End }, - { "f", RuleFlagType.Forbidden }, - { "forbidden", RuleFlagType.Forbidden }, - { "g", RuleFlagType.Gone }, - { "gone", RuleFlagType.Gone }, - { "h", RuleFlagType.Handler }, - { "handler", RuleFlagType.Handler }, - { "l", RuleFlagType.Last }, - { "last", RuleFlagType.Last }, - { "n", RuleFlagType.Next }, - { "next", RuleFlagType.Next }, - { "nc", RuleFlagType.NoCase }, - { "nocase", RuleFlagType.NoCase }, - { "ne", RuleFlagType.NoEscape }, - { "noescape", RuleFlagType.NoEscape }, - { "ns", RuleFlagType.NoSubReq }, - { "nosubreq", RuleFlagType.NoSubReq }, - { "p", RuleFlagType.Proxy }, - { "proxy", RuleFlagType.Proxy }, - { "pt", RuleFlagType.PassThrough }, - { "passthrough", RuleFlagType.PassThrough }, - { "qsa", RuleFlagType.QSAppend }, - { "qsappend", RuleFlagType.QSAppend }, - { "qsd", RuleFlagType.QSDiscard }, - { "qsdiscard", RuleFlagType.QSDiscard }, - { "qsl", RuleFlagType.QSLast }, - { "qslast", RuleFlagType.QSLast }, - { "r", RuleFlagType.Redirect }, - { "redirect", RuleFlagType.Redirect }, - { "s", RuleFlagType.Skip }, - { "skip", RuleFlagType.Skip }, - { "t", RuleFlagType.Type }, - { "type", RuleFlagType.Type }, - // TODO make this a load bool instead of a flag for the file and rules. - { "u", RuleFlagType.FullUrl }, - { "url", RuleFlagType.FullUrl } - }; - - public IDictionary FlagDictionary { get; } - - public RuleFlags(IDictionary flags) - { - // TODO use ref to check dictionary equality - FlagDictionary = flags; - } - - public RuleFlags() - { - FlagDictionary = new Dictionary(); - } - - public void SetFlag(string flag, string value) - { - RuleFlagType res; - if (!_ruleFlagLookup.TryGetValue(flag, out res)) - { - throw new FormatException("Invalid flag"); - } - SetFlag(res, value); - } - public void SetFlag(RuleFlagType flag, string value) - { - if (value == null) - { - value = string.Empty; - } - FlagDictionary[flag] = value; - } - - public string GetValue(RuleFlagType flag) - { - CleanupResources(); - string res; - if (!FlagDictionary.TryGetValue(flag, out res)) - { - return null; - } - return res; - } - - public string this[RuleFlagType flag] - { - get - { - string res; - if (!FlagDictionary.TryGetValue(flag, out res)) - { - return null; - } - return res; - } - set - { - FlagDictionary[flag] = value ?? string.Empty; - } - } - - public bool HasFlag(RuleFlagType flag) - { - CleanupResources(); - string res; - return FlagDictionary.TryGetValue(flag, out res); - } - - private void CleanupResources() - { - if (_ruleFlagLookup != null) - { - _ruleFlagLookup = null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs index 3d8254e44d..e063b36bc4 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs @@ -7,19 +7,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public static class RuleRegexParser { - public static ParsedModRewriteExpression ParseRuleRegex(string regex) + public static ParsedModRewriteInput ParseRuleRegex(string regex) { - if (regex == null || regex == String.Empty) + if (regex == null || regex == string.Empty) { - throw new FormatException(); + throw new FormatException("Regex expression is null"); } if (regex.StartsWith("!")) { - return new ParsedModRewriteExpression { Invert = true, Operand = regex.Substring(1) }; + return new ParsedModRewriteInput { Invert = true, Operand = regex.Substring(1) }; } else { - return new ParsedModRewriteExpression { Invert = false, Operand = regex}; + return new ParsedModRewriteInput { Invert = false, Operand = regex}; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/SegmentType.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/SegmentType.cs index b8cbb3f3c3..2e86cde344 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/SegmentType.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/SegmentType.cs @@ -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 +namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public enum SegmentType { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs index ec219142d2..5b664f4172 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs @@ -2,11 +2,8 @@ // 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.Globalization; -using System.Net.Sockets; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite @@ -16,164 +13,112 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// public static class ServerVariables { - public static HashSet ValidServerVariables = new HashSet() - { - "HTTP_ACCEPT", - "HTTP_COOKIE", - "HTTP_FORWARDED", - "HTTP_HOST", - "HTTP_PROXY_CONNECTION", - "HTTP_REFERER", - "HTTP_USER_AGENT", - "AUTH_TYPE", - "CONN_REMOTE_ADDR", - "CONTEXT_PREFIX", - "CONTEXT_DOCUMENT_ROOT", - "IPV6", - "PATH_INFO", - "QUERY_STRING", - "REMOTE_ADDR", - "REMOTE_HOST", - "REMOTE_IDENT", - "REMOTE_PORT", - "REMOTE_USER", - "REQUEST_METHOD", - "SCRIPT_FILENAME", - "DOCUMENT_ROOT", - "SCRIPT_GROUP", - "SCRIPT_USER", - "SERVER_ADDR", - "SERVER_ADMIN", - "SERVER_NAME", - "SERVER_PORT", - "SERVER_PROTOCOL", - "SERVER_SOFTWARE", - "TIME_YEAR", - "TIME_MON", - "TIME_DAY", - "TIME_HOUR", - "TIME_MIN", - "TIME_SEC", - "TIME_WDAY", - "TIME", - "API_VERSION", - "HTTPS", - "IS_SUBREQ", - "REQUEST_FILENAME", - "REQUEST_SCHEME", - "REQUEST_URI", - "THE_REQUEST" - - }; /// /// Translates mod_rewrite server variables strings to an enum of different server variables. /// /// The server variable string. - /// The HttpContext context. + /// The Parser context /// The appropriate enum if the server variable exists, else ServerVariable.None - public static string Resolve(string variable, HttpContext context) + public static PatternSegment FindServerVariable(string variable, ParserContext context) { - // TODO talk about perf here switch (variable) { case "HTTP_ACCEPT": - return context.Request.Headers[HeaderNames.Accept]; + return new HeaderSegment(HeaderNames.Accept); case "HTTP_COOKIE": - return context.Request.Headers[HeaderNames.Cookie]; - case "HTTP_FORWARDED": - return context.Request.Headers["Forwarded"]; + return new HeaderSegment(HeaderNames.Cookie); case "HTTP_HOST": - return context.Request.Headers[HeaderNames.Host]; - case "HTTP_PROXY_CONNECTION": - return context.Request.Headers[HeaderNames.ProxyAuthenticate]; + return new HeaderSegment(HeaderNames.Host); case "HTTP_REFERER": - return context.Request.Headers[HeaderNames.Referer]; + return new HeaderSegment(HeaderNames.Referer); case "HTTP_USER_AGENT": - return context.Request.Headers[HeaderNames.UserAgent]; + return new HeaderSegment(HeaderNames.UserAgent); + case "HTTP_CONNECTION": + return new HeaderSegment(HeaderNames.Connection); + case "HTTP_FORWARDED": + return new HeaderSegment("Forwarded"); case "AUTH_TYPE": - throw new NotImplementedException(); + throw new NotImplementedException("Auth-Type server variable is not supported"); case "CONN_REMOTE_ADDR": - return context.Connection.RemoteIpAddress?.ToString(); + return new RemoteAddressSegment(); case "CONTEXT_PREFIX": - throw new NotImplementedException(); + throw new NotImplementedException("Context-prefix server variable is not supported"); case "CONTEXT_DOCUMENT_ROOT": - throw new NotImplementedException(); + throw new NotImplementedException("Context-Document-Root server variable is not supported"); case "IPV6": - return context.Connection.LocalIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off"; + return new IsIPV6Segment(); case "PATH_INFO": - throw new NotImplementedException(); + throw new NotImplementedException("Path-Info server variable is not supported"); case "QUERY_STRING": - return context.Request.QueryString.Value; + return new QueryStringSegment(); case "REMOTE_ADDR": - return context.Connection.RemoteIpAddress?.ToString(); + return new RemoteAddressSegment(); case "REMOTE_HOST": - throw new NotImplementedException(); + throw new NotImplementedException("Remote-Host server variable is not supported"); case "REMOTE_IDENT": - throw new NotImplementedException(); + throw new NotImplementedException("Remote-Identity server variable is not supported"); case "REMOTE_PORT": - return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); + return new RemotePortSegment(); case "REMOTE_USER": - throw new NotImplementedException(); + throw new NotImplementedException("Remote-User server variable is not supported"); case "REQUEST_METHOD": - return context.Request.Method; + return new RequestMethodSegment(); case "SCRIPT_FILENAME": - throw new NotImplementedException(); + throw new NotImplementedException("Script-Filename server variable is not supported"); case "DOCUMENT_ROOT": - throw new NotImplementedException(); + throw new NotImplementedException("Document-Root server variable is not supported"); case "SCRIPT_GROUP": - throw new NotImplementedException(); + throw new NotImplementedException("Script-Group server variable is not supported"); case "SCRIPT_USER": - throw new NotImplementedException(); + throw new NotImplementedException("Script-User server variable is not supported"); case "SERVER_ADDR": - return context.Connection.LocalIpAddress?.ToString(); + return new LocalAddressSegment(); case "SERVER_ADMIN": - throw new NotImplementedException(); + throw new NotImplementedException("Server-Admin server variable is not supported"); case "SERVER_NAME": - throw new NotImplementedException(); + throw new NotImplementedException("Server-Name server variable is not supported"); case "SERVER_PORT": - return context.Connection.LocalPort.ToString(CultureInfo.InvariantCulture); + return new LocalPortSegment(); case "SERVER_PROTOCOL": - return context.Features.Get()?.Protocol; + return new ServerProtocolSegment(); case "SERVER_SOFTWARE": - throw new NotImplementedException(); + throw new NotImplementedException("Server-Software server variable is not supported"); case "TIME_YEAR": - return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_MON": - return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_DAY": - return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_HOUR": - return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_MIN": - return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_SEC": - return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME_WDAY": - return ((int) DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "TIME": - return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture); + return new DateTimeSegment(variable); case "API_VERSION": throw new NotImplementedException(); case "HTTPS": - return context.Request.IsHttps ? "on" : "off"; + return new IsHttpsModSegment(); case "HTTP2": - return context.Request.Scheme == "http2" ? "on" : "off"; + throw new NotImplementedException("Http2 server variable is not supported"); case "IS_SUBREQ": // TODO maybe can do this? context.Request.HttpContext ? - throw new NotImplementedException(); + throw new NotImplementedException("Is-Subrequest server variable is not supported"); case "REQUEST_FILENAME": - return context.Request.Path.Value.Substring(1); + return new RequestFileNameSegment(); case "REQUEST_SCHEME": - return context.Request.Scheme; + return new SchemeSegment(); case "REQUEST_URI": - // TODO This isn't an ideal solution. What this assumes is that all conditions don't have a leading slash before it. - return context.Request.Path.Value.Substring(1); + return new UrlSegment(); case "THE_REQUEST": - // TODO - throw new NotImplementedException(); + throw new NotImplementedException("The-Request server variable is not supported"); default: - return null; + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(variable, context.Index)); } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionTestStringParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs similarity index 72% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionTestStringParser.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs index e3f8b1a18e..4564c3cba1 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionTestStringParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { /// /// Parses the TestString segment of the mod_rewrite condition. /// - public class ConditionTestStringParser + public class TestStringParser { private const char Percent = '%'; private const char Dollar = '$'; @@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// %1 /// $1 /// A new , containing a list of - public static Pattern ParseConditionTestString(string testString) + /// http://httpd.apache.org/docs/current/mod/mod_rewrite.html + public static Pattern Parse(string testString) { if (testString == null) { @@ -45,12 +47,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite // This is a server parameter, parse for a condition variable if (!context.Next()) { - throw new FormatException(context.Error()); - } - if (!ParseConditionParameter(context, results)) - { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index)); } + ParseConditionParameter(context, results); } else if (context.Current == Dollar) { @@ -58,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite // and create a new Pattern Segment. if (!context.Next()) { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); } context.Mark(); if (context.Current >= '0' && context.Current <= '9') @@ -66,21 +65,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite context.Next(); var ruleVariable = context.Capture(); context.Back(); - results.Add(new PatternSegment(ruleVariable, SegmentType.RuleParameter)); + int parsedIndex; + if (!int.TryParse(ruleVariable, out parsedIndex)) + { + // TODO this should always pass, remove try parse? + throw new FormatException(Resources.FormatError_InputParserInvalidInteger(ruleVariable, context.Index)); + } + results.Add(new RuleMatchSegment(parsedIndex)); } else { - throw new FormatException(context.Error()); + throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index)); } } else { // Parse for literals, which will return on either the end of the test string // or when it hits a special character - if (!ParseLiteral(context, results)) - { - throw new FormatException(context.Error()); - } + ParseLiteral(context, results); } } return new Pattern(results); @@ -95,7 +97,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// The ParserContext /// The List of results which the new condition parameter will be added. /// true - private static bool ParseConditionParameter(ParserContext context, List results) + private static void ParseConditionParameter(ParserContext context, List results) { // Parse { } if (context.Current == OpenBrace) @@ -104,34 +106,26 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite if (!context.Next()) { // Dangling { - return false; + throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } context.Mark(); while (context.Current != CloseBrace) { if (!context.Next()) { - // No closing } for the server variable - return false; + throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } else if (context.Current == Colon) { // Have a segmented look up Ex: HTTP:xxxx // TODO + throw new NotImplementedException("Segmented Lookups no implemented"); } } // Need to verify server variable captured exists var rawServerVariable = context.Capture(); - if (IsValidServerVariable(rawServerVariable)) - { - results.Add(new PatternSegment(rawServerVariable, SegmentType.ServerParameter)); - } - else - { - // invalid. - return false; - } + results.Add(ServerVariables.FindServerVariable(rawServerVariable, context)); } else if (context.Current >= '0' && context.Current <= '9') { @@ -143,14 +137,18 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite // Once we leave this method, the while loop will call next again. Because // capture is exclusive, we need to go one past the end index, capture, and then go back. context.Back(); - results.Add(new PatternSegment(rawConditionParameter, SegmentType.ConditionParameter)); + int parsedIndex; + if (!int.TryParse(rawConditionParameter, out parsedIndex)) + { + throw new FormatException(Resources.FormatError_InputParserInvalidInteger(rawConditionParameter, context.Index)); + } + results.Add(new ConditionMatchSegment(parsedIndex)); } else { // illegal escape of a character - return false; + throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } - return true; } /// @@ -159,7 +157,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// /// /// - private static bool ParseLiteral(ParserContext context, List results) + private static void ParseLiteral(ParserContext context, List results) { context.Mark(); string literal; @@ -177,29 +175,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite break; } } - - if (IsValidLiteral(context, literal)) - { - // add results - results.Add(new PatternSegment(literal, SegmentType.Literal)); - return true; - } - else - { - return false; - } - } - - private static bool IsValidLiteral(ParserContext context, string literal) - { - // TODO Once escape characters are discussed, figure this out. - return true; - } - - private static bool IsValidServerVariable(string variable) - { - // TODO Once escape characters are discussed, figure this out. - return ServerVariables.ValidServerVariables.Contains(variable); + // add results + results.Add(new LiteralSegment(literal)); } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs index 18979e29a0..a13bdeb9ef 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Rewrite.Internal; namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { @@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite if (!context.Next()) { // Means that a character was not escaped appropriately Ex: "foo\" - throw new ArgumentException(); + throw new FormatException("Invalid escaper character in string " + rule); } } else if (context.Current == Space || context.Current == Tab) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs index 529ae08eb5..6d7e090b9a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs @@ -1,208 +1,54 @@ // 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 Microsoft.Net.Http.Headers; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public class ModRewriteRule : Rule { - public List Conditions { get; set; } = new List(); - public string Description { get; set; } = string.Empty; - public RuleExpression InitialRule { get; set; } - public Pattern Transform { get; set; } - public RuleFlags Flags { get; set; } = new RuleFlags(); - public ModRewriteRule() { } + public UrlMatch InitialMatch { get; set; } + public Conditions Conditions { get; set; } + public UrlAction Action { get; set; } + public List PreActions { get; set; } - public ModRewriteRule(List conditions, RuleExpression initialRule, Pattern transforms, RuleFlags flags, string description = "") + public ModRewriteRule(UrlMatch initialMatch, Conditions conditions, UrlAction urlAction, List preActions) { Conditions = conditions; - InitialRule = initialRule; - Transform = transforms; - Flags = flags; - Description = description; + InitialMatch = initialMatch; + Action = urlAction; + PreActions = preActions; } public override RuleResult ApplyRule(RewriteContext context) { // 1. Figure out which section of the string to match for the initial rule. - var results = InitialRule.Operand.RegexOperation.Match(context.HttpContext.Request.Path.ToString()); + var initMatchRes = InitialMatch.Evaluate(context.HttpContext.Request.Path, context); - string flagRes = null; - if (CheckMatchResult(results.Success)) + if (!initMatchRes.Success) { return RuleResult.Continue; } - if (Flags.HasFlag(RuleFlagType.EscapeBackreference)) + MatchResults condMatchRes = null; + if (Conditions != null) { - // TODO Escape Backreferences here. - } - - // 2. Go through all conditions and compare them to the created string - var previous = Match.Empty; - - if (!CheckCondition(context, results, previous)) - { - return RuleResult.Continue; - } - // TODO add chained flag - - // at this point, our rule passed, we can now apply the on match function - var result = Transform.GetPattern(context.HttpContext, results, previous); - - if (Flags.HasFlag(RuleFlagType.QSDiscard)) - { - context.HttpContext.Request.QueryString = new QueryString(); - } - - if ((flagRes = Flags.GetValue(RuleFlagType.Cookie)) != null) - { - // TODO CreateCookies(context); - // context.HttpContext.Response.Cookies.Append() - // Make sure this in on compile. - } - - if ((flagRes = Flags.GetValue(RuleFlagType.Env)) != null) - { - // TODO CreateEnv(context) - // context.HttpContext... - } - - if ((flagRes = Flags.GetValue(RuleFlagType.Next)) != null) - { - // TODO Next flag - } - - if (Flags.HasFlag(RuleFlagType.Forbidden)) - { - context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; - return RuleResult.ResponseComplete; - } - else if (Flags.HasFlag(RuleFlagType.Gone)) - { - context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone; - return RuleResult.ResponseComplete; - } - else if (result == "-") - { - // TODO set url to result. - } - else if (Flags.HasFlag(RuleFlagType.QSAppend)) - { - context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(result)); - } - - if ((flagRes = Flags.GetValue(RuleFlagType.Redirect)) != null) - { - int parsedInt; - if (!int.TryParse(flagRes, out parsedInt)) - { - // TODO PERF parse the status code when the flag is parsed rather than per request - throw new FormatException("Trying to parse non-int in integer comparison."); - } - context.HttpContext.Response.StatusCode = parsedInt; - if (Flags.HasFlag(RuleFlagType.FullUrl)) - { - // TODO review escaping - context.HttpContext.Response.Headers[HeaderNames.Location] = result; - } - else - { - // TODO str cat is bad, polish, review escaping - if (result.StartsWith("/")) - { - context.HttpContext.Response.Headers[HeaderNames.Location] = result + context.HttpContext.Request.QueryString; - } - else - { - context.HttpContext.Response.Headers[HeaderNames.Location] = "/" + result + context.HttpContext.Request.QueryString; - } - } - return RuleResult.ResponseComplete; - } - else - { - if (Flags.HasFlag(RuleFlagType.FullUrl)) - { - ModifyHttpContextFromUri(context.HttpContext, result); - } - else - { - if (result.StartsWith("/")) - { - context.HttpContext.Request.Path = new PathString(result); - } - else - { - context.HttpContext.Request.Path = new PathString("/" + result); - } - } - if (Flags.HasFlag(RuleFlagType.Last) || Flags.HasFlag(RuleFlagType.End)) - { - return RuleResult.StopRules; - } - else + condMatchRes = Conditions.Evaluate(context, initMatchRes); + if (!condMatchRes.Success) { return RuleResult.Continue; } } - } - private bool CheckMatchResult(bool? result) - { - if (result == null) + // At this point, we know our rule passed, first apply pre conditions, + // which can modify things like the cookie or env, and then apply the action + foreach (var preAction in PreActions) { - return false; - } - return !(result.Value ^ InitialRule.Invert); - } - - private bool CheckCondition(RewriteContext context, Match results, Match previous) - { - if (Conditions == null) - { - return true; + preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes); } - // TODO Visitor pattern here? - foreach (var condition in Conditions) - { - var concatTestString = condition.TestStringSegments.GetPattern(context.HttpContext, results, previous); - var match = condition.ConditionExpression.CheckConditionExpression(context, previous, concatTestString); - - if (match == null) - { - return false; - } - - if (!match.Value && !(condition.Flags.HasFlag(ConditionFlagType.Or))) - { - return false; - } - } - return true; - } - - private void ModifyHttpContextFromUri(HttpContext context, string uriString) - { - var uri = new Uri(uriString); - // TODO this is ugly, fix in later push. - // TODO super bad for perf, cache/locally store these and update httpcontext after all rules are applied. - var pathBase = PathString.FromUriComponent(uri); - if (!pathBase.Value.StartsWith(context.Request.PathBase)) - { - // cannot distinguish between path base and path. - throw new NotSupportedException("Modified path base from mod_rewrite rule"); - } - context.Request.Host = HostString.FromUriComponent(uri); - context.Request.Path = PathString.FromUriComponent(uri); - context.Request.QueryString = QueryString.FromUriComponent(uri); - context.Request.Scheme = uri.Scheme; + return Action.ApplyAction(context, initMatchRes, condMatchRes); } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ParserContext.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ParserContext.cs index 18b7833889..815f3012d8 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ParserContext.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ParserContext.cs @@ -8,19 +8,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal /// public class ParserContext { - private readonly string _template; + public readonly string Template; public int Index { get; set; } private int? _mark; public ParserContext(string condition) { - _template = condition; + Template = condition; Index = -1; } public char Current { - get { return (Index < _template.Length && Index >= 0) ? _template[Index] : (char)0; } + get { return (Index < Template.Length && Index >= 0) ? Template[Index] : (char)0; } } public bool Back() @@ -30,12 +30,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal public bool Next() { - return ++Index < _template.Length; + return ++Index < Template.Length; } public bool HasNext() { - return (Index + 1) < _template.Length; + return (Index + 1) < Template.Length; } public void Mark() @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal // TODO make this return a range rather than a string. if (_mark.HasValue) { - var value = _template.Substring(_mark.Value, Index - _mark.Value); + var value = Template.Substring(_mark.Value, Index - _mark.Value); _mark = null; return value; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Pattern.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/Pattern.cs similarity index 61% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Pattern.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/Pattern.cs index a796da468f..2c6367ea67 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Pattern.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/Pattern.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public class Pattern { @@ -15,16 +13,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite PatternSegments = patternSegments; } - public string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - var strBuilder = new StringBuilder(); - // TODO consider thread static for string builder - DAVID PERF foreach (var pattern in PatternSegments) { - strBuilder.Append(pattern.Evaluate(context, ruleMatch, condMatch)); + context.Builder.Append(pattern.Evaluate(context, ruleMatch, condMatch)); } - return strBuilder.ToString(); + var retVal = context.Builder.ToString(); + context.Builder.Clear(); + return retVal; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs similarity index 62% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs index 820d5cdfb4..6ea96b915f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs @@ -2,12 +2,13 @@ // 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.UrlRewrite; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class PatternSegment { // Match from prevRule, Match from prevCond - public abstract string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); + public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ConditionMatchSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ConditionMatchSegment.cs similarity index 67% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ConditionMatchSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ConditionMatchSegment.cs index 1c65243e16..057164a4cc 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ConditionMatchSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ConditionMatchSegment.cs @@ -1,9 +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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class ConditionMatchSegment : PatternSegment { @@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Index = index; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { return condMatch?.BackReference[Index]?.Value; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs new file mode 100644 index 0000000000..bb535d5178 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs @@ -0,0 +1,81 @@ +// 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.Globalization; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + public class DateTimeSegment : PatternSegment + { + private DateTimePortion _portion; + + public DateTimeSegment(string segment) + { + switch(segment) + { + case "TIME_YEAR": + _portion = DateTimePortion.Year; + break; + case "TIME_MON": + _portion = DateTimePortion.Month; + break; + case "TIME_DAY": + _portion = DateTimePortion.Day; + break; + case "TIME_HOUR": + _portion = DateTimePortion.Day; + break; + case "TIME_MIN": + _portion = DateTimePortion.Day; + break; + case "TIME_SEC": + _portion = DateTimePortion.Day; + break; + case "TIME_WDAY": + _portion = DateTimePortion.Day; + break; + case "TIME": + _portion = DateTimePortion.Day; + break; + default: + throw new FormatException("Unsupported segment: " + segment); + } + } + + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + switch (_portion) { + case DateTimePortion.Year: + return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Month: + return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Day: + return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Hour: + return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Minute: + return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Second: + return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture); + case DateTimePortion.DayOfWeek: + return ((int)DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture); + case DateTimePortion.Time: + return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture); + default: + return string.Empty; + } + } + + private enum DateTimePortion { + Year, + Month, + Day, + Hour, + Minute, + Second, + DayOfWeek, + Time + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/HeaderSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs similarity index 59% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/HeaderSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs index c83b2cf6ad..116dca22c4 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/HeaderSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs @@ -1,9 +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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class HeaderSegment : PatternSegment { @@ -14,9 +12,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Header = header; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - return context.Request.Headers[Header]; + return context.HttpContext.Request.Headers[Header]; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsModSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsModSegment.cs new file mode 100644 index 0000000000..c5a72eef55 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsModSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class IsHttpsModSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.IsHttps ? "on" : "off"; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsSegment.cs new file mode 100644 index 0000000000..be940dd284 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class IsHttpsSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.IsHttps ? "ON" : "OFF"; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsIPV6Segment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsIPV6Segment.cs new file mode 100644 index 0000000000..8131ae59a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsIPV6Segment.cs @@ -0,0 +1,20 @@ +// 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.Net.Sockets; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + + public class IsIPV6Segment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + if (context.HttpContext.Connection.RemoteIpAddress == null) + { + return "off"; + } + return context.HttpContext.Connection.RemoteIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off"; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LiteralSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LiteralSegment.cs similarity index 66% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LiteralSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LiteralSegment.cs index 390459c6c8..676a9e3d29 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LiteralSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LiteralSegment.cs @@ -1,9 +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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class LiteralSegment : PatternSegment { @@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Literal = literal; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { return Literal; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalAddressSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalAddressSegment.cs new file mode 100644 index 0000000000..5fc5ef8c95 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalAddressSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class LocalAddressSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Connection.LocalIpAddress?.ToString(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalPortSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalPortSegment.cs new file mode 100644 index 0000000000..d7f62e2905 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalPortSegment.cs @@ -0,0 +1,15 @@ +// 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.Globalization; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + public class LocalPortSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Connection.LocalPort.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/QueryStringSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/QueryStringSegment.cs new file mode 100644 index 0000000000..86341bc5a8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/QueryStringSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class QueryStringSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.QueryString.ToString(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemoteAddressSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemoteAddressSegment.cs new file mode 100644 index 0000000000..c18ec14ab7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemoteAddressSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class RemoteAddressSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Connection.RemoteIpAddress?.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemotePortSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemotePortSegment.cs new file mode 100644 index 0000000000..9c0e0257bb --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemotePortSegment.cs @@ -0,0 +1,15 @@ +// 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.Globalization; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + public class RemotePortSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RequestFilenameSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestFilenameSegment.cs similarity index 50% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RequestFilenameSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestFilenameSegment.cs index 8d467684f6..fe74df5c2b 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RequestFilenameSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestFilenameSegment.cs @@ -1,15 +1,13 @@ // 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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RequestFileNameSegment : PatternSegment { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - return context.Request.Path; + return context.HttpContext.Request.Path; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestMethodSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestMethodSegment.cs new file mode 100644 index 0000000000..b10103ebf5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestMethodSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class RequestMethodSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.Method; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RuleMatchSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RuleMatchSegment.cs similarity index 67% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RuleMatchSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RuleMatchSegment.cs index b8384aaa49..e54c08000b 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RuleMatchSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RuleMatchSegment.cs @@ -1,9 +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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RuleMatchSegment : PatternSegment { @@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Index = index; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { return ruleMatch?.BackReference[Index]?.Value; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/SchemeSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/SchemeSegment.cs new file mode 100644 index 0000000000..cd2bff5c06 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/SchemeSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class SchemeSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.Scheme; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ServerProtocolSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ServerProtocolSegment.cs new file mode 100644 index 0000000000..d2b1d8e6ec --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ServerProtocolSegment.cs @@ -0,0 +1,15 @@ +// 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.Features; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + public class ServerProtocolSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Features.Get()?.Protocol; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ToLowerSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ToLowerSegment.cs similarity index 51% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ToLowerSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ToLowerSegment.cs index b2c9d85679..c61e056331 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/ToLowerSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ToLowerSegment.cs @@ -1,9 +1,9 @@ // 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 System.Text; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class ToLowerSegment : PatternSegment { @@ -14,9 +14,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Pattern = pattern; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { + // PERF as we share the string builder across the context, we need to make a new one here to evaluate + // lowercase segments. + var tempBuilder = context.Builder; + context.Builder = new StringBuilder(64); var pattern = Pattern.Evaluate(context, ruleMatch, condMatch); + context.Builder = tempBuilder; return pattern.ToLowerInvariant(); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlEncodeSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs similarity index 63% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlEncodeSegment.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs index 83edb5019c..f64bb33cc6 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlEncodeSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs @@ -1,10 +1,10 @@ // 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.Text; using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class UrlEncodeSegment : PatternSegment { @@ -15,9 +15,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments Pattern = pattern; } - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { + var tempBuilder = context.Builder; + context.Builder = new StringBuilder(64); var pattern = Pattern.Evaluate(context, ruleMatch, condMatch); + context.Builder = tempBuilder; return UrlEncoder.Default.Encode(pattern); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlSegment.cs new file mode 100644 index 0000000000..da8adce3f7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlSegment.cs @@ -0,0 +1,13 @@ +// 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.PatternSegments +{ + public class UrlSegment : PatternSegment + { + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + return context.HttpContext.Request.Path; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PreAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PreAction.cs new file mode 100644 index 0000000000..08b1014c1c --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PreAction.cs @@ -0,0 +1,11 @@ + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; + +namespace Microsoft.AspNetCore.Rewrite.Internal +{ + public abstract class PreAction + { + public abstract void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs new file mode 100644 index 0000000000..13d2dd3ba9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions +{ + public class ChangeCookiePreAction : PreAction + { + public ChangeCookiePreAction(string cookie) + { + // TODO + throw new NotImplementedException(cookie); + } + + public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + { + // modify the cookies + + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeEnvironmentPreAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeEnvironmentPreAction.cs new file mode 100644 index 0000000000..79e84bde7f --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeEnvironmentPreAction.cs @@ -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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions +{ + public class ChangeEnvironmentPreAction : PreAction + { + public ChangeEnvironmentPreAction(string env) + { + // TODO + throw new NotImplementedException(); + } + + public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + { + // Do stuff to modify the env + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs similarity index 57% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs index 489f4a6b53..f1676c7d79 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs @@ -2,12 +2,13 @@ // 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.UrlRewrite; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class UrlAction { public Pattern Url { get; set; } - public abstract RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); + public abstract RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ForbiddenAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ForbiddenAction.cs new file mode 100644 index 0000000000..43589509f3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ForbiddenAction.cs @@ -0,0 +1,16 @@ +// 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; + +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions +{ + public class ForbiddenAction : UrlAction + { + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + return RuleResult.ResponseComplete; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/GoneAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/GoneAction.cs new file mode 100644 index 0000000000..d56eb09744 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/GoneAction.cs @@ -0,0 +1,16 @@ +// 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; + +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions +{ + public class GoneAction : UrlAction + { + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone; + return RuleResult.ResponseComplete; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs similarity index 58% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectAction.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs index a990c346dc..245d1020f1 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs @@ -2,10 +2,9 @@ // 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; using Microsoft.Net.Http.Headers; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class RedirectAction : UrlAction { @@ -16,11 +15,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions Url = pattern; } - public override RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { var pattern = Url.Evaluate(context, ruleMatch, condMatch); - context.Response.StatusCode = StatusCode; + context.HttpContext.Response.StatusCode = StatusCode; // url can either contain the full url or the path and query // always add to location header. @@ -28,13 +27,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions var split = pattern.IndexOf('?'); if (split >= 0) { - var query = context.Request.QueryString.Add(new QueryString(pattern.Substring(split))); - // not using the response.redirect here because status codes may be 301, 302, 307, 308 - context.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; + var query = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split))); + // not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308 + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; } else { - context.Response.Headers[HeaderNames.Location] = pattern; + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern; } return RuleResult.ResponseComplete; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectClearQueryAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.cs similarity index 67% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectClearQueryAction.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.cs index a6439811c5..c48bb00202 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RedirectClearQueryAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.cs @@ -2,10 +2,9 @@ // 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; using Microsoft.Net.Http.Headers; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class RedirectClearQueryAction : UrlAction { @@ -16,13 +15,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions Url = pattern; } - public override RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { var pattern = Url.Evaluate(context, ruleMatch, condMatch); - context.Response.StatusCode = StatusCode; + context.HttpContext.Response.StatusCode = StatusCode; // we are clearing the query, so just put the pattern in the location header - context.Response.Headers[HeaderNames.Location] = pattern; + context.HttpContext.Response.Headers[HeaderNames.Location] = pattern; return RuleResult.ResponseComplete; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RewriteAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs similarity index 58% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RewriteAction.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs index 105b023dd8..1574c2310e 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/RewriteAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class RewriteAction : UrlAction { @@ -19,28 +19,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions ClearQuery = clearQuery; } - public override RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { var pattern = Url.Evaluate(context, ruleMatch, condMatch); if (ClearQuery) { - context.Request.QueryString = new QueryString(); + context.HttpContext.Request.QueryString = new QueryString(); } // TODO PERF, substrings, object creation, etc. if (pattern.IndexOf("://") >= 0) { string scheme = null; - var host = new HostString(); - var path = new PathString(); - var query = new QueryString(); - var fragment = new FragmentString(); + HostString host; + PathString path; + QueryString query; + FragmentString fragment; UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment); - context.Request.Scheme = scheme; - context.Request.Host = host; - context.Request.Path = path; - context.Request.QueryString = query.Add(context.Request.QueryString); + context.HttpContext.Request.Scheme = scheme; + context.HttpContext.Request.Host = host; + context.HttpContext.Request.Path = path; + context.HttpContext.Request.QueryString = query.Add(context.HttpContext.Request.QueryString); } else { @@ -50,23 +50,23 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions var path = pattern.Substring(0, split); if (path.StartsWith(ForwardSlash)) { - context.Request.Path = new PathString(path); + context.HttpContext.Request.Path = PathString.FromUriComponent(path); } else { - context.Request.Path = new PathString(ForwardSlash + path); + context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path); } - context.Request.QueryString = context.Request.QueryString.Add(new QueryString(pattern.Substring(split))); + context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split))); } else { if (pattern.StartsWith(ForwardSlash)) { - context.Request.Path = new PathString(pattern); + context.HttpContext.Request.Path = PathString.FromUriComponent(pattern); } else { - context.Request.Path = new PathString(ForwardSlash + pattern); + context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern); } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/VoidAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/VoidAction.cs similarity index 61% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/VoidAction.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/VoidAction.cs index 5098a06ee6..7bf14afb11 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlActions/VoidAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/VoidAction.cs @@ -2,14 +2,13 @@ // 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; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class VoidAction : UrlAction { // Explicitly say that nothing happens - public override RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) + public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { return RuleResult.Continue; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatch.cs similarity index 85% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatch.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatch.cs index 5cd97d9c94..428c24ea75 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatch.cs @@ -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.UrlRewrite +namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class UrlMatch { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/ExactMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/ExactMatch.cs similarity index 91% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/ExactMatch.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/ExactMatch.cs index a42fe3e6a1..39b6449524 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/ExactMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/ExactMatch.cs @@ -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.UrlRewrite.UrlMatches +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class ExactMatch : UrlMatch { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs new file mode 100644 index 0000000000..d2d6397408 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs @@ -0,0 +1,19 @@ +// 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.UrlMatches +{ + public class FileSizeMatch : UrlMatch + { + public FileSizeMatch(bool negate) + { + Negate = negate; + } + + public override MatchResults Evaluate(string input, RewriteContext context) + { + var fileInfo = context.FileProvider.GetFileInfo(input); + return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperand.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerMatch.cs similarity index 52% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperand.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerMatch.cs index 227d1c2bbe..185c8de53e 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperand.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerMatch.cs @@ -3,22 +3,20 @@ using System; using System.Globalization; -using System.Text.RegularExpressions; -using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { - public class IntegerOperand : Operand + public class IntegerMatch : UrlMatch { public int Value { get; } public IntegerOperationType Operation { get; } - public IntegerOperand(int value, IntegerOperationType operation) + public IntegerMatch(int value, IntegerOperationType operation) { Value = value; Operation = operation; } - public IntegerOperand(string value, IntegerOperationType operation) + public IntegerMatch(string value, IntegerOperationType operation) { int compValue; if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) @@ -29,27 +27,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands Operation = operation; } - public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider) + public override MatchResults Evaluate(string input, RewriteContext context) { int compValue; - if (!int.TryParse(testString, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) + if (!int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) { - return false; + return MatchResults.EmptyFailure; } + switch (Operation) { case IntegerOperationType.Equal: - return compValue == Value; + return compValue == Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.Greater: - return compValue > Value; + return compValue > Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.GreaterEqual: - return compValue >= Value; + return compValue >= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.Less: - return compValue < Value; + return compValue < Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.LessEqual: - return compValue <= Value; + return compValue <= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.NotEqual: - return compValue != Value; + return compValue != Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; default: return null; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperation.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerOperation.cs similarity index 82% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperation.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerOperation.cs index 873846e123..fce53e3844 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/IntegerOperation.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerOperation.cs @@ -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.ModRewrite.Operands +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public enum IntegerOperationType { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsDirectoryMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs similarity index 89% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsDirectoryMatch.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs index 9c1068ada0..97d1177714 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsDirectoryMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs @@ -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.UrlRewrite.UrlMatches +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IsDirectoryMatch : UrlMatch { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsFileMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs similarity index 88% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsFileMatch.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs index ebce79b655..6dc8d9ea0b 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/IsFileMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs @@ -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.UrlRewrite.UrlMatches +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IsFileMatch : UrlMatch { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/RegexMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs similarity index 91% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/RegexMatch.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs index a1859a9370..a932d869f6 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlMatches/RegexMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs @@ -4,7 +4,7 @@ using System; using System.Text.RegularExpressions; -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class RegexMatch : UrlMatch { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringMatch.cs new file mode 100644 index 0000000000..81332401fe --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringMatch.cs @@ -0,0 +1,36 @@ +// 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.UrlMatches +{ + public class StringMatch : UrlMatch + { + public string Value { get; set; } + public StringOperationType Operation { get; set; } + public bool IgnoreCase { get; set; } + public StringMatch(string value, StringOperationType operation) + { + Value = value; + Operation = operation; + } + + public override MatchResults Evaluate(string input, RewriteContext context) + { + switch (Operation) + { + case StringOperationType.Equal: + return string.Compare(input, Value, IgnoreCase) == 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + case StringOperationType.Greater: + return string.Compare(input, Value, IgnoreCase) > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + case StringOperationType.GreaterEqual: + return string.Compare(input, Value, IgnoreCase) >= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + case StringOperationType.Less: + return string.Compare(input, Value, IgnoreCase) < 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + case StringOperationType.LessEqual: + return string.Compare(input, Value, IgnoreCase) <= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; + default: + return null; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperation.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringOperation.cs similarity index 81% rename from src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperation.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringOperation.cs index a35a51bf9e..15ff45d5f0 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Operands/StringOperation.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringOperation.cs @@ -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.ModRewrite.Operands +namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public enum StringOperationType { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Conditions.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Conditions.cs deleted file mode 100644 index b9ffa5b26c..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/Conditions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite -{ - public class Conditions - { - public List ConditionList { get; set; } = new List(); - public LogicalGrouping MatchType { get; set; } // default is MatchAll - public bool TrackingAllCaptures { get; set; } - - public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch) - { - MatchResults prevCond = null; - var success = true; - foreach (var condition in ConditionList) - { - var res = condition.Evaluate(context, ruleMatch, prevCond); - success = (MatchType == LogicalGrouping.MatchAll ? (success && res.Success) : (success || res.Success)); - prevCond = res; - } - return new MatchResults { Success = success, BackReference = prevCond?.BackReference }; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs index 08b666634b..a4e33665a3 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs @@ -3,12 +3,11 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { - /// - /// public class InputParser { private const char Colon = ':'; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/IsHttpsSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/IsHttpsSegment.cs deleted file mode 100644 index 5ffece8e11..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/IsHttpsSegment.cs +++ /dev/null @@ -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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class IsHttpsSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Request.IsHttps ? "ON" : "OFF"; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LocalAddressSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LocalAddressSegment.cs deleted file mode 100644 index 10d77aeb02..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/LocalAddressSegment.cs +++ /dev/null @@ -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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class LocalAddressSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Connection.LocalIpAddress?.ToString(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/QueryStringSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/QueryStringSegment.cs deleted file mode 100644 index d5081a4e6a..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/QueryStringSegment.cs +++ /dev/null @@ -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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class QueryStringSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Request.QueryString.ToString(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemoteAddressSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemoteAddressSegment.cs deleted file mode 100644 index 499b5a3e91..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemoteAddressSegment.cs +++ /dev/null @@ -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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class RemoteAddressSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Connection.RemoteIpAddress?.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemotePortSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemotePortSegment.cs deleted file mode 100644 index 25456dc102..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/RemotePortSegment.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Globalization; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class RemotePortSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlSegment.cs deleted file mode 100644 index dca3bfb5de..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/PatternSegments/UrlSegment.cs +++ /dev/null @@ -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; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments -{ - public class UrlSegment : PatternSegment - { - public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) - { - return context.Request.Path; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs index 9fa1a84bcc..aa5845a872 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs @@ -2,7 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite @@ -15,9 +16,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { // TODO Add all server variables here. case "ALL_RAW": - throw new NotImplementedException(); + throw new NotImplementedException("All-Raw server variable not implemented"); case "APP_POOL_ID": - throw new NotImplementedException(); + throw new NotImplementedException("All-Pool-Id server variable not implemented"); case "CONTENT_LENGTH": return new HeaderSegment(HeaderNames.ContentLength); case "CONTENT_TYPE": @@ -47,7 +48,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite case "REMOTE_ADDR": return new RemoteAddressSegment(); case "REMOTE_HOST": - throw new NotImplementedException(); + throw new NotImplementedException("Remote-Host server variable not implemented"); case "REMOTE_PORT": return new RemotePortSegment(); case "REQUEST_FILENAME": diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs index 4f9e0bbc22..39703c5e2e 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; +using Microsoft.AspNetCore.Rewrite.Internal; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { @@ -111,8 +112,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax); } - - private static void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { if (conditions == null) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs index ccee529c0a..2c4b9814ab 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; +using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { @@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite private UrlMatch _initialMatch; private Conditions _conditions; private UrlAction _action; + private bool _matchAny; public UrlRewriteRule Build() { @@ -96,10 +98,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite // TODO make this take two overloads and handle regex vs non regex case. public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate) { + // If there are no conditions specified, if (_conditions == null) { AddUrlConditions(LogicalGrouping.MatchAll, trackingAllCaptures: false); } + switch (patternSyntax) { case PatternSyntax.ECMAScript: @@ -123,17 +127,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite regex = new Regex(pattern, RegexOptions.Compiled, RegexTimeout); } - _conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate) }); + _conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny}); break; } case MatchType.IsDirectory: { - _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate) }); + _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate), OrNext = _matchAny }); break; } case MatchType.IsFile: { - _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(negate) }); + _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny }); break; } default: @@ -149,7 +153,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { throw new FormatException("Match does not have an associated pattern attribute in condition"); } - _conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate) }); + _conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny }); break; default: throw new FormatException("Unrecognized pattern syntax"); @@ -160,8 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { var conditions = new Conditions(); conditions.ConditionList = new List(); - conditions.MatchType = logicalGrouping; - conditions.TrackingAllCaptures = trackingAllCaptures; + _matchAny = logicalGrouping == LogicalGrouping.MatchAny; _conditions = conditions; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs index 4295548204..35ed34065f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs @@ -1,7 +1,12 @@ // 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.UrlRewrite +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Rewrite.Internal; + +namespace Microsoft.AspNetCore.Rewrite.Internal { public class UrlRewriteRule : Rule { @@ -39,7 +44,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite } // at this point we know the rule passed, evaluate the replacement. - return Action.ApplyAction(context.HttpContext, initMatchRes, condMatchRes); + return Action.ApplyAction(context, initMatchRes, condMatchRes); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs index c8af9432cc..77dc019956 100644 --- a/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Rewrite var path = Path.Combine(hostingEnv.ContentRootPath, filePath); using (var stream = File.OpenRead(path)) { - options.Rules.AddRange(FileParser.Parse(new StreamReader(stream))); + options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream))); }; return options; } @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite { throw new ArgumentNullException(nameof(reader)); } - options.Rules.AddRange(FileParser.Parse(reader)); + options.Rules.AddRange(new FileParser().Parse(reader)); return options; } @@ -79,7 +79,8 @@ namespace Microsoft.AspNetCore.Rewrite throw new ArgumentNullException(nameof(rule)); } - var builder = new RuleBuilder(rule); + var builder = new RuleBuilder(); + builder.AddRule(rule); options.Rules.Add(builder.Build()); return options; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs index 3be99059c0..4ab56f4ce3 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs @@ -106,6 +106,38 @@ namespace Microsoft.AspNetCore.Rewrite return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserUnrecognizedParameter"), p0, p1); } + /// + /// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. + /// + internal static string Error_ModRewriteParseError + { + get { return GetString("Error_ModRewriteParseError"); } + } + + /// + /// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. + /// + internal static string FormatError_ModRewriteParseError(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteParseError"), p0, p1); + } + + /// + /// Could not parse the mod_rewrite file. Line number '{0}'. + /// + internal static string Error_ModRewriteGeneralParseError + { + get { return GetString("Error_ModRewriteGeneralParseError"); } + } + + /// + /// Could not parse the mod_rewrite file. Line number '{0}'. + /// + internal static string FormatError_ModRewriteGeneralParseError(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteGeneralParseError"), p0); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Rewrite/Resources.resx b/src/Microsoft.AspNetCore.Rewrite/Resources.resx index ae034489ee..cb7277289a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Resources.resx +++ b/src/Microsoft.AspNetCore.Rewrite/Resources.resx @@ -135,4 +135,10 @@ Unrecognized parameter type: '{0}', terminated at string index: '{1}' + + Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. + + + Could not parse the mod_rewrite file. Line number '{0}'. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs index 12b1da7a48..be7108787d 100644 --- a/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs @@ -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 System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; @@ -13,5 +14,7 @@ namespace Microsoft.AspNetCore.Rewrite { public HttpContext HttpContext { get; set; } public IFileProvider FileProvider { get; set; } + // PERF: share the same string builder per request + internal StringBuilder Builder { get; set; } = new StringBuilder(64); } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ConditionActionTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ConditionActionTest.cs index d49d9ef695..d0d7ac7f57 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ConditionActionTest.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ConditionActionTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { var results = ConditionPatternParser.ParseActionCondition(condition); - var expected = new ParsedModRewriteExpression { Operation = operation, Type = conditionType, Operand = variable, Invert = false }; + var expected = new ParsedModRewriteInput { OperationType = operation, ConditionType = conditionType, Operand = variable, Invert = false }; Assert.True(CompareConditions(results, expected)); } @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite var condition = @"(.*)"; var results = ConditionPatternParser.ParseActionCondition(condition); - var expected = new ParsedModRewriteExpression { Type = ConditionType.Regex, Operand = "(.*)", Invert = false }; + var expected = new ParsedModRewriteInput { ConditionType = ConditionType.Regex, Operand = "(.*)", Invert = false }; Assert.True(CompareConditions(results, expected)); } @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { var results = ConditionPatternParser.ParseActionCondition(condition); - var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation , Invert = false }; + var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation , Invert = false }; Assert.True(CompareConditions(results, expected)); } @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { var results = ConditionPatternParser.ParseActionCondition(condition); - var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = true }; + var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = true }; Assert.True(CompareConditions(results, expected)); } @@ -79,15 +79,15 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { var results = ConditionPatternParser.ParseActionCondition(condition); - var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = false, Operand = variable }; + var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = false, Operand = variable }; Assert.True(CompareConditions(results, expected)); } // TODO negative tests - private bool CompareConditions(ParsedModRewriteExpression i1, ParsedModRewriteExpression i2) + private bool CompareConditions(ParsedModRewriteInput i1, ParsedModRewriteInput i2) { - if (i1.Operation != i2.Operation || - i1.Type != i2.Type || + if (i1.OperationType != i2.OperationType || + i1.ConditionType != i2.ConditionType || i1.Operand != i2.Operand || i1.Invert != i2.Invert) { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FlagParserTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FlagParserTest.cs index 572f376f2f..39c6cedb24 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FlagParserTest.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FlagParserTest.cs @@ -13,10 +13,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public void FlagParser_CheckSingleTerm() { - var results = FlagParser.ParseRuleFlags("[NC]"); - var dict = new Dictionary(); - dict.Add(RuleFlagType.NoCase, string.Empty); - var expected = new RuleFlags(dict); + var results = FlagParser.Parse("[NC]"); + var dict = new Dictionary(); + dict.Add(FlagType.NoCase, string.Empty); + var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); } @@ -24,12 +24,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public void FlagParser_CheckManyTerms() { - var results = FlagParser.ParseRuleFlags("[NC,F,L]"); - var dict = new Dictionary(); - dict.Add(RuleFlagType.NoCase, string.Empty); - dict.Add(RuleFlagType.Forbidden, string.Empty); - dict.Add(RuleFlagType.Last, string.Empty); - var expected = new RuleFlags(dict); + var results = FlagParser.Parse("[NC,F,L]"); + var dict = new Dictionary(); + dict.Add(FlagType.NoCase, string.Empty); + dict.Add(FlagType.Forbidden, string.Empty); + dict.Add(FlagType.Last, string.Empty); + var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); } @@ -37,12 +37,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public void FlagParser_CheckManyTermsWithEquals() { - var results = FlagParser.ParseRuleFlags("[NC,F,R=301]"); - var dict = new Dictionary(); - dict.Add(RuleFlagType.NoCase, string.Empty); - dict.Add(RuleFlagType.Forbidden, string.Empty); - dict.Add(RuleFlagType.Redirect, "301"); - var expected = new RuleFlags(dict); + var results = FlagParser.Parse("[NC,F,R=301]"); + var dict = new Dictionary(); + dict.Add(FlagType.NoCase, string.Empty); + dict.Add(FlagType.Forbidden, string.Empty); + dict.Add(FlagType.Redirect, "301"); + var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FormatExceptionTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FormatExceptionTests.cs new file mode 100644 index 0000000000..73d9f3c4c6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/FormatExceptionTests.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.AspNetCore.Rewrite.Internal.ModRewrite; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite +{ + public class FormatExceptionTests + { + [Theory] + [InlineData(@"RewriteCond 1 2\", @"Invalid escaper character in string RewriteCond 1 2\")] + [InlineData("BadExpression 1 2 3 4", "Could not parse the mod_rewrite file. Message: 'Too many tokens on line'. Line number '1'.")] + [InlineData("RewriteCond % 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %{ 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %{asdf} 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %z 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond $ 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond $z 2", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 !", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 >", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 >=", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 <", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 <=", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 =", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 -", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 -a", "Could not parse the mod_rewrite file. Line number '1'.")] + [InlineData("RewriteCond %1 -getemp", "Could not parse the mod_rewrite file. Line number '1'.")] + public void ThrowFormatExceptionWithCorrectMessage(string input, string expected) + { + // Arrange, Act, Assert + var ex = Assert.Throws(() => new FileParser().Parse(new StringReader(input))); + Assert.Equal(ex.Message, expected); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteConditionBuilderTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteConditionBuilderTest.cs deleted file mode 100644 index 05f610046a..0000000000 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteConditionBuilderTest.cs +++ /dev/null @@ -1,50 +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.Rewrite.Internal.ModRewrite; -using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands; -using Xunit; - -namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite -{ - // This file tests an input of a list of tokens and verifies that the appropriate condition is obtained - public class ModRewriteConditionBuilderTest - { - [Fact] - public void ConditionBuilder_PassInNoFlagsFlagsEmpty() - { - var conditionString = "RewriteCond /$1 /hello"; - var builder = new ConditionBuilder(conditionString); - var results = builder.Build(); - - //var expected = new Condition( - // new Pattern( - // new List() { - // new PatternSegment("/", SegmentType.Literal), - // new PatternSegment("1", SegmentType.RuleParameter) - // }), - // new ConditionExpression { Operand = new RegexOperand {Regex = new Regex("/hello") } }, - // new ConditionFlags()); - var expected = (new ConditionBuilder("/$1", "/hello")).Build(); - - Assert.True(results.Flags.FlagDictionary.Count == 0); - Assert.True(results.Flags.FlagDictionary.Count == expected.Flags.FlagDictionary.Count); - Assert.True((results.ConditionExpression.Operand is RegexOperand) - && (expected.ConditionExpression.Operand is RegexOperand)); - } - - [Fact] - public void ConditionBuilder_PassInFlagsFlagsExist() - { - var conditionString = "RewriteCond /$1 /hello [NC]"; - var builder = new ConditionBuilder(conditionString); - var results = builder.Build(); - var expected = (new ConditionBuilder("/$1", "/hello", "[NC]")).Build(); - - Assert.True(results.Flags.FlagDictionary.Count == 1); - Assert.True(results.Flags.FlagDictionary.Count == expected.Flags.FlagDictionary.Count); - Assert.True((results.ConditionExpression.Operand is RegexOperand) - && (expected.ConditionExpression.Operand is RegexOperand)); - } - } -} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteFlagTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteFlagTest.cs deleted file mode 100644 index ddc7329b37..0000000000 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteFlagTest.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite; -using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands; -using Microsoft.AspNetCore.Rewrite.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite -{ - public class ModRewriteFlagTest - { - // Flag tests - [Fact] - public void ModRewriteRule_Check403OnForbiddenFlag() - { - var context = new RewriteContext { HttpContext = CreateRequest("/", "/hey/hello") }; - var rule = new ModRewriteRule - { - InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")) , Invert = false }, - Transform = ConditionTestStringParser.ParseConditionTestString("/$1"), - Flags = FlagParser.ParseRuleFlags("[F]") - }; - var res = rule.ApplyRule(context); - Assert.True(res.Result == RuleTerminiation.ResponseComplete); - Assert.True(context.HttpContext.Response.StatusCode == 403); - } - - [Fact] - public void ModRewriteRule_Check410OnGoneFlag() - { - var context = new RewriteContext { HttpContext = CreateRequest("/", "/hey/hello") }; - var rule = new ModRewriteRule - { - InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false }, - Transform = ConditionTestStringParser.ParseConditionTestString("/$1"), - Flags = FlagParser.ParseRuleFlags("[G]") - }; - var res = rule.ApplyRule(context); - Assert.True(res.Result == RuleTerminiation.ResponseComplete); - Assert.True(context.HttpContext.Response.StatusCode == 410); - } - - [Fact] - public void ModRewriteRule_CheckLastFlag() - { - var context = new RewriteContext { HttpContext = CreateRequest("/", "/hey/hello") }; - var rule = new ModRewriteRule - { - InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false }, - Transform = ConditionTestStringParser.ParseConditionTestString("/$1"), - Flags = FlagParser.ParseRuleFlags("[L]") - }; - var res = rule.ApplyRule(context); - Assert.True(res.Result == RuleTerminiation.StopRules); - Assert.True(context.HttpContext.Request.Path.Equals(new PathString("/hello"))); - } - - - [Fact] - public void ModRewriteRule_CheckRedirectFlag() - { - // TODO fix this test. - var context = new RewriteContext { HttpContext = CreateRequest("/", "/hey/hello") }; - var rule = new ModRewriteRule - { - InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false }, - Transform = ConditionTestStringParser.ParseConditionTestString("/$1"), - Flags = FlagParser.ParseRuleFlags("[G]") - }; - var res = rule.ApplyRule(context); - Assert.True(res.Result == RuleTerminiation.ResponseComplete); - Assert.True(context.HttpContext.Response.StatusCode == 410); - } - - private HttpContext CreateRequest(string basePath, string requestPath, string requestQuery = "", string hostName = "") - { - HttpContext context = new DefaultHttpContext(); - context.Request.PathBase = new PathString(basePath); - context.Request.Path = new PathString(requestPath); - context.Request.QueryString = new QueryString(requestQuery); - context.Request.Host = new HostString(hostName); - return context; - } - } -} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs index 90a5d6dba1..62903412d5 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs @@ -66,12 +66,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite Assert.Equal(response, "/what"); } - [Theory] - [InlineData("", true)] - public void Invoke_StringComparisonTests(string input, bool expected) - { - - } [Fact] public async Task Invoke_ShouldIgnoreComments() @@ -89,8 +83,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite Assert.Equal(response, "/hey/hello"); } - - // TODO Add tests to check '//' being handled appropriately. [Fact] public async Task Invoke_ShouldRewriteHomepage() @@ -184,10 +176,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite } [Fact] - public async Task Invoke_CheckFullUrlWithUFlagOnlyPath() + public async Task Invoke_CheckFullUrlWithOnlyPath() { var options = new RewriteOptions() - .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]")); + .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { @@ -205,7 +197,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite public async Task Invoke_CheckFullUrlWithUFlag() { var options = new RewriteOptions() - .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]")); + .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { @@ -223,7 +215,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite public async Task Invoke_CheckModFileConditions() { var options = new RewriteOptions() - .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]")); + .ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { @@ -242,7 +234,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite public async Task Invoke_EnsureHttps(string input) { var options = new RewriteOptions() - .ImportFromModRewrite(new StringReader("RewriteCond %{REQUEST_URI} ^foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L,U]")); + .ImportFromModRewrite(new StringReader("RewriteCond %{REQUEST_URI} /foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]")); var builder = new WebHostBuilder() .Configure(app => { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs index 214bc87991..e9599a43d4 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; +using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite @@ -155,9 +155,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite }, Conditions = new Conditions { - ConditionList = conditions, - MatchType = condGrouping, - TrackingAllCaptures = condTracking + ConditionList = conditions } }; } @@ -184,8 +182,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite } else { - Assert.Equal(r1.Conditions.MatchType, r2.Conditions.MatchType); - Assert.Equal(r1.Conditions.TrackingAllCaptures, r2.Conditions.TrackingAllCaptures); Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count); for (var j = 0; j < r1.Conditions.ConditionList.Count; j++) { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs index 31f6a2e984..0fa95393c0 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs @@ -4,6 +4,7 @@ using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; using Xunit; @@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public void EvaluateBackReferenceRule(string testString, string expected) { var middle = InputParser.ParseInputString(testString); - var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch()); + var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); Assert.Equal(result, expected); } @@ -58,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public void EvaluatToLowerRule(string testString, string expected) { var middle = InputParser.ParseInputString(testString); - var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch()); + var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); Assert.Equal(result, expected); } @@ -67,7 +68,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public void EvaluatUriEncodeRule(string testString, string expected) { var middle = InputParser.ParseInputString(testString); - var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch()); + var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleMatch(), CreateTestCondMatch()); Assert.Equal(result, expected); } @@ -88,12 +89,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite Assert.Throws(() => InputParser.ParseInputString(testString)); } - private HttpContext CreateTestHttpContext() + private RewriteContext CreateTestRewriteContext() { - HttpContext context = new DefaultHttpContext(); + var context = new DefaultHttpContext(); // TODO add fields if necessary - return context; + return new RewriteContext { HttpContext = context, FileProvider = null }; } private MatchResults CreateTestRuleMatch() diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/ServerVariableTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/ServerVariableTests.cs index 6f11a3c21f..e0847cda75 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/ServerVariableTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/ServerVariableTests.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; using Microsoft.Net.Http.Headers; using Xunit; @@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite Assert.Equal(expected, lookup); } - private HttpContext CreateTestHttpContext() + private RewriteContext CreateTestHttpContext() { var context = new DefaultHttpContext(); context.Request.Host = new HostString("example.com"); @@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite context.Request.Headers[HeaderNames.Referer] = "referer"; context.Request.Headers[HeaderNames.UserAgent] = "useragent"; context.Request.Headers[HeaderNames.Connection] = "connection"; - return context; + return new RewriteContext { HttpContext = context }; } private MatchResults CreateTestRuleMatch()