Mod_rewrite refactor with cleanup

This commit is contained in:
Justin Kotalik 2016-08-10 14:56:42 -07:00
parent 903949cc74
commit 7a56073835
106 changed files with 1421 additions and 1835 deletions

View File

@ -1,7 +1,6 @@
# Ensure Https # Ensure Https
RewriteCond %{HTTPS} off RewriteCond %{HTTPS} off
# U is a new flag to represent full URL rewrites RewriteRule ^(.*)$ https://www.example.com$1 [L]
RewriteRule ^(.*)$ https://www.example.com$1 [L,U]
# Rewrite path with additional sub directory # Rewrite path with additional sub directory
RewriteRule ^(.*)$ /foo$1 RewriteRule ^(.*)$ /foo$1

View File

@ -27,18 +27,17 @@ namespace RewriteSample
// 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()
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml") .ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
.ImportFromModRewrite(hostingEnv, "Rewrite.txt") .ImportFromModRewrite(hostingEnv, "Rewrite.txt")
.RedirectToHttps(StatusCodes.Status307TemporaryRedirect) .RedirectToHttps(StatusCodes.Status307TemporaryRedirect)
.RewriteRule("/foo/(.*)/bar", "{R:1}/bar") .RewriteRule("/foo/(.*)/bar", "{R:1}/bar")
.AddRule(ctx => .AddRule(ctx =>
{ {
ctx.HttpContext.Request.Path = "/index"; ctx.HttpContext.Request.Path = "/index";
return RuleResult.Continue; return RuleResult.Continue;
})); }));
app.Run(context => context.Response.WriteAsync(context.Request.Path)); app.Run(context => context.Response.WriteAsync(context.Request.Path));
} }
public static void Main(string[] args) public static void Main(string[] args)

View File

@ -1,12 +1,8 @@
<rewrite> <rewrite>
<rules> <rules>
<rule name="Query String Rewrite"> <rule name="Example" stopProcessing="true">
<match url="page\.asp$" /> <match url="(.*)" />
<conditions> <action type="Rewrite" url="http://example.com/{R:1}" />
<add input="{QUERY_STRING}" pattern="p1=(\d+)" />
<add input="##{C:1}##_{QUERY_STRING}" pattern="##([^#]+)##_.*p2=(\d+)" />
</conditions>
<action type="rewrite" url="newpage.aspx?param1={C:1}&amp;param2={C:2}" appendQueryString="false"/>
</rule> </rule>
</rules> </rules>
</rewrite> </rewrite>

View File

@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
public int StatusCode { get; set; } public int StatusCode { get; set; }
public override RuleResult ApplyRule(RewriteContext context) public override RuleResult ApplyRule(RewriteContext context)
{ {
// TODO this only does http to https, add more features in the future.
if (!context.HttpContext.Request.IsHttps) if (!context.HttpContext.Request.IsHttps)
{ {
var host = context.HttpContext.Request.Host; var host = context.HttpContext.Request.Host;

View File

@ -1,4 +1,6 @@
 // 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
@ -10,7 +12,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
public int? SSLPort { get; set; } public int? SSLPort { get; set; }
public override RuleResult ApplyRule(RewriteContext context) public override RuleResult ApplyRule(RewriteContext context)
{ {
// TODO this only does http to https, add more features in the future.
if (!context.HttpContext.Request.IsHttps) if (!context.HttpContext.Request.IsHttps)
{ {
var host = context.HttpContext.Request.Host; var host = context.HttpContext.Request.Host;
@ -27,14 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.CodeRules
context.HttpContext.Request.Scheme = "https"; context.HttpContext.Request.Scheme = "https";
context.HttpContext.Request.Host = host; context.HttpContext.Request.Host = host;
if (stopProcessing) return stopProcessing ? RuleResult.StopRules: RuleResult.Continue;
{
return RuleResult.StopRules;
}
else
{
return RuleResult.Continue;
}
} }
return RuleResult.Continue; return RuleResult.Continue;
} }

View File

@ -1,15 +1,17 @@
// 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.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class Condition public class Condition
{ {
public Pattern Input { get; set; } public Pattern Input { get; set; }
public UrlMatch Match { get; set; } public UrlMatch Match { get; set; }
public bool OrNext { get; set; }
public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
var pattern = Input.Evaluate(context.HttpContext, ruleMatch, condMatch); var pattern = Input.Evaluate(context, ruleMatch, condMatch);
return Match.Evaluate(pattern, context); return Match.Evaluate(pattern, context);
} }
} }

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 System.Collections.Generic;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public class Conditions
{
public List<Condition> ConditionList { get; set; } = new List<Condition>();
public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch)
{
MatchResults prevCond = null;
var orSucceeded = false;
foreach (var condition in ConditionList)
{
if (orSucceeded && condition.OrNext)
{
continue;
}
else if (orSucceeded)
{
orSucceeded = false;
continue;
}
prevCond = condition.Evaluate(context, ruleMatch, prevCond);
if (condition.OrNext)
{
orSucceeded = prevCond.Success;
continue;
}
else if (!prevCond.Success)
{
return prevCond;
}
}
return prevCond;
}
}
}

View File

@ -3,10 +3,13 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class MatchResults public class MatchResults
{ {
public static readonly MatchResults EmptySuccess = new MatchResults { BackReference = null, Success = true };
public static readonly MatchResults EmptyFailure = new MatchResults { BackReference = null, Success = false };
public GroupCollection BackReference { get; set; } public GroupCollection BackReference { get; set; }
public bool Success { get; set; } public bool Success { get; set; }
} }

View File

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class Condition
{
public Pattern TestStringSegments { get; }
public ConditionExpression ConditionExpression { get; }
public ConditionFlags Flags { get; }
public Condition(Pattern testStringSegments, ConditionExpression conditionRegex, ConditionFlags flags)
{
TestStringSegments = testStringSegments;
ConditionExpression = conditionRegex;
Flags = flags;
}
}
}

View File

@ -1,94 +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;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class ConditionBuilder
{
private Pattern _testString;
private ParsedModRewriteExpression _pce;
private ConditionFlags _flags;
public ConditionBuilder(string conditionString)
{
var tokens = Tokenizer.Tokenize(conditionString);
if (tokens.Count == 3)
{
CreateCondition(tokens[1], tokens[2], flagsString: null);
}
else if (tokens.Count == 4)
{
CreateCondition(tokens[1], tokens[2], tokens[3]);
}
else
{
throw new FormatException("Invalid number of tokens.");
}
}
public ConditionBuilder(string testString, string condition)
{
CreateCondition(testString, condition, flagsString: null);
}
public ConditionBuilder(string testString, string condition, string flags)
{
CreateCondition(testString, condition, flags);
}
public Condition Build()
{
var expression = ExpressionCreator.CreateConditionExpression(_pce, _flags);
return new Condition(_testString, expression, _flags);
}
private void CreateCondition(string testString, string condition, string flagsString)
{
_testString = ConditionTestStringParser.ParseConditionTestString(testString);
_pce = ConditionPatternParser.ParseActionCondition(condition);
_flags = FlagParser.ParseConditionFlags(flagsString);
}
public void SetFlag(string flag)
{
SetFlag(flag, value: null);
}
public void SetFlag(ConditionFlagType flag)
{
SetFlag(flag, value: null);
}
public void SetFlag(string flag, string value)
{
if (_flags == null)
{
_flags = new ConditionFlags();
}
_flags.SetFlag(flag, value);
}
public void SetFlag(ConditionFlagType flag, string value)
{
if (_flags == null)
{
_flags = new ConditionFlags();
}
_flags.SetFlag(flag, value);
}
public void SetFlags(string flags)
{
if (_flags == null)
{
_flags = FlagParser.ParseConditionFlags(flags);
}
else
{
FlagParser.ParseConditionFlags(flags, _flags);
}
}
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// Represents the ConditionPattern for a mod_rewrite rule.
/// </summary>
public class ConditionExpression
{
public Operand Operand { get; set; }
public bool Invert { get; set; }
/// <summary>
/// Checks if a condition matches the context.
/// </summary>
/// <param name="context">The UrlRewriteContext.</param>
/// <param name="previous">The previous condition results (for backreferences).</param>
/// <param name="testString">The testString created from the <see cref="Pattern"/>.</param>
/// <returns>If the testString satisfies the condition</returns>
public bool? CheckConditionExpression(RewriteContext context, Match previous, string testString)
{
return Operand.CheckOperation(previous, testString, context.FileProvider) ^ Invert;
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public enum ConditionFlagType
{
NoCase,
Or,
NoVary
}
}

View File

@ -1,101 +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;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
// TODO Refactor Condition Flags and Rule Flags under base flag class
public class ConditionFlags
{
private IDictionary<string, ConditionFlagType> _conditionFlagLookup = new Dictionary<string, ConditionFlagType>(StringComparer.OrdinalIgnoreCase) {
{ "nc", ConditionFlagType.NoCase},
{ "nocase", ConditionFlagType.NoCase },
{ "or", ConditionFlagType.Or},
{ "ornext", ConditionFlagType.Or },
{ "nv", ConditionFlagType.NoVary},
{ "novary", ConditionFlagType.NoVary}
};
public IDictionary<ConditionFlagType, string> FlagDictionary { get; }
public ConditionFlags(IDictionary<ConditionFlagType, string> flags)
{
FlagDictionary = flags;
}
public ConditionFlags()
{
FlagDictionary = new Dictionary<ConditionFlagType, string>();
}
public void SetFlag(string flag)
{
SetFlag(flag, null);
}
public void SetFlag(string flag, string value)
{
ConditionFlagType res;
if (!_conditionFlagLookup.TryGetValue(flag, out res))
{
throw new ArgumentException("Invalid flag");
}
SetFlag(res, value);
}
public void SetFlag(ConditionFlagType flag, string value)
{
if (value == null)
{
value = string.Empty;
}
FlagDictionary[flag] = value;
}
public string GetFlag(ConditionFlagType flag)
{
CleanupResources();
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
return null;
}
return res;
}
public string this[ConditionFlagType flag]
{
get
{
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
return null;
}
return res;
}
set
{
FlagDictionary[flag] = value ?? string.Empty;
}
}
public bool HasFlag(ConditionFlagType flag)
{
CleanupResources();
string res;
return FlagDictionary.TryGetValue(flag, out res);
}
// If this method is called, all flags have been processed,
// therefore to clean up memory, delete dictionary.
private void CleanupResources()
{
if (_conditionFlagLookup != null)
{
_conditionFlagLookup = null;
}
}
}
}

View File

