Feedback from David and API Review/ Tests

This commit is contained in:
ZestyBread 2016-08-24 12:30:21 -07:00 committed by Justin
parent 0cb7445921
commit f3b4658c2c
76 changed files with 1209 additions and 486 deletions

View File

@ -1,10 +1,3 @@
# Ensure Https # Rewrite path with additional sub directory
RewriteCond %{HTTPS} off RewriteCond %{QUERY_STRING} id=20
RewriteRule ^(.*)$ https://www.example.com$1 [L] RewriteRule ^(.*)$ - [G]
# Rewrite path with additional sub directory
RewriteRule ^(.*)$ /foo$1
# Forbid a certain url from being accessed
RewriteRule /bar - [F]
RewriteRule /bar/ - [F]

View File

@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite; using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace RewriteSample namespace RewriteSample
{ {
@ -26,16 +25,11 @@ namespace RewriteSample
// (ex StringReplace) that are easy to implement in code, they can do so by calling // (ex StringReplace) that are easy to implement in code, they can do so by calling
// AddFunctionalRule(Func); // AddFunctionalRule(Func);
// TODO make this startup do something useful. // TODO make this startup do something useful.
app.UseRewriter(new RewriteOptions() app.UseRewriter(new RewriteOptions()
.Rewrite(@"foo/(\d+)", "foo?id={R:1}")
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml") .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($"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,8 @@
<rewrite> <rewrite>
<rules> <rules>
<rule name="Example" stopProcessing="true"> <rule name="" stopProcessing="true">
<match url="(.*)" /> <match url="foo/bar" />
<action type="Rewrite" url="http://example.com/{R:1}" /> <action type="Redirect" redirectType="Found" url="foo/" />
</rule> </rule>
</rules> </rules>
</rewrite> </rewrite>

View File

@ -1,104 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.CodeRules;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite
{
/// <summary>
/// The builder to a list of rules for <see cref="RewriteOptions"/> and <see cref="RewriteMiddleware"/>
/// </summary>
public static class CodeRewriteExtensions
{
/// <summary>
/// Adds a rule to the current rules.
/// </summary>
/// <param name="options">The UrlRewrite options.</param>
/// <param name="rule">A rule to be added to the current rules.</param>
public static RewriteOptions AddRule(this RewriteOptions options, Rule rule)
{
options.Rules.Add(rule);
return options;
}
/// <summary>
/// Adds a list of rules to the current rules.
/// </summary>
/// <param name="options">The UrlRewrite options.</param>
/// <param name="rules">A list of rules.</param>
public static RewriteOptions AddRules(this RewriteOptions options, List<Rule> rules)
{
options.Rules.AddRange(rules);
return options;
}
public static RewriteOptions RewriteRule(this RewriteOptions options, string regex, string onMatch)
{
return RewriteRule(options, regex, onMatch, stopProcessing: false);
}
public static RewriteOptions RewriteRule(this RewriteOptions options, string regex, string onMatch, bool stopProcessing)
{
var builder = new UrlRewriteRuleBuilder();
var pattern = new InputParser().ParseInputString(onMatch);
builder.AddUrlMatch(regex);
builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing);
options.Rules.Add(builder.Build());
return options;
}
public static RewriteOptions RedirectRule(this RewriteOptions options, string regex, string onMatch, int statusCode)
{
return RedirectRule(options, regex, onMatch, statusCode, stopProcessing: false);
}
public static RewriteOptions RedirectRule(this RewriteOptions options, string regex, string onMatch, int statusCode, bool stopProcessing)
{
var builder = new UrlRewriteRuleBuilder();
var pattern = new InputParser().ParseInputString(onMatch);
builder.AddUrlMatch(regex);
builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: stopProcessing);
options.Rules.Add(builder.Build());
return options;
}
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode)
{
return RedirectToHttps(options, statusCode, null);
}
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort)
{
options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort });
return options;
}
public static RewriteOptions RewriteToHttps(this RewriteOptions options)
{
return RewriteToHttps(options, sslPort: null, stopProcessing: false);
}
public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort)
{
return RewriteToHttps(options, sslPort, stopProcessing: false);
}
public static RewriteOptions RewriteToHttps(this RewriteOptions options, int? sslPort, bool stopProcessing)
{
options.Rules.Add(new RewriteToHttpsRule {SSLPort = sslPort, StopProcessing = stopProcessing });
return options;
}
public static RewriteOptions AddRule(this RewriteOptions options, Func<RewriteContext, RuleResult> rule)
{
options.Rules.Add(new FunctionalRule { OnApplyRule = rule});
return options;
}
}
}

View File

@ -5,9 +5,14 @@ using System;
namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
{ {
public class FunctionalRule : Rule public class DelegateRule : Rule
{ {
public Func<RewriteContext, RuleResult> OnApplyRule { get; set; } private readonly Func<RewriteContext, RuleResult> _onApplyRule;
public override RuleResult ApplyRule(RewriteContext context) => OnApplyRule(context);
public DelegateRule(Func<RewriteContext, RuleResult> onApplyRule)
{
_onApplyRule = onApplyRule;
}
public override RuleResult ApplyRule(RewriteContext context) => _onApplyRule(context);
} }
} }

View File

@ -1,36 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
{
public class RewriteToHttpsRule : Rule
{
public bool StopProcessing { get; set; }
public int? SSLPort { get; set; }
public override RuleResult ApplyRule(RewriteContext context)
{
if (!context.HttpContext.Request.IsHttps)
{
var host = context.HttpContext.Request.Host;
if (SSLPort.HasValue && SSLPort.Value > 0)
{
// a specific SSL port is specified
host = new HostString(host.Host, SSLPort.Value);
}
else
{
// clear the port
host = new HostString(host.Host);
}
context.HttpContext.Request.Scheme = "https";
context.HttpContext.Request.Host = host;
return StopProcessing ? RuleResult.StopRules: RuleResult.Continue;
}
return RuleResult.Continue;
}
}
}

View File

