diff --git a/samples/RewriteSample/Rewrite.txt b/samples/RewriteSample/Rewrite.txt index 574f251600..23d4948c18 100644 --- a/samples/RewriteSample/Rewrite.txt +++ b/samples/RewriteSample/Rewrite.txt @@ -1,10 +1,3 @@ -# Ensure Https -RewriteCond %{HTTPS} off -RewriteRule ^(.*)$ https://www.example.com$1 [L] - -# Rewrite path with additional sub directory -RewriteRule ^(.*)$ /foo$1 - -# Forbid a certain url from being accessed -RewriteRule /bar - [F] -RewriteRule /bar/ - [F] +# Rewrite path with additional sub directory +RewriteCond %{QUERY_STRING} id=20 +RewriteRule ^(.*)$ - [G] diff --git a/samples/RewriteSample/Startup.cs b/samples/RewriteSample/Startup.cs index a232a4bf45..ea80638c30 100644 --- a/samples/RewriteSample/Startup.cs +++ b/samples/RewriteSample/Startup.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; -using Microsoft.AspNetCore.Rewrite.Internal; namespace RewriteSample { @@ -26,16 +25,11 @@ namespace RewriteSample // (ex StringReplace) that are easy to implement in code, they can do so by calling // AddFunctionalRule(Func); // TODO make this startup do something useful. + app.UseRewriter(new RewriteOptions() + .Rewrite(@"foo/(\d+)", "foo?id={R:1}") .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; - })); + .ImportFromModRewrite(hostingEnv, "Rewrite.txt")); app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}")); } diff --git a/samples/RewriteSample/UrlRewrite.xml b/samples/RewriteSample/UrlRewrite.xml index 9ef1097666..9d9e089676 100644 --- a/samples/RewriteSample/UrlRewrite.xml +++ b/samples/RewriteSample/UrlRewrite.xml @@ -1,8 +1,8 @@  - - - + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs deleted file mode 100644 index 983ae1e36a..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs +++ /dev/null @@ -1,104 +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; -using Microsoft.AspNetCore.Rewrite.Internal; -using Microsoft.AspNetCore.Rewrite.Internal.CodeRules; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; - -namespace Microsoft.AspNetCore.Rewrite -{ - /// - /// The builder to a list of rules for and - /// - public static class CodeRewriteExtensions - { - /// - /// Adds a rule to the current rules. - /// - /// The UrlRewrite options. - /// A rule to be added to the current rules. - public static RewriteOptions AddRule(this RewriteOptions options, Rule rule) - { - options.Rules.Add(rule); - return options; - } - - /// - /// Adds a list of rules to the current rules. - /// - /// The UrlRewrite options. - /// A list of rules. - public static RewriteOptions AddRules(this RewriteOptions options, List rules) - { - options.Rules.AddRange(rules); - return options; - } - - public static RewriteOptions RewriteRule(this RewriteOptions options, string regex, string onMatch) - { - return RewriteRule(options, regex, onMatch, stopProcessing: false); - } - - public static RewriteOptions RewriteRule(this RewriteOptions options, string regex, string onMatch, bool stopProcessing) - { - var builder = new UrlRewriteRuleBuilder(); - var pattern = new InputParser().ParseInputString(onMatch); - - builder.AddUrlMatch(regex); - builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing); - options.Rules.Add(builder.Build()); - return options; - } - - public static RewriteOptions RedirectRule(this RewriteOptions options, string regex, string onMatch, int statusCode) - { - return RedirectRule(options, regex, onMatch, statusCode, stopProcessing: false); - } - - public static RewriteOptions RedirectRule(this RewriteOptions options, string regex, string onMatch, int statusCode, bool stopProcessing) - { - var builder = new UrlRewriteRuleBuilder(); - var pattern = new InputParser().ParseInputString(onMatch); - - builder.AddUrlMatch(regex); - builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: stopProcessing); - options.Rules.Add(builder.Build()); - return options; - } - - public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode) - { - return RedirectToHttps(options, statusCode, null); - } - - public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort) - { - options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort }); - return options; - } - - public static RewriteOptions RewriteToHttps(this RewriteOptions options) - { - return RewriteToHttps(options, sslPort: null, stopProcessing: false); - } - - public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort) - { - return RewriteToHttps(options, sslPort, stopProcessing: false); - } - - public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort, bool stopProcessing) - { - options.Rules.Add(new RewriteToHttpsRule {SSLPort = sslPort, StopProcessing = stopProcessing }); - return options; - } - - public static RewriteOptions AddRule(this RewriteOptions options, Func rule) - { - options.Rules.Add(new FunctionalRule { OnApplyRule = rule}); - return options; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/DelegateRule.cs similarity index 53% rename from src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/DelegateRule.cs index eccf9e735f..72216f0abc 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/DelegateRule.cs @@ -5,9 +5,14 @@ using System; namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules { - public class FunctionalRule : Rule + public class DelegateRule : Rule { - public Func OnApplyRule { get; set; } - public override RuleResult ApplyRule(RewriteContext context) => OnApplyRule(context); + private readonly Func _onApplyRule; + + public DelegateRule(Func onApplyRule) + { + _onApplyRule = onApplyRule; + } + public override RuleResult ApplyRule(RewriteContext context) => _onApplyRule(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs deleted file mode 100644 index bcb15fda25..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs +++ /dev/null @@ -1,36 +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.CodeRules -{ - public class RewriteToHttpsRule : Rule - { - public bool StopProcessing { get; set; } - public int? SSLPort { get; set; } - - public override RuleResult ApplyRule(RewriteContext context) - { - if (!context.HttpContext.Request.IsHttps) - { - var host = context.HttpContext.Request.Host; - if (SSLPort.HasValue && SSLPort.Value > 0) - { - // a specific SSL port is specified - host = new HostString(host.Host, SSLPort.Value); - } - else - { - // clear the port - host = new HostString(host.Host); - } - - context.HttpContext.Request.Scheme = "https"; - context.HttpContext.Request.Host = host; - return StopProcessing ? RuleResult.StopRules: RuleResult.Continue; - } - return RuleResult.Continue; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ConditionEvaluator.cs similarity index 78% rename from src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/ConditionEvaluator.cs index d8154c6b1d..4407ec3ce8 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/Conditions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ConditionEvaluator.cs @@ -5,15 +5,14 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal { - public class Conditions + public static class ConditionHelper { - public List ConditionList { get; set; } = new List(); - public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch) + public static MatchResults Evaluate(IEnumerable conditions, RewriteContext context, MatchResults ruleMatch) { MatchResults prevCond = null; var orSucceeded = false; - foreach (var condition in ConditionList) + foreach (var condition in conditions) { if (orSucceeded && condition.OrNext) { @@ -30,7 +29,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal if (condition.OrNext) { orSucceeded = prevCond.Success; - continue; } else if (!prevCond.Success) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs index dc9c8328be..fadd570f97 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ConditionPatternParser.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// A new parsed condition. public ParsedModRewriteInput ParseActionCondition(string condition) { - if (condition == null) + if (condition == null) { condition = string.Empty; } @@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite } // 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; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs index 2f16c8af08..baec9e2c4f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/FileParser.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public class FileParser { - public List Parse(TextReader input) + public IList Parse(TextReader input) { string line; var rules = new List(); @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite builder.AddAction(pattern, flags); rules.Add(builder.Build()); builder = new RuleBuilder(); - } + } catch (FormatException formatException) { throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs index 0e47a527ef..75bc8aa0cc 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRedirectAction.cs @@ -15,10 +15,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite public bool EscapeBackReferences { get; } public ModRewriteRedirectAction( - int statusCode, - Pattern pattern, - bool queryStringAppend, - bool queryStringDelete, + int statusCode, + Pattern pattern, + bool queryStringAppend, + bool queryStringDelete, bool escapeBackReferences) { StatusCode = statusCode; @@ -42,19 +42,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite // always add to location header. // TODO check for false positives var split = pattern.IndexOf('?'); - if (split >= 0) + if (split >= 0 && QueryStringAppend) { - QueryString query; - if (QueryStringAppend) - { - query = context.HttpContext.Request.QueryString.Add( - QueryString.FromUriComponent( - pattern.Substring(split))); - } - else - { - query = QueryString.FromUriComponent(pattern.Substring(split)); - } + var query = context.HttpContext.Request.QueryString.Add( + QueryString.FromUriComponent( + pattern.Substring(split))); // not using the response.redirect here because status codes may be 301, 302, 307, 308 context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs index 419bd0f440..01cb99dd24 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ModRewriteRewriteAction.cs @@ -16,8 +16,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite public bool EscapeBackReferences { get; } public ModRewriteRewriteAction( - RuleResult result, - Pattern pattern, + RuleResult result, + Pattern pattern, bool queryStringAppend, bool queryStringDelete, bool escapeBackReferences) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs index db0490745c..8190431803 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleBuilder.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public class RuleBuilder { - private Conditions _conditions; + private IList _conditions; private UrlAction _action; private UrlMatch _match; private List _preActions; @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { if (_conditions == null) { - _conditions = new Conditions(); + _conditions = new List(); } var condition = new Condition(); @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite } break; } - _conditions.ConditionList.Add(condition); + _conditions.Add(condition); } public void AddMatch( diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs index 67368cc6df..bdc6273d02 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleRegexParser.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite } else { - return new ParsedModRewriteInput { Invert = false, Operand = regex}; + return new ParsedModRewriteInput { Invert = false, Operand = regex }; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs index 863fd44bba..ebddffba19 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/ServerVariables.cs @@ -106,7 +106,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite case "HTTP2": throw new NotImplementedException("Http2 server variable is not supported"); case "IS_SUBREQ": - // TODO maybe can do this? context.Request.HttpContext ? throw new NotImplementedException("Is-Subrequest server variable is not supported"); case "REQUEST_FILENAME": return new RequestFileNameSegment(); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs index 002c69fa40..a87d9b1ed6 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/TestStringParser.cs @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite var ruleVariable = context.Capture(); context.Back(); var parsedIndex = int.Parse(ruleVariable); - + results.Add(new RuleMatchSegment(parsedIndex)); } else @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// The ParserContext /// The List of results which the new condition parameter will be added. /// true - private static void ParseConditionParameter(ParserContext context, List results) + private static void ParseConditionParameter(ParserContext context, IList results) { // Parse { } if (context.Current == OpenBrace) @@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// /// /// - private static void ParseLiteral(ParserContext context, List results) + private static void ParseLiteral(ParserContext context, IList results) { context.Mark(); string literal; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs index 6b51553dbb..1ecabd093d 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/Tokenizer.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite /// /// The rule to tokenize. /// A list of tokens. - public List Tokenize(string rule) + public IList Tokenize(string rule) { // TODO make list of strings a reference to the original rule? (run into problems with escaped spaces). // TODO handle "s and probably replace \ character with no slash. diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs index 6d7e090b9a..eb54eed72a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewriteRule.cs @@ -2,18 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal { public class ModRewriteRule : Rule { - public UrlMatch InitialMatch { get; set; } - public Conditions Conditions { get; set; } - public UrlAction Action { get; set; } - public List PreActions { get; set; } + public UrlMatch InitialMatch { get; } + public IList Conditions { get; } + public UrlAction Action { get; } + public IList PreActions { get; } - public ModRewriteRule(UrlMatch initialMatch, Conditions conditions, UrlAction urlAction, List preActions) + public ModRewriteRule(UrlMatch initialMatch, IList conditions, UrlAction urlAction, IList preActions) { Conditions = conditions; InitialMatch = initialMatch; @@ -28,21 +28,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal if (!initMatchRes.Success) { + context.Logger?.ModRewriteDidNotMatchRule(); return RuleResult.Continue; } MatchResults condMatchRes = null; if (Conditions != null) { - condMatchRes = Conditions.Evaluate(context, initMatchRes); + condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchRes); if (!condMatchRes.Success) { + context.Logger?.ModRewriteDidNotMatchRule(); return RuleResult.Continue; } } // 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 + context.Logger?.ModRewriteMatchedRule(); foreach (var preAction in PreActions) { preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs index 6ea96b915f..0cc7b7ddce 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs @@ -1,14 +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 Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; - namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class PatternSegment { - // Match from prevRule, Match from prevCond public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs index 4238ebb0fb..2d5202eeb8 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments public DateTimeSegment(string segment) { - switch(segment) + switch (segment) { case "TIME_YEAR": _portion = DateTimePortion.Year; @@ -24,28 +24,29 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments _portion = DateTimePortion.Day; break; case "TIME_HOUR": - _portion = DateTimePortion.Day; + _portion = DateTimePortion.Hour; break; case "TIME_MIN": - _portion = DateTimePortion.Day; + _portion = DateTimePortion.Minute; break; case "TIME_SEC": - _portion = DateTimePortion.Day; + _portion = DateTimePortion.Second; break; case "TIME_WDAY": - _portion = DateTimePortion.Day; + _portion = DateTimePortion.DayOfWeek; break; case "TIME": - _portion = DateTimePortion.Day; + _portion = DateTimePortion.Time; break; default: - throw new FormatException("Unsupported segment: " + segment); + throw new FormatException($"Unsupported segment: '{segment}'"); } } public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - switch (_portion) { + switch (_portion) + { case DateTimePortion.Year: return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Month: @@ -67,7 +68,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments } } - private enum DateTimePortion { + private enum DateTimePortion + { Year, Month, Day, diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs index cd506c75d2..5327c969fb 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments public class HeaderSegment : PatternSegment { private readonly string _header; - + public HeaderSegment(string header) { _header = header; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs index 5bca22d5ff..e5ad2aac30 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments public class UrlEncodeSegment : PatternSegment { private readonly Pattern _pattern; - + public UrlEncodeSegment(Pattern pattern) { _pattern = pattern; @@ -17,10 +17,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - var tempBuilder = context.Builder; + var oldBuilder = context.Builder; + // PERF + // Because we need to be able to evaluate multiple nested patterns, + // we provided a new string builder and evaluate the new pattern, + // and restore it after evaluation. context.Builder = new StringBuilder(64); var pattern = _pattern.Evaluate(context, ruleMatch, condMatch); - context.Builder = tempBuilder; + context.Builder = oldBuilder; return UrlEncoder.Default.Encode(pattern); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs index 13d2dd3ba9..a08506158f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PreActions/ChangeCookiePreAction.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) { // modify the cookies - + } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs index 8f2a8496c8..326d22b377 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class UrlAction { - public Pattern Url { get; set; } + protected Pattern Url { get; set; } public abstract RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs index 98df716b27..f0cac6afb7 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.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; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; @@ -9,25 +10,33 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions public class RedirectAction : UrlAction { public int StatusCode { get; } - public RedirectAction(int statusCode, Pattern pattern) + public bool AppendQueryString { get; } + + public RedirectAction(int statusCode, Pattern pattern, bool appendQueryString) { StatusCode = statusCode; Url = pattern; + AppendQueryString = appendQueryString; } public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - var pattern = Url.Evaluate(context, ruleMatch, condMatch); context.HttpContext.Response.StatusCode = StatusCode; + // TODO IIS guarantees that there will be a leading slash + if (pattern.IndexOf("://", StringComparison.Ordinal) == -1 && !pattern.StartsWith("/")) + { + pattern = '/' + pattern; + } + // 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) + if (split >= 0 && AppendQueryString) { - var query = context.HttpContext.Request.QueryString.Add( + var query = context.HttpContext.Request.QueryString.Add( QueryString.FromUriComponent( pattern.Substring(split))); // not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308 diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.cs deleted file mode 100644 index ec51cf748c..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectClearQueryAction.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 Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions -{ - public class RedirectClearQueryAction : UrlAction - { - public int StatusCode { get; } - public RedirectClearQueryAction(int statusCode, Pattern pattern) - { - StatusCode = statusCode; - Url = pattern; - } - - public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) - { - var pattern = Url.Evaluate(context, ruleMatch, condMatch); - context.HttpContext.Response.StatusCode = StatusCode; - - // we are clearing the query, so just put the pattern in the location header - context.HttpContext.Response.Headers[HeaderNames.Location] = pattern; - return RuleResult.ResponseComplete; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs index eb8d59dda2..7c9cd06103 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs @@ -10,10 +10,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions public class RewriteAction : UrlAction { private readonly string ForwardSlash = "/"; - public RuleTerminiation Result { get; } + public RuleTermination Result { get; } public bool ClearQuery { get; } - public RewriteAction(RuleTerminiation result, Pattern pattern, bool clearQuery) + public RewriteAction(RuleTermination result, Pattern pattern, bool clearQuery) { Result = result; Url = pattern; @@ -28,8 +28,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { context.HttpContext.Request.QueryString = QueryString.Empty; } - // TODO PERF, substrings, object creation, etc. - if (pattern.IndexOf("://") >= 0) + + if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0) { string scheme; HostString host; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs index d2d6397408..e798b0191c 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches public override MatchResults Evaluate(string input, RewriteContext context) { - var fileInfo = context.FileProvider.GetFileInfo(input); + var fileInfo = context.StaticFileProvider.GetFileInfo(input); return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs index 113ff88e2e..b4361c5d31 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs @@ -6,13 +6,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches public class IsDirectoryMatch : UrlMatch { public IsDirectoryMatch(bool negate) - { + { Negate = negate; } public override MatchResults Evaluate(string pattern, RewriteContext context) { - var res = context.FileProvider.GetFileInfo(pattern).IsDirectory; + var res = context.StaticFileProvider.GetFileInfo(pattern).IsDirectory; return new MatchResults { Success = (res != Negate) }; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs index 6dc8d9ea0b..e4d4816133 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs @@ -4,7 +4,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IsFileMatch : UrlMatch - { + { public IsFileMatch(bool negate) { Negate = negate; @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches public override MatchResults Evaluate(string pattern, RewriteContext context) { - var res = context.FileProvider.GetFileInfo(pattern).Exists; + var res = context.StaticFileProvider.GetFileInfo(pattern).Exists; return new MatchResults { Success = (res != Negate) }; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs index b869285b20..eb880c18ec 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches public override MatchResults Evaluate(string pattern, RewriteContext context) { var res = _match.Match(pattern); - return new MatchResults { BackReference = res.Groups, Success = (res.Success != Negate)}; + return new MatchResults { BackReference = res.Groups, Success = (res.Success != Negate) }; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs index 5fe5038eef..ada89f938d 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/InputParser.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite @@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite return new Pattern(results); } - private static void ParseParameter(ParserContext context, List results) + private static void ParseParameter(ParserContext context, IList results) { context.Mark(); // Four main cases: @@ -82,7 +81,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite else if (context.Current == Colon) { parameter = context.Capture(); - + // Only 5 strings to expect here. Case sensitive. switch (parameter) { @@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite return index; } - private static void ParseLiteral(ParserContext context, List results) + private static void ParseLiteral(ParserContext context, IList results) { context.Mark(); string literal; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs index aa5845a872..8a0fad4150 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ServerVariables.cs @@ -2,7 +2,6 @@ // 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; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; @@ -12,7 +11,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { public static PatternSegment FindServerVariable(string serverVariable) { - switch(serverVariable) + switch (serverVariable) { // TODO Add all server variables here. case "ALL_RAW": diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/FileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs similarity index 97% rename from src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/FileParser.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs index 6e6cd6419d..9dcd7f4a4a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/FileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs @@ -10,11 +10,11 @@ using System.Xml.Linq; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { - public class FileParser + public class UrlRewriteFileParser { private readonly InputParser _inputParser = new InputParser(); - public List Parse(TextReader reader) + public IList Parse(TextReader reader) { var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite return null; } - private void ParseRules(XElement rules, List result) + private void ParseRules(XElement rules, IList result) { if (rules == null) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs index 2dad4efcd0..90da1e57d9 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs @@ -13,12 +13,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite public class UrlRewriteRuleBuilder { private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); - + public string Name { get; set; } public bool Enabled { get; set; } private UrlMatch _initialMatch; - private Conditions _conditions; + private IList _conditions; private UrlAction _action; private bool _matchAny; @@ -28,40 +28,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match"); } - var rule = new UrlRewriteRule(); - rule.Action = _action; - rule.Conditions = _conditions; - rule.InitialMatch = _initialMatch; - rule.Name = Name; - return rule; + + return new UrlRewriteRule(Name, _initialMatch, _conditions, _action); } - - public void AddUrlAction(Pattern url, ActionType actionType = ActionType.None, bool appendQueryString = true, bool stopProcessing = false, int statusCode = StatusCodes.Status301MovedPermanently) + + public void AddUrlAction( + Pattern url, + ActionType actionType = ActionType.None, + bool appendQueryString = true, + bool stopProcessing = false, + int statusCode = StatusCodes.Status301MovedPermanently) { switch (actionType) { case ActionType.None: - _action = new VoidAction(stopProcessing ? RuleResult.StopRules : RuleResult.Continue); + _action = new VoidAction(stopProcessing ? RuleResult.StopRules : RuleResult.Continue); break; case ActionType.Rewrite: - if (appendQueryString) - { - _action = new RewriteAction(stopProcessing ? RuleTerminiation.StopRules : RuleTerminiation.Continue, url, clearQuery: false); - } - else - { - _action = new RewriteAction(stopProcessing ? RuleTerminiation.StopRules : RuleTerminiation.Continue, url, clearQuery: true); - } + _action = new RewriteAction(stopProcessing ? RuleTermination.StopRules : RuleTermination.Continue, + url, clearQuery: !appendQueryString); break; case ActionType.Redirect: - if (appendQueryString) - { - _action = new RedirectAction(statusCode, url); - } - else - { - _action = new RedirectClearQueryAction(statusCode, url); - } + _action = new RedirectAction(statusCode, url, appendQueryString); break; case ActionType.AbortRequest: throw new NotImplementedException("Abort Requests are not supported"); @@ -85,14 +73,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite else { var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout); - _initialMatch = new RegexMatch(regex, negate); + _initialMatch = new RegexMatch(regex, negate); } break; } case PatternSyntax.WildCard: throw new NotImplementedException("Wildcard syntax is not supported"); case PatternSyntax.ExactMatch: - _initialMatch = new ExactMatch(ignoreCase, input, negate); + _initialMatch = new ExactMatch(ignoreCase, input, negate); break; } } @@ -118,27 +106,23 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite throw new FormatException("Match does not have an associated pattern attribute in condition"); } - Regex regex = null; - if (ignoreCase) - { - regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); - } - else - { - regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout); - } - - _conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny}); + var regex = new Regex( + pattern, + ignoreCase ? RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase : + RegexOptions.CultureInvariant | RegexOptions.Compiled, + RegexTimeout); + + _conditions.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny }); break; } case MatchType.IsDirectory: { - _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate), OrNext = _matchAny }); + _conditions.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), OrNext = _matchAny }); + _conditions.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny }); break; } default: @@ -153,7 +137,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), OrNext = _matchAny }); + _conditions.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny }); break; default: throw new FormatException("Unrecognized pattern syntax"); @@ -162,10 +146,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite public void AddUrlConditions(LogicalGrouping logicalGrouping, bool trackingAllCaptures) { - var conditions = new Conditions(); - conditions.ConditionList = new List(); + _conditions = new List(); _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 736290b4fa..94a88763ff 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs @@ -1,28 +1,34 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal { public class UrlRewriteRule : Rule { - public string Name { get; set; } - public bool Enabled { get; set; } = true; - public UrlMatch InitialMatch { get; set; } - public Conditions Conditions { get; set; } - public UrlAction Action { get; set; } + public string Name { get; } + public UrlMatch InitialMatch { get; } + public IList Conditions { get; } + public UrlAction Action { get; } + + public UrlRewriteRule(string name, + UrlMatch initialMatch, + IList conditions, + UrlAction action) + { + Name = name; + InitialMatch = initialMatch; + Conditions = conditions; + Action = action; + } public override RuleResult ApplyRule(RewriteContext context) { - if (!Enabled) - { - return RuleResult.Continue; - } - // Due to the path string always having a leading slash, // remove it from the path before regex comparison - // TODO may need to check if there is a leading slash and remove conditionally var path = context.HttpContext.Request.Path; MatchResults initMatchResults; if (path == PathString.Empty) @@ -36,19 +42,22 @@ namespace Microsoft.AspNetCore.Rewrite.Internal if (!initMatchResults.Success) { + context.Logger?.UrlRewriteDidNotMatchRule(Name); return RuleResult.Continue; } MatchResults condMatchRes = null; if (Conditions != null) { - condMatchRes = Conditions.Evaluate(context, initMatchResults); + condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchResults); if (!condMatchRes.Success) { + context.Logger?.UrlRewriteDidNotMatchRule(Name); return RuleResult.Continue; } } + context.Logger?.UrlRewriteMatchedRule(Name); // at this point we know the rule passed, evaluate the replacement. return Action.ApplyAction(context, initMatchResults, condMatchRes); } diff --git a/src/Microsoft.AspNetCore.Rewrite/Logging/RewriteMiddlewareLoggingExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/Logging/RewriteMiddlewareLoggingExtensions.cs new file mode 100644 index 0000000000..0d32fe6cfa --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Logging/RewriteMiddlewareLoggingExtensions.cs @@ -0,0 +1,88 @@ +// 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.Extensions.Logging; + +namespace Microsoft.AspNetCore.Rewrite.Logging +{ + internal static class RewriteMiddlewareLoggingExtensions + { + private static readonly Action _requestContinueResults; + private static readonly Action _requestResponseComplete; + private static readonly Action _requestStopRules; + private static readonly Action _urlRewriteDidNotMatchRule; + private static readonly Action _urlRewriteMatchedRule; + private static readonly Action _modRewriteDidNotMatchRule; + private static readonly Action _modRewriteMatchedRule; + + static RewriteMiddlewareLoggingExtensions() + { + _requestContinueResults = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Request is continuing in applying rules."); + _requestResponseComplete = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Request is done processing, Location header '{Location}' with status code '{StatusCode}'."); + _requestStopRules = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Request is done applying rules."); + _urlRewriteDidNotMatchRule = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Request did not match current rule '{Name}'."); + + _urlRewriteMatchedRule = LoggerMessage.Define( + LogLevel.Debug, + 5, + "Request matched current UrlRewriteRule '{Name}'."); + + _modRewriteDidNotMatchRule = LoggerMessage.Define( + LogLevel.Debug, + 6, + "Request matched current ModRewriteRule."); + + _modRewriteMatchedRule = LoggerMessage.Define( + LogLevel.Debug, + 7, + "Request matched current ModRewriteRule."); + } + + public static void RewriteMiddlewareRequestContinueResults(this ILogger logger) + { + _requestContinueResults(logger, null); + } + + public static void RewriteMiddlewareRequestResponseComplete(this ILogger logger, string location, int statusCode) + { + _requestResponseComplete(logger, location, statusCode, null); + } + + public static void RewriteMiddlewareRequestStopRules(this ILogger logger) + { + _requestStopRules(logger, null); + } + + public static void UrlRewriteDidNotMatchRule(this ILogger logger, string name) + { + _urlRewriteDidNotMatchRule(logger, name, null); + } + + public static void UrlRewriteMatchedRule(this ILogger logger, string name) + { + _urlRewriteMatchedRule(logger, name, null); + } + + public static void ModRewriteDidNotMatchRule(this ILogger logger) + { + _modRewriteDidNotMatchRule(logger, null); + } + public static void ModRewriteMatchedRule(this ILogger logger) + { + _modRewriteMatchedRule(logger, null); + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/ModRewriteOptionsExtensions.cs similarity index 55% rename from src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs rename to src/Microsoft.AspNetCore.Rewrite/ModRewriteOptionsExtensions.cs index 77dc019956..1ad897b90e 100644 --- a/src/Microsoft.AspNetCore.Rewrite/ModRewriteExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/ModRewriteOptionsExtensions.cs @@ -8,24 +8,24 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite; namespace Microsoft.AspNetCore.Rewrite { - public static class ModRewriteExtensions + public static class ModRewriteOptionsExtensions { /// /// Imports rules from a mod_rewrite file and adds the rules to current rules. /// - /// The UrlRewrite options. - /// + /// The Rewrite options. + /// The Hosting Environment /// The path to the file containing mod_rewrite rules. - public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, IHostingEnvironment hostingEnv, string filePath) + public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - if (hostingEnv == null) + if (hostingEnvironment == null) { - throw new ArgumentNullException(nameof(hostingEnv)); + throw new ArgumentNullException(nameof(hostingEnvironment)); } if (string.IsNullOrEmpty(filePath)) @@ -33,18 +33,17 @@ namespace Microsoft.AspNetCore.Rewrite throw new ArgumentException(nameof(filePath)); } - var path = Path.Combine(hostingEnv.ContentRootPath, filePath); + var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath); using (var stream = File.OpenRead(path)) { - options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream))); - }; - return options; + return options.ImportFromModRewrite(new StreamReader(stream)); + } } /// /// Imports rules from a mod_rewrite file and adds the rules to current rules. /// - /// The UrlRewrite options. + /// The Rewrite options. /// Text reader containing a stream of mod_rewrite rules. public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, TextReader reader) { @@ -52,37 +51,19 @@ namespace Microsoft.AspNetCore.Rewrite { throw new ArgumentNullException(nameof(options)); } - + if (reader == null) { throw new ArgumentNullException(nameof(reader)); } - options.Rules.AddRange(new FileParser().Parse(reader)); + var rules = new FileParser().Parse(reader); + + foreach (var rule in rules) + { + options.Rules.Add(rule); + } return options; } - /// - /// Adds a mod_rewrite rule to the current rules. - /// - /// The UrlRewrite options. - /// The literal string of a mod_rewrite rule: - /// "RewriteRule Pattern Substitution [Flags]" - public static RewriteOptions AddModRewriteRule(this RewriteOptions options, string rule) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (rule == null) - { - throw new ArgumentNullException(nameof(rule)); - } - - var builder = new RuleBuilder(); - builder.AddRule(rule); - options.Rules.Add(builder.Build()); - return options; - } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteBuilderExtensions.cs similarity index 90% rename from src/Microsoft.AspNetCore.Rewrite/RewriteExtensions.cs rename to src/Microsoft.AspNetCore.Rewrite/RewriteBuilderExtensions.cs index 50026ddc83..88d7676c30 100644 --- a/src/Microsoft.AspNetCore.Rewrite/RewriteExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteBuilderExtensions.cs @@ -9,13 +9,13 @@ namespace Microsoft.AspNetCore.Builder /// /// Extension methods for the /// - public static class RewriteExtensions + public static class RewriteBuilderExtensions { /// /// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match. /// /// - /// Options for urlrewrite. + /// Options for rewrite. /// public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, RewriteOptions options) { diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs index be7108787d..4b4fb8e8db 100644 --- a/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs @@ -4,16 +4,19 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Rewrite { /// - /// The UrlRewrite Context contains the HttpContext of the request and the file provider to check conditions. + /// The UrlRewrite Context contains the HttpContext of the request, the file provider, and the logger. + /// There is also a shared string builder across the application of rules. /// public class RewriteContext { public HttpContext HttpContext { get; set; } - public IFileProvider FileProvider { get; set; } + public IFileProvider StaticFileProvider { get; set; } + public ILogger Logger { get; set; } // PERF: share the same string builder per request internal StringBuilder Builder { get; set; } = new StringBuilder(64); } diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteMiddleware.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteMiddleware.cs index 61c332b7c6..afcbcb576f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/RewriteMiddleware.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteMiddleware.cs @@ -6,7 +6,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite { @@ -20,14 +23,20 @@ namespace Microsoft.AspNetCore.Rewrite private readonly RequestDelegate _next; private readonly RewriteOptions _options; private readonly IFileProvider _fileProvider; + private readonly ILogger _logger; /// /// Creates a new instance of /// /// The delegate representing the next middleware in the request pipeline. - /// The Hosting Environment. + /// The Hosting Environment. + /// The Logger Factory. /// The middleware options, containing the rules to apply. - public RewriteMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, RewriteOptions options) + public RewriteMiddleware( + RequestDelegate next, + IHostingEnvironment hostingEnvironment, + ILoggerFactory loggerFactory, + RewriteOptions options) { if (next == null) { @@ -41,7 +50,8 @@ namespace Microsoft.AspNetCore.Rewrite _next = next; _options = options; - _fileProvider = _options.FileProvider ?? hostingEnv.WebRootFileProvider; + _fileProvider = _options.StaticFileProvider ?? hostingEnvironment.WebRootFileProvider; + _logger = loggerFactory.CreateLogger(); } /// @@ -55,19 +65,27 @@ namespace Microsoft.AspNetCore.Rewrite { throw new ArgumentNullException(nameof(context)); } - var urlContext = new RewriteContext { HttpContext = context, FileProvider = _fileProvider }; + var urlContext = new RewriteContext { + HttpContext = context, + StaticFileProvider = _fileProvider, + Logger = _logger + }; + foreach (var rule in _options.Rules) { - // Apply the rule var result = rule.ApplyRule(urlContext); switch (result.Result) { - case RuleTerminiation.Continue: - // Explicitly show that we continue executing rules + case RuleTermination.Continue: + _logger.RewriteMiddlewareRequestContinueResults(); break; - case RuleTerminiation.ResponseComplete: + case RuleTermination.ResponseComplete: + _logger.RewriteMiddlewareRequestResponseComplete( + urlContext.HttpContext.Response.Headers[HeaderNames.Location], + urlContext.HttpContext.Response.StatusCode); return CompletedTask; - case RuleTerminiation.StopRules: + case RuleTermination.StopRules: + _logger.RewriteMiddlewareRequestStopRules(); return _next(context); default: throw new ArgumentOutOfRangeException($"Invalid rule termination {result}"); diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteOptions.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteOptions.cs index 71eb2fef99..270c7a9313 100644 --- a/src/Microsoft.AspNetCore.Rewrite/RewriteOptions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteOptions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Rewrite @@ -12,10 +11,8 @@ namespace Microsoft.AspNetCore.Rewrite /// public class RewriteOptions { - /// - /// The ordered list of rules to apply to the context. - /// - public List Rules { get; set; } = new List(); - public IFileProvider FileProvider { get; set; } + // TODO doc comments + public IList Rules { get; } = new List(); + public IFileProvider StaticFileProvider { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/RewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/RewriteOptionsExtensions.cs new file mode 100644 index 0000000000..0861fa50f1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/RewriteOptionsExtensions.cs @@ -0,0 +1,135 @@ +// 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.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.CodeRules; +using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; + +namespace Microsoft.AspNetCore.Rewrite +{ + /// + /// The builder to a list of rules for and + /// + public static class RewriteOptionsExtensions + { + /// + /// Adds a rule to the current rules. + /// + /// The UrlRewrite options. + /// A rule to be added to the current rules. + /// The Rewrite options. + public static RewriteOptions Add(this RewriteOptions options, Rule rule) + { + options.Rules.Add(rule); + return options; + } + + /// + /// Adds a rule to the current rules. + /// + /// The Rewrite options. + /// A Func that checks and applies the rule. + /// + public static RewriteOptions Add(this RewriteOptions options, Func applyRule) + { + options.Rules.Add(new DelegateRule(applyRule)); + return options; + } + + /// + /// Rewrites the path if the regex matches the HttpContext's PathString + /// + /// The Rewrite options. + /// The regex string to compare with. + /// If the regex matches, what to replace HttpContext with. + /// The Rewrite options. + public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern) + { + return Rewrite(options, regex, urlPattern, stopProcessing: false); + } + + /// + /// Rewrites the path if the regex matches the HttpContext's PathString + /// + /// The Rewrite options. + /// The regex string to compare with. + /// If the regex matches, what to replace the uri with. + /// If the regex matches, conditionally stop processing other rules. + /// The Rewrite options. + public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern, bool stopProcessing) + { + var builder = new UrlRewriteRuleBuilder(); + var pattern = new InputParser().ParseInputString(urlPattern); + + builder.AddUrlMatch(regex); + builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing); + options.Rules.Add(builder.Build()); + return options; + } + + /// + /// Redirect the request if the regex matches the HttpContext's PathString + /// + /// The Rewrite options. + /// The regex string to compare with. + /// If the regex matches, what to replace the uri with. + /// The Rewrite options. + public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern) + { + return Redirect(options, regex, urlPattern, statusCode: 302); + } + + /// + /// Redirect the request if the regex matches the HttpContext's PathString + /// + /// The Rewrite options. + /// The regex string to compare with. + /// If the regex matches, what to replace the uri with. + /// The status code to add to the response. + /// The Rewrite options. + public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern, int statusCode) + { + var builder = new UrlRewriteRuleBuilder(); + var pattern = new InputParser().ParseInputString(urlPattern); + + builder.AddUrlMatch(regex); + builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: false); + options.Rules.Add(builder.Build()); + return options; + } + + // TODO 301 overload + + /// + /// Redirect a request to https if the incoming request is http + /// + /// The Rewrite options. + public static RewriteOptions RedirectToHttps(this RewriteOptions options) + { + return RedirectToHttps(options, statusCode: 302, sslPort: null); + } + + /// + /// Redirect a request to https if the incoming request is http + /// + /// The Rewrite options. + /// The status code to add to the response. + public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode) + { + return RedirectToHttps(options, statusCode, sslPort: null); + } + + /// + /// Redirect a request to https if the incoming request is http + /// + /// The Rewrite options. + /// The status code to add to the response. + /// The SSL port to add to the response. + public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort) + { + options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort }); + return options; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/Rule.cs b/src/Microsoft.AspNetCore.Rewrite/Rule.cs similarity index 78% rename from src/Microsoft.AspNetCore.Rewrite/Internal/Rule.cs rename to src/Microsoft.AspNetCore.Rewrite/Rule.cs index 3ba6067f42..82a70dabc5 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/Rule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Rule.cs @@ -1,8 +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. -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite { + // make this public and doc comements public abstract class Rule { public abstract RuleResult ApplyRule(RewriteContext context); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleResult.cs b/src/Microsoft.AspNetCore.Rewrite/RuleResult.cs similarity index 64% rename from src/Microsoft.AspNetCore.Rewrite/Internal/RuleResult.cs rename to src/Microsoft.AspNetCore.Rewrite/RuleResult.cs index f7b6c2b45b..2b2bb4abf3 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleResult.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RuleResult.cs @@ -1,14 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite { public class RuleResult { - public static RuleResult Continue = new RuleResult { Result = RuleTerminiation.Continue }; - public static RuleResult ResponseComplete = new RuleResult { Result = RuleTerminiation.ResponseComplete }; - public static RuleResult StopRules = new RuleResult { Result = RuleTerminiation.StopRules }; + public static RuleResult Continue = new RuleResult { Result = RuleTermination.Continue }; + public static RuleResult ResponseComplete = new RuleResult { Result = RuleTermination.ResponseComplete }; + public static RuleResult StopRules = new RuleResult { Result = RuleTermination.StopRules }; - public RuleTerminiation Result { get; set; } + public RuleTermination Result { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleTermination.cs b/src/Microsoft.AspNetCore.Rewrite/RuleTermination.cs similarity index 75% rename from src/Microsoft.AspNetCore.Rewrite/Internal/RuleTermination.cs rename to src/Microsoft.AspNetCore.Rewrite/RuleTermination.cs index 449a422944..dff816d9cc 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleTermination.cs +++ b/src/Microsoft.AspNetCore.Rewrite/RuleTermination.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. -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite { - public enum RuleTerminiation + public enum RuleTermination { Continue, ResponseComplete, diff --git a/src/Microsoft.AspNetCore.Rewrite/UrlRewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/UrlRewriteOptionsExtensions.cs similarity index 68% rename from src/Microsoft.AspNetCore.Rewrite/UrlRewriteExtensions.cs rename to src/Microsoft.AspNetCore.Rewrite/UrlRewriteOptionsExtensions.cs index fb362718a5..1ca090fdbe 100644 --- a/src/Microsoft.AspNetCore.Rewrite/UrlRewriteExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/UrlRewriteOptionsExtensions.cs @@ -8,24 +8,24 @@ using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; namespace Microsoft.AspNetCore.Rewrite { - public static class UrlRewriteExtensions + public static class UrlRewriteOptionsExtensions { /// /// Imports rules from a mod_rewrite file and adds the rules to current rules. /// /// The UrlRewrite options. - /// + /// /// The path to the file containing urlrewrite rules. - public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, IHostingEnvironment hostingEnv, string filePath) + public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - if (hostingEnv == null) + if (hostingEnvironment == null) { - throw new ArgumentNullException(nameof(hostingEnv)); + throw new ArgumentNullException(nameof(hostingEnvironment)); } if (string.IsNullOrEmpty(filePath)) @@ -33,35 +33,36 @@ namespace Microsoft.AspNetCore.Rewrite throw new ArgumentException(nameof(filePath)); } - var path = Path.Combine(hostingEnv.ContentRootPath, filePath); + var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath); using (var stream = File.OpenRead(path)) { - options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream))); - }; - return options; + return ImportFromUrlRewrite(options, new StreamReader(stream)); + } } /// /// Imports rules from a mod_rewrite file and adds the rules to current rules. /// /// The UrlRewrite options. - /// The text reader stream. - public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, TextReader stream) + /// The text reader stream. + public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, TextReader reader) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - if (stream == null) + if (reader == null) { - throw new ArgumentException(nameof(stream)); + throw new ArgumentException(nameof(reader)); } - using (stream) + var rules = new UrlRewriteFileParser().Parse(reader); + + foreach (var rule in rules) { - options.Rules.AddRange(new FileParser().Parse(stream)); - }; + options.Rules.Add(rule); + } return options; } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs index 1f08becb6c..ce73843067 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules [Fact] public async Task CheckRewritePath() { - var options = new RewriteOptions().RewriteRule("(.*)", "http://example.com/{R:1}"); + var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/{R:1}"); var builder = new WebHostBuilder() .Configure(app => { @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules [Fact] public async Task CheckRedirectPath() { - var options = new RewriteOptions().RedirectRule("(.*)","http://example.com/{R:1}", statusCode: 301); + var options = new RewriteOptions().Redirect("(.*)","http://example.com/{R:1}", statusCode: 301); var builder = new WebHostBuilder() .Configure(app => { @@ -52,25 +52,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules Assert.Equal(response.Headers.Location.OriginalString, "http://example.com/foo"); } - [Fact] - public async Task CheckRewriteToHttps() - { - var options = new RewriteOptions().RewriteToHttps(); - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseRewriter(options); - app.UseRewriter(options); - app.Run(context => context.Response.WriteAsync( - context.Request.Scheme)); - }); - var server = new TestServer(builder); - - var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com")); - - Assert.Equal(response, "https"); - } - [Fact] public async Task CheckRedirectToHttps() { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs index 62903412d5..7b358c265f 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/ModRewriteMiddlewareTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public async Task Invoke_RewritePathWhenMatching() { - var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 "); + var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 ")); var builder = new WebHostBuilder() .Configure(app => { @@ -34,8 +34,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule() { - var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 [L]") - .AddModRewriteRule("RewriteRule /hello /what"); + var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]")) + .ImportFromModRewrite(new StringReader("RewriteRule /hello /what")); var builder = new WebHostBuilder() .Configure(app => { @@ -52,8 +52,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite [Fact] public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule() { - var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1") - .AddModRewriteRule("RewriteRule /hello /what"); + var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1")) + .ImportFromModRewrite(new StringReader("RewriteRule /hello /what")); var builder = new WebHostBuilder() .Configure(app => { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/TestStringParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/TestStringParserTests.cs index a362137d10..b07dcdd7c6 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/TestStringParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/ModRewrite/TestStringParserTests.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; -namespace Microsoft.AspNetCore.Rewrite +namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class TestStringParserTests { @@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Rewrite [InlineData(@"%a", "Cannot parse '%a' to integer at string index: '1'")] // invalid character after % [InlineData(@"$a", "Cannot parse '$a' to integer at string index: '1'")] // invalid character after $ [InlineData(@"%{asdf", "Missing close brace for parameter at string index: '6'")] // no closing } with characters - public void ConditionParser_Bad(string testString, string expected) + public void ConditionParser_InvalidInput(string testString, string expected) { var ex = Assert.Throws(() => new TestStringParser().Parse(testString)); Assert.Equal(ex.Message, expected); diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ConditionMatchSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ConditionMatchSegmentTests.cs new file mode 100644 index 0000000000..51208cbba5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ConditionMatchSegmentTests.cs @@ -0,0 +1,37 @@ +// 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; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class ConditionMatchSegmentTests + { + + [Theory] + [InlineData(1, "foo")] + [InlineData(2, "bar")] + [InlineData(3, "baz")] + public void ConditionMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected) + { + // Arrange + var condMatch = CreateTestMatch(); + var segment = new ConditionMatchSegment(index); + + // Act + var results = segment.Evaluate(null, null, condMatch); + + // Assert + Assert.Equal(expected, results); + } + + private static MatchResults CreateTestMatch() + { + var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); + return new MatchResults {BackReference = match.Groups, Success = match.Success}; + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/DateTimeSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/DateTimeSegmentTests.cs new file mode 100644 index 0000000000..9e478da8e4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/DateTimeSegmentTests.cs @@ -0,0 +1,44 @@ +// 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.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class DateTimeSegmentTests + { + [Theory] + [InlineData("TIME_YEAR")] + [InlineData("TIME_MON")] + [InlineData("TIME_DAY")] + [InlineData("TIME_HOUR")] + [InlineData("TIME_MIN")] + [InlineData("TIME_SEC")] + [InlineData("TIME_WDAY")] + [InlineData("TIME")] + public void DateTime_AssertDoesntThrowOnCheckOfSegment(string input) + { + // Arrange + var segment = new DateTimeSegment(input); + + // Act + var results = segment.Evaluate(null, null, null); + + // TODO testing dates is hard, could use moq + // currently just assert that the segment doesn't throw. + } + + [Theory] + [InlineData("foo", "Unsupported segment: 'foo'")] + [InlineData("wow", "Unsupported segment: 'wow'")] + public void DateTime_AssertThrowsOnInvalidInput(string input, string expected) + { + + // Act And Assert + var ex = Assert.Throws(() => new DateTimeSegment(input)); + Assert.Equal(ex.Message, expected); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/HeaderSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/HeaderSegmentTests.cs new file mode 100644 index 0000000000..b8472929b7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/HeaderSegmentTests.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class HeaderSegmentTests + { + [Fact] + public void HeaderSegment_AssertGettingWithHeaderReturnsCorrectValue() + { + // Arrange + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + + context.HttpContext.Request.Headers[HeaderNames.Location] = "foo"; + var segment = new HeaderSegment(HeaderNames.Location); + + // Act + var results = segment.Evaluate(context, null, null); + + // Assert + Assert.Equal("foo", results); + } + + [Fact] + public void HeaderSegment_AssertGettingANonExistantHeaderReturnsNull() + { + // Arrange + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + var segment = new HeaderSegment(HeaderNames.Location); + + // Act + var results = segment.Evaluate(context, null, null); + + // Assert + Assert.Null(results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsModSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsModSegmentTests.cs new file mode 100644 index 0000000000..323f3198b4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsModSegmentTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class IsHttpsModSegmentTests + { + [Theory] + [InlineData("http", "off")] + [InlineData("https", "on")] + public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected) + { + // Arrange + var segement = new IsHttpsModSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Scheme = input; + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal(expected, results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsSegmentTests.cs new file mode 100644 index 0000000000..c3d3b6eccc --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsSegmentTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class IsHttpsSegmentTests + { + [Theory] + [InlineData("http", "OFF")] + [InlineData("https", "ON")] + public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected) + { + // Arrange + var segement = new IsHttpsSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Scheme = input; + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal(expected, results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsIPV6SegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsIPV6SegmentTests.cs new file mode 100644 index 0000000000..c787998e9c --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsIPV6SegmentTests.cs @@ -0,0 +1,59 @@ +// 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; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class IsIPV6SegmentTests + { + [Fact] + public void IsIPv6_AssertNullRemoteIpAddressReportsCorrectValue() + { + // Arrange + var segement = new IsIPV6Segment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemoteIpAddress = null; + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("off", results); + } + + [Fact] + public void IsIPv6_AssertCorrectBehaviorWhenIPv6IsUsed() + { + // Arrange + var segement = new IsIPV6Segment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("on", results); + } + + [Fact] + public void IsIPv6_AssertCorrectBehaviorWhenIPv4IsUsed() + { + // Arrange + var segement = new IsIPV6Segment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50"); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("off", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LIteralSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LIteralSegmentTests.cs new file mode 100644 index 0000000000..1bf3e74fee --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LIteralSegmentTests.cs @@ -0,0 +1,24 @@ +// 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.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class LiteralSegmentTests + { + [Fact] + public void LiteralSegment_AssertSegmentIsCorrect() + { + // Arrange + var segement = new LiteralSegment("foo"); + + // Act + var results = segement.Evaluate(null, null, null); + + // Assert + Assert.Equal("foo", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalAddressSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalAddressSegmentTests.cs new file mode 100644 index 0000000000..07d7254574 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalAddressSegmentTests.cs @@ -0,0 +1,41 @@ +// 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; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class LocalAddressSegmentTests + { + [Fact] + public void LocalAddress_AssertSegmentIsCorrect() + { + // Arrange + var segement = new LocalAddressSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.LocalIpAddress = IPAddress.Parse("20.30.40.50"); + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("20.30.40.50", results); + } + + [Fact] + public void LocalAddress_AssertNullLocalIpAddressReturnsNull() + { + var segement = new LocalAddressSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.LocalIpAddress = null; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Null( results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalPortSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalPortSegmentTests.cs new file mode 100644 index 0000000000..1a3dad1896 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalPortSegmentTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +using System.Globalization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class LocalPortSegmentTests + { + [Fact] + public void LocalPortSegment_AssertSegmentIsCorrect() + { + // Arrange + var segement = new LocalPortSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.LocalPort = 800; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("800", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/QueryStringSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/QueryStringSegmentTests.cs new file mode 100644 index 0000000000..a02986fc16 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/QueryStringSegmentTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class QueryStringSegmentTests + { + [Fact] + public void QueryString_AssertSegmentIsCorrect() + { + // Arrange + var segement = new QueryStringSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.QueryString = new QueryString("?hey=1"); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("?hey=1", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemoteAddressSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemoteAddressSegmentTests.cs new file mode 100644 index 0000000000..c7f5ede0dc --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemoteAddressSegmentTests.cs @@ -0,0 +1,40 @@ +// 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; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class RemoteAddressSegmentTests + { + [Fact] + public void RemoteAddress_AssertSegmentIsCorrect() + { + // Arrange + var segement = new RemoteAddressSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50"); + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("20.30.40.50", results); + } + + [Fact] + public void RemoteAddress_AssertNullLocalIpAddressReturnsNull() + { + var segement = new RemoteAddressSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemoteIpAddress = null; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Null(results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemotePortSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemotePortSegmentTests.cs new file mode 100644 index 0000000000..6980e2379f --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemotePortSegmentTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class RemotePortSegmentTests + { + [Fact] + public void RemotePort_AssertSegmentIsCorrect() + { + // Arrange + var segement = new RemotePortSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Connection.RemotePort = 800; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("800", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestFilenameSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestFilenameSegmentTests.cs new file mode 100644 index 0000000000..16e051d7af --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestFilenameSegmentTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class RequestFilenameSegmentTests + { + [Fact] + public void RequestFilename_AssertSegmentIsCorrect() + { + // Arrange + var segement = new RequestFileNameSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Path = new PathString("/foo/bar"); + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("/foo/bar", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestMethodSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestMethodSegmentTests.cs new file mode 100644 index 0000000000..fc9ac14b01 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestMethodSegmentTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class RequestMethodSegmentTests + { + [Fact] + public void RequestMethod_AssertSegmentIsCorrect() + { + // Arrange + var segement = new RequestMethodSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Method = "GET"; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("GET", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RuleMatchSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RuleMatchSegmentTests.cs new file mode 100644 index 0000000000..8e4d8ccfc4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RuleMatchSegmentTests.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. + +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class RuleMatchSegmentTests + { + [Theory] + [InlineData(1, "foo")] + [InlineData(2, "bar")] + [InlineData(3, "baz")] + public void RuleMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected) + { + // Arrange + var ruleMatch = CreateTestMatch(); + var segment = new RuleMatchSegment(index); + + // Act + var results = segment.Evaluate(null, ruleMatch, null); + + // Assert + Assert.Equal(expected, results); + } + + private static MatchResults CreateTestMatch() + { + var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); + return new MatchResults { BackReference = match.Groups, Success = match.Success }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/SchemeSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/SchemeSegmentTests.cs new file mode 100644 index 0000000000..3caac305c6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/SchemeSegmentTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class SchemeSegmentTests + { + [Fact] + public void SchemeSegment_AssertSegmentIsCorrect() + { + // Arrange + var segement = new SchemeSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Scheme = "http"; + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("http", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ServerProtocolSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ServerProtocolSegmentTests.cs new file mode 100644 index 0000000000..9128ef6b32 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ServerProtocolSegmentTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class ServerProtocolSegmentTests + { + [Fact] + public void ServerProtocol_AssertSegmentIsCorrect() + { + // Arrange + var segement = new ServerProtocolSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Features.Set(new HttpRequestFeature { Protocol = "http" }); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("http", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ToLowerSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ToLowerSegmentTests.cs new file mode 100644 index 0000000000..a6b434ff3d --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ToLowerSegmentTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class ToLowerSegmentTests + { + [Theory] + [InlineData("Hello", "hello")] + [InlineData("WHAT", "what")] + [InlineData("hey", "hey")] + public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected) + { + // Arrange + var pattern = new Pattern(new List()); + pattern.PatternSegments.Add(new LiteralSegment(input)); + var segement = new ToLowerSegment(pattern); + var context = new RewriteContext(); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal(expected, results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlEncodeSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlEncodeSegmentTests.cs new file mode 100644 index 0000000000..767876a9a5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlEncodeSegmentTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class UrlEncodeSegmentTests + { + [Theory] + [InlineData(" ", "%20")] + [InlineData("x&y", "x%26y")] + [InlineData("hey", "hey")] + public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected) + { + // Arrange + var pattern = new Pattern(new List()); + pattern.PatternSegments.Add(new LiteralSegment(input)); + var segement = new UrlEncodeSegment(pattern); + var context = new RewriteContext(); + + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal(expected, results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlSegmentTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlSegmentTests.cs new file mode 100644 index 0000000000..d23c2a5fb4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlSegmentTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments +{ + public class UrlSegmentTests + { + [Fact] + public void LocalPortSegment_AssertSegmentIsCorrect() + { + // Arrange + var segement = new UrlSegment(); + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + context.HttpContext.Request.Path = new PathString("/foo/bar"); + // Act + var results = segement.Evaluate(context, null, null); + + // Assert + Assert.Equal("/foo/bar", results); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs new file mode 100644 index 0000000000..27e2bcca40 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions +{ + public class ForbiddenActionTests + { + [Fact] + public void Forbidden_Verify403IsInStatusCode() + { + // Arrange + var context = new RewriteContext {HttpContext = new DefaultHttpContext()}; + var action = new ForbiddenAction(); + + // Act + var results = action.ApplyAction(context, null, null); + + // Assert + Assert.Equal(results.Result, RuleTermination.ResponseComplete); + Assert.Equal(context.HttpContext.Response.StatusCode, StatusCodes.Status403Forbidden); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/GoneActionTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/GoneActionTests.cs new file mode 100644 index 0000000000..1daccb17f2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/GoneActionTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions +{ + public class GoneActionTests + { + [Fact] + public void Gone_Verify410IsInStatusCode() + { + // Arrange + var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; + var action = new GoneAction(); + + // Act + var results = action.ApplyAction(context, null, null); + + // Assert + Assert.Equal(results.Result, RuleTermination.ResponseComplete); + Assert.Equal(context.HttpContext.Response.StatusCode, StatusCodes.Status410Gone); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs index 2c45fa8f02..43a8bac189 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs @@ -29,13 +29,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var expected = new List(); expected.Add(CreateTestRule(new List(), - Url: "^article/([0-9]+)/([_0-9a-z-]+)", + url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act - var res = new FileParser().Parse(new StringReader(xml)); + var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(res, expected); @@ -66,13 +66,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var expected = new List(); expected.Add(CreateTestRule(condList, - Url: "^article/([0-9]+)/([_0-9a-z-]+)", + url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act - var res = new FileParser().Parse(new StringReader(xml)); + var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(res, expected); @@ -110,18 +110,18 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var expected = new List(); expected.Add(CreateTestRule(condList, - Url: "^article/([0-9]+)/([_0-9a-z-]+)", + url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); expected.Add(CreateTestRule(condList, - Url: "^article/([0-9]+)/([_0-9a-z-]+)", + url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to another article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act - var res = new FileParser().Parse(new StringReader(xml)); + var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(res, expected); @@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite bool enabled = true, PatternSyntax patternSyntax = PatternSyntax.ECMAScript, bool stopProcessing = false, - string Url = "", + string url = "", bool ignoreCase = true, bool negate = false, ActionType actionType = ActionType.None, @@ -145,22 +145,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite RedirectType redirectType = RedirectType.Permanent ) { - return new UrlRewriteRule - { - Action = new RewriteAction(RuleTerminiation.Continue, new InputParser().ParseInputString(Url), clearQuery: false), - Name = name, - Enabled = enabled, - InitialMatch = new RegexMatch(new Regex("^OFF$"), false) - { - }, - Conditions = new Conditions - { - ConditionList = conditions - } - }; + return new UrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions, + new RewriteAction(RuleTermination.Continue, new InputParser().ParseInputString(url), clearQuery: false)); } - private void AssertUrlRewriteRuleEquality(List actual, List expected) + // TODO make rules comparable? + private void AssertUrlRewriteRuleEquality(IList actual, IList expected) { Assert.Equal(actual.Count, expected.Count); for (var i = 0; i < actual.Count; i++) @@ -169,23 +159,22 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var r2 = expected[i]; Assert.Equal(r1.Name, r2.Name); - Assert.Equal(r1.Enabled, r2.Enabled); if (r1.Conditions == null) { - Assert.Equal(r2.Conditions.ConditionList.Count, 0); + Assert.Equal(r2.Conditions.Count, 0); } else if (r2.Conditions == null) { - Assert.Equal(r1.Conditions.ConditionList.Count, 0); + Assert.Equal(r1.Conditions.Count, 0); } else { - Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count); - for (var j = 0; j < r1.Conditions.ConditionList.Count; j++) + Assert.Equal(r1.Conditions.Count, r2.Conditions.Count); + for (var j = 0; j < r1.Conditions.Count; j++) { - var c1 = r1.Conditions.ConditionList[j]; - var c2 = r2.Conditions.ConditionList[j]; + var c1 = r1.Conditions[j]; + var c2 = r2.Conditions[j]; Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count); } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs index b1e0a6f45b..db70d69e4a 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public void ThrowFormatExceptionWithCorrectMessage(string input, string expected) { // Arrange, Act, Assert - var ex = Assert.Throws(() => new FileParser().Parse(new StringReader(input))); + var ex = Assert.Throws(() => new UrlRewriteFileParser().Parse(new StringReader(input))); Assert.Equal(ex.Message, expected); } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs index 0a69cdf8ad..0f4b2ba7fb 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/InputParserTests.cs @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { var context = new DefaultHttpContext(); - return new RewriteContext { HttpContext = context, FileProvider = null }; + return new RewriteContext { HttpContext = context, StaticFileProvider = null }; } private MatchResults CreateTestRuleMatch() diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs index f2018d20b9..eadddbd60d 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var response = await server.CreateClient().GetAsync("article/10/hey"); - Assert.Equal(response.Headers.Location.OriginalString, "article.aspx?id=10&title=hey"); + Assert.Equal(response.Headers.Location.OriginalString, "/article.aspx?id=10&title=hey"); } [Fact] @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var response = await server.CreateClient().GetAsync("HElLo"); - Assert.Equal(response.Headers.Location.OriginalString, "hello"); + Assert.Equal(response.Headers.Location.OriginalString, "/hello"); } [Fact] @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var response = await server.CreateClient().GetAsync("hey/hello/"); - Assert.Equal(response.Headers.Location.OriginalString, "hey/hello"); + Assert.Equal(response.Headers.Location.OriginalString, "/hey/hello"); } [Fact] @@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var response = await server.CreateClient().GetAsync("hey/hello"); - Assert.Equal(response.Headers.Location.OriginalString, "hey/hello/"); + Assert.Equal(response.Headers.Location.OriginalString, "/hey/hello/"); } [Fact] diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/UrlRewriteApplicationTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/UrlRewriteApplicationTests.cs index 6c5abfedb5..6d6864d398 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/UrlRewriteApplicationTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/UrlRewriteApplicationTests.cs @@ -1,15 +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; using System.IO; using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; -using Microsoft.AspNetCore.TestHost; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite @@ -28,11 +23,11 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite "); - var rules = new FileParser().Parse(xml); + var rules = new UrlRewriteFileParser().Parse(xml); Assert.Equal(rules.Count, 1); var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext {HttpContext = new DefaultHttpContext()}); - Assert.Equal(ruleResults.Result, RuleTerminiation.StopRules); + Assert.Equal(ruleResults.Result, RuleTermination.StopRules); } [Fact] @@ -46,11 +41,11 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite "); - var rules = new FileParser().Parse(xml); + var rules = new UrlRewriteFileParser().Parse(xml); Assert.Equal(rules.Count, 1); var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext { HttpContext = new DefaultHttpContext() }); - Assert.Equal(ruleResults.Result, RuleTerminiation.Continue); + Assert.Equal(ruleResults.Result, RuleTermination.Continue); } } }