Adds XML comments and feedback from 1on1 with Nate.

This commit is contained in:
Justin Kotalik 2016-08-29 13:55:44 -07:00
parent 4a06b37280
commit af2c1acee3
27 changed files with 158 additions and 129 deletions

View File

@ -1,3 +1,4 @@
# Rewrite path with additional sub directory # Rewrite path with additional sub directory
RewriteCond %{QUERY_STRING} id=20 RewriteCond %{HTTP_HOST} !^www\.example\.com [NC,OR]
RewriteRule ^(.*)$ - [G] RewriteCond %{SERVER_PORT} !^5000$
RewriteRule ^/(.*) http://www.example.com/$1 [L,R=302]

View File

@ -11,25 +11,16 @@ namespace RewriteSample
{ {
public class Startup public class Startup
{ {
public void Configure(IApplicationBuilder app, IHostingEnvironment hostingEnv) public void Configure(IApplicationBuilder app, IHostingEnvironment hostingEnvironment)
{ {
// Four main use cases for Rewrite Options. var options = new RewriteOptions()
// 1. Importing from a UrlRewrite file, which are IIS Rewrite rules. .AddRedirect("(.*)/$", "$1")
// This file is in xml format, starting with the <rewrite> tag. .AddRewrite(@"app/(\d+)", "app?id=$1")
// 2. Importing from a mod_rewrite file, which are mod_rewrite rules. .AddRedirectToHttps(302)
// This file is in standard mod_rewrite format which only contains rewrite information. .AddIISUrlRewrite(hostingEnvironment, "UrlRewrite.xml")
// 3. Inline rules in code, where you can specify rules such as rewrites and redirects .AddApacheModRewrite(hostingEnvironment, "Rewrite.txt");
// 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() app.UseRewriter(options);
.Rewrite(@"foo/(\d+)", "foo?id=$1")
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
.ImportFromModRewrite(hostingEnv, "Rewrite.txt"));
app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}")); app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}"));
} }

View File

@ -1,8 +1,11 @@
<rewrite> <rewrite>
<rules> <rules>
<rule name="" stopProcessing="true"> <rule name="" stopProcessing="true">
<match url="foo/bar" /> <match url="(.*)" />
<action type="Redirect" redirectType="Found" url="foo/" /> <conditions>
<add input="{QUERY_STRING}" pattern="id=20" />
</conditions>
<action type="Rewrite" url="app?id=10" appendQueryString="false"/>
</rule> </rule>
</rules> </rules>
</rewrite> </rewrite>

View File