@ -5,15 +5,14 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class Conditions public static class ConditionHelper
{ {
public List<Condition> ConditionList { get; set; } = new List<Condition>();
public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch) public static MatchResults Evaluate(IEnumerable<Condition> conditions, RewriteContext context, MatchResults ruleMatch)
{ {
MatchResults prevCond = null; MatchResults prevCond = null;
var orSucceeded = false; var orSucceeded = false;
foreach (var condition in ConditionList) foreach (var condition in conditions)
{ {
if (orSucceeded && condition.OrNext) if (orSucceeded && condition.OrNext)
{ {
@ -30,7 +29,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
if (condition.OrNext) if (condition.OrNext)
{ {
orSucceeded = prevCond.Success; orSucceeded = prevCond.Success;
continue;
} }
else if (!prevCond.Success) else if (!prevCond.Success)
{ {

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <returns>A new parsed condition.</returns> /// <returns>A new parsed condition.</returns>
public ParsedModRewriteInput ParseActionCondition(string condition) public ParsedModRewriteInput ParseActionCondition(string condition)
{ {
if (condition == null) if (condition == null)
{ {
condition = string.Empty; condition = string.Empty;
} }
@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
} }
// Capture the rest of the string guarantee validity. // Capture the rest of the string guarantee validity.
results.Operand = condition.Substring(context.GetIndex()); results.Operand = condition.Substring(context.GetIndex());
if (IsValidActionCondition(results)) if (IsValidActionCondition(results))
{ {
return results; return results;

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public class FileParser public class FileParser
{ {
public List<Rule> Parse(TextReader input) public IList<Rule> Parse(TextReader input)
{ {
string line; string line;
var rules = new List<Rule>(); var rules = new List<Rule>();
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
builder.AddAction(pattern, flags); builder.AddAction(pattern, flags);
rules.Add(builder.Build()); rules.Add(builder.Build());
builder = new RuleBuilder(); builder = new RuleBuilder();
} }
catch (FormatException formatException) catch (FormatException formatException)
{ {
throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException);

View File

@ -15,10 +15,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
public bool EscapeBackReferences { get; } public bool EscapeBackReferences { get; }
public ModRewriteRedirectAction( public ModRewriteRedirectAction(
int statusCode, int statusCode,
Pattern pattern, Pattern pattern,
bool queryStringAppend, bool queryStringAppend,
bool queryStringDelete, bool queryStringDelete,
bool escapeBackReferences) bool escapeBackReferences)
{ {
StatusCode = statusCode; StatusCode = statusCode;
@ -42,19 +42,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// always add to location header. // always add to location header.
// TODO check for false positives // TODO check for false positives
var split = pattern.IndexOf('?'); var split = pattern.IndexOf('?');
if (split >= 0) if (split >= 0 && QueryStringAppend)
{ {
QueryString query; var query = context.HttpContext.Request.QueryString.Add(
if (QueryStringAppend) QueryString.FromUriComponent(
{ pattern.Substring(split)));
query = context.HttpContext.Request.QueryString.Add(
QueryString.FromUriComponent(
pattern.Substring(split)));
}
else
{
query = QueryString.FromUriComponent(pattern.Substring(split));
}
// not using the response.redirect here because status codes may be 301, 302, 307, 308 // not using the response.redirect here because status codes may be 301, 302, 307, 308
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;

View File

@ -16,8 +16,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
public bool EscapeBackReferences { get; } public bool EscapeBackReferences { get; }
public ModRewriteRewriteAction( public ModRewriteRewriteAction(
RuleResult result, RuleResult result,
Pattern pattern, Pattern pattern,
bool queryStringAppend, bool queryStringAppend,
bool queryStringDelete, bool queryStringDelete,
bool escapeBackReferences) bool escapeBackReferences)

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public class RuleBuilder public class RuleBuilder
{ {
private Conditions _conditions; private IList<Condition> _conditions;
private UrlAction _action; private UrlAction _action;
private UrlMatch _match; private UrlMatch _match;
private List<PreAction> _preActions; private List<PreAction> _preActions;
@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
if (_conditions == null) if (_conditions == null)
{ {
_conditions = new Conditions(); _conditions = new List<Condition>();
} }
var condition = new Condition(); var condition = new Condition();
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
} }
break; break;
} }
_conditions.ConditionList.Add(condition); _conditions.Add(condition);
} }
public void AddMatch( public void AddMatch(

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
} }
else else
{ {
return new ParsedModRewriteInput { Invert = false, Operand = regex}; return new ParsedModRewriteInput { Invert = false, Operand = regex };
} }
} }
} }

View File

@ -106,7 +106,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
case "HTTP2": case "HTTP2":
throw new NotImplementedException("Http2 server variable is not supported"); throw new NotImplementedException("Http2 server variable is not supported");
case "IS_SUBREQ": case "IS_SUBREQ":
// TODO maybe can do this? context.Request.HttpContext ?
throw new NotImplementedException("Is-Subrequest server variable is not supported"); throw new NotImplementedException("Is-Subrequest server variable is not supported");
case "REQUEST_FILENAME": case "REQUEST_FILENAME":
return new RequestFileNameSegment(); return new RequestFileNameSegment();

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
var ruleVariable = context.Capture(); var ruleVariable = context.Capture();
context.Back(); context.Back();
var parsedIndex = int.Parse(ruleVariable); var parsedIndex = int.Parse(ruleVariable);
results.Add(new RuleMatchSegment(parsedIndex)); results.Add(new RuleMatchSegment(parsedIndex));
} }
else else
@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <param name="context">The ParserContext</param> /// <param name="context">The ParserContext</param>
/// <param name="results">The List of results which the new condition parameter will be added.</param> /// <param name="results">The List of results which the new condition parameter will be added.</param>
/// <returns>true </returns> /// <returns>true </returns>
private static void ParseConditionParameter(ParserContext context, List<PatternSegment> results) private static void ParseConditionParameter(ParserContext context, IList<PatternSegment> results)
{ {
// Parse { } // Parse { }
if (context.Current == OpenBrace) if (context.Current == OpenBrace)
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <param name="context"></param> /// <param name="context"></param>
/// <param name="results"></param> /// <param name="results"></param>
/// <returns></returns> /// <returns></returns>
private static void ParseLiteral(ParserContext context, List<PatternSegment> results) private static void ParseLiteral(ParserContext context, IList<PatternSegment> results)
{ {
context.Mark(); context.Mark();
string literal; string literal;

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// </summary> /// </summary>
/// <param name="rule">The rule to tokenize.</param> /// <param name="rule">The rule to tokenize.</param>
/// <returns>A list of tokens.</returns> /// <returns>A list of tokens.</returns>
public List<string> Tokenize(string rule) public IList<string> Tokenize(string rule)
{ {
// TODO make list of strings a reference to the original rule? (run into problems with escaped spaces). // TODO make list of strings a reference to the original rule? (run into problems with escaped spaces).
// TODO handle "s and probably replace \ character with no slash. // TODO handle "s and probably replace \ character with no slash.

View File

@ -2,18 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Logging;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class ModRewriteRule : Rule public class ModRewriteRule : Rule
{ {
public UrlMatch InitialMatch { get; set; } public UrlMatch InitialMatch { get; }
public Conditions Conditions { get; set; } public IList<Condition> Conditions { get; }
public UrlAction Action { get; set; } public UrlAction Action { get; }
public List<PreAction> PreActions { get; set; } public IList<PreAction> PreActions { get; }
public ModRewriteRule(UrlMatch initialMatch, Conditions conditions, UrlAction urlAction, List<PreAction> preActions) public ModRewriteRule(UrlMatch initialMatch, IList<Condition> conditions, UrlAction urlAction, IList<PreAction> preActions)
{ {
Conditions = conditions; Conditions = conditions;
InitialMatch = initialMatch; InitialMatch = initialMatch;
@ -28,21 +28,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
if (!initMatchRes.Success) if (!initMatchRes.Success)
{ {
context.Logger?.ModRewriteDidNotMatchRule();
return RuleResult.Continue; return RuleResult.Continue;
} }
MatchResults condMatchRes = null; MatchResults condMatchRes = null;
if (Conditions != null) if (Conditions != null)
{ {
condMatchRes = Conditions.Evaluate(context, initMatchRes); condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchRes);
if (!condMatchRes.Success) if (!condMatchRes.Success)
{ {
context.Logger?.ModRewriteDidNotMatchRule();
return RuleResult.Continue; return RuleResult.Continue;
} }
} }
// 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();
foreach (var preAction in PreActions) foreach (var preAction in PreActions)
{ {
preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes); preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes);

View File

@ -1,14 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class PatternSegment public abstract class PatternSegment
{ {
// Match from prevRule, Match from prevCond
public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch);
} }
} }

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
public DateTimeSegment(string segment) public DateTimeSegment(string segment)
{ {
switch(segment) switch (segment)
{ {
case "TIME_YEAR": case "TIME_YEAR":
_portion = DateTimePortion.Year; _portion = DateTimePortion.Year;
@ -24,28 +24,29 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
_portion = DateTimePortion.Day; _portion = DateTimePortion.Day;
break; break;
case "TIME_HOUR": case "TIME_HOUR":
_portion = DateTimePortion.Day; _portion = DateTimePortion.Hour;
break; break;
case "TIME_MIN": case "TIME_MIN":
_portion = DateTimePortion.Day; _portion = DateTimePortion.Minute;
break; break;
case "TIME_SEC": case "TIME_SEC":
_portion = DateTimePortion.Day; _portion = DateTimePortion.Second;
break; break;
case "TIME_WDAY": case "TIME_WDAY":
_portion = DateTimePortion.Day; _portion = DateTimePortion.DayOfWeek;
break; break;
case "TIME": case "TIME":
_portion = DateTimePortion.Day; _portion = DateTimePortion.Time;
break; break;
default: default:
throw new FormatException("Unsupported segment: " + segment); throw new FormatException($"Unsupported segment: '{segment}'");
} }
} }
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
switch (_portion) { switch (_portion)
{
case DateTimePortion.Year: case DateTimePortion.Year:
return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Month: case DateTimePortion.Month:
@ -67,7 +68,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
} }
} }
private enum DateTimePortion { private enum DateTimePortion
{
Year, Year,
Month, Month,
Day, Day,

View File

@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
public class HeaderSegment : PatternSegment public class HeaderSegment : PatternSegment
{ {
private readonly string _header; private readonly string _header;
public HeaderSegment(string header) public HeaderSegment(string header)
{ {
_header = header; _header = header;

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
public class UrlEncodeSegment : PatternSegment public class UrlEncodeSegment : PatternSegment
{ {
private readonly Pattern _pattern; private readonly Pattern _pattern;
public UrlEncodeSegment(Pattern pattern) public UrlEncodeSegment(Pattern pattern)
{ {
_pattern = pattern; _pattern = pattern;
@ -17,10 +17,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
var tempBuilder = context.Builder; var oldBuilder = context.Builder;
// PERF
// Because we need to be able to evaluate multiple nested patterns,
// we provided a new string builder and evaluate the new pattern,
// and restore it after evaluation.
context.Builder = new StringBuilder(64); context.Builder = new StringBuilder(64);
var pattern = _pattern.Evaluate(context, ruleMatch, condMatch); var pattern = _pattern.Evaluate(context, ruleMatch, condMatch);
context.Builder = tempBuilder; context.Builder = oldBuilder;
return UrlEncoder.Default.Encode(pattern); return UrlEncoder.Default.Encode(pattern);
} }
} }

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions
public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
// modify the cookies // modify the cookies
} }
} }
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class UrlAction public abstract class UrlAction
{ {
public Pattern Url { get; set; } protected Pattern Url { get; set; }
public abstract RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch);
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -9,25 +10,33 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
public class RedirectAction : UrlAction public class RedirectAction : UrlAction
{ {
public int StatusCode { get; } public int StatusCode { get; }
public RedirectAction(int statusCode, Pattern pattern) public bool AppendQueryString { get; }
public RedirectAction(int statusCode, Pattern pattern, bool appendQueryString)
{ {
StatusCode = statusCode; StatusCode = statusCode;
Url = pattern; Url = pattern;
AppendQueryString = appendQueryString;
} }
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
var pattern = Url.Evaluate(context, ruleMatch, condMatch); var pattern = Url.Evaluate(context, ruleMatch, condMatch);
context.HttpContext.Response.StatusCode = StatusCode; context.HttpContext.Response.StatusCode = StatusCode;
// TODO IIS guarantees that there will be a leading slash
if (pattern.IndexOf("://", StringComparison.Ordinal) == -1 && !pattern.StartsWith("/"))
{
pattern = '/' + pattern;
}
// url can either contain the full url or the path and query // url can either contain the full url or the path and query
// always add to location header. // always add to location header.
// TODO check for false positives // TODO check for false positives
var split = pattern.IndexOf('?'); var split = pattern.IndexOf('?');
if (split >= 0) if (split >= 0 && AppendQueryString)
{ {
var query = context.HttpContext.Request.QueryString.Add( var query = context.HttpContext.Request.QueryString.Add(
QueryString.FromUriComponent( QueryString.FromUriComponent(
pattern.Substring(split))); pattern.Substring(split)));
// not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308 // not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308

View File

@ -1,27 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{
public class RedirectClearQueryAction : UrlAction
{
public int StatusCode { get; }
public RedirectClearQueryAction(int statusCode, Pattern pattern)
{
StatusCode = statusCode;
Url = pattern;
}
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
context.HttpContext.Response.StatusCode = StatusCode;
// we are clearing the query, so just put the pattern in the location header
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
return RuleResult.ResponseComplete;
}
}
}

View File

@ -10,10 +10,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
public class RewriteAction : UrlAction public class RewriteAction : UrlAction
{ {
private readonly string ForwardSlash = "/"; private readonly string ForwardSlash = "/";
public RuleTerminiation Result { get; } public RuleTermination Result { get; }
public bool ClearQuery { get; } public bool ClearQuery { get; }
public RewriteAction(RuleTerminiation result, Pattern pattern, bool clearQuery) public RewriteAction(RuleTermination result, Pattern pattern, bool clearQuery)
{ {
Result = result; Result = result;
Url = pattern; Url = pattern;
@ -28,8 +28,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
context.HttpContext.Request.QueryString = QueryString.Empty; context.HttpContext.Request.QueryString = QueryString.Empty;
} }
// TODO PERF, substrings, object creation, etc.
if (pattern.IndexOf("://") >= 0) if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0)
{ {
string scheme; string scheme;
HostString host; HostString host;

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public override MatchResults Evaluate(string input, RewriteContext context) public override MatchResults Evaluate(string input, RewriteContext context)
{ {
var fileInfo = context.FileProvider.GetFileInfo(input); var fileInfo = context.StaticFileProvider.GetFileInfo(input);
return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
} }
} }

View File

@ -6,13 +6,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public class IsDirectoryMatch : UrlMatch public class IsDirectoryMatch : UrlMatch
{ {
public IsDirectoryMatch(bool negate) public IsDirectoryMatch(bool negate)
{ {
Negate = negate; Negate = negate;
} }
public override MatchResults Evaluate(string pattern, RewriteContext context) public override MatchResults Evaluate(string pattern, RewriteContext context)
{ {
var res = context.FileProvider.GetFileInfo(pattern).IsDirectory; var res = context.StaticFileProvider.GetFileInfo(pattern).IsDirectory;
return new MatchResults { Success = (res != Negate) }; return new MatchResults { Success = (res != Negate) };
} }
} }

View File

@ -4,7 +4,7 @@
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IsFileMatch : UrlMatch public class IsFileMatch : UrlMatch
{ {
public IsFileMatch(bool negate) public IsFileMatch(bool negate)
{ {
Negate = negate; Negate = negate;
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public override MatchResults Evaluate(string pattern, RewriteContext context) public override MatchResults Evaluate(string pattern, RewriteContext context)
{ {
var res = context.FileProvider.GetFileInfo(pattern).Exists; var res = context.StaticFileProvider.GetFileInfo(pattern).Exists;
return new MatchResults { Success = (res != Negate) }; return new MatchResults { Success = (res != Negate) };
} }
} }

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
public override MatchResults Evaluate(string pattern, RewriteContext context) public override MatchResults Evaluate(string pattern, RewriteContext context)
{ {
var res = _match.Match(pattern); var res = _match.Match(pattern);
return new MatchResults { BackReference = res.Groups, Success = (res.Success != Negate)}; return new MatchResults { BackReference = res.Groups, Success = (res.Success != Negate) };
} }
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return new Pattern(results); return new Pattern(results);
} }
private static void ParseParameter(ParserContext context, List<PatternSegment> results) private static void ParseParameter(ParserContext context, IList<PatternSegment> results)
{ {
context.Mark(); context.Mark();
// Four main cases: // Four main cases:
@ -82,7 +81,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
else if (context.Current == Colon) else if (context.Current == Colon)
{ {
parameter = context.Capture(); parameter = context.Capture();
// Only 5 strings to expect here. Case sensitive. // Only 5 strings to expect here. Case sensitive.
switch (parameter) switch (parameter)
{ {
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return index; return index;
} }
private static void ParseLiteral(ParserContext context, List<PatternSegment> results) private static void ParseLiteral(ParserContext context, IList<PatternSegment> results)
{ {
context.Mark(); context.Mark();
string literal; string literal;

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -12,7 +11,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
public static PatternSegment FindServerVariable(string serverVariable) public static PatternSegment FindServerVariable(string serverVariable)
{ {
switch(serverVariable) switch (serverVariable)
{ {
// TODO Add all server variables here. // TODO Add all server variables here.
case "ALL_RAW": case "ALL_RAW":

View File

@ -10,11 +10,11 @@ using System.Xml.Linq;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
public class FileParser public class UrlRewriteFileParser
{ {
private readonly InputParser _inputParser = new InputParser(); private readonly InputParser _inputParser = new InputParser();
public List<UrlRewriteRule> Parse(TextReader reader) public IList<UrlRewriteRule> Parse(TextReader reader)
{ {
var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo);
var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault();
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return null; return null;
} }
private void ParseRules(XElement rules, List<UrlRewriteRule> result) private void ParseRules(XElement rules, IList<UrlRewriteRule> result)
{ {
if (rules == null) if (rules == null)
{ {

View File

@ -13,12 +13,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
public class UrlRewriteRuleBuilder public class UrlRewriteRuleBuilder
{ {
private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1); private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
public string Name { get; set; } public string Name { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
private UrlMatch _initialMatch; private UrlMatch _initialMatch;
private Conditions _conditions; private IList<Condition> _conditions;
private UrlAction _action; private UrlAction _action;
private bool _matchAny; private bool _matchAny;
@ -28,40 +28,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match"); throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match");
} }
var rule = new UrlRewriteRule();
rule.Action = _action; return new UrlRewriteRule(Name, _initialMatch, _conditions, _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) public void AddUrlAction(
Pattern url,
ActionType actionType = ActionType.None,
bool appendQueryString = true,
bool stopProcessing = false,
int statusCode = StatusCodes.Status301MovedPermanently)
{ {
switch (actionType) switch (actionType)
{ {
case ActionType.None: case ActionType.None:
_action = new VoidAction(stopProcessing ? RuleResult.StopRules : RuleResult.Continue); _action = new VoidAction(stopProcessing ? RuleResult.StopRules : RuleResult.Continue);
break; break;
case ActionType.Rewrite: case ActionType.Rewrite:
if (appendQueryString) _action = new RewriteAction(stopProcessing ? RuleTermination.StopRules : RuleTermination.Continue,
{ url, clearQuery: !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; break;
case ActionType.Redirect: case ActionType.Redirect:
if (appendQueryString) _action = new RedirectAction(statusCode, url, appendQueryString);
{
_action = new RedirectAction(statusCode, url);
}
else
{
_action = new RedirectClearQueryAction(statusCode, url);
}
break; break;
case ActionType.AbortRequest: case ActionType.AbortRequest:
throw new NotImplementedException("Abort Requests are not supported"); throw new NotImplementedException("Abort Requests are not supported");
@ -85,14 +73,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
else else
{ {
var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout); var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout);
_initialMatch = new RegexMatch(regex, negate); _initialMatch = new RegexMatch(regex, negate);
} }
break; break;
} }
case PatternSyntax.WildCard: case PatternSyntax.WildCard:
throw new NotImplementedException("Wildcard syntax is not supported"); throw new NotImplementedException("Wildcard syntax is not supported");
case PatternSyntax.ExactMatch: case PatternSyntax.ExactMatch:
_initialMatch = new ExactMatch(ignoreCase, input, negate); _initialMatch = new ExactMatch(ignoreCase, input, negate);
break; break;
} }
} }
@ -118,27 +106,23 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
throw new FormatException("Match does not have an associated pattern attribute in condition"); throw new FormatException("Match does not have an associated pattern attribute in condition");
} }
Regex regex = null; var regex = new Regex(
if (ignoreCase) pattern,
{ ignoreCase ? RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase :
regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout); RegexOptions.CultureInvariant | RegexOptions.Compiled,
} RegexTimeout);
else
{ _conditions.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny });
regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexTimeout);
}
_conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate), OrNext = _matchAny});
break; break;
} }
case MatchType.IsDirectory: case MatchType.IsDirectory:
{ {
_conditions.ConditionList.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate), OrNext = _matchAny }); _conditions.Add(new Condition { Input = input, Match = new IsDirectoryMatch(negate), OrNext = _matchAny });
break; break;
} }
case MatchType.IsFile: case MatchType.IsFile:
{ {
_conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny }); _conditions.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny });
break; break;
} }
default: default:
@ -153,7 +137,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
throw new FormatException("Match does not have an associated pattern attribute in condition"); throw new FormatException("Match does not have an associated pattern attribute in condition");
} }
_conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny }); _conditions.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny });
break; break;
default: default:
throw new FormatException("Unrecognized pattern syntax"); throw new FormatException("Unrecognized pattern syntax");
@ -162,10 +146,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
public void AddUrlConditions(LogicalGrouping logicalGrouping, bool trackingAllCaptures) public void AddUrlConditions(LogicalGrouping logicalGrouping, bool trackingAllCaptures)
{ {
var conditions = new Conditions(); _conditions = new List<Condition>();
conditions.ConditionList = new List<Condition>();
_matchAny = logicalGrouping == LogicalGrouping.MatchAny; _matchAny = logicalGrouping == LogicalGrouping.MatchAny;
_conditions = conditions;
} }
} }
} }

View File

@ -1,28 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Logging;
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class UrlRewriteRule : Rule public class UrlRewriteRule : Rule
{ {
public string Name { get; set; } public string Name { get; }
public bool Enabled { get; set; } = true; public UrlMatch InitialMatch { get; }
public UrlMatch InitialMatch { get; set; } public IList<Condition> Conditions { get; }
public Conditions Conditions { get; set; } public UrlAction Action { get; }
public UrlAction Action { get; set; }
public UrlRewriteRule(string name,
UrlMatch initialMatch,
IList<Condition> conditions,
UrlAction action)
{
Name = name;
InitialMatch = initialMatch;
Conditions = conditions;
Action = action;
}
public override RuleResult ApplyRule(RewriteContext context) public override RuleResult ApplyRule(RewriteContext context)
{ {
if (!Enabled)
{
return RuleResult.Continue;
}
// Due to the path string always having a leading slash, // Due to the path string always having a leading slash,
// remove it from the path before regex comparison // remove it from the path before regex comparison
// TODO may need to check if there is a leading slash and remove conditionally
var path = context.HttpContext.Request.Path; var path = context.HttpContext.Request.Path;
MatchResults initMatchResults; MatchResults initMatchResults;
if (path == PathString.Empty) if (path == PathString.Empty)
@ -36,19 +42,22 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
if (!initMatchResults.Success) if (!initMatchResults.Success)
{ {
context.Logger?.UrlRewriteDidNotMatchRule(Name);
return RuleResult.Continue; return RuleResult.Continue;
} }
MatchResults condMatchRes = null; MatchResults condMatchRes = null;
if (Conditions != null) if (Conditions != null)
{ {
condMatchRes = Conditions.Evaluate(context, initMatchResults); condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchResults);
if (!condMatchRes.Success) if (!condMatchRes.Success)
{ {
context.Logger?.UrlRewriteDidNotMatchRule(Name);
return RuleResult.Continue; return RuleResult.Continue;
} }
} }
context.Logger?.UrlRewriteMatchedRule(Name);
// at this point we know the rule passed, evaluate the replacement. // at this point we know the rule passed, evaluate the replacement.
return Action.ApplyAction(context, initMatchResults, condMatchRes); return Action.ApplyAction(context, initMatchResults, condMatchRes);
} }

View File

@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Rewrite.Logging
{
internal static class RewriteMiddlewareLoggingExtensions
{
private static readonly Action<ILogger, Exception> _requestContinueResults;
private static readonly Action<ILogger, string, int, Exception> _requestResponseComplete;
private static readonly Action<ILogger, Exception> _requestStopRules;
private static readonly Action<ILogger, string, Exception> _urlRewriteDidNotMatchRule;
private static readonly Action<ILogger, string, Exception> _urlRewriteMatchedRule;
private static readonly Action<ILogger, Exception> _modRewriteDidNotMatchRule;
private static readonly Action<ILogger, Exception> _modRewriteMatchedRule;
static RewriteMiddlewareLoggingExtensions()
{
_requestContinueResults = LoggerMessage.Define(
LogLevel.Debug,
1,
"Request is continuing in applying rules.");
_requestResponseComplete = LoggerMessage.Define<string, int>(
LogLevel.Debug,
2,
"Request is done processing, Location header '{Location}' with status code '{StatusCode}'.");
_requestStopRules = LoggerMessage.Define(
LogLevel.Debug,
3,
"Request is done applying rules.");
_urlRewriteDidNotMatchRule = LoggerMessage.Define<string>(
LogLevel.Debug,
4,
"Request did not match current rule '{Name}'.");
_urlRewriteMatchedRule = LoggerMessage.Define<string>(
LogLevel.Debug,
5,
"Request matched current UrlRewriteRule '{Name}'.");
_modRewriteDidNotMatchRule = LoggerMessage.Define(
LogLevel.Debug,
6,
"Request matched current ModRewriteRule.");
_modRewriteMatchedRule = LoggerMessage.Define(
LogLevel.Debug,
7,
"Request matched current ModRewriteRule.");
}
public static void RewriteMiddlewareRequestContinueResults(this ILogger logger)
{
_requestContinueResults(logger, null);
}
public static void RewriteMiddlewareRequestResponseComplete(this ILogger logger, string location, int statusCode)
{
_requestResponseComplete(logger, location, statusCode, null);
}
public static void RewriteMiddlewareRequestStopRules(this ILogger logger)
{
_requestStopRules(logger, null);
}
public static void UrlRewriteDidNotMatchRule(this ILogger logger, string name)
{
_urlRewriteDidNotMatchRule(logger, name, null);
}
public static void UrlRewriteMatchedRule(this ILogger logger, string name)
{
_urlRewriteMatchedRule(logger, name, null);
}
public static void ModRewriteDidNotMatchRule(this ILogger logger)
{
_modRewriteDidNotMatchRule(logger, null);
}
public static void ModRewriteMatchedRule(this ILogger logger)
{
_modRewriteMatchedRule(logger, null);
}
}
}

View File

@ -8,24 +8,24 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
public static class ModRewriteExtensions public static class ModRewriteOptionsExtensions
{ {
/// <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.
/// </summary> /// </summary>
/// <param name="options">The UrlRewrite options.</param> /// <param name="options">The Rewrite options.</param>
/// <param name="hostingEnv"></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 hostingEnv, string filePath) public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath)
{ {
if (options == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
if (hostingEnv == null) if (hostingEnvironment == null)
{ {
throw new ArgumentNullException(nameof(hostingEnv)); throw new ArgumentNullException(nameof(hostingEnvironment));
} }
if (string.IsNullOrEmpty(filePath)) if (string.IsNullOrEmpty(filePath))
@ -33,18 +33,17 @@ namespace Microsoft.AspNetCore.Rewrite
throw new ArgumentException(nameof(filePath)); throw new ArgumentException(nameof(filePath));
} }
var path = Path.Combine(hostingEnv.ContentRootPath, filePath); var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath);
using (var stream = File.OpenRead(path)) using (var stream = File.OpenRead(path))
{ {
options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream))); return options.ImportFromModRewrite(new StreamReader(stream));
}; }
return options;
} }
/// <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.
/// </summary> /// </summary>
/// <param name="options">The UrlRewrite 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">Text reader containing a stream of mod_rewrite rules.</param>
public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, TextReader reader) public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, TextReader reader)
{ {
@ -52,37 +51,19 @@ namespace Microsoft.AspNetCore.Rewrite
{ {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
if (reader == null) if (reader == null)
{ {
throw new ArgumentNullException(nameof(reader)); throw new ArgumentNullException(nameof(reader));
} }
options.Rules.AddRange(new FileParser().Parse(reader)); var rules = new FileParser().Parse(reader);
foreach (var rule in rules)
{
options.Rules.Add(rule);
}
return options; return options;
} }
/// <summary>
/// Adds a mod_rewrite rule to the current rules.
/// </summary>
/// <param name="options">The UrlRewrite options.</param>
/// <param name="rule">The literal string of a mod_rewrite rule:
/// "RewriteRule Pattern Substitution [Flags]"</param>
public static RewriteOptions AddModRewriteRule(this RewriteOptions options, string rule)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
var builder = new RuleBuilder();
builder.AddRule(rule);
options.Rules.Add(builder.Build());
return options;
}
} }
} }