@ -26,17 +26,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// </summary> /// </summary>
/// <param name="condition">The CondPattern portion of a mod_rewrite RewriteCond.</param> /// <param name="condition">The CondPattern portion of a mod_rewrite RewriteCond.</param>
/// <returns>A new parsed condition.</returns> /// <returns>A new parsed condition.</returns>
public static ParsedModRewriteExpression ParseActionCondition(string condition) public static ParsedModRewriteInput ParseActionCondition(string condition)
{ {
if (condition == null) if (condition == null)
{ {
condition = string.Empty; condition = string.Empty;
} }
var context = new ParserContext(condition); var context = new ParserContext(condition);
var results = new ParsedModRewriteExpression(); var results = new ParsedModRewriteInput();
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
// If we hit a !, make sure the condition is inverted when resolving the string // If we hit a !, make sure the condition is inverted when resolving the string
@ -45,88 +45,90 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
results.Invert = true; results.Invert = true;
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); // Dangling !
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
} }
// Control Block for strings. Set the operation and type fields based on the sign // Control Block for strings. Set the operation and type fields based on the sign
// Switch on current character
switch (context.Current) switch (context.Current)
{ {
case Greater: case Greater:
if (!context.Next()) if (!context.Next())
{ {
// Dangling ">" // Dangling ">"
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
if (context.Current == EqualSign) if (context.Current == EqualSign)
{ {
if (!context.Next()) if (!context.Next())
{ {
// Dangling ">=" // Dangling ">="
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
results.Operation = OperationType.GreaterEqual; results.OperationType = OperationType.GreaterEqual;
results.Type = ConditionType.StringComp; results.ConditionType = ConditionType.StringComp;
} }
else else
{ {
results.Operation = OperationType.Greater; results.OperationType = OperationType.Greater;
results.Type = ConditionType.StringComp; results.ConditionType = ConditionType.StringComp;
} }
break; break;
case Less: case Less:
if (!context.Next()) if (!context.Next())
{ {
// Dangling "<" // Dangling "<"
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
if (context.Current == EqualSign) if (context.Current == EqualSign)
{ {
if (!context.Next()) if (!context.Next())
{ {
// Dangling "<=" // Dangling "<="
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
results.Operation = OperationType.LessEqual; results.OperationType = OperationType.LessEqual;
results.Type = ConditionType.StringComp; results.ConditionType = ConditionType.StringComp;
} }
else else
{ {
results.Operation = OperationType.Less; results.OperationType = OperationType.Less;
results.Type = ConditionType.StringComp; results.ConditionType = ConditionType.StringComp;
} }
break; break;
case EqualSign: case EqualSign:
if (!context.Next()) if (!context.Next())
{ {
// Dangling "=" // Dangling "="
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
results.Operation = OperationType.Equal; results.OperationType = OperationType.Equal;
results.Type = ConditionType.StringComp; results.ConditionType = ConditionType.StringComp;
break; break;
case Dash: case Dash:
results = ParseProperty(context, results.Invert); results = ParseProperty(context, results.Invert);
if (results.Type == ConditionType.PropertyTest) if (results.ConditionType == ConditionType.PropertyTest)
{ {
return results; return results;
} }
context.Next(); context.Next();
break; break;
default: default:
results.Type = ConditionType.Regex; results.ConditionType = ConditionType.Regex;
break; break;
} }
// 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;
} }
else else
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
} }
} }
@ -137,37 +139,37 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <param name="context"></param> /// <param name="context"></param>
/// <param name="invert"></param> /// <param name="invert"></param>
/// <returns></returns> /// <returns></returns>
public static ParsedModRewriteExpression ParseProperty(ParserContext context, bool invert) public static ParsedModRewriteInput ParseProperty(ParserContext context, bool invert)
{ {
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
switch (context.Current) switch (context.Current)
{ {
case 'd': case 'd':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Directory, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Directory, operand: null);
case 'f': case 'f':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.RegularFile, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.RegularFile, operand: null);
case 'F': case 'F':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingFile, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingFile, operand: null);
case 'h': case 'h':
case 'L': case 'L':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null);
case 's': case 's':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Size, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Size, operand: null);
case 'U': case 'U':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, operand: null);
case 'x': case 'x':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Executable, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Executable, operand: null);
case 'e': case 'e':
if (!context.Next() || context.Current != 'q') if (!context.Next() || context.Current != 'q')
{ {
// Illegal statement. // Illegal statement.
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Equal, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Equal, operand: null);
case 'g': case 'g':
if (!context.Next()) if (!context.Next())
{ {
@ -175,47 +177,49 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
} }
if (context.Current == 't') if (context.Current == 't')
{ {
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Greater, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Greater, operand: null);
} }
else if (context.Current == 'e') else if (context.Current == 'e')
{ {
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.GreaterEqual, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null);
} }
else else
{ {
throw new FormatException(context.Error()); throw new FormatException(context.Error());
} }
case 'l': case 'l':
// name conflict with -l and -lt/-le, so the assumption is if there is no
// charcters after -l, we assume it a symbolic link
if (!context.Next()) if (!context.Next())
{ {
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null); return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null);
} }
if (context.Current == 't') if (context.Current == 't')
{ {
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Less, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Less, operand: null);
} }
else if (context.Current == 'e') else if (context.Current == 'e')
{ {
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.LessEqual, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null);
} }
else else
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
case 'n': case 'n':
if (!context.Next() || context.Current != 'e') if (!context.Next() || context.Current != 'e')
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.NotEqual, null); return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.NotEqual, operand: null);
default: default:
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
} }
private static bool IsValidActionCondition(ParsedModRewriteExpression results) private static bool IsValidActionCondition(ParsedModRewriteInput results)
{ {
if (results.Type == ConditionType.IntComp) if (results.ConditionType == ConditionType.IntComp)
{ {
// If the type is an integer, verify operand is actually an int // If the type is an integer, verify operand is actually an int
int res; int res;

View File

@ -1,127 +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.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// Converts a parsed expression into a mod_rewrite condition.
/// </summary>
public class ExpressionCreator
{
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
public static ConditionExpression CreateConditionExpression(ParsedModRewriteExpression pce, ConditionFlags flags)
{
var condExp = new ConditionExpression();
condExp.Invert = pce.Invert;
if (pce.Type == ConditionType.Regex)
{
// TODO make nullable?
if (flags != null && flags.HasFlag(ConditionFlagType.NoCase))
{
condExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout));
}
else
{
condExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.Compiled, RegexTimeout));
}
}
else if (pce.Type == ConditionType.IntComp)
{
switch (pce.Operation)
{
case OperationType.Equal:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Equal);
break;
case OperationType.Greater:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Greater);
break;
case OperationType.GreaterEqual:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.GreaterEqual);
break;
case OperationType.Less:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.Less);
break;
case OperationType.LessEqual:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.LessEqual);
break;
case OperationType.NotEqual:
condExp.Operand = new IntegerOperand(pce.Operand, IntegerOperationType.NotEqual);
break;
default:
throw new ArgumentException("No defined operation for integer comparison.");
}
}
else if (pce.Type == ConditionType.StringComp)
{
switch (pce.Operation)
{
case OperationType.Equal:
condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Equal);
break;
case OperationType.Greater:
condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Greater);
break;
case OperationType.GreaterEqual:
condExp.Operand = new StringOperand(pce.Operand, StringOperationType.GreaterEqual);
break;
case OperationType.Less:
condExp.Operand = new StringOperand(pce.Operand, StringOperationType.Less);
break;
case OperationType.LessEqual:
condExp.Operand = new StringOperand(pce.Operand, StringOperationType.LessEqual);
break;
default:
throw new ArgumentException("No defined operation for string comparison.");
}
}
else
{
switch (pce.Operation)
{
case OperationType.Directory:
condExp.Operand = new PropertyOperand(PropertyOperationType.Directory);
break;
case OperationType.RegularFile:
condExp.Operand = new PropertyOperand(PropertyOperationType.RegularFile);
break;
case OperationType.ExistingFile:
condExp.Operand = new PropertyOperand(PropertyOperationType.ExistingFile);
break;
case OperationType.SymbolicLink:
condExp.Operand = new PropertyOperand(PropertyOperationType.SymbolicLink);
break;
case OperationType.Size:
condExp.Operand = new PropertyOperand(PropertyOperationType.Size);
break;
case OperationType.ExistingUrl:
condExp.Operand = new PropertyOperand(PropertyOperationType.ExistingUrl);
break;
case OperationType.Executable:
condExp.Operand = new PropertyOperand(PropertyOperationType.Executable);
break;
default:
throw new ArgumentException("No defined operation for property comparison.");
}
}
return condExp;
}
public static RuleExpression CreateRuleExpression(ParsedModRewriteExpression pce, RuleFlags flags)
{
var ruleExp = new RuleExpression();
ruleExp.Invert = pce.Invert;
if (flags.HasFlag(RuleFlagType.NoCase))
{
ruleExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout));
}
else
{
ruleExp.Operand = new RegexOperand(new Regex(pce.Operand, RegexOptions.Compiled, RegexTimeout));
}
return ruleExp;
}
}
}

View File

@ -4,23 +4,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
/// <summary> public class FileParser
///
/// </summary>
public static class FileParser
{ {
public static List<Rule> Parse(TextReader input) public List<Rule> Parse(TextReader input)
{ {
string line = null; string line = null;
var rules = new List<Rule>(); var rules = new List<Rule>();
var conditions = new List<Condition>(); var builder = new RuleBuilder();
// TODO consider passing Itokenizer and Ifileparser and provide implementations var lineNum = 0;
while ((line = input.ReadLine()) != null) while ((line = input.ReadLine()) != null)
{ {
lineNum++;
if (string.IsNullOrEmpty(line)) if (string.IsNullOrEmpty(line))
{ {
continue; continue;
@ -33,71 +30,65 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (tokens.Count > 4) if (tokens.Count > 4)
{ {
// This means the line didn't have an appropriate format, throw format exception // This means the line didn't have an appropriate format, throw format exception
throw new FormatException(); throw new FormatException(Resources.FormatError_ModRewriteParseError("Too many tokens on line", lineNum));
} }
// TODO make a new class called rule parser that does and either return an exception or return the rule.
switch (tokens[0]) switch (tokens[0])
{ {
case "RewriteBase": case "RewriteBase":
throw new NotSupportedException(); throw new NotImplementedException("RewriteBase is not implemented");
//if (tokens.Count == 2)
//{
// ModRewriteBase.Base = tokens[1];
//}
//else
//{
// throw new FormatException("");
//}
//break;
case "RewriteCond": case "RewriteCond":
try
{ {
ConditionBuilder builder = null; var pattern = TestStringParser.Parse(tokens[1]);
if (tokens.Count == 3) var condActionParsed = ConditionPatternParser.ParseActionCondition(tokens[2]);
var flags = new Flags();
if (tokens.Count == 4)
{ {
builder = new ConditionBuilder(tokens[1], tokens[2]); flags = FlagParser.Parse(tokens[3]);
} }
else if (tokens.Count == 4)
{ builder.AddConditionFromParts(pattern, condActionParsed, flags);
builder = new ConditionBuilder(tokens[1], tokens[2], tokens[3]);
}
else
{
throw new FormatException();
}
conditions.Add(builder.Build());
break;
} }
catch (FormatException formatException)
{
throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException);
}
break;
case "RewriteRule": case "RewriteRule":
try
{ {
RuleBuilder builder = null; var regex = RuleRegexParser.ParseRuleRegex(tokens[1]);
if (tokens.Count == 3) var pattern = TestStringParser.Parse(tokens[2]);
// TODO see if we can have flags be null.
var flags = new Flags();
if (tokens.Count == 4)
{ {
builder = new RuleBuilder(tokens[1], tokens[2]); flags = FlagParser.Parse(tokens[3]);
} }
else if (tokens.Count == 4)
{ builder.AddMatch(regex, flags);
builder = new RuleBuilder(tokens[1], tokens[2], tokens[3]); builder.AddAction(pattern, flags);
}
else
{
throw new FormatException();
}
builder.AddConditions(conditions);
rules.Add(builder.Build()); rules.Add(builder.Build());
conditions = new List<Condition>(); builder = new RuleBuilder();
break; }
catch (FormatException formatException)
{
throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException);
} }
break;
case "RewriteMap": case "RewriteMap":
throw new NotImplementedException("RewriteMaps to be added soon."); throw new NotImplementedException("RewriteMap to be added soon.");
case "RewriteEngine": case "RewriteEngine":
// Explicitly do nothing here, no notion of turning on regex engine. // Explicitly do nothing here, no notion of turning on regex engine.
break; break;
default: default:
throw new FormatException(tokens[0]); throw new FormatException(Resources.FormatError_ModRewriteParseError("Unrecognized keyword: " + tokens[0], lineNum));
} }
} }
return rules; return rules;
} }
} }
} }

View File

@ -5,99 +5,38 @@ using System;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
/// <summary>
/// Parses the flags
/// </summary>
public class FlagParser public class FlagParser
{ {
// TODO Refactor Rule and Condition Flags under IFlags public static Flags Parse(string flagString)
public static RuleFlags ParseRuleFlags(string flagString)
{
var flags = new RuleFlags();
ParseRuleFlags(flagString, flags);
return flags;
}
public static void ParseRuleFlags(string flagString, RuleFlags flags)
{ {
if (string.IsNullOrEmpty(flagString)) if (string.IsNullOrEmpty(flagString))
{ {
return; return null;
} }
// Check that flags are contained within [] // Check that flags are contained within []
if (!flagString.StartsWith("[") || !flagString.EndsWith("]")) if (!flagString.StartsWith("[") || !flagString.EndsWith("]"))
{ {
throw new FormatException(); throw new FormatException();
} }
// Illegal syntax to have any spaces.
var tokens = flagString.Substring(1, flagString.Length - 2).Split(',');
// Go through tokens and verify they have meaning.
// Flags can be KVPs, delimited by '='.
foreach (string token in tokens)
{
if (string.IsNullOrEmpty(token))
{
continue;
}
string[] kvp = token.Split('=');
if (kvp.Length == 1)
{
flags.SetFlag(kvp[0], null);
}
else if (kvp.Length == 2)
{
flags.SetFlag(kvp[0], kvp[1]);
}
else
{
throw new FormatException();
}
}
}
public static ConditionFlags ParseConditionFlags(string flagString)
{
var flags = new ConditionFlags();
ParseConditionFlags(flagString, flags);
return flags;
}
public static void ParseConditionFlags(string flagString, ConditionFlags flags)
{
if (string.IsNullOrEmpty(flagString))
{
return;
}
// Check that flags are contained within []
if (!flagString.StartsWith("[") || !flagString.EndsWith("]"))
{
throw new FormatException();
}
// Lexing esque step to split all flags. // Lexing esque step to split all flags.
// Illegal syntax to have any spaces. // Invalid syntax to have any spaces.
var tokens = flagString.Substring(1, flagString.Length - 2).Split(','); var tokens = flagString.Substring(1, flagString.Length - 2).Split(',');
// Go through tokens and verify they have meaning. var flags = new Flags();
// Flags can be KVPs, delimited by '='.
foreach (string token in tokens) foreach (string token in tokens)
{ {
if (string.IsNullOrEmpty(token)) var hasPayload = token.Split('=');
if (hasPayload.Length == 2)
{ {
continue; flags.SetFlag(hasPayload[0], hasPayload[1]);
}
string[] kvp = token.Split('=');
if (kvp.Length == 1)
{
flags.SetFlag(kvp[0], null);
}
else if (kvp.Length == 2)
{
flags.SetFlag(kvp[0], kvp[1]);
} }
else else
{ {
throw new FormatException(); flags.SetFlag(hasPayload[0], string.Empty);
} }
} }
return flags;
} }
} }
} }

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public enum RuleFlagType public enum FlagType
{ {
EscapeBackreference, EscapeBackreference,
Chain, Chain,
@ -28,8 +28,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
QSLast, QSLast,
Redirect, Redirect,
Skip, Skip,
Type, Type
// Non-modrewrite rule
FullUrl
} }
} }