@ -8,7 +8,10 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
public static class ModRewriteOptionsExtensions /// <summary>
/// Apache mod_rewrite extensions on top of the <see cref="RewriteOptions"/>
/// </summary>
public static class ApacheModRewriteOptionsExtensions
{ {
/// <summary> /// <summary>
/// Imports rules from a mod_rewrite file and adds the rules to current rules. /// Imports rules from a mod_rewrite file and adds the rules to current rules.
@ -16,7 +19,7 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="options">The Rewrite options.</param> /// <param name="options">The Rewrite options.</param>
/// <param name="hostingEnvironment">The Hosting Environment</param> /// <param name="hostingEnvironment">The Hosting Environment</param>
/// <param name="filePath">The path to the file containing mod_rewrite rules.</param> /// <param name="filePath">The path to the file containing mod_rewrite rules.</param>
public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath) public static RewriteOptions AddApacheModRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath)
{ {
if (options == null) if (options == null)
{ {
@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite
var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath); var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath);
using (var stream = File.OpenRead(path)) using (var stream = File.OpenRead(path))
{ {
return options.ImportFromModRewrite(new StreamReader(stream)); return options.AddApacheModRewrite(new StreamReader(stream));
} }
} }
@ -44,8 +47,8 @@ namespace Microsoft.AspNetCore.Rewrite
/// Imports rules from a mod_rewrite file and adds the rules to current rules. /// Imports rules from a mod_rewrite file and adds the rules to current rules.
/// </summary> /// </summary>
/// <param name="options">The Rewrite options.</param> /// <param name="options">The Rewrite options.</param>
/// <param name="reader">Text reader containing a stream of mod_rewrite rules.</param> /// <param name="reader">A stream of mod_rewrite rules.</param>
public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, TextReader reader) public static RewriteOptions AddApacheModRewrite(this RewriteOptions options, TextReader reader)
{ {
if (options == null) if (options == null)
{ {

View File

@ -8,7 +8,10 @@ using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
public static class UrlRewriteOptionsExtensions /// <summary>
/// IIS Url rewrite extensions on top of the <see cref="RewriteOptions"/>
/// </summary>
public static class IISUrlRewriteOptionsExtensions
{ {
/// <summary> /// <summary>
/// Imports rules from a mod_rewrite file and adds the rules to current rules. /// Imports rules from a mod_rewrite file and adds the rules to current rules.
@ -16,7 +19,7 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="options">The UrlRewrite options.</param> /// <param name="options">The UrlRewrite options.</param>
/// <param name="hostingEnvironment"></param> /// <param name="hostingEnvironment"></param>
/// <param name="filePath">The path to the file containing urlrewrite rules.</param> /// <param name="filePath">The path to the file containing urlrewrite rules.</param>
public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath) public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath)
{ {
if (options == null) if (options == null)
{ {
@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite
var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath); var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath);
using (var stream = File.OpenRead(path)) using (var stream = File.OpenRead(path))
{ {
return ImportFromUrlRewrite(options, new StreamReader(stream)); return AddIISUrlRewrite(options, new StreamReader(stream));
} }
} }
@ -45,7 +48,7 @@ namespace Microsoft.AspNetCore.Rewrite
/// </summary> /// </summary>
/// <param name="options">The UrlRewrite options.</param> /// <param name="options">The UrlRewrite options.</param>
/// <param name="reader">The text reader stream.</param> /// <param name="reader">The text reader stream.</param>
public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, TextReader reader) public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader)
{ {
if (options == null) if (options == null)
{ {

View File

@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
{ {
var result = initMatchResults.Result(Replacement); var result = initMatchResults.Result(Replacement);
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
if (result.IndexOf("://", StringComparison.Ordinal) >= 0) if (result.IndexOf("://", StringComparison.Ordinal) >= 0)
{ {
string scheme; string scheme;

View File

@ -35,6 +35,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ "noescape", FlagType.NoEscape }, { "noescape", FlagType.NoEscape },
{ "ns", FlagType.NoSubReq }, { "ns", FlagType.NoSubReq },
{ "nosubreq", FlagType.NoSubReq }, { "nosubreq", FlagType.NoSubReq },
{ "or", FlagType.Or },
{ "ornext", FlagType.Or },
{ "p", FlagType.Proxy }, { "p", FlagType.Proxy },
{ "proxy", FlagType.Proxy }, { "proxy", FlagType.Proxy },
{ "pt", FlagType.PassThrough }, { "pt", FlagType.PassThrough },

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal.PreActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
@ -13,19 +12,18 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
public class RuleBuilder public class RuleBuilder
{ {
private IList<Condition> _conditions; private IList<Condition> _conditions;
private UrlAction _action; private IList<UrlAction> _actions = new List<UrlAction>();
private UrlMatch _match; private UrlMatch _match;
private List<PreAction> _preActions;
private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
public ModRewriteRule Build() public ModRewriteRule Build()
{ {
if (_action == null || _match == null) if (_actions.Count == 0 || _match == null)
{ {
throw new InvalidOperationException("Cannot create ModRewriteRule without action and match"); throw new InvalidOperationException("Cannot create ModRewriteRule without action and match");
} }
return new ModRewriteRule(_match, _conditions, _action, _preActions); return new ModRewriteRule(_match, _conditions, _actions);
} }
public void AddRule(string rule) public void AddRule(string rule)
@ -169,31 +167,26 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
Flags flags) Flags flags)
{ {
// first create pre conditions // first create pre conditions
if (_preActions == null)
{
_preActions = new List<PreAction>();
}
string flag; string flag;
if (flags.GetValue(FlagType.Cookie, out flag)) if (flags.GetValue(FlagType.Cookie, out flag))
{ {
// parse cookie // parse cookie
_preActions.Add(new ChangeCookiePreAction(flag)); _actions.Add(new ChangeCookieAction(flag));
} }
if (flags.GetValue(FlagType.Env, out flag)) if (flags.GetValue(FlagType.Env, out flag))
{ {
// parse env // parse env
_preActions.Add(new ChangeEnvironmentPreAction(flag)); _actions.Add(new ChangeEnvironmentAction(flag));
} }
if (flags.HasFlag(FlagType.Forbidden)) if (flags.HasFlag(FlagType.Forbidden))
{ {
_action = new ForbiddenAction(); _actions.Add(new ForbiddenAction());
} }
else if (flags.HasFlag(FlagType.Gone)) else if (flags.HasFlag(FlagType.Gone))
{ {
_action = new GoneAction(); _actions.Add(new GoneAction());
} }
else else
{ {
@ -210,13 +203,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1)); throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1));
} }
_action = new RedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference); _actions.Add(new RedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference));
} }
else else
{ {
var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last); var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last);
var termination = last ? RuleTermination.StopRules : RuleTermination.Continue; var termination = last ? RuleTermination.StopRules : RuleTermination.Continue;
_action = new RewriteAction(termination, pattern, queryStringAppend, queryStringDelete, escapeBackReference); _actions.Add(new RewriteAction(termination, pattern, queryStringAppend, queryStringDelete, escapeBackReference));
} }
} }
} }