View File

@ -9,13 +9,13 @@ namespace Microsoft.AspNetCore.Builder
/// <summary> /// <summary>
/// Extension methods for the <see cref="RewriteMiddleware"/> /// Extension methods for the <see cref="RewriteMiddleware"/>
/// </summary> /// </summary>
public static class RewriteExtensions public static class RewriteBuilderExtensions
{ {
/// <summary> /// <summary>
/// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match. /// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match.
/// </summary> /// </summary>
/// <param name="app"></param> /// <param name="app"></param>
/// <param name="options">Options for urlrewrite.</param> /// <param name="options">Options for rewrite.</param>
/// <returns></returns> /// <returns></returns>
public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, RewriteOptions options) public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, RewriteOptions options)
{ {

View File

@ -4,16 +4,19 @@
using System.Text; using System.Text;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
/// <summary> /// <summary>
/// The UrlRewrite Context contains the HttpContext of the request and the file provider to check conditions. /// The UrlRewrite Context contains the HttpContext of the request, the file provider, and the logger.
/// There is also a shared string builder across the application of rules.
/// </summary> /// </summary>
public class RewriteContext public class RewriteContext
{ {
public HttpContext HttpContext { get; set; } public HttpContext HttpContext { get; set; }
public IFileProvider FileProvider { get; set; } public IFileProvider StaticFileProvider { get; set; }
public ILogger Logger { get; set; }
// PERF: share the same string builder per request // 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

@ -6,7 +6,10 @@ 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.Internal;
using Microsoft.AspNetCore.Rewrite.Logging;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
@ -20,14 +23,20 @@ namespace Microsoft.AspNetCore.Rewrite
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly RewriteOptions _options; private readonly RewriteOptions _options;
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
private readonly ILogger _logger;
/// <summary> /// <summary>
/// Creates a new instance of <see cref="RewriteMiddleware"/> /// Creates a new instance of <see cref="RewriteMiddleware"/>
/// </summary> /// </summary>
/// <param name="next">The delegate representing the next middleware in the request pipeline.</param> /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
/// <param name="hostingEnv">The Hosting Environment.</param> /// <param name="hostingEnvironment">The Hosting Environment.</param>
/// <param name="loggerFactory">The Logger Factory.</param>
/// <param name="options">The middleware options, containing the rules to apply.</param> /// <param name="options">The middleware options, containing the rules to apply.</param>
public RewriteMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, RewriteOptions options) public RewriteMiddleware(
RequestDelegate next,
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory,
RewriteOptions options)
{ {
if (next == null) if (next == null)
{ {
@ -41,7 +50,8 @@ namespace Microsoft.AspNetCore.Rewrite
_next = next; _next = next;
_options = options; _options = options;
_fileProvider = _options.FileProvider ?? hostingEnv.WebRootFileProvider; _fileProvider = _options.StaticFileProvider ?? hostingEnvironment.WebRootFileProvider;
_logger = loggerFactory.CreateLogger<RewriteMiddleware>();
} }
/// <summary> /// <summary>
@ -55,19 +65,27 @@ namespace Microsoft.AspNetCore.Rewrite
{ {
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
var urlContext = new RewriteContext { HttpContext = context, FileProvider = _fileProvider }; var urlContext = new RewriteContext {
HttpContext = context,
StaticFileProvider = _fileProvider,
Logger = _logger
};
foreach (var rule in _options.Rules) foreach (var rule in _options.Rules)
{ {
// Apply the rule
var result = rule.ApplyRule(urlContext); var result = rule.ApplyRule(urlContext);
switch (result.Result) switch (result.Result)
{ {
case RuleTerminiation.Continue: case RuleTermination.Continue:
// Explicitly show that we continue executing rules _logger.RewriteMiddlewareRequestContinueResults();
break; break;
case RuleTerminiation.ResponseComplete: case RuleTermination.ResponseComplete:
_logger.RewriteMiddlewareRequestResponseComplete(
urlContext.HttpContext.Response.Headers[HeaderNames.Location],
urlContext.HttpContext.Response.StatusCode);
return CompletedTask; return CompletedTask;
case RuleTerminiation.StopRules: case RuleTermination.StopRules:
_logger.RewriteMiddlewareRequestStopRules();
return _next(context); return _next(context);
default: default:
throw new ArgumentOutOfRangeException($"Invalid rule termination {result}"); throw new ArgumentOutOfRangeException($"Invalid rule termination {result}");

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
@ -12,10 +11,8 @@ namespace Microsoft.AspNetCore.Rewrite
/// </summary> /// </summary>
public class RewriteOptions public class RewriteOptions
{ {
/// <summary> // TODO doc comments
/// The ordered list of rules to apply to the context. public IList<Rule> Rules { get; } = new List<Rule>();
/// </summary> public IFileProvider StaticFileProvider { get; set; }
public List<Rule> Rules { get; set; } = new List<Rule>();
public IFileProvider FileProvider { get; set; }
} }
} }

View File

@ -0,0 +1,135 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.CodeRules;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite
{
/// <summary>
/// The builder to a list of rules for <see cref="RewriteOptions"/> and <see cref="RewriteMiddleware"/>
/// </summary>
public static class RewriteOptionsExtensions
{
/// <summary>
/// Adds a rule to the current rules.
/// </summary>
/// <param name="options">The UrlRewrite options.</param>
/// <param name="rule">A rule to be added to the current rules.</param>
/// <returns>The Rewrite options.</returns>
public static RewriteOptions Add(this RewriteOptions options, Rule rule)
{
options.Rules.Add(rule);
return options;
}
/// <summary>
/// Adds a rule to the current rules.
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="applyRule">A Func that checks and applies the rule.</param>
/// <returns></returns>
public static RewriteOptions Add(this RewriteOptions options, Func<RewriteContext, RuleResult> applyRule)
{
options.Rules.Add(new DelegateRule(applyRule));
return options;
}
/// <summary>
/// Rewrites the path if the regex matches the HttpContext's PathString
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="regex">The regex string to compare with.</param>
/// <param name="urlPattern">If the regex matches, what to replace HttpContext with.</param>
/// <returns>The Rewrite options.</returns>
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern)
{
return Rewrite(options, regex, urlPattern, stopProcessing: false);
}
/// <summary>
/// Rewrites the path if the regex matches the HttpContext's PathString
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="regex">The regex string to compare with.</param>
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
/// <param name="stopProcessing">If the regex matches, conditionally stop processing other rules.</param>
/// <returns>The Rewrite options.</returns>
public static RewriteOptions Rewrite(this RewriteOptions options, string regex, string urlPattern, bool stopProcessing)
{
var builder = new UrlRewriteRuleBuilder();
var pattern = new InputParser().ParseInputString(urlPattern);
builder.AddUrlMatch(regex);
builder.AddUrlAction(pattern, actionType: ActionType.Rewrite, stopProcessing: stopProcessing);
options.Rules.Add(builder.Build());
return options;
}
/// <summary>
/// Redirect the request if the regex matches the HttpContext's PathString
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="regex">The regex string to compare with.</param>
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
/// <returns>The Rewrite options.</returns>
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern)
{
return Redirect(options, regex, urlPattern, statusCode: 302);
}
/// <summary>
/// Redirect the request if the regex matches the HttpContext's PathString
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="regex">The regex string to compare with.</param>
/// <param name="urlPattern">If the regex matches, what to replace the uri with.</param>
/// <param name="statusCode">The status code to add to the response.</param>
/// <returns>The Rewrite options.</returns>
public static RewriteOptions Redirect(this RewriteOptions options, string regex, string urlPattern, int statusCode)
{
var builder = new UrlRewriteRuleBuilder();
var pattern = new InputParser().ParseInputString(urlPattern);
builder.AddUrlMatch(regex);
builder.AddUrlAction(pattern, actionType: ActionType.Redirect, stopProcessing: false);
options.Rules.Add(builder.Build());
return options;
}
// TODO 301 overload
/// <summary>
/// Redirect a request to https if the incoming request is http
/// </summary>
/// <param name="options">The Rewrite options.</param>
public static RewriteOptions RedirectToHttps(this RewriteOptions options)
{
return RedirectToHttps(options, statusCode: 302, sslPort: null);
}
/// <summary>
/// Redirect a request to https if the incoming request is http
/// </summary>
/// <param name="options">The Rewrite options.</param>
/// <param name="statusCode">The status code to add to the response.</param>
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode)
{
return RedirectToHttps(options, statusCode, sslPort: null);
}
/// <summary>
/// Redirect a request to https if the incoming request is http
/// </summary>
/// <param name="options">The Rewrite options.</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>
public static RewriteOptions RedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort)
{
options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort });
return options;
}
}
}

View File

@ -1,8 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite
{ {
// make this public and doc comements
public abstract class Rule public abstract class Rule
{ {
public abstract RuleResult ApplyRule(RewriteContext context); public abstract RuleResult ApplyRule(RewriteContext context);

View File

@ -1,14 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite
{ {
public class RuleResult public class RuleResult
{ {
public static RuleResult Continue = new RuleResult { Result = RuleTerminiation.Continue }; public static RuleResult Continue = new RuleResult { Result = RuleTermination.Continue };
public static RuleResult ResponseComplete = new RuleResult { Result = RuleTerminiation.ResponseComplete }; public static RuleResult ResponseComplete = new RuleResult { Result = RuleTermination.ResponseComplete };
public static RuleResult StopRules = new RuleResult { Result = RuleTerminiation.StopRules }; public static RuleResult StopRules = new RuleResult { Result = RuleTermination.StopRules };
public RuleTerminiation Result { get; set; } public RuleTermination Result { get; set; }
} }
} }

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal namespace Microsoft.AspNetCore.Rewrite
{ {
public enum RuleTerminiation public enum RuleTermination
{ {
Continue, Continue,
ResponseComplete, ResponseComplete,

View File

@ -8,24 +8,24 @@ using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite
{ {
public static class UrlRewriteExtensions public static class UrlRewriteOptionsExtensions
{ {
/// <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.
/// </summary> /// </summary>
/// <param name="options">The UrlRewrite options.</param> /// <param name="options">The UrlRewrite options.</param>
/// <param name="hostingEnv"></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 hostingEnv, string filePath) public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, IHostingEnvironment hostingEnvironment, string filePath)
{ {
if (options == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
if (hostingEnv == null) if (hostingEnvironment == null)
{ {
throw new ArgumentNullException(nameof(hostingEnv)); throw new ArgumentNullException(nameof(hostingEnvironment));
} }
if (string.IsNullOrEmpty(filePath)) if (string.IsNullOrEmpty(filePath))
@ -33,35 +33,36 @@ namespace Microsoft.AspNetCore.Rewrite
throw new ArgumentException(nameof(filePath)); throw new ArgumentException(nameof(filePath));
} }
var path = Path.Combine(hostingEnv.ContentRootPath, filePath); var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath);
using (var stream = File.OpenRead(path)) using (var stream = File.OpenRead(path))
{ {
options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream))); return ImportFromUrlRewrite(options, new StreamReader(stream));
}; }
return options;
} }
/// <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.
/// </summary> /// </summary>
/// <param name="options">The UrlRewrite options.</param> /// <param name="options">The UrlRewrite options.</param>
/// <param name="stream">The text reader stream.</param> /// <param name="reader">The text reader stream.</param>
public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, TextReader stream) public static RewriteOptions ImportFromUrlRewrite(this RewriteOptions options, TextReader reader)
{ {
if (options == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
if (stream == null) if (reader == null)
{ {
throw new ArgumentException(nameof(stream)); throw new ArgumentException(nameof(reader));
} }
using (stream) var rules = new UrlRewriteFileParser().Parse(reader);
foreach (var rule in rules)
{ {
options.Rules.AddRange(new FileParser().Parse(stream)); options.Rules.Add(rule);
}; }
return options; return options;
} }
} }

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().RewriteRule("(.*)", "http://example.com/{R:1}"); var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/{R: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().RedirectRule("(.*)","http://example.com/{R:1}", statusCode: 301); var options = new RewriteOptions().Redirect("(.*)","http://example.com/{R:1}", statusCode: 301);
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
@ -52,25 +52,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
Assert.Equal(response.Headers.Location.OriginalString, "http://example.com/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] [Fact]
public async Task CheckRedirectToHttps() public async Task CheckRedirectToHttps()
{ {

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().AddModRewriteRule("RewriteRule /hey/(.*) /$1 "); var options = new RewriteOptions().ImportFromModRewrite(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().AddModRewriteRule("RewriteRule /hey/(.*) /$1 [L]") var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]"))
.AddModRewriteRule("RewriteRule /hello /what"); .ImportFromModRewrite(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().AddModRewriteRule("RewriteRule /hey/(.*) /$1") var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1"))
.AddModRewriteRule("RewriteRule /hello /what"); .ImportFromModRewrite(new StringReader("RewriteRule /hello /what"));
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{ {
public class TestStringParserTests public class TestStringParserTests
{ {
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Rewrite
[InlineData(@"%a", "Cannot parse '%a' to integer at string index: '1'")] // invalid character after % [InlineData(@"%a", "Cannot parse '%a' to integer at string index: '1'")] // invalid character after %
[InlineData(@"$a", "Cannot parse '$a' to integer at string index: '1'")] // invalid character after $ [InlineData(@"$a", "Cannot parse '$a' to integer at string index: '1'")] // invalid character after $
[InlineData(@"%{asdf", "Missing close brace for parameter at string index: '6'")] // no closing } with characters [InlineData(@"%{asdf", "Missing close brace for parameter at string index: '6'")] // no closing } with characters
public void ConditionParser_Bad(string testString, string expected) public void ConditionParser_InvalidInput(string testString, string expected)
{ {
var ex = Assert.Throws<FormatException>(() => new TestStringParser().Parse(testString)); var ex = Assert.Throws<FormatException>(() => new TestStringParser().Parse(testString));
Assert.Equal(ex.Message, expected); Assert.Equal(ex.Message, expected);

View File

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class ConditionMatchSegmentTests
{
[Theory]
[InlineData(1, "foo")]
[InlineData(2, "bar")]
[InlineData(3, "baz")]
public void ConditionMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected)
{
// Arrange
var condMatch = CreateTestMatch();
var segment = new ConditionMatchSegment(index);
// Act
var results = segment.Evaluate(null, null, condMatch);
// Assert
Assert.Equal(expected, results);
}
private static MatchResults CreateTestMatch()
{
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults {BackReference = match.Groups, Success = match.Success};
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class DateTimeSegmentTests
{
[Theory]
[InlineData("TIME_YEAR")]
[InlineData("TIME_MON")]
[InlineData("TIME_DAY")]
[InlineData("TIME_HOUR")]
[InlineData("TIME_MIN")]
[InlineData("TIME_SEC")]
[InlineData("TIME_WDAY")]
[InlineData("TIME")]
public void DateTime_AssertDoesntThrowOnCheckOfSegment(string input)
{
// Arrange
var segment = new DateTimeSegment(input);
// Act
var results = segment.Evaluate(null, null, null);
// TODO testing dates is hard, could use moq
// currently just assert that the segment doesn't throw.
}
[Theory]
[InlineData("foo", "Unsupported segment: 'foo'")]
[InlineData("wow", "Unsupported segment: 'wow'")]
public void DateTime_AssertThrowsOnInvalidInput(string input, string expected)
{
// Act And Assert
var ex = Assert.Throws<FormatException>(() => new DateTimeSegment(input));
Assert.Equal(ex.Message, expected);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class HeaderSegmentTests
{
[Fact]
public void HeaderSegment_AssertGettingWithHeaderReturnsCorrectValue()
{
// Arrange
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Headers[HeaderNames.Location] = "foo";
var segment = new HeaderSegment(HeaderNames.Location);
// Act
var results = segment.Evaluate(context, null, null);
// Assert
Assert.Equal("foo", results);
}
[Fact]
public void HeaderSegment_AssertGettingANonExistantHeaderReturnsNull()
{
// Arrange
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
var segment = new HeaderSegment(HeaderNames.Location);
// Act
var results = segment.Evaluate(context, null, null);
// Assert
Assert.Null(results);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class IsHttpsModSegmentTests
{
[Theory]
[InlineData("http", "off")]
[InlineData("https", "on")]
public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected)
{
// Arrange
var segement = new IsHttpsModSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Scheme = input;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal(expected, results);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class IsHttpsSegmentTests
{
[Theory]
[InlineData("http", "OFF")]
[InlineData("https", "ON")]
public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected)
{
// Arrange
var segement = new IsHttpsSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Scheme = input;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal(expected, results);
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class IsIPV6SegmentTests
{
[Fact]
public void IsIPv6_AssertNullRemoteIpAddressReportsCorrectValue()
{
// Arrange
var segement = new IsIPV6Segment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemoteIpAddress = null;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("off", results);
}
[Fact]
public void IsIPv6_AssertCorrectBehaviorWhenIPv6IsUsed()
{
// Arrange
var segement = new IsIPV6Segment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("on", results);
}
[Fact]
public void IsIPv6_AssertCorrectBehaviorWhenIPv4IsUsed()
{
// Arrange
var segement = new IsIPV6Segment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("off", results);
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class LiteralSegmentTests
{
[Fact]
public void LiteralSegment_AssertSegmentIsCorrect()
{
// Arrange
var segement = new LiteralSegment("foo");
// Act
var results = segement.Evaluate(null, null, null);
// Assert
Assert.Equal("foo", results);
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class LocalAddressSegmentTests
{
[Fact]
public void LocalAddress_AssertSegmentIsCorrect()
{
// Arrange
var segement = new LocalAddressSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.LocalIpAddress = IPAddress.Parse("20.30.40.50");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("20.30.40.50", results);
}
[Fact]
public void LocalAddress_AssertNullLocalIpAddressReturnsNull()
{
var segement = new LocalAddressSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.LocalIpAddress = null;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Null( results);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class LocalPortSegmentTests
{
[Fact]
public void LocalPortSegment_AssertSegmentIsCorrect()
{
// Arrange
var segement = new LocalPortSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.LocalPort = 800;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("800", results);
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class QueryStringSegmentTests
{
[Fact]
public void QueryString_AssertSegmentIsCorrect()
{
// Arrange
var segement = new QueryStringSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.QueryString = new QueryString("?hey=1");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("?hey=1", results);
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class RemoteAddressSegmentTests
{
[Fact]
public void RemoteAddress_AssertSegmentIsCorrect()
{
// Arrange
var segement = new RemoteAddressSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("20.30.40.50", results);
}
[Fact]
public void RemoteAddress_AssertNullLocalIpAddressReturnsNull()
{
var segement = new RemoteAddressSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemoteIpAddress = null;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Null(results);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class RemotePortSegmentTests
{
[Fact]
public void RemotePort_AssertSegmentIsCorrect()
{
// Arrange
var segement = new RemotePortSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Connection.RemotePort = 800;
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("800", results);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class RequestFilenameSegmentTests
{
[Fact]
public void RequestFilename_AssertSegmentIsCorrect()
{
// Arrange
var segement = new RequestFileNameSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Path = new PathString("/foo/bar");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("/foo/bar", results);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class RequestMethodSegmentTests
{
[Fact]
public void RequestMethod_AssertSegmentIsCorrect()
{
// Arrange
var segement = new RequestMethodSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Method = "GET";
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("GET", results);
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class RuleMatchSegmentTests
{
[Theory]
[InlineData(1, "foo")]
[InlineData(2, "bar")]
[InlineData(3, "baz")]
public void RuleMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected)
{
// Arrange
var ruleMatch = CreateTestMatch();
var segment = new RuleMatchSegment(index);
// Act
var results = segment.Evaluate(null, ruleMatch, null);
// Assert
Assert.Equal(expected, results);
}
private static MatchResults CreateTestMatch()
{
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
return new MatchResults { BackReference = match.Groups, Success = match.Success };
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class SchemeSegmentTests
{
[Fact]
public void SchemeSegment_AssertSegmentIsCorrect()
{
// Arrange
var segement = new SchemeSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Scheme = "http";
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("http", results);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class ServerProtocolSegmentTests
{
[Fact]
public void ServerProtocol_AssertSegmentIsCorrect()
{
// Arrange
var segement = new ServerProtocolSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Features.Set<IHttpRequestFeature>(new HttpRequestFeature { Protocol = "http" });
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("http", results);
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class ToLowerSegmentTests
{
[Theory]
[InlineData("Hello", "hello")]
[InlineData("WHAT", "what")]
[InlineData("hey", "hey")]
public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected)
{
// Arrange
var pattern = new Pattern(new List<PatternSegment>());
pattern.PatternSegments.Add(new LiteralSegment(input));
var segement = new ToLowerSegment(pattern);
var context = new RewriteContext();
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal(expected, results);
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class UrlEncodeSegmentTests
{
[Theory]
[InlineData(" ", "%20")]
[InlineData("x&y", "x%26y")]
[InlineData("hey", "hey")]
public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected)
{
// Arrange
var pattern = new Pattern(new List<PatternSegment>());
pattern.PatternSegments.Add(new LiteralSegment(input));
var segement = new UrlEncodeSegment(pattern);
var context = new RewriteContext();
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal(expected, results);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments
{
public class UrlSegmentTests
{
[Fact]
public void LocalPortSegment_AssertSegmentIsCorrect()
{
// Arrange
var segement = new UrlSegment();
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
context.HttpContext.Request.Path = new PathString("/foo/bar");
// Act
var results = segement.Evaluate(context, null, null);
// Assert
Assert.Equal("/foo/bar", results);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions
{
public class ForbiddenActionTests
{
[Fact]
public void Forbidden_Verify403IsInStatusCode()
{
// Arrange
var context = new RewriteContext {HttpContext = new DefaultHttpContext()};
var action = new ForbiddenAction();
// Act
var results = action.ApplyAction(context, null, null);
// Assert
Assert.Equal(results.Result, RuleTermination.ResponseComplete);
Assert.Equal(context.HttpContext.Response.StatusCode, StatusCodes.Status403Forbidden);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions
{
public class GoneActionTests
{
[Fact]
public void Gone_Verify410IsInStatusCode()
{
// Arrange
var context = new RewriteContext { HttpContext = new DefaultHttpContext() };
var action = new GoneAction();
// Act
var results = action.ApplyAction(context, null, null);
// Assert
Assert.Equal(results.Result, RuleTermination.ResponseComplete);
Assert.Equal(context.HttpContext.Response.StatusCode, StatusCodes.Status410Gone);
}
}
}

View File

@ -29,13 +29,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var expected = new List<UrlRewriteRule>(); var expected = new List<UrlRewriteRule>();
expected.Add(CreateTestRule(new List<Condition>(), expected.Add(CreateTestRule(new List<Condition>(),
Url: "^article/([0-9]+)/([_0-9a-z-]+)", url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx", name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite, actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}")); pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act // act
var res = new FileParser().Parse(new StringReader(xml)); var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert // assert
AssertUrlRewriteRuleEquality(res, expected); AssertUrlRewriteRuleEquality(res, expected);
@ -66,13 +66,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var expected = new List<UrlRewriteRule>(); var expected = new List<UrlRewriteRule>();
expected.Add(CreateTestRule(condList, expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)", url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx", name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite, actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}")); pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act // act
var res = new FileParser().Parse(new StringReader(xml)); var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert // assert
AssertUrlRewriteRuleEquality(res, expected); AssertUrlRewriteRuleEquality(res, expected);
@ -110,18 +110,18 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var expected = new List<UrlRewriteRule>(); var expected = new List<UrlRewriteRule>();
expected.Add(CreateTestRule(condList, expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)", url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx", name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite, actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}")); pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
expected.Add(CreateTestRule(condList, expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)", url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to another article.aspx", name: "Rewrite to another article.aspx",
actionType: ActionType.Rewrite, actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}")); pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act // act
var res = new FileParser().Parse(new StringReader(xml)); var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert // assert
AssertUrlRewriteRuleEquality(res, expected); AssertUrlRewriteRuleEquality(res, expected);
@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
bool enabled = true, bool enabled = true,
PatternSyntax patternSyntax = PatternSyntax.ECMAScript, PatternSyntax patternSyntax = PatternSyntax.ECMAScript,
bool stopProcessing = false, bool stopProcessing = false,
string Url = "", string url = "",
bool ignoreCase = true, bool ignoreCase = true,
bool negate = false, bool negate = false,
ActionType actionType = ActionType.None, ActionType actionType = ActionType.None,
@ -145,22 +145,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
RedirectType redirectType = RedirectType.Permanent RedirectType redirectType = RedirectType.Permanent
) )
{ {
return new UrlRewriteRule return new UrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
{ new RewriteAction(RuleTermination.Continue, new InputParser().ParseInputString(url), clearQuery: false));
Action = new RewriteAction(RuleTerminiation.Continue, new InputParser().ParseInputString(Url), clearQuery: false),
Name = name,
Enabled = enabled,
InitialMatch = new RegexMatch(new Regex("^OFF$"), false)
{
},
Conditions = new Conditions
{
ConditionList = conditions
}
};
} }
private void AssertUrlRewriteRuleEquality(List<UrlRewriteRule> actual, List<UrlRewriteRule> expected) // TODO make rules comparable?
private void AssertUrlRewriteRuleEquality(IList<UrlRewriteRule> actual, IList<UrlRewriteRule> expected)
{ {
Assert.Equal(actual.Count, expected.Count); Assert.Equal(actual.Count, expected.Count);
for (var i = 0; i < actual.Count; i++) for (var i = 0; i < actual.Count; i++)
@ -169,23 +159,22 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var r2 = expected[i]; var r2 = expected[i];
Assert.Equal(r1.Name, r2.Name); Assert.Equal(r1.Name, r2.Name);
Assert.Equal(r1.Enabled, r2.Enabled);
if (r1.Conditions == null) if (r1.Conditions == null)
{ {
Assert.Equal(r2.Conditions.ConditionList.Count, 0); Assert.Equal(r2.Conditions.Count, 0);
} }
else if (r2.Conditions == null) else if (r2.Conditions == null)
{ {
Assert.Equal(r1.Conditions.ConditionList.Count, 0); Assert.Equal(r1.Conditions.Count, 0);
} }
else else
{ {
Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count); Assert.Equal(r1.Conditions.Count, r2.Conditions.Count);
for (var j = 0; j < r1.Conditions.ConditionList.Count; j++) for (var j = 0; j < r1.Conditions.Count; j++)
{ {
var c1 = r1.Conditions.ConditionList[j]; var c1 = r1.Conditions[j];
var c2 = r2.Conditions.ConditionList[j]; var c2 = r2.Conditions[j];
Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count); Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count);
} }
} }

View File

@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void ThrowFormatExceptionWithCorrectMessage(string input, string expected) public void ThrowFormatExceptionWithCorrectMessage(string input, string expected)
{ {
// Arrange, Act, Assert // Arrange, Act, Assert
var ex = Assert.Throws<FormatException>(() => new FileParser().Parse(new StringReader(input))); var ex = Assert.Throws<FormatException>(() => new UrlRewriteFileParser().Parse(new StringReader(input)));
Assert.Equal(ex.Message, expected); Assert.Equal(ex.Message, expected);
} }
} }

View File

@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
{ {
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
return new RewriteContext { HttpContext = context, FileProvider = null }; return new RewriteContext { HttpContext = context, StaticFileProvider = null };
} }
private MatchResults CreateTestRuleMatch() private MatchResults CreateTestRuleMatch()

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var response = await server.CreateClient().GetAsync("article/10/hey"); var response = await server.CreateClient().GetAsync("article/10/hey");
Assert.Equal(response.Headers.Location.OriginalString, "article.aspx?id=10&title=hey"); Assert.Equal(response.Headers.Location.OriginalString, "/article.aspx?id=10&title=hey");
} }
[Fact] [Fact]
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var response = await server.CreateClient().GetAsync("HElLo"); var response = await server.CreateClient().GetAsync("HElLo");
Assert.Equal(response.Headers.Location.OriginalString, "hello"); Assert.Equal(response.Headers.Location.OriginalString, "/hello");
} }
[Fact] [Fact]
@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var response = await server.CreateClient().GetAsync("hey/hello/"); var response = await server.CreateClient().GetAsync("hey/hello/");
Assert.Equal(response.Headers.Location.OriginalString, "hey/hello"); Assert.Equal(response.Headers.Location.OriginalString, "/hey/hello");
} }
[Fact] [Fact]
@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var response = await server.CreateClient().GetAsync("hey/hello"); var response = await server.CreateClient().GetAsync("hey/hello");
Assert.Equal(response.Headers.Location.OriginalString, "hey/hello/"); Assert.Equal(response.Headers.Location.OriginalString, "/hey/hello/");
} }
[Fact] [Fact]

View File

@ -1,15 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
using Microsoft.AspNetCore.TestHost;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
@ -28,11 +23,11 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
</rule> </rule>
</rules> </rules>
</rewrite>"); </rewrite>");
var rules = new FileParser().Parse(xml); var rules = new UrlRewriteFileParser().Parse(xml);
Assert.Equal(rules.Count, 1); Assert.Equal(rules.Count, 1);
var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext {HttpContext = new DefaultHttpContext()}); var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext {HttpContext = new DefaultHttpContext()});
Assert.Equal(ruleResults.Result, RuleTerminiation.StopRules); Assert.Equal(ruleResults.Result, RuleTermination.StopRules);
} }
[Fact] [Fact]
@ -46,11 +41,11 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
</rule> </rule>
</rules> </rules>
</rewrite>"); </rewrite>");
var rules = new FileParser().Parse(xml); var rules = new UrlRewriteFileParser().Parse(xml);
Assert.Equal(rules.Count, 1); Assert.Equal(rules.Count, 1);
var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext { HttpContext = new DefaultHttpContext() }); var ruleResults = rules.FirstOrDefault().ApplyRule(new RewriteContext { HttpContext = new DefaultHttpContext() });
Assert.Equal(ruleResults.Result, RuleTerminiation.Continue); Assert.Equal(ruleResults.Result, RuleTermination.Continue);
} }
} }
} }