View File

@ -0,0 +1,125 @@
// 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
// For more information of flags, and what flags we currently support:
// https://github.com/aspnet/BasicMiddleware/issues/66
// http://httpd.apache.org/docs/current/expr.html#vars
public class Flags
{
private static IDictionary<string, FlagType> _ruleFlagLookup = new Dictionary<string, FlagType>(StringComparer.OrdinalIgnoreCase) {
{ "b", FlagType.EscapeBackreference},
{ "c", FlagType.Chain },
{ "chain", FlagType.Chain},
{ "co", FlagType.Cookie },
{ "cookie", FlagType.Cookie },
{ "dpi", FlagType.DiscardPath },
{ "discardpath", FlagType.DiscardPath },
{ "e", FlagType.Env},
{ "env", FlagType.Env},
{ "end", FlagType.End },
{ "f", FlagType.Forbidden },
{ "forbidden", FlagType.Forbidden },
{ "g", FlagType.Gone },
{ "gone", FlagType.Gone },
{ "h", FlagType.Handler },
{ "handler", FlagType.Handler },
{ "l", FlagType.Last },
{ "last", FlagType.Last },
{ "n", FlagType.Next },
{ "next", FlagType.Next },
{ "nc", FlagType.NoCase },
{ "nocase", FlagType.NoCase },
{ "ne", FlagType.NoEscape },
{ "noescape", FlagType.NoEscape },
{ "ns", FlagType.NoSubReq },
{ "nosubreq", FlagType.NoSubReq },
{ "p", FlagType.Proxy },
{ "proxy", FlagType.Proxy },
{ "pt", FlagType.PassThrough },
{ "passthrough", FlagType.PassThrough },
{ "qsa", FlagType.QSAppend },
{ "qsappend", FlagType.QSAppend },
{ "qsd", FlagType.QSDiscard },
{ "qsdiscard", FlagType.QSDiscard },
{ "qsl", FlagType.QSLast },
{ "qslast", FlagType.QSLast },
{ "r", FlagType.Redirect },
{ "redirect", FlagType.Redirect },
{ "s", FlagType.Skip },
{ "skip", FlagType.Skip },
{ "t", FlagType.Type },
{ "type", FlagType.Type },
};
public IDictionary<FlagType, string> FlagDictionary { get; }
public Flags(IDictionary<FlagType, string> flags)
{
FlagDictionary = flags;
}
public Flags()
{
FlagDictionary = new Dictionary<FlagType, string>();
}
public void SetFlag(string flag, string value)
{
FlagType res;
if (!_ruleFlagLookup.TryGetValue(flag, out res))
{
throw new FormatException("Unrecognized flag");
}
SetFlag(res, value);
}
public void SetFlag(FlagType flag, string value)
{
if (value == null)
{
value = string.Empty;
}
FlagDictionary[flag] = value;
}
public bool GetValue(FlagType flag, out string value)
{
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
value = null;
return false;
}
value = res;
return true;
}
public string this[FlagType flag]
{
get
{
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
return null;
}
return res;
}
set
{
FlagDictionary[flag] = value ?? string.Empty;
}
}
public bool HasFlag(FlagType flag)
{
string res;
return FlagDictionary.TryGetValue(flag, out res);
}
}
}

View File

@ -0,0 +1,77 @@
// 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.AspNetCore.Rewrite.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class ModRewriteRedirectAction : UrlAction
{
public int StatusCode { get; }
public bool QueryStringAppend { get; }
public bool QueryStringDelete { get; }
public bool EscapeBackReferences { get; }
public ModRewriteRedirectAction(
int statusCode,
Pattern pattern,
bool queryStringAppend,
bool queryStringDelete,
bool escapeBackReferences)
{
StatusCode = statusCode;
Url = pattern;
QueryStringAppend = queryStringAppend;
QueryStringDelete = queryStringDelete;
EscapeBackReferences = escapeBackReferences;
}
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
if (EscapeBackReferences)
{
// TODO right way to escape backreferences?
pattern = Uri.EscapeDataString(pattern);
}
context.HttpContext.Response.StatusCode = StatusCode;
// 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)
{
QueryString query;
if (QueryStringAppend)
{
query = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
}
else
{
query = new QueryString(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;
}
else
{
// If the request url has a query string and the target does not, append the query string
// by default.
if (QueryStringDelete)
{
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
}
else
{
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern + context.HttpContext.Request.QueryString;
}
}
return RuleResult.ResponseComplete;
}
}
}

View File

@ -0,0 +1,98 @@
// 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.Extensions;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class ModRewriteRewriteAction : UrlAction
{
private readonly string ForwardSlash = "/";
public RuleResult Result { get; }
public bool QueryStringAppend { get; }
public bool QueryStringDelete { get; }
public bool EscapeBackReferences { get; }
public ModRewriteRewriteAction(
RuleResult result,
Pattern pattern,
bool queryStringAppend,
bool queryStringDelete,
bool escapeBackReferences)
{
Result = result;
Url = pattern;
QueryStringAppend = queryStringAppend;
QueryStringDelete = queryStringDelete;
EscapeBackReferences = escapeBackReferences;
}
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
var pattern = Url.Evaluate(context, ruleMatch, condMatch);
// TODO PERF, substrings, object creation, etc.
if (pattern.IndexOf("://") >= 0)
{
string scheme = null;
HostString host;
PathString path;
QueryString query;
FragmentString fragment;
UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment);
if (query.HasValue)
{
if (QueryStringAppend)
{
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(query);
}
else
{
context.HttpContext.Request.QueryString = query;
}
}
else if (QueryStringDelete)
{
context.HttpContext.Request.QueryString = QueryString.Empty;
}
context.HttpContext.Request.Scheme = scheme;
context.HttpContext.Request.Host = host;
context.HttpContext.Request.Path = path;
}
else
{
// TODO fix with redirect action logic
var split = pattern.IndexOf('?');
if (split >= 0)
{
var path = pattern.Substring(0, split);
if (path.StartsWith(ForwardSlash))
{
context.HttpContext.Request.Path = PathString.FromUriComponent(path);
}
else
{
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path);
}
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
}
else
{
if (pattern.StartsWith(ForwardSlash))
{
context.HttpContext.Request.Path = PathString.FromUriComponent(pattern);
}
else
{
context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern);
}
}
}
return Result;
}
}
}

View File

@ -1,13 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
{
public abstract class Operand
{
public abstract bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider);
}
}

View File

@ -1,44 +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.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
{
public class PropertyOperand : Operand
{
public PropertyOperationType Operation { get; }
public PropertyOperand(PropertyOperationType operation)
{
Operation = operation;
}
public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider)
{
switch(Operation)
{
case PropertyOperationType.Directory:
return fileProvider.GetFileInfo(testString).IsDirectory;
case PropertyOperationType.RegularFile:
return fileProvider.GetFileInfo(testString).Exists;
case PropertyOperationType.Size:
var fileInfo = fileProvider.GetFileInfo(testString);
return fileInfo.Exists && fileInfo.Length > 0;
case PropertyOperationType.ExistingUrl:
throw new NotSupportedException("No support for internal sub requests.");
case PropertyOperationType.ExistingFile:
throw new NotSupportedException("No support for internal sub requests.");
case PropertyOperationType.SymbolicLink:
throw new NotSupportedException("No support for checking symbolic links.");
case PropertyOperationType.Executable:
throw new NotSupportedException("No support for checking executable permissions.");
default:
return false;
}
}
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
{
public enum PropertyOperationType
{
None,
Directory,
RegularFile,
ExistingFile,
SymbolicLink,
Size,
ExistingUrl,
Executable
}
}

View File

@ -1,24 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
{
public class RegexOperand : Operand
{
public Regex RegexOperation { get; }
public RegexOperand(Regex regex)
{
RegexOperation = regex;
}
public override bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider)
{
previous = RegexOperation.Match(concatTestString);
return previous.Success;
}
}
}

View File

@ -1,40 +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.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
{
public class StringOperand : Operand
{
public string Value { get; set; }
public StringOperationType Operation { get; set; }
public StringOperand(string value, StringOperationType operation)
{
Value = value;
Operation = operation;
}
public override bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider)
{
switch (Operation)
{
case StringOperationType.Equal:
return concatTestString.CompareTo(Value) == 0;
case StringOperationType.Greater:
return concatTestString.CompareTo(Value) > 0;
case StringOperationType.GreaterEqual:
return concatTestString.CompareTo(Value) >= 0;
case StringOperationType.Less:
return concatTestString.CompareTo(Value) < 0;
case StringOperationType.LessEqual:
return concatTestString.CompareTo(Value) <= 0;
default:
return null;
}
}
}
}

View File

@ -3,20 +3,21 @@
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public class ParsedModRewriteExpression public class ParsedModRewriteInput
{ {
public bool Invert { get; set; } public bool Invert { get; set; }
public ConditionType Type { get; set; } public ConditionType ConditionType { get; set; }
public OperationType Operation { get; set; } public OperationType OperationType { get; set; }
public string Operand { get; set; } public string Operand { get; set; }
public ParsedModRewriteExpression(bool invert, ConditionType type, OperationType operation, string operand)
public ParsedModRewriteInput() { }
public ParsedModRewriteInput(bool invert, ConditionType conditionType, OperationType operationType, string operand)
{ {
Invert = invert; Invert = invert;
Type = type; ConditionType = conditionType;
Operation = operation; OperationType = operationType;
Operand = operand; Operand = operand;
} }
public ParsedModRewriteExpression() { }
} }
} }

View File

@ -1,67 +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.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// Contains a sequence of pattern segments, which on obtaining the context, will create the appropriate
/// test string and condition for rules and conditions.
/// </summary>
public class Pattern
{
private IReadOnlyList<PatternSegment> Segments { get; }
/// <summary>
/// Creates a new Pattern
/// </summary>
/// <param name="segments">List of pattern segments which will be applied.</param>
public Pattern(IReadOnlyList<PatternSegment> segments)
{
Segments = segments;
}
/// <summary>
/// Creates the appropriate test string from the Httpcontext and Segments.
/// </summary>
/// <param name="context"></param>
/// <param name="ruleMatch"></param>
/// <param name="prevCondition"></param>
/// <returns></returns>
public string GetPattern(HttpContext context, Match ruleMatch, Match prevCondition)
{
var res = new StringBuilder();
foreach (var segment in Segments)
{
// TODO handle case when segment.Variable is 0 in rule and condition
switch (segment.Type)
{
case SegmentType.Literal:
res.Append(segment.Variable);
break;
case SegmentType.ServerParameter:
res.Append(ServerVariables.Resolve(segment.Variable, context));
break;
case SegmentType.RuleParameter:
var ruleParam = ruleMatch.Groups[segment.Variable];
if (ruleParam != null)
{
res.Append(ruleParam);
}
break;
case SegmentType.ConditionParameter:
var condParam = prevCondition.Groups[segment.Variable];
if (condParam != null)
{
res.Append(condParam);
}
break;
}
}
return res.ToString();
}
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// A Pattern segment contains a portion of the test string/ substitution segment with a type associated.
/// This type can either be: Regex, Rule Variable, Condition Variable, or a Server Variable.
/// </summary>
public class PatternSegment
{
public string Variable { get; } // TODO make this a range s.t. we don't copy the string.
public SegmentType Type { get; }
/// <summary>
/// Create a Pattern segment.
/// </summary>
/// <param name="variable"></param>
/// <param name="type"></param>
public PatternSegment(string variable, SegmentType type)
{
Variable = variable;
Type = type;
}
}
}

View File