View File

@ -10,15 +10,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public UrlMatch InitialMatch { get; } public UrlMatch InitialMatch { get; }
public IList<Condition> Conditions { get; } public IList<Condition> Conditions { get; }
public UrlAction Action { get; } public IList<UrlAction> Actions { get; }
public IList<PreAction> PreActions { get; }
public ModRewriteRule(UrlMatch initialMatch, IList<Condition> conditions, UrlAction urlAction, IList<PreAction> preActions) public ModRewriteRule(UrlMatch initialMatch, IList<Condition> conditions, IList<UrlAction> urlActions)
{ {
Conditions = conditions; Conditions = conditions;
InitialMatch = initialMatch; InitialMatch = initialMatch;
Action = urlAction; Actions = urlActions;
PreActions = preActions;
} }
public override void ApplyRule(RewriteContext context) public override void ApplyRule(RewriteContext context)
@ -46,12 +44,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
// At this point, we know our rule passed, first apply pre conditions, // 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 // which can modify things like the cookie or env, and then apply the action
context.Logger?.ModRewriteMatchedRule(); context.Logger?.ModRewriteMatchedRule();
foreach (var preAction in PreActions)
{
preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes);
}
Action.ApplyAction(context, initMatchRes, condMatchRes); foreach (var action in Actions)
{
action.ApplyAction(context, initMatchRes, condMatchRes);
}
} }
} }
} }

View File

