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
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://www.example.com$1 [L]
# Rewrite path with additional sub directory
RewriteRule ^(.*)$ /foo$1
# Forbid a certain url from being accessed
RewriteRule /bar - [F]
RewriteRule /bar/ - [F]
# Rewrite path with additional sub directory
RewriteCond %{QUERY_STRING} id=20
RewriteRule ^(.*)$ - [G]

View File

@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace RewriteSample
{
@ -26,16 +25,11 @@ namespace RewriteSample
// (ex StringReplace) that are easy to implement in code, they can do so by calling
// AddFunctionalRule(Func);
// TODO make this startup do something useful.
app.UseRewriter(new RewriteOptions()
.Rewrite(@"foo/(\d+)", "foo?id={R:1}")
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
.ImportFromModRewrite(hostingEnv, "Rewrite.txt")
.RedirectToHttps(StatusCodes.Status307TemporaryRedirect)
.RewriteRule("/foo/(.*)/bar", "{R:1}/bar")
.AddRule(ctx =>
{
ctx.HttpContext.Request.Path = "/index";
return RuleResult.Continue;
}));
.ImportFromModRewrite(hostingEnv, "Rewrite.txt"));
app.Run(context => context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}"));
}

View File

@ -1,8 +1,8 @@
<rewrite>
<rules>
<rule name="Example" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://example.com/{R:1}" />
<rule name="" stopProcessing="true">
<match url="foo/bar" />
<action type="Redirect" redirectType="Found" url="foo/" />
</rule>
</rules>
</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
{
public class FunctionalRule : Rule
public class DelegateRule : Rule
{
public Func<RewriteContext, RuleResult> OnApplyRule { get; set; }
public override RuleResult ApplyRule(RewriteContext context) => OnApplyRule(context);
private readonly Func<RewriteContext, RuleResult> _onApplyRule;
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
{
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;
var orSucceeded = false;
foreach (var condition in ConditionList)
foreach (var condition in conditions)
{
if (orSucceeded && condition.OrNext)
{
@ -30,7 +29,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
if (condition.OrNext)
{
orSucceeded = prevCond.Success;
continue;
}
else if (!prevCond.Success)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// </summary>
/// <param name="rule">The rule to tokenize.</param>
/// <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 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.
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Logging;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public class ModRewriteRule : Rule
{
public UrlMatch InitialMatch { get; set; }
public Conditions Conditions { get; set; }
public UrlAction Action { get; set; }
public List<PreAction> PreActions { get; set; }
public UrlMatch InitialMatch { get; }
public IList<Condition> Conditions { get; }
public UrlAction Action { get; }
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;
InitialMatch = initialMatch;
@ -28,21 +28,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
if (!initMatchRes.Success)
{
context.Logger?.ModRewriteDidNotMatchRule();
return RuleResult.Continue;
}
MatchResults condMatchRes = null;
if (Conditions != null)
{
condMatchRes = Conditions.Evaluate(context, initMatchRes);
condMatchRes = ConditionHelper.Evaluate(Conditions, context, initMatchRes);
if (!condMatchRes.Success)
{
context.Logger?.ModRewriteDidNotMatchRule();
return RuleResult.Continue;
}
}
// At this point, we know our rule passed, first apply pre conditions,
// which can modify things like the cookie or env, and then apply the action
context.Logger?.ModRewriteMatchedRule();
foreach (var preAction in PreActions)
{
preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return new Pattern(results);
}
private static void ParseParameter(ParserContext context, List<PatternSegment> results)
private static void ParseParameter(ParserContext context, IList<PatternSegment> results)
{
context.Mark();
// Four main cases:
@ -82,7 +81,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
else if (context.Current == Colon)
{
parameter = context.Capture();
// Only 5 strings to expect here. Case sensitive.
switch (parameter)
{
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
return index;
}
private static void ParseLiteral(ParserContext context, List<PatternSegment> results)
private static void ParseLiteral(ParserContext context, IList<PatternSegment> results)
{
context.Mark();
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.
using System;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Microsoft.Net.Http.Headers;
@ -12,7 +11,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{
public static PatternSegment FindServerVariable(string serverVariable)
{
switch(serverVariable)
switch (serverVariable)
{
// TODO Add all server variables here.
case "ALL_RAW":

View File

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

View File

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

View File

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

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
{
public static class ModRewriteExtensions
public static class ModRewriteOptionsExtensions
{
/// <summary>
/// Imports rules from a mod_rewrite file and adds the rules to current rules.
/// </summary>
/// <param name="options">The UrlRewrite options.</param>
/// <param name="hostingEnv"></param>
/// <param name="options">The Rewrite options.</param>
/// <param name="hostingEnvironment">The Hosting Environment</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)
{
throw new ArgumentNullException(nameof(options));
}
if (hostingEnv == null)
if (hostingEnvironment == null)
{
throw new ArgumentNullException(nameof(hostingEnv));
throw new ArgumentNullException(nameof(hostingEnvironment));
}
if (string.IsNullOrEmpty(filePath))
@ -33,18 +33,17 @@ namespace Microsoft.AspNetCore.Rewrite
throw new ArgumentException(nameof(filePath));
}
var path = Path.Combine(hostingEnv.ContentRootPath, filePath);
var path = Path.Combine(hostingEnvironment.ContentRootPath, filePath);
using (var stream = File.OpenRead(path))
{
options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream)));
};
return options;
return options.ImportFromModRewrite(new StreamReader(stream));
}
}
/// <summary>
/// Imports rules from a mod_rewrite file and adds the rules to current rules.
/// </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>
public static RewriteOptions ImportFromModRewrite(this RewriteOptions options, TextReader reader)
{
@ -52,37 +51,19 @@ namespace Microsoft.AspNetCore.Rewrite
{
throw new ArgumentNullException(nameof(options));
}
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
options.Rules.AddRange(new FileParser().Parse(reader));
var rules = new FileParser().Parse(reader);
foreach (var rule in rules)
{
options.Rules.Add(rule);
}
return options;
}
/// <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>
/// Extension methods for the <see cref="RewriteMiddleware"/>
/// </summary>
public static class RewriteExtensions
public static class RewriteBuilderExtensions
{
/// <summary>
/// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match.
/// </summary>
/// <param name="app"></param>
/// <param name="options">Options for urlrewrite.</param>
/// <param name="options">Options for rewrite.</param>
/// <returns></returns>
public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, RewriteOptions options)
{

View File

@ -4,16 +4,19 @@
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Rewrite
{
/// <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>
public class RewriteContext
{
public HttpContext HttpContext { get; set; }
public IFileProvider FileProvider { get; set; }
public IFileProvider StaticFileProvider { get; set; }
public ILogger Logger { get; set; }
// PERF: share the same string builder per request
internal StringBuilder Builder { get; set; } = new StringBuilder(64);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
[Fact]
public async Task CheckRewritePath()
{
var options = new RewriteOptions().RewriteRule("(.*)", "http://example.com/{R:1}");
var options = new RewriteOptions().Rewrite("(.*)", "http://example.com/{R:1}");
var builder = new WebHostBuilder()
.Configure(app =>
{
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
[Fact]
public async Task CheckRedirectPath()
{
var options = new RewriteOptions().RedirectRule("(.*)","http://example.com/{R:1}", statusCode: 301);
var options = new RewriteOptions().Redirect("(.*)","http://example.com/{R:1}", statusCode: 301);
var builder = new WebHostBuilder()
.Configure(app =>
{
@ -52,25 +52,6 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules
Assert.Equal(response.Headers.Location.OriginalString, "http://example.com/foo");
}
[Fact]
public async Task CheckRewriteToHttps()
{
var options = new RewriteOptions().RewriteToHttps();
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRewriter(options);
app.UseRewriter(options);
app.Run(context => context.Response.WriteAsync(
context.Request.Scheme));
});
var server = new TestServer(builder);
var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com"));
Assert.Equal(response, "https");
}
[Fact]
public async Task CheckRedirectToHttps()
{

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact]
public async Task Invoke_RewritePathWhenMatching()
{
var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 ");
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 "));
var builder = new WebHostBuilder()
.Configure(app =>
{
@ -34,8 +34,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact]
public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule()
{
var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 [L]")
.AddModRewriteRule("RewriteRule /hello /what");
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]"))
.ImportFromModRewrite(new StringReader("RewriteRule /hello /what"));
var builder = new WebHostBuilder()
.Configure(app =>
{
@ -52,8 +52,8 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact]
public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule()
{
var options = new RewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1")
.AddModRewriteRule("RewriteRule /hello /what");
var options = new RewriteOptions().ImportFromModRewrite(new StringReader("RewriteRule /hey/(.*) /$1"))
.ImportFromModRewrite(new StringReader("RewriteRule /hello /what"));
var builder = new WebHostBuilder()
.Configure(app =>
{

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite
namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{
public class TestStringParserTests
{
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Rewrite
[InlineData(@"%a", "Cannot parse '%a' to integer at string index: '1'")] // invalid character after %
[InlineData(@"$a", "Cannot parse '$a' to integer at string index: '1'")] // invalid character after $
[InlineData(@"%{asdf", "Missing close brace for parameter at string index: '6'")] // no closing } with characters
public void ConditionParser_Bad(string testString, string expected)
public void ConditionParser_InvalidInput(string testString, string expected)
{
var ex = Assert.Throws<FormatException>(() => new TestStringParser().Parse(testString));
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>();
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",
actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act
var res = new FileParser().Parse(new StringReader(xml));
var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert
AssertUrlRewriteRuleEquality(res, expected);
@ -66,13 +66,13 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var expected = new List<UrlRewriteRule>();
expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act
var res = new FileParser().Parse(new StringReader(xml));
var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert
AssertUrlRewriteRuleEquality(res, expected);
@ -110,18 +110,18 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var expected = new List<UrlRewriteRule>();
expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to article.aspx",
actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
expected.Add(CreateTestRule(condList,
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
url: "^article/([0-9]+)/([_0-9a-z-]+)",
name: "Rewrite to another article.aspx",
actionType: ActionType.Rewrite,
pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
// act
var res = new FileParser().Parse(new StringReader(xml));
var res = new UrlRewriteFileParser().Parse(new StringReader(xml));
// assert
AssertUrlRewriteRuleEquality(res, expected);
@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
bool enabled = true,
PatternSyntax patternSyntax = PatternSyntax.ECMAScript,
bool stopProcessing = false,
string Url = "",
string url = "",
bool ignoreCase = true,
bool negate = false,
ActionType actionType = ActionType.None,
@ -145,22 +145,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
RedirectType redirectType = RedirectType.Permanent
)
{
return new UrlRewriteRule
{
Action = new RewriteAction(RuleTerminiation.Continue, new InputParser().ParseInputString(Url), clearQuery: false),
Name = name,
Enabled = enabled,
InitialMatch = new RegexMatch(new Regex("^OFF$"), false)
{
},
Conditions = new Conditions
{
ConditionList = conditions
}
};
return new UrlRewriteRule(name, new RegexMatch(new Regex("^OFF$"), false), conditions,
new RewriteAction(RuleTermination.Continue, new InputParser().ParseInputString(url), clearQuery: false));
}
private void AssertUrlRewriteRuleEquality(List<UrlRewriteRule> actual, List<UrlRewriteRule> expected)
// TODO make rules comparable?
private void AssertUrlRewriteRuleEquality(IList<UrlRewriteRule> actual, IList<UrlRewriteRule> expected)
{
Assert.Equal(actual.Count, expected.Count);
for (var i = 0; i < actual.Count; i++)
@ -169,23 +159,22 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
var r2 = expected[i];
Assert.Equal(r1.Name, r2.Name);
Assert.Equal(r1.Enabled, r2.Enabled);
if (r1.Conditions == null)
{
Assert.Equal(r2.Conditions.ConditionList.Count, 0);
Assert.Equal(r2.Conditions.Count, 0);
}
else if (r2.Conditions == null)
{
Assert.Equal(r1.Conditions.ConditionList.Count, 0);
Assert.Equal(r1.Conditions.Count, 0);
}
else
{
Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count);
for (var j = 0; j < r1.Conditions.ConditionList.Count; j++)
Assert.Equal(r1.Conditions.Count, r2.Conditions.Count);
for (var j = 0; j < r1.Conditions.Count; j++)
{
var c1 = r1.Conditions.ConditionList[j];
var c2 = r2.Conditions.ConditionList[j];
var c1 = r1.Conditions[j];
var c2 = r2.Conditions[j];
Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count);
}
}

View File

@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
public void ThrowFormatExceptionWithCorrectMessage(string input, string expected)
{
// 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);
}
}

View File

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

View File

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

View File

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