@ -3,119 +3,221 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PreActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public class RuleBuilder public class RuleBuilder
{ {
private ParsedModRewriteExpression _pce; private Conditions _conditions;
private List<Condition> _conditions; private UrlAction _action;
private RuleFlags _flags; private UrlMatch _match;
private Pattern _patterns; private List<PreAction> _preActions;
private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
public ModRewriteRule Build() public ModRewriteRule Build()
{ {
var ruleExpression = ExpressionCreator.CreateRuleExpression(_pce, _flags); if (_action == null || _match == null)
return new ModRewriteRule(_conditions, ruleExpression, _patterns, _flags); {
// TODO throw an exception here, find apporpriate exception
}
return new ModRewriteRule(_match, _conditions, _action, _preActions);
} }
public RuleBuilder(string initialRule, string transformation) : public void AddRule(string rule)
this(initialRule, transformation, flags: null)
{
}
public RuleBuilder(string rule)
{ {
// TODO
var tokens = Tokenizer.Tokenize(rule); var tokens = Tokenizer.Tokenize(rule);
if (tokens.Count == 3) var regex = RuleRegexParser.ParseRuleRegex(tokens[1]);
var pattern = TestStringParser.Parse(tokens[2]);
var flags = new Flags();
if (tokens.Count == 4)
{ {
CreateRule(tokens[1], tokens[2], flags: null); flags = FlagParser.Parse(tokens[3]);
} }
else if (tokens.Count == 4) AddMatch(regex, flags);
AddAction(pattern, flags);
}
public void AddConditionFromParts(
Pattern pattern,
ParsedModRewriteInput input,
Flags flags)
{
if (_conditions == null)
{ {
CreateRule(tokens[1], tokens[2], tokens[3]); _conditions = new Conditions();
}
var condition = new Condition();
condition.OrNext = flags.HasFlag(FlagType.Or);
condition.Input = pattern;
if (input.ConditionType == ConditionType.Regex)
{
// TODO make nullable?
if (flags.HasFlag(FlagType.NoCase))
{
condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert);
}
else
{
condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert);
}
}
else if (input.ConditionType == ConditionType.IntComp)
{
switch (input.OperationType)
{
case OperationType.Equal:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Equal);
break;
case OperationType.Greater:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Greater);
break;
case OperationType.GreaterEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.GreaterEqual);
break;
case OperationType.Less:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Less);
break;
case OperationType.LessEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.LessEqual);
break;
case OperationType.NotEqual:
condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.NotEqual);
break;
default:
throw new ArgumentException("Invalid operation for integer comparison.");
}
}
else if (input.ConditionType == ConditionType.StringComp)
{
switch (input.OperationType)
{
case OperationType.Equal:
condition.Match = new StringMatch(input.Operand, StringOperationType.Equal);
break;
case OperationType.Greater:
condition.Match = new StringMatch(input.Operand, StringOperationType.Greater);
break;
case OperationType.GreaterEqual:
condition.Match = new StringMatch(input.Operand, StringOperationType.GreaterEqual);
break;
case OperationType.Less:
condition.Match = new StringMatch(input.Operand, StringOperationType.Less);
break;
case OperationType.LessEqual:
condition.Match = new StringMatch(input.Operand, StringOperationType.LessEqual);
break;
default:
throw new ArgumentException("Invalid operation for string comparison.");
}
} }
else else
{ {
throw new ArgumentException(); switch (input.OperationType)
{
case OperationType.Directory:
condition.Match = new IsDirectoryMatch(input.Invert);
break;
case OperationType.RegularFile:
condition.Match = new IsFileMatch(input.Invert);
break;
case OperationType.ExistingFile:
condition.Match = new IsFileMatch(input.Invert);
break;
case OperationType.SymbolicLink:
throw new NotImplementedException("Symbolic links are not implemented");
case OperationType.Size:
condition.Match = new FileSizeMatch(input.Invert);
break;
case OperationType.ExistingUrl:
throw new NotImplementedException("Existing Url lookups not implemented");
case OperationType.Executable:
throw new NotImplementedException("Executable Property search is not implemented");
default:
// TODO change exception
throw new ArgumentException("Invalid operation for property comparison.");
}
} }
_conditions.ConditionList.Add(condition);
} }
public RuleBuilder(string initialRule, string transformation, string flags) public void AddMatch(
ParsedModRewriteInput input,
Flags flags)
{ {
CreateRule(initialRule, transformation, flags); if (flags.HasFlag(FlagType.NoCase))
}
public void CreateRule(string initialRule, string transformation, string flags)
{
_pce = RuleRegexParser.ParseRuleRegex(initialRule);
_patterns = ConditionTestStringParser.ParseConditionTestString(transformation);
_flags = FlagParser.ParseRuleFlags(flags);
}
public void AddCondition(string condition)
{
if (_conditions == null)
{ {
_conditions = new List<Condition>(); _match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert);
}
var condBuilder = new ConditionBuilder(condition);
_conditions.Add(condBuilder.Build());
}
public void AddCondition(Condition condition)
{
if (_conditions == null)
{
_conditions = new List<Condition>();
}
_conditions.Add(condition);
}
public void AddConditions(List<Condition> conditions)
{
if (_conditions == null)
{
_conditions = new List<Condition>();
}
_conditions.AddRange(conditions);
}
public void SetFlag(string flag)
{
SetFlag(flag, value: null);
}
public void SetFlag(RuleFlagType flag)
{
SetFlag(flag, value: null);
}
public void SetFlag(string flag, string value)
{
if (_flags == null)
{
_flags = new RuleFlags();
}
_flags.SetFlag(flag, value);
}
public void SetFlag(RuleFlagType flag, string value)
{
if (_flags == null)
{
_flags = new RuleFlags();
}
_flags.SetFlag(flag, value);
}
public void SetFlags(string flags)
{
if (_flags == null)
{
_flags = FlagParser.ParseRuleFlags(flags);
} }
else else
{ {
FlagParser.ParseRuleFlags(flags, _flags); _match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled, RegexTimeout), input.Invert);
}
}
public void AddAction(
Pattern pattern,
Flags flags)
{
// first create pre conditions
if (_preActions == null)
{
_preActions = new List<PreAction>();
}
string flag;
if (flags.GetValue(FlagType.Cookie, out flag))
{
// parse cookie
_preActions.Add(new ChangeCookiePreAction(flag));
}
if (flags.GetValue(FlagType.Env, out flag))
{
// parse env
_preActions.Add(new ChangeEnvironmentPreAction(flag));
}
if (flags.HasFlag(FlagType.Forbidden))
{
_action = new ForbiddenAction();
}
else if (flags.HasFlag(FlagType.Gone))
{
_action = new GoneAction();
}
else
{
var escapeBackReference = flags.HasFlag(FlagType.EscapeBackreference);
var queryStringAppend = flags.HasFlag(FlagType.QSAppend);
var queryStringDelete = flags.HasFlag(FlagType.QSDiscard);
// is redirect?
string statusCode;
if (flags.GetValue(FlagType.Redirect, out statusCode))
{
int res;
if (!int.TryParse(statusCode, out res))
{
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1));
}
_action = new ModRewriteRedirectAction(res, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
}
else
{
var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last);
var redirect = last ? RuleResult.StopRules : RuleResult.Continue;
_action = new ModRewriteRewriteAction(redirect, pattern, queryStringAppend, queryStringDelete, escapeBackReference);
}
} }
} }
} }

View File

@ -1,13 +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.Rewrite.Internal.ModRewrite.Operands;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class RuleExpression
{
public RegexOperand Operand { get; set; }
public bool Invert { get; set; }
}
}

View File

@ -1,133 +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;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
public class RuleFlags
{
private IDictionary<string, RuleFlagType> _ruleFlagLookup = new Dictionary<string, RuleFlagType>(StringComparer.OrdinalIgnoreCase) {
{ "b", RuleFlagType.EscapeBackreference},
{ "c", RuleFlagType.Chain },
{ "chain", RuleFlagType.Chain},
{ "co", RuleFlagType.Cookie },
{ "cookie", RuleFlagType.Cookie },
{ "dpi", RuleFlagType.DiscardPath },
{ "discardpath", RuleFlagType.DiscardPath },
{ "e", RuleFlagType.Env},
{ "env", RuleFlagType.Env},
{ "end", RuleFlagType.End },
{ "f", RuleFlagType.Forbidden },
{ "forbidden", RuleFlagType.Forbidden },
{ "g", RuleFlagType.Gone },
{ "gone", RuleFlagType.Gone },
{ "h", RuleFlagType.Handler },
{ "handler", RuleFlagType.Handler },
{ "l", RuleFlagType.Last },
{ "last", RuleFlagType.Last },
{ "n", RuleFlagType.Next },
{ "next", RuleFlagType.Next },
{ "nc", RuleFlagType.NoCase },
{ "nocase", RuleFlagType.NoCase },
{ "ne", RuleFlagType.NoEscape },
{ "noescape", RuleFlagType.NoEscape },
{ "ns", RuleFlagType.NoSubReq },
{ "nosubreq", RuleFlagType.NoSubReq },
{ "p", RuleFlagType.Proxy },
{ "proxy", RuleFlagType.Proxy },
{ "pt", RuleFlagType.PassThrough },
{ "passthrough", RuleFlagType.PassThrough },
{ "qsa", RuleFlagType.QSAppend },
{ "qsappend", RuleFlagType.QSAppend },
{ "qsd", RuleFlagType.QSDiscard },
{ "qsdiscard", RuleFlagType.QSDiscard },
{ "qsl", RuleFlagType.QSLast },
{ "qslast", RuleFlagType.QSLast },
{ "r", RuleFlagType.Redirect },
{ "redirect", RuleFlagType.Redirect },
{ "s", RuleFlagType.Skip },
{ "skip", RuleFlagType.Skip },
{ "t", RuleFlagType.Type },
{ "type", RuleFlagType.Type },
// TODO make this a load bool instead of a flag for the file and rules.
{ "u", RuleFlagType.FullUrl },
{ "url", RuleFlagType.FullUrl }
};
public IDictionary<RuleFlagType, string> FlagDictionary { get; }
public RuleFlags(IDictionary<RuleFlagType, string> flags)
{
// TODO use ref to check dictionary equality
FlagDictionary = flags;
}
public RuleFlags()
{
FlagDictionary = new Dictionary<RuleFlagType, string>();
}
public void SetFlag(string flag, string value)
{
RuleFlagType res;
if (!_ruleFlagLookup.TryGetValue(flag, out res))
{
throw new FormatException("Invalid flag");
}
SetFlag(res, value);
}
public void SetFlag(RuleFlagType flag, string value)
{
if (value == null)
{
value = string.Empty;
}
FlagDictionary[flag] = value;
}
public string GetValue(RuleFlagType flag)
{
CleanupResources();
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
return null;
}
return res;
}
public string this[RuleFlagType flag]
{
get
{
string res;
if (!FlagDictionary.TryGetValue(flag, out res))
{
return null;
}
return res;
}
set
{
FlagDictionary[flag] = value ?? string.Empty;
}
}
public bool HasFlag(RuleFlagType flag)
{
CleanupResources();
string res;
return FlagDictionary.TryGetValue(flag, out res);
}
private void CleanupResources()
{
if (_ruleFlagLookup != null)
{
_ruleFlagLookup = null;
}
}
}
}

View File