@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class IsHttpsModSegment : PatternSegment public class IsHttpsModSegment : PatternSegment
{ {
// Note: Mod rewrite pattern matches on lower case "on" and "off"
// while IIS looks for capitalized "ON" and "OFF"
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return context.HttpContext.Request.IsHttps ? "on" : "off"; return context.HttpContext.Request.IsHttps ? "on" : "off";

View File

@ -3,8 +3,10 @@
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class IsHttpsSegment : PatternSegment public class IsHttpsUrlSegment : PatternSegment
{ {
// Note: Mod rewrite pattern matches on lower case "on" and "off"
// while IIS looks for capitalized "ON" and "OFF"
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return context.HttpContext.Request.IsHttps ? "ON" : "OFF"; return context.HttpContext.Request.IsHttps ? "ON" : "OFF";

View File

@ -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.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public abstract class PreAction
{
public abstract void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch);
}
}

View File

@ -4,17 +4,17 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class ChangeCookiePreAction : PreAction public class ChangeCookieAction : UrlAction
{ {
public ChangeCookiePreAction(string cookie) public ChangeCookieAction(string cookie)
{ {
// TODO // TODO
throw new NotImplementedException(cookie); throw new NotImplementedException(cookie);
} }
public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
// modify the cookies // modify the cookies

View File

@ -4,17 +4,17 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class ChangeEnvironmentPreAction : PreAction public class ChangeEnvironmentAction : UrlAction
{ {
public ChangeEnvironmentPreAction(string env) public ChangeEnvironmentAction(string env)
{ {
// TODO // TODO
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
// Do stuff to modify the env // Do stuff to modify the env
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -41,7 +41,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
queryStringDelete: true, queryStringDelete: true,
escapeBackReferences: false) escapeBackReferences: false)
{ {
} }
public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
@ -58,7 +57,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
pattern = '/' + pattern; pattern = '/' + pattern;
} }
response.StatusCode = StatusCode; response.StatusCode = StatusCode;
// url can either contain the full url or the path and query // url can either contain the full url or the path and query

View File

@ -46,6 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
var pattern = Url.Evaluate(context, ruleMatch, condMatch); var pattern = Url.Evaluate(context, ruleMatch, condMatch);
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
if (EscapeBackReferences) if (EscapeBackReferences)
{ {
// because escapebackreferences will be encapsulated by the pattern, just escape the pattern // because escapebackreferences will be encapsulated by the pattern, just escape the pattern

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
case "HTTP_URL": case "HTTP_URL":
return new UrlSegment(); return new UrlSegment();
case "HTTPS": case "HTTPS":
return new IsHttpsSegment(); return new IsHttpsUrlSegment();
case "LOCAL_ADDR": case "LOCAL_ADDR":
return new LocalAddressSegment(); return new LocalAddressSegment();
case "HTTP_PROXY_CONNECTION": case "HTTP_PROXY_CONNECTION":

View File

@ -9,15 +9,31 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
/// <summary> /// <summary>
/// The UrlRewrite Context contains the HttpContext of the request, the file provider, and the logger. /// A context object for <see cref="RewriteMiddleware"/>
/// There is also a shared string builder across the application of rules.
/// </summary> /// </summary>
public class RewriteContext public class RewriteContext
{ {
/// <summary>
/// Gets and sets the <see cref="HttpContext"/>
/// </summary>
public HttpContext HttpContext { get; set; } public HttpContext HttpContext { get; set; }
/// <summary>
/// Gets and sets the File Provider for file and directory checks.
/// </summary>
public IFileProvider StaticFileProvider { get; set; } public IFileProvider StaticFileProvider { get; set; }
/// <summary>
/// Gets and sets the logger
/// </summary>
public ILogger Logger { get; set; } public ILogger Logger { get; set; }
/// <summary>
/// A shared result that is set appropriately by each rule for the next action that
/// should be take. See <see cref="RuleTermination"/>
/// </summary>
public RuleTermination Result { get; set; } public RuleTermination Result { get; set; }
// PERF: share the same string builder per request // PERF: share the same string builder per request
internal StringBuilder Builder { get; set; } = new StringBuilder(64); internal StringBuilder Builder { get; set; } = new StringBuilder(64);
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.AspNetCore.Rewrite.Logging;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -11,8 +11,14 @@ namespace Microsoft.AspNetCore.Rewrite
/// </summary> /// </summary>
public class RewriteOptions public class RewriteOptions
{ {
// TODO doc comments /// <summary>
/// A list of <see cref="Rule"/> that will be applied in order upon a request.
/// </summary>
public IList<Rule> Rules { get; } = new List<Rule>(); public IList<Rule> Rules { get; } = new List<Rule>();
/// <summary>
/// Gets and sets the File Provider for file and directory checks.
/// </summary>
public IFileProvider StaticFileProvider { get; set; } public IFileProvider StaticFileProvider { get; set; }
} }
} }

View File

@ -44,9 +44,9 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="regex">The regex string to compare with.</param> /// <param name="regex">The regex string to compare with.</param>
/// <param name="replacement">If the regex matches, what to replace HttpContext with.</param> /// <param name="replacement">If the regex matches, what to replace HttpContext with.</param>
/// <returns>The Rewrite options.</returns> /// <returns>The Rewrite options.</returns>
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string replacement) public static RewriteOptions AddRewrite(this RewriteOptions options, string regex, string replacement)
{ {
return Rewrite(options, regex, replacement, stopProcessing: false); return AddRewrite(options, regex, replacement, stopProcessing: false);
} }
/// <summary> /// <summary>
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="replacement">If the regex matches, what to replace the uri with.</param> /// <param name="replacement">If the regex matches, what to replace the uri with.</param>
/// <param name="stopProcessing">If the regex matches, conditionally stop processing other rules.</param> /// <param name="stopProcessing">If the regex matches, conditionally stop processing other rules.</param>
/// <returns>The Rewrite options.</returns> /// <returns>The Rewrite options.</returns>
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string replacement, bool stopProcessing) public static RewriteOptions AddRewrite(this RewriteOptions options, string regex, string replacement, bool stopProcessing)
{ {
options.Rules.Add(new RewriteRule(regex, replacement, stopProcessing)); options.Rules.Add(new RewriteRule(regex, replacement, stopProcessing));
return options; return options;
@ -70,9 +70,9 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="regex">The regex string to compare with.</param> /// <param name="regex">The regex string to compare with.</param>
/// <param name="replacement">If the regex matches, what to replace the uri with.</param> /// <param name="replacement">If the regex matches, what to replace the uri with.</param>
/// <returns>The Rewrite options.</returns> /// <returns>The Rewrite options.</returns>
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string replacement) public static RewriteOptions AddRedirect(this RewriteOptions options, string regex, string replacement)
{ {
return Redirect(options, regex, replacement, statusCode: 302); return AddRedirect(options, regex, replacement, statusCode: 302);
} }
/// <summary> /// <summary>
@ -83,24 +83,30 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="replacement">If the regex matches, what to replace the uri with.</param> /// <param name="replacement">If the regex matches, what to replace the uri with.</param>
/// <param name="statusCode">The status code to add to the response.</param> /// <param name="statusCode">The status code to add to the response.</param>
/// <returns>The Rewrite options.</returns> /// <returns>The Rewrite options.</returns>
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string replacement, int statusCode) public static RewriteOptions AddRedirect(this RewriteOptions options, string regex, string replacement, int statusCode)
{ {
options.Rules.Add(new RedirectRule(regex, replacement, statusCode)); options.Rules.Add(new RedirectRule(regex, replacement, statusCode));
return options; return options;
} }
public static RewriteOptions RedirectToHttpsPermanent(this RewriteOptions options) /// <summary>
/// Redirect a request to https if the incoming request is http, with returning a 301
/// status code for permanently redirected.
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public static RewriteOptions AddRedirectToHttpsPermanent(this RewriteOptions options)
{ {
return RedirectToHttps(options, statusCode: 301, sslPort: null); return AddRedirectToHttps(options, statusCode: 301, sslPort: null);
} }
/// <summary> /// <summary>
/// Redirect a request to https if the incoming request is http /// Redirect a request to https if the incoming request is http
/// </summary> /// </summary>
/// <param name="options">The Rewrite options.</param> /// <param name="options">The Rewrite options.</param>
public static RewriteOptions RedirectToHttps(this RewriteOptions options) public static RewriteOptions AddRedirectToHttps(this RewriteOptions options)
{ {
return RedirectToHttps(options, statusCode: 302, sslPort: null); return AddRedirectToHttps(options, statusCode: 302, sslPort: null);
} }
/// <summary> /// <summary>
@ -108,9 +114,9 @@ namespace Microsoft.AspNetCore.Rewrite
/// </summary> /// </summary>
/// <param name="options">The Rewrite options.</param> /// <param name="options">The Rewrite options.</param>
/// <param name="statusCode">The status code to add to the response.</param> /// <param name="statusCode">The status code to add to the response.</param>
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode) public static RewriteOptions AddRedirectToHttps(this RewriteOptions options, int statusCode)
{ {
return RedirectToHttps(options, statusCode, sslPort: null); return AddRedirectToHttps(options, statusCode, sslPort: null);
} }
/// <summary> /// <summary>
@ -119,7 +125,7 @@ namespace Microsoft.AspNetCore.Rewrite
/// <param name="options">The Rewrite options.</param> /// <param name="options">The Rewrite options.</param>
/// <param name="statusCode">The status code to add to the response.</param> /// <param name="statusCode">The status code to add to the response.</param>
/// <param name="sslPort">The SSL port to add to the response.</param> /// <param name="sslPort">The SSL port to add to the response.</param>
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort) public static RewriteOptions AddRedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort)
{ {
options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort }); options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort });
return options; return options;

