From 903949cc74cd95f10f2dc2cf80191bb0001d9bb8 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 8 Aug 2016 14:26:57 -0700 Subject: [PATCH] Update structure of code rules, refactor to IISRewrite --- BasicMiddleware.sln | 3 + samples/RewriteSample/Startup.cs | 23 +- .../CodeRewriteExtensions.cs | 95 +++--- .../{ => CodeRules}/FunctionalRule.cs | 3 +- .../RedirectToHttpsRule.cs} | 22 +- .../Internal/CodeRules/RewriteToHttpsRule.cs | 42 +++ .../{ => ModRewrite}/RuleExpression.cs | 2 +- .../Internal/PathRule.cs | 48 --- .../Internal/Transformation.cs | 12 - .../Internal/UrlRewrite/ParsedCondition.cs | 12 - .../Internal/UrlRewrite/ParsedUrlAction.cs | 14 - .../Internal/UrlRewrite/ParsedUrlMatch.cs | 11 - .../Internal/UrlRewrite/RewriteTags.cs | 2 +- .../Internal/UrlRewrite/UrlAction.cs | 1 - .../UrlRewrite/UrlRewriteFileParser.cs | 275 +++++------------- .../UrlRewrite/UrlRewriteRuleBuilder.cs | 168 +++++++++++ .../Internal/UrlRewriteRule.cs | 14 +- .../CodeRules/MiddlewareTests.cs | 90 ++++++ .../UrlRewrite/FileParserTests.cs | 40 +-- .../FormatExceptionHandlingTests.cs | 3 +- .../UrlRewrite/MiddleWareTests.cs | 96 +++--- 21 files changed, 538 insertions(+), 438 deletions(-) rename src/Microsoft.AspNetCore.Rewrite/Internal/{ => CodeRules}/FunctionalRule.cs (75%) rename src/Microsoft.AspNetCore.Rewrite/Internal/{SchemeRule.cs => CodeRules/RedirectToHttpsRule.cs} (65%) create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs rename src/Microsoft.AspNetCore.Rewrite/Internal/{ => ModRewrite}/RuleExpression.cs (86%) delete mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/PathRule.cs delete mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/Transformation.cs delete mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedCondition.cs delete mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlAction.cs delete mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlMatch.cs create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs create mode 100644 test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs diff --git a/BasicMiddleware.sln b/BasicMiddleware.sln index a431cdab18..dd15eee89b 100644 --- a/BasicMiddleware.sln +++ b/BasicMiddleware.sln @@ -5,6 +5,9 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.xproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5076D28-FA7E-4606-9410-FEDD0D603527}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8437B0F3-3894-4828-A945-A9187F37631D}" EndProject diff --git a/samples/RewriteSample/Startup.cs b/samples/RewriteSample/Startup.cs index 5fe6a3840c..b83d5677e7 100644 --- a/samples/RewriteSample/Startup.cs +++ b/samples/RewriteSample/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; +using Microsoft.AspNetCore.Rewrite.Internal; namespace RewriteSample { @@ -13,9 +14,29 @@ namespace RewriteSample { public void Configure(IApplicationBuilder app, IHostingEnvironment hostingEnv) { + // Four main use cases for Rewrite Options. + // 1. Importing from a UrlRewrite file, which are IIS Rewrite rules. + // This file is in xml format, starting with the tag. + // 2. Importing from a mod_rewrite file, which are mod_rewrite rules. + // This file is in standard mod_rewrite format which only contains rewrite information. + // 3. Inline rules in code, where you can specify rules such as rewrites and redirects + // based on certain conditions. Ex: RedirectToHttps will check if the request is https, + // else it will redirect the request with https. + // 4. Functional rules. If a user has a very specific function they would like to implement + // (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() .ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml") - .ImportFromModRewrite(hostingEnv, "Rewrite.txt")); + .ImportFromModRewrite(hostingEnv, "Rewrite.txt") + .RedirectToHttps(StatusCodes.Status307TemporaryRedirect) + .RewriteRule("/foo/(.*)/bar", "{R:1}/bar") + .AddRule(ctx => + { + ctx.HttpContext.Request.Path = "/index"; + return RuleResult.Continue; + })); + app.Run(context => context.Response.WriteAsync(context.Request.Path)); } diff --git a/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs index 51060ebdfc..9da5101c96 100644 --- a/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/CodeRewriteExtensions.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal; +using Microsoft.AspNetCore.Rewrite.Internal.CodeRules; +using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; namespace Microsoft.AspNetCore.Rewrite { @@ -35,69 +36,69 @@ namespace Microsoft.AspNetCore.Rewrite return options; } - /// - /// Creates a rewrite path rule. - /// - /// The Url rewrite options. - /// The string regex pattern to compare against the http context. - /// The string to replace the path with (with capture parameters). - /// Whether or not to stop rewriting on success of rule. - /// - public static RewriteOptions RewritePath(this RewriteOptions options, string regex, string newPath, bool stopRewriteOnSuccess = false) + public static RewriteOptions RewriteRule(this RewriteOptions options, string regex, string onMatch) { - options.Rules.Add(new PathRule { MatchPattern = new Regex(regex, RegexOptions.Compiled, TimeSpan.FromMilliseconds(1)), OnMatch = newPath, OnCompletion = stopRewriteOnSuccess ? Transformation.TerminatingRewrite : Transformation.Rewrite }); + 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 = InputParser.ParseInputString(onMatch); + + builder.AddUrlMatch(regex); + builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing); + options.Rules.Add(builder.Build()); return options; } - /// - /// Rewrite http to https. - /// - /// The Url rewrite options. - /// Whether or not to stop rewriting on success of rule. - /// - public static RewriteOptions RewriteScheme(this RewriteOptions options, bool stopRewriteOnSuccess = false) + public static RewriteOptions RedirectRule(this RewriteOptions options, string regex, string onMatch, int statusCode) { - options.Rules.Add(new SchemeRule {OnCompletion = stopRewriteOnSuccess ? Transformation.TerminatingRewrite : Transformation.Rewrite }); + 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 = InputParser.ParseInputString(onMatch); + + builder.AddUrlMatch(regex); + builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: stopProcessing); + options.Rules.Add(builder.Build()); return options; } - /// - /// Redirect a path to another path. - /// - /// The Url rewrite options. - /// The string regex pattern to compare against the http context. - /// The string to replace the path with (with capture parameters). - /// Whether or not to stop rewriting on success of rule. - /// - public static RewriteOptions RedirectPath(this RewriteOptions options, string regex, string newPath, bool stopRewriteOnSuccess = false) + public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode) { - options.Rules.Add(new PathRule { MatchPattern = new Regex(regex, RegexOptions.Compiled, TimeSpan.FromMilliseconds(1)), OnMatch = newPath, OnCompletion = Transformation.Redirect }); + return RedirectToHttps(options, statusCode, null); + } + + // TODO Don't do this, it doesn't work in all cases. Will refactor tonight/ tomorrow. + public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort) + { + options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort }); return options; } - /// - /// Redirect http to https. - /// - /// The Url rewrite options. - /// The port to redirect the scheme to. - /// - public static RewriteOptions RedirectScheme(this RewriteOptions options, int? sslPort) + public static RewriteOptions RewriteToHttps(this RewriteOptions options) + { + return RewriteToHttps(options, sslPort: null, stopProcessing: false); + } + + public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort) { - options.Rules.Add(new SchemeRule { SSLPort = sslPort, OnCompletion = Transformation.Redirect }); + 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; } - /// - /// User generated rule to do a specific match on a path and what to do on success of the match. - /// - /// - /// - /// - /// - /// - public static RewriteOptions CustomRule(this RewriteOptions options, Func onApplyRule, Transformation transform, string description = null) + public static RewriteOptions AddRule(this RewriteOptions options, Func rule) { - options.Rules.Add(new FunctionalRule { OnApplyRule = onApplyRule, OnCompletion = transform}); + options.Rules.Add(new FunctionalRule { OnApplyRule = rule}); return options; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/FunctionalRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs similarity index 75% rename from src/Microsoft.AspNetCore.Rewrite/Internal/FunctionalRule.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs index 5704a23a40..eccf9e735f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/FunctionalRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/FunctionalRule.cs @@ -3,12 +3,11 @@ using System; -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules { public class FunctionalRule : Rule { public Func OnApplyRule { get; set; } - public Transformation OnCompletion { get; set; } = Transformation.Rewrite; public override RuleResult ApplyRule(RewriteContext context) => OnApplyRule(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/SchemeRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs similarity index 65% rename from src/Microsoft.AspNetCore.Rewrite/Internal/SchemeRule.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs index 87a08544e8..3dd9d6df39 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/SchemeRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RedirectToHttpsRule.cs @@ -4,12 +4,12 @@ using System.Text; using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules { - public class SchemeRule : Rule + public class RedirectToHttpsRule : Rule { public int? SSLPort { get; set; } - public Transformation OnCompletion { get; set; } = Transformation.Rewrite; + public int StatusCode { get; set; } public override RuleResult ApplyRule(RewriteContext context) { @@ -28,20 +28,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal host = new HostString(host.Host); } - if ((OnCompletion != Transformation.Redirect)) - { - context.HttpContext.Request.Scheme = "https"; - context.HttpContext.Request.Host = host; - if (OnCompletion == Transformation.TerminatingRewrite) - { - return RuleResult.StopRules; - } - else - { - return RuleResult.Continue; - } - } - var req = context.HttpContext.Request; var newUrl = new StringBuilder().Append("https://").Append(host).Append(req.PathBase).Append(req.Path).Append(req.QueryString); @@ -51,4 +37,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal return RuleResult.Continue; } } -} +} \ 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 new file mode 100644 index 0000000000..cccb76e6d1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/CodeRules/RewriteToHttpsRule.cs @@ -0,0 +1,42 @@ + +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) + { + // TODO this only does http to https, add more features in the future. + 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; + if (stopProcessing) + { + return RuleResult.StopRules; + } + else + { + return RuleResult.Continue; + } + } + return RuleResult.Continue; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleExpression.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs similarity index 86% rename from src/Microsoft.AspNetCore.Rewrite/Internal/RuleExpression.cs rename to src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs index 52458cc542..1c29b18f0e 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/RuleExpression.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/ModRewrite/RuleExpression.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands; -namespace Microsoft.AspNetCore.Rewrite.Internal +namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite { public class RuleExpression { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PathRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PathRule.cs deleted file mode 100644 index 88b07e6cde..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PathRule.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text.RegularExpressions; - -namespace Microsoft.AspNetCore.Rewrite.Internal -{ - public class PathRule : Rule - { - public Regex MatchPattern { get; set; } - public string OnMatch { get; set; } - public Transformation OnCompletion { get; set; } = Transformation.Rewrite; - public override RuleResult ApplyRule(RewriteContext context) - { - var matches = MatchPattern.Match(context.HttpContext.Request.Path); - if (matches.Success) - { - // New method here to translate the outgoing format string to the correct value. - var path = matches.Result(OnMatch); - if (OnCompletion == Transformation.Redirect) - { - var req = context.HttpContext.Request; - var newUrl = string.Concat( - req.Scheme, - "://", - req.PathBase, - path, - req.QueryString); - context.HttpContext.Response.Redirect(newUrl); - return RuleResult.ResponseComplete; - } - else - { - context.HttpContext.Request.Path = path; - } - if (OnCompletion == Transformation.TerminatingRewrite) - { - return RuleResult.StopRules; - } - else - { - return RuleResult.Continue; - } - } - return RuleResult.Continue; - } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/Transformation.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/Transformation.cs deleted file mode 100644 index 01ca2dc9cf..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/Transformation.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Rewrite.Internal -{ - public enum Transformation - { - Rewrite, - Redirect, - TerminatingRewrite - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedCondition.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedCondition.cs deleted file mode 100644 index e34ccdb00f..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedCondition.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite -{ - public class ParsedCondition - { - public bool Negate { get; set; } - public bool IgnoreCase { get; set; } = true; - public MatchType MatchType { get; set; } = MatchType.Pattern; - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlAction.cs deleted file mode 100644 index 857c2f16cd..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlAction.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite -{ - public class ParsedUrlAction - { - public ActionType Type { get; set; } - public Pattern Url { get; set; } - public bool AppendQueryString { get; set; } = true; - public bool LogRewrittenUrl { get; set; } // Ignoring this flag. - public RedirectType RedirectType { get; set; } = RedirectType.Permanent; - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlMatch.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlMatch.cs deleted file mode 100644 index 9f183fadc3..0000000000 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/ParsedUrlMatch.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite -{ - public class ParsedUrlMatch - { - public bool IgnoreCase { get; set; } - public bool Negate { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/RewriteTags.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/RewriteTags.cs index 16074abfb3..2421fe2eb5 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/RewriteTags.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/RewriteTags.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite public const string Input = "input"; public const string Pattern = "pattern"; public const string Type = "type"; - public const string AppendQuery = "appendQueryString"; + public const string AppendQueryString = "appendQueryString"; public const string LogRewrittenUrl = "logRewrittenUrl"; public const string RedirectType = "redirectType"; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs index 404561b25b..489f4a6b53 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlAction.cs @@ -8,7 +8,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite public abstract class UrlAction { public Pattern Url { get; set; } - public abstract RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs index 350e697b24..4f9e0bbc22 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteFileParser.cs @@ -5,11 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions; -using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { @@ -27,14 +24,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite var result = new List(); // TODO Global rules are currently not treated differently than normal rules, fix. // See: https://github.com/aspnet/BasicMiddleware/issues/59 - ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result, isGlobalRule: true); - ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result, isGlobalRule: false); + ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result); + ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result); return result; } return null; } - private static void ParseRules(XElement rules, List result, bool isGlobalRule) + private static void ParseRules(XElement rules, List result) { if (rules == null) { @@ -43,297 +40,176 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite foreach (var rule in rules.Elements(RewriteTags.Rule)) { - var res = new UrlRewriteRule(); - SetRuleAttributes(rule, res); - var action = rule.Element(RewriteTags.Action); - if (action == null) + var builder = new UrlRewriteRuleBuilder(); + ParseRuleAttributes(rule, builder); + + if (builder.Enabled) { - ThrowUrlFormatException(rule, "Rule does not have an associated action attribute"); - } - CreateUrlAction(action, res, isGlobalRule); - if (res.Enabled) - { - result.Add(res); + result.Add(builder.Build()); } } } - private static void SetRuleAttributes(XElement rule, UrlRewriteRule res) + private static void ParseRuleAttributes(XElement rule, UrlRewriteRuleBuilder builder) { - - res.Name = rule.Attribute(RewriteTags.Name)?.Value; + builder.Name = rule.Attribute(RewriteTags.Name)?.Value; bool enabled; - if (bool.TryParse(rule.Attribute(RewriteTags.Enabled)?.Value, out enabled)) + if (!bool.TryParse(rule.Attribute(RewriteTags.Enabled)?.Value, out enabled)) { - res.Enabled = enabled; + builder.Enabled = true; } PatternSyntax patternSyntax; - if (Enum.TryParse(rule.Attribute(RewriteTags.PatternSyntax)?.Value, out patternSyntax)) + if (!Enum.TryParse(rule.Attribute(RewriteTags.PatternSyntax)?.Value, out patternSyntax)) { - res.PatternSyntax = patternSyntax; + patternSyntax = PatternSyntax.ECMAScript; } bool stopProcessing; - if (bool.TryParse(rule.Attribute(RewriteTags.StopProcessing)?.Value, out stopProcessing)) + if (!bool.TryParse(rule.Attribute(RewriteTags.StopProcessing)?.Value, out stopProcessing)) { - res.StopProcessing = stopProcessing; + stopProcessing = false; } + var match = rule.Element(RewriteTags.Match); if (match == null) { ThrowUrlFormatException(rule, "Cannot have rule without match"); } - CreateMatch(match, res); - CreateConditions(rule.Element(RewriteTags.Conditions), res); + + var action = rule.Element(RewriteTags.Action); + if (action == null) + { + ThrowUrlFormatException(rule, "Rule does not have an associated action attribute"); + } + + ParseMatch(match, builder, patternSyntax); + ParseConditions(rule.Element(RewriteTags.Conditions), builder, patternSyntax); + ParseUrlAction(action, builder, stopProcessing); } - private static void CreateMatch(XElement match, UrlRewriteRule res) + private static void ParseMatch(XElement match, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { - var matchRes = new ParsedUrlMatch(); - - bool parBool; - if (bool.TryParse(match.Attribute(RewriteTags.IgnoreCase)?.Value, out parBool)) - { - matchRes.IgnoreCase = parBool; - } - - if (bool.TryParse(match.Attribute(RewriteTags.Negate)?.Value, out parBool)) - { - matchRes.Negate = parBool; - } - var parsedInputString = match.Attribute(RewriteTags.Url)?.Value; if (parsedInputString == null) { ThrowUrlFormatException(match, "Match must have Url Attribute"); } - switch (res.PatternSyntax) + bool ignoreCase; + if (!bool.TryParse(match.Attribute(RewriteTags.IgnoreCase)?.Value, out ignoreCase)) { - case PatternSyntax.ECMAScript: - { - if (matchRes.IgnoreCase) - { - var regex = new Regex(parsedInputString, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); - res.InitialMatch = new RegexMatch(regex, matchRes.Negate); - } - else - { - var regex = new Regex(parsedInputString, RegexOptions.Compiled, RegexTimeout); - res.InitialMatch = new RegexMatch(regex, matchRes.Negate); - } - } - break; - case PatternSyntax.WildCard: - throw new NotImplementedException("Wildcard syntax is not supported."); - case PatternSyntax.ExactMatch: - res.InitialMatch = new ExactMatch(matchRes.IgnoreCase, parsedInputString, matchRes.Negate); - break; + ignoreCase = true; // default } + + bool negate; + if (!bool.TryParse(match.Attribute(RewriteTags.Negate)?.Value, out negate)) + { + negate = false; + } + builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax); } - private static void CreateConditions(XElement conditions, UrlRewriteRule res) + + + private static void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { - // This is to avoid nullptr exception on referencing conditions. - res.Conditions = new Conditions(); if (conditions == null) { return; } LogicalGrouping grouping; - if (Enum.TryParse(conditions.Attribute(RewriteTags.MatchType)?.Value, out grouping)) + if (!Enum.TryParse(conditions.Attribute(RewriteTags.MatchType)?.Value, out grouping)) { - res.Conditions.MatchType = grouping; + grouping = LogicalGrouping.MatchAll; } - bool parBool; - if (bool.TryParse(conditions.Attribute(RewriteTags.TrackingAllCaptures)?.Value, out parBool)) + bool trackingAllCaptures; + if (!bool.TryParse(conditions.Attribute(RewriteTags.TrackingAllCaptures)?.Value, out trackingAllCaptures)) { - res.Conditions.TrackingAllCaptures = parBool; + trackingAllCaptures = false; } + builder.AddUrlConditions(grouping, trackingAllCaptures); + foreach (var cond in conditions.Elements(RewriteTags.Add)) { - CreateCondition(cond, res); + ParseCondition(cond, builder, patternSyntax); } } - private static void CreateCondition(XElement condition, UrlRewriteRule res) + private static void ParseCondition(XElement condition, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { - var parsedCondRes = new ParsedCondition(); - - bool parBool; - if (bool.TryParse(condition.Attribute(RewriteTags.IgnoreCase)?.Value, out parBool)) + bool ignoreCase; + if (!bool.TryParse(condition.Attribute(RewriteTags.IgnoreCase)?.Value, out ignoreCase)) { - parsedCondRes.IgnoreCase = parBool; + ignoreCase = true; } - if (bool.TryParse(condition.Attribute(RewriteTags.Negate)?.Value, out parBool)) + bool negate; + if (!bool.TryParse(condition.Attribute(RewriteTags.Negate)?.Value, out negate)) { - parsedCondRes.Negate = parBool; + ignoreCase = false; } MatchType matchType; - if (Enum.TryParse(condition.Attribute(RewriteTags.MatchType)?.Value, out matchType)) + if (!Enum.TryParse(condition.Attribute(RewriteTags.MatchType)?.Value, out matchType)) { - parsedCondRes.MatchType = matchType; + matchType = MatchType.Pattern; } - var parsedString = condition.Attribute(RewriteTags.Input)?.Value; - if (parsedString == null) + var parsedInputString = condition.Attribute(RewriteTags.Input)?.Value; + if (parsedInputString == null) { ThrowUrlFormatException(condition, "Conditions must have an input attribute"); } + var parsedPatternString = condition.Attribute(RewriteTags.Pattern)?.Value; + Pattern input = null; try { - input = InputParser.ParseInputString(parsedString); + input = InputParser.ParseInputString(parsedInputString); + builder.AddUrlCondition(input, parsedPatternString, patternSyntax, matchType, ignoreCase, negate); + } catch (FormatException formatException) { ThrowUrlFormatException(condition, formatException.Message, formatException); } - - switch (res.PatternSyntax) - { - case PatternSyntax.ECMAScript: - { - switch (parsedCondRes.MatchType) - { - case MatchType.Pattern: - { - parsedString = condition.Attribute(RewriteTags.Pattern)?.Value; - if (parsedString == null) - { - ThrowUrlFormatException(condition, "Match does not have an associated pattern attribute in condition"); - - } - Regex regex = null; - - if (parsedCondRes.IgnoreCase) - { - regex = new Regex(parsedString, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); - } - else - { - regex = new Regex(parsedString, RegexOptions.Compiled, RegexTimeout); - } - - res.Conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, parsedCondRes.Negate) }); - } - break; - case MatchType.IsDirectory: - { - res.Conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(parsedCondRes.Negate) }); - } - break; - case MatchType.IsFile: - { - res.Conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(parsedCondRes.Negate) }); - } - break; - default: - // TODO don't this this can ever be thrown - ThrowUrlFormatException(condition, "Unrecognized matchType"); - break; - } - } - break; - case PatternSyntax.WildCard: - throw new NotImplementedException("Wildcard syntax is not supported"); - case PatternSyntax.ExactMatch: - parsedString = condition.Attribute(RewriteTags.Pattern)?.Value; - if (parsedString == null) - { - ThrowUrlFormatException(condition, "Pattern match does not have an associated pattern attribute in condition"); - } - res.Conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(parsedCondRes.IgnoreCase, parsedString, parsedCondRes.Negate) }); - break; - default: - ThrowUrlFormatException(condition, "Unrecognized pattern syntax"); - break; - } } - private static void CreateUrlAction(XElement urlAction, UrlRewriteRule res, bool globalRule) + private static void ParseUrlAction(XElement urlAction, UrlRewriteRuleBuilder builder, bool stopProcessing) { - - var actionRes = new ParsedUrlAction(); - ActionType actionType; - if (Enum.TryParse(urlAction.Attribute(RewriteTags.Type)?.Value, out actionType)) + if (!Enum.TryParse(urlAction.Attribute(RewriteTags.Type)?.Value, out actionType)) { - actionRes.Type = actionType; + actionType = ActionType.None; } - bool parseBool; - if (bool.TryParse(urlAction.Attribute(RewriteTags.AppendQuery)?.Value, out parseBool)) + bool appendQuery; + if (!bool.TryParse(urlAction.Attribute(RewriteTags.AppendQueryString)?.Value, out appendQuery)) { - actionRes.AppendQueryString = parseBool; - } - - if (bool.TryParse(urlAction.Attribute(RewriteTags.LogRewrittenUrl)?.Value, out parseBool)) - { - actionRes.LogRewrittenUrl = parseBool; + appendQuery = true; } RedirectType redirectType; - if (Enum.TryParse(urlAction.Attribute(RewriteTags.RedirectType)?.Value, out redirectType)) + if (!Enum.TryParse(urlAction.Attribute(RewriteTags.RedirectType)?.Value, out redirectType)) { - actionRes.RedirectType = redirectType; + redirectType = RedirectType.Permanent; } try { - actionRes.Url = InputParser.ParseInputString(urlAction.Attribute(RewriteTags.Url)?.Value); + var input = InputParser.ParseInputString(urlAction.Attribute(RewriteTags.Url)?.Value); + builder.AddUrlAction(input, actionType, appendQuery, stopProcessing, (int)redirectType); } catch (FormatException formatException) { ThrowUrlFormatException(urlAction, formatException.Message, formatException); } - - CreateUrlActionFromParsedAction(urlAction, actionRes, globalRule, res); - } - - private static void CreateUrlActionFromParsedAction(XElement urlAction, ParsedUrlAction actionRes, bool globalRule, UrlRewriteRule res) - { - switch (actionRes.Type) - { - case ActionType.None: - res.Action = new VoidAction(); - break; - case ActionType.Rewrite: - if (actionRes.AppendQueryString) - { - res.Action = new RewriteAction(res.StopProcessing ? RuleTerminiation.StopRules : RuleTerminiation.Continue, actionRes.Url, clearQuery: false); - } - else - { - res.Action = new RewriteAction(res.StopProcessing ? RuleTerminiation.StopRules : RuleTerminiation.Continue, actionRes.Url, clearQuery: true); - } - break; - case ActionType.Redirect: - if (actionRes.AppendQueryString) - { - res.Action = new RedirectAction((int)actionRes.RedirectType, actionRes.Url); - } - else - { - res.Action = new RedirectClearQueryAction((int)actionRes.RedirectType, actionRes.Url); - } - break; - case ActionType.AbortRequest: - ThrowUrlFormatException(urlAction, "Abort Requests are not supported."); - break; - case ActionType.CustomResponse: - // TODO - ThrowUrlFormatException(urlAction, "Custom Responses are not supported"); - break; - } } private static void ThrowUrlFormatException(XElement element, string message) @@ -342,6 +218,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite var col = ((IXmlLineInfo)element).LinePosition; throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col)); } + private static void ThrowUrlFormatException(XElement element, string message, Exception ex) { var line = ((IXmlLineInfo)element).LineNumber; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs new file mode 100644 index 0000000000..ccee529c0a --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewrite/UrlRewriteRuleBuilder.cs @@ -0,0 +1,168 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions; +using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches; + +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 UrlAction _action; + + public UrlRewriteRule Build() + { + // TODO some of these are required fields, throw if null? + var rule = new UrlRewriteRule(); + rule.Action = _action; + rule.Conditions = _conditions; + rule.InitialMatch = _initialMatch; + rule.Name = Name; + return rule; + } + + 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(); + 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); + } + break; + case ActionType.Redirect: + if (appendQueryString) + { + _action = new RedirectAction(statusCode, url); + } + else + { + _action = new RedirectClearQueryAction(statusCode, url); + } + break; + case ActionType.AbortRequest: + throw new FormatException("Abort Requests are not supported"); + case ActionType.CustomResponse: + // TODO + throw new FormatException("Custom Responses are not supported"); + } + } + + public void AddUrlMatch(string input, bool ignoreCase = true, bool negate = false, PatternSyntax patternSyntax = PatternSyntax.ECMAScript) + { + switch (patternSyntax) + { + case PatternSyntax.ECMAScript: + { + if (ignoreCase) + { + var regex = new Regex(input, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); + _initialMatch = new RegexMatch(regex, negate); + } + else + { + var regex = new Regex(input, RegexOptions.Compiled, RegexTimeout); + _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); + break; + } + } + + // TODO make this take two overloads and handle regex vs non regex case. + public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate) + { + if (_conditions == null) + { + AddUrlConditions(LogicalGrouping.MatchAll, trackingAllCaptures: false); + } + switch (patternSyntax) + { + case PatternSyntax.ECMAScript: + { + switch (matchType) + { + case MatchType.Pattern: + { + if (pattern == null) + { + throw new FormatException("Match does not have an associated pattern attribute in condition"); + } + + Regex regex = null; + if (ignoreCase) + { + regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); + } + else + { + regex = new Regex(pattern, RegexOptions.Compiled, RegexTimeout); + } + + _conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate) }); + break; + } + case MatchType.IsDirectory: + { + _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate) }); + break; + } + case MatchType.IsFile: + { + _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(negate) }); + break; + } + default: + // TODO new exception handling + throw new FormatException("Unrecognized matchType"); + } + break; + } + case PatternSyntax.WildCard: + throw new NotImplementedException("Wildcard syntax is not supported"); + case PatternSyntax.ExactMatch: + if (pattern == null) + { + throw new FormatException("Match does not have an associated pattern attribute in condition"); + } + _conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate) }); + break; + default: + throw new FormatException("Unrecognized pattern syntax"); + } + } + + public void AddUrlConditions(LogicalGrouping logicalGrouping, bool trackingAllCaptures) + { + var conditions = new Conditions(); + conditions.ConditionList = new List(); + conditions.MatchType = logicalGrouping; + conditions.TrackingAllCaptures = trackingAllCaptures; + _conditions = conditions; + } + } +} diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs index 9bd9c7e155..4295548204 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/UrlRewriteRule.cs @@ -7,8 +7,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { public string Name { get; set; } public bool Enabled { get; set; } = true; - public PatternSyntax PatternSyntax { get; set; } - public bool StopProcessing { get; set; } public UrlMatch InitialMatch { get; set; } public Conditions Conditions { get; set; } public UrlAction Action { get; set; } @@ -19,8 +17,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite { 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 initMatchRes = InitialMatch.Evaluate(context.HttpContext.Request.Path.ToString().Substring(1), context); if (!initMatchRes.Success) @@ -28,10 +28,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite return RuleResult.Continue; } - var condMatchRes = Conditions.Evaluate(context, initMatchRes); - if (!condMatchRes.Success) + MatchResults condMatchRes = null; + if (Conditions != null) { - return RuleResult.Continue; + condMatchRes = Conditions.Evaluate(context, initMatchRes); + if (!condMatchRes.Success) + { + return RuleResult.Continue; + } } // at this point we know the rule passed, evaluate the replacement. diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs new file mode 100644 index 0000000000..1f08becb6c --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/CodeRules/MiddlewareTests.cs @@ -0,0 +1,90 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules +{ + public class MiddlewareTests + { + [Fact] + public async Task CheckRewritePath() + { + var options = new RewriteOptions().RewriteRule("(.*)", "http://example.com/{R:1}"); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + app.UseRewriter(options); + app.Run(context => context.Response.WriteAsync( + context.Request.Scheme + + "://" + + context.Request.Host + + context.Request.Path + + context.Request.QueryString)); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetStringAsync("foo"); + + Assert.Equal(response, "http://example.com/foo"); + } + + [Fact] + public async Task CheckRedirectPath() + { + var options = new RewriteOptions().RedirectRule("(.*)","http://example.com/{R:1}", statusCode: 301); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync("foo"); + + 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() + { + var options = new RewriteOptions().RedirectToHttps(statusCode: 301); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); + + Assert.Equal(response.Headers.Location.OriginalString, "https://example.com/"); + } + } +} diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs index 4cdfe347aa..214bc87991 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FileParserTests.cs @@ -150,8 +150,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite Action = new RewriteAction(RuleTerminiation.Continue, InputParser.ParseInputString(Url), clearQuery: false), Name = name, Enabled = enabled, - StopProcessing = stopProcessing, - PatternSyntax = patternSyntax, InitialMatch = new RegexMatch(new Regex("^OFF$"), false) { }, @@ -164,29 +162,37 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite }; } - private void AssertUrlRewriteRuleEquality(List expected, List actual) + private void AssertUrlRewriteRuleEquality(List actual, List expected) { - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) + Assert.Equal(actual.Count, expected.Count); + for (var i = 0; i < actual.Count; i++) { - var r1 = expected[i]; - var r2 = actual[i]; + var r1 = actual[i]; + var r2 = expected[i]; Assert.Equal(r1.Name, r2.Name); Assert.Equal(r1.Enabled, r2.Enabled); - Assert.Equal(r1.StopProcessing, r2.StopProcessing); - Assert.Equal(r1.PatternSyntax, r2.PatternSyntax); // TODO conditions, url pattern, initial match regex - Assert.Equal(r1.Conditions.MatchType, r2.Conditions.MatchType); - Assert.Equal(r1.Conditions.TrackingAllCaptures, r2.Conditions.TrackingAllCaptures); - Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count); - - for (var j = 0; j < r1.Conditions.ConditionList.Count; j++) + if (r1.Conditions == null) { - var c1 = r1.Conditions.ConditionList[j]; - var c2 = r2.Conditions.ConditionList[j]; - Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count); + Assert.Equal(r2.Conditions.ConditionList.Count, 0); + } + else if (r2.Conditions == null) + { + Assert.Equal(r1.Conditions.ConditionList.Count, 0); + } + else + { + Assert.Equal(r1.Conditions.MatchType, r2.Conditions.MatchType); + Assert.Equal(r1.Conditions.TrackingAllCaptures, r2.Conditions.TrackingAllCaptures); + Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count); + for (var j = 0; j < r1.Conditions.ConditionList.Count; j++) + { + var c1 = r1.Conditions.ConditionList[j]; + var c2 = r2.Conditions.ConditionList[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 6a4f5f47e0..7ba0bc07a0 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/FormatExceptionHandlingTests.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite ", - "Could not parse the UrlRewrite file. Message: 'Abort Requests are not supported.'. Line number '5': '14'.")] + "Could not parse the UrlRewrite file. Message: 'Abort Requests are not supported'. Line number '5': '14'.")] [InlineData( @" @@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite + ", diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs index a34101b375..f2018d20b9 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/UrlRewrite/MiddleWareTests.cs @@ -19,13 +19,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public async Task Invoke_RedirectPathToPathAndQuery() { var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - ")); + + + + + + + ")); var builder = new WebHostBuilder() .Configure(app => { @@ -43,13 +43,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite public async Task Invoke_RewritePathToPathAndQuery() { var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - ")); + + + + + + + ")); var builder = new WebHostBuilder() .Configure(app => { @@ -121,13 +121,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - + + + + + + + ")); var builder = new WebHostBuilder() @@ -148,13 +148,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - + + + + + + + ")); var builder = new WebHostBuilder() @@ -174,13 +174,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - + + + + + + + ")); var builder = new WebHostBuilder() @@ -200,13 +200,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - - - - + + + + + + + ")); var builder = new WebHostBuilder() @@ -214,10 +214,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( - context.Request.Scheme + + context.Request.Scheme + "://" + context.Request.Host + - context.Request.Path + + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); @@ -232,10 +232,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@" - - - - + + + + ")); var builder = new WebHostBuilder()