@ -7,19 +7,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public static class RuleRegexParser public static class RuleRegexParser
{ {
public static ParsedModRewriteExpression ParseRuleRegex(string regex) public static ParsedModRewriteInput ParseRuleRegex(string regex)
{ {
if (regex == null || regex == String.Empty) if (regex == null || regex == string.Empty)
{ {
throw new FormatException(); throw new FormatException("Regex expression is null");
} }
if (regex.StartsWith("!")) if (regex.StartsWith("!"))
{ {
return new ParsedModRewriteExpression { Invert = true, Operand = regex.Substring(1) }; return new ParsedModRewriteInput { Invert = true, Operand = regex.Substring(1) };
} }
else else
{ {
return new ParsedModRewriteExpression { Invert = false, Operand = regex}; return new ParsedModRewriteInput { Invert = false, Operand = regex};
} }
} }
} }

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
public enum SegmentType public enum SegmentType
{ {

View File

@ -2,11 +2,8 @@
// 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 System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal;
using System.Globalization; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using System.Net.Sockets;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
@ -16,164 +13,112 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// </summary> /// </summary>
public static class ServerVariables public static class ServerVariables
{ {
public static HashSet<string> ValidServerVariables = new HashSet<string>()
{
"HTTP_ACCEPT",
"HTTP_COOKIE",
"HTTP_FORWARDED",
"HTTP_HOST",
"HTTP_PROXY_CONNECTION",
"HTTP_REFERER",
"HTTP_USER_AGENT",
"AUTH_TYPE",
"CONN_REMOTE_ADDR",
"CONTEXT_PREFIX",
"CONTEXT_DOCUMENT_ROOT",
"IPV6",
"PATH_INFO",
"QUERY_STRING",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_IDENT",
"REMOTE_PORT",
"REMOTE_USER",
"REQUEST_METHOD",
"SCRIPT_FILENAME",
"DOCUMENT_ROOT",
"SCRIPT_GROUP",
"SCRIPT_USER",
"SERVER_ADDR",
"SERVER_ADMIN",
"SERVER_NAME",
"SERVER_PORT",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
"TIME_YEAR",
"TIME_MON",
"TIME_DAY",
"TIME_HOUR",
"TIME_MIN",
"TIME_SEC",
"TIME_WDAY",
"TIME",
"API_VERSION",
"HTTPS",
"IS_SUBREQ",
"REQUEST_FILENAME",
"REQUEST_SCHEME",
"REQUEST_URI",
"THE_REQUEST"
};
/// <summary> /// <summary>
/// Translates mod_rewrite server variables strings to an enum of different server variables. /// Translates mod_rewrite server variables strings to an enum of different server variables.
/// </summary> /// </summary>
/// <param name="variable">The server variable string.</param> /// <param name="variable">The server variable string.</param>
/// <param name="context">The HttpContext context.</param> /// <param name="context">The Parser context</param>
/// <returns>The appropriate enum if the server variable exists, else ServerVariable.None</returns> /// <returns>The appropriate enum if the server variable exists, else ServerVariable.None</returns>
public static string Resolve(string variable, HttpContext context) public static PatternSegment FindServerVariable(string variable, ParserContext context)
{ {
// TODO talk about perf here
switch (variable) switch (variable)
{ {
case "HTTP_ACCEPT": case "HTTP_ACCEPT":
return context.Request.Headers[HeaderNames.Accept]; return new HeaderSegment(HeaderNames.Accept);
case "HTTP_COOKIE": case "HTTP_COOKIE":
return context.Request.Headers[HeaderNames.Cookie]; return new HeaderSegment(HeaderNames.Cookie);
case "HTTP_FORWARDED":
return context.Request.Headers["Forwarded"];
case "HTTP_HOST": case "HTTP_HOST":
return context.Request.Headers[HeaderNames.Host]; return new HeaderSegment(HeaderNames.Host);
case "HTTP_PROXY_CONNECTION":
return context.Request.Headers[HeaderNames.ProxyAuthenticate];
case "HTTP_REFERER": case "HTTP_REFERER":
return context.Request.Headers[HeaderNames.Referer]; return new HeaderSegment(HeaderNames.Referer);
case "HTTP_USER_AGENT": case "HTTP_USER_AGENT":
return context.Request.Headers[HeaderNames.UserAgent]; return new HeaderSegment(HeaderNames.UserAgent);
case "HTTP_CONNECTION":
return new HeaderSegment(HeaderNames.Connection);
case "HTTP_FORWARDED":
return new HeaderSegment("Forwarded");
case "AUTH_TYPE": case "AUTH_TYPE":
throw new NotImplementedException(); throw new NotImplementedException("Auth-Type server variable is not supported");
case "CONN_REMOTE_ADDR": case "CONN_REMOTE_ADDR":
return context.Connection.RemoteIpAddress?.ToString(); return new RemoteAddressSegment();
case "CONTEXT_PREFIX": case "CONTEXT_PREFIX":
throw new NotImplementedException(); throw new NotImplementedException("Context-prefix server variable is not supported");
case "CONTEXT_DOCUMENT_ROOT": case "CONTEXT_DOCUMENT_ROOT":
throw new NotImplementedException(); throw new NotImplementedException("Context-Document-Root server variable is not supported");
case "IPV6": case "IPV6":
return context.Connection.LocalIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off"; return new IsIPV6Segment();
case "PATH_INFO": case "PATH_INFO":
throw new NotImplementedException(); throw new NotImplementedException("Path-Info server variable is not supported");
case "QUERY_STRING": case "QUERY_STRING":
return context.Request.QueryString.Value; return new QueryStringSegment();
case "REMOTE_ADDR": case "REMOTE_ADDR":
return context.Connection.RemoteIpAddress?.ToString(); return new RemoteAddressSegment();
case "REMOTE_HOST": case "REMOTE_HOST":
throw new NotImplementedException(); throw new NotImplementedException("Remote-Host server variable is not supported");
case "REMOTE_IDENT": case "REMOTE_IDENT":
throw new NotImplementedException(); throw new NotImplementedException("Remote-Identity server variable is not supported");
case "REMOTE_PORT": case "REMOTE_PORT":
return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); return new RemotePortSegment();
case "REMOTE_USER": case "REMOTE_USER":
throw new NotImplementedException(); throw new NotImplementedException("Remote-User server variable is not supported");
case "REQUEST_METHOD": case "REQUEST_METHOD":
return context.Request.Method; return new RequestMethodSegment();
case "SCRIPT_FILENAME": case "SCRIPT_FILENAME":
throw new NotImplementedException(); throw new NotImplementedException("Script-Filename server variable is not supported");
case "DOCUMENT_ROOT": case "DOCUMENT_ROOT":
throw new NotImplementedException(); throw new NotImplementedException("Document-Root server variable is not supported");
case "SCRIPT_GROUP": case "SCRIPT_GROUP":
throw new NotImplementedException(); throw new NotImplementedException("Script-Group server variable is not supported");
case "SCRIPT_USER": case "SCRIPT_USER":
throw new NotImplementedException(); throw new NotImplementedException("Script-User server variable is not supported");
case "SERVER_ADDR": case "SERVER_ADDR":
return context.Connection.LocalIpAddress?.ToString(); return new LocalAddressSegment();
case "SERVER_ADMIN": case "SERVER_ADMIN":
throw new NotImplementedException(); throw new NotImplementedException("Server-Admin server variable is not supported");
case "SERVER_NAME": case "SERVER_NAME":
throw new NotImplementedException(); throw new NotImplementedException("Server-Name server variable is not supported");
case "SERVER_PORT": case "SERVER_PORT":
return context.Connection.LocalPort.ToString(CultureInfo.InvariantCulture); return new LocalPortSegment();
case "SERVER_PROTOCOL": case "SERVER_PROTOCOL":
return context.Features.Get<IHttpRequestFeature>()?.Protocol; return new ServerProtocolSegment();
case "SERVER_SOFTWARE": case "SERVER_SOFTWARE":
throw new NotImplementedException(); throw new NotImplementedException("Server-Software server variable is not supported");
case "TIME_YEAR": case "TIME_YEAR":
return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_MON": case "TIME_MON":
return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_DAY": case "TIME_DAY":
return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_HOUR": case "TIME_HOUR":
return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_MIN": case "TIME_MIN":
return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_SEC": case "TIME_SEC":
return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME_WDAY": case "TIME_WDAY":
return ((int) DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "TIME": case "TIME":
return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture); return new DateTimeSegment(variable);
case "API_VERSION": case "API_VERSION":
throw new NotImplementedException(); throw new NotImplementedException();
case "HTTPS": case "HTTPS":
return context.Request.IsHttps ? "on" : "off"; return new IsHttpsModSegment();
case "HTTP2": case "HTTP2":
return context.Request.Scheme == "http2" ? "on" : "off"; throw new NotImplementedException("Http2 server variable is not supported");
case "IS_SUBREQ": case "IS_SUBREQ":
// TODO maybe can do this? context.Request.HttpContext ? // TODO maybe can do this? context.Request.HttpContext ?
throw new NotImplementedException(); throw new NotImplementedException("Is-Subrequest server variable is not supported");
case "REQUEST_FILENAME": case "REQUEST_FILENAME":
return context.Request.Path.Value.Substring(1); return new RequestFileNameSegment();
case "REQUEST_SCHEME": case "REQUEST_SCHEME":
return context.Request.Scheme; return new SchemeSegment();
case "REQUEST_URI": case "REQUEST_URI":
// TODO This isn't an ideal solution. What this assumes is that all conditions don't have a leading slash before it. return new UrlSegment();
return context.Request.Path.Value.Substring(1);
case "THE_REQUEST": case "THE_REQUEST":
// TODO throw new NotImplementedException("The-Request server variable is not supported");
throw new NotImplementedException();
default: default:
return null; throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(variable, context.Index));
} }
} }
} }

View File

@ -4,13 +4,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
/// <summary> /// <summary>
/// Parses the TestString segment of the mod_rewrite condition. /// Parses the TestString segment of the mod_rewrite condition.
/// </summary> /// </summary>
public class ConditionTestStringParser public class TestStringParser
{ {
private const char Percent = '%'; private const char Percent = '%';
private const char Dollar = '$'; private const char Dollar = '$';
@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// %1 /// %1
/// $1</param> /// $1</param>
/// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns> /// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns>
public static Pattern ParseConditionTestString(string testString) /// http://httpd.apache.org/docs/current/mod/mod_rewrite.html
public static Pattern Parse(string testString)
{ {
if (testString == null) if (testString == null)
{ {
@ -45,12 +47,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// This is a server parameter, parse for a condition variable // This is a server parameter, parse for a condition variable
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index));
}
if (!ParseConditionParameter(context, results))
{
throw new FormatException(context.Error());
} }
ParseConditionParameter(context, results);
} }
else if (context.Current == Dollar) else if (context.Current == Dollar)
{ {
@ -58,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// and create a new Pattern Segment. // and create a new Pattern Segment.
if (!context.Next()) if (!context.Next())
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index));
} }
context.Mark(); context.Mark();
if (context.Current >= '0' && context.Current <= '9') if (context.Current >= '0' && context.Current <= '9')
@ -66,21 +65,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
context.Next(); context.Next();
var ruleVariable = context.Capture(); var ruleVariable = context.Capture();
context.Back(); context.Back();
results.Add(new PatternSegment(ruleVariable, SegmentType.RuleParameter)); int parsedIndex;
if (!int.TryParse(ruleVariable, out parsedIndex))
{
// TODO this should always pass, remove try parse?
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(ruleVariable, context.Index));
}
results.Add(new RuleMatchSegment(parsedIndex));
} }
else else
{ {
throw new FormatException(context.Error()); throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index));
} }
} }
else else
{ {
// Parse for literals, which will return on either the end of the test string // Parse for literals, which will return on either the end of the test string
// or when it hits a special character // or when it hits a special character
if (!ParseLiteral(context, results)) ParseLiteral(context, results);
{
throw new FormatException(context.Error());
}
} }
} }
return new Pattern(results); return new Pattern(results);
@ -95,7 +97,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 bool ParseConditionParameter(ParserContext context, List<PatternSegment> results) private static void ParseConditionParameter(ParserContext context, List<PatternSegment> results)
{ {
// Parse { } // Parse { }
if (context.Current == OpenBrace) if (context.Current == OpenBrace)
@ -104,34 +106,26 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (!context.Next()) if (!context.Next())
{ {
// Dangling { // Dangling {
return false; throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index));
} }
context.Mark(); context.Mark();
while (context.Current != CloseBrace) while (context.Current != CloseBrace)
{ {
if (!context.Next()) if (!context.Next())
{ {
// No closing } for the server variable throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index));
return false;
} }
else if (context.Current == Colon) else if (context.Current == Colon)
{ {
// Have a segmented look up Ex: HTTP:xxxx // Have a segmented look up Ex: HTTP:xxxx
// TODO // TODO
throw new NotImplementedException("Segmented Lookups no implemented");
} }
} }
// Need to verify server variable captured exists // Need to verify server variable captured exists
var rawServerVariable = context.Capture(); var rawServerVariable = context.Capture();
if (IsValidServerVariable(rawServerVariable)) results.Add(ServerVariables.FindServerVariable(rawServerVariable, context));
{
results.Add(new PatternSegment(rawServerVariable, SegmentType.ServerParameter));
}
else
{
// invalid.
return false;
}
} }
else if (context.Current >= '0' && context.Current <= '9') else if (context.Current >= '0' && context.Current <= '9')
{ {
@ -143,14 +137,18 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// Once we leave this method, the while loop will call next again. Because // Once we leave this method, the while loop will call next again. Because
// capture is exclusive, we need to go one past the end index, capture, and then go back. // capture is exclusive, we need to go one past the end index, capture, and then go back.
context.Back(); context.Back();
results.Add(new PatternSegment(rawConditionParameter, SegmentType.ConditionParameter)); int parsedIndex;
if (!int.TryParse(rawConditionParameter, out parsedIndex))
{
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(rawConditionParameter, context.Index));
}
results.Add(new ConditionMatchSegment(parsedIndex));
} }
else else
{ {
// illegal escape of a character // illegal escape of a character
return false; throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
} }
return true;
} }
/// <summary> /// <summary>
@ -159,7 +157,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 bool ParseLiteral(ParserContext context, List<PatternSegment> results) private static void ParseLiteral(ParserContext context, List<PatternSegment> results)
{ {
context.Mark(); context.Mark();
string literal; string literal;
@ -177,29 +175,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
break; break;
} }
} }
// add results
if (IsValidLiteral(context, literal)) results.Add(new LiteralSegment(literal));
{
// add results
results.Add(new PatternSegment(literal, SegmentType.Literal));
return true;
}
else
{
return false;
}
}
private static bool IsValidLiteral(ParserContext context, string literal)
{
// TODO Once escape characters are discussed, figure this out.
return true;
}
private static bool IsValidServerVariable(string variable)
{
// TODO Once escape characters are discussed, figure this out.
return ServerVariables.ValidServerVariables.Contains(variable);
} }
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{ {
@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (!context.Next()) if (!context.Next())
{ {
// Means that a character was not escaped appropriately Ex: "foo\" // Means that a character was not escaped appropriately Ex: "foo\"
throw new ArgumentException(); throw new FormatException("Invalid escaper character in string " + rule);
} }
} }
else if (context.Current == Space || context.Current == Tab) else if (context.Current == Space || context.Current == Tab)