View File

@ -3,10 +3,17 @@
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
// make this public and doc comements /// <summary>
// caller must set the context.Results field appropriately in rule. /// Represents an abstract rule.
/// </summary>
public abstract class Rule public abstract class Rule
{ {
/// <summary>
/// Applies the rule.
/// Implementations of ApplyRule should set the value for RewriteContext.Results
/// (defaults to RuleTermination.Continue)
/// </summary>
/// <param name="context"></param>
public abstract void ApplyRule(RewriteContext context); public abstract void ApplyRule(RewriteContext context);
} }
} }

View File

@ -3,10 +3,22 @@
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
/// <summary>
/// An enum representing the result of a rule.
/// </summary>
public enum RuleTermination public enum RuleTermination
{ {
/// <summary>
/// Default value, continue applying rules.
/// </summary>
Continue, Continue,
///<summary>
/// Redirect occured, should send back new rewritten url.
/// </summary>
ResponseComplete, ResponseComplete,
/// <summary>
/// Stop applying rules and send context to the next middleware
/// </summary>
StopRules StopRules
} }
} }

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
[Fact] [Fact]
public async Task CheckRewritePath() public async Task CheckRewritePath()
{ {
var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/$1"); var options = new RewriteOptions().AddRewrite("(.*)", "http://example.com/$1");
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
[Fact] [Fact]
public async Task CheckRedirectPath() public async Task CheckRedirectPath()
{ {
var options = new RewriteOptions().Redirect("(.*)","http://example.com/$1", statusCode: 301); var options = new RewriteOptions().AddRedirect("(.*)","http://example.com/$1", statusCode: 301);
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
[Fact] [Fact]
public async Task CheckRedirectToHttps() public async Task CheckRedirectToHttps()
{ {
var options = new RewriteOptions().RedirectToHttps(statusCode: 301); var options = new RewriteOptions().AddRedirectToHttps(statusCode: 301);
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_RewritePathWhenMatching() public async Task Invoke_RewritePathWhenMatching()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 ")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 "));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -34,8 +34,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule() public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]")) var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]"))
.ImportFromModRewrite(new StringReader("RewriteRule /hello /what")); .AddApacheModRewrite(new StringReader("RewriteRule /hello /what"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -52,8 +52,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule() public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1")) var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1"))
.ImportFromModRewrite(new StringReader("RewriteRule /hello /what")); .AddApacheModRewrite(new StringReader("RewriteRule /hello /what"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_ShouldIgnoreComments() public async Task Invoke_ShouldIgnoreComments()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("#RewriteRule ^/hey/(.*) /$1 ")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader("#RewriteRule ^/hey/(.*) /$1 "));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_ShouldRewriteHomepage() public async Task Invoke_ShouldRewriteHomepage()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_ShouldIgnorePorts() public async Task Invoke_ShouldIgnorePorts()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_HandleNegatedRewriteRules() public async Task Invoke_HandleNegatedRewriteRules()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule !^/$ /homepage.html")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule !^/$ /homepage.html"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public async Task Invoke_BackReferencesShouldBeApplied() public async Task Invoke_BackReferencesShouldBeApplied()
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule (.*)\.aspx $1.php")); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule (.*)\.aspx $1.php"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")] [InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")]
public async Task Invoke_ShouldHandleFlagNoCase(string url, string rule, string expected) public async Task Invoke_ShouldHandleFlagNoCase(string url, string rule, string expected)
{ {
var options = new RewriteOptions().ImportFromModRewrite(new StringReader(rule)); var options = new RewriteOptions().AddApacheModRewrite(new StringReader(rule));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
public async Task Invoke_CheckFullUrlWithOnlyPath() public async Task Invoke_CheckFullUrlWithOnlyPath()
{ {
var options = new RewriteOptions() var options = new RewriteOptions()
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
public async Task Invoke_CheckFullUrlWithUFlag() public async Task Invoke_CheckFullUrlWithUFlag()
{ {
var options = new RewriteOptions() var options = new RewriteOptions()
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
public async Task Invoke_CheckModFileConditions() public async Task Invoke_CheckModFileConditions()
{ {
var options = new RewriteOptions() var options = new RewriteOptions()
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
public async Task Invoke_EnsureHttps(string input) public async Task Invoke_EnsureHttps(string input)
{ {
var options = new RewriteOptions() var options = new RewriteOptions()
.ImportFromModRewrite(new StringReader("RewriteCond %{REQUEST_URI} /foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]")); .AddApacheModRewrite(new StringReader("RewriteCond %{REQUEST_URI} /foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected) public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected)
{ {
// Arrange // Arrange
var segement = new IsHttpsSegment(); var segement = new IsHttpsUrlSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Scheme = input; context.HttpContext.Request.Scheme = input;

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RedirectPathToPathAndQuery() public async Task Invoke_RedirectPathToPathAndQuery()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Rewrite to article.aspx""> <rule name=""Rewrite to article.aspx"">
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" /> <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RewritePathToPathAndQuery() public async Task Invoke_RewritePathToPathAndQuery()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Rewrite to article.aspx""> <rule name=""Rewrite to article.aspx"">
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" /> <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RewriteBasedOnQueryStringParameters() public async Task Invoke_RewriteBasedOnQueryStringParameters()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Query String Rewrite""> <rule name=""Query String Rewrite"">
<match url=""page\.asp$"" /> <match url=""page\.asp$"" />
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RedirectToLowerCase() public async Task Invoke_RedirectToLowerCase()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Convert to lower case"" stopProcessing=""true""> <rule name=""Convert to lower case"" stopProcessing=""true"">
<match url="".*[A-Z].*"" ignoreCase=""false"" /> <match url="".*[A-Z].*"" ignoreCase=""false"" />
@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RedirectRemoveTrailingSlash() public async Task Invoke_RedirectRemoveTrailingSlash()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Remove trailing slash"" stopProcessing=""true""> <rule name=""Remove trailing slash"" stopProcessing=""true"">
<match url=""(.*)/$"" /> <match url=""(.*)/$"" />
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RedirectAddTrailingSlash() public async Task Invoke_RedirectAddTrailingSlash()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Add trailing slash"" stopProcessing=""true""> <rule name=""Add trailing slash"" stopProcessing=""true"">
<match url=""(.*[^/])$"" /> <match url=""(.*[^/])$"" />
@ -172,7 +172,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RedirectToHttps() public async Task Invoke_RedirectToHttps()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Redirect to HTTPS"" stopProcessing=""true""> <rule name=""Redirect to HTTPS"" stopProcessing=""true"">
<match url=""(.*)"" /> <match url=""(.*)"" />
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_RewriteToHttps() public async Task Invoke_RewriteToHttps()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Rewrite to HTTPS"" stopProcessing=""true""> <rule name=""Rewrite to HTTPS"" stopProcessing=""true"">
<match url=""(.*)"" /> <match url=""(.*)"" />
@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
[Fact] [Fact]
public async Task Invoke_ReverseProxyToAnotherSite() public async Task Invoke_ReverseProxyToAnotherSite()
{ {
var options = new RewriteOptions().ImportFromUrlRewrite(new StringReader(@"<rewrite> var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite>
<rules> <rules>
<rule name=""Proxy""> <rule name=""Proxy"">
<match url=""(.*)"" /> <match url=""(.*)"" />