View File

@ -1,208 +1,54 @@
// 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.Collections.Generic; using System.Collections.Generic;
using Microsoft.Net.Http.Headers; using Microsoft.AspNetCore.Rewrite.Internal;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class ModRewriteRule : Rule public class ModRewriteRule : Rule
{ {
public List<Condition> Conditions { get; set; } = new List<Condition>(); public UrlMatch InitialMatch { get; set; }
public string Description { get; set; } = string.Empty; public Conditions Conditions { get; set; }
public RuleExpression InitialRule { get; set; } public UrlAction Action { get; set; }
public Pattern Transform { get; set; } public List<PreAction> PreActions { get; set; }
public RuleFlags Flags { get; set; } = new RuleFlags();
public ModRewriteRule() { }
public ModRewriteRule(List<Condition> conditions, RuleExpression initialRule, Pattern transforms, RuleFlags flags, string description = "") public ModRewriteRule(UrlMatch initialMatch, Conditions conditions, UrlAction urlAction, List<PreAction> preActions)
{ {
Conditions = conditions; Conditions = conditions;
InitialRule = initialRule; InitialMatch = initialMatch;
Transform = transforms; Action = urlAction;
Flags = flags; PreActions = preActions;
Description = description;
} }
public override RuleResult ApplyRule(RewriteContext context) public override RuleResult ApplyRule(RewriteContext context)
{ {
// 1. Figure out which section of the string to match for the initial rule. // 1. Figure out which section of the string to match for the initial rule.
var results = InitialRule.Operand.RegexOperation.Match(context.HttpContext.Request.Path.ToString()); var initMatchRes = InitialMatch.Evaluate(context.HttpContext.Request.Path, context);
string flagRes = null; if (!initMatchRes.Success)
if (CheckMatchResult(results.Success))
{ {
return RuleResult.Continue; return RuleResult.Continue;
} }
if (Flags.HasFlag(RuleFlagType.EscapeBackreference)) MatchResults condMatchRes = null;
if (Conditions != null)
{ {
// TODO Escape Backreferences here. condMatchRes = Conditions.Evaluate(context, initMatchRes);
} if (!condMatchRes.Success)
// 2. Go through all conditions and compare them to the created string
var previous = Match.Empty;
if (!CheckCondition(context, results, previous))
{
return RuleResult.Continue;
}
// TODO add chained flag
// at this point, our rule passed, we can now apply the on match function
var result = Transform.GetPattern(context.HttpContext, results, previous);
if (Flags.HasFlag(RuleFlagType.QSDiscard))
{
context.HttpContext.Request.QueryString = new QueryString();
}
if ((flagRes = Flags.GetValue(RuleFlagType.Cookie)) != null)
{
// TODO CreateCookies(context);
// context.HttpContext.Response.Cookies.Append()
// Make sure this in on compile.
}
if ((flagRes = Flags.GetValue(RuleFlagType.Env)) != null)
{
// TODO CreateEnv(context)
// context.HttpContext...
}
if ((flagRes = Flags.GetValue(RuleFlagType.Next)) != null)
{
// TODO Next flag
}
if (Flags.HasFlag(RuleFlagType.Forbidden))
{
context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
return RuleResult.ResponseComplete;
}
else if (Flags.HasFlag(RuleFlagType.Gone))
{
context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone;
return RuleResult.ResponseComplete;
}
else if (result == "-")
{
// TODO set url to result.
}
else if (Flags.HasFlag(RuleFlagType.QSAppend))
{
context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(result));
}
if ((flagRes = Flags.GetValue(RuleFlagType.Redirect)) != null)
{
int parsedInt;
if (!int.TryParse(flagRes, out parsedInt))
{
// TODO PERF parse the status code when the flag is parsed rather than per request
throw new FormatException("Trying to parse non-int in integer comparison.");
}
context.HttpContext.Response.StatusCode = parsedInt;
if (Flags.HasFlag(RuleFlagType.FullUrl))
{
// TODO review escaping
context.HttpContext.Response.Headers[HeaderNames.Location] = result;
}
else
{
// TODO str cat is bad, polish, review escaping
if (result.StartsWith("/"))
{
context.HttpContext.Response.Headers[HeaderNames.Location] = result + context.HttpContext.Request.QueryString;
}
else
{
context.HttpContext.Response.Headers[HeaderNames.Location] = "/" + result + context.HttpContext.Request.QueryString;
}
}
return RuleResult.ResponseComplete;
}
else
{
if (Flags.HasFlag(RuleFlagType.FullUrl))
{
ModifyHttpContextFromUri(context.HttpContext, result);
}
else
{
if (result.StartsWith("/"))
{
context.HttpContext.Request.Path = new PathString(result);
}
else
{
context.HttpContext.Request.Path = new PathString("/" + result);
}
}
if (Flags.HasFlag(RuleFlagType.Last) || Flags.HasFlag(RuleFlagType.End))
{
return RuleResult.StopRules;
}
else
{ {
return RuleResult.Continue; return RuleResult.Continue;
} }
} }
}
private bool CheckMatchResult(bool? result) // 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
if (result == null) foreach (var preAction in PreActions)
{ {
return false; preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes);
}
return !(result.Value ^ InitialRule.Invert);
}
private bool CheckCondition(RewriteContext context, Match results, Match previous)
{
if (Conditions == null)
{
return true;
} }
// TODO Visitor pattern here? return Action.ApplyAction(context, initMatchRes, condMatchRes);
foreach (var condition in Conditions)
{
var concatTestString = condition.TestStringSegments.GetPattern(context.HttpContext, results, previous);
var match = condition.ConditionExpression.CheckConditionExpression(context, previous, concatTestString);
if (match == null)
{
return false;
}
if (!match.Value && !(condition.Flags.HasFlag(ConditionFlagType.Or)))
{
return false;
}
}
return true;
}
private void ModifyHttpContextFromUri(HttpContext context, string uriString)
{
var uri = new Uri(uriString);
// TODO this is ugly, fix in later push.
// TODO super bad for perf, cache/locally store these and update httpcontext after all rules are applied.
var pathBase = PathString.FromUriComponent(uri);
if (!pathBase.Value.StartsWith(context.Request.PathBase))
{
// cannot distinguish between path base and path.
throw new NotSupportedException("Modified path base from mod_rewrite rule");
}
context.Request.Host = HostString.FromUriComponent(uri);
context.Request.Path = PathString.FromUriComponent(uri);
context.Request.QueryString = QueryString.FromUriComponent(uri);
context.Request.Scheme = uri.Scheme;
} }
} }
} }

View File

@ -8,19 +8,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
/// </summary> /// </summary>
public class ParserContext public class ParserContext
{ {
private readonly string _template; public readonly string Template;
public int Index { get; set; } public int Index { get; set; }
private int? _mark; private int? _mark;
public ParserContext(string condition) public ParserContext(string condition)
{ {
_template = condition; Template = condition;
Index = -1; Index = -1;
} }
public char Current public char Current
{ {
get { return (Index < _template.Length && Index >= 0) ? _template[Index] : (char)0; } get { return (Index < Template.Length && Index >= 0) ? Template[Index] : (char)0; }
} }
public bool Back() public bool Back()
@ -30,12 +30,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
public bool Next() public bool Next()
{ {
return ++Index < _template.Length; return ++Index < Template.Length;
} }
public bool HasNext() public bool HasNext()
{ {
return (Index + 1) < _template.Length; return (Index + 1) < Template.Length;
} }
public void Mark() public void Mark()
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
// TODO make this return a range rather than a string. // TODO make this return a range rather than a string.
if (_mark.HasValue) if (_mark.HasValue)
{ {
var value = _template.Substring(_mark.Value, Index - _mark.Value); var value = Template.Substring(_mark.Value, Index - _mark.Value);
_mark = null; _mark = null;
return value; return value;
} }

View File

@ -2,10 +2,8 @@
// 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 System.Text;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class Pattern public class Pattern
{ {
@ -15,16 +13,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
PatternSegments = patternSegments; PatternSegments = patternSegments;
} }
public string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
var strBuilder = new StringBuilder();
// TODO consider thread static for string builder - DAVID PERF // TODO consider thread static for string builder - DAVID PERF
foreach (var pattern in PatternSegments) foreach (var pattern in PatternSegments)
{ {
strBuilder.Append(pattern.Evaluate(context, ruleMatch, condMatch)); context.Builder.Append(pattern.Evaluate(context, ruleMatch, condMatch));
} }
return strBuilder.ToString(); var retVal = context.Builder.ToString();
context.Builder.Clear();
return retVal;
} }
} }
} }

View File

@ -2,12 +2,13 @@
// 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.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class PatternSegment public abstract class PatternSegment
{ {
// Match from prevRule, Match from prevCond // Match from prevRule, Match from prevCond
public abstract string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch);
} }
} }

View File

@ -1,9 +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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{ {
public class ConditionMatchSegment : PatternSegment public class ConditionMatchSegment : PatternSegment
{ {
@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Index = index; Index = index;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return condMatch?.BackReference[Index]?.Value; return condMatch?.BackReference[Index]?.Value;
} }

View File

@ -0,0 +1,81 @@
// 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.Globalization;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class DateTimeSegment : PatternSegment
{
private DateTimePortion _portion;
public DateTimeSegment(string segment)
{
switch(segment)
{
case "TIME_YEAR":
_portion = DateTimePortion.Year;
break;
case "TIME_MON":
_portion = DateTimePortion.Month;
break;
case "TIME_DAY":
_portion = DateTimePortion.Day;
break;
case "TIME_HOUR":
_portion = DateTimePortion.Day;
break;
case "TIME_MIN":
_portion = DateTimePortion.Day;
break;
case "TIME_SEC":
_portion = DateTimePortion.Day;
break;
case "TIME_WDAY":
_portion = DateTimePortion.Day;
break;
case "TIME":
_portion = DateTimePortion.Day;
break;
default:
throw new FormatException("Unsupported segment: " + segment);
}
}
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
switch (_portion) {
case DateTimePortion.Year:
return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Month:
return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Day:
return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Hour:
return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Minute:
return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Second:
return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture);
case DateTimePortion.DayOfWeek:
return ((int)DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture);
case DateTimePortion.Time:
return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture);
default:
return string.Empty;
}
}
private enum DateTimePortion {
Year,
Month,
Day,
Hour,
Minute,
Second,
DayOfWeek,
Time
}
}
}

View File

@ -1,9 +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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{ {
public class HeaderSegment : PatternSegment public class HeaderSegment : PatternSegment
{ {
@ -14,9 +12,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Header = header; Header = header;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return context.Request.Headers[Header]; return context.HttpContext.Request.Headers[Header];
} }
} }
} }

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class IsHttpsModSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.IsHttps ? "on" : "off";
}
}
}

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class IsHttpsSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.IsHttps ? "ON" : "OFF";
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Sockets;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class IsIPV6Segment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
if (context.HttpContext.Connection.RemoteIpAddress == null)
{
return "off";
}
return context.HttpContext.Connection.RemoteIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off";
}
}
}

View File

@ -1,9 +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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{ {
public class LiteralSegment : PatternSegment public class LiteralSegment : PatternSegment
{ {
@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Literal = literal; Literal = literal;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return Literal; return Literal;
} }

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class LocalAddressSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Connection.LocalIpAddress?.ToString();
}
}
}

View File

@ -0,0 +1,15 @@
// 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class LocalPortSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Connection.LocalPort.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class QueryStringSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.QueryString.ToString();
}
}
}

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class RemoteAddressSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Connection.RemoteIpAddress?.ToString();
}
}
}

View File

@ -0,0 +1,15 @@
// 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class RemotePortSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Connection.RemotePort.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@ -1,15 +1,13 @@
// 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; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{ {
public class RequestFileNameSegment : PatternSegment public class RequestFileNameSegment : PatternSegment
{ {
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return context.Request.Path; return context.HttpContext.Request.Path;
} }
} }
} }

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class RequestMethodSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.Method;
}
}
}

View File

@ -1,9 +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 Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{ {
public class RuleMatchSegment : PatternSegment public class RuleMatchSegment : PatternSegment
{ {
@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Index = index; Index = index;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return ruleMatch?.BackReference[Index]?.Value; return ruleMatch?.BackReference[Index]?.Value;
} }

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class SchemeSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.Scheme;
}
}
}

View File

@ -0,0 +1,15 @@
// 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.Features;
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class ServerProtocolSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Features.Get<IHttpRequestFeature>()?.Protocol;
}
}
}

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.
using Microsoft.AspNetCore.Http; using System.Text;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class ToLowerSegment : PatternSegment public class ToLowerSegment : PatternSegment
{ {
@ -14,9 +14,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Pattern = pattern; Pattern = pattern;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
// PERF as we share the string builder across the context, we need to make a new one here to evaluate
// lowercase segments.
var tempBuilder = context.Builder;
context.Builder = new StringBuilder(64);
var pattern = Pattern.Evaluate(context, ruleMatch, condMatch); var pattern = Pattern.Evaluate(context, ruleMatch, condMatch);
context.Builder = tempBuilder;
return pattern.ToLowerInvariant(); return pattern.ToLowerInvariant();
} }
} }

View File

@ -1,10 +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.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{ {
public class UrlEncodeSegment : PatternSegment public class UrlEncodeSegment : PatternSegment
{ {
@ -15,9 +15,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
Pattern = pattern; Pattern = pattern;
} }
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
var tempBuilder = context.Builder;
context.Builder = new StringBuilder(64);
var pattern = Pattern.Evaluate(context, ruleMatch, condMatch); var pattern = Pattern.Evaluate(context, ruleMatch, condMatch);
context.Builder = tempBuilder;
return UrlEncoder.Default.Encode(pattern); return UrlEncoder.Default.Encode(pattern);
} }
} }

View File

@ -0,0 +1,13 @@
// 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.PatternSegments
{
public class UrlSegment : PatternSegment
{
public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.HttpContext.Request.Path;
}
}
}

View File

@ -0,0 +1,11 @@

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public abstract class PreAction
{
public abstract void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch);
}
}

View File

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions
{
public class ChangeCookiePreAction : PreAction
{
public ChangeCookiePreAction(string cookie)
{
// TODO
throw new NotImplementedException(cookie);
}
public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
// modify the cookies
}
}
}

View File

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.AspNetCore.Rewrite.Internal.PreActions
{
public class ChangeEnvironmentPreAction : PreAction
{
public ChangeEnvironmentPreAction(string env)
{
// TODO
throw new NotImplementedException();
}
public override void ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
// Do stuff to modify the env
}
}
}

View File

@ -2,12 +2,13 @@
// 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.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class UrlAction public abstract class UrlAction
{ {
public Pattern Url { get; set; } public Pattern Url { get; set; }
public abstract RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch); public abstract RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch);
} }
} }

View File

@ -0,0 +1,16 @@
// 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.UrlActions
{
public class ForbiddenAction : UrlAction
{
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
return RuleResult.ResponseComplete;
}
}
}

View File

@ -0,0 +1,16 @@
// 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.UrlActions
{
public class GoneAction : UrlAction
{
public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{
context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone;
return RuleResult.ResponseComplete;
}
}
}

View File

@ -2,10 +2,9 @@
// 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.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class RedirectAction : UrlAction public class RedirectAction : UrlAction
{ {
@ -16,11 +15,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
Url = pattern; Url = pattern;
} }
public override RuleResult ApplyAction(HttpContext 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.Response.StatusCode = StatusCode; context.HttpContext.Response.StatusCode = StatusCode;
// url can either contain the full url or the path and query // url can either contain the full url or the path and query
// always add to location header. // always add to location header.
@ -28,13 +27,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
var split = pattern.IndexOf('?'); var split = pattern.IndexOf('?');
if (split >= 0) if (split >= 0)
{ {
var query = context.Request.QueryString.Add(new QueryString(pattern.Substring(split))); var query = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
// not using the 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
context.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query; context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
} }
else else
{ {
context.Response.Headers[HeaderNames.Location] = pattern; context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
} }
return RuleResult.ResponseComplete; return RuleResult.ResponseComplete;
} }

View File

@ -2,10 +2,9 @@
// 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.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class RedirectClearQueryAction : UrlAction public class RedirectClearQueryAction : UrlAction
{ {
@ -16,13 +15,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
Url = pattern; Url = pattern;
} }
public override RuleResult ApplyAction(HttpContext 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.Response.StatusCode = StatusCode; context.HttpContext.Response.StatusCode = StatusCode;
// we are clearing the query, so just put the pattern in the location header // we are clearing the query, so just put the pattern in the location header
context.Response.Headers[HeaderNames.Location] = pattern; context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
return RuleResult.ResponseComplete; return RuleResult.ResponseComplete;
} }
} }

View File

@ -4,7 +4,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class RewriteAction : UrlAction public class RewriteAction : UrlAction
{ {
@ -19,28 +19,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
ClearQuery = clearQuery; ClearQuery = clearQuery;
} }
public override RuleResult ApplyAction(HttpContext 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);
if (ClearQuery) if (ClearQuery)
{ {
context.Request.QueryString = new QueryString(); context.HttpContext.Request.QueryString = new QueryString();
} }
// TODO PERF, substrings, object creation, etc. // TODO PERF, substrings, object creation, etc.
if (pattern.IndexOf("://") >= 0) if (pattern.IndexOf("://") >= 0)
{ {
string scheme = null; string scheme = null;
var host = new HostString(); HostString host;
var path = new PathString(); PathString path;
var query = new QueryString(); QueryString query;
var fragment = new FragmentString(); FragmentString fragment;
UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment); UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment);
context.Request.Scheme = scheme; context.HttpContext.Request.Scheme = scheme;
context.Request.Host = host; context.HttpContext.Request.Host = host;
context.Request.Path = path; context.HttpContext.Request.Path = path;
context.Request.QueryString = query.Add(context.Request.QueryString); context.HttpContext.Request.QueryString = query.Add(context.HttpContext.Request.QueryString);
} }
else else
{ {
@ -50,23 +50,23 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
var path = pattern.Substring(0, split); var path = pattern.Substring(0, split);
if (path.StartsWith(ForwardSlash)) if (path.StartsWith(ForwardSlash))
{ {
context.Request.Path = new PathString(path); context.HttpContext.Request.Path = PathString.FromUriComponent(path);
} }
else else
{ {
context.Request.Path = new PathString(ForwardSlash + path); context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + path);
} }
context.Request.QueryString = context.Request.QueryString.Add(new QueryString(pattern.Substring(split))); context.HttpContext.Request.QueryString = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
} }
else else
{ {
if (pattern.StartsWith(ForwardSlash)) if (pattern.StartsWith(ForwardSlash))
{ {
context.Request.Path = new PathString(pattern); context.HttpContext.Request.Path = PathString.FromUriComponent(pattern);
} }
else else
{ {
context.Request.Path = new PathString(ForwardSlash + pattern); context.HttpContext.Request.Path = PathString.FromUriComponent(ForwardSlash + pattern);
} }
} }
} }

View File

@ -2,14 +2,13 @@
// 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.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{ {
public class VoidAction : UrlAction public class VoidAction : UrlAction
{ {
// Explicitly say that nothing happens // Explicitly say that nothing happens
public override RuleResult ApplyAction(HttpContext context, MatchResults ruleMatch, MatchResults condMatch) public override RuleResult ApplyAction(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch)
{ {
return RuleResult.Continue; return RuleResult.Continue;
} }

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public abstract class UrlMatch public abstract class UrlMatch
{ {

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class ExactMatch : UrlMatch public class ExactMatch : UrlMatch
{ {

View File

@ -0,0 +1,19 @@
// 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.UrlMatches
{
public class FileSizeMatch : UrlMatch
{
public FileSizeMatch(bool negate)
{
Negate = negate;
}
public override MatchResults Evaluate(string input, RewriteContext context)
{
var fileInfo = context.FileProvider.GetFileInfo(input);
return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
}
}
}

View File

@ -3,22 +3,20 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IntegerOperand : Operand public class IntegerMatch : UrlMatch
{ {
public int Value { get; } public int Value { get; }
public IntegerOperationType Operation { get; } public IntegerOperationType Operation { get; }
public IntegerOperand(int value, IntegerOperationType operation) public IntegerMatch(int value, IntegerOperationType operation)
{ {
Value = value; Value = value;
Operation = operation; Operation = operation;
} }
public IntegerOperand(string value, IntegerOperationType operation) public IntegerMatch(string value, IntegerOperationType operation)
{ {
int compValue; int compValue;
if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue))
@ -29,27 +27,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
Operation = operation; Operation = operation;
} }
public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider) public override MatchResults Evaluate(string input, RewriteContext context)
{ {
int compValue; int compValue;
if (!int.TryParse(testString, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) if (!int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out compValue))
{ {
return false; return MatchResults.EmptyFailure;
} }
switch (Operation) switch (Operation)
{ {
case IntegerOperationType.Equal: case IntegerOperationType.Equal:
return compValue == Value; return compValue == Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Greater: case IntegerOperationType.Greater:
return compValue > Value; return compValue > Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.GreaterEqual: case IntegerOperationType.GreaterEqual:
return compValue >= Value; return compValue >= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Less: case IntegerOperationType.Less:
return compValue < Value; return compValue < Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.LessEqual: case IntegerOperationType.LessEqual:
return compValue <= Value; return compValue <= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.NotEqual: case IntegerOperationType.NotEqual:
return compValue != Value; return compValue != Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
default: default:
return null; return null;
} }

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public enum IntegerOperationType public enum IntegerOperationType
{ {

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IsDirectoryMatch : UrlMatch public class IsDirectoryMatch : UrlMatch
{ {

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class IsFileMatch : UrlMatch public class IsFileMatch : UrlMatch
{ {

View File

@ -4,7 +4,7 @@
using System; using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public class RegexMatch : UrlMatch public class RegexMatch : UrlMatch
{ {

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.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
public class StringMatch : UrlMatch
{
public string Value { get; set; }
public StringOperationType Operation { get; set; }
public bool IgnoreCase { get; set; }
public StringMatch(string value, StringOperationType operation)
{
Value = value;
Operation = operation;
}
public override MatchResults Evaluate(string input, RewriteContext context)
{
switch (Operation)
{
case StringOperationType.Equal:
return string.Compare(input, Value, IgnoreCase) == 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.Greater:
return string.Compare(input, Value, IgnoreCase) > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.GreaterEqual:
return string.Compare(input, Value, IgnoreCase) >= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.Less:
return string.Compare(input, Value, IgnoreCase) < 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case StringOperationType.LessEqual:
return string.Compare(input, Value, IgnoreCase) <= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
default:
return null;
}
}
}
}

View File

@ -1,7 +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.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{ {
public enum StringOperationType public enum StringOperationType
{ {

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 System.Collections.Generic;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{
public class Conditions
{
public List<Condition> ConditionList { get; set; } = new List<Condition>();
public LogicalGrouping MatchType { get; set; } // default is MatchAll
public bool TrackingAllCaptures { get; set; }
public MatchResults Evaluate(RewriteContext context, MatchResults ruleMatch)
{
MatchResults prevCond = null;
var success = true;
foreach (var condition in ConditionList)
{
var res = condition.Evaluate(context, ruleMatch, prevCond);
success = (MatchType == LogicalGrouping.MatchAll ? (success && res.Success) : (success || res.Success));
prevCond = res;
}
return new MatchResults { Success = success, BackReference = prevCond?.BackReference };
}
}
}

View File

@ -3,12 +3,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments; using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
/// <summary>
/// </summary>
public class InputParser public class InputParser
{ {
private const char Colon = ':'; private const char Colon = ':';

View File

@ -1,15 +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.UrlRewrite.PatternSegments
{
public class IsHttpsSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Request.IsHttps ? "ON" : "OFF";
}
}
}

View File

@ -1,15 +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.UrlRewrite.PatternSegments
{
public class LocalAddressSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Connection.LocalIpAddress?.ToString();
}
}
}

View File

@ -1,15 +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.UrlRewrite.PatternSegments
{
public class QueryStringSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Request.QueryString.ToString();
}
}
}

View File

@ -1,15 +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.UrlRewrite.PatternSegments
{
public class RemoteAddressSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Connection.RemoteIpAddress?.ToString();
}
}
}

View File

@ -1,16 +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.Globalization;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
{
public class RemotePortSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@ -1,15 +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.UrlRewrite.PatternSegments
{
public class UrlSegment : PatternSegment
{
public override string Evaluate(HttpContext context, MatchResults ruleMatch, MatchResults condMatch)
{
return context.Request.Path;
}
}
}

View File

@ -2,7 +2,8 @@
// 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.UrlRewrite.PatternSegments; using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
@ -15,9 +16,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
// TODO Add all server variables here. // TODO Add all server variables here.
case "ALL_RAW": case "ALL_RAW":
throw new NotImplementedException(); throw new NotImplementedException("All-Raw server variable not implemented");
case "APP_POOL_ID": case "APP_POOL_ID":
throw new NotImplementedException(); throw new NotImplementedException("All-Pool-Id server variable not implemented");
case "CONTENT_LENGTH": case "CONTENT_LENGTH":
return new HeaderSegment(HeaderNames.ContentLength); return new HeaderSegment(HeaderNames.ContentLength);
case "CONTENT_TYPE": case "CONTENT_TYPE":
@ -47,7 +48,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
case "REMOTE_ADDR": case "REMOTE_ADDR":
return new RemoteAddressSegment(); return new RemoteAddressSegment();
case "REMOTE_HOST": case "REMOTE_HOST":
throw new NotImplementedException(); throw new NotImplementedException("Remote-Host server variable not implemented");
case "REMOTE_PORT": case "REMOTE_PORT":
return new RemotePortSegment(); return new RemotePortSegment();
case "REQUEST_FILENAME": case "REQUEST_FILENAME":

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
@ -111,8 +112,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax); builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax);
} }
private static void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) private static void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
{ {
if (conditions == null) if (conditions == null)

View File

@ -5,8 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
private UrlMatch _initialMatch; private UrlMatch _initialMatch;
private Conditions _conditions; private Conditions _conditions;
private UrlAction _action; private UrlAction _action;
private bool _matchAny;
public UrlRewriteRule Build() public UrlRewriteRule Build()
{ {
@ -96,10 +98,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
// TODO make this take two overloads and handle regex vs non regex case. // TODO make this take two overloads and handle regex vs non regex case.
public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate) public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate)
{ {
// If there are no conditions specified,
if (_conditions == null) if (_conditions == null)
{ {
AddUrlConditions(LogicalGrouping.MatchAll, trackingAllCaptures: false); AddUrlConditions(LogicalGrouping.MatchAll, trackingAllCaptures: false);
} }
switch (patternSyntax) switch (patternSyntax)
{ {
case PatternSyntax.ECMAScript: case PatternSyntax.ECMAScript:
@ -123,17 +127,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
regex = new Regex(pattern, RegexOptions.Compiled, RegexTimeout); regex = new Regex(pattern, RegexOptions.Compiled, RegexTimeout);
} }
_conditions.ConditionList.Add(new Condition { Input = input, Match = new RegexMatch(regex, negate) }); _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) }); _conditions.ConditionList.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) }); _conditions.ConditionList.Add(new Condition { Input = input, Match = new IsFileMatch(negate), OrNext = _matchAny });
break; break;
} }
default: default:
@ -149,7 +153,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) }); _conditions.ConditionList.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");
@ -160,8 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{ {
var conditions = new Conditions(); var conditions = new Conditions();
conditions.ConditionList = new List<Condition>(); conditions.ConditionList = new List<Condition>();
conditions.MatchType = logicalGrouping; _matchAny = logicalGrouping == LogicalGrouping.MatchAny;
conditions.TrackingAllCaptures = trackingAllCaptures;
_conditions = conditions; _conditions = conditions;
} }
} }

View File

@ -1,7 +1,12 @@
// 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.UrlRewrite using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal
{ {
public class UrlRewriteRule : Rule public class UrlRewriteRule : Rule
{ {
@ -39,7 +44,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
} }
// 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.HttpContext, initMatchRes, condMatchRes); return Action.ApplyAction(context, initMatchRes, condMatchRes);
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Rewrite
var path = Path.Combine(hostingEnv.ContentRootPath, filePath); var path = Path.Combine(hostingEnv.ContentRootPath, filePath);
using (var stream = File.OpenRead(path)) using (var stream = File.OpenRead(path))
{ {
options.Rules.AddRange(FileParser.Parse(new StreamReader(stream))); options.Rules.AddRange(new FileParser().Parse(new StreamReader(stream)));
}; };
return options; return options;
} }
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite
{ {
throw new ArgumentNullException(nameof(reader)); throw new ArgumentNullException(nameof(reader));
} }
options.Rules.AddRange(FileParser.Parse(reader)); options.Rules.AddRange(new FileParser().Parse(reader));
return options; return options;
} }
@ -79,7 +79,8 @@ namespace Microsoft.AspNetCore.Rewrite
throw new ArgumentNullException(nameof(rule)); throw new ArgumentNullException(nameof(rule));
} }
var builder = new RuleBuilder(rule); var builder = new RuleBuilder();
builder.AddRule(rule);
options.Rules.Add(builder.Build()); options.Rules.Add(builder.Build());
return options; return options;
} }

View File

@ -106,6 +106,38 @@ namespace Microsoft.AspNetCore.Rewrite
return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserUnrecognizedParameter"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserUnrecognizedParameter"), p0, p1);
} }
/// <summary>
/// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'.
/// </summary>
internal static string Error_ModRewriteParseError
{
get { return GetString("Error_ModRewriteParseError"); }
}
/// <summary>
/// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'.
/// </summary>
internal static string FormatError_ModRewriteParseError(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteParseError"), p0, p1);
}
/// <summary>
/// Could not parse the mod_rewrite file. Line number '{0}'.
/// </summary>
internal static string Error_ModRewriteGeneralParseError
{
get { return GetString("Error_ModRewriteGeneralParseError"); }
}
/// <summary>
/// Could not parse the mod_rewrite file. Line number '{0}'.
/// </summary>
internal static string FormatError_ModRewriteGeneralParseError(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteGeneralParseError"), p0);
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -135,4 +135,10 @@
<data name="Error_InputParserUnrecognizedParameter" xml:space="preserve"> <data name="Error_InputParserUnrecognizedParameter" xml:space="preserve">
<value>Unrecognized parameter type: '{0}', terminated at string index: '{1}'</value> <value>Unrecognized parameter type: '{0}', terminated at string index: '{1}'</value>
</data> </data>
<data name="Error_ModRewriteParseError" xml:space="preserve">
<value>Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'.</value>
</data>
<data name="Error_ModRewriteGeneralParseError" xml:space="preserve">
<value>Could not parse the mod_rewrite file. Line number '{0}'.</value>
</data>
</root> </root>

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.Text;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@ -13,5 +14,7 @@ namespace Microsoft.AspNetCore.Rewrite
{ {
public HttpContext HttpContext { get; set; } public HttpContext HttpContext { get; set; }
public IFileProvider FileProvider { get; set; } public IFileProvider FileProvider { get; set; }
// PERF: share the same string builder per request
internal StringBuilder Builder { get; set; } = new StringBuilder(64);
} }
} }

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{ {
var results = ConditionPatternParser.ParseActionCondition(condition); var results = ConditionPatternParser.ParseActionCondition(condition);
var expected = new ParsedModRewriteExpression { Operation = operation, Type = conditionType, Operand = variable, Invert = false }; var expected = new ParsedModRewriteInput { OperationType = operation, ConditionType = conditionType, Operand = variable, Invert = false };
Assert.True(CompareConditions(results, expected)); Assert.True(CompareConditions(results, expected));
} }
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
var condition = @"(.*)"; var condition = @"(.*)";
var results = ConditionPatternParser.ParseActionCondition(condition); var results = ConditionPatternParser.ParseActionCondition(condition);
var expected = new ParsedModRewriteExpression { Type = ConditionType.Regex, Operand = "(.*)", Invert = false }; var expected = new ParsedModRewriteInput { ConditionType = ConditionType.Regex, Operand = "(.*)", Invert = false };
Assert.True(CompareConditions(results, expected)); Assert.True(CompareConditions(results, expected));
} }
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{ {
var results = ConditionPatternParser.ParseActionCondition(condition); var results = ConditionPatternParser.ParseActionCondition(condition);
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation , Invert = false }; var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation , Invert = false };
Assert.True(CompareConditions(results, expected)); Assert.True(CompareConditions(results, expected));
} }
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{ {
var results = ConditionPatternParser.ParseActionCondition(condition); var results = ConditionPatternParser.ParseActionCondition(condition);
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = true }; var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = true };
Assert.True(CompareConditions(results, expected)); Assert.True(CompareConditions(results, expected));
} }
@ -79,15 +79,15 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{ {
var results = ConditionPatternParser.ParseActionCondition(condition); var results = ConditionPatternParser.ParseActionCondition(condition);
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = false, Operand = variable }; var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = false, Operand = variable };
Assert.True(CompareConditions(results, expected)); Assert.True(CompareConditions(results, expected));
} }
// TODO negative tests // TODO negative tests
private bool CompareConditions(ParsedModRewriteExpression i1, ParsedModRewriteExpression i2) private bool CompareConditions(ParsedModRewriteInput i1, ParsedModRewriteInput i2)
{ {
if (i1.Operation != i2.Operation || if (i1.OperationType != i2.OperationType ||
i1.Type != i2.Type || i1.ConditionType != i2.ConditionType ||
i1.Operand != i2.Operand || i1.Operand != i2.Operand ||
i1.Invert != i2.Invert) i1.Invert != i2.Invert)
{ {

View File

@ -13,10 +13,10 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public void FlagParser_CheckSingleTerm() public void FlagParser_CheckSingleTerm()
{ {
var results = FlagParser.ParseRuleFlags("[NC]"); var results = FlagParser.Parse("[NC]");
var dict = new Dictionary<RuleFlagType, string>(); var dict = new Dictionary<FlagType, string>();
dict.Add(RuleFlagType.NoCase, string.Empty); dict.Add(FlagType.NoCase, string.Empty);
var expected = new RuleFlags(dict); var expected = new Flags(dict);
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
} }
@ -24,12 +24,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public void FlagParser_CheckManyTerms() public void FlagParser_CheckManyTerms()
{ {
var results = FlagParser.ParseRuleFlags("[NC,F,L]"); var results = FlagParser.Parse("[NC,F,L]");
var dict = new Dictionary<RuleFlagType, string>(); var dict = new Dictionary<FlagType, string>();
dict.Add(RuleFlagType.NoCase, string.Empty); dict.Add(FlagType.NoCase, string.Empty);
dict.Add(RuleFlagType.Forbidden, string.Empty); dict.Add(FlagType.Forbidden, string.Empty);
dict.Add(RuleFlagType.Last, string.Empty); dict.Add(FlagType.Last, string.Empty);
var expected = new RuleFlags(dict); var expected = new Flags(dict);
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
} }
@ -37,12 +37,12 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
[Fact] [Fact]
public void FlagParser_CheckManyTermsWithEquals() public void FlagParser_CheckManyTermsWithEquals()
{ {
var results = FlagParser.ParseRuleFlags("[NC,F,R=301]"); var results = FlagParser.Parse("[NC,F,R=301]");
var dict = new Dictionary<RuleFlagType, string>(); var dict = new Dictionary<FlagType, string>();
dict.Add(RuleFlagType.NoCase, string.Empty); dict.Add(FlagType.NoCase, string.Empty);
dict.Add(RuleFlagType.Forbidden, string.Empty); dict.Add(FlagType.Forbidden, string.Empty);
dict.Add(RuleFlagType.Redirect, "301"); dict.Add(FlagType.Redirect, "301");
var expected = new RuleFlags(dict); var expected = new Flags(dict);
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary)); Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
} }

View File

@ -0,0 +1,38 @@
// 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 Microsoft.AspNetCore.Rewrite.Internal.ModRewrite;
using Xunit;
namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{
public class FormatExceptionTests
{
[Theory]
[InlineData(@"RewriteCond 1 2\", @"Invalid escaper character in string RewriteCond 1 2\")]
[InlineData("BadExpression 1 2 3 4", "Could not parse the mod_rewrite file. Message: 'Too many tokens on line'. Line number '1'.")]
[InlineData("RewriteCond % 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %{ 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %{asdf} 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %z 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond $ 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond $z 2", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 !", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 >", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 >=", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 <", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 <=", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 =", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 -", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 -a", "Could not parse the mod_rewrite file. Line number '1'.")]
[InlineData("RewriteCond %1 -getemp", "Could not parse the mod_rewrite file. Line number '1'.")]
public void ThrowFormatExceptionWithCorrectMessage(string input, string expected)
{
// Arrange, Act, Assert
var ex = Assert.Throws<FormatException>(() => new FileParser().Parse(new StringReader(input)));
Assert.Equal(ex.Message, expected);
}
}
}

Some files were not shown because too many files have changed in this diff Show More